feat: add receipt evidence metadata
This commit is contained in:
parent
98486d3a55
commit
975ef28b15
|
|
@ -54,6 +54,9 @@ describe("rsi reconcile", () => {
|
|||
expect(receipt.facts.cron_present).toBe(true);
|
||||
expect(receipt.facts.latest_score).toBe("65/65 (100%)");
|
||||
expect(receipt.decision).toBe("healthy_but_autopatch_unproven");
|
||||
expect(receipt.evidence.deploy_target).toBe("8099/deploy");
|
||||
expect(receipt.evidence.rsi_healthy).toBe(false);
|
||||
expect(receipt.rubric.format).toBe(5);
|
||||
});
|
||||
|
||||
it("marks healthy when auto-patch evidence exists", () => {
|
||||
|
|
@ -61,6 +64,8 @@ describe("rsi reconcile", () => {
|
|||
|
||||
expect(receipt.facts.auto_patch_proven).toBe(true);
|
||||
expect(receipt.decision).toBe("healthy");
|
||||
expect(receipt.evidence.auto_patch_proven).toBe(true);
|
||||
expect(receipt.evidence.rsi_healthy).toBe(true);
|
||||
});
|
||||
|
||||
it("marks healthy when a fresh structured canary proof receipt exists", () => {
|
||||
|
|
|
|||
|
|
@ -22,10 +22,28 @@ export interface RsiCanaryProofReceipt {
|
|||
status_after: 0;
|
||||
}
|
||||
|
||||
export interface ReceiptRubric {
|
||||
format: number;
|
||||
factuality: number;
|
||||
consistency: number;
|
||||
realism: number;
|
||||
quality: number;
|
||||
}
|
||||
|
||||
export interface RsiReceiptEvidence {
|
||||
cron_present: boolean;
|
||||
rsi_healthy: boolean;
|
||||
deploy_target: "8099/deploy";
|
||||
auto_patch_proven: boolean;
|
||||
receipt_id: string;
|
||||
}
|
||||
|
||||
export interface RsiReconcileReceipt {
|
||||
created_at: string;
|
||||
host: string;
|
||||
port: number;
|
||||
evidence: RsiReceiptEvidence;
|
||||
rubric: ReceiptRubric;
|
||||
checks: Array<{ name: string; ok: boolean; status: number | null; stdout: string; stderr: string }>;
|
||||
facts: {
|
||||
cron_present: boolean;
|
||||
|
|
@ -62,13 +80,23 @@ export function reconcileRsi(opts: RsiReconcileOptions): RsiReconcileReceipt {
|
|||
};
|
||||
// ponytail: cron name drift is a warning; live health + watcher + deploy route are the gate.
|
||||
const coreHealthy = facts.skill_health_present && /100%|65\/65|66\/66/.test(facts.latest_score ?? "") && facts.factory_watcher_active && facts.deploy_route_present;
|
||||
const createdAt = (opts.now ?? new Date()).toISOString();
|
||||
const decision = coreHealthy ? (facts.auto_patch_proven ? "healthy" : "healthy_but_autopatch_unproven") : "degraded";
|
||||
const receipt: RsiReconcileReceipt = {
|
||||
created_at: (opts.now ?? new Date()).toISOString(),
|
||||
created_at: createdAt,
|
||||
host: opts.host,
|
||||
port,
|
||||
evidence: {
|
||||
cron_present: facts.cron_present,
|
||||
rsi_healthy: decision === "healthy",
|
||||
deploy_target: "8099/deploy",
|
||||
auto_patch_proven: facts.auto_patch_proven,
|
||||
receipt_id: `rsi:${opts.host}:${createdAt}`,
|
||||
},
|
||||
rubric: receiptRubric(decision === "healthy"),
|
||||
checks,
|
||||
facts,
|
||||
decision: coreHealthy ? (facts.auto_patch_proven ? "healthy" : "healthy_but_autopatch_unproven") : "degraded",
|
||||
decision,
|
||||
};
|
||||
if (opts.out) writeRsiReconcileReceipt(opts.out, receipt);
|
||||
return receipt;
|
||||
|
|
@ -117,3 +145,13 @@ function runCommand(command: string, args: string[]) {
|
|||
const result = spawnSync(command, args, { encoding: "utf-8" });
|
||||
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
||||
}
|
||||
|
||||
function receiptRubric(ok: boolean): ReceiptRubric {
|
||||
return {
|
||||
format: 5,
|
||||
factuality: ok ? 5 : 3,
|
||||
consistency: ok ? 5 : 3,
|
||||
realism: 4,
|
||||
quality: ok ? 5 : 3,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,11 @@ describe("verifyCycle", () => {
|
|||
expect(receipt.commit_sha).toBe("abc123");
|
||||
expect(receipt.changed_files).toEqual([" M src/file.ts"]);
|
||||
expect(receipt.rsi.checks.find((c) => c.name === "deploy_route")?.ok).toBe(true);
|
||||
expect(JSON.parse(fs.readFileSync(out, "utf-8")).result).toBe("passed");
|
||||
const written = JSON.parse(fs.readFileSync(out, "utf-8"));
|
||||
expect(written.result).toBe("passed");
|
||||
expect(written.evidence.deploy_target).toBe("8099/deploy");
|
||||
expect(written.evidence.verify_cycle_passed).toBe(true);
|
||||
expect(written.rubric.format).toBe(5);
|
||||
});
|
||||
|
||||
it("passes reconciled stale factory only with explicit opt-in and canary proof", async () => {
|
||||
|
|
@ -100,6 +104,7 @@ describe("verifyCycle", () => {
|
|||
const receipt = await verifyCycle({ host: "127.0.0.1", skill: "rsi_canary", allowDirty: true, factoryCheck: async () => factoryOk, runner, rsiRunner: unprovenRunner });
|
||||
expect(receipt.rsi.decision).toBe("healthy_but_autopatch_unproven");
|
||||
expect(receipt.result).toBe("failed");
|
||||
expect(receipt.evidence.verify_cycle_passed).toBe(false);
|
||||
});
|
||||
|
||||
it("fails dirty worktrees unless explicitly allowed", async () => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|||
import * as path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { checkFactory, factoryGateReady, type FactoryCheckResult } from "./factory-status.js";
|
||||
import { reconcileRsi, type RsiReconcileOptions, type RsiReconcileReceipt } from "./rsi-reconcile.js";
|
||||
import { reconcileRsi, type ReceiptRubric, type RsiReconcileOptions, type RsiReconcileReceipt } from "./rsi-reconcile.js";
|
||||
|
||||
export interface VerifyCycleOptions {
|
||||
host: string;
|
||||
|
|
@ -18,11 +18,23 @@ export interface VerifyCycleOptions {
|
|||
runner?: (command: string, args: string[]) => { status: number | null; stdout: string; stderr: string };
|
||||
}
|
||||
|
||||
export interface VerifyCycleEvidence {
|
||||
container_count?: number;
|
||||
factory_ok: boolean;
|
||||
cron_present: boolean;
|
||||
rsi_healthy: boolean;
|
||||
deploy_target: "8099/deploy";
|
||||
verify_cycle_passed: boolean;
|
||||
receipt_id: string;
|
||||
}
|
||||
|
||||
export interface VerifyCycleReceipt {
|
||||
created_at: string;
|
||||
commit_sha: string;
|
||||
skill: string;
|
||||
deploy: "8099/deploy gated";
|
||||
evidence: VerifyCycleEvidence;
|
||||
rubric: ReceiptRubric;
|
||||
factory_ready: boolean;
|
||||
factory: FactoryCheckResult;
|
||||
rsi: RsiReconcileReceipt;
|
||||
|
|
@ -56,6 +68,16 @@ export async function verifyCycle(opts: VerifyCycleOptions): Promise<VerifyCycle
|
|||
commit_sha: commit,
|
||||
skill,
|
||||
deploy: "8099/deploy gated",
|
||||
evidence: {
|
||||
container_count: factory.factory.containers,
|
||||
factory_ok: factoryReady,
|
||||
cron_present: rsi.facts.cron_present,
|
||||
rsi_healthy: rsi.decision === "healthy",
|
||||
deploy_target: "8099/deploy",
|
||||
verify_cycle_passed: passed,
|
||||
receipt_id: `verify-cycle:${commit}:${created}`,
|
||||
},
|
||||
rubric: receiptRubric(passed),
|
||||
factory_ready: factoryReady,
|
||||
factory,
|
||||
rsi,
|
||||
|
|
@ -99,3 +121,13 @@ function runCommand(command: string, args: string[]) {
|
|||
const result = spawnSync(command, args, { encoding: "utf-8" });
|
||||
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
||||
}
|
||||
|
||||
function receiptRubric(ok: boolean): ReceiptRubric {
|
||||
return {
|
||||
format: 5,
|
||||
factuality: ok ? 5 : 3,
|
||||
consistency: ok ? 5 : 3,
|
||||
realism: 4,
|
||||
quality: ok ? 5 : 3,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue