feat: Auswertung der Sprachnotizen mit Claude (T7 L4 Tasks 14, 15)
Task 14: Port StructuredModel mit Anthropic-Adapter (@anthropic-ai/sdk 0.131.0 exakt, Lieferkette geprüft), fester Endpunkt https://api.anthropic.com (Umgebungs-Header werden entfernt), feste Systemregeln ohne Datum/Volk/Person, Transkript als Daten in Tags, strukturierte Ausgabe per JSON-Schema mit effort low, serverseitiger Fallback per VOICE_SERVER_FALLBACK abschaltbar. Jeder Vorschlag wird geprüft: bekanntes Feld, Wert in den Formulargrenzen, Zitat steht wörtlich im Gesagten (Dezimalzahl als ein Wort), Korrektur gewinnt. Netzwächter: nur die Worker-Verdrahtung erreicht den Adapter (transitiv geprüft). Task 15: Worker-Schleife voice-extractions (LISTEN + 15-s-Abfrage, ein Lauf zugleich, höchstens 20 Notizen je Lauf), Modellaufruf außerhalb jeder Transaktion mit 240-s-Obergrenze, Ergebnis versiegelt mit Ereignis nur bei gehaltenem Claim; ohne Schlüssel EXTRACT_DISABLED ohne Versuch, Tageslimit zählt jeden bezahlten Aufruf über das neue Journal VoiceExtractionCall (nur IDs, Modell, Tokens, Ergebnis; 90 Tage, nicht im Backup). Nur der Worker-Container erhält ANTHROPIC_API_KEY aus dem Secret apicore-anthropic (optional); API- und Job-Rollen warnen, wenn der Schlüssel bei ihnen gesetzt ist. Gates: Backend lint, typecheck, 4589 Unit-Tests, build; e2e 104 Suites (nur der Wetter-Test gegen die echte API schwankt); Manifest-Policy 55. Refs #27 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
89a1546f15
commit
6a915aad9c
38 files changed
+3612
-33
No files matched your search
@@ -1,11 +1,11 @@
|
||||
# Sprachnotizen (Spec T7 11.1 und 11.3): der CronJob, der Ton im Cluster
|
||||
# abschreibt. Rollout: nach backend-roles (gleiche Backend-Rollen, gleiches
|
||||
# Secret) und erst, wenn VOICE_ENCRYPTION_KEY in apicore-backend-secret steht.
|
||||
#
|
||||
# Zurueckgestellt (Controller-Entscheid 9): patch-worker-voice.yaml mit
|
||||
# ANTHROPIC_API_KEY (secretKeyRef apicore-anthropic, optional) im Worker. Es
|
||||
# wird erst angelegt, wenn die Claude-Auswertung freigegeben ist.
|
||||
# patch-worker-voice.yaml: ANTHROPIC_API_KEY nur im Worker (Secret apicore-anthropic,
|
||||
# optional).
|
||||
apiVersion: kustomize.config.k8s.io/v1alpha1
|
||||
kind: Component
|
||||
resources:
|
||||
- cronjob-voice-transcribe.yaml
|
||||
patches:
|
||||
- path: patch-worker-voice.yaml
|
||||
@@ -0,0 +1,20 @@
|
||||
# Claude-Auswertung der Sprachnotizen (Spec T7 6.4, 11.3): nur der Worker-
|
||||
# Container bekommt den Schluessel, als einzelne Variable (kein envFrom, kein
|
||||
# Volume), Muster apicore-telegram. Das Secret legt der Inhaber an; fehlt es,
|
||||
# startet der Worker trotzdem und die Auswertung bleibt aus (EXTRACT_DISABLED).
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: apicore-worker
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: apicore-worker
|
||||
env:
|
||||
- name: ANTHROPIC_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: apicore-anthropic
|
||||
key: ANTHROPIC_API_KEY
|
||||
optional: true
|
||||
@@ -690,16 +690,39 @@ test('the voice CronJob is not matched by the shared job patch and has exactly f
|
||||
}
|
||||
});
|
||||
|
||||
// The Claude extraction is on hold (no worker patch yet). Until it lands, no pod
|
||||
// at all may carry the key; once the worker patch exists the worker is exempt.
|
||||
// Claude extraction (spec T7 6.4, 11.3): only the worker's main container carries
|
||||
// the key, as one optional secretKeyRef (no envFrom, no volume, no other secret
|
||||
// reference to apicore-anthropic).
|
||||
for (const [label, model] of [['target', target], ...stages]) {
|
||||
test(`${label}: ${ANTHROPIC_KEY} appears only in the worker (no other pod references it)`, () => {
|
||||
const hasKey = label === 'target' || label === 'step6-voice';
|
||||
test(`${label}: ${ANTHROPIC_KEY} reaches only the worker container through an optional secretKeyRef`, () => {
|
||||
for (const pod of model.pods) {
|
||||
if (pod.labels.app === 'apicore-worker') continue;
|
||||
const isWorker = pod.labels.app === 'apicore-worker';
|
||||
for (const c of [...pod.containers, ...pod.initContainers]) {
|
||||
assert.ok(!(c.env ?? []).some((e) => e.name === ANTHROPIC_KEY), `${pod.owner}/${c.name} defines ${ANTHROPIC_KEY}`);
|
||||
const entries = (c.env ?? []).filter((e) => e.name === ANTHROPIC_KEY);
|
||||
if (isWorker && c.name === 'apicore-worker' && hasKey) {
|
||||
assert.equal(entries.length, 1, `${c.name} must define ${ANTHROPIC_KEY} exactly once`);
|
||||
assert.deepEqual(
|
||||
entries[0].valueFrom,
|
||||
{ secretKeyRef: { name: ANTHROPIC_SECRET, key: ANTHROPIC_KEY, optional: true } },
|
||||
`${c.name}: ${ANTHROPIC_KEY} must be an optional secretKeyRef to ${ANTHROPIC_SECRET}`,
|
||||
);
|
||||
assert.equal(entries[0].value, undefined, `${c.name}: ${ANTHROPIC_KEY} has a literal value`);
|
||||
} else if (hasKey || !isWorker) {
|
||||
assert.equal(entries.length, 0, `${pod.owner}/${c.name} defines ${ANTHROPIC_KEY}`);
|
||||
}
|
||||
const viaEnvFrom = (c.envFrom ?? []).some((f) => f.secretRef?.name === ANTHROPIC_SECRET);
|
||||
assert.ok(!viaEnvFrom, `${pod.owner}/${c.name} takes ${ANTHROPIC_SECRET} through envFrom`);
|
||||
}
|
||||
const refs = secretReferences(pod, ANTHROPIC_SECRET);
|
||||
if (isWorker && hasKey) {
|
||||
assert.deepEqual(refs.map((r) => r.container), ['apicore-worker'], 'only the worker main container may reference the secret');
|
||||
} else if (!isWorker) {
|
||||
assert.deepEqual(refs, [], `${pod.owner} must not use secret ${ANTHROPIC_SECRET}`);
|
||||
}
|
||||
for (const volume of pod.spec.volumes ?? []) {
|
||||
assert.notEqual(volume.secret?.secretName, ANTHROPIC_SECRET, `${pod.owner} mounts ${ANTHROPIC_SECRET}`);
|
||||
}
|
||||
assert.deepEqual(secretReferences(pod, ANTHROPIC_SECRET), [], `${pod.owner} must not use secret ${ANTHROPIC_SECRET}`);
|
||||
}
|
||||
for (const doc of model.docs.filter((d) => d.kind === 'ConfigMap')) {
|
||||
assert.ok(!(ANTHROPIC_KEY in (doc.data ?? {})), `${doc.metadata.name} holds ${ANTHROPIC_KEY}`);
|
||||
|
||||
Reference in new issue
Block a user