open-multi-agent-kit 0.90.8 → 0.91.0
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 +41 -0
- package/README.md +21 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -0
- 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 +140 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1179 -95
- 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/command-safety.d.ts.map +1 -1
- package/dist/core/command-safety.js +0 -8
- package/dist/core/command-safety.js.map +1 -1
- 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-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 +4 -0
- package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-provider.js +19 -0
- 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-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 +46 -37
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +7 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +12 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/index.d.ts +7 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/metrics-sampler.d.ts +20 -0
- package/dist/core/metrics-sampler.d.ts.map +1 -1
- package/dist/core/metrics-sampler.js +52 -0
- package/dist/core/metrics-sampler.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +4 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +16 -7
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/provider-resilience.d.ts +51 -0
- package/dist/core/provider-resilience.d.ts.map +1 -0
- package/dist/core/provider-resilience.js +86 -0
- package/dist/core/provider-resilience.js.map +1 -0
- 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 +1045 -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 +106 -0
- package/dist/core/session-termination.d.ts.map +1 -0
- package/dist/core/session-termination.js +445 -0
- package/dist/core/session-termination.js.map +1 -0
- package/dist/core/settings-manager.d.ts +52 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +54 -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/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +47 -0
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/omp-pure-seams.d.ts +41 -0
- package/dist/core/tools/omp-pure-seams.d.ts.map +1 -0
- package/dist/core/tools/omp-pure-seams.js +142 -0
- package/dist/core/tools/omp-pure-seams.js.map +1 -0
- package/dist/core/tools/omp-seam-runtime.d.ts +100 -0
- package/dist/core/tools/omp-seam-runtime.d.ts.map +1 -0
- package/dist/core/tools/omp-seam-runtime.js +40 -0
- package/dist/core/tools/omp-seam-runtime.js.map +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +34 -1
- package/dist/core/tools/read.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 +39 -15
- package/dist/guardrails/evidence-system.d.ts.map +1 -1
- package/dist/guardrails/evidence-system.js +255 -97
- 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/footer.d.ts +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +11 -8
- 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/interactive/theme/omk-aurora-dark.json +80 -0
- package/dist/modes/interactive/theme/omk-aurora-light.json +80 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +5 -0
- package/dist/modes/interactive/theme/theme.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 +211 -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/adaptorch-preview.md +1 -1
- package/docs/compaction.md +38 -4
- package/docs/extensions.md +2 -0
- package/docs/provider-resilience.md +87 -0
- package/docs/sdk.md +238 -1
- package/docs/settings.md +80 -2
- package/docs/themes.md +2 -0
- package/docs/usage.md +38 -0
- 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 +22 -13
- package/package.json +6 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-command-tokenizer.d.ts","sourceRoot":"","sources":["../../src/guardrails/shell-command-tokenizer.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1B;AACD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACrB;AACD,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AACD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACnD;AACD,qBAAa,sBAAuB,SAAQ,KAAK;IAChD,cAGC;CACD;AAoBD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,CA0HxE","sourcesContent":["import { hasActiveShellNameGlob } from \"./shell-command-name-glob.ts\";\n\nconst MAX_STATIC_SHELL_INPUT_CHARS = 64 * 1024;\nconst MAX_STATIC_SHELL_WORDS = 4_096;\ntype ShellQuote = '\"' | \"'\";\nexport interface ShellSourceRange {\n\treadonly start: number;\n\treadonly end: number;\n\treadonly contentEnd: number;\n\treadonly quoteStart?: number;\n\treadonly kind: \"literal\" | \"variable\";\n\treadonly escaped: boolean;\n}\nexport interface StaticShellWord {\n\treadonly start: number;\n\treadonly end: number;\n}\nexport interface StaticShellVariable {\n\treadonly start: number;\n\treadonly end: number;\n\treadonly sourceStart: number;\n\treadonly sourceEnd: number;\n}\nexport interface NormalizedStaticShell {\n\treadonly text: string;\n\treadonly sources: readonly ShellSourceRange[];\n\treadonly words: readonly StaticShellWord[];\n\treadonly variables: readonly StaticShellVariable[];\n}\nexport class StaticShellSyntaxError extends Error {\n\tconstructor() {\n\t\tsuper(\"shell command is dynamic or malformed; failing closed\");\n\t\tthis.name = \"StaticShellSyntaxError\";\n\t}\n}\nfunction fail(): never {\n\tthrow new StaticShellSyntaxError();\n}\nfunction isWordBoundary(character: string): boolean {\n\treturn /[\\s;&|()<>]/.test(character);\n}\n\nfunction variableAt(text: string, index: number): string | undefined {\n\tconst remainder = text.slice(index);\n\treturn /^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}/.exec(remainder)?.[0] ?? /^\\$[A-Za-z_][A-Za-z0-9_]*/.exec(remainder)?.[0];\n}\n\nfunction isBraceExpansion(text: string, index: number): boolean {\n\tconst close = text.indexOf(\"}\", index + 1);\n\tif (close < 0) return false;\n\tconst body = text.slice(index + 1, close);\n\treturn body.includes(\",\") || body.includes(\"..\");\n}\n\nexport function normalizeStaticShell(text: string): NormalizedStaticShell {\n\tif (text.length > MAX_STATIC_SHELL_INPUT_CHARS || text.includes(\"`\") || text.includes(\"<(\") || text.includes(\">(\"))\n\t\tfail();\n\tconst output: string[] = [];\n\tconst sources: ShellSourceRange[] = [];\n\tconst words: StaticShellWord[] = [];\n\tconst variables: StaticShellVariable[] = [];\n\tconst append = (\n\t\tcharacter: string,\n\t\tstart: number,\n\t\tend: number,\n\t\tkind: ShellSourceRange[\"kind\"],\n\t\tescaped: boolean,\n\t\tquoteStart?: number,\n\t): void => {\n\t\toutput.push(character);\n\t\tsources.push({ start, end, contentEnd: end, kind, escaped, ...(quoteStart === undefined ? {} : { quoteStart }) });\n\t};\n\tlet index = 0;\n\twhile (index < text.length) {\n\t\tif (isWordBoundary(text[index])) {\n\t\t\tappend(text[index], index, index + 1, \"literal\", false);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (words.length >= MAX_STATIC_SHELL_WORDS) fail();\n\t\tconst wordStart = output.length;\n\t\tlet quote: ShellQuote | undefined;\n\t\tlet quoteStart: number | undefined;\n\t\tlet pendingStart = index;\n\t\tlet lastSource = -1;\n\t\twhile (index < text.length && (quote !== undefined || !isWordBoundary(text[index]))) {\n\t\t\tconst character = text[index];\n\t\t\tif (quote === \"'\") {\n\t\t\t\tif (character === \"'\") {\n\t\t\t\t\tif (lastSource >= wordStart) sources[lastSource] = { ...sources[lastSource], end: index + 1 };\n\t\t\t\t\tquote = undefined;\n\t\t\t\t\tquoteStart = undefined;\n\t\t\t\t\tindex++;\n\t\t\t\t\tpendingStart = index;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tappend(character, pendingStart, index + 1, \"literal\", false, quoteStart);\n\t\t\t\tlastSource = sources.length - 1;\n\t\t\t\tindex++;\n\t\t\t\tpendingStart = index;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (quote === '\"' && character === '\"') {\n\t\t\t\tif (lastSource >= wordStart) sources[lastSource] = { ...sources[lastSource], end: index + 1 };\n\t\t\t\tquote = undefined;\n\t\t\t\tquoteStart = undefined;\n\t\t\t\tindex++;\n\t\t\t\tpendingStart = index;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (character === \"\\\\\") {\n\t\t\t\tconst next = text[index + 1];\n\t\t\t\tif (next === undefined) fail();\n\t\t\t\tif (next === \"\\n\") {\n\t\t\t\t\tindex += 2;\n\t\t\t\t\tpendingStart = index;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (quote === '\"' && !'\\\\\"$'.includes(next)) {\n\t\t\t\t\tappend(character, pendingStart, index + 1, \"literal\", false, quoteStart);\n\t\t\t\t\tlastSource = sources.length - 1;\n\t\t\t\t\tindex++;\n\t\t\t\t\tpendingStart = index;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tappend(next, pendingStart, index + 2, \"literal\", true, quoteStart);\n\t\t\t\tlastSource = sources.length - 1;\n\t\t\t\tindex += 2;\n\t\t\t\tpendingStart = index;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (quote === undefined && (character === '\"' || character === \"'\")) {\n\t\t\t\tquote = character;\n\t\t\t\tquoteStart = index;\n\t\t\t\tindex++;\n\t\t\t\tpendingStart = index;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (character === \"$\") {\n\t\t\t\tif (text[index + 1] === \"(\") fail();\n\t\t\t\tconst token = variableAt(text, index);\n\t\t\t\tif (token === undefined) fail();\n\t\t\t\tconst normalizedStart = output.length;\n\t\t\t\tfor (let offset = 0; offset < token.length; offset++) {\n\t\t\t\t\tappend(\n\t\t\t\t\t\ttoken[offset],\n\t\t\t\t\t\toffset === 0 ? pendingStart : index + offset,\n\t\t\t\t\t\tindex + offset + 1,\n\t\t\t\t\t\t\"variable\",\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tquoteStart,\n\t\t\t\t\t);\n\t\t\t\t\tlastSource = sources.length - 1;\n\t\t\t\t}\n\t\t\t\tvariables.push({\n\t\t\t\t\tstart: normalizedStart,\n\t\t\t\t\tend: output.length,\n\t\t\t\t\tsourceStart: index,\n\t\t\t\t\tsourceEnd: index + token.length,\n\t\t\t\t});\n\t\t\t\tindex += token.length;\n\t\t\t\tpendingStart = index;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (quote === undefined && character === \"{\" && isBraceExpansion(text, index)) fail();\n\t\t\tappend(character, pendingStart, index + 1, \"literal\", false, quoteStart);\n\t\t\tlastSource = sources.length - 1;\n\t\t\tindex++;\n\t\t\tpendingStart = index;\n\t\t}\n\t\tif (quote !== undefined) fail();\n\t\twords.push({ start: wordStart, end: output.length });\n\t}\n\tconst shell = { text: output.join(\"\"), sources, words, variables };\n\tif (hasActiveShellNameGlob(shell)) fail();\n\treturn shell;\n}\n"]}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { hasActiveShellNameGlob } from "./shell-command-name-glob.js";
|
|
2
|
+
const MAX_STATIC_SHELL_INPUT_CHARS = 64 * 1024;
|
|
3
|
+
const MAX_STATIC_SHELL_WORDS = 4_096;
|
|
4
|
+
export class StaticShellSyntaxError extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super("shell command is dynamic or malformed; failing closed");
|
|
7
|
+
this.name = "StaticShellSyntaxError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function fail() {
|
|
11
|
+
throw new StaticShellSyntaxError();
|
|
12
|
+
}
|
|
13
|
+
function isWordBoundary(character) {
|
|
14
|
+
return /[\s;&|()<>]/.test(character);
|
|
15
|
+
}
|
|
16
|
+
function variableAt(text, index) {
|
|
17
|
+
const remainder = text.slice(index);
|
|
18
|
+
return /^\$\{[A-Za-z_][A-Za-z0-9_]*\}/.exec(remainder)?.[0] ?? /^\$[A-Za-z_][A-Za-z0-9_]*/.exec(remainder)?.[0];
|
|
19
|
+
}
|
|
20
|
+
function isBraceExpansion(text, index) {
|
|
21
|
+
const close = text.indexOf("}", index + 1);
|
|
22
|
+
if (close < 0)
|
|
23
|
+
return false;
|
|
24
|
+
const body = text.slice(index + 1, close);
|
|
25
|
+
return body.includes(",") || body.includes("..");
|
|
26
|
+
}
|
|
27
|
+
export function normalizeStaticShell(text) {
|
|
28
|
+
if (text.length > MAX_STATIC_SHELL_INPUT_CHARS || text.includes("`") || text.includes("<(") || text.includes(">("))
|
|
29
|
+
fail();
|
|
30
|
+
const output = [];
|
|
31
|
+
const sources = [];
|
|
32
|
+
const words = [];
|
|
33
|
+
const variables = [];
|
|
34
|
+
const append = (character, start, end, kind, escaped, quoteStart) => {
|
|
35
|
+
output.push(character);
|
|
36
|
+
sources.push({ start, end, contentEnd: end, kind, escaped, ...(quoteStart === undefined ? {} : { quoteStart }) });
|
|
37
|
+
};
|
|
38
|
+
let index = 0;
|
|
39
|
+
while (index < text.length) {
|
|
40
|
+
if (isWordBoundary(text[index])) {
|
|
41
|
+
append(text[index], index, index + 1, "literal", false);
|
|
42
|
+
index++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (words.length >= MAX_STATIC_SHELL_WORDS)
|
|
46
|
+
fail();
|
|
47
|
+
const wordStart = output.length;
|
|
48
|
+
let quote;
|
|
49
|
+
let quoteStart;
|
|
50
|
+
let pendingStart = index;
|
|
51
|
+
let lastSource = -1;
|
|
52
|
+
while (index < text.length && (quote !== undefined || !isWordBoundary(text[index]))) {
|
|
53
|
+
const character = text[index];
|
|
54
|
+
if (quote === "'") {
|
|
55
|
+
if (character === "'") {
|
|
56
|
+
if (lastSource >= wordStart)
|
|
57
|
+
sources[lastSource] = { ...sources[lastSource], end: index + 1 };
|
|
58
|
+
quote = undefined;
|
|
59
|
+
quoteStart = undefined;
|
|
60
|
+
index++;
|
|
61
|
+
pendingStart = index;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
append(character, pendingStart, index + 1, "literal", false, quoteStart);
|
|
65
|
+
lastSource = sources.length - 1;
|
|
66
|
+
index++;
|
|
67
|
+
pendingStart = index;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (quote === '"' && character === '"') {
|
|
71
|
+
if (lastSource >= wordStart)
|
|
72
|
+
sources[lastSource] = { ...sources[lastSource], end: index + 1 };
|
|
73
|
+
quote = undefined;
|
|
74
|
+
quoteStart = undefined;
|
|
75
|
+
index++;
|
|
76
|
+
pendingStart = index;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (character === "\\") {
|
|
80
|
+
const next = text[index + 1];
|
|
81
|
+
if (next === undefined)
|
|
82
|
+
fail();
|
|
83
|
+
if (next === "\n") {
|
|
84
|
+
index += 2;
|
|
85
|
+
pendingStart = index;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (quote === '"' && !'\\"$'.includes(next)) {
|
|
89
|
+
append(character, pendingStart, index + 1, "literal", false, quoteStart);
|
|
90
|
+
lastSource = sources.length - 1;
|
|
91
|
+
index++;
|
|
92
|
+
pendingStart = index;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
append(next, pendingStart, index + 2, "literal", true, quoteStart);
|
|
96
|
+
lastSource = sources.length - 1;
|
|
97
|
+
index += 2;
|
|
98
|
+
pendingStart = index;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (quote === undefined && (character === '"' || character === "'")) {
|
|
102
|
+
quote = character;
|
|
103
|
+
quoteStart = index;
|
|
104
|
+
index++;
|
|
105
|
+
pendingStart = index;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (character === "$") {
|
|
109
|
+
if (text[index + 1] === "(")
|
|
110
|
+
fail();
|
|
111
|
+
const token = variableAt(text, index);
|
|
112
|
+
if (token === undefined)
|
|
113
|
+
fail();
|
|
114
|
+
const normalizedStart = output.length;
|
|
115
|
+
for (let offset = 0; offset < token.length; offset++) {
|
|
116
|
+
append(token[offset], offset === 0 ? pendingStart : index + offset, index + offset + 1, "variable", false, quoteStart);
|
|
117
|
+
lastSource = sources.length - 1;
|
|
118
|
+
}
|
|
119
|
+
variables.push({
|
|
120
|
+
start: normalizedStart,
|
|
121
|
+
end: output.length,
|
|
122
|
+
sourceStart: index,
|
|
123
|
+
sourceEnd: index + token.length,
|
|
124
|
+
});
|
|
125
|
+
index += token.length;
|
|
126
|
+
pendingStart = index;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (quote === undefined && character === "{" && isBraceExpansion(text, index))
|
|
130
|
+
fail();
|
|
131
|
+
append(character, pendingStart, index + 1, "literal", false, quoteStart);
|
|
132
|
+
lastSource = sources.length - 1;
|
|
133
|
+
index++;
|
|
134
|
+
pendingStart = index;
|
|
135
|
+
}
|
|
136
|
+
if (quote !== undefined)
|
|
137
|
+
fail();
|
|
138
|
+
words.push({ start: wordStart, end: output.length });
|
|
139
|
+
}
|
|
140
|
+
const shell = { text: output.join(""), sources, words, variables };
|
|
141
|
+
if (hasActiveShellNameGlob(shell))
|
|
142
|
+
fail();
|
|
143
|
+
return shell;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=shell-command-tokenizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-command-tokenizer.js","sourceRoot":"","sources":["../../src/guardrails/shell-command-tokenizer.ts"],"names":[],"mappings":"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","sourcesContent":["import { hasActiveShellNameGlob } from \"./shell-command-name-glob.ts\";\n\nconst MAX_STATIC_SHELL_INPUT_CHARS = 64 * 1024;\nconst MAX_STATIC_SHELL_WORDS = 4_096;\ntype ShellQuote = '\"' | \"'\";\nexport interface ShellSourceRange {\n\treadonly start: number;\n\treadonly end: number;\n\treadonly contentEnd: number;\n\treadonly quoteStart?: number;\n\treadonly kind: \"literal\" | \"variable\";\n\treadonly escaped: boolean;\n}\nexport interface StaticShellWord {\n\treadonly start: number;\n\treadonly end: number;\n}\nexport interface StaticShellVariable {\n\treadonly start: number;\n\treadonly end: number;\n\treadonly sourceStart: number;\n\treadonly sourceEnd: number;\n}\nexport interface NormalizedStaticShell {\n\treadonly text: string;\n\treadonly sources: readonly ShellSourceRange[];\n\treadonly words: readonly StaticShellWord[];\n\treadonly variables: readonly StaticShellVariable[];\n}\nexport class StaticShellSyntaxError extends Error {\n\tconstructor() {\n\t\tsuper(\"shell command is dynamic or malformed; failing closed\");\n\t\tthis.name = \"StaticShellSyntaxError\";\n\t}\n}\nfunction fail(): never {\n\tthrow new StaticShellSyntaxError();\n}\nfunction isWordBoundary(character: string): boolean {\n\treturn /[\\s;&|()<>]/.test(character);\n}\n\nfunction variableAt(text: string, index: number): string | undefined {\n\tconst remainder = text.slice(index);\n\treturn /^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}/.exec(remainder)?.[0] ?? /^\\$[A-Za-z_][A-Za-z0-9_]*/.exec(remainder)?.[0];\n}\n\nfunction isBraceExpansion(text: string, index: number): boolean {\n\tconst close = text.indexOf(\"}\", index + 1);\n\tif (close < 0) return false;\n\tconst body = text.slice(index + 1, close);\n\treturn body.includes(\",\") || body.includes(\"..\");\n}\n\nexport function normalizeStaticShell(text: string): NormalizedStaticShell {\n\tif (text.length > MAX_STATIC_SHELL_INPUT_CHARS || text.includes(\"`\") || text.includes(\"<(\") || text.includes(\">(\"))\n\t\tfail();\n\tconst output: string[] = [];\n\tconst sources: ShellSourceRange[] = [];\n\tconst words: StaticShellWord[] = [];\n\tconst variables: StaticShellVariable[] = [];\n\tconst append = (\n\t\tcharacter: string,\n\t\tstart: number,\n\t\tend: number,\n\t\tkind: ShellSourceRange[\"kind\"],\n\t\tescaped: boolean,\n\t\tquoteStart?: number,\n\t): void => {\n\t\toutput.push(character);\n\t\tsources.push({ start, end, contentEnd: end, kind, escaped, ...(quoteStart === undefined ? {} : { quoteStart }) });\n\t};\n\tlet index = 0;\n\twhile (index < text.length) {\n\t\tif (isWordBoundary(text[index])) {\n\t\t\tappend(text[index], index, index + 1, \"literal\", false);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (words.length >= MAX_STATIC_SHELL_WORDS) fail();\n\t\tconst wordStart = output.length;\n\t\tlet quote: ShellQuote | undefined;\n\t\tlet quoteStart: number | undefined;\n\t\tlet pendingStart = index;\n\t\tlet lastSource = -1;\n\t\twhile (index < text.length && (quote !== undefined || !isWordBoundary(text[index]))) {\n\t\t\tconst character = text[index];\n\t\t\tif (quote === \"'\") {\n\t\t\t\tif (character === \"'\") {\n\t\t\t\t\tif (lastSource >= wordStart) sources[lastSource] = { ...sources[lastSource], end: index + 1 };\n\t\t\t\t\tquote = undefined;\n\t\t\t\t\tquoteStart = undefined;\n\t\t\t\t\tindex++;\n\t\t\t\t\tpendingStart = index;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tappend(character, pendingStart, index + 1, \"literal\", false, quoteStart);\n\t\t\t\tlastSource = sources.length - 1;\n\t\t\t\tindex++;\n\t\t\t\tpendingStart = index;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (quote === '\"' && character === '\"') {\n\t\t\t\tif (lastSource >= wordStart) sources[lastSource] = { ...sources[lastSource], end: index + 1 };\n\t\t\t\tquote = undefined;\n\t\t\t\tquoteStart = undefined;\n\t\t\t\tindex++;\n\t\t\t\tpendingStart = index;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (character === \"\\\\\") {\n\t\t\t\tconst next = text[index + 1];\n\t\t\t\tif (next === undefined) fail();\n\t\t\t\tif (next === \"\\n\") {\n\t\t\t\t\tindex += 2;\n\t\t\t\t\tpendingStart = index;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (quote === '\"' && !'\\\\\"$'.includes(next)) {\n\t\t\t\t\tappend(character, pendingStart, index + 1, \"literal\", false, quoteStart);\n\t\t\t\t\tlastSource = sources.length - 1;\n\t\t\t\t\tindex++;\n\t\t\t\t\tpendingStart = index;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tappend(next, pendingStart, index + 2, \"literal\", true, quoteStart);\n\t\t\t\tlastSource = sources.length - 1;\n\t\t\t\tindex += 2;\n\t\t\t\tpendingStart = index;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (quote === undefined && (character === '\"' || character === \"'\")) {\n\t\t\t\tquote = character;\n\t\t\t\tquoteStart = index;\n\t\t\t\tindex++;\n\t\t\t\tpendingStart = index;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (character === \"$\") {\n\t\t\t\tif (text[index + 1] === \"(\") fail();\n\t\t\t\tconst token = variableAt(text, index);\n\t\t\t\tif (token === undefined) fail();\n\t\t\t\tconst normalizedStart = output.length;\n\t\t\t\tfor (let offset = 0; offset < token.length; offset++) {\n\t\t\t\t\tappend(\n\t\t\t\t\t\ttoken[offset],\n\t\t\t\t\t\toffset === 0 ? pendingStart : index + offset,\n\t\t\t\t\t\tindex + offset + 1,\n\t\t\t\t\t\t\"variable\",\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tquoteStart,\n\t\t\t\t\t);\n\t\t\t\t\tlastSource = sources.length - 1;\n\t\t\t\t}\n\t\t\t\tvariables.push({\n\t\t\t\t\tstart: normalizedStart,\n\t\t\t\t\tend: output.length,\n\t\t\t\t\tsourceStart: index,\n\t\t\t\t\tsourceEnd: index + token.length,\n\t\t\t\t});\n\t\t\t\tindex += token.length;\n\t\t\t\tpendingStart = index;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (quote === undefined && character === \"{\" && isBraceExpansion(text, index)) fail();\n\t\t\tappend(character, pendingStart, index + 1, \"literal\", false, quoteStart);\n\t\t\tlastSource = sources.length - 1;\n\t\t\tindex++;\n\t\t\tpendingStart = index;\n\t\t}\n\t\tif (quote !== undefined) fail();\n\t\twords.push({ start: wordStart, end: output.length });\n\t}\n\tconst shell = { text: output.join(\"\"), sources, words, variables };\n\tif (hasActiveShellNameGlob(shell)) fail();\n\treturn shell;\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ShellCommandWrapperState {
|
|
2
|
+
readonly kind: "command" | "exec";
|
|
3
|
+
readonly options: boolean;
|
|
4
|
+
readonly pendingName?: boolean;
|
|
5
|
+
readonly lookup?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ShellCommandWrapperStep {
|
|
8
|
+
readonly state?: ShellCommandWrapperState;
|
|
9
|
+
readonly ambiguous?: boolean;
|
|
10
|
+
readonly optionEnd?: number;
|
|
11
|
+
readonly value?: boolean;
|
|
12
|
+
readonly target?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Recognize only wrappers whose option grammar is modeled below. */
|
|
15
|
+
export declare function shellCommandWrapper(commandName: string): ShellCommandWrapperState | undefined;
|
|
16
|
+
/** Consume one wrapper argument without mistaking option values for command names. */
|
|
17
|
+
export declare function consumeShellCommandWrapper(state: ShellCommandWrapperState, word: string): ShellCommandWrapperStep;
|
|
18
|
+
//# sourceMappingURL=shell-command-wrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-command-wrapper.d.ts","sourceRoot":"","sources":["../../src/guardrails/shell-command-wrapper.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,wBAAwB,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,wBAAwB,GAAG,SAAS,CAG7F;AAED,sFAAsF;AACtF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,wBAAwB,EAAE,IAAI,EAAE,MAAM,GAAG,uBAAuB,CA4BjH","sourcesContent":["export interface ShellCommandWrapperState {\n\treadonly kind: \"command\" | \"exec\";\n\treadonly options: boolean;\n\treadonly pendingName?: boolean;\n\treadonly lookup?: boolean;\n}\n\nexport interface ShellCommandWrapperStep {\n\treadonly state?: ShellCommandWrapperState;\n\treadonly ambiguous?: boolean;\n\treadonly optionEnd?: number;\n\treadonly value?: boolean;\n\treadonly target?: boolean;\n}\n\n/** Recognize only wrappers whose option grammar is modeled below. */\nexport function shellCommandWrapper(commandName: string): ShellCommandWrapperState | undefined {\n\tconst name = commandName.slice(commandName.lastIndexOf(\"/\") + 1).replace(/\\.exe$/i, \"\");\n\treturn name === \"command\" || name === \"exec\" ? { kind: name, options: true } : undefined;\n}\n\n/** Consume one wrapper argument without mistaking option values for command names. */\nexport function consumeShellCommandWrapper(state: ShellCommandWrapperState, word: string): ShellCommandWrapperStep {\n\tif (state.kind === \"command\") {\n\t\tif (state.lookup && (!state.options || !word.startsWith(\"-\") || word === \"-\")) {\n\t\t\treturn { value: true };\n\t\t}\n\t\tif (state.options && word === \"--\") return { state: { ...state, options: false }, optionEnd: word.length };\n\t\tif (state.options && word.startsWith(\"-\") && word !== \"-\") {\n\t\t\tif (!/^-[pVv]+$/.test(word)) return { ambiguous: true };\n\t\t\treturn {\n\t\t\t\tstate: /[Vv]/.test(word) ? { ...state, lookup: true } : state,\n\t\t\t\toptionEnd: word.length,\n\t\t\t};\n\t\t}\n\t\treturn state.lookup ? { value: true } : { target: true };\n\t}\n\tif (state.pendingName) return { state: { ...state, pendingName: false }, value: true };\n\tif (state.options && word === \"--\") return { state: { ...state, options: false }, optionEnd: word.length };\n\tif (state.options && word.startsWith(\"-\") && word !== \"-\") {\n\t\tfor (let index = 1; index < word.length; index++) {\n\t\t\tif (word[index] === \"c\" || word[index] === \"l\") continue;\n\t\t\tif (word[index] !== \"a\") return { ambiguous: true };\n\t\t\treturn index + 1 < word.length\n\t\t\t\t? { state, optionEnd: index + 1 }\n\t\t\t\t: { state: { ...state, pendingName: true }, optionEnd: word.length };\n\t\t}\n\t\treturn { state, optionEnd: word.length };\n\t}\n\treturn { target: true };\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Recognize only wrappers whose option grammar is modeled below. */
|
|
2
|
+
export function shellCommandWrapper(commandName) {
|
|
3
|
+
const name = commandName.slice(commandName.lastIndexOf("/") + 1).replace(/\.exe$/i, "");
|
|
4
|
+
return name === "command" || name === "exec" ? { kind: name, options: true } : undefined;
|
|
5
|
+
}
|
|
6
|
+
/** Consume one wrapper argument without mistaking option values for command names. */
|
|
7
|
+
export function consumeShellCommandWrapper(state, word) {
|
|
8
|
+
if (state.kind === "command") {
|
|
9
|
+
if (state.lookup && (!state.options || !word.startsWith("-") || word === "-")) {
|
|
10
|
+
return { value: true };
|
|
11
|
+
}
|
|
12
|
+
if (state.options && word === "--")
|
|
13
|
+
return { state: { ...state, options: false }, optionEnd: word.length };
|
|
14
|
+
if (state.options && word.startsWith("-") && word !== "-") {
|
|
15
|
+
if (!/^-[pVv]+$/.test(word))
|
|
16
|
+
return { ambiguous: true };
|
|
17
|
+
return {
|
|
18
|
+
state: /[Vv]/.test(word) ? { ...state, lookup: true } : state,
|
|
19
|
+
optionEnd: word.length,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return state.lookup ? { value: true } : { target: true };
|
|
23
|
+
}
|
|
24
|
+
if (state.pendingName)
|
|
25
|
+
return { state: { ...state, pendingName: false }, value: true };
|
|
26
|
+
if (state.options && word === "--")
|
|
27
|
+
return { state: { ...state, options: false }, optionEnd: word.length };
|
|
28
|
+
if (state.options && word.startsWith("-") && word !== "-") {
|
|
29
|
+
for (let index = 1; index < word.length; index++) {
|
|
30
|
+
if (word[index] === "c" || word[index] === "l")
|
|
31
|
+
continue;
|
|
32
|
+
if (word[index] !== "a")
|
|
33
|
+
return { ambiguous: true };
|
|
34
|
+
return index + 1 < word.length
|
|
35
|
+
? { state, optionEnd: index + 1 }
|
|
36
|
+
: { state: { ...state, pendingName: true }, optionEnd: word.length };
|
|
37
|
+
}
|
|
38
|
+
return { state, optionEnd: word.length };
|
|
39
|
+
}
|
|
40
|
+
return { target: true };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=shell-command-wrapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-command-wrapper.js","sourceRoot":"","sources":["../../src/guardrails/shell-command-wrapper.ts"],"names":[],"mappings":"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","sourcesContent":["export interface ShellCommandWrapperState {\n\treadonly kind: \"command\" | \"exec\";\n\treadonly options: boolean;\n\treadonly pendingName?: boolean;\n\treadonly lookup?: boolean;\n}\n\nexport interface ShellCommandWrapperStep {\n\treadonly state?: ShellCommandWrapperState;\n\treadonly ambiguous?: boolean;\n\treadonly optionEnd?: number;\n\treadonly value?: boolean;\n\treadonly target?: boolean;\n}\n\n/** Recognize only wrappers whose option grammar is modeled below. */\nexport function shellCommandWrapper(commandName: string): ShellCommandWrapperState | undefined {\n\tconst name = commandName.slice(commandName.lastIndexOf(\"/\") + 1).replace(/\\.exe$/i, \"\");\n\treturn name === \"command\" || name === \"exec\" ? { kind: name, options: true } : undefined;\n}\n\n/** Consume one wrapper argument without mistaking option values for command names. */\nexport function consumeShellCommandWrapper(state: ShellCommandWrapperState, word: string): ShellCommandWrapperStep {\n\tif (state.kind === \"command\") {\n\t\tif (state.lookup && (!state.options || !word.startsWith(\"-\") || word === \"-\")) {\n\t\t\treturn { value: true };\n\t\t}\n\t\tif (state.options && word === \"--\") return { state: { ...state, options: false }, optionEnd: word.length };\n\t\tif (state.options && word.startsWith(\"-\") && word !== \"-\") {\n\t\t\tif (!/^-[pVv]+$/.test(word)) return { ambiguous: true };\n\t\t\treturn {\n\t\t\t\tstate: /[Vv]/.test(word) ? { ...state, lookup: true } : state,\n\t\t\t\toptionEnd: word.length,\n\t\t\t};\n\t\t}\n\t\treturn state.lookup ? { value: true } : { target: true };\n\t}\n\tif (state.pendingName) return { state: { ...state, pendingName: false }, value: true };\n\tif (state.options && word === \"--\") return { state: { ...state, options: false }, optionEnd: word.length };\n\tif (state.options && word.startsWith(\"-\") && word !== \"-\") {\n\t\tfor (let index = 1; index < word.length; index++) {\n\t\t\tif (word[index] === \"c\" || word[index] === \"l\") continue;\n\t\t\tif (word[index] !== \"a\") return { ambiguous: true };\n\t\t\treturn index + 1 < word.length\n\t\t\t\t? { state, optionEnd: index + 1 }\n\t\t\t\t: { state: { ...state, pendingName: true }, optionEnd: word.length };\n\t\t}\n\t\treturn { state, optionEnd: word.length };\n\t}\n\treturn { target: true };\n}\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { EvidenceCommandDescriptor, EvidenceExecutor, EvidenceReceipt, EvidenceReceiptDisposition, ReplayEvent, Sha256Hex, WorkspaceScope } from "../types/evidence.ts";
|
|
2
|
+
import { type CommandHmacBinder } from "./evidence-attestation.ts";
|
|
3
|
+
import type { AlreadyRedactedOutputBytes } from "./evidence-receipt.ts";
|
|
4
|
+
import type { EvidenceReceiptStore } from "./evidence-receipt-store.ts";
|
|
5
|
+
import type { EvidenceGateOptions, ReplayLedgerManager } from "./evidence-system.ts";
|
|
6
|
+
export type VerifiedEvidenceExecutionOutcome = EvidenceReceiptDisposition & {
|
|
7
|
+
readonly alreadyRedactedOutput: AlreadyRedactedOutputBytes;
|
|
8
|
+
};
|
|
9
|
+
export interface VerifiedEvidenceExecutionRequest {
|
|
10
|
+
readonly goalId: string;
|
|
11
|
+
readonly laneId?: string;
|
|
12
|
+
readonly claim: string;
|
|
13
|
+
readonly command: EvidenceCommandDescriptor;
|
|
14
|
+
readonly cwd: string;
|
|
15
|
+
readonly timeoutMs: number | null;
|
|
16
|
+
readonly workspaceScope: WorkspaceScope;
|
|
17
|
+
readonly executor: EvidenceExecutor;
|
|
18
|
+
readonly toolCallId?: string;
|
|
19
|
+
readonly execute: () => Promise<VerifiedEvidenceExecutionOutcome>;
|
|
20
|
+
}
|
|
21
|
+
export interface VerifiedEvidenceMetadata {
|
|
22
|
+
readonly receiptId: string;
|
|
23
|
+
readonly receiptSchemaVersion: 3;
|
|
24
|
+
readonly receiptCommandSha256: Sha256Hex;
|
|
25
|
+
readonly receiptLaneId?: string;
|
|
26
|
+
readonly timestamp: string;
|
|
27
|
+
}
|
|
28
|
+
export interface VerifiedEvidenceExecutionResult {
|
|
29
|
+
readonly receipt: EvidenceReceipt;
|
|
30
|
+
readonly receiptPath: string;
|
|
31
|
+
readonly evidenceMetadata: VerifiedEvidenceMetadata;
|
|
32
|
+
}
|
|
33
|
+
export interface VerifiedEvidenceExecutorOptions {
|
|
34
|
+
readonly store: EvidenceReceiptStore;
|
|
35
|
+
readonly ledger: ReplayLedgerManager;
|
|
36
|
+
readonly now?: () => Date;
|
|
37
|
+
readonly receiptIdFactory?: () => string;
|
|
38
|
+
readonly commandAttestationBinder?: CommandHmacBinder;
|
|
39
|
+
}
|
|
40
|
+
export declare class VerifiedEvidenceExecutorError extends Error {
|
|
41
|
+
constructor(message: string);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Execute one caller-provided verification operation between two scoped workspace snapshots,
|
|
45
|
+
* then persist a replay-bound immutable receipt. The callback must normalize process
|
|
46
|
+
* completion and provide already-redacted bounded output bytes.
|
|
47
|
+
*/
|
|
48
|
+
export declare class VerifiedEvidenceExecutor {
|
|
49
|
+
private readonly store;
|
|
50
|
+
private readonly ledger;
|
|
51
|
+
private readonly now;
|
|
52
|
+
private readonly receiptIdFactory;
|
|
53
|
+
private readonly commandAttestationBinder;
|
|
54
|
+
private readonly attestedCommands;
|
|
55
|
+
constructor(options: VerifiedEvidenceExecutorOptions);
|
|
56
|
+
readonly resolveReceipt: (receiptId: string) => EvidenceReceipt;
|
|
57
|
+
readonly resolveLedgerEvent: (seq: number) => ReplayEvent | undefined;
|
|
58
|
+
readonly resolveVerifiedLedgerSnapshot: () => import("../types/evidence.ts").VerifiedReplayLedgerSnapshot;
|
|
59
|
+
readonly resolveAttestedCommand: (receiptId: string) => EvidenceCommandDescriptor | undefined;
|
|
60
|
+
/** Latest relevant `workspace_mutation` seq from this executor's own replay ledger. */
|
|
61
|
+
readonly resolveLatestWorkspaceMutationSeq: (scope: WorkspaceScope) => number | null;
|
|
62
|
+
createGateOptions(): Pick<EvidenceGateOptions, "resolveReceipt" | "resolveLedgerEvent" | "resolveVerifiedLedgerSnapshot" | "captureWorkspaceFingerprint" | "resolveLatestWorkspaceMutationSeq" | "commandAttestationBinder" | "resolveAttestedCommand">;
|
|
63
|
+
execute(request: VerifiedEvidenceExecutionRequest): Promise<VerifiedEvidenceExecutionResult>;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=verified-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verified-executor.d.ts","sourceRoot":"","sources":["../../src/guardrails/verified-executor.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type {\n\tEvidenceCommandDescriptor,\n\tEvidenceExecutor,\n\tEvidenceReceipt,\n\tEvidenceReceiptDisposition,\n\tReplayEvent,\n\tSha256Hex,\n\tWorkspaceScope,\n} from \"../types/evidence.ts\";\nimport { redactCommandDescriptor } from \"./command-redaction.ts\";\nimport { type CommandHmacBinder, createCommandHmacBinder } from \"./evidence-attestation.ts\";\nimport type { AlreadyRedactedOutputBytes } from \"./evidence-receipt.ts\";\nimport {\n\tcomputeEvidenceCommandSha256,\n\tcreateEvidenceReceipt,\n\tevidenceReceiptReplayPayload,\n\tisSafeEvidenceReceiptId,\n\tparseSha256Hex,\n\twithEvidenceReceiptEnvelope,\n} from \"./evidence-receipt.ts\";\nimport type { EvidenceReceiptStore } from \"./evidence-receipt-store.ts\";\nimport type { EvidenceGateOptions, ReplayLedgerManager } from \"./evidence-system.ts\";\nimport { latestRelevantWorkspaceMutationSeq } from \"./evidence-system.ts\";\nimport { captureWorkspaceFingerprint } from \"./workspace-fingerprint.ts\";\n\nexport type VerifiedEvidenceExecutionOutcome = EvidenceReceiptDisposition & {\n\treadonly alreadyRedactedOutput: AlreadyRedactedOutputBytes;\n};\n\nexport interface VerifiedEvidenceExecutionRequest {\n\treadonly goalId: string;\n\treadonly laneId?: string;\n\treadonly claim: string;\n\treadonly command: EvidenceCommandDescriptor;\n\treadonly cwd: string;\n\treadonly timeoutMs: number | null;\n\treadonly workspaceScope: WorkspaceScope;\n\treadonly executor: EvidenceExecutor;\n\treadonly toolCallId?: string;\n\treadonly execute: () => Promise<VerifiedEvidenceExecutionOutcome>;\n}\n\nexport interface VerifiedEvidenceMetadata {\n\treadonly receiptId: string;\n\treadonly receiptSchemaVersion: 3;\n\treadonly receiptCommandSha256: Sha256Hex;\n\treadonly receiptLaneId?: string;\n\treadonly timestamp: string;\n}\n\nexport interface VerifiedEvidenceExecutionResult {\n\treadonly receipt: EvidenceReceipt;\n\treadonly receiptPath: string;\n\treadonly evidenceMetadata: VerifiedEvidenceMetadata;\n}\n\nexport interface VerifiedEvidenceExecutorOptions {\n\treadonly store: EvidenceReceiptStore;\n\treadonly ledger: ReplayLedgerManager;\n\treadonly now?: () => Date;\n\treadonly receiptIdFactory?: () => string;\n\treadonly commandAttestationBinder?: CommandHmacBinder;\n}\n\nexport class VerifiedEvidenceExecutorError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"VerifiedEvidenceExecutorError\";\n\t}\n}\n\nconst EVIDENCE_EXECUTORS: ReadonlySet<string> = new Set([\"bash-tool\", \"ci-runner\", \"mcp\", \"internal\"]);\n\n/**\n * Fail-closed parse of the callback-reported disposition. Only these two facts\n * (plus the redacted output bytes) are accepted from the callback; a contradictory\n * pair is rejected rather than repaired.\n */\nfunction parseExecutionDisposition(status: unknown, exitCode: unknown): EvidenceReceiptDisposition {\n\tif (status === \"passed\" && exitCode === 0) return { status: \"passed\", exitCode: 0 };\n\tif (status === \"failed\" && Number.isSafeInteger(exitCode) && exitCode !== 0) {\n\t\treturn { status: \"failed\", exitCode: exitCode as number };\n\t}\n\tif ((status === \"timeout\" || status === \"aborted\") && exitCode === null) {\n\t\treturn { status, exitCode: null };\n\t}\n\tthrow new VerifiedEvidenceExecutorError(\"execution callback returned an invalid disposition\");\n}\n\nfunction assertRequestText(value: unknown, label: string): void {\n\tif (typeof value !== \"string\" || value.length === 0 || value.includes(\"\\0\")) {\n\t\tthrow new VerifiedEvidenceExecutorError(`${label} must be a non-empty string without NUL bytes`);\n\t}\n}\n\nfunction validateRequest(request: VerifiedEvidenceExecutionRequest): void {\n\tassertRequestText(request.goalId, \"goalId\");\n\tif (request.laneId !== undefined) assertRequestText(request.laneId, \"laneId\");\n\tassertRequestText(request.claim, \"claim\");\n\tassertRequestText(request.cwd, \"cwd\");\n\tif (request.toolCallId !== undefined) assertRequestText(request.toolCallId, \"toolCallId\");\n\tif (request.timeoutMs !== null && (!Number.isSafeInteger(request.timeoutMs) || request.timeoutMs <= 0)) {\n\t\tthrow new VerifiedEvidenceExecutorError(\"timeoutMs must be null or a positive safe integer\");\n\t}\n\tif (!EVIDENCE_EXECUTORS.has(request.executor)) {\n\t\tthrow new VerifiedEvidenceExecutorError(\"executor is invalid\");\n\t}\n\tif (typeof request.execute !== \"function\") {\n\t\tthrow new VerifiedEvidenceExecutorError(\"execute must be a function\");\n\t}\n}\n\n/**\n * Execute one caller-provided verification operation between two scoped workspace snapshots,\n * then persist a replay-bound immutable receipt. The callback must normalize process\n * completion and provide already-redacted bounded output bytes.\n */\nexport class VerifiedEvidenceExecutor {\n\tprivate readonly store: EvidenceReceiptStore;\n\tprivate readonly ledger: ReplayLedgerManager;\n\tprivate readonly now: () => Date;\n\tprivate readonly receiptIdFactory: () => string;\n\tprivate readonly commandAttestationBinder: CommandHmacBinder;\n\tprivate readonly attestedCommands = new Map<string, EvidenceCommandDescriptor>();\n\n\tconstructor(options: VerifiedEvidenceExecutorOptions) {\n\t\tthis.store = options.store;\n\t\tthis.ledger = options.ledger;\n\t\tthis.now = options.now ?? (() => new Date());\n\t\tthis.receiptIdFactory = options.receiptIdFactory ?? randomUUID;\n\t\tthis.commandAttestationBinder = options.commandAttestationBinder ?? createCommandHmacBinder();\n\t}\n\n\treadonly resolveReceipt = (receiptId: string): EvidenceReceipt => this.store.read(receiptId);\n\n\treadonly resolveLedgerEvent = (seq: number): ReplayEvent | undefined =>\n\t\tthis.ledger.getEvents().find((event) => event.seq === seq);\n\n\treadonly resolveVerifiedLedgerSnapshot = () => this.ledger.getVerifiedSnapshot();\n\n\treadonly resolveAttestedCommand = (receiptId: string): EvidenceCommandDescriptor | undefined => {\n\t\tconst command = this.attestedCommands.get(receiptId);\n\t\treturn command === undefined ? undefined : structuredClone(command);\n\t};\n\n\t/** Latest relevant `workspace_mutation` seq from this executor's own replay ledger. */\n\treadonly resolveLatestWorkspaceMutationSeq = (scope: WorkspaceScope): number | null =>\n\t\tlatestRelevantWorkspaceMutationSeq(this.ledger.getEvents(), scope);\n\n\tcreateGateOptions(): Pick<\n\t\tEvidenceGateOptions,\n\t\t| \"resolveReceipt\"\n\t\t| \"resolveLedgerEvent\"\n\t\t| \"resolveVerifiedLedgerSnapshot\"\n\t\t| \"captureWorkspaceFingerprint\"\n\t\t| \"resolveLatestWorkspaceMutationSeq\"\n\t\t| \"commandAttestationBinder\"\n\t\t| \"resolveAttestedCommand\"\n\t> {\n\t\treturn {\n\t\t\tresolveReceipt: this.resolveReceipt,\n\t\t\tresolveLedgerEvent: this.resolveLedgerEvent,\n\t\t\tresolveVerifiedLedgerSnapshot: this.resolveVerifiedLedgerSnapshot,\n\t\t\tcaptureWorkspaceFingerprint,\n\t\t\tresolveLatestWorkspaceMutationSeq: this.resolveLatestWorkspaceMutationSeq,\n\t\t\tcommandAttestationBinder: this.commandAttestationBinder,\n\t\t\tresolveAttestedCommand: this.resolveAttestedCommand,\n\t\t};\n\t}\n\n\tasync execute(request: VerifiedEvidenceExecutionRequest): Promise<VerifiedEvidenceExecutionResult> {\n\t\tvalidateRequest(request);\n\t\tconst ledgerGoalId = this.ledger.getLedger().goalId;\n\t\tif (request.goalId !== ledgerGoalId) {\n\t\t\tthrow new VerifiedEvidenceExecutorError(\"verification goalId does not match the replay ledger\");\n\t\t}\n\t\tconst receiptId = this.receiptIdFactory();\n\t\tif (!isSafeEvidenceReceiptId(receiptId)) {\n\t\t\tthrow new VerifiedEvidenceExecutorError(\"receiptIdFactory returned an unsafe receipt ID\");\n\t\t}\n\t\t// Tokenize the persisted representation and bind the ORIGINAL command under the\n\t\t// internal process key BEFORE any side effect; unrepresentable or oversize\n\t\t// redaction metadata fails closed here. The original is never persisted.\n\t\tconst redaction = redactCommandDescriptor(request.command);\n\t\tconst commandBinding = this.commandAttestationBinder.bind(request.command);\n\t\tconst receiptCommandSha256 = computeEvidenceCommandSha256(redaction.command);\n\t\tconst workspaceBefore = captureWorkspaceFingerprint(request.workspaceScope);\n\t\tconst started = this.now();\n\t\tconst startedMs = started.getTime();\n\t\tif (!Number.isSafeInteger(startedMs)) {\n\t\t\tthrow new VerifiedEvidenceExecutorError(\"verification clock produced an invalid start time\");\n\t\t}\n\t\tconst outcome = await request.execute();\n\t\t// Independent-review P3: accept ONLY the disposition and the already-redacted\n\t\t// output bytes from the callback. Every other receipt field is executor-captured\n\t\t// provenance that a callback outcome object must never be able to override.\n\t\tconst { status, exitCode, alreadyRedactedOutput } = outcome;\n\t\tconst disposition = parseExecutionDisposition(status, exitCode);\n\t\tconst finished = this.now();\n\t\tconst finishedMs = finished.getTime();\n\t\tif (!Number.isSafeInteger(finishedMs) || finishedMs < startedMs) {\n\t\t\tthrow new VerifiedEvidenceExecutorError(\"verification clock produced an invalid execution interval\");\n\t\t}\n\t\tconst workspaceAfter = captureWorkspaceFingerprint(request.workspaceScope);\n\t\tconst receipt = createEvidenceReceipt({\n\t\t\treceiptId,\n\t\t\tgoalId: request.goalId,\n\t\t\t...(request.laneId !== undefined ? { laneId: request.laneId } : {}),\n\t\t\tclaim: request.claim,\n\t\t\tcommand: redaction.command,\n\t\t\tcommandRedaction: redaction.summary,\n\t\t\tcommandBinding,\n\t\t\tcwd: request.cwd,\n\t\t\ttimeoutMs: request.timeoutMs,\n\t\t\tstartedAt: started.toISOString(),\n\t\t\tfinishedAt: finished.toISOString(),\n\t\t\tdurationMs: finishedMs - startedMs,\n\t\t\tworkspaceBefore,\n\t\t\tworkspaceAfter,\n\t\t\texecutor: request.executor,\n\t\t\t...disposition,\n\t\t\talreadyRedactedOutput,\n\t\t\t...(request.toolCallId !== undefined ? { toolCallId: request.toolCallId } : {}),\n\t\t});\n\t\tconst event = this.ledger.append({\n\t\t\ttype: \"evidence_receipt\",\n\t\t\tgoalId: request.goalId,\n\t\t\t...(request.laneId !== undefined ? { laneId: request.laneId } : {}),\n\t\t\tpayload: evidenceReceiptReplayPayload(receipt),\n\t\t});\n\t\tthis.ledger.persist();\n\t\tconst boundReceipt = withEvidenceReceiptEnvelope(receipt, {\n\t\t\tledgerBinding: { seq: event.seq, eventHash: parseSha256Hex(event.eventHash, \"ledger eventHash\") },\n\t\t});\n\t\tconst receiptPath = this.store.write(boundReceipt);\n\t\tthis.attestedCommands.set(receiptId, structuredClone(request.command));\n\t\treturn {\n\t\t\treceipt: boundReceipt,\n\t\t\treceiptPath,\n\t\t\tevidenceMetadata: {\n\t\t\t\treceiptId,\n\t\t\t\treceiptSchemaVersion: 3,\n\t\t\t\treceiptCommandSha256,\n\t\t\t\t...(request.laneId !== undefined ? { receiptLaneId: request.laneId } : {}),\n\t\t\t\ttimestamp: finished.toISOString(),\n\t\t\t},\n\t\t};\n\t}\n}\n"]}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { redactCommandDescriptor } from "./command-redaction.js";
|
|
3
|
+
import { createCommandHmacBinder } from "./evidence-attestation.js";
|
|
4
|
+
import { computeEvidenceCommandSha256, createEvidenceReceipt, evidenceReceiptReplayPayload, isSafeEvidenceReceiptId, parseSha256Hex, withEvidenceReceiptEnvelope, } from "./evidence-receipt.js";
|
|
5
|
+
import { latestRelevantWorkspaceMutationSeq } from "./evidence-system.js";
|
|
6
|
+
import { captureWorkspaceFingerprint } from "./workspace-fingerprint.js";
|
|
7
|
+
export class VerifiedEvidenceExecutorError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "VerifiedEvidenceExecutorError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const EVIDENCE_EXECUTORS = new Set(["bash-tool", "ci-runner", "mcp", "internal"]);
|
|
14
|
+
/**
|
|
15
|
+
* Fail-closed parse of the callback-reported disposition. Only these two facts
|
|
16
|
+
* (plus the redacted output bytes) are accepted from the callback; a contradictory
|
|
17
|
+
* pair is rejected rather than repaired.
|
|
18
|
+
*/
|
|
19
|
+
function parseExecutionDisposition(status, exitCode) {
|
|
20
|
+
if (status === "passed" && exitCode === 0)
|
|
21
|
+
return { status: "passed", exitCode: 0 };
|
|
22
|
+
if (status === "failed" && Number.isSafeInteger(exitCode) && exitCode !== 0) {
|
|
23
|
+
return { status: "failed", exitCode: exitCode };
|
|
24
|
+
}
|
|
25
|
+
if ((status === "timeout" || status === "aborted") && exitCode === null) {
|
|
26
|
+
return { status, exitCode: null };
|
|
27
|
+
}
|
|
28
|
+
throw new VerifiedEvidenceExecutorError("execution callback returned an invalid disposition");
|
|
29
|
+
}
|
|
30
|
+
function assertRequestText(value, label) {
|
|
31
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
|
|
32
|
+
throw new VerifiedEvidenceExecutorError(`${label} must be a non-empty string without NUL bytes`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function validateRequest(request) {
|
|
36
|
+
assertRequestText(request.goalId, "goalId");
|
|
37
|
+
if (request.laneId !== undefined)
|
|
38
|
+
assertRequestText(request.laneId, "laneId");
|
|
39
|
+
assertRequestText(request.claim, "claim");
|
|
40
|
+
assertRequestText(request.cwd, "cwd");
|
|
41
|
+
if (request.toolCallId !== undefined)
|
|
42
|
+
assertRequestText(request.toolCallId, "toolCallId");
|
|
43
|
+
if (request.timeoutMs !== null && (!Number.isSafeInteger(request.timeoutMs) || request.timeoutMs <= 0)) {
|
|
44
|
+
throw new VerifiedEvidenceExecutorError("timeoutMs must be null or a positive safe integer");
|
|
45
|
+
}
|
|
46
|
+
if (!EVIDENCE_EXECUTORS.has(request.executor)) {
|
|
47
|
+
throw new VerifiedEvidenceExecutorError("executor is invalid");
|
|
48
|
+
}
|
|
49
|
+
if (typeof request.execute !== "function") {
|
|
50
|
+
throw new VerifiedEvidenceExecutorError("execute must be a function");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Execute one caller-provided verification operation between two scoped workspace snapshots,
|
|
55
|
+
* then persist a replay-bound immutable receipt. The callback must normalize process
|
|
56
|
+
* completion and provide already-redacted bounded output bytes.
|
|
57
|
+
*/
|
|
58
|
+
export class VerifiedEvidenceExecutor {
|
|
59
|
+
store;
|
|
60
|
+
ledger;
|
|
61
|
+
now;
|
|
62
|
+
receiptIdFactory;
|
|
63
|
+
commandAttestationBinder;
|
|
64
|
+
attestedCommands = new Map();
|
|
65
|
+
constructor(options) {
|
|
66
|
+
this.store = options.store;
|
|
67
|
+
this.ledger = options.ledger;
|
|
68
|
+
this.now = options.now ?? (() => new Date());
|
|
69
|
+
this.receiptIdFactory = options.receiptIdFactory ?? randomUUID;
|
|
70
|
+
this.commandAttestationBinder = options.commandAttestationBinder ?? createCommandHmacBinder();
|
|
71
|
+
}
|
|
72
|
+
resolveReceipt = (receiptId) => this.store.read(receiptId);
|
|
73
|
+
resolveLedgerEvent = (seq) => this.ledger.getEvents().find((event) => event.seq === seq);
|
|
74
|
+
resolveVerifiedLedgerSnapshot = () => this.ledger.getVerifiedSnapshot();
|
|
75
|
+
resolveAttestedCommand = (receiptId) => {
|
|
76
|
+
const command = this.attestedCommands.get(receiptId);
|
|
77
|
+
return command === undefined ? undefined : structuredClone(command);
|
|
78
|
+
};
|
|
79
|
+
/** Latest relevant `workspace_mutation` seq from this executor's own replay ledger. */
|
|
80
|
+
resolveLatestWorkspaceMutationSeq = (scope) => latestRelevantWorkspaceMutationSeq(this.ledger.getEvents(), scope);
|
|
81
|
+
createGateOptions() {
|
|
82
|
+
return {
|
|
83
|
+
resolveReceipt: this.resolveReceipt,
|
|
84
|
+
resolveLedgerEvent: this.resolveLedgerEvent,
|
|
85
|
+
resolveVerifiedLedgerSnapshot: this.resolveVerifiedLedgerSnapshot,
|
|
86
|
+
captureWorkspaceFingerprint,
|
|
87
|
+
resolveLatestWorkspaceMutationSeq: this.resolveLatestWorkspaceMutationSeq,
|
|
88
|
+
commandAttestationBinder: this.commandAttestationBinder,
|
|
89
|
+
resolveAttestedCommand: this.resolveAttestedCommand,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async execute(request) {
|
|
93
|
+
validateRequest(request);
|
|
94
|
+
const ledgerGoalId = this.ledger.getLedger().goalId;
|
|
95
|
+
if (request.goalId !== ledgerGoalId) {
|
|
96
|
+
throw new VerifiedEvidenceExecutorError("verification goalId does not match the replay ledger");
|
|
97
|
+
}
|
|
98
|
+
const receiptId = this.receiptIdFactory();
|
|
99
|
+
if (!isSafeEvidenceReceiptId(receiptId)) {
|
|
100
|
+
throw new VerifiedEvidenceExecutorError("receiptIdFactory returned an unsafe receipt ID");
|
|
101
|
+
}
|
|
102
|
+
// Tokenize the persisted representation and bind the ORIGINAL command under the
|
|
103
|
+
// internal process key BEFORE any side effect; unrepresentable or oversize
|
|
104
|
+
// redaction metadata fails closed here. The original is never persisted.
|
|
105
|
+
const redaction = redactCommandDescriptor(request.command);
|
|
106
|
+
const commandBinding = this.commandAttestationBinder.bind(request.command);
|
|
107
|
+
const receiptCommandSha256 = computeEvidenceCommandSha256(redaction.command);
|
|
108
|
+
const workspaceBefore = captureWorkspaceFingerprint(request.workspaceScope);
|
|
109
|
+
const started = this.now();
|
|
110
|
+
const startedMs = started.getTime();
|
|
111
|
+
if (!Number.isSafeInteger(startedMs)) {
|
|
112
|
+
throw new VerifiedEvidenceExecutorError("verification clock produced an invalid start time");
|
|
113
|
+
}
|
|
114
|
+
const outcome = await request.execute();
|
|
115
|
+
// Independent-review P3: accept ONLY the disposition and the already-redacted
|
|
116
|
+
// output bytes from the callback. Every other receipt field is executor-captured
|
|
117
|
+
// provenance that a callback outcome object must never be able to override.
|
|
118
|
+
const { status, exitCode, alreadyRedactedOutput } = outcome;
|
|
119
|
+
const disposition = parseExecutionDisposition(status, exitCode);
|
|
120
|
+
const finished = this.now();
|
|
121
|
+
const finishedMs = finished.getTime();
|
|
122
|
+
if (!Number.isSafeInteger(finishedMs) || finishedMs < startedMs) {
|
|
123
|
+
throw new VerifiedEvidenceExecutorError("verification clock produced an invalid execution interval");
|
|
124
|
+
}
|
|
125
|
+
const workspaceAfter = captureWorkspaceFingerprint(request.workspaceScope);
|
|
126
|
+
const receipt = createEvidenceReceipt({
|
|
127
|
+
receiptId,
|
|
128
|
+
goalId: request.goalId,
|
|
129
|
+
...(request.laneId !== undefined ? { laneId: request.laneId } : {}),
|
|
130
|
+
claim: request.claim,
|
|
131
|
+
command: redaction.command,
|
|
132
|
+
commandRedaction: redaction.summary,
|
|
133
|
+
commandBinding,
|
|
134
|
+
cwd: request.cwd,
|
|
135
|
+
timeoutMs: request.timeoutMs,
|
|
136
|
+
startedAt: started.toISOString(),
|
|
137
|
+
finishedAt: finished.toISOString(),
|
|
138
|
+
durationMs: finishedMs - startedMs,
|
|
139
|
+
workspaceBefore,
|
|
140
|
+
workspaceAfter,
|
|
141
|
+
executor: request.executor,
|
|
142
|
+
...disposition,
|
|
143
|
+
alreadyRedactedOutput,
|
|
144
|
+
...(request.toolCallId !== undefined ? { toolCallId: request.toolCallId } : {}),
|
|
145
|
+
});
|
|
146
|
+
const event = this.ledger.append({
|
|
147
|
+
type: "evidence_receipt",
|
|
148
|
+
goalId: request.goalId,
|
|
149
|
+
...(request.laneId !== undefined ? { laneId: request.laneId } : {}),
|
|
150
|
+
payload: evidenceReceiptReplayPayload(receipt),
|
|
151
|
+
});
|
|
152
|
+
this.ledger.persist();
|
|
153
|
+
const boundReceipt = withEvidenceReceiptEnvelope(receipt, {
|
|
154
|
+
ledgerBinding: { seq: event.seq, eventHash: parseSha256Hex(event.eventHash, "ledger eventHash") },
|
|
155
|
+
});
|
|
156
|
+
const receiptPath = this.store.write(boundReceipt);
|
|
157
|
+
this.attestedCommands.set(receiptId, structuredClone(request.command));
|
|
158
|
+
return {
|
|
159
|
+
receipt: boundReceipt,
|
|
160
|
+
receiptPath,
|
|
161
|
+
evidenceMetadata: {
|
|
162
|
+
receiptId,
|
|
163
|
+
receiptSchemaVersion: 3,
|
|
164
|
+
receiptCommandSha256,
|
|
165
|
+
...(request.laneId !== undefined ? { receiptLaneId: request.laneId } : {}),
|
|
166
|
+
timestamp: finished.toISOString(),
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=verified-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verified-executor.js","sourceRoot":"","sources":["../../src/guardrails/verified-executor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAUzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAA0B,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAE5F,OAAO,EACN,4BAA4B,EAC5B,qBAAqB,EACrB,4BAA4B,EAC5B,uBAAuB,EACvB,cAAc,EACd,2BAA2B,GAC3B,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,kCAAkC,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAyCzE,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IACvD,YAAY,OAAe,EAAE;QAC5B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAC;IAAA,CAC5C;CACD;AAED,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;AAEvG;;;;GAIG;AACH,SAAS,yBAAyB,CAAC,MAAe,EAAE,QAAiB,EAA8B;IAClG,IAAI,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACpF,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC7E,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAkB,EAAE,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,CAAC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACzE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACnC,CAAC;IACD,MAAM,IAAI,6BAA6B,CAAC,oDAAoD,CAAC,CAAC;AAAA,CAC9F;AAED,SAAS,iBAAiB,CAAC,KAAc,EAAE,KAAa,EAAQ;IAC/D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7E,MAAM,IAAI,6BAA6B,CAAC,GAAG,KAAK,+CAA+C,CAAC,CAAC;IAClG,CAAC;AAAA,CACD;AAED,SAAS,eAAe,CAAC,OAAyC,EAAQ;IACzE,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC5C,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;QAAE,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC9E,iBAAiB,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC1C,iBAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACtC,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;QAAE,iBAAiB,CAAC,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC1F,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC;QACxG,MAAM,IAAI,6BAA6B,CAAC,mDAAmD,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,6BAA6B,CAAC,qBAAqB,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3C,MAAM,IAAI,6BAA6B,CAAC,4BAA4B,CAAC,CAAC;IACvE,CAAC;AAAA,CACD;AAED;;;;GAIG;AACH,MAAM,OAAO,wBAAwB;IACnB,KAAK,CAAuB;IAC5B,MAAM,CAAsB;IAC5B,GAAG,CAAa;IAChB,gBAAgB,CAAe;IAC/B,wBAAwB,CAAoB;IAC5C,gBAAgB,GAAG,IAAI,GAAG,EAAqC,CAAC;IAEjF,YAAY,OAAwC,EAAE;QACrD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,UAAU,CAAC;QAC/D,IAAI,CAAC,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,IAAI,uBAAuB,EAAE,CAAC;IAAA,CAC9F;IAEQ,cAAc,GAAG,CAAC,SAAiB,EAAmB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEpF,kBAAkB,GAAG,CAAC,GAAW,EAA2B,EAAE,CACtE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;IAEnD,6BAA6B,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,mBAAmB,EAAE,CAAC;IAExE,sBAAsB,GAAG,CAAC,SAAiB,EAAyC,EAAE,CAAC;QAC/F,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACrD,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAAA,CACpE,CAAC;IAEF,uFAAuF;IAC9E,iCAAiC,GAAG,CAAC,KAAqB,EAAiB,EAAE,CACrF,kCAAkC,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,KAAK,CAAC,CAAC;IAEpE,iBAAiB,GASf;QACD,OAAO;YACN,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;YAC3C,6BAA6B,EAAE,IAAI,CAAC,6BAA6B;YACjE,2BAA2B;YAC3B,iCAAiC,EAAE,IAAI,CAAC,iCAAiC;YACzE,wBAAwB,EAAE,IAAI,CAAC,wBAAwB;YACvD,sBAAsB,EAAE,IAAI,CAAC,sBAAsB;SACnD,CAAC;IAAA,CACF;IAED,KAAK,CAAC,OAAO,CAAC,OAAyC,EAA4C;QAClG,eAAe,CAAC,OAAO,CAAC,CAAC;QACzB,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC;QACpD,IAAI,OAAO,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;YACrC,MAAM,IAAI,6BAA6B,CAAC,sDAAsD,CAAC,CAAC;QACjG,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1C,IAAI,CAAC,uBAAuB,CAAC,SAAS,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,6BAA6B,CAAC,gDAAgD,CAAC,CAAC;QAC3F,CAAC;QACD,gFAAgF;QAChF,2EAA2E;QAC3E,yEAAyE;QACzE,MAAM,SAAS,GAAG,uBAAuB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC3D,MAAM,cAAc,GAAG,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC3E,MAAM,oBAAoB,GAAG,4BAA4B,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC7E,MAAM,eAAe,GAAG,2BAA2B,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,6BAA6B,CAAC,mDAAmD,CAAC,CAAC;QAC9F,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxC,8EAA8E;QAC9E,iFAAiF;QACjF,4EAA4E;QAC5E,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC;QAC5D,MAAM,WAAW,GAAG,yBAAyB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,SAAS,EAAE,CAAC;YACjE,MAAM,IAAI,6BAA6B,CAAC,2DAA2D,CAAC,CAAC;QACtG,CAAC;QACD,MAAM,cAAc,GAAG,2BAA2B,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC3E,MAAM,OAAO,GAAG,qBAAqB,CAAC;YACrC,SAAS;YACT,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,gBAAgB,EAAE,SAAS,CAAC,OAAO;YACnC,cAAc;YACd,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,SAAS,EAAE,OAAO,CAAC,WAAW,EAAE;YAChC,UAAU,EAAE,QAAQ,CAAC,WAAW,EAAE;YAClC,UAAU,EAAE,UAAU,GAAG,SAAS;YAClC,eAAe;YACf,cAAc;YACd,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,GAAG,WAAW;YACd,qBAAqB;YACrB,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/E,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;YAChC,IAAI,EAAE,kBAAkB;YACxB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,OAAO,EAAE,4BAA4B,CAAC,OAAO,CAAC;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACtB,MAAM,YAAY,GAAG,2BAA2B,CAAC,OAAO,EAAE;YACzD,aAAa,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,SAAS,EAAE,cAAc,CAAC,KAAK,CAAC,SAAS,EAAE,kBAAkB,CAAC,EAAE;SACjG,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACnD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QACvE,OAAO;YACN,OAAO,EAAE,YAAY;YACrB,WAAW;YACX,gBAAgB,EAAE;gBACjB,SAAS;gBACT,oBAAoB,EAAE,CAAC;gBACvB,oBAAoB;gBACpB,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1E,SAAS,EAAE,QAAQ,CAAC,WAAW,EAAE;aACjC;SACD,CAAC;IAAA,CACF;CACD","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type {\n\tEvidenceCommandDescriptor,\n\tEvidenceExecutor,\n\tEvidenceReceipt,\n\tEvidenceReceiptDisposition,\n\tReplayEvent,\n\tSha256Hex,\n\tWorkspaceScope,\n} from \"../types/evidence.ts\";\nimport { redactCommandDescriptor } from \"./command-redaction.ts\";\nimport { type CommandHmacBinder, createCommandHmacBinder } from \"./evidence-attestation.ts\";\nimport type { AlreadyRedactedOutputBytes } from \"./evidence-receipt.ts\";\nimport {\n\tcomputeEvidenceCommandSha256,\n\tcreateEvidenceReceipt,\n\tevidenceReceiptReplayPayload,\n\tisSafeEvidenceReceiptId,\n\tparseSha256Hex,\n\twithEvidenceReceiptEnvelope,\n} from \"./evidence-receipt.ts\";\nimport type { EvidenceReceiptStore } from \"./evidence-receipt-store.ts\";\nimport type { EvidenceGateOptions, ReplayLedgerManager } from \"./evidence-system.ts\";\nimport { latestRelevantWorkspaceMutationSeq } from \"./evidence-system.ts\";\nimport { captureWorkspaceFingerprint } from \"./workspace-fingerprint.ts\";\n\nexport type VerifiedEvidenceExecutionOutcome = EvidenceReceiptDisposition & {\n\treadonly alreadyRedactedOutput: AlreadyRedactedOutputBytes;\n};\n\nexport interface VerifiedEvidenceExecutionRequest {\n\treadonly goalId: string;\n\treadonly laneId?: string;\n\treadonly claim: string;\n\treadonly command: EvidenceCommandDescriptor;\n\treadonly cwd: string;\n\treadonly timeoutMs: number | null;\n\treadonly workspaceScope: WorkspaceScope;\n\treadonly executor: EvidenceExecutor;\n\treadonly toolCallId?: string;\n\treadonly execute: () => Promise<VerifiedEvidenceExecutionOutcome>;\n}\n\nexport interface VerifiedEvidenceMetadata {\n\treadonly receiptId: string;\n\treadonly receiptSchemaVersion: 3;\n\treadonly receiptCommandSha256: Sha256Hex;\n\treadonly receiptLaneId?: string;\n\treadonly timestamp: string;\n}\n\nexport interface VerifiedEvidenceExecutionResult {\n\treadonly receipt: EvidenceReceipt;\n\treadonly receiptPath: string;\n\treadonly evidenceMetadata: VerifiedEvidenceMetadata;\n}\n\nexport interface VerifiedEvidenceExecutorOptions {\n\treadonly store: EvidenceReceiptStore;\n\treadonly ledger: ReplayLedgerManager;\n\treadonly now?: () => Date;\n\treadonly receiptIdFactory?: () => string;\n\treadonly commandAttestationBinder?: CommandHmacBinder;\n}\n\nexport class VerifiedEvidenceExecutorError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"VerifiedEvidenceExecutorError\";\n\t}\n}\n\nconst EVIDENCE_EXECUTORS: ReadonlySet<string> = new Set([\"bash-tool\", \"ci-runner\", \"mcp\", \"internal\"]);\n\n/**\n * Fail-closed parse of the callback-reported disposition. Only these two facts\n * (plus the redacted output bytes) are accepted from the callback; a contradictory\n * pair is rejected rather than repaired.\n */\nfunction parseExecutionDisposition(status: unknown, exitCode: unknown): EvidenceReceiptDisposition {\n\tif (status === \"passed\" && exitCode === 0) return { status: \"passed\", exitCode: 0 };\n\tif (status === \"failed\" && Number.isSafeInteger(exitCode) && exitCode !== 0) {\n\t\treturn { status: \"failed\", exitCode: exitCode as number };\n\t}\n\tif ((status === \"timeout\" || status === \"aborted\") && exitCode === null) {\n\t\treturn { status, exitCode: null };\n\t}\n\tthrow new VerifiedEvidenceExecutorError(\"execution callback returned an invalid disposition\");\n}\n\nfunction assertRequestText(value: unknown, label: string): void {\n\tif (typeof value !== \"string\" || value.length === 0 || value.includes(\"\\0\")) {\n\t\tthrow new VerifiedEvidenceExecutorError(`${label} must be a non-empty string without NUL bytes`);\n\t}\n}\n\nfunction validateRequest(request: VerifiedEvidenceExecutionRequest): void {\n\tassertRequestText(request.goalId, \"goalId\");\n\tif (request.laneId !== undefined) assertRequestText(request.laneId, \"laneId\");\n\tassertRequestText(request.claim, \"claim\");\n\tassertRequestText(request.cwd, \"cwd\");\n\tif (request.toolCallId !== undefined) assertRequestText(request.toolCallId, \"toolCallId\");\n\tif (request.timeoutMs !== null && (!Number.isSafeInteger(request.timeoutMs) || request.timeoutMs <= 0)) {\n\t\tthrow new VerifiedEvidenceExecutorError(\"timeoutMs must be null or a positive safe integer\");\n\t}\n\tif (!EVIDENCE_EXECUTORS.has(request.executor)) {\n\t\tthrow new VerifiedEvidenceExecutorError(\"executor is invalid\");\n\t}\n\tif (typeof request.execute !== \"function\") {\n\t\tthrow new VerifiedEvidenceExecutorError(\"execute must be a function\");\n\t}\n}\n\n/**\n * Execute one caller-provided verification operation between two scoped workspace snapshots,\n * then persist a replay-bound immutable receipt. The callback must normalize process\n * completion and provide already-redacted bounded output bytes.\n */\nexport class VerifiedEvidenceExecutor {\n\tprivate readonly store: EvidenceReceiptStore;\n\tprivate readonly ledger: ReplayLedgerManager;\n\tprivate readonly now: () => Date;\n\tprivate readonly receiptIdFactory: () => string;\n\tprivate readonly commandAttestationBinder: CommandHmacBinder;\n\tprivate readonly attestedCommands = new Map<string, EvidenceCommandDescriptor>();\n\n\tconstructor(options: VerifiedEvidenceExecutorOptions) {\n\t\tthis.store = options.store;\n\t\tthis.ledger = options.ledger;\n\t\tthis.now = options.now ?? (() => new Date());\n\t\tthis.receiptIdFactory = options.receiptIdFactory ?? randomUUID;\n\t\tthis.commandAttestationBinder = options.commandAttestationBinder ?? createCommandHmacBinder();\n\t}\n\n\treadonly resolveReceipt = (receiptId: string): EvidenceReceipt => this.store.read(receiptId);\n\n\treadonly resolveLedgerEvent = (seq: number): ReplayEvent | undefined =>\n\t\tthis.ledger.getEvents().find((event) => event.seq === seq);\n\n\treadonly resolveVerifiedLedgerSnapshot = () => this.ledger.getVerifiedSnapshot();\n\n\treadonly resolveAttestedCommand = (receiptId: string): EvidenceCommandDescriptor | undefined => {\n\t\tconst command = this.attestedCommands.get(receiptId);\n\t\treturn command === undefined ? undefined : structuredClone(command);\n\t};\n\n\t/** Latest relevant `workspace_mutation` seq from this executor's own replay ledger. */\n\treadonly resolveLatestWorkspaceMutationSeq = (scope: WorkspaceScope): number | null =>\n\t\tlatestRelevantWorkspaceMutationSeq(this.ledger.getEvents(), scope);\n\n\tcreateGateOptions(): Pick<\n\t\tEvidenceGateOptions,\n\t\t| \"resolveReceipt\"\n\t\t| \"resolveLedgerEvent\"\n\t\t| \"resolveVerifiedLedgerSnapshot\"\n\t\t| \"captureWorkspaceFingerprint\"\n\t\t| \"resolveLatestWorkspaceMutationSeq\"\n\t\t| \"commandAttestationBinder\"\n\t\t| \"resolveAttestedCommand\"\n\t> {\n\t\treturn {\n\t\t\tresolveReceipt: this.resolveReceipt,\n\t\t\tresolveLedgerEvent: this.resolveLedgerEvent,\n\t\t\tresolveVerifiedLedgerSnapshot: this.resolveVerifiedLedgerSnapshot,\n\t\t\tcaptureWorkspaceFingerprint,\n\t\t\tresolveLatestWorkspaceMutationSeq: this.resolveLatestWorkspaceMutationSeq,\n\t\t\tcommandAttestationBinder: this.commandAttestationBinder,\n\t\t\tresolveAttestedCommand: this.resolveAttestedCommand,\n\t\t};\n\t}\n\n\tasync execute(request: VerifiedEvidenceExecutionRequest): Promise<VerifiedEvidenceExecutionResult> {\n\t\tvalidateRequest(request);\n\t\tconst ledgerGoalId = this.ledger.getLedger().goalId;\n\t\tif (request.goalId !== ledgerGoalId) {\n\t\t\tthrow new VerifiedEvidenceExecutorError(\"verification goalId does not match the replay ledger\");\n\t\t}\n\t\tconst receiptId = this.receiptIdFactory();\n\t\tif (!isSafeEvidenceReceiptId(receiptId)) {\n\t\t\tthrow new VerifiedEvidenceExecutorError(\"receiptIdFactory returned an unsafe receipt ID\");\n\t\t}\n\t\t// Tokenize the persisted representation and bind the ORIGINAL command under the\n\t\t// internal process key BEFORE any side effect; unrepresentable or oversize\n\t\t// redaction metadata fails closed here. The original is never persisted.\n\t\tconst redaction = redactCommandDescriptor(request.command);\n\t\tconst commandBinding = this.commandAttestationBinder.bind(request.command);\n\t\tconst receiptCommandSha256 = computeEvidenceCommandSha256(redaction.command);\n\t\tconst workspaceBefore = captureWorkspaceFingerprint(request.workspaceScope);\n\t\tconst started = this.now();\n\t\tconst startedMs = started.getTime();\n\t\tif (!Number.isSafeInteger(startedMs)) {\n\t\t\tthrow new VerifiedEvidenceExecutorError(\"verification clock produced an invalid start time\");\n\t\t}\n\t\tconst outcome = await request.execute();\n\t\t// Independent-review P3: accept ONLY the disposition and the already-redacted\n\t\t// output bytes from the callback. Every other receipt field is executor-captured\n\t\t// provenance that a callback outcome object must never be able to override.\n\t\tconst { status, exitCode, alreadyRedactedOutput } = outcome;\n\t\tconst disposition = parseExecutionDisposition(status, exitCode);\n\t\tconst finished = this.now();\n\t\tconst finishedMs = finished.getTime();\n\t\tif (!Number.isSafeInteger(finishedMs) || finishedMs < startedMs) {\n\t\t\tthrow new VerifiedEvidenceExecutorError(\"verification clock produced an invalid execution interval\");\n\t\t}\n\t\tconst workspaceAfter = captureWorkspaceFingerprint(request.workspaceScope);\n\t\tconst receipt = createEvidenceReceipt({\n\t\t\treceiptId,\n\t\t\tgoalId: request.goalId,\n\t\t\t...(request.laneId !== undefined ? { laneId: request.laneId } : {}),\n\t\t\tclaim: request.claim,\n\t\t\tcommand: redaction.command,\n\t\t\tcommandRedaction: redaction.summary,\n\t\t\tcommandBinding,\n\t\t\tcwd: request.cwd,\n\t\t\ttimeoutMs: request.timeoutMs,\n\t\t\tstartedAt: started.toISOString(),\n\t\t\tfinishedAt: finished.toISOString(),\n\t\t\tdurationMs: finishedMs - startedMs,\n\t\t\tworkspaceBefore,\n\t\t\tworkspaceAfter,\n\t\t\texecutor: request.executor,\n\t\t\t...disposition,\n\t\t\talreadyRedactedOutput,\n\t\t\t...(request.toolCallId !== undefined ? { toolCallId: request.toolCallId } : {}),\n\t\t});\n\t\tconst event = this.ledger.append({\n\t\t\ttype: \"evidence_receipt\",\n\t\t\tgoalId: request.goalId,\n\t\t\t...(request.laneId !== undefined ? { laneId: request.laneId } : {}),\n\t\t\tpayload: evidenceReceiptReplayPayload(receipt),\n\t\t});\n\t\tthis.ledger.persist();\n\t\tconst boundReceipt = withEvidenceReceiptEnvelope(receipt, {\n\t\t\tledgerBinding: { seq: event.seq, eventHash: parseSha256Hex(event.eventHash, \"ledger eventHash\") },\n\t\t});\n\t\tconst receiptPath = this.store.write(boundReceipt);\n\t\tthis.attestedCommands.set(receiptId, structuredClone(request.command));\n\t\treturn {\n\t\t\treceipt: boundReceipt,\n\t\t\treceiptPath,\n\t\t\tevidenceMetadata: {\n\t\t\t\treceiptId,\n\t\t\t\treceiptSchemaVersion: 3,\n\t\t\t\treceiptCommandSha256,\n\t\t\t\t...(request.laneId !== undefined ? { receiptLaneId: request.laneId } : {}),\n\t\t\t\ttimestamp: finished.toISOString(),\n\t\t\t},\n\t\t};\n\t}\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ArtifactState, GitWorkspaceState, Sha256Hex, WorkspaceFingerprint, WorkspaceScope } from "../types/evidence.ts";
|
|
2
|
+
export type WorkspaceFingerprintCaptureFaultStage = "before-artifact-open";
|
|
3
|
+
export interface WorkspaceFingerprintCaptureOptions {
|
|
4
|
+
/** Tests only: mutate a selected path immediately before its artifact is opened. */
|
|
5
|
+
readonly faultInjector?: (stage: WorkspaceFingerprintCaptureFaultStage, artifactPath: string) => void;
|
|
6
|
+
}
|
|
7
|
+
/** Compute the domain-separated digest for an already validated canonical manifest. */
|
|
8
|
+
export declare function computeWorkspaceManifestSha256(scope: WorkspaceScope, artifacts: readonly ArtifactState[]): Sha256Hex;
|
|
9
|
+
/** Canonical digest committing sorted changed paths, both diff digests, and every selected artifact state. */
|
|
10
|
+
export declare function computeGitWorkspaceDirtySha256(changedPaths: readonly string[], stagedDiffSha256: Sha256Hex, unstagedDiffSha256: Sha256Hex, artifacts: readonly ArtifactState[]): Sha256Hex;
|
|
11
|
+
/** Compute the domain-separated digest for an already validated canonical git manifest. */
|
|
12
|
+
export declare function computeGitWorkspaceManifestSha256(scope: WorkspaceScope, artifacts: readonly ArtifactState[], git: GitWorkspaceState): Sha256Hex;
|
|
13
|
+
/** Strictly parse and validate a fingerprint of either kind, including its canonical digests. */
|
|
14
|
+
export declare function parseWorkspaceFingerprint(value: unknown): WorkspaceFingerprint;
|
|
15
|
+
/**
|
|
16
|
+
* Hash the selected workspace scope. When the trusted root is itself a git work-tree
|
|
17
|
+
* top level, capture HEAD plus a scope-limited canonical dirty digest; otherwise hash
|
|
18
|
+
* the caller-selected artifact set directly and perform no git commands.
|
|
19
|
+
* Phase A assumes a trusted, quiescent workspace: pathname identity checks reject
|
|
20
|
+
* observed rebinds, but do not prove an immutable snapshot against a same-UID adversary.
|
|
21
|
+
*/
|
|
22
|
+
export declare function captureWorkspaceFingerprint(scope: WorkspaceScope, options?: WorkspaceFingerprintCaptureOptions): WorkspaceFingerprint;
|
|
23
|
+
//# sourceMappingURL=workspace-fingerprint.d.ts.map
|