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
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
import { inspectTranscriptIntegrity } from "omk-agent-core";
|
|
2
|
+
export const SESSION_REVISION_TOKEN_SCHEMA_VERSION = 1;
|
|
3
|
+
export const COMPACTION_TRANSACTION_SCHEMA_VERSION = 1;
|
|
4
|
+
export const COMPACTION_ENVELOPE_SCHEMA_VERSION = 2;
|
|
5
|
+
const MAX_METADATA_LENGTH = 256;
|
|
6
|
+
const MAX_FILE_IDENTITY_LENGTH = 32;
|
|
7
|
+
const MAX_SOURCE_ENTRIES = 4096;
|
|
8
|
+
const MAX_PROVENANCE_IDS = 1024;
|
|
9
|
+
const MAX_LATEST_INTENT_LENGTH = 16_384;
|
|
10
|
+
const MAX_SUMMARY_LENGTH = 262_144;
|
|
11
|
+
const MAX_PROVENANCE_TEXT_LENGTH = 4096;
|
|
12
|
+
const MAX_NEXT_ACTION_LENGTH = 4096;
|
|
13
|
+
const MAX_MODEL_HISTORY_ENTRIES = 256;
|
|
14
|
+
const EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
15
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
|
|
16
|
+
const DECIMAL_STRING_PATTERN = /^[0-9]+$/u;
|
|
17
|
+
const CANONICAL_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
|
|
18
|
+
const METADATA_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/u;
|
|
19
|
+
const CONTENT_CONTROL_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
|
|
20
|
+
const CREDENTIAL_SHAPE_PATTERN = /-----BEGIN [A-Z ]*PRIVATE KEY-----|(?:^|[\s"'`:])bearer\s+[A-Za-z0-9._~+/-]{8,}|(?:api[-_ ]?key|access[-_ ]?token|refresh[-_ ]?token|password|passwd|client[-_ ]?secret|secret[-_ ]?(?:key|token))\s*[:=]\s*["']?[^\s"',;]{3,}|(?:sk|ghp|gho|ghu|ghs|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{8,}/iu;
|
|
21
|
+
function isRecord(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function assertOnlyKeys(value, allowed, field) {
|
|
25
|
+
const allowedKeys = new Set(allowed);
|
|
26
|
+
if (Object.keys(value).some((key) => !allowedKeys.has(key))) {
|
|
27
|
+
throw new TypeError(`${field} contains unsupported metadata`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function assertSafeInteger(value, field) {
|
|
31
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
32
|
+
throw new TypeError(`${field} must be a non-negative safe integer`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function assertMetadataText(value, field, maxLength = MAX_METADATA_LENGTH) {
|
|
36
|
+
if (typeof value !== "string" ||
|
|
37
|
+
value.length === 0 ||
|
|
38
|
+
value.length > maxLength ||
|
|
39
|
+
METADATA_CONTROL_PATTERN.test(value)) {
|
|
40
|
+
throw new TypeError(`${field} must be non-empty bounded metadata without control characters`);
|
|
41
|
+
}
|
|
42
|
+
if (CREDENTIAL_SHAPE_PATTERN.test(value)) {
|
|
43
|
+
throw new TypeError(`${field} must not contain credential-shaped metadata`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function assertContentText(value, field, maxLength) {
|
|
47
|
+
if (typeof value !== "string" ||
|
|
48
|
+
value.length === 0 ||
|
|
49
|
+
value.length > maxLength ||
|
|
50
|
+
CONTENT_CONTROL_PATTERN.test(value)) {
|
|
51
|
+
throw new TypeError(`${field} must be non-empty bounded text without unsafe control characters`);
|
|
52
|
+
}
|
|
53
|
+
if (CREDENTIAL_SHAPE_PATTERN.test(value)) {
|
|
54
|
+
throw new TypeError(`${field} must not contain credential-shaped content`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function assertSha256(value, field) {
|
|
58
|
+
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
59
|
+
throw new TypeError(`${field} must be a lowercase 64-hex SHA-256 digest`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function assertCanonicalTimestamp(value) {
|
|
63
|
+
assertMetadataText(value, "createdAt");
|
|
64
|
+
if (!CANONICAL_TIMESTAMP_PATTERN.test(value)) {
|
|
65
|
+
throw new TypeError("createdAt must be a canonical UTC ISO-8601 timestamp");
|
|
66
|
+
}
|
|
67
|
+
const parsed = new Date(value);
|
|
68
|
+
if (!Number.isFinite(parsed.valueOf()) || parsed.toISOString() !== value) {
|
|
69
|
+
throw new TypeError("createdAt must be a canonical UTC ISO-8601 timestamp");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function copyFileIdentity(value) {
|
|
73
|
+
if (value === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
if (!isRecord(value))
|
|
76
|
+
throw new TypeError("fileIdentity must be an object");
|
|
77
|
+
assertOnlyKeys(value, ["dev", "ino"], "fileIdentity");
|
|
78
|
+
for (const field of ["dev", "ino"]) {
|
|
79
|
+
const part = value[field];
|
|
80
|
+
if (typeof part !== "string" ||
|
|
81
|
+
part.length === 0 ||
|
|
82
|
+
part.length > MAX_FILE_IDENTITY_LENGTH ||
|
|
83
|
+
!DECIMAL_STRING_PATTERN.test(part)) {
|
|
84
|
+
throw new TypeError(`fileIdentity.${field} must be a bounded decimal string`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return Object.freeze({ dev: value.dev, ino: value.ino });
|
|
88
|
+
}
|
|
89
|
+
function copyIdArray(value, field, maxItems) {
|
|
90
|
+
if (!Array.isArray(value) || value.length > maxItems) {
|
|
91
|
+
throw new TypeError(`${field} must be a bounded array`);
|
|
92
|
+
}
|
|
93
|
+
const copy = value.map((item, index) => {
|
|
94
|
+
assertMetadataText(item, `${field}[${index}]`);
|
|
95
|
+
return item;
|
|
96
|
+
});
|
|
97
|
+
if (new Set(copy).size !== copy.length)
|
|
98
|
+
throw new TypeError(`${field} must contain unique IDs`);
|
|
99
|
+
return Object.freeze(copy);
|
|
100
|
+
}
|
|
101
|
+
function assertProvenanceText(value, field, maxLength) {
|
|
102
|
+
if (typeof value !== "string" ||
|
|
103
|
+
value.length === 0 ||
|
|
104
|
+
value.length > maxLength ||
|
|
105
|
+
CONTENT_CONTROL_PATTERN.test(value)) {
|
|
106
|
+
throw new TypeError(`${field} must be non-empty bounded text without unsafe control characters`);
|
|
107
|
+
}
|
|
108
|
+
if (CREDENTIAL_SHAPE_PATTERN.test(value)) {
|
|
109
|
+
throw new TypeError(`${field} must not contain credential-shaped content`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function assertNullableMetadataText(value, field, maxLength = MAX_METADATA_LENGTH) {
|
|
113
|
+
if (value === null)
|
|
114
|
+
return;
|
|
115
|
+
assertMetadataText(value, field, maxLength);
|
|
116
|
+
}
|
|
117
|
+
function copyTextArray(value, field, maxItems, maxLength, requireUnique) {
|
|
118
|
+
if (!Array.isArray(value) || value.length > maxItems) {
|
|
119
|
+
throw new TypeError(`${field} must be a bounded array`);
|
|
120
|
+
}
|
|
121
|
+
const copy = value.map((item, index) => {
|
|
122
|
+
assertProvenanceText(item, `${field}[${index}]`, maxLength);
|
|
123
|
+
return item;
|
|
124
|
+
});
|
|
125
|
+
if (requireUnique && new Set(copy).size !== copy.length) {
|
|
126
|
+
throw new TypeError(`${field} must contain unique entries`);
|
|
127
|
+
}
|
|
128
|
+
return Object.freeze(copy);
|
|
129
|
+
}
|
|
130
|
+
function copyModelHistory(value) {
|
|
131
|
+
if (!Array.isArray(value) || value.length > MAX_MODEL_HISTORY_ENTRIES) {
|
|
132
|
+
throw new TypeError("preserved.modelHistory must be a bounded array");
|
|
133
|
+
}
|
|
134
|
+
const seenEntryIds = new Set();
|
|
135
|
+
const copy = value.map((item, index) => {
|
|
136
|
+
if (!isRecord(item))
|
|
137
|
+
throw new TypeError(`preserved.modelHistory[${index}] must be an object`);
|
|
138
|
+
assertOnlyKeys(item, ["entryId", "provider", "modelId"], `preserved.modelHistory[${index}]`);
|
|
139
|
+
assertMetadataText(item.entryId, `preserved.modelHistory[${index}].entryId`);
|
|
140
|
+
assertMetadataText(item.provider, `preserved.modelHistory[${index}].provider`);
|
|
141
|
+
assertMetadataText(item.modelId, `preserved.modelHistory[${index}].modelId`);
|
|
142
|
+
const entryId = item.entryId;
|
|
143
|
+
if (seenEntryIds.has(entryId))
|
|
144
|
+
throw new TypeError("preserved.modelHistory must contain unique entryId values");
|
|
145
|
+
seenEntryIds.add(entryId);
|
|
146
|
+
return Object.freeze({ entryId, provider: item.provider, modelId: item.modelId });
|
|
147
|
+
});
|
|
148
|
+
return Object.freeze(copy);
|
|
149
|
+
}
|
|
150
|
+
function copyPreservedProvenance(input) {
|
|
151
|
+
if (!isRecord(input))
|
|
152
|
+
throw new TypeError("preserved must be an object");
|
|
153
|
+
assertOnlyKeys(input, [
|
|
154
|
+
"latestIntent",
|
|
155
|
+
"openTasks",
|
|
156
|
+
"laneIds",
|
|
157
|
+
"acceptancePredicateIds",
|
|
158
|
+
"evidenceReceiptIds",
|
|
159
|
+
"blockerReasons",
|
|
160
|
+
"repairEventIds",
|
|
161
|
+
"branch",
|
|
162
|
+
"worktree",
|
|
163
|
+
"modelHistory",
|
|
164
|
+
"nextAction",
|
|
165
|
+
], "preserved");
|
|
166
|
+
assertProvenanceText(input.latestIntent, "preserved.latestIntent", MAX_LATEST_INTENT_LENGTH);
|
|
167
|
+
const openTasks = copyTextArray(input.openTasks, "preserved.openTasks", MAX_PROVENANCE_IDS, MAX_PROVENANCE_TEXT_LENGTH, false);
|
|
168
|
+
const laneIds = copyTextArray(input.laneIds, "preserved.laneIds", MAX_PROVENANCE_IDS, MAX_METADATA_LENGTH, true);
|
|
169
|
+
const acceptancePredicateIds = copyTextArray(input.acceptancePredicateIds, "preserved.acceptancePredicateIds", MAX_PROVENANCE_IDS, MAX_METADATA_LENGTH, true);
|
|
170
|
+
const evidenceReceiptIds = copyTextArray(input.evidenceReceiptIds, "preserved.evidenceReceiptIds", MAX_PROVENANCE_IDS, MAX_METADATA_LENGTH, true);
|
|
171
|
+
const blockerReasons = copyTextArray(input.blockerReasons, "preserved.blockerReasons", MAX_PROVENANCE_IDS, MAX_PROVENANCE_TEXT_LENGTH, false);
|
|
172
|
+
const repairEventIds = copyTextArray(input.repairEventIds, "preserved.repairEventIds", MAX_PROVENANCE_IDS, MAX_METADATA_LENGTH, true);
|
|
173
|
+
assertNullableMetadataText(input.branch, "preserved.branch", MAX_PROVENANCE_TEXT_LENGTH);
|
|
174
|
+
assertNullableMetadataText(input.worktree, "preserved.worktree", MAX_PROVENANCE_TEXT_LENGTH);
|
|
175
|
+
const modelHistory = copyModelHistory(input.modelHistory);
|
|
176
|
+
assertProvenanceText(input.nextAction, "preserved.nextAction", MAX_NEXT_ACTION_LENGTH);
|
|
177
|
+
return Object.freeze({
|
|
178
|
+
latestIntent: input.latestIntent,
|
|
179
|
+
openTasks,
|
|
180
|
+
laneIds,
|
|
181
|
+
acceptancePredicateIds,
|
|
182
|
+
evidenceReceiptIds,
|
|
183
|
+
blockerReasons,
|
|
184
|
+
repairEventIds,
|
|
185
|
+
branch: input.branch,
|
|
186
|
+
worktree: input.worktree,
|
|
187
|
+
modelHistory,
|
|
188
|
+
nextAction: input.nextAction,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
export function createSessionRevisionToken(input) {
|
|
192
|
+
if (!isRecord(input))
|
|
193
|
+
throw new TypeError("session revision token must be an object");
|
|
194
|
+
assertOnlyKeys(input, [
|
|
195
|
+
"schemaVersion",
|
|
196
|
+
"sessionId",
|
|
197
|
+
"completeBytes",
|
|
198
|
+
"recordCount",
|
|
199
|
+
"leafId",
|
|
200
|
+
"lastEntryId",
|
|
201
|
+
"completePrefixSha256",
|
|
202
|
+
"fileIdentity",
|
|
203
|
+
], "session revision token");
|
|
204
|
+
if (input.schemaVersion !== undefined && input.schemaVersion !== SESSION_REVISION_TOKEN_SCHEMA_VERSION) {
|
|
205
|
+
throw new TypeError("unsupported session revision token schema version");
|
|
206
|
+
}
|
|
207
|
+
assertMetadataText(input.sessionId, "sessionId");
|
|
208
|
+
assertSafeInteger(input.completeBytes, "completeBytes");
|
|
209
|
+
assertSafeInteger(input.recordCount, "recordCount");
|
|
210
|
+
if (input.leafId !== null)
|
|
211
|
+
assertMetadataText(input.leafId, "leafId");
|
|
212
|
+
if (input.lastEntryId !== null)
|
|
213
|
+
assertMetadataText(input.lastEntryId, "lastEntryId");
|
|
214
|
+
assertSha256(input.completePrefixSha256, "completePrefixSha256");
|
|
215
|
+
if ((input.completeBytes === 0) !== (input.recordCount === 0)) {
|
|
216
|
+
throw new TypeError("completeBytes and recordCount must both be empty or both be non-empty");
|
|
217
|
+
}
|
|
218
|
+
if (input.recordCount === 0 && (input.leafId !== null || input.lastEntryId !== null)) {
|
|
219
|
+
throw new TypeError("an empty revision cannot identify entries");
|
|
220
|
+
}
|
|
221
|
+
if (input.leafId !== null && input.lastEntryId === null) {
|
|
222
|
+
throw new TypeError("leafId requires a lastEntryId");
|
|
223
|
+
}
|
|
224
|
+
if (input.completeBytes === 0 && input.completePrefixSha256 !== EMPTY_SHA256) {
|
|
225
|
+
throw new TypeError("an empty revision must use the SHA-256 digest of empty bytes");
|
|
226
|
+
}
|
|
227
|
+
const fileIdentity = copyFileIdentity(input.fileIdentity);
|
|
228
|
+
return Object.freeze({
|
|
229
|
+
schemaVersion: SESSION_REVISION_TOKEN_SCHEMA_VERSION,
|
|
230
|
+
sessionId: input.sessionId,
|
|
231
|
+
completeBytes: input.completeBytes,
|
|
232
|
+
recordCount: input.recordCount,
|
|
233
|
+
leafId: input.leafId,
|
|
234
|
+
lastEntryId: input.lastEntryId,
|
|
235
|
+
completePrefixSha256: input.completePrefixSha256,
|
|
236
|
+
...(fileIdentity === undefined ? {} : { fileIdentity }),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
export function createCompactionSourceIdentity(input) {
|
|
240
|
+
if (!isRecord(input))
|
|
241
|
+
throw new TypeError("compaction source identity must be an object");
|
|
242
|
+
assertOnlyKeys(input, ["sessionId", "entryIds", "firstEntryId", "lastEntryId", "sourceSha256", "activeLeafId", "messageCount"], "compaction source identity");
|
|
243
|
+
assertMetadataText(input.sessionId, "source.sessionId");
|
|
244
|
+
const entryIds = copyIdArray(input.entryIds, "source.entryIds", MAX_SOURCE_ENTRIES);
|
|
245
|
+
if (entryIds.length === 0)
|
|
246
|
+
throw new TypeError("source.entryIds must be non-empty");
|
|
247
|
+
assertMetadataText(input.firstEntryId, "source.firstEntryId");
|
|
248
|
+
assertMetadataText(input.lastEntryId, "source.lastEntryId");
|
|
249
|
+
assertMetadataText(input.activeLeafId, "source.activeLeafId");
|
|
250
|
+
assertSha256(input.sourceSha256, "source.sourceSha256");
|
|
251
|
+
assertSafeInteger(input.messageCount, "source.messageCount");
|
|
252
|
+
if (input.messageCount > entryIds.length) {
|
|
253
|
+
throw new TypeError("source.messageCount cannot exceed source.entryIds length");
|
|
254
|
+
}
|
|
255
|
+
if (input.firstEntryId !== entryIds[0] || input.lastEntryId !== entryIds.at(-1)) {
|
|
256
|
+
throw new TypeError("source first/last IDs must match the ordered entry IDs");
|
|
257
|
+
}
|
|
258
|
+
// R30 P2: active leaf must be the last entry in the source range
|
|
259
|
+
if (input.activeLeafId !== input.lastEntryId) {
|
|
260
|
+
throw new TypeError("source.activeLeafId must match source.lastEntryId");
|
|
261
|
+
}
|
|
262
|
+
return Object.freeze({
|
|
263
|
+
sessionId: input.sessionId,
|
|
264
|
+
entryIds,
|
|
265
|
+
firstEntryId: input.firstEntryId,
|
|
266
|
+
lastEntryId: input.lastEntryId,
|
|
267
|
+
sourceSha256: input.sourceSha256,
|
|
268
|
+
activeLeafId: input.activeLeafId,
|
|
269
|
+
messageCount: input.messageCount,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function copyModelIdentity(value) {
|
|
273
|
+
if (!isRecord(value))
|
|
274
|
+
throw new TypeError("model must be an object");
|
|
275
|
+
assertOnlyKeys(value, ["provider", "id"], "model");
|
|
276
|
+
assertMetadataText(value.provider, "model.provider");
|
|
277
|
+
assertMetadataText(value.id, "model.id");
|
|
278
|
+
return Object.freeze({ provider: value.provider, id: value.id });
|
|
279
|
+
}
|
|
280
|
+
export function createCompactionTransaction(input) {
|
|
281
|
+
if (!isRecord(input))
|
|
282
|
+
throw new TypeError("compaction transaction must be an object");
|
|
283
|
+
assertOnlyKeys(input, ["schemaVersion", "transactionId", "baseRevision", "source", "createdAt", "model", "preserved"], "compaction transaction");
|
|
284
|
+
if (input.schemaVersion !== undefined && input.schemaVersion !== COMPACTION_TRANSACTION_SCHEMA_VERSION) {
|
|
285
|
+
throw new TypeError("unsupported compaction transaction schema version");
|
|
286
|
+
}
|
|
287
|
+
assertMetadataText(input.transactionId, "transactionId");
|
|
288
|
+
assertCanonicalTimestamp(input.createdAt);
|
|
289
|
+
const baseRevision = createSessionRevisionToken(input.baseRevision);
|
|
290
|
+
const source = createCompactionSourceIdentity(input.source);
|
|
291
|
+
if (baseRevision.sessionId !== source.sessionId || baseRevision.leafId !== source.activeLeafId) {
|
|
292
|
+
throw new TypeError("transaction revision and source must identify the same session and active leaf");
|
|
293
|
+
}
|
|
294
|
+
const model = copyModelIdentity(input.model);
|
|
295
|
+
const preserved = copyPreservedProvenance(input.preserved);
|
|
296
|
+
return Object.freeze({
|
|
297
|
+
schemaVersion: COMPACTION_TRANSACTION_SCHEMA_VERSION,
|
|
298
|
+
transactionId: input.transactionId,
|
|
299
|
+
baseRevision,
|
|
300
|
+
source,
|
|
301
|
+
createdAt: input.createdAt,
|
|
302
|
+
model,
|
|
303
|
+
preserved,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
const KNOWN_FINDING_REASONS = new Set([
|
|
307
|
+
"trailing_fragment",
|
|
308
|
+
"invalid_utf8",
|
|
309
|
+
"malformed_json",
|
|
310
|
+
"missing_header",
|
|
311
|
+
"invalid_header",
|
|
312
|
+
"unsupported_header",
|
|
313
|
+
"multiple_header",
|
|
314
|
+
"late_header",
|
|
315
|
+
"invalid_entry",
|
|
316
|
+
"unsupported_entry",
|
|
317
|
+
"duplicate_entry_id",
|
|
318
|
+
"missing_parent",
|
|
319
|
+
"self_cycle",
|
|
320
|
+
"parent_cycle",
|
|
321
|
+
"active_leaf_missing",
|
|
322
|
+
"compaction_first_kept_not_ancestor",
|
|
323
|
+
"active_branch_context_invalid",
|
|
324
|
+
"transcript_missing_result",
|
|
325
|
+
"transcript_duplicate_result",
|
|
326
|
+
"transcript_orphan_result",
|
|
327
|
+
"transcript_duplicate_call_id",
|
|
328
|
+
"transcript_interleaved_non_result",
|
|
329
|
+
]);
|
|
330
|
+
const TRANSCRIPT_FINDING_REASONS = new Set([
|
|
331
|
+
"transcript_missing_result",
|
|
332
|
+
"transcript_duplicate_result",
|
|
333
|
+
"transcript_orphan_result",
|
|
334
|
+
"transcript_duplicate_call_id",
|
|
335
|
+
"transcript_interleaved_non_result",
|
|
336
|
+
]);
|
|
337
|
+
const HEADER_FINDING_REASONS = new Set([
|
|
338
|
+
"missing_header",
|
|
339
|
+
"invalid_header",
|
|
340
|
+
"unsupported_header",
|
|
341
|
+
"multiple_header",
|
|
342
|
+
"late_header",
|
|
343
|
+
]);
|
|
344
|
+
const TRANSCRIPT_ISSUE_KINDS = new Set([
|
|
345
|
+
"missing_result",
|
|
346
|
+
"duplicate_result",
|
|
347
|
+
"orphan_result",
|
|
348
|
+
"duplicate_call_id",
|
|
349
|
+
"interleaved_non_result",
|
|
350
|
+
]);
|
|
351
|
+
function messageToolCalls(message) {
|
|
352
|
+
if (message.role !== "assistant")
|
|
353
|
+
return [];
|
|
354
|
+
return message.content.flatMap((block) => (block.type === "toolCall" ? [{ id: block.id, name: block.name }] : []));
|
|
355
|
+
}
|
|
356
|
+
function transcriptFindingReason(issue) {
|
|
357
|
+
switch (issue.kind) {
|
|
358
|
+
case "missing_result":
|
|
359
|
+
return "transcript_missing_result";
|
|
360
|
+
case "duplicate_result":
|
|
361
|
+
return "transcript_duplicate_result";
|
|
362
|
+
case "orphan_result":
|
|
363
|
+
return "transcript_orphan_result";
|
|
364
|
+
case "duplicate_call_id":
|
|
365
|
+
return "transcript_duplicate_call_id";
|
|
366
|
+
case "interleaved_non_result":
|
|
367
|
+
return "transcript_interleaved_non_result";
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function issueIdentity(issue) {
|
|
371
|
+
return JSON.stringify([issue.kind, issue.toolCallId, issue.toolName ?? null]);
|
|
372
|
+
}
|
|
373
|
+
function findingIdentity(finding) {
|
|
374
|
+
return JSON.stringify([
|
|
375
|
+
finding.reason,
|
|
376
|
+
finding.line ?? null,
|
|
377
|
+
finding.entryId ?? null,
|
|
378
|
+
finding.parentId ?? null,
|
|
379
|
+
finding.cycleEntryIds ?? null,
|
|
380
|
+
finding.toolCallId ?? null,
|
|
381
|
+
finding.toolName ?? null,
|
|
382
|
+
]);
|
|
383
|
+
}
|
|
384
|
+
function sameMultiset(left, right) {
|
|
385
|
+
if (left.length !== right.length)
|
|
386
|
+
return false;
|
|
387
|
+
const counts = new Map();
|
|
388
|
+
for (const item of left)
|
|
389
|
+
counts.set(item, (counts.get(item) ?? 0) + 1);
|
|
390
|
+
for (const item of right) {
|
|
391
|
+
const count = counts.get(item);
|
|
392
|
+
if (count === undefined)
|
|
393
|
+
return false;
|
|
394
|
+
if (count === 1)
|
|
395
|
+
counts.delete(item);
|
|
396
|
+
else
|
|
397
|
+
counts.set(item, count - 1);
|
|
398
|
+
}
|
|
399
|
+
return counts.size === 0;
|
|
400
|
+
}
|
|
401
|
+
function validByteDigest(value, completePrefix) {
|
|
402
|
+
if (!isRecord(value))
|
|
403
|
+
return false;
|
|
404
|
+
const keys = completePrefix ? ["byteCount", "sha256", "lineCount"] : ["byteCount", "sha256"];
|
|
405
|
+
if (Object.keys(value).some((key) => !keys.includes(key)))
|
|
406
|
+
return false;
|
|
407
|
+
if (!Number.isSafeInteger(value.byteCount) || value.byteCount < 0)
|
|
408
|
+
return false;
|
|
409
|
+
if (typeof value.sha256 !== "string" || !SHA256_PATTERN.test(value.sha256))
|
|
410
|
+
return false;
|
|
411
|
+
if (!completePrefix)
|
|
412
|
+
return true;
|
|
413
|
+
return Number.isSafeInteger(value.lineCount) && value.lineCount >= 0;
|
|
414
|
+
}
|
|
415
|
+
function validFinding(value) {
|
|
416
|
+
if (!isRecord(value) || typeof value.reason !== "string")
|
|
417
|
+
return false;
|
|
418
|
+
if (!KNOWN_FINDING_REASONS.has(value.reason))
|
|
419
|
+
return false;
|
|
420
|
+
const allowed = ["reason", "line", "entryId", "parentId", "cycleEntryIds", "toolCallId", "toolName"];
|
|
421
|
+
if (Object.keys(value).some((key) => !allowed.includes(key)))
|
|
422
|
+
return false;
|
|
423
|
+
if (value.line !== undefined && (!Number.isSafeInteger(value.line) || value.line < 1))
|
|
424
|
+
return false;
|
|
425
|
+
for (const field of ["entryId", "parentId", "toolCallId", "toolName"]) {
|
|
426
|
+
if (value[field] !== undefined && typeof value[field] !== "string")
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
return (value.cycleEntryIds === undefined ||
|
|
430
|
+
(Array.isArray(value.cycleEntryIds) && value.cycleEntryIds.every((item) => typeof item === "string")));
|
|
431
|
+
}
|
|
432
|
+
function validTranscriptIssue(value) {
|
|
433
|
+
if (!isRecord(value) || !TRANSCRIPT_ISSUE_KINDS.has(value.kind))
|
|
434
|
+
return false;
|
|
435
|
+
if (typeof value.toolCallId !== "string" || value.toolCallId.length === 0)
|
|
436
|
+
return false;
|
|
437
|
+
if (value.toolName !== undefined && typeof value.toolName !== "string")
|
|
438
|
+
return false;
|
|
439
|
+
return Object.keys(value).every((key) => ["kind", "toolCallId", "toolName"].includes(key));
|
|
440
|
+
}
|
|
441
|
+
function activeBranchIsConsistent(report) {
|
|
442
|
+
if (!Array.isArray(report.entries) || !Array.isArray(report.activeBranch) || !Array.isArray(report.activeMessages)) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
const entryIds = new Set();
|
|
446
|
+
for (const entry of report.entries) {
|
|
447
|
+
if (!isRecord(entry) || typeof entry.id !== "string" || entry.id.length === 0)
|
|
448
|
+
return false;
|
|
449
|
+
entryIds.add(entry.id);
|
|
450
|
+
}
|
|
451
|
+
if (report.activeLeafId === null)
|
|
452
|
+
return report.activeBranch.length === 0;
|
|
453
|
+
if (typeof report.activeLeafId !== "string" || report.activeBranch.length === 0)
|
|
454
|
+
return false;
|
|
455
|
+
const branchIds = new Set();
|
|
456
|
+
for (let index = 0; index < report.activeBranch.length; index++) {
|
|
457
|
+
const entry = report.activeBranch[index];
|
|
458
|
+
if (!isRecord(entry) || typeof entry.id !== "string" || branchIds.has(entry.id) || !entryIds.has(entry.id)) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
branchIds.add(entry.id);
|
|
462
|
+
const expectedParent = index === 0 ? null : report.activeBranch[index - 1]?.id;
|
|
463
|
+
if (entry.parentId !== expectedParent)
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
return report.activeBranch.at(-1)?.id === report.activeLeafId;
|
|
467
|
+
}
|
|
468
|
+
function reportIsConsistent(report) {
|
|
469
|
+
if (!isRecord(report))
|
|
470
|
+
return false;
|
|
471
|
+
if (!validByteDigest(report.source, false) || !validByteDigest(report.completePrefix, true))
|
|
472
|
+
return false;
|
|
473
|
+
if (report.trailingFragment !== null && !validByteDigest(report.trailingFragment, false))
|
|
474
|
+
return false;
|
|
475
|
+
const trailingBytes = report.trailingFragment?.byteCount ?? 0;
|
|
476
|
+
if (report.source.byteCount !== report.completePrefix.byteCount + trailingBytes)
|
|
477
|
+
return false;
|
|
478
|
+
if (report.completePrefix.lineCount > report.completePrefix.byteCount)
|
|
479
|
+
return false;
|
|
480
|
+
if (report.completePrefix.byteCount === 0 && report.completePrefix.sha256 !== EMPTY_SHA256)
|
|
481
|
+
return false;
|
|
482
|
+
if (report.trailingFragment === null && report.source.sha256 !== report.completePrefix.sha256)
|
|
483
|
+
return false;
|
|
484
|
+
if (!Array.isArray(report.findings) || !report.findings.every(validFinding))
|
|
485
|
+
return false;
|
|
486
|
+
if (typeof report.ok !== "boolean" || report.ok !== (report.findings.length === 0))
|
|
487
|
+
return false;
|
|
488
|
+
const trailingFindings = report.findings.filter((finding) => finding.reason === "trailing_fragment");
|
|
489
|
+
if (trailingFindings.length !== (report.trailingFragment === null ? 0 : 1) ||
|
|
490
|
+
trailingFindings.some((finding) => Object.keys(finding).length !== 1)) {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
if (report.header === null) {
|
|
494
|
+
if (!report.findings.some((finding) => HEADER_FINDING_REASONS.has(finding.reason)))
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
else if (!isRecord(report.header) || typeof report.header.id !== "string" || report.header.id.length === 0) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
if (!activeBranchIsConsistent(report))
|
|
501
|
+
return false;
|
|
502
|
+
const transcriptFindings = report.findings.filter((finding) => TRANSCRIPT_FINDING_REASONS.has(finding.reason));
|
|
503
|
+
if (report.transcript === null) {
|
|
504
|
+
// R30 P1-2: null transcript can never produce ready.
|
|
505
|
+
// A producer-consistent null transcript requires activeMessages to be empty
|
|
506
|
+
// and a structural finding to explain why no transcript was produced.
|
|
507
|
+
if (report.activeMessages.length > 0)
|
|
508
|
+
return false;
|
|
509
|
+
if (transcriptFindings.length !== 0)
|
|
510
|
+
return false;
|
|
511
|
+
const hasStructuralFinding = report.findings.some((finding) => finding.reason !== "trailing_fragment" && !TRANSCRIPT_FINDING_REASONS.has(finding.reason));
|
|
512
|
+
return hasStructuralFinding;
|
|
513
|
+
}
|
|
514
|
+
if (!isRecord(report.transcript) || !Array.isArray(report.transcript.issues))
|
|
515
|
+
return false;
|
|
516
|
+
if (!report.transcript.issues.every(validTranscriptIssue))
|
|
517
|
+
return false;
|
|
518
|
+
if (report.transcript.ok !== (report.transcript.issues.length === 0))
|
|
519
|
+
return false;
|
|
520
|
+
if (report.findings.some((finding) => finding.reason !== "trailing_fragment" && !TRANSCRIPT_FINDING_REASONS.has(finding.reason))) {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
let inspected;
|
|
524
|
+
try {
|
|
525
|
+
inspected = inspectTranscriptIntegrity(report.activeMessages);
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
if (inspected.ok !== report.transcript.ok ||
|
|
531
|
+
!sameMultiset(inspected.issues.map(issueIdentity), report.transcript.issues.map(issueIdentity))) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
return sameMultiset(transcriptFindings.map(findingIdentity), inspected.issues.map((issue) => JSON.stringify([
|
|
535
|
+
transcriptFindingReason(issue),
|
|
536
|
+
null,
|
|
537
|
+
null,
|
|
538
|
+
null,
|
|
539
|
+
null,
|
|
540
|
+
issue.toolCallId,
|
|
541
|
+
issue.toolName ?? null,
|
|
542
|
+
])));
|
|
543
|
+
}
|
|
544
|
+
function missingCallsAreAtActiveTail(report, missingIds) {
|
|
545
|
+
let lastAssistantIndex = -1;
|
|
546
|
+
for (let index = report.activeMessages.length - 1; index >= 0; index--) {
|
|
547
|
+
if (report.activeMessages[index]?.role === "assistant") {
|
|
548
|
+
lastAssistantIndex = index;
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (lastAssistantIndex === -1)
|
|
553
|
+
return false;
|
|
554
|
+
const assistant = report.activeMessages[lastAssistantIndex];
|
|
555
|
+
if (!assistant || assistant.role !== "assistant")
|
|
556
|
+
return false;
|
|
557
|
+
const tailCallIds = new Set(messageToolCalls(assistant).map((call) => call.id));
|
|
558
|
+
if ([...missingIds].some((id) => !tailCallIds.has(id)))
|
|
559
|
+
return false;
|
|
560
|
+
for (let index = lastAssistantIndex + 1; index < report.activeMessages.length; index++) {
|
|
561
|
+
const message = report.activeMessages[index];
|
|
562
|
+
if (!message || message.role !== "toolResult" || !tailCallIds.has(message.toolCallId))
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
function barrierResult(status, reason, pendingToolCallIds, missingToolCallIds = []) {
|
|
568
|
+
return Object.freeze({
|
|
569
|
+
status,
|
|
570
|
+
reason,
|
|
571
|
+
pendingToolCallIds: Object.freeze([...pendingToolCallIds]),
|
|
572
|
+
missingToolCallIds: Object.freeze([...missingToolCallIds]),
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
/** Derive a closed-turn compaction barrier without repairing or mutating the report. */
|
|
576
|
+
export function evaluateCompactionBarrier(report, pendingToolCallIds) {
|
|
577
|
+
let pending;
|
|
578
|
+
try {
|
|
579
|
+
pending = copyIdArray(pendingToolCallIds, "pendingToolCallIds", MAX_PROVENANCE_IDS);
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
return barrierResult("fail_closed", "invalid_pending_tool_ids", []);
|
|
583
|
+
}
|
|
584
|
+
if (!reportIsConsistent(report)) {
|
|
585
|
+
return barrierResult("fail_closed", "inconsistent_integrity_report", pending);
|
|
586
|
+
}
|
|
587
|
+
const blockingFinding = report.findings.find((finding) => finding.reason !== "transcript_missing_result");
|
|
588
|
+
const blockingIssue = report.transcript?.issues.find((issue) => issue.kind !== "missing_result");
|
|
589
|
+
if (blockingFinding || blockingIssue) {
|
|
590
|
+
return barrierResult("fail_closed", "structural_integrity_failure", pending);
|
|
591
|
+
}
|
|
592
|
+
const missing = Object.freeze((report.transcript?.issues ?? [])
|
|
593
|
+
.filter((issue) => issue.kind === "missing_result")
|
|
594
|
+
.map((issue) => issue.toolCallId));
|
|
595
|
+
const missingSet = new Set(missing);
|
|
596
|
+
const pendingSet = new Set(pending);
|
|
597
|
+
if (missing.length > 0 &&
|
|
598
|
+
(!missingCallsAreAtActiveTail(report, missingSet) || missing.some((toolCallId) => !pendingSet.has(toolCallId)))) {
|
|
599
|
+
return barrierResult("fail_closed", "unsafe_missing_tool_results", pending, missing);
|
|
600
|
+
}
|
|
601
|
+
if (missing.length > 0)
|
|
602
|
+
return barrierResult("defer", "missing_active_tail_results", pending, missing);
|
|
603
|
+
if (pending.length > 0)
|
|
604
|
+
return barrierResult("defer", "pending_tool_calls", pending);
|
|
605
|
+
return barrierResult("ready", "closed_active_branch", []);
|
|
606
|
+
}
|
|
607
|
+
function sameRevision(left, right) {
|
|
608
|
+
return (left.schemaVersion === right.schemaVersion &&
|
|
609
|
+
left.sessionId === right.sessionId &&
|
|
610
|
+
left.completeBytes === right.completeBytes &&
|
|
611
|
+
left.recordCount === right.recordCount &&
|
|
612
|
+
left.leafId === right.leafId &&
|
|
613
|
+
left.lastEntryId === right.lastEntryId &&
|
|
614
|
+
left.completePrefixSha256 === right.completePrefixSha256 &&
|
|
615
|
+
left.fileIdentity?.dev === right.fileIdentity?.dev &&
|
|
616
|
+
left.fileIdentity?.ino === right.fileIdentity?.ino);
|
|
617
|
+
}
|
|
618
|
+
function sameSource(left, right) {
|
|
619
|
+
return (left.sessionId === right.sessionId &&
|
|
620
|
+
left.firstEntryId === right.firstEntryId &&
|
|
621
|
+
left.lastEntryId === right.lastEntryId &&
|
|
622
|
+
left.sourceSha256 === right.sourceSha256 &&
|
|
623
|
+
left.activeLeafId === right.activeLeafId &&
|
|
624
|
+
left.messageCount === right.messageCount &&
|
|
625
|
+
left.entryIds.length === right.entryIds.length &&
|
|
626
|
+
left.entryIds.every((id, index) => id === right.entryIds[index]));
|
|
627
|
+
}
|
|
628
|
+
function validBarrierResult(barrier) {
|
|
629
|
+
if (!isRecord(barrier) || !Array.isArray(barrier.pendingToolCallIds) || !Array.isArray(barrier.missingToolCallIds)) {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
const pending = barrier.pendingToolCallIds;
|
|
633
|
+
const missing = barrier.missingToolCallIds;
|
|
634
|
+
// R30 P1-3: validate all IDs are bounded, unique, safe metadata text
|
|
635
|
+
try {
|
|
636
|
+
for (const ids of [pending, missing]) {
|
|
637
|
+
if (ids.length > MAX_PROVENANCE_IDS)
|
|
638
|
+
return false;
|
|
639
|
+
if (new Set(ids).size !== ids.length)
|
|
640
|
+
return false;
|
|
641
|
+
for (const id of ids)
|
|
642
|
+
assertMetadataText(id, "barrier toolCallId");
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
catch {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
switch (barrier.status) {
|
|
649
|
+
case "ready":
|
|
650
|
+
return barrier.reason === "closed_active_branch" && pending.length === 0 && missing.length === 0;
|
|
651
|
+
case "defer":
|
|
652
|
+
if (barrier.reason === "pending_tool_calls") {
|
|
653
|
+
return pending.length > 0 && missing.length === 0;
|
|
654
|
+
}
|
|
655
|
+
if (barrier.reason === "missing_active_tail_results") {
|
|
656
|
+
return missing.length > 0 && missing.every((id) => pending.includes(id));
|
|
657
|
+
}
|
|
658
|
+
return false;
|
|
659
|
+
case "fail_closed":
|
|
660
|
+
return (barrier.reason === "invalid_pending_tool_ids" ||
|
|
661
|
+
barrier.reason === "inconsistent_integrity_report" ||
|
|
662
|
+
barrier.reason === "structural_integrity_failure" ||
|
|
663
|
+
barrier.reason === "unsafe_missing_tool_results");
|
|
664
|
+
default:
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Authorize an in-memory commit attempt. A "commit" decision is not evidence of
|
|
670
|
+
* durable persistence; the integration layer must still perform its own atomic CAS write.
|
|
671
|
+
*/
|
|
672
|
+
export function decideCompactionCommit(input) {
|
|
673
|
+
let transaction;
|
|
674
|
+
try {
|
|
675
|
+
transaction = createCompactionTransaction(input.transaction);
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
return Object.freeze({ decision: "fail_closed", reason: "invalid_transaction" });
|
|
679
|
+
}
|
|
680
|
+
if (!validBarrierResult(input.barrier)) {
|
|
681
|
+
return Object.freeze({
|
|
682
|
+
decision: "fail_closed",
|
|
683
|
+
reason: "invalid_commit_input",
|
|
684
|
+
transactionId: transaction.transactionId,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
if (input.barrier.status === "fail_closed") {
|
|
688
|
+
return Object.freeze({
|
|
689
|
+
decision: "fail_closed",
|
|
690
|
+
reason: "barrier_fail_closed",
|
|
691
|
+
transactionId: transaction.transactionId,
|
|
692
|
+
barrierReason: input.barrier.reason,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
if (input.barrier.status === "defer") {
|
|
696
|
+
return Object.freeze({
|
|
697
|
+
decision: "defer",
|
|
698
|
+
reason: "barrier_defer",
|
|
699
|
+
transactionId: transaction.transactionId,
|
|
700
|
+
barrierReason: input.barrier.reason,
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
let currentRevision;
|
|
704
|
+
let currentSource;
|
|
705
|
+
let priorDigests;
|
|
706
|
+
try {
|
|
707
|
+
currentRevision = createSessionRevisionToken(input.currentRevision);
|
|
708
|
+
currentSource = createCompactionSourceIdentity(input.currentSource);
|
|
709
|
+
if (!Array.isArray(input.priorCommittedSourceDigests) ||
|
|
710
|
+
input.priorCommittedSourceDigests.length > MAX_SOURCE_ENTRIES) {
|
|
711
|
+
throw new TypeError("priorCommittedSourceDigests must be a bounded array");
|
|
712
|
+
}
|
|
713
|
+
priorDigests = input.priorCommittedSourceDigests.map((digest, index) => {
|
|
714
|
+
assertSha256(digest, `priorCommittedSourceDigests[${index}]`);
|
|
715
|
+
return digest;
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
return Object.freeze({
|
|
720
|
+
decision: "fail_closed",
|
|
721
|
+
reason: "invalid_commit_input",
|
|
722
|
+
transactionId: transaction.transactionId,
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
if (priorDigests.includes(transaction.source.sourceSha256)) {
|
|
726
|
+
return Object.freeze({
|
|
727
|
+
decision: "duplicate",
|
|
728
|
+
reason: "source_already_committed",
|
|
729
|
+
transactionId: transaction.transactionId,
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
if (!sameRevision(transaction.baseRevision, currentRevision)) {
|
|
733
|
+
return Object.freeze({
|
|
734
|
+
decision: "stale",
|
|
735
|
+
reason: "revision_mismatch",
|
|
736
|
+
transactionId: transaction.transactionId,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
if (!sameSource(transaction.source, currentSource)) {
|
|
740
|
+
return Object.freeze({
|
|
741
|
+
decision: "stale",
|
|
742
|
+
reason: "source_mismatch",
|
|
743
|
+
transactionId: transaction.transactionId,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
return Object.freeze({
|
|
747
|
+
decision: "commit",
|
|
748
|
+
reason: "exact_match",
|
|
749
|
+
transactionId: transaction.transactionId,
|
|
750
|
+
revision: currentRevision,
|
|
751
|
+
source: currentSource,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
const COMPACTION_ENVELOPE_KEYS = [
|
|
755
|
+
"schemaVersion",
|
|
756
|
+
"transactionId",
|
|
757
|
+
"baseRevision",
|
|
758
|
+
"source",
|
|
759
|
+
"createdAt",
|
|
760
|
+
"model",
|
|
761
|
+
"summary",
|
|
762
|
+
"summarySha256",
|
|
763
|
+
"preserved",
|
|
764
|
+
];
|
|
765
|
+
/** Parse and deep-freeze a persisted schema-v2 compaction envelope. */
|
|
766
|
+
export function validateCompactionEnvelope(input) {
|
|
767
|
+
if (!isRecord(input))
|
|
768
|
+
throw new TypeError("compaction envelope must be an object");
|
|
769
|
+
assertOnlyKeys(input, COMPACTION_ENVELOPE_KEYS, "compaction envelope");
|
|
770
|
+
if (input.schemaVersion !== COMPACTION_ENVELOPE_SCHEMA_VERSION) {
|
|
771
|
+
throw new TypeError("unsupported compaction envelope schema version");
|
|
772
|
+
}
|
|
773
|
+
assertMetadataText(input.transactionId, "transactionId");
|
|
774
|
+
assertCanonicalTimestamp(input.createdAt);
|
|
775
|
+
assertContentText(input.summary, "summary", MAX_SUMMARY_LENGTH);
|
|
776
|
+
assertSha256(input.summarySha256, "summarySha256");
|
|
777
|
+
const baseRevision = createSessionRevisionToken(input.baseRevision);
|
|
778
|
+
const source = createCompactionSourceIdentity(input.source);
|
|
779
|
+
if (baseRevision.sessionId !== source.sessionId || baseRevision.leafId !== source.activeLeafId) {
|
|
780
|
+
throw new TypeError("envelope revision and source must identify the same session and active leaf");
|
|
781
|
+
}
|
|
782
|
+
return Object.freeze({
|
|
783
|
+
schemaVersion: COMPACTION_ENVELOPE_SCHEMA_VERSION,
|
|
784
|
+
transactionId: input.transactionId,
|
|
785
|
+
baseRevision,
|
|
786
|
+
source,
|
|
787
|
+
createdAt: input.createdAt,
|
|
788
|
+
model: copyModelIdentity(input.model),
|
|
789
|
+
summary: input.summary,
|
|
790
|
+
summarySha256: input.summarySha256,
|
|
791
|
+
preserved: copyPreservedProvenance(input.preserved),
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
/** Create provenance-bearing summary data only from an exact commit authorization. */
|
|
795
|
+
export function createCompactionEnvelope(input) {
|
|
796
|
+
if (!isRecord(input))
|
|
797
|
+
throw new TypeError("compaction envelope input must be an object");
|
|
798
|
+
assertOnlyKeys(input, ["transaction", "decision", "summary", "summarySha256"], "compaction envelope input");
|
|
799
|
+
assertContentText(input.summary, "summary", MAX_SUMMARY_LENGTH);
|
|
800
|
+
assertSha256(input.summarySha256, "summarySha256");
|
|
801
|
+
if (input.decision.decision !== "commit" || input.decision.reason !== "exact_match") {
|
|
802
|
+
throw new TypeError("a commit decision is required to create a compaction envelope");
|
|
803
|
+
}
|
|
804
|
+
const transaction = createCompactionTransaction(input.transaction);
|
|
805
|
+
const decisionRevision = createSessionRevisionToken(input.decision.revision);
|
|
806
|
+
const decisionSource = createCompactionSourceIdentity(input.decision.source);
|
|
807
|
+
if (input.decision.transactionId !== transaction.transactionId ||
|
|
808
|
+
!sameRevision(transaction.baseRevision, decisionRevision) ||
|
|
809
|
+
!sameSource(transaction.source, decisionSource)) {
|
|
810
|
+
throw new TypeError("commit decision does not match the compaction transaction");
|
|
811
|
+
}
|
|
812
|
+
return validateCompactionEnvelope({
|
|
813
|
+
schemaVersion: COMPACTION_ENVELOPE_SCHEMA_VERSION,
|
|
814
|
+
transactionId: transaction.transactionId,
|
|
815
|
+
baseRevision: transaction.baseRevision,
|
|
816
|
+
source: transaction.source,
|
|
817
|
+
createdAt: transaction.createdAt,
|
|
818
|
+
model: transaction.model,
|
|
819
|
+
summary: input.summary,
|
|
820
|
+
summarySha256: input.summarySha256,
|
|
821
|
+
preserved: transaction.preserved,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
//# sourceMappingURL=transaction.js.map
|