Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,17 @@ 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

- `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
Expand Down
34 changes: 34 additions & 0 deletions src/cli/se-trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,40 @@ program
}
});

program
.command("recursive")
.description("Walk the evidence chains and surface recursive (model-generated) items (RFC-0002)")
.argument("<path>", "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")
Expand Down
13 changes: 12 additions & 1 deletion src/cots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] };
}

Expand Down Expand Up @@ -105,13 +105,24 @@ 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;
source_id: URI;
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 {
Expand Down
36 changes: 36 additions & 0 deletions tests/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
});
});
Loading