From 22ad8ff5b5777fbd9d70668aa52361e99b387d38 Mon Sep 17 00:00:00 2001 From: Vibe Nuage Agent Date: Sat, 31 Oct 2026 00:54:32 +0000 Subject: [PATCH] feat(cli): add se-trace recursive to surface recursive evidence chains (RFC-0002) Co-authored-by: emmanuel-a-otchere --- README.md | 5 ++++- src/cli/se-trace.ts | 34 ++++++++++++++++++++++++++++++++++ src/cots.ts | 13 ++++++++++++- tests/tools.test.ts | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5ed1c4..a88a638 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ se-trace validate examples/legal-summarization.cots.json # Surface the Layer 2 (Runtime Validation) record of a COTS (RFC-0001) se-trace validate-runtime examples/legal-summarization.cots.json + +# Walk the evidence chains and surface recursive (model-generated) items (RFC-0002) +se-trace recursive examples/legal-summarization.cots.json ``` ## What is in this repository @@ -36,7 +39,7 @@ se-trace validate-runtime examples/legal-summarization.cots.json - `src/inspect.ts` - the inspect command. Prints a human-readable summary of a COTS. - `src/cite.ts` - the cite command. Prints the evidence chain for a specific decision. - `src/visualize.ts` - the visualize command. Renders a COTS as a Mermaid diagram + HTML page. -- `src/cli/se-trace.ts` - the CLI entry point, including `validate-runtime` (Layer 2 ValidationFlags surfacing, RFC-0001). +- `src/cli/se-trace.ts` - the CLI entry point, including `validate-runtime` (RFC-0001) and `recursive` (RFC-0002 chain-walking). - `tests/tools.test.ts` - the test suite. ## SDKs diff --git a/src/cli/se-trace.ts b/src/cli/se-trace.ts index 722b2ad..cf4ef02 100644 --- a/src/cli/se-trace.ts +++ b/src/cli/se-trace.ts @@ -103,6 +103,40 @@ program } }); +program + .command("recursive") + .description("Walk the evidence chains and surface recursive (model-generated) items (RFC-0002)") + .argument("", "Path to a COTS JSON file") + .action((path: string) => { + const cots = loadCots(path); + const termination = cots.evidence.chain_termination; + console.log(`chain_termination: ${termination?.policy ?? "terminal (default; RFC-0002 fields absent)"}`); + if (termination?.policy === "bounded_recursion") { + console.log(`max_levels: ${termination.max_levels}`); + } + let recursive = 0; + for (const chain of cots.evidence.evidence_chain) { + for (const item of [...chain.supporting_evidence, ...chain.contradicting_evidence]) { + if (item.source_type === "model-generated") { + recursive++; + console.log(`\n[RECURSIVE] ${item.id}`); + console.log(` claim: ${item.claim_id}`); + console.log(` source: ${item.source_id} (model-generated; the chain does not terminate at a human-readable source)`); + } + } + } + if (recursive === 0) { + console.log("\nNo model-generated evidence items. All chains terminate at their declared sources."); + } else { + console.log(`\nRecursive items: ${recursive}`); + const policy = termination?.policy ?? "terminal"; + if (policy === "terminal") { + console.log("The declared (or default) terminal policy is violated: model-generated items are not admitted."); + process.exit(1); + } + } + }); + program .command("inspect") .description("Print a human-readable summary of a COTS") diff --git a/src/cots.ts b/src/cots.ts index f07aa1e..d729840 100644 --- a/src/cots.ts +++ b/src/cots.ts @@ -15,7 +15,7 @@ export interface Cots { inputs: { source_artifacts: SourceArtifact[] }; context: Context; decisions: DecisionRecord[]; - evidence: { evidence_chain: EvidenceChainEntry[] }; + evidence: { chain_termination?: ChainTermination; evidence_chain: EvidenceChainEntry[] }; validation: { feedback_surfaces: FeedbackSurface[] }; } @@ -105,6 +105,15 @@ export interface Claim { contradicting_evidence: URI[]; } +export type SourceType = "human-authored" | "human-curated" | "model-generated" | "mixed"; + +export type ChainTerminationPolicy = "terminal" | "bounded_recursion" | "unbounded_recursion"; + +export interface ChainTermination { + policy: ChainTerminationPolicy; + max_levels?: number; +} + export interface EvidenceItem { id: URI; claim_id: URI; @@ -112,6 +121,8 @@ export interface EvidenceItem { transformation: { operation: string; inputs: string[]; output: string; recorded_at: ISO8601 }; uncertainty: { calibrated_score: number; basis: string }; polarity: string; + /** RFC-0002 (COTS schema v1.2.0). Optional. */ + source_type?: SourceType; } export interface FeedbackSurface { diff --git a/tests/tools.test.ts b/tests/tools.test.ts index 6dd43f0..61fdc5f 100644 --- a/tests/tools.test.ts +++ b/tests/tools.test.ts @@ -106,3 +106,39 @@ describe("validate-runtime", () => { expect(cots.decisions[0].validation).toBeUndefined(); }); }); + +describe("recursive (RFC-0002)", () => { + it("surfaces model-generated evidence items and the declared policy", () => { + const cots = loadFixture("legal-summarization.cots.json"); + cots.evidence.chain_termination = { policy: "bounded_recursion", max_levels: 1 }; + cots.evidence.evidence_chain[0].supporting_evidence[0].source_type = "model-generated"; + const { execFileSync } = require("node:child_process"); + const { writeFileSync, rmSync } = require("node:fs"); + const tmp = resolve(__dirname, "..", "examples", "tmp-recursive.cots.json"); + writeFileSync(tmp, JSON.stringify(cots)); + try { + const out = execFileSync("node", [resolve(__dirname, "..", "dist", "cli", "se-trace.js"), "recursive", tmp], { encoding: "utf-8" }); + expect(out).toContain("chain_termination: bounded_recursion"); + expect(out).toContain("[RECURSIVE]"); + expect(out).toContain("Recursive items: 1"); + } finally { + rmSync(tmp, { force: true }); + } + }); + + it("reports a clean chain when all evidence is human-authored", () => { + const cots = loadFixture("legal-summarization.cots.json"); + cots.evidence.chain_termination = { policy: "terminal" }; + cots.evidence.evidence_chain[0].supporting_evidence[0].source_type = "human-authored"; + const { execFileSync } = require("node:child_process"); + const { writeFileSync, rmSync } = require("node:fs"); + const tmp = resolve(__dirname, "..", "examples", "tmp-recursive-clean.cots.json"); + writeFileSync(tmp, JSON.stringify(cots)); + try { + const out = execFileSync("node", [resolve(__dirname, "..", "dist", "cli", "se-trace.js"), "recursive", tmp], { encoding: "utf-8" }); + expect(out).toContain("No model-generated evidence items"); + } finally { + rmSync(tmp, { force: true }); + } + }); +});