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,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe, pure context-cache invalidation snapshot helper.
|
|
3
|
+
*
|
|
4
|
+
* Every public function is side-effect free and depends only on
|
|
5
|
+
* platform-agnostic primitives. Returned snapshots are always
|
|
6
|
+
* independently deep-frozen copies of the caller-supplied data, so
|
|
7
|
+
* callers cannot mutate cached state through retained references.
|
|
8
|
+
*
|
|
9
|
+
* Snapshots are cache-key material for invalidation tracking, not an
|
|
10
|
+
* authorization or authentication primitive.
|
|
11
|
+
*
|
|
12
|
+
* Schema: context-cache-invalidation-v1.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION: "context-cache-invalidation-v1";
|
|
15
|
+
export declare const CONTEXT_CACHE_INVALIDATION_SAFE_ID_MAX_LENGTH = 256;
|
|
16
|
+
/** Event counters tracked by an invalidation snapshot. */
|
|
17
|
+
export interface ContextCacheInvalidationCounters {
|
|
18
|
+
readonly transcriptRepair: number;
|
|
19
|
+
readonly toolResultDisposition: number;
|
|
20
|
+
readonly evidenceReceipt: number;
|
|
21
|
+
readonly userSteering: number;
|
|
22
|
+
readonly settings: number;
|
|
23
|
+
}
|
|
24
|
+
/** Immutable schema-v1 invalidation snapshot. */
|
|
25
|
+
export interface ContextCacheInvalidationSnapshot {
|
|
26
|
+
readonly schemaVersion: typeof CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION;
|
|
27
|
+
readonly forkId: string;
|
|
28
|
+
readonly globalEpoch: number;
|
|
29
|
+
readonly counters: ContextCacheInvalidationCounters;
|
|
30
|
+
readonly worktreeFingerprint: string;
|
|
31
|
+
readonly activeModelId: string;
|
|
32
|
+
readonly compactionModelId: string;
|
|
33
|
+
}
|
|
34
|
+
/** Constructor input for a fresh snapshot. Counters and epoch default to 0. */
|
|
35
|
+
export interface ContextCacheInvalidationSnapshotInit {
|
|
36
|
+
readonly forkId: string;
|
|
37
|
+
readonly worktreeFingerprint: string;
|
|
38
|
+
readonly activeModelId: string;
|
|
39
|
+
readonly compactionModelId: string;
|
|
40
|
+
readonly globalEpoch?: number;
|
|
41
|
+
readonly transcriptRepair?: number;
|
|
42
|
+
readonly toolResultDisposition?: number;
|
|
43
|
+
readonly evidenceReceipt?: number;
|
|
44
|
+
readonly userSteering?: number;
|
|
45
|
+
readonly settings?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Strict union covering all eight invalidation events. The five counter
|
|
49
|
+
* events increment their counter; the three identity events set a value.
|
|
50
|
+
*/
|
|
51
|
+
export type ContextCacheInvalidationEvent = {
|
|
52
|
+
readonly type: "transcriptRepair";
|
|
53
|
+
} | {
|
|
54
|
+
readonly type: "toolResultDisposition";
|
|
55
|
+
} | {
|
|
56
|
+
readonly type: "evidenceReceipt";
|
|
57
|
+
} | {
|
|
58
|
+
readonly type: "userSteering";
|
|
59
|
+
} | {
|
|
60
|
+
readonly type: "settings";
|
|
61
|
+
} | {
|
|
62
|
+
readonly type: "worktreeFingerprint";
|
|
63
|
+
readonly value: string;
|
|
64
|
+
} | {
|
|
65
|
+
readonly type: "activeModelId";
|
|
66
|
+
readonly value: string;
|
|
67
|
+
} | {
|
|
68
|
+
readonly type: "compactionModelId";
|
|
69
|
+
readonly value: string;
|
|
70
|
+
};
|
|
71
|
+
export type ContextCacheInvalidationStatus = "applied" | "unchanged" | "overflow";
|
|
72
|
+
export interface ContextCacheInvalidationResult {
|
|
73
|
+
readonly status: ContextCacheInvalidationStatus;
|
|
74
|
+
readonly snapshot: ContextCacheInvalidationSnapshot;
|
|
75
|
+
}
|
|
76
|
+
export type ContextCacheMergeStatus = "equal" | "dominant" | "divergent";
|
|
77
|
+
export type ContextCacheMergeResult = {
|
|
78
|
+
readonly status: "equal";
|
|
79
|
+
readonly snapshot: ContextCacheInvalidationSnapshot;
|
|
80
|
+
} | {
|
|
81
|
+
readonly status: "dominant";
|
|
82
|
+
readonly snapshot: ContextCacheInvalidationSnapshot;
|
|
83
|
+
} | {
|
|
84
|
+
readonly status: "divergent";
|
|
85
|
+
readonly left: ContextCacheInvalidationSnapshot;
|
|
86
|
+
readonly right: ContextCacheInvalidationSnapshot;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Strictly validate that {@link input} is a structurally correct snapshot.
|
|
90
|
+
* Rejects extra keys, missing keys, malformed counters, unsafe integers,
|
|
91
|
+
* and control/credential-unsafe identifier strings. Does not require the
|
|
92
|
+
* object to be frozen.
|
|
93
|
+
*/
|
|
94
|
+
export declare function validateContextCacheInvalidationSnapshot(input: unknown): asserts input is ContextCacheInvalidationSnapshot;
|
|
95
|
+
/** Non-throwing variant of {@link validateContextCacheInvalidationSnapshot}. */
|
|
96
|
+
export declare function isContextCacheInvalidationSnapshot(input: unknown): input is ContextCacheInvalidationSnapshot;
|
|
97
|
+
/** Build a validated, deep-frozen initial snapshot. */
|
|
98
|
+
export declare function createContextCacheInvalidationSnapshot(init: ContextCacheInvalidationSnapshotInit): ContextCacheInvalidationSnapshot;
|
|
99
|
+
/**
|
|
100
|
+
* Apply a single invalidation event. Increments the global epoch and the
|
|
101
|
+
* relevant counter, or sets an identity. A same-value identity set is a
|
|
102
|
+
* no-op and returns {@link ContextCacheInvalidationStatus.unchanged} without
|
|
103
|
+
* an epoch bump. Any counter or epoch overflow returns
|
|
104
|
+
* {@link ContextCacheInvalidationStatus.overflow} with the snapshot unchanged
|
|
105
|
+
* and never wraps.
|
|
106
|
+
*/
|
|
107
|
+
export declare function applyContextCacheInvalidation(current: ContextCacheInvalidationSnapshot, event: ContextCacheInvalidationEvent): ContextCacheInvalidationResult;
|
|
108
|
+
/**
|
|
109
|
+
* Fork a snapshot: keep all event counters and identity values, change the
|
|
110
|
+
* fork id, and reset the global epoch to 0. The caller supplies the new fork
|
|
111
|
+
* id. Rejects an id equal to the current fork id.
|
|
112
|
+
*/
|
|
113
|
+
export declare function forkContextCacheSnapshot(current: ContextCacheInvalidationSnapshot, nextForkId: string): ContextCacheInvalidationSnapshot;
|
|
114
|
+
/**
|
|
115
|
+
* Fail-closed merge of two snapshots.
|
|
116
|
+
*
|
|
117
|
+
* - Different fork ids, or two snapshots that are not on a shared linear
|
|
118
|
+
* history, yield {@link ContextCacheMergeStatus.divergent}. Disjoint
|
|
119
|
+
* changes are never max-merged.
|
|
120
|
+
* - Structurally equal snapshots yield {@link ContextCacheMergeStatus.equal}.
|
|
121
|
+
* - When one snapshot componentwise-dominates the other (epoch and every
|
|
122
|
+
* counter greater-or-equal, and all identity values equal), the dominant
|
|
123
|
+
* snapshot is chosen via {@link ContextCacheMergeStatus.dominant}.
|
|
124
|
+
*/
|
|
125
|
+
export declare function mergeContextCacheSnapshots(a: ContextCacheInvalidationSnapshot, b: ContextCacheInvalidationSnapshot): ContextCacheMergeResult;
|
|
126
|
+
/**
|
|
127
|
+
* Canonical, fixed-order JSON serialization of a snapshot, suitable as
|
|
128
|
+
* future cache-key material. The field order is fixed and every distinct
|
|
129
|
+
* field participates, so any distinct field change alters the result.
|
|
130
|
+
* Returns a deterministic string with no hash claim.
|
|
131
|
+
*/
|
|
132
|
+
export declare function serializeContextCacheSnapshot(snapshot: ContextCacheInvalidationSnapshot): string;
|
|
133
|
+
//# sourceMappingURL=context-budget-v2-cache-invalidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-budget-v2-cache-invalidation.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-v2-cache-invalidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,yCAAyC,iCAA2C,CAAC;AAClG,eAAO,MAAM,6CAA6C,MAAM,CAAC;AAEjE,0DAA0D;AAC1D,MAAM,WAAW,gCAAgC;IAChD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED,iDAAiD;AACjD,MAAM,WAAW,gCAAgC;IAChD,QAAQ,CAAC,aAAa,EAAE,OAAO,yCAAyC,CAAC;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,gCAAgC,CAAC;IACpD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACnC;AAED,+EAA+E;AAC/E,MAAM,WAAW,oCAAoC;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GACtC;IAAE,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;CAAE,GACrC;IAAE,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAA;CAAE,GAC1C;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;CAAE,GACpC;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC7B;IAAE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,MAAM,MAAM,8BAA8B,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAElF,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,MAAM,EAAE,8BAA8B,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,gCAAgC,CAAC;CACpD;AAED,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,CAAC;AAEzE,MAAM,MAAM,uBAAuB,GAChC;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,gCAAgC,CAAA;CAAE,GACjF;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,gCAAgC,CAAA;CAAE,GACpF;IACA,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,gCAAgC,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,gCAAgC,CAAC;CAChD,CAAC;AAmLL;;;;;GAKG;AACH,wBAAgB,wCAAwC,CACvD,KAAK,EAAE,OAAO,GACZ,OAAO,CAAC,KAAK,IAAI,gCAAgC,CAwBnD;AAED,gFAAgF;AAChF,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gCAAgC,CAO5G;AA6CD,uDAAuD;AACvD,wBAAgB,sCAAsC,CACrD,IAAI,EAAE,oCAAoC,GACxC,gCAAgC,CAuBlC;AAiDD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC5C,OAAO,EAAE,gCAAgC,EACzC,KAAK,EAAE,6BAA6B,GAClC,8BAA8B,CAqBhC;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,gCAAgC,EACzC,UAAU,EAAE,MAAM,GAChB,gCAAgC,CAelC;AA8BD;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACzC,CAAC,EAAE,gCAAgC,EACnC,CAAC,EAAE,gCAAgC,GACjC,uBAAuB,CAkBzB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,gCAAgC,GAAG,MAAM,CAiBhG","sourcesContent":["/**\n * Browser-safe, pure context-cache invalidation snapshot helper.\n *\n * Every public function is side-effect free and depends only on\n * platform-agnostic primitives. Returned snapshots are always\n * independently deep-frozen copies of the caller-supplied data, so\n * callers cannot mutate cached state through retained references.\n *\n * Snapshots are cache-key material for invalidation tracking, not an\n * authorization or authentication primitive.\n *\n * Schema: context-cache-invalidation-v1.\n */\n\nexport const CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION = \"context-cache-invalidation-v1\" as const;\nexport const CONTEXT_CACHE_INVALIDATION_SAFE_ID_MAX_LENGTH = 256;\n\n/** Event counters tracked by an invalidation snapshot. */\nexport interface ContextCacheInvalidationCounters {\n\treadonly transcriptRepair: number;\n\treadonly toolResultDisposition: number;\n\treadonly evidenceReceipt: number;\n\treadonly userSteering: number;\n\treadonly settings: number;\n}\n\n/** Immutable schema-v1 invalidation snapshot. */\nexport interface ContextCacheInvalidationSnapshot {\n\treadonly schemaVersion: typeof CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION;\n\treadonly forkId: string;\n\treadonly globalEpoch: number;\n\treadonly counters: ContextCacheInvalidationCounters;\n\treadonly worktreeFingerprint: string;\n\treadonly activeModelId: string;\n\treadonly compactionModelId: string;\n}\n\n/** Constructor input for a fresh snapshot. Counters and epoch default to 0. */\nexport interface ContextCacheInvalidationSnapshotInit {\n\treadonly forkId: string;\n\treadonly worktreeFingerprint: string;\n\treadonly activeModelId: string;\n\treadonly compactionModelId: string;\n\treadonly globalEpoch?: number;\n\treadonly transcriptRepair?: number;\n\treadonly toolResultDisposition?: number;\n\treadonly evidenceReceipt?: number;\n\treadonly userSteering?: number;\n\treadonly settings?: number;\n}\n\n/**\n * Strict union covering all eight invalidation events. The five counter\n * events increment their counter; the three identity events set a value.\n */\nexport type ContextCacheInvalidationEvent =\n\t| { readonly type: \"transcriptRepair\" }\n\t| { readonly type: \"toolResultDisposition\" }\n\t| { readonly type: \"evidenceReceipt\" }\n\t| { readonly type: \"userSteering\" }\n\t| { readonly type: \"settings\" }\n\t| { readonly type: \"worktreeFingerprint\"; readonly value: string }\n\t| { readonly type: \"activeModelId\"; readonly value: string }\n\t| { readonly type: \"compactionModelId\"; readonly value: string };\n\nexport type ContextCacheInvalidationStatus = \"applied\" | \"unchanged\" | \"overflow\";\n\nexport interface ContextCacheInvalidationResult {\n\treadonly status: ContextCacheInvalidationStatus;\n\treadonly snapshot: ContextCacheInvalidationSnapshot;\n}\n\nexport type ContextCacheMergeStatus = \"equal\" | \"dominant\" | \"divergent\";\n\nexport type ContextCacheMergeResult =\n\t| { readonly status: \"equal\"; readonly snapshot: ContextCacheInvalidationSnapshot }\n\t| { readonly status: \"dominant\"; readonly snapshot: ContextCacheInvalidationSnapshot }\n\t| {\n\t\t\treadonly status: \"divergent\";\n\t\t\treadonly left: ContextCacheInvalidationSnapshot;\n\t\t\treadonly right: ContextCacheInvalidationSnapshot;\n\t };\n\ntype CounterKey = \"transcriptRepair\" | \"toolResultDisposition\" | \"evidenceReceipt\" | \"userSteering\" | \"settings\";\n\ntype IdentityKey = \"worktreeFingerprint\" | \"activeModelId\" | \"compactionModelId\";\n\nconst MAX_REPRESENTABLE = Number.MAX_SAFE_INTEGER;\nconst SAFE_ID_PATTERN = /^[\\w.\\-:+]+$/u;\n\n/**\n * High-confidence credential markers, checked in addition to the\n * safe-character gate. A value already consists only of safe characters\n * before it is tested here. Each pattern is anchored to the whole string\n * to keep the false-positive rate low; identifiers and fingerprints in\n * this domain never legitimately take these shapes.\n */\nconst CREDENTIAL_MARKERS: ReadonlyArray<RegExp> = [\n\t/^sk-[A-Za-z0-9_-]{8,}$/i, // OpenAI / generic \"sk-\" secret keys\n\t/^gh[pousr]_[A-Za-z0-9]{16,}$/i, // GitHub classic tokens (ghp_ gho_ ghu_ ghs_ ghr_)\n\t/^github_pat_[A-Za-z0-9_]{20,}$/i, // GitHub fine-grained PATs\n\t/^xox[pabrse]-[A-Za-z0-9-]{10,}$/i, // Slack tokens (xoxp xoxb xoxa xoxs xoxr xoxe)\n\t/^AKIA[0-9A-Z]{16}$/, // AWS access key id (20 chars, uppercase)\n\t/^eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}$/, // JWT (three base64url segments)\n\t/^bearer$/i, // literal Bearer token marker\n];\n\nfunction looksLikeCredential(value: string): boolean {\n\tfor (const marker of CREDENTIAL_MARKERS) {\n\t\tif (marker.test(value)) return true;\n\t}\n\treturn false;\n}\n\nconst TOP_LEVEL_KEYS: ReadonlySet<string> = new Set([\n\t\"schemaVersion\",\n\t\"forkId\",\n\t\"globalEpoch\",\n\t\"counters\",\n\t\"worktreeFingerprint\",\n\t\"activeModelId\",\n\t\"compactionModelId\",\n]);\n\nconst COUNTER_KEYS: ReadonlySet<string> = new Set([\n\t\"transcriptRepair\",\n\t\"toolResultDisposition\",\n\t\"evidenceReceipt\",\n\t\"userSteering\",\n\t\"settings\",\n]);\n\nfunction fail(reason: string): never {\n\tthrow new Error(`context-cache-invalidation: ${reason}`);\n}\n\nfunction assertSafeId(value: unknown, field: string): asserts value is string {\n\tif (typeof value !== \"string\") fail(`${field} must be a string`);\n\tif (value.length === 0) fail(`${field} must not be empty`);\n\tif (value.length > CONTEXT_CACHE_INVALIDATION_SAFE_ID_MAX_LENGTH) {\n\t\tfail(`${field} exceeds ${CONTEXT_CACHE_INVALIDATION_SAFE_ID_MAX_LENGTH} characters`);\n\t}\n\tif (!SAFE_ID_PATTERN.test(value)) fail(`${field} contains unsafe characters`);\n\tif (looksLikeCredential(value)) fail(`${field} looks like a credential and was rejected`);\n}\n\nfunction assertNonNegativeSafeInt(value: unknown, field: string): asserts value is number {\n\tif (typeof value !== \"number\") fail(`${field} must be a number`);\n\tif (!Number.isFinite(value)) fail(`${field} must be finite`);\n\tif (!Number.isInteger(value)) fail(`${field} must be an integer`);\n\tif (value < 0) fail(`${field} must be nonnegative`);\n\tif (!Number.isSafeInteger(value)) fail(`${field} must be a safe integer`);\n}\n\nfunction assertExactKeys(record: Record<string, unknown>, allowed: ReadonlySet<string>, scope: string): void {\n\tconst keys = Object.keys(record);\n\tif (keys.length !== allowed.size) fail(`${scope} has unexpected keys`);\n\tfor (const key of keys) {\n\t\tif (!allowed.has(key)) fail(`${scope} has unknown key \"${key}\"`);\n\t}\n}\n\n/** Reject non-plain inputs: null, arrays, primitives, and class instances. */\nfunction assertPlainObject(value: unknown, scope: string): asserts value is Record<string, unknown> {\n\tif (value === null || typeof value !== \"object\") fail(`${scope} must be a plain object`);\n\tif (Array.isArray(value)) fail(`${scope} must be a plain object`);\n\tconst proto = Object.getPrototypeOf(value);\n\tif (proto !== null && proto !== Object.prototype) fail(`${scope} must be a plain object`);\n}\n\n/** Reject getter/setter accessors, whose reads are not stable. */\nfunction assertNoAccessors(record: object, scope: string): void {\n\tfor (const key of Object.getOwnPropertyNames(record)) {\n\t\tconst desc = Object.getOwnPropertyDescriptor(record, key);\n\t\tif (desc !== undefined && (typeof desc.get === \"function\" || typeof desc.set === \"function\")) {\n\t\t\tfail(`${scope} must not define accessors`);\n\t\t}\n\t}\n}\n\nconst INIT_REQUIRED_KEYS: ReadonlySet<string> = new Set([\n\t\"forkId\",\n\t\"worktreeFingerprint\",\n\t\"activeModelId\",\n\t\"compactionModelId\",\n]);\n\nconst INIT_OPTIONAL_KEYS: ReadonlySet<string> = new Set([\n\t\"globalEpoch\",\n\t\"transcriptRepair\",\n\t\"toolResultDisposition\",\n\t\"evidenceReceipt\",\n\t\"userSteering\",\n\t\"settings\",\n]);\n\nconst INIT_ALLOWED_KEYS: ReadonlySet<string> = new Set([...INIT_REQUIRED_KEYS, ...INIT_OPTIONAL_KEYS]);\n\n/**\n * Strictly validate a snapshot constructor input: a plain data object with\n * exactly the required identity/fork keys plus optional counters/epoch, and\n * no accessor properties. Rejects null, arrays, class instances, unknown\n * keys, and getters/setters with a stable error.\n */\nfunction assertInitShape(input: unknown): asserts input is ContextCacheInvalidationSnapshotInit {\n\tassertPlainObject(input, \"init\");\n\tassertNoAccessors(input, \"init\");\n\tfor (const key of Object.keys(input)) {\n\t\tif (!INIT_ALLOWED_KEYS.has(key)) fail(`init has unknown key \"${key}\"`);\n\t}\n\tfor (const required of INIT_REQUIRED_KEYS) {\n\t\tif (!Object.hasOwn(input, required)) {\n\t\t\tfail(`init is missing required key \"${required}\"`);\n\t\t}\n\t}\n}\n\nconst COUNTER_EVENT_TYPES: ReadonlySet<string> = new Set([\n\t\"transcriptRepair\",\n\t\"toolResultDisposition\",\n\t\"evidenceReceipt\",\n\t\"userSteering\",\n\t\"settings\",\n]);\n\nconst IDENTITY_EVENT_TYPES: ReadonlySet<string> = new Set([\n\t\"worktreeFingerprint\",\n\t\"activeModelId\",\n\t\"compactionModelId\",\n]);\n\nconst COUNTER_EVENT_KEYS: ReadonlySet<string> = new Set([\"type\"]);\nconst IDENTITY_EVENT_KEYS: ReadonlySet<string> = new Set([\"type\", \"value\"]);\n\n/**\n * Strictly validate a runtime invalidation event before dispatch. The event\n * must be a plain data object carrying exactly the keys its discriminant\n * allows (counter events: `{ type }`; identity events: `{ type, value }`),\n * a known `type` string, and (for identity events) a safe value. Forged or\n * malformed events always throw here and can never fall through to an\n * undefined result.\n */\nfunction validateEvent(input: unknown): asserts input is ContextCacheInvalidationEvent {\n\tassertPlainObject(input, \"event\");\n\tassertNoAccessors(input, \"event\");\n\tconst type = input.type;\n\tif (type === undefined) fail(\"event is missing type\");\n\tif (typeof type !== \"string\") fail(\"event type must be a string\");\n\tif (COUNTER_EVENT_TYPES.has(type)) {\n\t\tassertExactKeys(input, COUNTER_EVENT_KEYS, \"event\");\n\t\treturn;\n\t}\n\tif (IDENTITY_EVENT_TYPES.has(type)) {\n\t\tassertExactKeys(input, IDENTITY_EVENT_KEYS, \"event\");\n\t\tassertSafeId(input.value, type);\n\t\treturn;\n\t}\n\tfail(`event type \"${type}\" is unknown`);\n}\n\n/**\n * Strictly validate that {@link input} is a structurally correct snapshot.\n * Rejects extra keys, missing keys, malformed counters, unsafe integers,\n * and control/credential-unsafe identifier strings. Does not require the\n * object to be frozen.\n */\nexport function validateContextCacheInvalidationSnapshot(\n\tinput: unknown,\n): asserts input is ContextCacheInvalidationSnapshot {\n\tif (input === null || typeof input !== \"object\" || Array.isArray(input)) {\n\t\tfail(\"snapshot must be a plain object\");\n\t}\n\tconst obj = input as Record<string, unknown>;\n\tassertExactKeys(obj, TOP_LEVEL_KEYS, \"snapshot\");\n\tif (obj.schemaVersion !== CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION) {\n\t\tfail(`schemaVersion must be ${CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION}`);\n\t}\n\tassertSafeId(obj.forkId, \"forkId\");\n\tassertNonNegativeSafeInt(obj.globalEpoch, \"globalEpoch\");\n\tif (obj.counters === null || typeof obj.counters !== \"object\" || Array.isArray(obj.counters)) {\n\t\tfail(\"counters must be a plain object\");\n\t}\n\tconst counters = obj.counters as Record<string, unknown>;\n\tassertExactKeys(counters, COUNTER_KEYS, \"counters\");\n\tassertNonNegativeSafeInt(counters.transcriptRepair, \"counters.transcriptRepair\");\n\tassertNonNegativeSafeInt(counters.toolResultDisposition, \"counters.toolResultDisposition\");\n\tassertNonNegativeSafeInt(counters.evidenceReceipt, \"counters.evidenceReceipt\");\n\tassertNonNegativeSafeInt(counters.userSteering, \"counters.userSteering\");\n\tassertNonNegativeSafeInt(counters.settings, \"counters.settings\");\n\tassertSafeId(obj.worktreeFingerprint, \"worktreeFingerprint\");\n\tassertSafeId(obj.activeModelId, \"activeModelId\");\n\tassertSafeId(obj.compactionModelId, \"compactionModelId\");\n}\n\n/** Non-throwing variant of {@link validateContextCacheInvalidationSnapshot}. */\nexport function isContextCacheInvalidationSnapshot(input: unknown): input is ContextCacheInvalidationSnapshot {\n\ttry {\n\t\tvalidateContextCacheInvalidationSnapshot(input);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\ninterface SnapshotBuilder {\n\tschemaVersion: typeof CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION;\n\tforkId: string;\n\tglobalEpoch: number;\n\tcounters: ContextCacheInvalidationCounters;\n\tworktreeFingerprint: string;\n\tactiveModelId: string;\n\tcompactionModelId: string;\n}\n\nfunction freezeSnapshot(builder: SnapshotBuilder): ContextCacheInvalidationSnapshot {\n\tObject.freeze(builder.counters);\n\treturn Object.freeze(builder) as ContextCacheInvalidationSnapshot;\n}\n\nfunction copyCounters(counters: ContextCacheInvalidationCounters): Record<CounterKey, number> {\n\treturn {\n\t\ttranscriptRepair: counters.transcriptRepair,\n\t\ttoolResultDisposition: counters.toolResultDisposition,\n\t\tevidenceReceipt: counters.evidenceReceipt,\n\t\tuserSteering: counters.userSteering,\n\t\tsettings: counters.settings,\n\t};\n}\n\nfunction cloneAndFreeze(snapshot: ContextCacheInvalidationSnapshot): ContextCacheInvalidationSnapshot {\n\treturn freezeSnapshot({\n\t\tschemaVersion: snapshot.schemaVersion,\n\t\tforkId: snapshot.forkId,\n\t\tglobalEpoch: snapshot.globalEpoch,\n\t\tcounters: copyCounters(snapshot.counters),\n\t\tworktreeFingerprint: snapshot.worktreeFingerprint,\n\t\tactiveModelId: snapshot.activeModelId,\n\t\tcompactionModelId: snapshot.compactionModelId,\n\t});\n}\n\nfunction resolveOptionalCounter(value: number | undefined, field: string): number {\n\tif (value === undefined) return 0;\n\tassertNonNegativeSafeInt(value, field);\n\treturn value;\n}\n\n/** Build a validated, deep-frozen initial snapshot. */\nexport function createContextCacheInvalidationSnapshot(\n\tinit: ContextCacheInvalidationSnapshotInit,\n): ContextCacheInvalidationSnapshot {\n\tassertInitShape(init);\n\tassertSafeId(init.forkId, \"forkId\");\n\tassertSafeId(init.worktreeFingerprint, \"worktreeFingerprint\");\n\tassertSafeId(init.activeModelId, \"activeModelId\");\n\tassertSafeId(init.compactionModelId, \"compactionModelId\");\n\tconst globalEpoch = init.globalEpoch ?? 0;\n\tassertNonNegativeSafeInt(globalEpoch, \"globalEpoch\");\n\treturn freezeSnapshot({\n\t\tschemaVersion: CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION,\n\t\tforkId: init.forkId,\n\t\tglobalEpoch,\n\t\tcounters: {\n\t\t\ttranscriptRepair: resolveOptionalCounter(init.transcriptRepair, \"transcriptRepair\"),\n\t\t\ttoolResultDisposition: resolveOptionalCounter(init.toolResultDisposition, \"toolResultDisposition\"),\n\t\t\tevidenceReceipt: resolveOptionalCounter(init.evidenceReceipt, \"evidenceReceipt\"),\n\t\t\tuserSteering: resolveOptionalCounter(init.userSteering, \"userSteering\"),\n\t\t\tsettings: resolveOptionalCounter(init.settings, \"settings\"),\n\t\t},\n\t\tworktreeFingerprint: init.worktreeFingerprint,\n\t\tactiveModelId: init.activeModelId,\n\t\tcompactionModelId: init.compactionModelId,\n\t});\n}\n\nfunction applyCounterEvent(current: ContextCacheInvalidationSnapshot, key: CounterKey): ContextCacheInvalidationResult {\n\tconst value = current.counters[key];\n\tif (value >= MAX_REPRESENTABLE || current.globalEpoch >= MAX_REPRESENTABLE) {\n\t\treturn Object.freeze({ status: \"overflow\", snapshot: cloneAndFreeze(current) } as const);\n\t}\n\tconst nextCounters = copyCounters(current.counters);\n\tnextCounters[key] = value + 1;\n\treturn Object.freeze({\n\t\tstatus: \"applied\",\n\t\tsnapshot: freezeSnapshot({\n\t\t\tschemaVersion: current.schemaVersion,\n\t\t\tforkId: current.forkId,\n\t\t\tglobalEpoch: current.globalEpoch + 1,\n\t\t\tcounters: nextCounters,\n\t\t\tworktreeFingerprint: current.worktreeFingerprint,\n\t\t\tactiveModelId: current.activeModelId,\n\t\t\tcompactionModelId: current.compactionModelId,\n\t\t}),\n\t} as const);\n}\n\nfunction applyIdentityEvent(\n\tcurrent: ContextCacheInvalidationSnapshot,\n\tkey: IdentityKey,\n\tvalue: string,\n): ContextCacheInvalidationResult {\n\tassertSafeId(value, key);\n\tif (current[key] === value) {\n\t\treturn Object.freeze({ status: \"unchanged\", snapshot: cloneAndFreeze(current) } as const);\n\t}\n\tif (current.globalEpoch >= MAX_REPRESENTABLE) {\n\t\treturn Object.freeze({ status: \"overflow\", snapshot: cloneAndFreeze(current) } as const);\n\t}\n\treturn Object.freeze({\n\t\tstatus: \"applied\",\n\t\tsnapshot: freezeSnapshot({\n\t\t\tschemaVersion: current.schemaVersion,\n\t\t\tforkId: current.forkId,\n\t\t\tglobalEpoch: current.globalEpoch + 1,\n\t\t\tcounters: copyCounters(current.counters),\n\t\t\tworktreeFingerprint: key === \"worktreeFingerprint\" ? value : current.worktreeFingerprint,\n\t\t\tactiveModelId: key === \"activeModelId\" ? value : current.activeModelId,\n\t\t\tcompactionModelId: key === \"compactionModelId\" ? value : current.compactionModelId,\n\t\t}),\n\t} as const);\n}\n\n/**\n * Apply a single invalidation event. Increments the global epoch and the\n * relevant counter, or sets an identity. A same-value identity set is a\n * no-op and returns {@link ContextCacheInvalidationStatus.unchanged} without\n * an epoch bump. Any counter or epoch overflow returns\n * {@link ContextCacheInvalidationStatus.overflow} with the snapshot unchanged\n * and never wraps.\n */\nexport function applyContextCacheInvalidation(\n\tcurrent: ContextCacheInvalidationSnapshot,\n\tevent: ContextCacheInvalidationEvent,\n): ContextCacheInvalidationResult {\n\tvalidateContextCacheInvalidationSnapshot(current);\n\tvalidateEvent(event);\n\tswitch (event.type) {\n\t\tcase \"transcriptRepair\":\n\t\tcase \"toolResultDisposition\":\n\t\tcase \"evidenceReceipt\":\n\t\tcase \"userSteering\":\n\t\tcase \"settings\":\n\t\t\treturn applyCounterEvent(current, event.type);\n\t\tcase \"worktreeFingerprint\":\n\t\tcase \"activeModelId\":\n\t\tcase \"compactionModelId\":\n\t\t\treturn applyIdentityEvent(current, event.type, event.value);\n\t\tdefault: {\n\t\t\t// Exhaustiveness guard: a new event variant without a matching\n\t\t\t// case becomes a compile error on the assignment below.\n\t\t\tconst _exhaustive: never = event;\n\t\t\tfail(`unhandled event type ${_exhaustive}`);\n\t\t}\n\t}\n}\n\n/**\n * Fork a snapshot: keep all event counters and identity values, change the\n * fork id, and reset the global epoch to 0. The caller supplies the new fork\n * id. Rejects an id equal to the current fork id.\n */\nexport function forkContextCacheSnapshot(\n\tcurrent: ContextCacheInvalidationSnapshot,\n\tnextForkId: string,\n): ContextCacheInvalidationSnapshot {\n\tvalidateContextCacheInvalidationSnapshot(current);\n\tassertSafeId(nextForkId, \"forkId\");\n\tif (nextForkId === current.forkId) {\n\t\tfail(\"forkId must differ from current fork\");\n\t}\n\treturn freezeSnapshot({\n\t\tschemaVersion: current.schemaVersion,\n\t\tforkId: nextForkId,\n\t\tglobalEpoch: 0,\n\t\tcounters: copyCounters(current.counters),\n\t\tworktreeFingerprint: current.worktreeFingerprint,\n\t\tactiveModelId: current.activeModelId,\n\t\tcompactionModelId: current.compactionModelId,\n\t});\n}\n\nfunction snapshotsEqual(a: ContextCacheInvalidationSnapshot, b: ContextCacheInvalidationSnapshot): boolean {\n\treturn (\n\t\ta.globalEpoch === b.globalEpoch &&\n\t\ta.counters.transcriptRepair === b.counters.transcriptRepair &&\n\t\ta.counters.toolResultDisposition === b.counters.toolResultDisposition &&\n\t\ta.counters.evidenceReceipt === b.counters.evidenceReceipt &&\n\t\ta.counters.userSteering === b.counters.userSteering &&\n\t\ta.counters.settings === b.counters.settings &&\n\t\ta.worktreeFingerprint === b.worktreeFingerprint &&\n\t\ta.activeModelId === b.activeModelId &&\n\t\ta.compactionModelId === b.compactionModelId\n\t);\n}\n\nfunction dominates(x: ContextCacheInvalidationSnapshot, y: ContextCacheInvalidationSnapshot): boolean {\n\treturn (\n\t\tx.globalEpoch >= y.globalEpoch &&\n\t\tx.counters.transcriptRepair >= y.counters.transcriptRepair &&\n\t\tx.counters.toolResultDisposition >= y.counters.toolResultDisposition &&\n\t\tx.counters.evidenceReceipt >= y.counters.evidenceReceipt &&\n\t\tx.counters.userSteering >= y.counters.userSteering &&\n\t\tx.counters.settings >= y.counters.settings &&\n\t\tx.worktreeFingerprint === y.worktreeFingerprint &&\n\t\tx.activeModelId === y.activeModelId &&\n\t\tx.compactionModelId === y.compactionModelId\n\t);\n}\n\n/**\n * Fail-closed merge of two snapshots.\n *\n * - Different fork ids, or two snapshots that are not on a shared linear\n * history, yield {@link ContextCacheMergeStatus.divergent}. Disjoint\n * changes are never max-merged.\n * - Structurally equal snapshots yield {@link ContextCacheMergeStatus.equal}.\n * - When one snapshot componentwise-dominates the other (epoch and every\n * counter greater-or-equal, and all identity values equal), the dominant\n * snapshot is chosen via {@link ContextCacheMergeStatus.dominant}.\n */\nexport function mergeContextCacheSnapshots(\n\ta: ContextCacheInvalidationSnapshot,\n\tb: ContextCacheInvalidationSnapshot,\n): ContextCacheMergeResult {\n\tvalidateContextCacheInvalidationSnapshot(a);\n\tvalidateContextCacheInvalidationSnapshot(b);\n\tif (a.forkId !== b.forkId) {\n\t\treturn Object.freeze({ status: \"divergent\", left: cloneAndFreeze(a), right: cloneAndFreeze(b) } as const);\n\t}\n\tif (snapshotsEqual(a, b)) {\n\t\treturn Object.freeze({ status: \"equal\", snapshot: cloneAndFreeze(a) } as const);\n\t}\n\tconst aDominates = dominates(a, b);\n\tconst bDominates = dominates(b, a);\n\tif (aDominates && !bDominates) {\n\t\treturn Object.freeze({ status: \"dominant\", snapshot: cloneAndFreeze(a) } as const);\n\t}\n\tif (bDominates && !aDominates) {\n\t\treturn Object.freeze({ status: \"dominant\", snapshot: cloneAndFreeze(b) } as const);\n\t}\n\treturn Object.freeze({ status: \"divergent\", left: cloneAndFreeze(a), right: cloneAndFreeze(b) } as const);\n}\n\n/**\n * Canonical, fixed-order JSON serialization of a snapshot, suitable as\n * future cache-key material. The field order is fixed and every distinct\n * field participates, so any distinct field change alters the result.\n * Returns a deterministic string with no hash claim.\n */\nexport function serializeContextCacheSnapshot(snapshot: ContextCacheInvalidationSnapshot): string {\n\tvalidateContextCacheInvalidationSnapshot(snapshot);\n\treturn JSON.stringify({\n\t\tschemaVersion: snapshot.schemaVersion,\n\t\tforkId: snapshot.forkId,\n\t\tglobalEpoch: snapshot.globalEpoch,\n\t\tcounters: {\n\t\t\ttranscriptRepair: snapshot.counters.transcriptRepair,\n\t\t\ttoolResultDisposition: snapshot.counters.toolResultDisposition,\n\t\t\tevidenceReceipt: snapshot.counters.evidenceReceipt,\n\t\t\tuserSteering: snapshot.counters.userSteering,\n\t\t\tsettings: snapshot.counters.settings,\n\t\t},\n\t\tworktreeFingerprint: snapshot.worktreeFingerprint,\n\t\tactiveModelId: snapshot.activeModelId,\n\t\tcompactionModelId: snapshot.compactionModelId,\n\t});\n}\n"]}
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe, pure context-cache invalidation snapshot helper.
|
|
3
|
+
*
|
|
4
|
+
* Every public function is side-effect free and depends only on
|
|
5
|
+
* platform-agnostic primitives. Returned snapshots are always
|
|
6
|
+
* independently deep-frozen copies of the caller-supplied data, so
|
|
7
|
+
* callers cannot mutate cached state through retained references.
|
|
8
|
+
*
|
|
9
|
+
* Snapshots are cache-key material for invalidation tracking, not an
|
|
10
|
+
* authorization or authentication primitive.
|
|
11
|
+
*
|
|
12
|
+
* Schema: context-cache-invalidation-v1.
|
|
13
|
+
*/
|
|
14
|
+
export const CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION = "context-cache-invalidation-v1";
|
|
15
|
+
export const CONTEXT_CACHE_INVALIDATION_SAFE_ID_MAX_LENGTH = 256;
|
|
16
|
+
const MAX_REPRESENTABLE = Number.MAX_SAFE_INTEGER;
|
|
17
|
+
const SAFE_ID_PATTERN = /^[\w.\-:+]+$/u;
|
|
18
|
+
/**
|
|
19
|
+
* High-confidence credential markers, checked in addition to the
|
|
20
|
+
* safe-character gate. A value already consists only of safe characters
|
|
21
|
+
* before it is tested here. Each pattern is anchored to the whole string
|
|
22
|
+
* to keep the false-positive rate low; identifiers and fingerprints in
|
|
23
|
+
* this domain never legitimately take these shapes.
|
|
24
|
+
*/
|
|
25
|
+
const CREDENTIAL_MARKERS = [
|
|
26
|
+
/^sk-[A-Za-z0-9_-]{8,}$/i, // OpenAI / generic "sk-" secret keys
|
|
27
|
+
/^gh[pousr]_[A-Za-z0-9]{16,}$/i, // GitHub classic tokens (ghp_ gho_ ghu_ ghs_ ghr_)
|
|
28
|
+
/^github_pat_[A-Za-z0-9_]{20,}$/i, // GitHub fine-grained PATs
|
|
29
|
+
/^xox[pabrse]-[A-Za-z0-9-]{10,}$/i, // Slack tokens (xoxp xoxb xoxa xoxs xoxr xoxe)
|
|
30
|
+
/^AKIA[0-9A-Z]{16}$/, // AWS access key id (20 chars, uppercase)
|
|
31
|
+
/^eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}$/, // JWT (three base64url segments)
|
|
32
|
+
/^bearer$/i, // literal Bearer token marker
|
|
33
|
+
];
|
|
34
|
+
function looksLikeCredential(value) {
|
|
35
|
+
for (const marker of CREDENTIAL_MARKERS) {
|
|
36
|
+
if (marker.test(value))
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const TOP_LEVEL_KEYS = new Set([
|
|
42
|
+
"schemaVersion",
|
|
43
|
+
"forkId",
|
|
44
|
+
"globalEpoch",
|
|
45
|
+
"counters",
|
|
46
|
+
"worktreeFingerprint",
|
|
47
|
+
"activeModelId",
|
|
48
|
+
"compactionModelId",
|
|
49
|
+
]);
|
|
50
|
+
const COUNTER_KEYS = new Set([
|
|
51
|
+
"transcriptRepair",
|
|
52
|
+
"toolResultDisposition",
|
|
53
|
+
"evidenceReceipt",
|
|
54
|
+
"userSteering",
|
|
55
|
+
"settings",
|
|
56
|
+
]);
|
|
57
|
+
function fail(reason) {
|
|
58
|
+
throw new Error(`context-cache-invalidation: ${reason}`);
|
|
59
|
+
}
|
|
60
|
+
function assertSafeId(value, field) {
|
|
61
|
+
if (typeof value !== "string")
|
|
62
|
+
fail(`${field} must be a string`);
|
|
63
|
+
if (value.length === 0)
|
|
64
|
+
fail(`${field} must not be empty`);
|
|
65
|
+
if (value.length > CONTEXT_CACHE_INVALIDATION_SAFE_ID_MAX_LENGTH) {
|
|
66
|
+
fail(`${field} exceeds ${CONTEXT_CACHE_INVALIDATION_SAFE_ID_MAX_LENGTH} characters`);
|
|
67
|
+
}
|
|
68
|
+
if (!SAFE_ID_PATTERN.test(value))
|
|
69
|
+
fail(`${field} contains unsafe characters`);
|
|
70
|
+
if (looksLikeCredential(value))
|
|
71
|
+
fail(`${field} looks like a credential and was rejected`);
|
|
72
|
+
}
|
|
73
|
+
function assertNonNegativeSafeInt(value, field) {
|
|
74
|
+
if (typeof value !== "number")
|
|
75
|
+
fail(`${field} must be a number`);
|
|
76
|
+
if (!Number.isFinite(value))
|
|
77
|
+
fail(`${field} must be finite`);
|
|
78
|
+
if (!Number.isInteger(value))
|
|
79
|
+
fail(`${field} must be an integer`);
|
|
80
|
+
if (value < 0)
|
|
81
|
+
fail(`${field} must be nonnegative`);
|
|
82
|
+
if (!Number.isSafeInteger(value))
|
|
83
|
+
fail(`${field} must be a safe integer`);
|
|
84
|
+
}
|
|
85
|
+
function assertExactKeys(record, allowed, scope) {
|
|
86
|
+
const keys = Object.keys(record);
|
|
87
|
+
if (keys.length !== allowed.size)
|
|
88
|
+
fail(`${scope} has unexpected keys`);
|
|
89
|
+
for (const key of keys) {
|
|
90
|
+
if (!allowed.has(key))
|
|
91
|
+
fail(`${scope} has unknown key "${key}"`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Reject non-plain inputs: null, arrays, primitives, and class instances. */
|
|
95
|
+
function assertPlainObject(value, scope) {
|
|
96
|
+
if (value === null || typeof value !== "object")
|
|
97
|
+
fail(`${scope} must be a plain object`);
|
|
98
|
+
if (Array.isArray(value))
|
|
99
|
+
fail(`${scope} must be a plain object`);
|
|
100
|
+
const proto = Object.getPrototypeOf(value);
|
|
101
|
+
if (proto !== null && proto !== Object.prototype)
|
|
102
|
+
fail(`${scope} must be a plain object`);
|
|
103
|
+
}
|
|
104
|
+
/** Reject getter/setter accessors, whose reads are not stable. */
|
|
105
|
+
function assertNoAccessors(record, scope) {
|
|
106
|
+
for (const key of Object.getOwnPropertyNames(record)) {
|
|
107
|
+
const desc = Object.getOwnPropertyDescriptor(record, key);
|
|
108
|
+
if (desc !== undefined && (typeof desc.get === "function" || typeof desc.set === "function")) {
|
|
109
|
+
fail(`${scope} must not define accessors`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const INIT_REQUIRED_KEYS = new Set([
|
|
114
|
+
"forkId",
|
|
115
|
+
"worktreeFingerprint",
|
|
116
|
+
"activeModelId",
|
|
117
|
+
"compactionModelId",
|
|
118
|
+
]);
|
|
119
|
+
const INIT_OPTIONAL_KEYS = new Set([
|
|
120
|
+
"globalEpoch",
|
|
121
|
+
"transcriptRepair",
|
|
122
|
+
"toolResultDisposition",
|
|
123
|
+
"evidenceReceipt",
|
|
124
|
+
"userSteering",
|
|
125
|
+
"settings",
|
|
126
|
+
]);
|
|
127
|
+
const INIT_ALLOWED_KEYS = new Set([...INIT_REQUIRED_KEYS, ...INIT_OPTIONAL_KEYS]);
|
|
128
|
+
/**
|
|
129
|
+
* Strictly validate a snapshot constructor input: a plain data object with
|
|
130
|
+
* exactly the required identity/fork keys plus optional counters/epoch, and
|
|
131
|
+
* no accessor properties. Rejects null, arrays, class instances, unknown
|
|
132
|
+
* keys, and getters/setters with a stable error.
|
|
133
|
+
*/
|
|
134
|
+
function assertInitShape(input) {
|
|
135
|
+
assertPlainObject(input, "init");
|
|
136
|
+
assertNoAccessors(input, "init");
|
|
137
|
+
for (const key of Object.keys(input)) {
|
|
138
|
+
if (!INIT_ALLOWED_KEYS.has(key))
|
|
139
|
+
fail(`init has unknown key "${key}"`);
|
|
140
|
+
}
|
|
141
|
+
for (const required of INIT_REQUIRED_KEYS) {
|
|
142
|
+
if (!Object.hasOwn(input, required)) {
|
|
143
|
+
fail(`init is missing required key "${required}"`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const COUNTER_EVENT_TYPES = new Set([
|
|
148
|
+
"transcriptRepair",
|
|
149
|
+
"toolResultDisposition",
|
|
150
|
+
"evidenceReceipt",
|
|
151
|
+
"userSteering",
|
|
152
|
+
"settings",
|
|
153
|
+
]);
|
|
154
|
+
const IDENTITY_EVENT_TYPES = new Set([
|
|
155
|
+
"worktreeFingerprint",
|
|
156
|
+
"activeModelId",
|
|
157
|
+
"compactionModelId",
|
|
158
|
+
]);
|
|
159
|
+
const COUNTER_EVENT_KEYS = new Set(["type"]);
|
|
160
|
+
const IDENTITY_EVENT_KEYS = new Set(["type", "value"]);
|
|
161
|
+
/**
|
|
162
|
+
* Strictly validate a runtime invalidation event before dispatch. The event
|
|
163
|
+
* must be a plain data object carrying exactly the keys its discriminant
|
|
164
|
+
* allows (counter events: `{ type }`; identity events: `{ type, value }`),
|
|
165
|
+
* a known `type` string, and (for identity events) a safe value. Forged or
|
|
166
|
+
* malformed events always throw here and can never fall through to an
|
|
167
|
+
* undefined result.
|
|
168
|
+
*/
|
|
169
|
+
function validateEvent(input) {
|
|
170
|
+
assertPlainObject(input, "event");
|
|
171
|
+
assertNoAccessors(input, "event");
|
|
172
|
+
const type = input.type;
|
|
173
|
+
if (type === undefined)
|
|
174
|
+
fail("event is missing type");
|
|
175
|
+
if (typeof type !== "string")
|
|
176
|
+
fail("event type must be a string");
|
|
177
|
+
if (COUNTER_EVENT_TYPES.has(type)) {
|
|
178
|
+
assertExactKeys(input, COUNTER_EVENT_KEYS, "event");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (IDENTITY_EVENT_TYPES.has(type)) {
|
|
182
|
+
assertExactKeys(input, IDENTITY_EVENT_KEYS, "event");
|
|
183
|
+
assertSafeId(input.value, type);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
fail(`event type "${type}" is unknown`);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Strictly validate that {@link input} is a structurally correct snapshot.
|
|
190
|
+
* Rejects extra keys, missing keys, malformed counters, unsafe integers,
|
|
191
|
+
* and control/credential-unsafe identifier strings. Does not require the
|
|
192
|
+
* object to be frozen.
|
|
193
|
+
*/
|
|
194
|
+
export function validateContextCacheInvalidationSnapshot(input) {
|
|
195
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
196
|
+
fail("snapshot must be a plain object");
|
|
197
|
+
}
|
|
198
|
+
const obj = input;
|
|
199
|
+
assertExactKeys(obj, TOP_LEVEL_KEYS, "snapshot");
|
|
200
|
+
if (obj.schemaVersion !== CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION) {
|
|
201
|
+
fail(`schemaVersion must be ${CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION}`);
|
|
202
|
+
}
|
|
203
|
+
assertSafeId(obj.forkId, "forkId");
|
|
204
|
+
assertNonNegativeSafeInt(obj.globalEpoch, "globalEpoch");
|
|
205
|
+
if (obj.counters === null || typeof obj.counters !== "object" || Array.isArray(obj.counters)) {
|
|
206
|
+
fail("counters must be a plain object");
|
|
207
|
+
}
|
|
208
|
+
const counters = obj.counters;
|
|
209
|
+
assertExactKeys(counters, COUNTER_KEYS, "counters");
|
|
210
|
+
assertNonNegativeSafeInt(counters.transcriptRepair, "counters.transcriptRepair");
|
|
211
|
+
assertNonNegativeSafeInt(counters.toolResultDisposition, "counters.toolResultDisposition");
|
|
212
|
+
assertNonNegativeSafeInt(counters.evidenceReceipt, "counters.evidenceReceipt");
|
|
213
|
+
assertNonNegativeSafeInt(counters.userSteering, "counters.userSteering");
|
|
214
|
+
assertNonNegativeSafeInt(counters.settings, "counters.settings");
|
|
215
|
+
assertSafeId(obj.worktreeFingerprint, "worktreeFingerprint");
|
|
216
|
+
assertSafeId(obj.activeModelId, "activeModelId");
|
|
217
|
+
assertSafeId(obj.compactionModelId, "compactionModelId");
|
|
218
|
+
}
|
|
219
|
+
/** Non-throwing variant of {@link validateContextCacheInvalidationSnapshot}. */
|
|
220
|
+
export function isContextCacheInvalidationSnapshot(input) {
|
|
221
|
+
try {
|
|
222
|
+
validateContextCacheInvalidationSnapshot(input);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function freezeSnapshot(builder) {
|
|
230
|
+
Object.freeze(builder.counters);
|
|
231
|
+
return Object.freeze(builder);
|
|
232
|
+
}
|
|
233
|
+
function copyCounters(counters) {
|
|
234
|
+
return {
|
|
235
|
+
transcriptRepair: counters.transcriptRepair,
|
|
236
|
+
toolResultDisposition: counters.toolResultDisposition,
|
|
237
|
+
evidenceReceipt: counters.evidenceReceipt,
|
|
238
|
+
userSteering: counters.userSteering,
|
|
239
|
+
settings: counters.settings,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function cloneAndFreeze(snapshot) {
|
|
243
|
+
return freezeSnapshot({
|
|
244
|
+
schemaVersion: snapshot.schemaVersion,
|
|
245
|
+
forkId: snapshot.forkId,
|
|
246
|
+
globalEpoch: snapshot.globalEpoch,
|
|
247
|
+
counters: copyCounters(snapshot.counters),
|
|
248
|
+
worktreeFingerprint: snapshot.worktreeFingerprint,
|
|
249
|
+
activeModelId: snapshot.activeModelId,
|
|
250
|
+
compactionModelId: snapshot.compactionModelId,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function resolveOptionalCounter(value, field) {
|
|
254
|
+
if (value === undefined)
|
|
255
|
+
return 0;
|
|
256
|
+
assertNonNegativeSafeInt(value, field);
|
|
257
|
+
return value;
|
|
258
|
+
}
|
|
259
|
+
/** Build a validated, deep-frozen initial snapshot. */
|
|
260
|
+
export function createContextCacheInvalidationSnapshot(init) {
|
|
261
|
+
assertInitShape(init);
|
|
262
|
+
assertSafeId(init.forkId, "forkId");
|
|
263
|
+
assertSafeId(init.worktreeFingerprint, "worktreeFingerprint");
|
|
264
|
+
assertSafeId(init.activeModelId, "activeModelId");
|
|
265
|
+
assertSafeId(init.compactionModelId, "compactionModelId");
|
|
266
|
+
const globalEpoch = init.globalEpoch ?? 0;
|
|
267
|
+
assertNonNegativeSafeInt(globalEpoch, "globalEpoch");
|
|
268
|
+
return freezeSnapshot({
|
|
269
|
+
schemaVersion: CONTEXT_CACHE_INVALIDATION_SCHEMA_VERSION,
|
|
270
|
+
forkId: init.forkId,
|
|
271
|
+
globalEpoch,
|
|
272
|
+
counters: {
|
|
273
|
+
transcriptRepair: resolveOptionalCounter(init.transcriptRepair, "transcriptRepair"),
|
|
274
|
+
toolResultDisposition: resolveOptionalCounter(init.toolResultDisposition, "toolResultDisposition"),
|
|
275
|
+
evidenceReceipt: resolveOptionalCounter(init.evidenceReceipt, "evidenceReceipt"),
|
|
276
|
+
userSteering: resolveOptionalCounter(init.userSteering, "userSteering"),
|
|
277
|
+
settings: resolveOptionalCounter(init.settings, "settings"),
|
|
278
|
+
},
|
|
279
|
+
worktreeFingerprint: init.worktreeFingerprint,
|
|
280
|
+
activeModelId: init.activeModelId,
|
|
281
|
+
compactionModelId: init.compactionModelId,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
function applyCounterEvent(current, key) {
|
|
285
|
+
const value = current.counters[key];
|
|
286
|
+
if (value >= MAX_REPRESENTABLE || current.globalEpoch >= MAX_REPRESENTABLE) {
|
|
287
|
+
return Object.freeze({ status: "overflow", snapshot: cloneAndFreeze(current) });
|
|
288
|
+
}
|
|
289
|
+
const nextCounters = copyCounters(current.counters);
|
|
290
|
+
nextCounters[key] = value + 1;
|
|
291
|
+
return Object.freeze({
|
|
292
|
+
status: "applied",
|
|
293
|
+
snapshot: freezeSnapshot({
|
|
294
|
+
schemaVersion: current.schemaVersion,
|
|
295
|
+
forkId: current.forkId,
|
|
296
|
+
globalEpoch: current.globalEpoch + 1,
|
|
297
|
+
counters: nextCounters,
|
|
298
|
+
worktreeFingerprint: current.worktreeFingerprint,
|
|
299
|
+
activeModelId: current.activeModelId,
|
|
300
|
+
compactionModelId: current.compactionModelId,
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function applyIdentityEvent(current, key, value) {
|
|
305
|
+
assertSafeId(value, key);
|
|
306
|
+
if (current[key] === value) {
|
|
307
|
+
return Object.freeze({ status: "unchanged", snapshot: cloneAndFreeze(current) });
|
|
308
|
+
}
|
|
309
|
+
if (current.globalEpoch >= MAX_REPRESENTABLE) {
|
|
310
|
+
return Object.freeze({ status: "overflow", snapshot: cloneAndFreeze(current) });
|
|
311
|
+
}
|
|
312
|
+
return Object.freeze({
|
|
313
|
+
status: "applied",
|
|
314
|
+
snapshot: freezeSnapshot({
|
|
315
|
+
schemaVersion: current.schemaVersion,
|
|
316
|
+
forkId: current.forkId,
|
|
317
|
+
globalEpoch: current.globalEpoch + 1,
|
|
318
|
+
counters: copyCounters(current.counters),
|
|
319
|
+
worktreeFingerprint: key === "worktreeFingerprint" ? value : current.worktreeFingerprint,
|
|
320
|
+
activeModelId: key === "activeModelId" ? value : current.activeModelId,
|
|
321
|
+
compactionModelId: key === "compactionModelId" ? value : current.compactionModelId,
|
|
322
|
+
}),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Apply a single invalidation event. Increments the global epoch and the
|
|
327
|
+
* relevant counter, or sets an identity. A same-value identity set is a
|
|
328
|
+
* no-op and returns {@link ContextCacheInvalidationStatus.unchanged} without
|
|
329
|
+
* an epoch bump. Any counter or epoch overflow returns
|
|
330
|
+
* {@link ContextCacheInvalidationStatus.overflow} with the snapshot unchanged
|
|
331
|
+
* and never wraps.
|
|
332
|
+
*/
|
|
333
|
+
export function applyContextCacheInvalidation(current, event) {
|
|
334
|
+
validateContextCacheInvalidationSnapshot(current);
|
|
335
|
+
validateEvent(event);
|
|
336
|
+
switch (event.type) {
|
|
337
|
+
case "transcriptRepair":
|
|
338
|
+
case "toolResultDisposition":
|
|
339
|
+
case "evidenceReceipt":
|
|
340
|
+
case "userSteering":
|
|
341
|
+
case "settings":
|
|
342
|
+
return applyCounterEvent(current, event.type);
|
|
343
|
+
case "worktreeFingerprint":
|
|
344
|
+
case "activeModelId":
|
|
345
|
+
case "compactionModelId":
|
|
346
|
+
return applyIdentityEvent(current, event.type, event.value);
|
|
347
|
+
default: {
|
|
348
|
+
// Exhaustiveness guard: a new event variant without a matching
|
|
349
|
+
// case becomes a compile error on the assignment below.
|
|
350
|
+
const _exhaustive = event;
|
|
351
|
+
fail(`unhandled event type ${_exhaustive}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Fork a snapshot: keep all event counters and identity values, change the
|
|
357
|
+
* fork id, and reset the global epoch to 0. The caller supplies the new fork
|
|
358
|
+
* id. Rejects an id equal to the current fork id.
|
|
359
|
+
*/
|
|
360
|
+
export function forkContextCacheSnapshot(current, nextForkId) {
|
|
361
|
+
validateContextCacheInvalidationSnapshot(current);
|
|
362
|
+
assertSafeId(nextForkId, "forkId");
|
|
363
|
+
if (nextForkId === current.forkId) {
|
|
364
|
+
fail("forkId must differ from current fork");
|
|
365
|
+
}
|
|
366
|
+
return freezeSnapshot({
|
|
367
|
+
schemaVersion: current.schemaVersion,
|
|
368
|
+
forkId: nextForkId,
|
|
369
|
+
globalEpoch: 0,
|
|
370
|
+
counters: copyCounters(current.counters),
|
|
371
|
+
worktreeFingerprint: current.worktreeFingerprint,
|
|
372
|
+
activeModelId: current.activeModelId,
|
|
373
|
+
compactionModelId: current.compactionModelId,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function snapshotsEqual(a, b) {
|
|
377
|
+
return (a.globalEpoch === b.globalEpoch &&
|
|
378
|
+
a.counters.transcriptRepair === b.counters.transcriptRepair &&
|
|
379
|
+
a.counters.toolResultDisposition === b.counters.toolResultDisposition &&
|
|
380
|
+
a.counters.evidenceReceipt === b.counters.evidenceReceipt &&
|
|
381
|
+
a.counters.userSteering === b.counters.userSteering &&
|
|
382
|
+
a.counters.settings === b.counters.settings &&
|
|
383
|
+
a.worktreeFingerprint === b.worktreeFingerprint &&
|
|
384
|
+
a.activeModelId === b.activeModelId &&
|
|
385
|
+
a.compactionModelId === b.compactionModelId);
|
|
386
|
+
}
|
|
387
|
+
function dominates(x, y) {
|
|
388
|
+
return (x.globalEpoch >= y.globalEpoch &&
|
|
389
|
+
x.counters.transcriptRepair >= y.counters.transcriptRepair &&
|
|
390
|
+
x.counters.toolResultDisposition >= y.counters.toolResultDisposition &&
|
|
391
|
+
x.counters.evidenceReceipt >= y.counters.evidenceReceipt &&
|
|
392
|
+
x.counters.userSteering >= y.counters.userSteering &&
|
|
393
|
+
x.counters.settings >= y.counters.settings &&
|
|
394
|
+
x.worktreeFingerprint === y.worktreeFingerprint &&
|
|
395
|
+
x.activeModelId === y.activeModelId &&
|
|
396
|
+
x.compactionModelId === y.compactionModelId);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Fail-closed merge of two snapshots.
|
|
400
|
+
*
|
|
401
|
+
* - Different fork ids, or two snapshots that are not on a shared linear
|
|
402
|
+
* history, yield {@link ContextCacheMergeStatus.divergent}. Disjoint
|
|
403
|
+
* changes are never max-merged.
|
|
404
|
+
* - Structurally equal snapshots yield {@link ContextCacheMergeStatus.equal}.
|
|
405
|
+
* - When one snapshot componentwise-dominates the other (epoch and every
|
|
406
|
+
* counter greater-or-equal, and all identity values equal), the dominant
|
|
407
|
+
* snapshot is chosen via {@link ContextCacheMergeStatus.dominant}.
|
|
408
|
+
*/
|
|
409
|
+
export function mergeContextCacheSnapshots(a, b) {
|
|
410
|
+
validateContextCacheInvalidationSnapshot(a);
|
|
411
|
+
validateContextCacheInvalidationSnapshot(b);
|
|
412
|
+
if (a.forkId !== b.forkId) {
|
|
413
|
+
return Object.freeze({ status: "divergent", left: cloneAndFreeze(a), right: cloneAndFreeze(b) });
|
|
414
|
+
}
|
|
415
|
+
if (snapshotsEqual(a, b)) {
|
|
416
|
+
return Object.freeze({ status: "equal", snapshot: cloneAndFreeze(a) });
|
|
417
|
+
}
|
|
418
|
+
const aDominates = dominates(a, b);
|
|
419
|
+
const bDominates = dominates(b, a);
|
|
420
|
+
if (aDominates && !bDominates) {
|
|
421
|
+
return Object.freeze({ status: "dominant", snapshot: cloneAndFreeze(a) });
|
|
422
|
+
}
|
|
423
|
+
if (bDominates && !aDominates) {
|
|
424
|
+
return Object.freeze({ status: "dominant", snapshot: cloneAndFreeze(b) });
|
|
425
|
+
}
|
|
426
|
+
return Object.freeze({ status: "divergent", left: cloneAndFreeze(a), right: cloneAndFreeze(b) });
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Canonical, fixed-order JSON serialization of a snapshot, suitable as
|
|
430
|
+
* future cache-key material. The field order is fixed and every distinct
|
|
431
|
+
* field participates, so any distinct field change alters the result.
|
|
432
|
+
* Returns a deterministic string with no hash claim.
|
|
433
|
+
*/
|
|
434
|
+
export function serializeContextCacheSnapshot(snapshot) {
|
|
435
|
+
validateContextCacheInvalidationSnapshot(snapshot);
|
|
436
|
+
return JSON.stringify({
|
|
437
|
+
schemaVersion: snapshot.schemaVersion,
|
|
438
|
+
forkId: snapshot.forkId,
|
|
439
|
+
globalEpoch: snapshot.globalEpoch,
|
|
440
|
+
counters: {
|
|
441
|
+
transcriptRepair: snapshot.counters.transcriptRepair,
|
|
442
|
+
toolResultDisposition: snapshot.counters.toolResultDisposition,
|
|
443
|
+
evidenceReceipt: snapshot.counters.evidenceReceipt,
|
|
444
|
+
userSteering: snapshot.counters.userSteering,
|
|
445
|
+
settings: snapshot.counters.settings,
|
|
446
|
+
},
|
|
447
|
+
worktreeFingerprint: snapshot.worktreeFingerprint,
|
|
448
|
+
activeModelId: snapshot.activeModelId,
|
|
449
|
+
compactionModelId: snapshot.compactionModelId,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
//# sourceMappingURL=context-budget-v2-cache-invalidation.js.map
|