feat: add external evidence receipts

This commit is contained in:
artale 2026-07-01 18:00:48 +02:00
parent 712d94c689
commit 93c9697dbb
5 changed files with 100 additions and 0 deletions

View File

@ -395,6 +395,8 @@ fable-agent plinius godmode "improve explanation quality"
- `--output <path>` - `--output <path>`
- `fable5 falsification` - `fable5 falsification`
- `--output <path>` - `--output <path>`
- `fable5 external-rsi-evidence`
- `--output <path>`
- `fable5 receipt-health` - `fable5 receipt-health`
- `--root <path>` - `--root <path>`
- `--window-minutes <n>` - `--window-minutes <n>`

View File

@ -0,0 +1,32 @@
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { describe, expect, it } from "vitest";
import { createExternalRsiEvidenceReceipt, writeExternalEvidenceReceipt } from "./external-evidence.js";
describe("external evidence receipts", () => {
it("records external RSI proof as advisory, not local deploy authority", () => {
const receipt = createExternalRsiEvidenceReceipt(new Date("2026-07-01T00:00:00.000Z"));
expect(receipt).toMatchObject({
schema: "fable.external_evidence.receipt.v1",
source: "conversation memory citing pi-real-engineering RSI proof",
decision: "advisory",
deployAttempted: false,
});
expect(receipt.evidence.every((entry) => entry.scope === "external")).toBe(true);
expect(receipt.notUsableFor).toContain("local fable-agent deploy authority");
expect(receipt.notUsableFor).toContain("auto_patch_proven=true without fresh local degraded-skill recovery receipt");
expect(receipt.notUsableFor).toContain("8098/deploy-webhook trust");
});
it("writes external evidence receipts", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "external-evidence-"));
const file = path.join(dir, "receipt.json");
writeExternalEvidenceReceipt(file, createExternalRsiEvidenceReceipt());
expect(JSON.parse(fs.readFileSync(file, "utf-8"))).toMatchObject({ schema: "fable.external_evidence.receipt.v1", decision: "advisory" });
fs.rmSync(dir, { recursive: true, force: true });
});
});

View File

@ -0,0 +1,48 @@
import * as fs from "node:fs";
import * as path from "node:path";
export interface ExternalEvidenceReceipt {
schema: "fable.external_evidence.receipt.v1";
createdAt: string;
source: string;
evidence: Array<{ claim: string; reference: string; scope: "external" | "local" }>;
usableFor: string[];
notUsableFor: string[];
decision: "advisory" | "local-authority";
deployAttempted: false;
}
export function createExternalRsiEvidenceReceipt(now = new Date()): ExternalEvidenceReceipt {
return {
schema: "fable.external_evidence.receipt.v1",
createdAt: now.toISOString(),
source: "conversation memory citing pi-real-engineering RSI proof",
evidence: [
{
claim: "hermes rsi_canary recovered after repair",
reference: "pi-real-engineering:/tmp/rsi-proof-20260619T093724Z.md",
scope: "external",
},
{
claim: "worker_skill_repair.sh copied skill_health.py and verified rsi_canary",
reference: "pi-real-engineering:/tmp/rsi-proof-20260619T093724Z.md",
scope: "external",
},
],
usableFor: ["advisory RSI recovery history", "future local proof import checklist", "red-team/safety context"],
notUsableFor: [
"local fable-agent deploy authority",
"auto_patch_proven=true without fresh local degraded-skill recovery receipt",
"8098/deploy-webhook trust",
"container count truth without live reconciliation",
],
decision: "advisory",
deployAttempted: false,
};
}
export function writeExternalEvidenceReceipt(file: string, receipt: ExternalEvidenceReceipt): string {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `${JSON.stringify(receipt, null, 2)}\n`);
return file;
}

View File

@ -72,6 +72,9 @@ export type { StaleMemoryClaim, StaleMemoryQuarantineReceipt, StaleMemorySeverit
export { createFactoryMemoryFalsificationReceipt, createFalsificationReceipt, writeFalsificationReceipt } from "./falsification-receipt.js"; export { createFactoryMemoryFalsificationReceipt, createFalsificationReceipt, writeFalsificationReceipt } from "./falsification-receipt.js";
export type { FalsificationCase, FalsificationReceipt } from "./falsification-receipt.js"; export type { FalsificationCase, FalsificationReceipt } from "./falsification-receipt.js";
export { createExternalRsiEvidenceReceipt, writeExternalEvidenceReceipt } from "./external-evidence.js";
export type { ExternalEvidenceReceipt } from "./external-evidence.js";
export { channelPath, emitChannelEvent, readChannelEvents } from "./channel.js"; export { channelPath, emitChannelEvent, readChannelEvents } from "./channel.js";
export type { ChannelEvent, ChannelEventSource, ChannelEventType, NewChannelEvent } from "./channel.js"; export type { ChannelEvent, ChannelEventSource, ChannelEventType, NewChannelEvent } from "./channel.js";

View File

@ -2335,6 +2335,21 @@ fable
if (receipt.decision === "failed") process.exit(1); if (receipt.decision === "failed") process.exit(1);
}); });
fable
.command("external-rsi-evidence")
.description("Write advisory external RSI evidence receipt without local deploy authority")
.option("--output <path>", "Receipt output path", path.join(".fable", "evidence", "external-rsi-evidence-live.json"))
.action(async (opts: { output?: string }) => {
const { createExternalRsiEvidenceReceipt, writeExternalEvidenceReceipt } = await import("./fable5/external-evidence.js");
const receipt = createExternalRsiEvidenceReceipt();
const out = opts.output ?? path.join(".fable", "evidence", "external-rsi-evidence-live.json");
writeExternalEvidenceReceipt(out, receipt);
console.log(JSON.stringify(receipt, null, 2));
console.log(`
Receipt: ${out}
`);
});
fable fable
.command("receipt-health") .command("receipt-health")
.description("Check receipt consumer health over a recent time window") .description("Check receipt consumer health over a recent time window")