Que un auditor pueda rastrear cualquier claim hasta (a) la ejecución exacta del agente que lo generó —con integridad referencial DB-enforced— y (b) el documento fuente, vía un grafo de linaje real y poblado, no teórico.
GAP 2 (Wave 1, barato): emitClaim deja de delegar las aristas al caller y las escribe él mismo, transaccionalmente, desde source_refs tipados; + backfill. Desbloquea el eje claim→documento. Ya estaba medio resuelto: publishArtifact escribe edges; faltaba el de claims.
GAP 1 (Wave 2, caro): hoy emitClaim corre antes de que exista la fila step_executions → se reordena el ciclo de vida del step (crearla al inicio con su id) para atar el claim por FK compuesta a la PK de la tabla particionada; + backfill + VALIDATE.
vitest) documentados y verificados.emitClaim ocurre antes de su recordStepExecution → justifica el reorder de Task 5.step_executions(id, trace_started_at), no trigger.cd apps/api && npx vitest run.source_refs Wave 1 GAP 2EmitClaimInput) · test claims.test.tsvitest run src/substrate/claims.test.ts PASS, con caso legacy string[] y caso {id,type}.tsc --noEmit sin errores nuevos.normalizeSourceRefs(['abc']) → [{id:'abc',type:'claim'}].SourceRef + normalizeSourceRefs + ampliar EmitClaimInput.emitClaim escribe lineage_edges atómico Wave 1 GAP 2emitClaim) · test claims.test.tsemitClaim con 2 source_refs → 2 filas en lineage_edges (from_type='claim').sql.begin).sql.begin; loop sobre source_refs insertando edges; borrar "handled by the caller".ON CONFLICT DO NOTHING), re-ejecutable sin duplicar.from_type='claim' = suma de source_refs.source_refs legacy sin tipo se asumen claim.INSERT … SELECT … LATERAL jsonb_array_elements ….startStepExecution devuelve {step_execution_id, trace_started_at} y crea la fila running.finishStepExecution la cierra a succeeded por PK.execute-plan.ts llama start antes del primer emitClaim; sin regresión global.RETURNING id, trace_started_at; finish con UPDATE … WHERE id=$1 AND trace_started_at=$2.step_execution_id a emitClaim Wave 2 GAP 1emitClaim con step_execution_id lo persiste en claims.step_execution_id.startStepExecution.vitest run global PASS.EmitClaimInput.provenance + INSERT + call-sites.claims con step_execution_id + step_exec_started_at (nullable) y FK compuesta a step_executions(id, trace_started_at) ON DELETE RESTRICT NOT VALID.step_execution_id inexistente → falla (FK activa en filas nuevas aun NOT VALID).ALTER TABLE claims ADD COLUMN … ADD CONSTRAINT … NOT VALID.(trace_id, step_id) → step_executions eligiendo la succeeded más cercana a asserted_at; 0 o >1 candidatos → NULL + reporte.VALIDATE CONSTRAINT claims_step_exec_fk corre sin error.DISTINCT ON + cercanía a asserted_at) + migración con VALIDATE.actor_resolved + step exacto vía FK; Eje 2 (recursivo) llega al artifact doc con su content_addr.Si tras la Task 1 el reorder del step (Task 5) resulta más invasivo de lo estimado, la Wave 2 (GAP 1) puede diferirse y entregar solo la Wave 1 (GAP 2) — que ya cierra la trazabilidad-al-documento, el 80% del valor para el auditor.
El GAP 1 (integridad DB-enforced del salto claim→step) es el 20% restante y se justifica recién cuando exista auditoría contractual que lo exija.