open-multi-agent-kit 0.90.7 → 0.90.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +17 -10
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +5 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/doctor-provider-cli.d.ts +33 -0
- package/dist/commands/doctor-provider-cli.d.ts.map +1 -0
- package/dist/commands/doctor-provider-cli.js +215 -0
- package/dist/commands/doctor-provider-cli.js.map +1 -0
- package/dist/commands/doctor-provider-transport.d.ts +35 -0
- package/dist/commands/doctor-provider-transport.d.ts.map +1 -0
- package/dist/commands/doctor-provider-transport.js +185 -0
- package/dist/commands/doctor-provider-transport.js.map +1 -0
- package/dist/commands/doctor-provider.d.ts +121 -7
- package/dist/commands/doctor-provider.d.ts.map +1 -1
- package/dist/commands/doctor-provider.js +1295 -102
- package/dist/commands/doctor-provider.js.map +1 -1
- package/dist/commands/session-doctor-cli.d.ts +43 -0
- package/dist/commands/session-doctor-cli.d.ts.map +1 -0
- package/dist/commands/session-doctor-cli.js +494 -0
- package/dist/commands/session-doctor-cli.js.map +1 -0
- package/dist/commands/verify-ci.d.ts +21 -0
- package/dist/commands/verify-ci.d.ts.map +1 -0
- package/dist/commands/verify-ci.js +66 -0
- package/dist/commands/verify-ci.js.map +1 -0
- package/dist/core/agent-session-services.d.ts +3 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +116 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1015 -71
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-tool-settings.d.ts +35 -0
- package/dist/core/agent-tool-settings.d.ts.map +1 -0
- package/dist/core/agent-tool-settings.js +155 -0
- package/dist/core/agent-tool-settings.js.map +1 -0
- package/dist/core/atomic-session-file.d.ts +14 -0
- package/dist/core/atomic-session-file.d.ts.map +1 -0
- package/dist/core/atomic-session-file.js +104 -0
- package/dist/core/atomic-session-file.js.map +1 -0
- package/dist/core/compaction/compaction.d.ts +6 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +12 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/hysteresis.d.ts +27 -0
- package/dist/core/compaction/hysteresis.d.ts.map +1 -0
- package/dist/core/compaction/hysteresis.js +84 -0
- package/dist/core/compaction/hysteresis.js.map +1 -0
- package/dist/core/compaction/index.d.ts +2 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +2 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/transaction.d.ts +172 -0
- package/dist/core/compaction/transaction.d.ts.map +1 -0
- package/dist/core/compaction/transaction.js +824 -0
- package/dist/core/compaction/transaction.js.map +1 -0
- package/dist/core/context-budget-governor-v2.d.ts +2 -1
- package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
- package/dist/core/context-budget-governor-v2.js +2 -1
- package/dist/core/context-budget-governor-v2.js.map +1 -1
- package/dist/core/context-budget-headroom.d.ts.map +1 -1
- package/dist/core/context-budget-headroom.js +5 -1
- package/dist/core/context-budget-headroom.js.map +1 -1
- package/dist/core/context-budget-reserved-tokens.d.ts +110 -0
- package/dist/core/context-budget-reserved-tokens.d.ts.map +1 -0
- package/dist/core/context-budget-reserved-tokens.js +188 -0
- package/dist/core/context-budget-reserved-tokens.js.map +1 -0
- package/dist/core/context-budget-v2-cache-invalidation.d.ts +133 -0
- package/dist/core/context-budget-v2-cache-invalidation.d.ts.map +1 -0
- package/dist/core/context-budget-v2-cache-invalidation.js +452 -0
- package/dist/core/context-budget-v2-cache-invalidation.js.map +1 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-keys.js +6 -0
- package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-cache-provider.d.ts +8 -2
- package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-provider.js +28 -5
- package/dist/core/context-budget-v2-cache-provider.js.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.js +1 -0
- package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
- package/dist/core/context-budget-v2-planner.js +1 -0
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
- package/dist/core/context-budget-v2-selection.js +9 -0
- package/dist/core/context-budget-v2-selection.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +4 -0
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/durable-file-directory.d.ts +4 -0
- package/dist/core/durable-file-directory.d.ts.map +1 -0
- package/dist/core/durable-file-directory.js +76 -0
- package/dist/core/durable-file-directory.js.map +1 -0
- package/dist/core/durable-file-identity.d.ts +26 -0
- package/dist/core/durable-file-identity.d.ts.map +1 -0
- package/dist/core/durable-file-identity.js +118 -0
- package/dist/core/durable-file-identity.js.map +1 -0
- package/dist/core/durable-file-io.d.ts +29 -0
- package/dist/core/durable-file-io.d.ts.map +1 -0
- package/dist/core/durable-file-io.js +179 -0
- package/dist/core/durable-file-io.js.map +1 -0
- package/dist/core/durable-file-lock-cleanup.d.ts +12 -0
- package/dist/core/durable-file-lock-cleanup.d.ts.map +1 -0
- package/dist/core/durable-file-lock-cleanup.js +39 -0
- package/dist/core/durable-file-lock-cleanup.js.map +1 -0
- package/dist/core/durable-file-lock-owner.d.ts +18 -0
- package/dist/core/durable-file-lock-owner.d.ts.map +1 -0
- package/dist/core/durable-file-lock-owner.js +78 -0
- package/dist/core/durable-file-lock-owner.js.map +1 -0
- package/dist/core/durable-file-lock-storage.d.ts +35 -0
- package/dist/core/durable-file-lock-storage.d.ts.map +1 -0
- package/dist/core/durable-file-lock-storage.js +263 -0
- package/dist/core/durable-file-lock-storage.js.map +1 -0
- package/dist/core/durable-file-mode.d.ts +8 -0
- package/dist/core/durable-file-mode.d.ts.map +1 -0
- package/dist/core/durable-file-mode.js +41 -0
- package/dist/core/durable-file-mode.js.map +1 -0
- package/dist/core/extensions/loader.d.ts +12 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +50 -37
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/index.d.ts +6 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/loop-gates.d.ts.map +1 -1
- package/dist/core/loop-gates.js +25 -1
- package/dist/core/loop-gates.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/pi-package-intake.d.ts +1 -0
- package/dist/core/pi-package-intake.d.ts.map +1 -1
- package/dist/core/pi-package-intake.js +7 -0
- package/dist/core/pi-package-intake.js.map +1 -1
- package/dist/core/redaction.d.ts +8 -0
- package/dist/core/redaction.d.ts.map +1 -0
- package/dist/core/redaction.js +31 -0
- package/dist/core/redaction.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +36 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/run-journal-store.d.ts +53 -0
- package/dist/core/run-journal-store.d.ts.map +1 -0
- package/dist/core/run-journal-store.js +278 -0
- package/dist/core/run-journal-store.js.map +1 -0
- package/dist/core/run-journal.d.ts +172 -0
- package/dist/core/run-journal.d.ts.map +1 -0
- package/dist/core/run-journal.js +1053 -0
- package/dist/core/run-journal.js.map +1 -0
- package/dist/core/sdk.d.ts +7 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +94 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-doctor-plan.d.ts +128 -0
- package/dist/core/session-doctor-plan.d.ts.map +1 -0
- package/dist/core/session-doctor-plan.js +833 -0
- package/dist/core/session-doctor-plan.js.map +1 -0
- package/dist/core/session-file-compaction.d.ts +6 -0
- package/dist/core/session-file-compaction.d.ts.map +1 -0
- package/dist/core/session-file-compaction.js +169 -0
- package/dist/core/session-file-compaction.js.map +1 -0
- package/dist/core/session-file-head.d.ts +10 -0
- package/dist/core/session-file-head.d.ts.map +1 -0
- package/dist/core/session-file-head.js +78 -0
- package/dist/core/session-file-head.js.map +1 -0
- package/dist/core/session-file-persistence.d.ts +30 -0
- package/dist/core/session-file-persistence.d.ts.map +1 -0
- package/dist/core/session-file-persistence.js +106 -0
- package/dist/core/session-file-persistence.js.map +1 -0
- package/dist/core/session-integrity.d.ts +43 -0
- package/dist/core/session-integrity.d.ts.map +1 -0
- package/dist/core/session-integrity.js +420 -0
- package/dist/core/session-integrity.js.map +1 -0
- package/dist/core/session-manager.d.ts +24 -4
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +242 -125
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-owner-lease.d.ts +22 -0
- package/dist/core/session-owner-lease.d.ts.map +1 -0
- package/dist/core/session-owner-lease.js +176 -0
- package/dist/core/session-owner-lease.js.map +1 -0
- package/dist/core/session-path-policy.d.ts +132 -0
- package/dist/core/session-path-policy.d.ts.map +1 -0
- package/dist/core/session-path-policy.js +780 -0
- package/dist/core/session-path-policy.js.map +1 -0
- package/dist/core/session-repair-plan.d.ts +49 -0
- package/dist/core/session-repair-plan.d.ts.map +1 -0
- package/dist/core/session-repair-plan.js +202 -0
- package/dist/core/session-repair-plan.js.map +1 -0
- package/dist/core/session-termination.d.ts +100 -0
- package/dist/core/session-termination.d.ts.map +1 -0
- package/dist/core/session-termination.js +403 -0
- package/dist/core/session-termination.js.map +1 -0
- package/dist/core/settings-manager.d.ts +32 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +52 -5
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/verified-bash-adapter.d.ts +36 -0
- package/dist/core/verified-bash-adapter.d.ts.map +1 -0
- package/dist/core/verified-bash-adapter.js +90 -0
- package/dist/core/verified-bash-adapter.js.map +1 -0
- package/dist/guardrails/command-redaction.d.ts +32 -0
- package/dist/guardrails/command-redaction.d.ts.map +1 -0
- package/dist/guardrails/command-redaction.js +776 -0
- package/dist/guardrails/command-redaction.js.map +1 -0
- package/dist/guardrails/evidence-attestation.d.ts +31 -0
- package/dist/guardrails/evidence-attestation.d.ts.map +1 -0
- package/dist/guardrails/evidence-attestation.js +178 -0
- package/dist/guardrails/evidence-attestation.js.map +1 -0
- package/dist/guardrails/evidence-receipt-store.d.ts +33 -0
- package/dist/guardrails/evidence-receipt-store.d.ts.map +1 -0
- package/dist/guardrails/evidence-receipt-store.js +446 -0
- package/dist/guardrails/evidence-receipt-store.js.map +1 -0
- package/dist/guardrails/evidence-receipt.d.ts +58 -0
- package/dist/guardrails/evidence-receipt.d.ts.map +1 -0
- package/dist/guardrails/evidence-receipt.js +389 -0
- package/dist/guardrails/evidence-receipt.js.map +1 -0
- package/dist/guardrails/evidence-system.d.ts +40 -9
- package/dist/guardrails/evidence-system.d.ts.map +1 -1
- package/dist/guardrails/evidence-system.js +379 -83
- package/dist/guardrails/evidence-system.js.map +1 -1
- package/dist/guardrails/replay-ledger-io.d.ts +6 -0
- package/dist/guardrails/replay-ledger-io.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-io.js +42 -0
- package/dist/guardrails/replay-ledger-io.js.map +1 -0
- package/dist/guardrails/replay-ledger-lock-owner.d.ts +32 -0
- package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-lock-owner.js +138 -0
- package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -0
- package/dist/guardrails/replay-ledger-lock.d.ts +24 -0
- package/dist/guardrails/replay-ledger-lock.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-lock.js +135 -0
- package/dist/guardrails/replay-ledger-lock.js.map +1 -0
- package/dist/guardrails/replay-ledger-mutation-gate.d.ts +24 -0
- package/dist/guardrails/replay-ledger-mutation-gate.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-mutation-gate.js +205 -0
- package/dist/guardrails/replay-ledger-mutation-gate.js.map +1 -0
- package/dist/guardrails/replay-ledger-quarantine.d.ts +2 -0
- package/dist/guardrails/replay-ledger-quarantine.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-quarantine.js +19 -0
- package/dist/guardrails/replay-ledger-quarantine.js.map +1 -0
- package/dist/guardrails/replay-ledger-store.d.ts +32 -0
- package/dist/guardrails/replay-ledger-store.d.ts.map +1 -0
- package/dist/guardrails/replay-ledger-store.js +211 -0
- package/dist/guardrails/replay-ledger-store.js.map +1 -0
- package/dist/guardrails/shell-command-name-glob.d.ts +3 -0
- package/dist/guardrails/shell-command-name-glob.d.ts.map +1 -0
- package/dist/guardrails/shell-command-name-glob.js +233 -0
- package/dist/guardrails/shell-command-name-glob.js.map +1 -0
- package/dist/guardrails/shell-command-option.d.ts +7 -0
- package/dist/guardrails/shell-command-option.d.ts.map +1 -0
- package/dist/guardrails/shell-command-option.js +24 -0
- package/dist/guardrails/shell-command-option.js.map +1 -0
- package/dist/guardrails/shell-command-tokenizer.d.ts +29 -0
- package/dist/guardrails/shell-command-tokenizer.d.ts.map +1 -0
- package/dist/guardrails/shell-command-tokenizer.js +145 -0
- package/dist/guardrails/shell-command-tokenizer.js.map +1 -0
- package/dist/guardrails/shell-command-wrapper.d.ts +18 -0
- package/dist/guardrails/shell-command-wrapper.d.ts.map +1 -0
- package/dist/guardrails/shell-command-wrapper.js +42 -0
- package/dist/guardrails/shell-command-wrapper.js.map +1 -0
- package/dist/guardrails/verified-executor.d.ts +65 -0
- package/dist/guardrails/verified-executor.d.ts.map +1 -0
- package/dist/guardrails/verified-executor.js +171 -0
- package/dist/guardrails/verified-executor.js.map +1 -0
- package/dist/guardrails/workspace-fingerprint.d.ts +23 -0
- package/dist/guardrails/workspace-fingerprint.d.ts.map +1 -0
- package/dist/guardrails/workspace-fingerprint.js +634 -0
- package/dist/guardrails/workspace-fingerprint.js.map +1 -0
- package/dist/index.d.ts +15 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +12 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/control-panel-layout.d.ts.map +1 -1
- package/dist/modes/interactive/components/control-panel-layout.js +1 -1
- package/dist/modes/interactive/components/control-panel-layout.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +59 -9
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +43 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +23 -5
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +3 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/types/evidence.d.ts +215 -1
- package/dist/types/evidence.d.ts.map +1 -1
- package/dist/types/evidence.js.map +1 -1
- package/dist/verify-ci.d.ts +3 -0
- package/dist/verify-ci.d.ts.map +1 -0
- package/dist/verify-ci.js +30 -0
- package/dist/verify-ci.js.map +1 -0
- package/docs/compaction.md +44 -4
- package/docs/extensions.md +2 -0
- package/docs/providers.md +6 -0
- package/docs/sdk.md +238 -1
- package/docs/settings.md +67 -4
- package/docs/usage.md +38 -0
- package/examples/extensions/correctness-wall/README.md +6 -6
- package/examples/extensions/correctness-wall/index.ts +37 -11
- package/examples/extensions/custom-provider-anthropic/package-lock.json +5 -5
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +4 -4
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
// allow: SIZE_OK -
|
|
1
|
+
// allow: SIZE_OK - evidence ledger guardrails: tamper-evident replay chain + fail-closed parsing.
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
+
import { redactCommandDescriptor } from "./command-redaction.js";
|
|
6
|
+
import { latestRelevantWorkspaceMutationSeq, parseVerifiedLedgerSnapshot, replayPayloadMatches, verifyCommandAttestation, verifyWorkspaceBinding, } from "./evidence-attestation.js";
|
|
7
|
+
export { latestRelevantWorkspaceMutationSeq } from "./evidence-attestation.js";
|
|
8
|
+
import { computeEvidenceCommandSha256, constantTimeSha256Equal, evidenceReceiptReplayPayload, parseSha256Hex, validateEvidenceReceipt, } from "./evidence-receipt.js";
|
|
9
|
+
import { ReplayLedgerStore, replayLedgerHeadsEqual } from "./replay-ledger-store.js";
|
|
5
10
|
function sha256(input) {
|
|
6
11
|
return createHash("sha256").update(input).digest("hex");
|
|
7
12
|
}
|
|
@@ -66,97 +71,370 @@ export class TaskContractBuilder {
|
|
|
66
71
|
this.updatedAt = new Date().toISOString();
|
|
67
72
|
}
|
|
68
73
|
build() {
|
|
69
|
-
return {
|
|
74
|
+
return structuredClone({
|
|
70
75
|
goalId: this.goalId,
|
|
71
76
|
completionClaim: this.completionClaim,
|
|
72
|
-
requiredEvidence:
|
|
77
|
+
requiredEvidence: this.requiredEvidence,
|
|
73
78
|
finalRisk: this.finalRisk,
|
|
74
79
|
verdict: this.verdict,
|
|
75
80
|
createdAt: this.createdAt,
|
|
76
81
|
updatedAt: this.updatedAt,
|
|
77
|
-
};
|
|
82
|
+
});
|
|
78
83
|
}
|
|
84
|
+
/** Parse and validate a serialized TaskContract. Fails closed on any shape violation. */
|
|
79
85
|
static fromJSON(json) {
|
|
80
|
-
|
|
86
|
+
const raw = JSON.parse(json);
|
|
87
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
88
|
+
throw new Error("TaskContract JSON must be an object");
|
|
89
|
+
}
|
|
90
|
+
const { goalId, completionClaim, finalRisk, verdict, createdAt, updatedAt, requiredEvidence } = raw;
|
|
91
|
+
if (typeof goalId !== "string" ||
|
|
92
|
+
typeof completionClaim !== "string" ||
|
|
93
|
+
typeof finalRisk !== "string" ||
|
|
94
|
+
typeof createdAt !== "string" ||
|
|
95
|
+
typeof updatedAt !== "string") {
|
|
96
|
+
throw new Error("TaskContract JSON has missing or mistyped string fields");
|
|
97
|
+
}
|
|
98
|
+
if (verdict !== "pass" && verdict !== "fail" && verdict !== "conditional") {
|
|
99
|
+
throw new Error(`TaskContract verdict must be pass|fail|conditional, got ${String(verdict)}`);
|
|
100
|
+
}
|
|
101
|
+
if (!Array.isArray(requiredEvidence)) {
|
|
102
|
+
throw new Error("TaskContract requiredEvidence must be an array");
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
goalId,
|
|
106
|
+
completionClaim,
|
|
107
|
+
finalRisk,
|
|
108
|
+
verdict,
|
|
109
|
+
createdAt,
|
|
110
|
+
updatedAt,
|
|
111
|
+
requiredEvidence: requiredEvidence.map((item, index) => parseEvidenceItem(item, index)),
|
|
112
|
+
};
|
|
81
113
|
}
|
|
82
114
|
static toJSON(contract) {
|
|
83
115
|
return JSON.stringify(contract, null, 2);
|
|
84
116
|
}
|
|
85
117
|
}
|
|
118
|
+
const EVIDENCE_STATUSES = new Set(["pending", "gathering", "satisfied", "failed", "waived"]);
|
|
119
|
+
function optionalStringField(value, field, index) {
|
|
120
|
+
if (value !== undefined && typeof value !== "string") {
|
|
121
|
+
throw new Error(`TaskContract evidence[${index}].${field} must be a string when present`);
|
|
122
|
+
}
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
function parseEvidenceItem(raw, index) {
|
|
126
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
127
|
+
throw new Error(`TaskContract evidence[${index}] must be an object`);
|
|
128
|
+
}
|
|
129
|
+
const item = raw;
|
|
130
|
+
const { claim, category, timestamp, status } = item;
|
|
131
|
+
if (typeof claim !== "string" || typeof category !== "string" || typeof timestamp !== "string") {
|
|
132
|
+
throw new Error(`TaskContract evidence[${index}] has missing or mistyped fields`);
|
|
133
|
+
}
|
|
134
|
+
if (typeof status !== "string" || !EVIDENCE_STATUSES.has(status)) {
|
|
135
|
+
throw new Error(`TaskContract evidence[${index}] has invalid status ${String(status)}`);
|
|
136
|
+
}
|
|
137
|
+
const receiptSchemaVersion = item.receiptSchemaVersion;
|
|
138
|
+
if (receiptSchemaVersion !== undefined && receiptSchemaVersion !== 3) {
|
|
139
|
+
throw new Error(`TaskContract evidence[${index}].receiptSchemaVersion must be 3 when present`);
|
|
140
|
+
}
|
|
141
|
+
const receiptCommandSha256 = optionalStringField(item.receiptCommandSha256, "receiptCommandSha256", index);
|
|
142
|
+
return {
|
|
143
|
+
claim,
|
|
144
|
+
category: category,
|
|
145
|
+
timestamp,
|
|
146
|
+
status: status,
|
|
147
|
+
artifactPath: optionalStringField(item.artifactPath, "artifactPath", index),
|
|
148
|
+
verificationCommand: optionalStringField(item.verificationCommand, "verificationCommand", index),
|
|
149
|
+
hash: optionalStringField(item.hash, "hash", index),
|
|
150
|
+
receiptId: optionalStringField(item.receiptId, "receiptId", index),
|
|
151
|
+
receiptSchemaVersion,
|
|
152
|
+
...(receiptCommandSha256 !== undefined
|
|
153
|
+
? { receiptCommandSha256: parseSha256Hex(receiptCommandSha256, "receiptCommandSha256") }
|
|
154
|
+
: {}),
|
|
155
|
+
receiptLaneId: optionalStringField(item.receiptLaneId, "receiptLaneId", index),
|
|
156
|
+
gapReason: optionalStringField(item.gapReason, "gapReason", index),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
86
159
|
// ============================================================================
|
|
87
160
|
// ReplayLedger
|
|
88
161
|
// ============================================================================
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
162
|
+
/** prevHash sentinel for the first event in a replay chain. */
|
|
163
|
+
const GENESIS_HASH = "genesis";
|
|
164
|
+
function computeEventHash(event) {
|
|
165
|
+
return sha256(JSON.stringify([
|
|
166
|
+
event.seq,
|
|
167
|
+
event.type,
|
|
168
|
+
event.timestamp,
|
|
169
|
+
event.goalId,
|
|
170
|
+
event.laneId ?? null,
|
|
171
|
+
event.payloadHash,
|
|
172
|
+
event.prevHash,
|
|
173
|
+
]));
|
|
174
|
+
}
|
|
175
|
+
function parseReplayEventLine(line, lineNumber) {
|
|
176
|
+
let raw;
|
|
177
|
+
try {
|
|
178
|
+
raw = JSON.parse(line);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
throw new Error(`Replay ledger corrupted at line ${lineNumber}: invalid JSON`);
|
|
182
|
+
}
|
|
183
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
184
|
+
throw new Error(`Replay ledger corrupted at line ${lineNumber}: event must be an object`);
|
|
185
|
+
}
|
|
186
|
+
const event = raw;
|
|
187
|
+
const { seq, type, timestamp, goalId, laneId, payloadHash, prevHash, eventHash } = event;
|
|
188
|
+
if (typeof seq !== "number" ||
|
|
189
|
+
typeof type !== "string" ||
|
|
190
|
+
typeof timestamp !== "string" ||
|
|
191
|
+
typeof goalId !== "string" ||
|
|
192
|
+
typeof payloadHash !== "string" ||
|
|
193
|
+
typeof prevHash !== "string" ||
|
|
194
|
+
typeof eventHash !== "string" ||
|
|
195
|
+
(laneId !== undefined && typeof laneId !== "string") ||
|
|
196
|
+
!("payload" in event)) {
|
|
197
|
+
throw new Error(`Replay ledger corrupted at line ${lineNumber}: event fields are missing or mistyped`);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
seq,
|
|
201
|
+
type: type,
|
|
202
|
+
timestamp,
|
|
203
|
+
goalId,
|
|
204
|
+
laneId,
|
|
205
|
+
payload: event.payload,
|
|
206
|
+
payloadHash,
|
|
207
|
+
prevHash,
|
|
208
|
+
eventHash,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function parseReplayLedgerBytes(bytes, goalId) {
|
|
212
|
+
const lines = bytes
|
|
213
|
+
.toString("utf8")
|
|
214
|
+
.split("\n")
|
|
215
|
+
.filter((line) => line.trim().length > 0);
|
|
216
|
+
const events = [];
|
|
217
|
+
let prevHash = GENESIS_HASH;
|
|
218
|
+
for (let index = 0; index < lines.length; index++) {
|
|
219
|
+
const event = parseReplayEventLine(lines[index], index + 1);
|
|
220
|
+
if (event.goalId !== goalId)
|
|
221
|
+
throw new Error(`Replay ledger goal mismatch at line ${index + 1}`);
|
|
222
|
+
if (event.seq !== index + 1) {
|
|
223
|
+
throw new Error(`Replay ledger corrupted at line ${index + 1}: expected seq ${index + 1}, got ${event.seq}`);
|
|
224
|
+
}
|
|
225
|
+
if (event.prevHash !== prevHash)
|
|
226
|
+
throw new Error(`Replay ledger chain broken at line ${index + 1}`);
|
|
227
|
+
if (sha256(JSON.stringify(event.payload)) !== event.payloadHash) {
|
|
228
|
+
throw new Error(`Replay ledger payload tampered at line ${index + 1} (seq ${event.seq})`);
|
|
103
229
|
}
|
|
230
|
+
if (computeEventHash(event) !== event.eventHash) {
|
|
231
|
+
throw new Error(`Replay ledger event hash mismatch at line ${index + 1} (seq ${event.seq})`);
|
|
232
|
+
}
|
|
233
|
+
prevHash = event.eventHash;
|
|
234
|
+
events.push(event);
|
|
104
235
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
236
|
+
return events;
|
|
237
|
+
}
|
|
238
|
+
export class ReplayLedgerManager {
|
|
239
|
+
goalId;
|
|
240
|
+
ledgerPath;
|
|
241
|
+
store;
|
|
242
|
+
events = [];
|
|
243
|
+
committedHead;
|
|
244
|
+
constructor(goalId, ledgerPath, expectedHead) {
|
|
245
|
+
this.goalId = goalId;
|
|
246
|
+
this.ledgerPath = ledgerPath;
|
|
247
|
+
this.committedHead = { fileIdentity: null, size: 0, lastSeq: 0, lastHash: GENESIS_HASH };
|
|
248
|
+
this.store = new ReplayLedgerStore(ledgerPath, (bytes) => {
|
|
249
|
+
const events = parseReplayLedgerBytes(bytes, goalId);
|
|
250
|
+
const last = events.at(-1);
|
|
251
|
+
return { lastSeq: last?.seq ?? 0, lastHash: last?.eventHash ?? GENESIS_HASH };
|
|
252
|
+
});
|
|
253
|
+
this.refresh(expectedHead);
|
|
254
|
+
}
|
|
255
|
+
refresh(expectedHead) {
|
|
256
|
+
const snapshot = this.store.load(expectedHead);
|
|
257
|
+
this.events = parseReplayLedgerBytes(snapshot.bytes, this.goalId);
|
|
258
|
+
this.committedHead = snapshot.head;
|
|
259
|
+
}
|
|
260
|
+
append(event, expectedHead = this.committedHead) {
|
|
261
|
+
if (event.goalId !== this.goalId)
|
|
262
|
+
throw new Error("Replay event goalId does not match the ledger");
|
|
263
|
+
if (!replayLedgerHeadsEqual(expectedHead, this.committedHead)) {
|
|
264
|
+
throw new Error("Replay manager expected-head CAS is stale");
|
|
265
|
+
}
|
|
266
|
+
const payloadHash = sha256(JSON.stringify(event.payload));
|
|
267
|
+
const chained = {
|
|
108
268
|
...event,
|
|
109
|
-
seq:
|
|
269
|
+
seq: expectedHead.lastSeq + 1,
|
|
110
270
|
timestamp: new Date().toISOString(),
|
|
111
|
-
payloadHash
|
|
271
|
+
payloadHash,
|
|
272
|
+
prevHash: expectedHead.lastHash,
|
|
112
273
|
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const events = content
|
|
132
|
-
.split("\n")
|
|
133
|
-
.filter((line) => line.trim().length > 0)
|
|
134
|
-
.map((line) => JSON.parse(line));
|
|
135
|
-
this.ledger.events = events;
|
|
136
|
-
this.ledger.lastPersistedSeq = events.length > 0 ? events[events.length - 1].seq : 0;
|
|
137
|
-
this.nextSeq = this.ledger.lastPersistedSeq + 1;
|
|
274
|
+
const fullEvent = { ...chained, eventHash: computeEventHash(chained) };
|
|
275
|
+
this.committedHead = this.store.append(Buffer.from(`${JSON.stringify(fullEvent)}\n`, "utf8"), fullEvent.seq, fullEvent.eventHash, expectedHead);
|
|
276
|
+
this.events.push(fullEvent);
|
|
277
|
+
return structuredClone(fullEvent);
|
|
278
|
+
}
|
|
279
|
+
/** Compatibility no-op that still verifies the caller's expected committed head. */
|
|
280
|
+
persist(expectedHead = this.committedHead) {
|
|
281
|
+
this.getVerifiedSnapshot(expectedHead);
|
|
282
|
+
}
|
|
283
|
+
load(expectedHead = this.committedHead) {
|
|
284
|
+
this.refresh(expectedHead);
|
|
285
|
+
}
|
|
286
|
+
getCommittedHead() {
|
|
287
|
+
return structuredClone(this.committedHead);
|
|
288
|
+
}
|
|
289
|
+
getVerifiedSnapshot(expectedHead = this.committedHead) {
|
|
290
|
+
this.refresh(expectedHead);
|
|
291
|
+
return structuredClone({ events: this.events, head: this.committedHead });
|
|
138
292
|
}
|
|
139
293
|
getEvents() {
|
|
140
|
-
return this.
|
|
294
|
+
return structuredClone(this.events);
|
|
141
295
|
}
|
|
142
296
|
getLedger() {
|
|
143
|
-
return {
|
|
297
|
+
return structuredClone({
|
|
298
|
+
goalId: this.goalId,
|
|
299
|
+
events: this.events,
|
|
300
|
+
ledgerPath: this.ledgerPath,
|
|
301
|
+
lastPersistedSeq: this.committedHead.lastSeq,
|
|
302
|
+
});
|
|
144
303
|
}
|
|
145
304
|
replay(handler) {
|
|
146
|
-
|
|
147
|
-
for (const event of this.ledger.events) {
|
|
148
|
-
const result = handler(event);
|
|
149
|
-
if (result !== undefined) {
|
|
150
|
-
results.push(result);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return results;
|
|
305
|
+
return this.events.map(handler).filter((result) => result !== undefined);
|
|
154
306
|
}
|
|
155
307
|
exportToFile(path) {
|
|
156
308
|
ensureDir(path);
|
|
157
|
-
writeFileSync(path, JSON.stringify(this.
|
|
309
|
+
writeFileSync(path, JSON.stringify(this.events, null, 2), "utf8");
|
|
158
310
|
}
|
|
159
311
|
}
|
|
312
|
+
function receiptCheckIssue(message, hard) {
|
|
313
|
+
return { message, hard };
|
|
314
|
+
}
|
|
315
|
+
function receiptIssue(item, contract, options) {
|
|
316
|
+
if (!item.receiptId) {
|
|
317
|
+
return receiptCheckIssue(`Execution receipt is missing for evidence "${item.claim}".`, false);
|
|
318
|
+
}
|
|
319
|
+
const strict = options.receiptMode === "strict";
|
|
320
|
+
if (item.receiptSchemaVersion !== 3 && (item.receiptSchemaVersion !== undefined || strict)) {
|
|
321
|
+
return receiptCheckIssue(`Execution receipt schema metadata is invalid for evidence "${item.claim}".`, true);
|
|
322
|
+
}
|
|
323
|
+
if (!options.resolveReceipt) {
|
|
324
|
+
return receiptCheckIssue(`Execution receipt resolver is missing for evidence "${item.claim}".`, false);
|
|
325
|
+
}
|
|
326
|
+
let rawReceipt;
|
|
327
|
+
try {
|
|
328
|
+
rawReceipt = options.resolveReceipt(item.receiptId);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return receiptCheckIssue(`Execution receipt resolver failed for evidence "${item.claim}".`, true);
|
|
332
|
+
}
|
|
333
|
+
if (rawReceipt === undefined) {
|
|
334
|
+
return receiptCheckIssue(`Execution receipt is missing for evidence "${item.claim}".`, false);
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const receipt = validateEvidenceReceipt(rawReceipt);
|
|
338
|
+
if (receipt.core.receiptId !== item.receiptId) {
|
|
339
|
+
return receiptCheckIssue(`Execution receipt ID does not match evidence "${item.claim}".`, true);
|
|
340
|
+
}
|
|
341
|
+
if (receipt.core.goalId !== contract.goalId) {
|
|
342
|
+
return receiptCheckIssue(`Execution receipt goal does not match evidence "${item.claim}".`, true);
|
|
343
|
+
}
|
|
344
|
+
if (receipt.core.claim !== item.claim) {
|
|
345
|
+
return receiptCheckIssue(`Execution receipt claim does not match evidence "${item.claim}".`, true);
|
|
346
|
+
}
|
|
347
|
+
if (receipt.core.status !== "passed" || receipt.core.exitCode !== 0) {
|
|
348
|
+
return receiptCheckIssue(`Execution receipt for evidence "${item.claim}" did not pass with exit code 0.`, true);
|
|
349
|
+
}
|
|
350
|
+
if (item.receiptCommandSha256 === undefined) {
|
|
351
|
+
return receiptCheckIssue(`Execution receipt command binding is missing for evidence "${item.claim}".`, false);
|
|
352
|
+
}
|
|
353
|
+
const receiptCommandSha256 = computeEvidenceCommandSha256(receipt.core.command);
|
|
354
|
+
if (!constantTimeSha256Equal(item.receiptCommandSha256, receiptCommandSha256)) {
|
|
355
|
+
return receiptCheckIssue(`Execution receipt command does not match evidence "${item.claim}".`, true);
|
|
356
|
+
}
|
|
357
|
+
const attestationIssue = verifyCommandAttestation({
|
|
358
|
+
receiptId: receipt.core.receiptId,
|
|
359
|
+
binding: receipt.core.commandBinding,
|
|
360
|
+
persistedCommand: receipt.core.command,
|
|
361
|
+
persistedSummary: receipt.core.commandRedaction,
|
|
362
|
+
binder: options.commandAttestationBinder,
|
|
363
|
+
resolveCommand: options.resolveAttestedCommand,
|
|
364
|
+
redact: redactCommandDescriptor,
|
|
365
|
+
});
|
|
366
|
+
if (attestationIssue !== undefined) {
|
|
367
|
+
return receiptCheckIssue(`Execution receipt command attestation ${attestationIssue.detail} for evidence "${item.claim}".`, attestationIssue.hard);
|
|
368
|
+
}
|
|
369
|
+
if (receipt.core.laneId !== undefined && item.receiptLaneId === undefined) {
|
|
370
|
+
return receiptCheckIssue(`Execution receipt lane binding is missing for evidence "${item.claim}".`, false);
|
|
371
|
+
}
|
|
372
|
+
if (item.receiptLaneId !== receipt.core.laneId) {
|
|
373
|
+
return receiptCheckIssue(`Execution receipt lane does not match evidence "${item.claim}".`, true);
|
|
374
|
+
}
|
|
375
|
+
const workspaceIssue = verifyWorkspaceBinding(receipt.core.workspaceAfter, options.captureWorkspaceFingerprint);
|
|
376
|
+
if (workspaceIssue !== undefined) {
|
|
377
|
+
return receiptCheckIssue(`Execution receipt ${workspaceIssue.detail} for evidence "${item.claim}".`, workspaceIssue.hard);
|
|
378
|
+
}
|
|
379
|
+
let snapshot;
|
|
380
|
+
if (options.resolveVerifiedLedgerSnapshot !== undefined) {
|
|
381
|
+
try {
|
|
382
|
+
snapshot = parseVerifiedLedgerSnapshot(options.resolveVerifiedLedgerSnapshot(), contract.goalId, parseReplayLedgerBytes);
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return receiptCheckIssue(`Execution receipt verified ledger snapshot failed for evidence "${item.claim}".`, true);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
else if (strict) {
|
|
389
|
+
return receiptCheckIssue(`Execution receipt verified ledger snapshot is missing for evidence "${item.claim}".`, false);
|
|
390
|
+
}
|
|
391
|
+
let latestMutationSeq = snapshot === undefined
|
|
392
|
+
? null
|
|
393
|
+
: latestRelevantWorkspaceMutationSeq(snapshot.events, receipt.core.workspaceAfter.scope);
|
|
394
|
+
if (snapshot === undefined && options.resolveLatestWorkspaceMutationSeq !== undefined) {
|
|
395
|
+
let resolved;
|
|
396
|
+
try {
|
|
397
|
+
resolved = options.resolveLatestWorkspaceMutationSeq(receipt.core.workspaceAfter.scope);
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
return receiptCheckIssue(`Execution receipt workspace-mutation freshness source failed for evidence "${item.claim}".`, true);
|
|
401
|
+
}
|
|
402
|
+
if (resolved !== null && resolved !== undefined) {
|
|
403
|
+
if (!Number.isSafeInteger(resolved) || resolved <= 0) {
|
|
404
|
+
return receiptCheckIssue(`Execution receipt workspace-mutation freshness source is malformed for evidence "${item.claim}".`, true);
|
|
405
|
+
}
|
|
406
|
+
latestMutationSeq = resolved;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const binding = receipt.envelope.ledgerBinding;
|
|
410
|
+
if (!binding) {
|
|
411
|
+
return receiptCheckIssue(`Execution receipt ledger binding is missing for evidence "${item.claim}".`, latestMutationSeq !== null);
|
|
412
|
+
}
|
|
413
|
+
const event = snapshot
|
|
414
|
+
? snapshot.events.find((candidate) => candidate.seq === binding.seq)
|
|
415
|
+
: options.resolveLedgerEvent?.(binding.seq);
|
|
416
|
+
if (!event && snapshot === undefined && !options.resolveLedgerEvent) {
|
|
417
|
+
return receiptCheckIssue(`Execution receipt ledger resolver is missing for evidence "${item.claim}".`, latestMutationSeq !== null);
|
|
418
|
+
}
|
|
419
|
+
const replayPayload = evidenceReceiptReplayPayload(receipt);
|
|
420
|
+
if (!event ||
|
|
421
|
+
event.seq !== binding.seq ||
|
|
422
|
+
event.type !== "evidence_receipt" ||
|
|
423
|
+
event.goalId !== contract.goalId ||
|
|
424
|
+
event.laneId !== receipt.core.laneId ||
|
|
425
|
+
!constantTimeSha256Equal(event.eventHash, binding.eventHash) ||
|
|
426
|
+
!replayPayloadMatches(event.payload, replayPayload.receiptId, replayPayload.coreSha256)) {
|
|
427
|
+
return receiptCheckIssue(`Execution receipt ledger binding does not match evidence "${item.claim}".`, true);
|
|
428
|
+
}
|
|
429
|
+
if (latestMutationSeq !== null && event.seq <= latestMutationSeq) {
|
|
430
|
+
return receiptCheckIssue(`Execution receipt is stale: workspace mutation seq ${latestMutationSeq} does not precede receipt ledger seq ${event.seq} for evidence "${item.claim}".`, true);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
return receiptCheckIssue(`Execution receipt is invalid for evidence "${item.claim}".`, true);
|
|
435
|
+
}
|
|
436
|
+
return undefined;
|
|
437
|
+
}
|
|
160
438
|
export class EvidenceGate {
|
|
161
439
|
options;
|
|
162
440
|
constructor(options = {}) {
|
|
@@ -164,6 +442,7 @@ export class EvidenceGate {
|
|
|
164
442
|
minEvidenceCount: 1,
|
|
165
443
|
requireHash: true,
|
|
166
444
|
requireVerificationCommand: true,
|
|
445
|
+
receiptMode: "prefer",
|
|
167
446
|
...options,
|
|
168
447
|
};
|
|
169
448
|
}
|
|
@@ -172,32 +451,42 @@ export class EvidenceGate {
|
|
|
172
451
|
const satisfied = evidence.filter((e) => e.status === "satisfied");
|
|
173
452
|
const failed = evidence.filter((e) => e.status === "failed");
|
|
174
453
|
const pending = evidence.filter((e) => e.status === "pending" || e.status === "gathering");
|
|
454
|
+
const mode = this.options.receiptMode ?? "prefer";
|
|
175
455
|
const checks = [];
|
|
176
|
-
|
|
456
|
+
let hardReceiptFailure = false;
|
|
177
457
|
if (satisfied.length < (this.options.minEvidenceCount ?? 1)) {
|
|
178
458
|
checks.push(`Only ${satisfied.length}/${this.options.minEvidenceCount} required evidence items satisfied.`);
|
|
179
459
|
}
|
|
180
|
-
// Check 2: no failed evidence
|
|
181
460
|
if (failed.length > 0) {
|
|
182
461
|
checks.push(`${failed.length} evidence item(s) failed.`);
|
|
183
462
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
463
|
+
if (mode === "legacy") {
|
|
464
|
+
if (this.options.requireHash) {
|
|
465
|
+
const hashed = satisfied.filter((e) => e.hash && e.hash.length > 0);
|
|
466
|
+
if (hashed.length === 0)
|
|
467
|
+
checks.push("No satisfied evidence has a SHA-256 hash.");
|
|
468
|
+
}
|
|
469
|
+
if (this.options.requireVerificationCommand) {
|
|
470
|
+
const withCommand = satisfied.filter((e) => e.verificationCommand && e.verificationCommand.length > 0);
|
|
471
|
+
if (withCommand.length === 0)
|
|
472
|
+
checks.push("No satisfied evidence has a verification command.");
|
|
189
473
|
}
|
|
190
474
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
475
|
+
else {
|
|
476
|
+
const unsatisfied = evidence.filter((item) => item.status !== "satisfied");
|
|
477
|
+
if (unsatisfied.length > 0) {
|
|
478
|
+
checks.push(`${unsatisfied.length} required evidence item(s) are not satisfied.`);
|
|
479
|
+
}
|
|
480
|
+
for (const item of satisfied) {
|
|
481
|
+
const issue = receiptIssue(item, contract, this.options);
|
|
482
|
+
if (issue) {
|
|
483
|
+
checks.push(issue.message);
|
|
484
|
+
hardReceiptFailure ||= issue.hard;
|
|
485
|
+
}
|
|
196
486
|
}
|
|
197
487
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
checks.push("Task contract verdict is 'fail'.");
|
|
488
|
+
if (contract.verdict === "fail" || (mode !== "legacy" && contract.verdict !== "pass")) {
|
|
489
|
+
checks.push(`Task contract verdict is '${contract.verdict}'.`);
|
|
201
490
|
}
|
|
202
491
|
let status;
|
|
203
492
|
let reason;
|
|
@@ -207,9 +496,9 @@ export class EvidenceGate {
|
|
|
207
496
|
reason = `Evidence gate passed: ${satisfied.length}/${evidence.length} evidence items satisfied.`;
|
|
208
497
|
}
|
|
209
498
|
else {
|
|
210
|
-
|
|
211
|
-
const hasHardFailure = failed.length > 0 ||
|
|
212
|
-
status = pending.length > 0
|
|
499
|
+
const contractFailure = mode === "legacy" ? contract.verdict === "fail" : contract.verdict !== "pass";
|
|
500
|
+
const hasHardFailure = failed.length > 0 || contractFailure || hardReceiptFailure || mode === "strict";
|
|
501
|
+
status = hasHardFailure ? "blocked" : mode === "prefer" || pending.length > 0 ? "conditional" : "blocked";
|
|
213
502
|
reason = checks.join("; ");
|
|
214
503
|
suggestion = `Gather remaining evidence (${pending.length} pending) and re-run verification.`;
|
|
215
504
|
}
|
|
@@ -260,6 +549,13 @@ export class FailClosedMergeGate {
|
|
|
260
549
|
};
|
|
261
550
|
}
|
|
262
551
|
}
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// Verify Reporter v2
|
|
554
|
+
// ============================================================================
|
|
555
|
+
/** Escape pipes/newlines so untrusted claim or command text cannot restructure the report table. */
|
|
556
|
+
function escapeTableCell(value) {
|
|
557
|
+
return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
558
|
+
}
|
|
263
559
|
export class VerifyReporterV2 {
|
|
264
560
|
options;
|
|
265
561
|
constructor(options) {
|
|
@@ -273,9 +569,9 @@ export class VerifyReporterV2 {
|
|
|
273
569
|
"",
|
|
274
570
|
`| Field | Value |`,
|
|
275
571
|
`|-------|-------|`,
|
|
276
|
-
`| Claim | ${contract.completionClaim} |`,
|
|
572
|
+
`| Claim | ${escapeTableCell(contract.completionClaim)} |`,
|
|
277
573
|
`| Verdict | ${contract.verdict} |`,
|
|
278
|
-
`| Final Risk | ${contract.finalRisk} |`,
|
|
574
|
+
`| Final Risk | ${escapeTableCell(contract.finalRisk)} |`,
|
|
279
575
|
"",
|
|
280
576
|
"### Evidence",
|
|
281
577
|
"",
|
|
@@ -283,9 +579,9 @@ export class VerifyReporterV2 {
|
|
|
283
579
|
`|-------|--------|----------|----------|`,
|
|
284
580
|
];
|
|
285
581
|
for (const ev of contract.requiredEvidence) {
|
|
286
|
-
lines.push(`| ${ev.claim} | ${ev.status} | ${ev.artifactPath ?? "—"} | ${ev.verificationCommand ?? "—"} |`);
|
|
582
|
+
lines.push(`| ${escapeTableCell(ev.claim)} | ${ev.status} | ${escapeTableCell(ev.artifactPath ?? "—")} | ${escapeTableCell(ev.verificationCommand ?? "—")} |`);
|
|
287
583
|
}
|
|
288
|
-
lines.push("", "### Merge Gate", "", `| Gate | Status | Reason |`, `|------|--------|--------|`, `| ${mergeGate.gateId} | ${mergeGate.status} | ${mergeGate.reason} |`, "");
|
|
584
|
+
lines.push("", "### Merge Gate", "", `| Gate | Status | Reason |`, `|------|--------|--------|`, `| ${mergeGate.gateId} | ${mergeGate.status} | ${escapeTableCell(mergeGate.reason)} |`, "");
|
|
289
585
|
if (mergeGate.suggestion) {
|
|
290
586
|
lines.push(`**Suggestion:** ${mergeGate.suggestion}`, "");
|
|
291
587
|
}
|