open-multi-agent-kit 1.2.0 → 1.2.3
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 +54 -0
- package/README.md +2 -2
- package/dist/approval-api.d.ts +5 -0
- package/dist/approval-api.d.ts.map +1 -0
- package/dist/approval-api.js +5 -0
- package/dist/approval-api.js.map +1 -0
- package/dist/cli/session-picker.d.ts +2 -2
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/commands/verified-run-cli.d.ts.map +1 -1
- package/dist/commands/verified-run-cli.js +69 -22
- package/dist/commands/verified-run-cli.js.map +1 -1
- package/dist/commands/verified-run-signal.d.ts +3 -0
- package/dist/commands/verified-run-signal.d.ts.map +1 -0
- package/dist/commands/verified-run-signal.js +15 -0
- package/dist/commands/verified-run-signal.js.map +1 -0
- package/dist/coordination/broker.d.ts.map +1 -1
- package/dist/coordination/broker.js +13 -6
- package/dist/coordination/broker.js.map +1 -1
- package/dist/coordination/operation.d.ts +1 -0
- package/dist/coordination/operation.d.ts.map +1 -1
- package/dist/coordination/operation.js +55 -3
- package/dist/coordination/operation.js.map +1 -1
- package/dist/coordination/resource.d.ts +1 -1
- package/dist/coordination/resource.d.ts.map +1 -1
- package/dist/coordination/resource.js +10 -4
- package/dist/coordination/resource.js.map +1 -1
- package/dist/coordination/types.d.ts.map +1 -1
- package/dist/coordination/types.js +1 -1
- package/dist/coordination/types.js.map +1 -1
- package/dist/core/adaptorch-bridge.d.ts +1 -1
- package/dist/core/adaptorch-bridge.d.ts.map +1 -1
- package/dist/core/adaptorch-bridge.js +1 -1
- package/dist/core/adaptorch-bridge.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +16 -16
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +32 -33
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/canonical-json.d.ts +3 -0
- package/dist/core/canonical-json.d.ts.map +1 -0
- package/dist/core/canonical-json.js +20 -0
- package/dist/core/canonical-json.js.map +1 -0
- package/dist/core/compaction/compaction.d.ts +7 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +14 -29
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/fallback.d.ts +16 -5
- package/dist/core/compaction/fallback.d.ts.map +1 -1
- package/dist/core/compaction/fallback.js +4 -3
- package/dist/core/compaction/fallback.js.map +1 -1
- package/dist/core/compaction/model-failover.d.ts +21 -0
- package/dist/core/compaction/model-failover.d.ts.map +1 -0
- package/dist/core/compaction/model-failover.js +46 -0
- package/dist/core/compaction/model-failover.js.map +1 -0
- package/dist/core/compaction/session-summary.d.ts +21 -0
- package/dist/core/compaction/session-summary.d.ts.map +1 -0
- package/dist/core/compaction/session-summary.js +27 -0
- package/dist/core/compaction/session-summary.js.map +1 -0
- package/dist/core/export-html/template.js +14 -0
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/session-lifecycle-types.d.ts +14 -0
- package/dist/core/extensions/session-lifecycle-types.d.ts.map +1 -0
- package/dist/core/extensions/session-lifecycle-types.js +2 -0
- package/dist/core/extensions/session-lifecycle-types.js.map +1 -0
- package/dist/core/extensions/types.d.ts +4 -9
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +10 -1
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +3 -6
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/legacy-resource-policy.d.ts +7 -0
- package/dist/core/legacy-resource-policy.d.ts.map +1 -0
- package/dist/core/legacy-resource-policy.js +9 -0
- package/dist/core/legacy-resource-policy.js.map +1 -0
- package/dist/core/mcp/config.d.ts.map +1 -1
- package/dist/core/mcp/config.js +17 -3
- package/dist/core/mcp/config.js.map +1 -1
- package/dist/core/mcp/connection-queue.d.ts +12 -0
- package/dist/core/mcp/connection-queue.d.ts.map +1 -0
- package/dist/core/mcp/connection-queue.js +46 -0
- package/dist/core/mcp/connection-queue.js.map +1 -0
- package/dist/core/mcp/manager-runtime.d.ts +55 -0
- package/dist/core/mcp/manager-runtime.d.ts.map +1 -0
- package/dist/core/mcp/manager-runtime.js +66 -0
- package/dist/core/mcp/manager-runtime.js.map +1 -0
- package/dist/core/mcp/manager.d.ts +12 -68
- package/dist/core/mcp/manager.d.ts.map +1 -1
- package/dist/core/mcp/manager.js +33 -113
- package/dist/core/mcp/manager.js.map +1 -1
- package/dist/core/mcp/protocol.d.ts.map +1 -1
- package/dist/core/mcp/protocol.js +70 -40
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/package-manager.d.ts +4 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +59 -129
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-resource-patterns.d.ts +7 -0
- package/dist/core/package-resource-patterns.d.ts.map +1 -0
- package/dist/core/package-resource-patterns.js +99 -0
- package/dist/core/package-resource-patterns.js.map +1 -0
- package/dist/core/provider-resilience.d.ts +9 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +15 -0
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/reasoning-router-v4.d.ts +5 -2
- package/dist/core/reasoning-router-v4.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +4 -8
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/run-execution-api.d.ts +8 -1
- package/dist/core/run-execution-api.d.ts.map +1 -1
- package/dist/core/run-execution-api.js +4 -0
- package/dist/core/run-execution-api.js.map +1 -1
- package/dist/core/run-journal.d.ts +1 -2
- package/dist/core/run-journal.d.ts.map +1 -1
- package/dist/core/run-journal.js +2 -19
- package/dist/core/run-journal.js.map +1 -1
- package/dist/core/session-listing.d.ts +51 -0
- package/dist/core/session-listing.d.ts.map +1 -0
- package/dist/core/session-listing.js +111 -0
- package/dist/core/session-listing.js.map +1 -0
- package/dist/core/session-manager.d.ts +12 -37
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +12 -118
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-message-text.d.ts +5 -0
- package/dist/core/session-message-text.d.ts.map +1 -0
- package/dist/core/session-message-text.js +9 -0
- package/dist/core/session-message-text.js.map +1 -0
- package/dist/core/skills-catalog-cache.d.ts +2 -0
- package/dist/core/skills-catalog-cache.d.ts.map +1 -1
- package/dist/core/skills-catalog-cache.js +71 -17
- package/dist/core/skills-catalog-cache.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/verified-run/authority-clock.d.ts +3 -0
- package/dist/core/verified-run/authority-clock.d.ts.map +1 -0
- package/dist/core/verified-run/authority-clock.js +25 -0
- package/dist/core/verified-run/authority-clock.js.map +1 -0
- package/dist/core/verified-run/authority-errors.d.ts +10 -0
- package/dist/core/verified-run/authority-errors.d.ts.map +1 -0
- package/dist/core/verified-run/authority-errors.js +17 -0
- package/dist/core/verified-run/authority-errors.js.map +1 -0
- package/dist/core/verified-run/authority-events.d.ts +126 -0
- package/dist/core/verified-run/authority-events.d.ts.map +1 -0
- package/dist/core/verified-run/authority-events.js +482 -0
- package/dist/core/verified-run/authority-events.js.map +1 -0
- package/dist/core/verified-run/authority-journal.d.ts +27 -0
- package/dist/core/verified-run/authority-journal.d.ts.map +1 -0
- package/dist/core/verified-run/authority-journal.js +83 -0
- package/dist/core/verified-run/authority-journal.js.map +1 -0
- package/dist/core/verified-run/authority-meaning.d.ts +13 -0
- package/dist/core/verified-run/authority-meaning.d.ts.map +1 -0
- package/dist/core/verified-run/authority-meaning.js +40 -0
- package/dist/core/verified-run/authority-meaning.js.map +1 -0
- package/dist/core/verified-run/authority-runtime.d.ts +82 -0
- package/dist/core/verified-run/authority-runtime.d.ts.map +1 -0
- package/dist/core/verified-run/authority-runtime.js +169 -0
- package/dist/core/verified-run/authority-runtime.js.map +1 -0
- package/dist/core/verified-run/authority-store.d.ts +204 -0
- package/dist/core/verified-run/authority-store.d.ts.map +1 -0
- package/dist/core/verified-run/authority-store.js +852 -0
- package/dist/core/verified-run/authority-store.js.map +1 -0
- package/dist/core/verified-run/broker.d.ts +8 -1
- package/dist/core/verified-run/broker.d.ts.map +1 -1
- package/dist/core/verified-run/broker.js +94 -90
- package/dist/core/verified-run/broker.js.map +1 -1
- package/dist/core/verified-run/candidate-policy.d.ts +10 -0
- package/dist/core/verified-run/candidate-policy.d.ts.map +1 -0
- package/dist/core/verified-run/candidate-policy.js +33 -0
- package/dist/core/verified-run/candidate-policy.js.map +1 -0
- package/dist/core/verified-run/candidate.d.ts.map +1 -1
- package/dist/core/verified-run/candidate.js +5 -10
- package/dist/core/verified-run/candidate.js.map +1 -1
- package/dist/core/verified-run/coordinator.d.ts +35 -17
- package/dist/core/verified-run/coordinator.d.ts.map +1 -1
- package/dist/core/verified-run/coordinator.js +111 -57
- package/dist/core/verified-run/coordinator.js.map +1 -1
- package/dist/core/verified-run/dag-phase.d.ts.map +1 -1
- package/dist/core/verified-run/dag-phase.js +1 -1
- package/dist/core/verified-run/dag-phase.js.map +1 -1
- package/dist/core/verified-run/dag-recovery.d.ts +5 -1
- package/dist/core/verified-run/dag-recovery.d.ts.map +1 -1
- package/dist/core/verified-run/dag-recovery.js +10 -4
- package/dist/core/verified-run/dag-recovery.js.map +1 -1
- package/dist/core/verified-run/event-parser.d.ts.map +1 -1
- package/dist/core/verified-run/event-parser.js +33 -0
- package/dist/core/verified-run/event-parser.js.map +1 -1
- package/dist/core/verified-run/evidence-read-projection.d.ts +4 -0
- package/dist/core/verified-run/evidence-read-projection.d.ts.map +1 -0
- package/dist/core/verified-run/evidence-read-projection.js +27 -0
- package/dist/core/verified-run/evidence-read-projection.js.map +1 -0
- package/dist/core/verified-run/evidence.d.ts +12 -1
- package/dist/core/verified-run/evidence.d.ts.map +1 -1
- package/dist/core/verified-run/evidence.js +51 -2
- package/dist/core/verified-run/evidence.js.map +1 -1
- package/dist/core/verified-run/git-candidate-preflight.d.ts +4 -0
- package/dist/core/verified-run/git-candidate-preflight.d.ts.map +1 -0
- package/dist/core/verified-run/git-candidate-preflight.js +34 -0
- package/dist/core/verified-run/git-candidate-preflight.js.map +1 -0
- package/dist/core/verified-run/git-effect-supervisor.d.ts +24 -0
- package/dist/core/verified-run/git-effect-supervisor.d.ts.map +1 -0
- package/dist/core/verified-run/git-effect-supervisor.js +174 -0
- package/dist/core/verified-run/git-effect-supervisor.js.map +1 -0
- package/dist/core/verified-run/git-execution.d.ts +27 -0
- package/dist/core/verified-run/git-execution.d.ts.map +1 -0
- package/dist/core/verified-run/git-execution.js +118 -0
- package/dist/core/verified-run/git-execution.js.map +1 -0
- package/dist/core/verified-run/git-plumbing.d.ts +37 -0
- package/dist/core/verified-run/git-plumbing.d.ts.map +1 -0
- package/dist/core/verified-run/git-plumbing.js +171 -0
- package/dist/core/verified-run/git-plumbing.js.map +1 -0
- package/dist/core/verified-run/git-publication-worker.d.ts +2 -0
- package/dist/core/verified-run/git-publication-worker.d.ts.map +1 -0
- package/dist/core/verified-run/git-publication-worker.js +54 -0
- package/dist/core/verified-run/git-publication-worker.js.map +1 -0
- package/dist/core/verified-run/git-sandbox-layout.d.ts +4 -0
- package/dist/core/verified-run/git-sandbox-layout.d.ts.map +1 -0
- package/dist/core/verified-run/git-sandbox-layout.js +29 -0
- package/dist/core/verified-run/git-sandbox-layout.js.map +1 -0
- package/dist/core/verified-run/git-worker-protocol.d.ts +19 -0
- package/dist/core/verified-run/git-worker-protocol.d.ts.map +1 -0
- package/dist/core/verified-run/git-worker-protocol.js +109 -0
- package/dist/core/verified-run/git-worker-protocol.js.map +1 -0
- package/dist/core/verified-run/git-worker-runtime.d.ts +19 -0
- package/dist/core/verified-run/git-worker-runtime.d.ts.map +1 -0
- package/dist/core/verified-run/git-worker-runtime.js +74 -0
- package/dist/core/verified-run/git-worker-runtime.js.map +1 -0
- package/dist/core/verified-run/journal.d.ts +2 -3
- package/dist/core/verified-run/journal.d.ts.map +1 -1
- package/dist/core/verified-run/journal.js.map +1 -1
- package/dist/core/verified-run/namespace-identity.d.ts.map +1 -1
- package/dist/core/verified-run/namespace-identity.js +1 -1
- package/dist/core/verified-run/namespace-identity.js.map +1 -1
- package/dist/core/verified-run/owned-execution.d.ts +10 -1
- package/dist/core/verified-run/owned-execution.d.ts.map +1 -1
- package/dist/core/verified-run/owned-execution.js +124 -31
- package/dist/core/verified-run/owned-execution.js.map +1 -1
- package/dist/core/verified-run/phase-context.d.ts +2 -0
- package/dist/core/verified-run/phase-context.d.ts.map +1 -1
- package/dist/core/verified-run/phase-context.js.map +1 -1
- package/dist/core/verified-run/projection.d.ts.map +1 -1
- package/dist/core/verified-run/projection.js +51 -1
- package/dist/core/verified-run/projection.js.map +1 -1
- package/dist/core/verified-run/publish-preflight.d.ts +12 -0
- package/dist/core/verified-run/publish-preflight.d.ts.map +1 -0
- package/dist/core/verified-run/publish-preflight.js +38 -0
- package/dist/core/verified-run/publish-preflight.js.map +1 -0
- package/dist/core/verified-run/publish-start.d.ts +5 -0
- package/dist/core/verified-run/publish-start.d.ts.map +1 -0
- package/dist/core/verified-run/publish-start.js +7 -0
- package/dist/core/verified-run/publish-start.js.map +1 -0
- package/dist/core/verified-run/recovery-projection.d.ts.map +1 -1
- package/dist/core/verified-run/recovery-projection.js +5 -0
- package/dist/core/verified-run/recovery-projection.js.map +1 -1
- package/dist/core/verified-run/recovery.d.ts +5 -1
- package/dist/core/verified-run/recovery.d.ts.map +1 -1
- package/dist/core/verified-run/recovery.js +10 -4
- package/dist/core/verified-run/recovery.js.map +1 -1
- package/dist/core/verified-run/run-plan.d.ts +17 -0
- package/dist/core/verified-run/run-plan.d.ts.map +1 -0
- package/dist/core/verified-run/run-plan.js +18 -0
- package/dist/core/verified-run/run-plan.js.map +1 -0
- package/dist/core/verified-run/run-publish-owned.d.ts +6 -0
- package/dist/core/verified-run/run-publish-owned.d.ts.map +1 -0
- package/dist/core/verified-run/run-publish-owned.js +199 -0
- package/dist/core/verified-run/run-publish-owned.js.map +1 -0
- package/dist/core/verified-run/run-publish.d.ts +19 -0
- package/dist/core/verified-run/run-publish.d.ts.map +1 -0
- package/dist/core/verified-run/run-publish.js +210 -0
- package/dist/core/verified-run/run-publish.js.map +1 -0
- package/dist/core/verified-run/run-status-owned.d.ts +4 -0
- package/dist/core/verified-run/run-status-owned.d.ts.map +1 -0
- package/dist/core/verified-run/run-status-owned.js +14 -0
- package/dist/core/verified-run/run-status-owned.js.map +1 -0
- package/dist/core/verified-run/run-status.d.ts +133 -0
- package/dist/core/verified-run/run-status.d.ts.map +1 -0
- package/dist/core/verified-run/run-status.js +193 -0
- package/dist/core/verified-run/run-status.js.map +1 -0
- package/dist/core/verified-run/run-types.d.ts +31 -0
- package/dist/core/verified-run/run-types.d.ts.map +1 -1
- package/dist/core/verified-run/run-types.js.map +1 -1
- package/dist/core/verified-run/storage.d.ts.map +1 -1
- package/dist/core/verified-run/storage.js +1 -1
- package/dist/core/verified-run/storage.js.map +1 -1
- package/dist/core/verified-run/supervisor-adapter.d.ts +81 -0
- package/dist/core/verified-run/supervisor-adapter.d.ts.map +1 -0
- package/dist/core/verified-run/supervisor-adapter.js +168 -0
- package/dist/core/verified-run/supervisor-adapter.js.map +1 -0
- package/dist/core/verified-run/verification-phase.d.ts.map +1 -1
- package/dist/core/verified-run/verification-phase.js +1 -1
- package/dist/core/verified-run/verification-phase.js.map +1 -1
- package/dist/core/verified-run/writer-phase.d.ts.map +1 -1
- package/dist/core/verified-run/writer-phase.js +1 -0
- package/dist/core/verified-run/writer-phase.js.map +1 -1
- package/dist/core/verified-run/writer-recovery.d.ts +2 -0
- package/dist/core/verified-run/writer-recovery.d.ts.map +1 -1
- package/dist/core/verified-run/writer-recovery.js +1 -0
- package/dist/core/verified-run/writer-recovery.js.map +1 -1
- package/dist/core/workload-shard-execution-types.d.ts +4 -1
- package/dist/core/workload-shard-execution-types.d.ts.map +1 -1
- package/dist/core/workload-shard-execution-types.js.map +1 -1
- package/dist/core/workload-shard-executor.d.ts +2 -2
- package/dist/core/workload-shard-executor.d.ts.map +1 -1
- package/dist/core/workload-shard-executor.js +16 -37
- package/dist/core/workload-shard-executor.js.map +1 -1
- package/dist/core/workload-shard-frontier.d.ts +5 -0
- package/dist/core/workload-shard-frontier.d.ts.map +1 -0
- package/dist/core/workload-shard-frontier.js +47 -0
- package/dist/core/workload-shard-frontier.js.map +1 -0
- package/dist/core/workload-shard-runner.d.ts.map +1 -1
- package/dist/core/workload-shard-runner.js +28 -17
- package/dist/core/workload-shard-runner.js.map +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -1
- package/dist/main.js.map +1 -1
- package/dist/metacognition/bridge-result.d.ts +148 -0
- package/dist/metacognition/bridge-result.d.ts.map +1 -0
- package/dist/metacognition/bridge-result.js +46 -0
- package/dist/metacognition/bridge-result.js.map +1 -0
- package/dist/metacognition/calibration-selective.d.ts.map +1 -1
- package/dist/metacognition/calibration-selective.js +8 -2
- package/dist/metacognition/calibration-selective.js.map +1 -1
- package/dist/metacognition/index.d.ts +1 -0
- package/dist/metacognition/index.d.ts.map +1 -1
- package/dist/metacognition/index.js +1 -0
- package/dist/metacognition/index.js.map +1 -1
- package/dist/metacognition/risk.d.ts.map +1 -1
- package/dist/metacognition/risk.js +28 -14
- package/dist/metacognition/risk.js.map +1 -1
- package/dist/metacognition/runtime-bridge.d.ts +41 -14
- package/dist/metacognition/runtime-bridge.d.ts.map +1 -1
- package/dist/metacognition/runtime-bridge.js +357 -39
- package/dist/metacognition/runtime-bridge.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +2 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +42 -26
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/chat-container.d.ts +7 -0
- package/dist/modes/interactive/components/chat-container.d.ts.map +1 -0
- package/dist/modes/interactive/components/chat-container.js +14 -0
- package/dist/modes/interactive/components/chat-container.js.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +4 -2
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +0 -1
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-async-search.d.ts +33 -0
- package/dist/modes/interactive/components/session-selector-async-search.d.ts.map +1 -0
- package/dist/modes/interactive/components/session-selector-async-search.js +109 -0
- package/dist/modes/interactive/components/session-selector-async-search.js.map +1 -0
- package/dist/modes/interactive/components/session-selector-loaders.d.ts +6 -0
- package/dist/modes/interactive/components/session-selector-loaders.d.ts.map +1 -0
- package/dist/modes/interactive/components/session-selector-loaders.js +9 -0
- package/dist/modes/interactive/components/session-selector-loaders.js.map +1 -0
- package/dist/modes/interactive/components/session-selector-search.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-tree.d.ts +16 -0
- package/dist/modes/interactive/components/session-selector-tree.d.ts.map +1 -0
- package/dist/modes/interactive/components/session-selector-tree.js +46 -0
- package/dist/modes/interactive/components/session-selector-tree.js.map +1 -0
- package/dist/modes/interactive/components/session-selector.d.ts +6 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +36 -76
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution-images.d.ts +32 -0
- package/dist/modes/interactive/components/tool-execution-images.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-execution-images.js +144 -0
- package/dist/modes/interactive/components/tool-execution-images.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts +3 -4
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +22 -71
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +7 -9
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/tui-diagnostics.d.ts +8 -4
- package/dist/modes/interactive/tui-diagnostics.d.ts.map +1 -1
- package/dist/modes/interactive/tui-diagnostics.js +35 -1
- package/dist/modes/interactive/tui-diagnostics.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +14 -17
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-response.d.ts +5 -0
- package/dist/modes/rpc/rpc-response.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-response.js +17 -0
- package/dist/modes/rpc/rpc-response.js.map +1 -0
- package/dist/utils/exif-orientation.d.ts.map +1 -1
- package/dist/utils/exif-orientation.js +2 -3
- package/dist/utils/exif-orientation.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +3 -5
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +7 -7
- package/docs/atomic-commit-planning.md +69 -0
- package/docs/compaction.md +26 -1
- package/docs/extensions.md +14 -0
- package/docs/mcp.md +22 -0
- package/docs/metacognition.md +79 -0
- package/docs/model-catalog-refresh.md +159 -1
- package/docs/models.md +1 -1
- package/docs/runtime-algorithms.md +49 -4
- package/docs/session-format.md +8 -0
- package/docs/sessions.md +19 -0
- package/docs/skills.md +19 -0
- package/docs/terminal-setup.md +15 -0
- package/docs/tui.md +41 -0
- package/docs/verified-run-safety.md +75 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/doom-overlay/wad-finder.ts +7 -3
- 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 +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +6 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker.js","sourceRoot":"","sources":["../../src/coordination/broker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * In-process admission broker for parallel sessions.\n *\n * Safety property: a grant that may still own a live external effect keeps\n * holding its claims. Expiry, cancellation and authority restart are\n * authorization events; only a trusted supervisor reporting termination frees\n * the claims. Treating a lapsed deadline as \"the effect stopped\" is exactly how\n * two sessions end up writing the same path.\n *\n * Bounded reference semantics: a single trusted state machine, canonical claim\n * keys, and every shared effect crossing this broker. No IPC, no OS fencing,\n * no persistence — those belong to the supervisor this broker reports to.\n */\n\nimport { claimSetsConflict, sameClaimSet } from \"./resource.ts\";\nimport type { EffectState, GrantToken, ResourceClaim, Sequence } from \"./types.ts\";\nimport { nextSequence, SETTLED_EFFECT_STATES, sequence } from \"./types.ts\";\n\ninterface Grant {\n\treadonly token: GrantToken;\n\treadonly claims: readonly ResourceClaim[];\n\treadonly weight: number;\n\tstate: EffectState;\n\t/** True once an external effect may exist. Never inferred from the clock. */\n\teffectLive: boolean;\n}\n\nexport interface AdmissionBrokerOptions {\n\t/** Total admissible weight of concurrently active grants. */\n\treadonly capacity: number;\n}\n\nexport interface AcquireInput {\n\treadonly sessionId: string;\n\treadonly incarnation: Sequence | string;\n\treadonly claims: readonly ResourceClaim[];\n\treadonly now: number;\n\treadonly ttl: number;\n\treadonly weight?: number;\n}\n\nexport interface StartInput {\n\treadonly token: GrantToken;\n\treadonly now: number;\n\t/** Claims the operation actually resolved to after approval hooks. */\n\treadonly actualClaims: readonly ResourceClaim[];\n}\n\nfunction assertNonNegativeInteger(value: number, label: string): void {\n\tif (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < 0) {\n\t\tthrow new TypeError(`${label} must be a nonnegative safe integer`);\n\t}\n}\n\nexport class AdmissionBroker {\n\tprivate readonly capacity: number;\n\tprivate authorityEpoch: Sequence = sequence(\"1\");\n\tprivate grantCounter: Sequence = sequence(\"0\");\n\tprivate readonly incarnations = new Map<string, Sequence>();\n\tprivate readonly grants = new Map<string, Grant>();\n\n\tconstructor(options: AdmissionBrokerOptions) {\n\t\tassertNonNegativeInteger(options.capacity, \"capacity\");\n\t\tthis.capacity = options.capacity;\n\t}\n\n\t/**\n\t * Announce a session incarnation. Re-registering does not free the previous\n\t * incarnation's grants: a restarted session cannot assume its predecessor's\n\t * effects stopped.\n\t */\n\tregister(sessionId: string): Sequence {\n\t\tif (typeof sessionId !== \"string\" || sessionId.length === 0) {\n\t\t\tthrow new TypeError(\"sessionId must be a non-empty string\");\n\t\t}\n\t\tconst next = nextSequence(this.incarnations.get(sessionId) ?? sequence(\"0\"));\n\t\tthis.incarnations.set(sessionId, next);\n\t\treturn next;\n\t}\n\n\t/** Grants still holding their claims, including quarantined ones. */\n\tprivate activeGrants(): Grant[] {\n\t\treturn [...this.grants.values()].filter((g) => !SETTLED_EFFECT_STATES.has(g.state));\n\t}\n\n\tacquire(input: AcquireInput): GrantToken | null {\n\t\tassertNonNegativeInteger(input.now, \"now\");\n\t\tconst weight = input.weight ?? 1;\n\t\tassertNonNegativeInteger(weight, \"weight\");\n\t\tif (!Number.isSafeInteger(input.ttl) || input.ttl <= 0) {\n\t\t\tthrow new TypeError(\"ttl must be a positive safe integer\");\n\t\t}\n\t\tconst incarnation = sequence(input.incarnation);\n\t\tif (this.incarnations.get(input.sessionId) !== incarnation) {\n\t\t\tthrow new TypeError(\"stale session incarnation\");\n\t\t}\n\t\tif (input.claims.length === 0) {\n\t\t\tthrow new TypeError(\"an unknown scope must not be admitted as an empty scope\");\n\t\t}\n\n\t\t// Settle lapsed authorizations first; this never evicts a live effect.\n\t\tthis.expire(input.now);\n\t\tconst active = this.activeGrants();\n\t\tconst used = active.reduce((sum, g) => sum + g.weight, 0);\n\t\tif (weight + used > this.capacity) return null;\n\t\tconst claims = [...input.claims];\n\t\tif (active.some((g) => claimSetsConflict(claims, g.claims))) return null;\n\n\t\tthis.grantCounter = nextSequence(this.grantCounter);\n\t\tconst token: GrantToken = Object.freeze({\n\t\t\tauthorityEpoch: this.authorityEpoch,\n\t\t\tgrantSequence: this.grantCounter,\n\t\t\tsessionId: input.sessionId,\n\t\t\tsessionIncarnation: incarnation,\n\t\t\tauthorizationDeadline: input.now + input.ttl,\n\t\t});\n\t\tthis.grants.set(token.grantSequence, {\n\t\t\ttoken,\n\t\t\tclaims: Object.freeze(claims),\n\t\t\tweight,\n\t\t\tstate: \"reserved\",\n\t\t\teffectLive: false,\n\t\t});\n\t\treturn token;\n\t}\n\n\t/** Resolve a token that is still authoritative for new action. */\n\tprivate authoritative(token: GrantToken): Grant | undefined {\n\t\tconst grant = this.grants.get(token.grantSequence);\n\t\tif (grant === undefined || !sameToken(grant.token, token)) return undefined;\n\t\tif (token.authorityEpoch !== this.authorityEpoch) return undefined;\n\t\tif (this.incarnations.get(token.sessionId) !== token.sessionIncarnation) return undefined;\n\t\tif (SETTLED_EFFECT_STATES.has(grant.state)) return undefined;\n\t\treturn grant;\n\t}\n\n\t/** Resolve a token for settlement only; accepts superseded epochs. */\n\tprivate settleable(token: GrantToken): Grant | undefined {\n\t\tconst grant = this.grants.get(token.grantSequence);\n\t\tif (grant === undefined || !sameToken(grant.token, token)) return undefined;\n\t\treturn SETTLED_EFFECT_STATES.has(grant.state) ? undefined : grant;\n\t}\n\n\t/**\n\t * Bind an effect to its reservation. The actual claims must equal the\n\t * reserved set exactly — an approval hook that widened scope has to be\n\t * re-admitted, not silently allowed to run under the old grant.\n\t */\n\tstart(input: StartInput): boolean {\n\t\tassertNonNegativeInteger(input.now, \"now\");\n\t\tconst grant = this.authoritative(input.token);\n\t\tif (grant === undefined || grant.state !== \"reserved\") return false;\n\t\tif (input.now >= grant.token.authorizationDeadline) return false;\n\t\tif (!sameClaimSet(input.actualClaims, grant.claims)) return false;\n\t\tgrant.state = \"running\";\n\t\tgrant.effectLive = true;\n\t\treturn true;\n\t}\n\n\t/**\n\t * Apply lapsed authorization deadlines. A reservation that never started is\n\t * released; a running effect is quarantined and keeps its claims.\n\t */\n\texpire(now: number): void {\n\t\tassertNonNegativeInteger(now, \"now\");\n\t\tfor (const grant of this.activeGrants()) {\n\t\t\tif (now >= grant.token.authorizationDeadline) {\n\t\t\t\tgrant.state = grant.effectLive ? \"quarantined\" : \"cancelled\";\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Withdraw authorization. Requested cancellation is not observed termination. */\n\tcancel(token: GrantToken): boolean {\n\t\tconst grant = this.settleable(token);\n\t\tif (grant === undefined) return false;\n\t\tgrant.state = grant.effectLive ? \"quarantined\" : \"cancelled\";\n\t\treturn true;\n\t}\n\n\t/**\n\t * Trusted supervisor event: this exact effect is gone. Accepted for\n\t * superseded epochs so a restarted authority can still settle what the\n\t * previous one started, never to authorize new work.\n\t */\n\tconfirmTerminated(token: GrantToken): boolean {\n\t\tconst grant = this.settleable(token);\n\t\tif (grant === undefined) return false;\n\t\tgrant.effectLive = false;\n\t\tgrant.state = \"terminated\";\n\t\treturn true;\n\t}\n\n\t/** Fence the authority. Live effects stay quarantined until confirmed. */\n\trestart(): void {\n\t\tthis.authorityEpoch = nextSequence(this.authorityEpoch);\n\t\tfor (const grant of this.activeGrants()) {\n\t\t\tgrant.state = grant.effectLive ? \"quarantined\" : \"cancelled\";\n\t\t}\n\t}\n\n\tstateOf(token: GrantToken): EffectState | undefined {\n\t\tconst grant = this.grants.get(token.grantSequence);\n\t\treturn grant !== undefined && sameToken(grant.token, token) ? grant.state : undefined;\n\t}\n\n\tclaimsOf(token: GrantToken): readonly ResourceClaim[] {\n\t\tconst grant = this.grants.get(token.grantSequence);\n\t\treturn grant !== undefined && sameToken(grant.token, token) ? grant.claims : [];\n\t}\n\n\t/** Invariant: active grants fit capacity and never pairwise conflict. */\n\tisSafe(): boolean {\n\t\tconst active = this.activeGrants();\n\t\tif (active.reduce((sum, g) => sum + g.weight, 0) > this.capacity) return false;\n\t\tfor (const [index, grant] of active.entries()) {\n\t\t\tfor (const other of active.slice(index + 1)) {\n\t\t\t\tif (claimSetsConflict(grant.claims, other.claims)) return false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\n\nfunction sameToken(a: GrantToken, b: GrantToken): boolean {\n\treturn (\n\t\ta.authorityEpoch === b.authorityEpoch &&\n\t\ta.grantSequence === b.grantSequence &&\n\t\ta.sessionId === b.sessionId &&\n\t\ta.sessionIncarnation === b.sessionIncarnation &&\n\t\ta.authorizationDeadline === b.authorizationDeadline\n\t);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"broker.js","sourceRoot":"","sources":["../../src/coordination/broker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * In-process admission broker for parallel sessions.\n *\n * Safety property: a grant that may still own a live external effect keeps\n * holding its claims. Expiry, cancellation and authority restart are\n * authorization events; only a trusted supervisor reporting termination frees\n * the claims. Treating a lapsed deadline as \"the effect stopped\" is exactly how\n * two sessions end up writing the same path.\n *\n * Bounded reference semantics: a single trusted state machine, canonical claim\n * keys, and every shared effect crossing this broker. No IPC, no OS fencing,\n * no persistence — those belong to the supervisor this broker reports to.\n */\n\nimport { canonicalClaim, claimSetsConflict, sameClaimSet } from \"./resource.ts\";\nimport type { EffectState, GrantToken, ResourceClaim, Sequence } from \"./types.ts\";\nimport { nextSequence, SETTLED_EFFECT_STATES, sequence } from \"./types.ts\";\n\ninterface Grant {\n\treadonly token: GrantToken;\n\treadonly claims: readonly ResourceClaim[];\n\treadonly weight: number;\n\tstate: EffectState;\n\t/** True once an external effect may exist. Never inferred from the clock. */\n\teffectLive: boolean;\n}\n\nexport interface AdmissionBrokerOptions {\n\t/** Total admissible weight of concurrently active grants. */\n\treadonly capacity: number;\n}\n\nexport interface AcquireInput {\n\treadonly sessionId: string;\n\treadonly incarnation: Sequence | string;\n\treadonly claims: readonly ResourceClaim[];\n\treadonly now: number;\n\treadonly ttl: number;\n\treadonly weight?: number;\n}\n\nexport interface StartInput {\n\treadonly token: GrantToken;\n\treadonly now: number;\n\t/** Claims the operation actually resolved to after approval hooks. */\n\treadonly actualClaims: readonly ResourceClaim[];\n}\n\nfunction assertNonNegativeInteger(value: number, label: string): void {\n\tif (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < 0) {\n\t\tthrow new TypeError(`${label} must be a nonnegative safe integer`);\n\t}\n}\n\nexport class AdmissionBroker {\n\tprivate readonly capacity: number;\n\tprivate authorityEpoch: Sequence = sequence(\"1\");\n\tprivate grantCounter: Sequence = sequence(\"0\");\n\tprivate readonly incarnations = new Map<string, Sequence>();\n\tprivate readonly grants = new Map<string, Grant>();\n\n\tconstructor(options: AdmissionBrokerOptions) {\n\t\tassertNonNegativeInteger(options.capacity, \"capacity\");\n\t\tthis.capacity = options.capacity;\n\t}\n\n\t/**\n\t * Announce a session incarnation. Re-registering does not free the previous\n\t * incarnation's grants: a restarted session cannot assume its predecessor's\n\t * effects stopped.\n\t */\n\tregister(sessionId: string): Sequence {\n\t\tif (typeof sessionId !== \"string\" || sessionId.length === 0) {\n\t\t\tthrow new TypeError(\"sessionId must be a non-empty string\");\n\t\t}\n\t\tconst next = nextSequence(this.incarnations.get(sessionId) ?? sequence(\"0\"));\n\t\tthis.incarnations.set(sessionId, next);\n\t\treturn next;\n\t}\n\n\t/** Grants still holding their claims, including quarantined ones. */\n\tprivate activeGrants(): Grant[] {\n\t\treturn [...this.grants.values()].filter((g) => !SETTLED_EFFECT_STATES.has(g.state));\n\t}\n\n\tacquire(input: AcquireInput): GrantToken | null {\n\t\tassertNonNegativeInteger(input.now, \"now\");\n\t\tconst weight = input.weight ?? 1;\n\t\tassertNonNegativeInteger(weight, \"weight\");\n\t\tif (!Number.isSafeInteger(input.ttl) || input.ttl <= 0) {\n\t\t\tthrow new TypeError(\"ttl must be a positive safe integer\");\n\t\t}\n\t\tconst authorizationDeadline = input.now + input.ttl;\n\t\tif (!Number.isSafeInteger(authorizationDeadline)) throw new RangeError(\"authorization deadline overflow\");\n\t\tconst incarnation = sequence(input.incarnation);\n\t\tif (this.incarnations.get(input.sessionId) !== incarnation) {\n\t\t\tthrow new TypeError(\"stale session incarnation\");\n\t\t}\n\t\tif (!Array.isArray(input.claims) || input.claims.length === 0) {\n\t\t\tthrow new TypeError(\"an unknown scope must not be admitted as an empty scope\");\n\t\t}\n\t\t// Take owned, validated snapshots before any state mutation.\n\t\tconst claims = Array.from(input.claims, (claim) => canonicalClaim(claim));\n\n\t\t// Settle lapsed authorizations first; this never evicts a live effect.\n\t\tthis.expire(input.now);\n\t\tconst active = this.activeGrants();\n\t\tconst used = active.reduce((sum, g) => sum + g.weight, 0);\n\t\tif (weight > this.capacity - used) return null;\n\t\tif (active.some((g) => claimSetsConflict(claims, g.claims))) return null;\n\n\t\tthis.grantCounter = nextSequence(this.grantCounter);\n\t\tconst token: GrantToken = Object.freeze({\n\t\t\tauthorityEpoch: this.authorityEpoch,\n\t\t\tgrantSequence: this.grantCounter,\n\t\t\tsessionId: input.sessionId,\n\t\t\tsessionIncarnation: incarnation,\n\t\t\tauthorizationDeadline,\n\t\t});\n\t\tthis.grants.set(token.grantSequence, {\n\t\t\ttoken,\n\t\t\tclaims: Object.freeze(claims),\n\t\t\tweight,\n\t\t\tstate: \"reserved\",\n\t\t\teffectLive: false,\n\t\t});\n\t\treturn token;\n\t}\n\n\t/** Resolve a token that is still authoritative for new action. */\n\tprivate authoritative(token: GrantToken): Grant | undefined {\n\t\tconst grant = this.grants.get(token.grantSequence);\n\t\tif (grant === undefined || !sameToken(grant.token, token)) return undefined;\n\t\tif (token.authorityEpoch !== this.authorityEpoch) return undefined;\n\t\tif (this.incarnations.get(token.sessionId) !== token.sessionIncarnation) return undefined;\n\t\tif (SETTLED_EFFECT_STATES.has(grant.state)) return undefined;\n\t\treturn grant;\n\t}\n\n\t/** Resolve a token for settlement only; accepts superseded epochs. */\n\tprivate settleable(token: GrantToken): Grant | undefined {\n\t\tconst grant = this.grants.get(token.grantSequence);\n\t\tif (grant === undefined || !sameToken(grant.token, token)) return undefined;\n\t\treturn SETTLED_EFFECT_STATES.has(grant.state) ? undefined : grant;\n\t}\n\n\t/**\n\t * Bind an effect to its reservation. The actual claims must equal the\n\t * reserved set exactly — an approval hook that widened scope has to be\n\t * re-admitted, not silently allowed to run under the old grant.\n\t */\n\tstart(input: StartInput): boolean {\n\t\tassertNonNegativeInteger(input.now, \"now\");\n\t\tconst grant = this.authoritative(input.token);\n\t\tif (grant === undefined || grant.state !== \"reserved\") return false;\n\t\tif (input.now >= grant.token.authorizationDeadline) return false;\n\t\tif (!Array.isArray(input.actualClaims)) throw new TypeError(\"actualClaims must be an array\");\n\t\tconst actualClaims = Array.from(input.actualClaims, (claim) => canonicalClaim(claim));\n\t\tif (!sameClaimSet(actualClaims, grant.claims)) return false;\n\t\tgrant.state = \"running\";\n\t\tgrant.effectLive = true;\n\t\treturn true;\n\t}\n\n\t/**\n\t * Apply lapsed authorization deadlines. A reservation that never started is\n\t * released; a running effect is quarantined and keeps its claims.\n\t */\n\texpire(now: number): void {\n\t\tassertNonNegativeInteger(now, \"now\");\n\t\tfor (const grant of this.activeGrants()) {\n\t\t\tif (now >= grant.token.authorizationDeadline) {\n\t\t\t\tgrant.state = grant.effectLive ? \"quarantined\" : \"cancelled\";\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Withdraw authorization. Requested cancellation is not observed termination. */\n\tcancel(token: GrantToken): boolean {\n\t\tconst grant = this.settleable(token);\n\t\tif (grant === undefined) return false;\n\t\tgrant.state = grant.effectLive ? \"quarantined\" : \"cancelled\";\n\t\treturn true;\n\t}\n\n\t/**\n\t * Trusted supervisor event: this exact effect is gone. Accepted for\n\t * superseded epochs so a restarted authority can still settle what the\n\t * previous one started, never to authorize new work.\n\t */\n\tconfirmTerminated(token: GrantToken): boolean {\n\t\tconst grant = this.settleable(token);\n\t\tif (grant === undefined) return false;\n\t\tgrant.effectLive = false;\n\t\tgrant.state = \"terminated\";\n\t\treturn true;\n\t}\n\n\t/** Fence the authority. Live effects stay quarantined until confirmed. */\n\trestart(): void {\n\t\tthis.authorityEpoch = nextSequence(this.authorityEpoch);\n\t\tfor (const grant of this.activeGrants()) {\n\t\t\tgrant.state = grant.effectLive ? \"quarantined\" : \"cancelled\";\n\t\t}\n\t}\n\n\tstateOf(token: GrantToken): EffectState | undefined {\n\t\tconst grant = this.grants.get(token.grantSequence);\n\t\treturn grant !== undefined && sameToken(grant.token, token) ? grant.state : undefined;\n\t}\n\n\tclaimsOf(token: GrantToken): readonly ResourceClaim[] {\n\t\tconst grant = this.grants.get(token.grantSequence);\n\t\treturn grant !== undefined && sameToken(grant.token, token) ? grant.claims : [];\n\t}\n\n\t/** Invariant: active grants fit capacity and never pairwise conflict. */\n\tisSafe(): boolean {\n\t\tconst active = this.activeGrants();\n\t\tif (active.reduce((sum, g) => sum + g.weight, 0) > this.capacity) return false;\n\t\tfor (const [index, grant] of active.entries()) {\n\t\t\tfor (const other of active.slice(index + 1)) {\n\t\t\t\tif (claimSetsConflict(grant.claims, other.claims)) return false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\n\nfunction sameToken(a: GrantToken, b: GrantToken): boolean {\n\treturn (\n\t\ta.authorityEpoch === b.authorityEpoch &&\n\t\ta.grantSequence === b.grantSequence &&\n\t\ta.sessionId === b.sessionId &&\n\t\ta.sessionIncarnation === b.sessionIncarnation &&\n\t\ta.authorizationDeadline === b.authorizationDeadline\n\t);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation.d.ts","sourceRoot":"","sources":["../../src/coordination/operation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authorized operation lifecycle for GUI-driving adapters (Jev audit A6).\n *\n * Two reproduced defects shape this module:\n * F01 — an adapter re-interpreted a free-text instruction at execution time,\n * so an approved candidate and the executed action could differ. Here\n * dispatch consumes a recorded observation binding, never a sentence.\n * F02 — a cancellation signal was accepted and then ignored across the\n * approval await, so a cancelled operation still dispatched. Here the\n * permit is re-evaluated at the dispatch boundary, and cancellation\n * after dispatch can never be reported as cancelled-before-dispatch.\n *\n * Pure state machine: no browser, no driver, no timers. \"Dispatched\" means the\n * command left this process, which is not evidence about the remote effect —\n * that is why `outcome-unknown` is a first-class state rather than a failure.\n */\n\nimport type { Sequence } from \"./types.ts\";\n\nexport type OperationState =\n\t| \"created\"\n\t| \"observed\"\n\t| \"proposed\"\n\t| \"authorized\"\n\t| \"dispatched\"\n\t| \"applied\"\n\t| \"verified\"\n\t| \"inspection-required\"\n\t| \"failed-confirmed\"\n\t| \"outcome-unknown\"\n\t| \"cancelled-before-dispatch\";\n\n/** Outcomes a driver may report once a command has left the process. */\nexport type SettledOutcome = \"applied\" | \"failed-confirmed\" | \"outcome-unknown\";\n\nconst TERMINAL_STATES: ReadonlySet<OperationState> = new Set<OperationState>([\n\t\"verified\",\n\t\"inspection-required\",\n\t\"failed-confirmed\",\n\t\"cancelled-before-dispatch\",\n]);\n\n/**\n * Host-held reference to one observed candidate.\n *\n * The adapter receives these identifiers, not an instruction string, so the\n * action that was approved is the action that runs.\n */\nexport interface ObservationBinding {\n\treadonly observationId: string;\n\treadonly actionId: string;\n\treadonly targetId: string;\n\t/** Document/DOM generation the candidate was observed against. */\n\treadonly documentGeneration: Sequence;\n}\n\nexport type PermitDenial =\n\t| \"cancelled\"\n\t| \"policy-denied\"\n\t| \"approval-mismatch\"\n\t| \"stale-lease\"\n\t| \"observation-invalid\"\n\t| \"budget-exhausted\";\n\nexport type PermitDecision = { readonly allowed: true } | { readonly allowed: false; readonly reason: PermitDenial };\n\nexport interface PermitInput {\n\treadonly policyAllowed: boolean;\n\t/** Digest of the intent actually about to run. */\n\treadonly intentDigest: string;\n\t/** Digest the operator approved; a post-approval edit breaks this equality. */\n\treadonly approvalDigest: string;\n\treadonly policyVersion: string;\n\treadonly approvalPolicyVersion: string;\n\treadonly leaseGeneration: Sequence;\n\treadonly currentLeaseGeneration: Sequence;\n\treadonly observationValid: boolean;\n\treadonly cancelled: boolean;\n\treadonly budgetAvailable: boolean;\n}\n\n/**\n * Permit(I) = PolicyAllowed ∧ ApprovalMatches ∧ LeaseCurrent ∧ ObservationValid\n * ∧ ¬Cancelled ∧ BudgetAvailable.\n *\n * Cancellation is reported first: when a run is cancelled, saying \"budget\n * exhausted\" would send the operator looking for the wrong problem.\n */\nexport function evaluatePermit(input: PermitInput): PermitDecision {\n\tif (input.cancelled) return { allowed: false, reason: \"cancelled\" };\n\tif (!input.policyAllowed) return { allowed: false, reason: \"policy-denied\" };\n\tif (input.approvalDigest !== input.intentDigest || input.approvalPolicyVersion !== input.policyVersion) {\n\t\treturn { allowed: false, reason: \"approval-mismatch\" };\n\t}\n\tif (input.leaseGeneration !== input.currentLeaseGeneration) return { allowed: false, reason: \"stale-lease\" };\n\tif (!input.observationValid) return { allowed: false, reason: \"observation-invalid\" };\n\tif (!input.budgetAvailable) return { allowed: false, reason: \"budget-exhausted\" };\n\treturn { allowed: true };\n}\n\nexport class OperationLifecycle {\n\treadonly operationId: string;\n\tprivate current: OperationState = \"created\";\n\tprivate readonly states: OperationState[] = [\"created\"];\n\tprivate observations: readonly ObservationBinding[] = [];\n\tprivate selected: ObservationBinding | undefined;\n\tprivate hasDispatched = false;\n\tprivate cancelRequested = false;\n\n\tconstructor(operationId: string) {\n\t\tif (typeof operationId !== \"string\" || operationId.length === 0) {\n\t\t\tthrow new TypeError(\"operationId must be a non-empty string\");\n\t\t}\n\t\tthis.operationId = operationId;\n\t}\n\n\tget state(): OperationState {\n\t\treturn this.current;\n\t}\n\n\tget history(): readonly OperationState[] {\n\t\treturn [...this.states];\n\t}\n\n\t/** True once a command left this process; it says nothing about the effect. */\n\tget dispatched(): boolean {\n\t\treturn this.hasDispatched;\n\t}\n\n\tget cancellationRequested(): boolean {\n\t\treturn this.cancelRequested;\n\t}\n\n\tprivate enter(next: OperationState): void {\n\t\tthis.current = next;\n\t\tthis.states.push(next);\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (TERMINAL_STATES.has(this.current)) {\n\t\t\tthrow new Error(`operation ${this.operationId} is terminal in state ${this.current}`);\n\t\t}\n\t}\n\n\t/** Record the host-held candidate set. Without this, nothing can dispatch. */\n\tobserve(candidates: readonly ObservationBinding[]): void {\n\t\tthis.assertOpen();\n\t\tif (candidates.length === 0) throw new TypeError(\"observation must yield at least one candidate\");\n\t\tthis.observations = [...candidates];\n\t\tthis.enter(\"observed\");\n\t}\n\n\t/** Select one observed candidate. Free-text re-interpretation has no path here. */\n\tpropose(candidate: ObservationBinding): void {\n\t\tthis.assertOpen();\n\t\tif (this.observations.length === 0) {\n\t\t\tthrow new Error(\"cannot propose before observe: the candidate set is unbound\");\n\t\t}\n\t\tconst match = this.observations.find(\n\t\t\t(o) =>\n\t\t\t\to.observationId === candidate.observationId &&\n\t\t\t\to.actionId === candidate.actionId &&\n\t\t\t\to.targetId === candidate.targetId &&\n\t\t\t\to.documentGeneration === candidate.documentGeneration,\n\t\t);\n\t\tif (match === undefined) throw new Error(\"proposed action is not among the recorded observations\");\n\t\tthis.selected = match;\n\t\tthis.enter(\"proposed\");\n\t}\n\n\tauthorize(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"proposed\") throw new Error(\"authorize requires a proposed operation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tthis.enter(\"authorized\");\n\t\treturn decision;\n\t}\n\n\t/**\n\t * Re-evaluate the permit and hand the command to the driver.\n\t *\n\t * The second evaluation is the point of the audit finding: approval and\n\t * dispatch are separated by awaits (queueing, the operator prompt, browser\n\t * startup), and state observed before those awaits is not state now.\n\t */\n\tdispatch(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"authorized\") throw new Error(\"dispatch requires an authorized operation\");\n\t\tif (this.selected === undefined) throw new Error(\"dispatch requires a bound observation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tthis.hasDispatched = true;\n\t\tthis.enter(\"dispatched\");\n\t\treturn decision;\n\t}\n\n\tprivate applyPreDispatchDenial(reason: PermitDenial): void {\n\t\tif (reason === \"cancelled\") {\n\t\t\tthis.cancelRequested = true;\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t}\n\t}\n\n\t/** Driver-reported outcome. Only valid once the command actually left. */\n\tsettle(outcome: SettledOutcome): void {\n\t\tif (!this.hasDispatched) throw new Error(\"settle requires a dispatched operation\");\n\t\tif (this.current === \"verified\" || this.current === \"inspection-required\") {\n\t\t\tthrow new Error(\"outcome already verified\");\n\t\t}\n\t\tthis.enter(outcome);\n\t}\n\n\t/** Explicit postcondition check. A driver's return value is not this. */\n\tverify(postconditionMet: boolean): void {\n\t\tif (this.current !== \"applied\") throw new Error(\"verify requires an applied outcome\");\n\t\tthis.enter(postconditionMet ? \"verified\" : \"inspection-required\");\n\t}\n\n\t/**\n\t * Request cancellation.\n\t *\n\t * Before dispatch this is terminal. After dispatch it only records the\n\t * request: the command is already outside this process, so the real outcome\n\t * has to be settled separately and stays `outcome-unknown` until it is.\n\t */\n\tcancel(): void {\n\t\tthis.cancelRequested = true;\n\t\tif (TERMINAL_STATES.has(this.current)) return;\n\t\tif (!this.hasDispatched) {\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.current === \"dispatched\") this.enter(\"outcome-unknown\");\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"operation.d.ts","sourceRoot":"","sources":["../../src/coordination/operation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authorized operation lifecycle for GUI-driving adapters (Jev audit A6).\n *\n * Two reproduced defects shape this module:\n * F01 — an adapter re-interpreted a free-text instruction at execution time,\n * so an approved candidate and the executed action could differ. Here\n * dispatch consumes a recorded observation binding, never a sentence.\n * F02 — a cancellation signal was accepted and then ignored across the\n * approval await, so a cancelled operation still dispatched. Here the\n * permit is re-evaluated at the dispatch boundary, and cancellation\n * after dispatch can never be reported as cancelled-before-dispatch.\n *\n * Pure state machine: no browser, no driver, no timers. \"Dispatched\" means the\n * command left this process, which is not evidence about the remote effect —\n * that is why `outcome-unknown` is a first-class state rather than a failure.\n */\n\nimport type { Sequence } from \"./types.ts\";\nimport { sequence } from \"./types.ts\";\n\nexport type OperationState =\n\t| \"created\"\n\t| \"observed\"\n\t| \"proposed\"\n\t| \"authorized\"\n\t| \"dispatched\"\n\t| \"applied\"\n\t| \"verified\"\n\t| \"inspection-required\"\n\t| \"failed-confirmed\"\n\t| \"outcome-unknown\"\n\t| \"cancelled-before-dispatch\";\n\n/** Outcomes a driver may report once a command has left the process. */\nexport type SettledOutcome = \"applied\" | \"failed-confirmed\" | \"outcome-unknown\";\n\nconst TERMINAL_STATES: ReadonlySet<OperationState> = new Set<OperationState>([\n\t\"verified\",\n\t\"inspection-required\",\n\t\"failed-confirmed\",\n\t\"cancelled-before-dispatch\",\n]);\n\n/**\n * Host-held reference to one observed candidate.\n *\n * The adapter receives these identifiers, not an instruction string, so the\n * action that was approved is the action that runs.\n */\nexport interface ObservationBinding {\n\treadonly observationId: string;\n\treadonly actionId: string;\n\treadonly targetId: string;\n\t/** Document/DOM generation the candidate was observed against. */\n\treadonly documentGeneration: Sequence;\n}\n\nexport type PermitDenial =\n\t| \"cancelled\"\n\t| \"policy-denied\"\n\t| \"approval-mismatch\"\n\t| \"stale-lease\"\n\t| \"observation-invalid\"\n\t| \"budget-exhausted\";\n\nexport type PermitDecision = { readonly allowed: true } | { readonly allowed: false; readonly reason: PermitDenial };\n\nexport interface PermitInput {\n\treadonly policyAllowed: boolean;\n\t/** Digest of the intent actually about to run. */\n\treadonly intentDigest: string;\n\t/** Digest the operator approved; a post-approval edit breaks this equality. */\n\treadonly approvalDigest: string;\n\treadonly policyVersion: string;\n\treadonly approvalPolicyVersion: string;\n\treadonly leaseGeneration: Sequence;\n\treadonly currentLeaseGeneration: Sequence;\n\treadonly observationValid: boolean;\n\treadonly cancelled: boolean;\n\treadonly budgetAvailable: boolean;\n}\n\n/**\n * Permit(I) = PolicyAllowed ∧ ApprovalMatches ∧ LeaseCurrent ∧ ObservationValid\n * ∧ ¬Cancelled ∧ BudgetAvailable.\n *\n * Cancellation is reported first: when a run is cancelled, saying \"budget\n * exhausted\" would send the operator looking for the wrong problem.\n */\nexport function evaluatePermit(input: PermitInput): PermitDecision {\n\tfor (const value of [input.cancelled, input.policyAllowed, input.observationValid, input.budgetAvailable]) {\n\t\tif (typeof value !== \"boolean\") throw new TypeError(\"permit flags must be booleans\");\n\t}\n\tfor (const value of [input.intentDigest, input.approvalDigest, input.policyVersion, input.approvalPolicyVersion]) {\n\t\tif (typeof value !== \"string\" || value.length === 0 || value.length > 4096) {\n\t\t\tthrow new TypeError(\"permit binding must be a bounded non-empty string\");\n\t\t}\n\t}\n\tsequence(input.leaseGeneration);\n\tsequence(input.currentLeaseGeneration);\n\tif (input.cancelled) return { allowed: false, reason: \"cancelled\" };\n\tif (!input.policyAllowed) return { allowed: false, reason: \"policy-denied\" };\n\tif (input.approvalDigest !== input.intentDigest || input.approvalPolicyVersion !== input.policyVersion) {\n\t\treturn { allowed: false, reason: \"approval-mismatch\" };\n\t}\n\tif (input.leaseGeneration !== input.currentLeaseGeneration) return { allowed: false, reason: \"stale-lease\" };\n\tif (!input.observationValid) return { allowed: false, reason: \"observation-invalid\" };\n\tif (!input.budgetAvailable) return { allowed: false, reason: \"budget-exhausted\" };\n\treturn { allowed: true };\n}\n\nexport class OperationLifecycle {\n\treadonly operationId: string;\n\tprivate current: OperationState = \"created\";\n\tprivate readonly states: OperationState[] = [\"created\"];\n\tprivate observations: readonly ObservationBinding[] = [];\n\tprivate selected: ObservationBinding | undefined;\n\tprivate authorizedPermit: Readonly<PermitInput> | undefined;\n\tprivate hasDispatched = false;\n\tprivate cancelRequested = false;\n\n\tconstructor(operationId: string) {\n\t\tif (typeof operationId !== \"string\" || operationId.length === 0) {\n\t\t\tthrow new TypeError(\"operationId must be a non-empty string\");\n\t\t}\n\t\tthis.operationId = operationId;\n\t}\n\n\tget state(): OperationState {\n\t\treturn this.current;\n\t}\n\n\tget history(): readonly OperationState[] {\n\t\treturn [...this.states];\n\t}\n\n\t/** True once a command left this process; it says nothing about the effect. */\n\tget dispatched(): boolean {\n\t\treturn this.hasDispatched;\n\t}\n\n\tget cancellationRequested(): boolean {\n\t\treturn this.cancelRequested;\n\t}\n\n\tprivate enter(next: OperationState): void {\n\t\tthis.current = next;\n\t\tthis.states.push(next);\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (TERMINAL_STATES.has(this.current)) {\n\t\t\tthrow new Error(`operation ${this.operationId} is terminal in state ${this.current}`);\n\t\t}\n\t}\n\n\t/** Record the host-held candidate set. Without this, nothing can dispatch. */\n\tobserve(candidates: readonly ObservationBinding[]): void {\n\t\tthis.assertOpen();\n\t\tif (candidates.length === 0) throw new TypeError(\"observation must yield at least one candidate\");\n\t\tif (this.current !== \"created\" && this.current !== \"observed\") {\n\t\t\tthrow new Error(\"observe requires a created or observed operation\");\n\t\t}\n\t\tconst snapshots = candidates.map((candidate) => {\n\t\t\tfor (const value of [candidate.observationId, candidate.actionId, candidate.targetId]) {\n\t\t\t\tif (typeof value !== \"string\" || value.length === 0 || value.length > 4096) {\n\t\t\t\t\tthrow new TypeError(\"observation binding must be a bounded non-empty string\");\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Object.freeze({\n\t\t\t\tobservationId: candidate.observationId,\n\t\t\t\tactionId: candidate.actionId,\n\t\t\t\ttargetId: candidate.targetId,\n\t\t\t\tdocumentGeneration: sequence(candidate.documentGeneration),\n\t\t\t});\n\t\t});\n\t\tthis.observations = Object.freeze(snapshots);\n\t\tthis.enter(\"observed\");\n\t}\n\n\t/** Select one observed candidate. Free-text re-interpretation has no path here. */\n\tpropose(candidate: ObservationBinding): void {\n\t\tthis.assertOpen();\n\t\tif (this.observations.length === 0) {\n\t\t\tthrow new Error(\"cannot propose before observe: the candidate set is unbound\");\n\t\t}\n\t\tif (this.current !== \"observed\" && this.current !== \"proposed\") {\n\t\t\tthrow new Error(\"propose requires an observed or proposed operation\");\n\t\t}\n\t\tconst match = this.observations.find(\n\t\t\t(o) =>\n\t\t\t\to.observationId === candidate.observationId &&\n\t\t\t\to.actionId === candidate.actionId &&\n\t\t\t\to.targetId === candidate.targetId &&\n\t\t\t\to.documentGeneration === candidate.documentGeneration,\n\t\t);\n\t\tif (match === undefined) throw new Error(\"proposed action is not among the recorded observations\");\n\t\tthis.selected = match;\n\t\tthis.enter(\"proposed\");\n\t}\n\n\tauthorize(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"proposed\") throw new Error(\"authorize requires a proposed operation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tthis.authorizedPermit = Object.freeze({ ...permit });\n\t\tthis.enter(\"authorized\");\n\t\treturn decision;\n\t}\n\n\t/**\n\t * Re-evaluate the permit and hand the command to the driver.\n\t *\n\t * The second evaluation is the point of the audit finding: approval and\n\t * dispatch are separated by awaits (queueing, the operator prompt, browser\n\t * startup), and state observed before those awaits is not state now.\n\t */\n\tdispatch(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"authorized\") throw new Error(\"dispatch requires an authorized operation\");\n\t\tif (this.selected === undefined) throw new Error(\"dispatch requires a bound observation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tconst approved = this.authorizedPermit;\n\t\tif (\n\t\t\tapproved === undefined ||\n\t\t\tapproved.intentDigest !== permit.intentDigest ||\n\t\t\tapproved.approvalDigest !== permit.approvalDigest ||\n\t\t\tapproved.policyVersion !== permit.policyVersion ||\n\t\t\tapproved.approvalPolicyVersion !== permit.approvalPolicyVersion\n\t\t) {\n\t\t\treturn { allowed: false, reason: \"approval-mismatch\" };\n\t\t}\n\t\tif (approved.leaseGeneration !== permit.leaseGeneration) {\n\t\t\treturn { allowed: false, reason: \"stale-lease\" };\n\t\t}\n\t\tthis.hasDispatched = true;\n\t\tthis.enter(\"dispatched\");\n\t\treturn decision;\n\t}\n\n\tprivate applyPreDispatchDenial(reason: PermitDenial): void {\n\t\tif (reason === \"cancelled\") {\n\t\t\tthis.cancelRequested = true;\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t}\n\t}\n\n\t/** Driver-reported outcome. Only valid once the command actually left. */\n\tsettle(outcome: SettledOutcome): void {\n\t\tif (!this.hasDispatched) throw new Error(\"settle requires a dispatched operation\");\n\t\tif (outcome !== \"applied\" && outcome !== \"failed-confirmed\" && outcome !== \"outcome-unknown\") {\n\t\t\tthrow new TypeError(\"invalid settled outcome\");\n\t\t}\n\t\t// Exact duplicate reports are idempotent. A final result cannot be revised.\n\t\tif (this.current === outcome) return;\n\t\tif (this.current !== \"dispatched\" && this.current !== \"outcome-unknown\") {\n\t\t\tthrow new Error(\"settle requires an unresolved dispatched outcome\");\n\t\t}\n\t\tthis.enter(outcome);\n\t}\n\n\t/** Explicit postcondition check. A driver's return value is not this. */\n\tverify(postconditionMet: boolean): void {\n\t\tif (typeof postconditionMet !== \"boolean\") throw new TypeError(\"postcondition must be a boolean\");\n\t\tif (this.current !== \"applied\") throw new Error(\"verify requires an applied outcome\");\n\t\tthis.enter(postconditionMet ? \"verified\" : \"inspection-required\");\n\t}\n\n\t/**\n\t * Request cancellation.\n\t *\n\t * Before dispatch this is terminal. After dispatch it only records the\n\t * request: the command is already outside this process, so the real outcome\n\t * has to be settled separately and stays `outcome-unknown` until it is.\n\t */\n\tcancel(): void {\n\t\tthis.cancelRequested = true;\n\t\tif (TERMINAL_STATES.has(this.current)) return;\n\t\tif (!this.hasDispatched) {\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.current === \"dispatched\") this.enter(\"outcome-unknown\");\n\t}\n}\n"]}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* command left this process, which is not evidence about the remote effect —
|
|
15
15
|
* that is why `outcome-unknown` is a first-class state rather than a failure.
|
|
16
16
|
*/
|
|
17
|
+
import { sequence } from "./types.js";
|
|
17
18
|
const TERMINAL_STATES = new Set([
|
|
18
19
|
"verified",
|
|
19
20
|
"inspection-required",
|
|
@@ -28,6 +29,17 @@ const TERMINAL_STATES = new Set([
|
|
|
28
29
|
* exhausted" would send the operator looking for the wrong problem.
|
|
29
30
|
*/
|
|
30
31
|
export function evaluatePermit(input) {
|
|
32
|
+
for (const value of [input.cancelled, input.policyAllowed, input.observationValid, input.budgetAvailable]) {
|
|
33
|
+
if (typeof value !== "boolean")
|
|
34
|
+
throw new TypeError("permit flags must be booleans");
|
|
35
|
+
}
|
|
36
|
+
for (const value of [input.intentDigest, input.approvalDigest, input.policyVersion, input.approvalPolicyVersion]) {
|
|
37
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 4096) {
|
|
38
|
+
throw new TypeError("permit binding must be a bounded non-empty string");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
sequence(input.leaseGeneration);
|
|
42
|
+
sequence(input.currentLeaseGeneration);
|
|
31
43
|
if (input.cancelled)
|
|
32
44
|
return { allowed: false, reason: "cancelled" };
|
|
33
45
|
if (!input.policyAllowed)
|
|
@@ -49,6 +61,7 @@ export class OperationLifecycle {
|
|
|
49
61
|
states = ["created"];
|
|
50
62
|
observations = [];
|
|
51
63
|
selected;
|
|
64
|
+
authorizedPermit;
|
|
52
65
|
hasDispatched = false;
|
|
53
66
|
cancelRequested = false;
|
|
54
67
|
constructor(operationId) {
|
|
@@ -84,7 +97,23 @@ export class OperationLifecycle {
|
|
|
84
97
|
this.assertOpen();
|
|
85
98
|
if (candidates.length === 0)
|
|
86
99
|
throw new TypeError("observation must yield at least one candidate");
|
|
87
|
-
this.
|
|
100
|
+
if (this.current !== "created" && this.current !== "observed") {
|
|
101
|
+
throw new Error("observe requires a created or observed operation");
|
|
102
|
+
}
|
|
103
|
+
const snapshots = candidates.map((candidate) => {
|
|
104
|
+
for (const value of [candidate.observationId, candidate.actionId, candidate.targetId]) {
|
|
105
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 4096) {
|
|
106
|
+
throw new TypeError("observation binding must be a bounded non-empty string");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return Object.freeze({
|
|
110
|
+
observationId: candidate.observationId,
|
|
111
|
+
actionId: candidate.actionId,
|
|
112
|
+
targetId: candidate.targetId,
|
|
113
|
+
documentGeneration: sequence(candidate.documentGeneration),
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
this.observations = Object.freeze(snapshots);
|
|
88
117
|
this.enter("observed");
|
|
89
118
|
}
|
|
90
119
|
/** Select one observed candidate. Free-text re-interpretation has no path here. */
|
|
@@ -93,6 +122,9 @@ export class OperationLifecycle {
|
|
|
93
122
|
if (this.observations.length === 0) {
|
|
94
123
|
throw new Error("cannot propose before observe: the candidate set is unbound");
|
|
95
124
|
}
|
|
125
|
+
if (this.current !== "observed" && this.current !== "proposed") {
|
|
126
|
+
throw new Error("propose requires an observed or proposed operation");
|
|
127
|
+
}
|
|
96
128
|
const match = this.observations.find((o) => o.observationId === candidate.observationId &&
|
|
97
129
|
o.actionId === candidate.actionId &&
|
|
98
130
|
o.targetId === candidate.targetId &&
|
|
@@ -111,6 +143,7 @@ export class OperationLifecycle {
|
|
|
111
143
|
this.applyPreDispatchDenial(decision.reason);
|
|
112
144
|
return decision;
|
|
113
145
|
}
|
|
146
|
+
this.authorizedPermit = Object.freeze({ ...permit });
|
|
114
147
|
this.enter("authorized");
|
|
115
148
|
return decision;
|
|
116
149
|
}
|
|
@@ -132,6 +165,17 @@ export class OperationLifecycle {
|
|
|
132
165
|
this.applyPreDispatchDenial(decision.reason);
|
|
133
166
|
return decision;
|
|
134
167
|
}
|
|
168
|
+
const approved = this.authorizedPermit;
|
|
169
|
+
if (approved === undefined ||
|
|
170
|
+
approved.intentDigest !== permit.intentDigest ||
|
|
171
|
+
approved.approvalDigest !== permit.approvalDigest ||
|
|
172
|
+
approved.policyVersion !== permit.policyVersion ||
|
|
173
|
+
approved.approvalPolicyVersion !== permit.approvalPolicyVersion) {
|
|
174
|
+
return { allowed: false, reason: "approval-mismatch" };
|
|
175
|
+
}
|
|
176
|
+
if (approved.leaseGeneration !== permit.leaseGeneration) {
|
|
177
|
+
return { allowed: false, reason: "stale-lease" };
|
|
178
|
+
}
|
|
135
179
|
this.hasDispatched = true;
|
|
136
180
|
this.enter("dispatched");
|
|
137
181
|
return decision;
|
|
@@ -146,13 +190,21 @@ export class OperationLifecycle {
|
|
|
146
190
|
settle(outcome) {
|
|
147
191
|
if (!this.hasDispatched)
|
|
148
192
|
throw new Error("settle requires a dispatched operation");
|
|
149
|
-
if (
|
|
150
|
-
throw new
|
|
193
|
+
if (outcome !== "applied" && outcome !== "failed-confirmed" && outcome !== "outcome-unknown") {
|
|
194
|
+
throw new TypeError("invalid settled outcome");
|
|
195
|
+
}
|
|
196
|
+
// Exact duplicate reports are idempotent. A final result cannot be revised.
|
|
197
|
+
if (this.current === outcome)
|
|
198
|
+
return;
|
|
199
|
+
if (this.current !== "dispatched" && this.current !== "outcome-unknown") {
|
|
200
|
+
throw new Error("settle requires an unresolved dispatched outcome");
|
|
151
201
|
}
|
|
152
202
|
this.enter(outcome);
|
|
153
203
|
}
|
|
154
204
|
/** Explicit postcondition check. A driver's return value is not this. */
|
|
155
205
|
verify(postconditionMet) {
|
|
206
|
+
if (typeof postconditionMet !== "boolean")
|
|
207
|
+
throw new TypeError("postcondition must be a boolean");
|
|
156
208
|
if (this.current !== "applied")
|
|
157
209
|
throw new Error("verify requires an applied outcome");
|
|
158
210
|
this.enter(postconditionMet ? "verified" : "inspection-required");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation.js","sourceRoot":"","sources":["../../src/coordination/operation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authorized operation lifecycle for GUI-driving adapters (Jev audit A6).\n *\n * Two reproduced defects shape this module:\n * F01 — an adapter re-interpreted a free-text instruction at execution time,\n * so an approved candidate and the executed action could differ. Here\n * dispatch consumes a recorded observation binding, never a sentence.\n * F02 — a cancellation signal was accepted and then ignored across the\n * approval await, so a cancelled operation still dispatched. Here the\n * permit is re-evaluated at the dispatch boundary, and cancellation\n * after dispatch can never be reported as cancelled-before-dispatch.\n *\n * Pure state machine: no browser, no driver, no timers. \"Dispatched\" means the\n * command left this process, which is not evidence about the remote effect —\n * that is why `outcome-unknown` is a first-class state rather than a failure.\n */\n\nimport type { Sequence } from \"./types.ts\";\n\nexport type OperationState =\n\t| \"created\"\n\t| \"observed\"\n\t| \"proposed\"\n\t| \"authorized\"\n\t| \"dispatched\"\n\t| \"applied\"\n\t| \"verified\"\n\t| \"inspection-required\"\n\t| \"failed-confirmed\"\n\t| \"outcome-unknown\"\n\t| \"cancelled-before-dispatch\";\n\n/** Outcomes a driver may report once a command has left the process. */\nexport type SettledOutcome = \"applied\" | \"failed-confirmed\" | \"outcome-unknown\";\n\nconst TERMINAL_STATES: ReadonlySet<OperationState> = new Set<OperationState>([\n\t\"verified\",\n\t\"inspection-required\",\n\t\"failed-confirmed\",\n\t\"cancelled-before-dispatch\",\n]);\n\n/**\n * Host-held reference to one observed candidate.\n *\n * The adapter receives these identifiers, not an instruction string, so the\n * action that was approved is the action that runs.\n */\nexport interface ObservationBinding {\n\treadonly observationId: string;\n\treadonly actionId: string;\n\treadonly targetId: string;\n\t/** Document/DOM generation the candidate was observed against. */\n\treadonly documentGeneration: Sequence;\n}\n\nexport type PermitDenial =\n\t| \"cancelled\"\n\t| \"policy-denied\"\n\t| \"approval-mismatch\"\n\t| \"stale-lease\"\n\t| \"observation-invalid\"\n\t| \"budget-exhausted\";\n\nexport type PermitDecision = { readonly allowed: true } | { readonly allowed: false; readonly reason: PermitDenial };\n\nexport interface PermitInput {\n\treadonly policyAllowed: boolean;\n\t/** Digest of the intent actually about to run. */\n\treadonly intentDigest: string;\n\t/** Digest the operator approved; a post-approval edit breaks this equality. */\n\treadonly approvalDigest: string;\n\treadonly policyVersion: string;\n\treadonly approvalPolicyVersion: string;\n\treadonly leaseGeneration: Sequence;\n\treadonly currentLeaseGeneration: Sequence;\n\treadonly observationValid: boolean;\n\treadonly cancelled: boolean;\n\treadonly budgetAvailable: boolean;\n}\n\n/**\n * Permit(I) = PolicyAllowed ∧ ApprovalMatches ∧ LeaseCurrent ∧ ObservationValid\n * ∧ ¬Cancelled ∧ BudgetAvailable.\n *\n * Cancellation is reported first: when a run is cancelled, saying \"budget\n * exhausted\" would send the operator looking for the wrong problem.\n */\nexport function evaluatePermit(input: PermitInput): PermitDecision {\n\tif (input.cancelled) return { allowed: false, reason: \"cancelled\" };\n\tif (!input.policyAllowed) return { allowed: false, reason: \"policy-denied\" };\n\tif (input.approvalDigest !== input.intentDigest || input.approvalPolicyVersion !== input.policyVersion) {\n\t\treturn { allowed: false, reason: \"approval-mismatch\" };\n\t}\n\tif (input.leaseGeneration !== input.currentLeaseGeneration) return { allowed: false, reason: \"stale-lease\" };\n\tif (!input.observationValid) return { allowed: false, reason: \"observation-invalid\" };\n\tif (!input.budgetAvailable) return { allowed: false, reason: \"budget-exhausted\" };\n\treturn { allowed: true };\n}\n\nexport class OperationLifecycle {\n\treadonly operationId: string;\n\tprivate current: OperationState = \"created\";\n\tprivate readonly states: OperationState[] = [\"created\"];\n\tprivate observations: readonly ObservationBinding[] = [];\n\tprivate selected: ObservationBinding | undefined;\n\tprivate hasDispatched = false;\n\tprivate cancelRequested = false;\n\n\tconstructor(operationId: string) {\n\t\tif (typeof operationId !== \"string\" || operationId.length === 0) {\n\t\t\tthrow new TypeError(\"operationId must be a non-empty string\");\n\t\t}\n\t\tthis.operationId = operationId;\n\t}\n\n\tget state(): OperationState {\n\t\treturn this.current;\n\t}\n\n\tget history(): readonly OperationState[] {\n\t\treturn [...this.states];\n\t}\n\n\t/** True once a command left this process; it says nothing about the effect. */\n\tget dispatched(): boolean {\n\t\treturn this.hasDispatched;\n\t}\n\n\tget cancellationRequested(): boolean {\n\t\treturn this.cancelRequested;\n\t}\n\n\tprivate enter(next: OperationState): void {\n\t\tthis.current = next;\n\t\tthis.states.push(next);\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (TERMINAL_STATES.has(this.current)) {\n\t\t\tthrow new Error(`operation ${this.operationId} is terminal in state ${this.current}`);\n\t\t}\n\t}\n\n\t/** Record the host-held candidate set. Without this, nothing can dispatch. */\n\tobserve(candidates: readonly ObservationBinding[]): void {\n\t\tthis.assertOpen();\n\t\tif (candidates.length === 0) throw new TypeError(\"observation must yield at least one candidate\");\n\t\tthis.observations = [...candidates];\n\t\tthis.enter(\"observed\");\n\t}\n\n\t/** Select one observed candidate. Free-text re-interpretation has no path here. */\n\tpropose(candidate: ObservationBinding): void {\n\t\tthis.assertOpen();\n\t\tif (this.observations.length === 0) {\n\t\t\tthrow new Error(\"cannot propose before observe: the candidate set is unbound\");\n\t\t}\n\t\tconst match = this.observations.find(\n\t\t\t(o) =>\n\t\t\t\to.observationId === candidate.observationId &&\n\t\t\t\to.actionId === candidate.actionId &&\n\t\t\t\to.targetId === candidate.targetId &&\n\t\t\t\to.documentGeneration === candidate.documentGeneration,\n\t\t);\n\t\tif (match === undefined) throw new Error(\"proposed action is not among the recorded observations\");\n\t\tthis.selected = match;\n\t\tthis.enter(\"proposed\");\n\t}\n\n\tauthorize(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"proposed\") throw new Error(\"authorize requires a proposed operation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tthis.enter(\"authorized\");\n\t\treturn decision;\n\t}\n\n\t/**\n\t * Re-evaluate the permit and hand the command to the driver.\n\t *\n\t * The second evaluation is the point of the audit finding: approval and\n\t * dispatch are separated by awaits (queueing, the operator prompt, browser\n\t * startup), and state observed before those awaits is not state now.\n\t */\n\tdispatch(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"authorized\") throw new Error(\"dispatch requires an authorized operation\");\n\t\tif (this.selected === undefined) throw new Error(\"dispatch requires a bound observation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tthis.hasDispatched = true;\n\t\tthis.enter(\"dispatched\");\n\t\treturn decision;\n\t}\n\n\tprivate applyPreDispatchDenial(reason: PermitDenial): void {\n\t\tif (reason === \"cancelled\") {\n\t\t\tthis.cancelRequested = true;\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t}\n\t}\n\n\t/** Driver-reported outcome. Only valid once the command actually left. */\n\tsettle(outcome: SettledOutcome): void {\n\t\tif (!this.hasDispatched) throw new Error(\"settle requires a dispatched operation\");\n\t\tif (this.current === \"verified\" || this.current === \"inspection-required\") {\n\t\t\tthrow new Error(\"outcome already verified\");\n\t\t}\n\t\tthis.enter(outcome);\n\t}\n\n\t/** Explicit postcondition check. A driver's return value is not this. */\n\tverify(postconditionMet: boolean): void {\n\t\tif (this.current !== \"applied\") throw new Error(\"verify requires an applied outcome\");\n\t\tthis.enter(postconditionMet ? \"verified\" : \"inspection-required\");\n\t}\n\n\t/**\n\t * Request cancellation.\n\t *\n\t * Before dispatch this is terminal. After dispatch it only records the\n\t * request: the command is already outside this process, so the real outcome\n\t * has to be settled separately and stays `outcome-unknown` until it is.\n\t */\n\tcancel(): void {\n\t\tthis.cancelRequested = true;\n\t\tif (TERMINAL_STATES.has(this.current)) return;\n\t\tif (!this.hasDispatched) {\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.current === \"dispatched\") this.enter(\"outcome-unknown\");\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"operation.js","sourceRoot":"","sources":["../../src/coordination/operation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authorized operation lifecycle for GUI-driving adapters (Jev audit A6).\n *\n * Two reproduced defects shape this module:\n * F01 — an adapter re-interpreted a free-text instruction at execution time,\n * so an approved candidate and the executed action could differ. Here\n * dispatch consumes a recorded observation binding, never a sentence.\n * F02 — a cancellation signal was accepted and then ignored across the\n * approval await, so a cancelled operation still dispatched. Here the\n * permit is re-evaluated at the dispatch boundary, and cancellation\n * after dispatch can never be reported as cancelled-before-dispatch.\n *\n * Pure state machine: no browser, no driver, no timers. \"Dispatched\" means the\n * command left this process, which is not evidence about the remote effect —\n * that is why `outcome-unknown` is a first-class state rather than a failure.\n */\n\nimport type { Sequence } from \"./types.ts\";\nimport { sequence } from \"./types.ts\";\n\nexport type OperationState =\n\t| \"created\"\n\t| \"observed\"\n\t| \"proposed\"\n\t| \"authorized\"\n\t| \"dispatched\"\n\t| \"applied\"\n\t| \"verified\"\n\t| \"inspection-required\"\n\t| \"failed-confirmed\"\n\t| \"outcome-unknown\"\n\t| \"cancelled-before-dispatch\";\n\n/** Outcomes a driver may report once a command has left the process. */\nexport type SettledOutcome = \"applied\" | \"failed-confirmed\" | \"outcome-unknown\";\n\nconst TERMINAL_STATES: ReadonlySet<OperationState> = new Set<OperationState>([\n\t\"verified\",\n\t\"inspection-required\",\n\t\"failed-confirmed\",\n\t\"cancelled-before-dispatch\",\n]);\n\n/**\n * Host-held reference to one observed candidate.\n *\n * The adapter receives these identifiers, not an instruction string, so the\n * action that was approved is the action that runs.\n */\nexport interface ObservationBinding {\n\treadonly observationId: string;\n\treadonly actionId: string;\n\treadonly targetId: string;\n\t/** Document/DOM generation the candidate was observed against. */\n\treadonly documentGeneration: Sequence;\n}\n\nexport type PermitDenial =\n\t| \"cancelled\"\n\t| \"policy-denied\"\n\t| \"approval-mismatch\"\n\t| \"stale-lease\"\n\t| \"observation-invalid\"\n\t| \"budget-exhausted\";\n\nexport type PermitDecision = { readonly allowed: true } | { readonly allowed: false; readonly reason: PermitDenial };\n\nexport interface PermitInput {\n\treadonly policyAllowed: boolean;\n\t/** Digest of the intent actually about to run. */\n\treadonly intentDigest: string;\n\t/** Digest the operator approved; a post-approval edit breaks this equality. */\n\treadonly approvalDigest: string;\n\treadonly policyVersion: string;\n\treadonly approvalPolicyVersion: string;\n\treadonly leaseGeneration: Sequence;\n\treadonly currentLeaseGeneration: Sequence;\n\treadonly observationValid: boolean;\n\treadonly cancelled: boolean;\n\treadonly budgetAvailable: boolean;\n}\n\n/**\n * Permit(I) = PolicyAllowed ∧ ApprovalMatches ∧ LeaseCurrent ∧ ObservationValid\n * ∧ ¬Cancelled ∧ BudgetAvailable.\n *\n * Cancellation is reported first: when a run is cancelled, saying \"budget\n * exhausted\" would send the operator looking for the wrong problem.\n */\nexport function evaluatePermit(input: PermitInput): PermitDecision {\n\tfor (const value of [input.cancelled, input.policyAllowed, input.observationValid, input.budgetAvailable]) {\n\t\tif (typeof value !== \"boolean\") throw new TypeError(\"permit flags must be booleans\");\n\t}\n\tfor (const value of [input.intentDigest, input.approvalDigest, input.policyVersion, input.approvalPolicyVersion]) {\n\t\tif (typeof value !== \"string\" || value.length === 0 || value.length > 4096) {\n\t\t\tthrow new TypeError(\"permit binding must be a bounded non-empty string\");\n\t\t}\n\t}\n\tsequence(input.leaseGeneration);\n\tsequence(input.currentLeaseGeneration);\n\tif (input.cancelled) return { allowed: false, reason: \"cancelled\" };\n\tif (!input.policyAllowed) return { allowed: false, reason: \"policy-denied\" };\n\tif (input.approvalDigest !== input.intentDigest || input.approvalPolicyVersion !== input.policyVersion) {\n\t\treturn { allowed: false, reason: \"approval-mismatch\" };\n\t}\n\tif (input.leaseGeneration !== input.currentLeaseGeneration) return { allowed: false, reason: \"stale-lease\" };\n\tif (!input.observationValid) return { allowed: false, reason: \"observation-invalid\" };\n\tif (!input.budgetAvailable) return { allowed: false, reason: \"budget-exhausted\" };\n\treturn { allowed: true };\n}\n\nexport class OperationLifecycle {\n\treadonly operationId: string;\n\tprivate current: OperationState = \"created\";\n\tprivate readonly states: OperationState[] = [\"created\"];\n\tprivate observations: readonly ObservationBinding[] = [];\n\tprivate selected: ObservationBinding | undefined;\n\tprivate authorizedPermit: Readonly<PermitInput> | undefined;\n\tprivate hasDispatched = false;\n\tprivate cancelRequested = false;\n\n\tconstructor(operationId: string) {\n\t\tif (typeof operationId !== \"string\" || operationId.length === 0) {\n\t\t\tthrow new TypeError(\"operationId must be a non-empty string\");\n\t\t}\n\t\tthis.operationId = operationId;\n\t}\n\n\tget state(): OperationState {\n\t\treturn this.current;\n\t}\n\n\tget history(): readonly OperationState[] {\n\t\treturn [...this.states];\n\t}\n\n\t/** True once a command left this process; it says nothing about the effect. */\n\tget dispatched(): boolean {\n\t\treturn this.hasDispatched;\n\t}\n\n\tget cancellationRequested(): boolean {\n\t\treturn this.cancelRequested;\n\t}\n\n\tprivate enter(next: OperationState): void {\n\t\tthis.current = next;\n\t\tthis.states.push(next);\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (TERMINAL_STATES.has(this.current)) {\n\t\t\tthrow new Error(`operation ${this.operationId} is terminal in state ${this.current}`);\n\t\t}\n\t}\n\n\t/** Record the host-held candidate set. Without this, nothing can dispatch. */\n\tobserve(candidates: readonly ObservationBinding[]): void {\n\t\tthis.assertOpen();\n\t\tif (candidates.length === 0) throw new TypeError(\"observation must yield at least one candidate\");\n\t\tif (this.current !== \"created\" && this.current !== \"observed\") {\n\t\t\tthrow new Error(\"observe requires a created or observed operation\");\n\t\t}\n\t\tconst snapshots = candidates.map((candidate) => {\n\t\t\tfor (const value of [candidate.observationId, candidate.actionId, candidate.targetId]) {\n\t\t\t\tif (typeof value !== \"string\" || value.length === 0 || value.length > 4096) {\n\t\t\t\t\tthrow new TypeError(\"observation binding must be a bounded non-empty string\");\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Object.freeze({\n\t\t\t\tobservationId: candidate.observationId,\n\t\t\t\tactionId: candidate.actionId,\n\t\t\t\ttargetId: candidate.targetId,\n\t\t\t\tdocumentGeneration: sequence(candidate.documentGeneration),\n\t\t\t});\n\t\t});\n\t\tthis.observations = Object.freeze(snapshots);\n\t\tthis.enter(\"observed\");\n\t}\n\n\t/** Select one observed candidate. Free-text re-interpretation has no path here. */\n\tpropose(candidate: ObservationBinding): void {\n\t\tthis.assertOpen();\n\t\tif (this.observations.length === 0) {\n\t\t\tthrow new Error(\"cannot propose before observe: the candidate set is unbound\");\n\t\t}\n\t\tif (this.current !== \"observed\" && this.current !== \"proposed\") {\n\t\t\tthrow new Error(\"propose requires an observed or proposed operation\");\n\t\t}\n\t\tconst match = this.observations.find(\n\t\t\t(o) =>\n\t\t\t\to.observationId === candidate.observationId &&\n\t\t\t\to.actionId === candidate.actionId &&\n\t\t\t\to.targetId === candidate.targetId &&\n\t\t\t\to.documentGeneration === candidate.documentGeneration,\n\t\t);\n\t\tif (match === undefined) throw new Error(\"proposed action is not among the recorded observations\");\n\t\tthis.selected = match;\n\t\tthis.enter(\"proposed\");\n\t}\n\n\tauthorize(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"proposed\") throw new Error(\"authorize requires a proposed operation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tthis.authorizedPermit = Object.freeze({ ...permit });\n\t\tthis.enter(\"authorized\");\n\t\treturn decision;\n\t}\n\n\t/**\n\t * Re-evaluate the permit and hand the command to the driver.\n\t *\n\t * The second evaluation is the point of the audit finding: approval and\n\t * dispatch are separated by awaits (queueing, the operator prompt, browser\n\t * startup), and state observed before those awaits is not state now.\n\t */\n\tdispatch(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"authorized\") throw new Error(\"dispatch requires an authorized operation\");\n\t\tif (this.selected === undefined) throw new Error(\"dispatch requires a bound observation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tconst approved = this.authorizedPermit;\n\t\tif (\n\t\t\tapproved === undefined ||\n\t\t\tapproved.intentDigest !== permit.intentDigest ||\n\t\t\tapproved.approvalDigest !== permit.approvalDigest ||\n\t\t\tapproved.policyVersion !== permit.policyVersion ||\n\t\t\tapproved.approvalPolicyVersion !== permit.approvalPolicyVersion\n\t\t) {\n\t\t\treturn { allowed: false, reason: \"approval-mismatch\" };\n\t\t}\n\t\tif (approved.leaseGeneration !== permit.leaseGeneration) {\n\t\t\treturn { allowed: false, reason: \"stale-lease\" };\n\t\t}\n\t\tthis.hasDispatched = true;\n\t\tthis.enter(\"dispatched\");\n\t\treturn decision;\n\t}\n\n\tprivate applyPreDispatchDenial(reason: PermitDenial): void {\n\t\tif (reason === \"cancelled\") {\n\t\t\tthis.cancelRequested = true;\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t}\n\t}\n\n\t/** Driver-reported outcome. Only valid once the command actually left. */\n\tsettle(outcome: SettledOutcome): void {\n\t\tif (!this.hasDispatched) throw new Error(\"settle requires a dispatched operation\");\n\t\tif (outcome !== \"applied\" && outcome !== \"failed-confirmed\" && outcome !== \"outcome-unknown\") {\n\t\t\tthrow new TypeError(\"invalid settled outcome\");\n\t\t}\n\t\t// Exact duplicate reports are idempotent. A final result cannot be revised.\n\t\tif (this.current === outcome) return;\n\t\tif (this.current !== \"dispatched\" && this.current !== \"outcome-unknown\") {\n\t\t\tthrow new Error(\"settle requires an unresolved dispatched outcome\");\n\t\t}\n\t\tthis.enter(outcome);\n\t}\n\n\t/** Explicit postcondition check. A driver's return value is not this. */\n\tverify(postconditionMet: boolean): void {\n\t\tif (typeof postconditionMet !== \"boolean\") throw new TypeError(\"postcondition must be a boolean\");\n\t\tif (this.current !== \"applied\") throw new Error(\"verify requires an applied outcome\");\n\t\tthis.enter(postconditionMet ? \"verified\" : \"inspection-required\");\n\t}\n\n\t/**\n\t * Request cancellation.\n\t *\n\t * Before dispatch this is terminal. After dispatch it only records the\n\t * request: the command is already outside this process, so the real outcome\n\t * has to be settled separately and stays `outcome-unknown` until it is.\n\t */\n\tcancel(): void {\n\t\tthis.cancelRequested = true;\n\t\tif (TERMINAL_STATES.has(this.current)) return;\n\t\tif (!this.hasDispatched) {\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.current === \"dispatched\") this.enter(\"outcome-unknown\");\n\t}\n}\n"]}
|
|
@@ -28,6 +28,6 @@ export declare function claimsConflict(a: ResourceClaim, b: ResourceClaim): bool
|
|
|
28
28
|
export declare function claimSetsConflict(a: readonly ResourceClaim[], b: readonly ResourceClaim[]): boolean;
|
|
29
29
|
/** Stable key for exact-set comparison when binding a start to its reservation. */
|
|
30
30
|
export declare function claimKey(claim: ResourceClaim): string;
|
|
31
|
-
/** Exact set equality by identity+access; a post-hook that widened scope fails this. */
|
|
31
|
+
/** Exact set equality by identity+access+generation; a post-hook that widened scope fails this. */
|
|
32
32
|
export declare function sameClaimSet(a: readonly ResourceClaim[], b: readonly ResourceClaim[]): boolean;
|
|
33
33
|
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/coordination/resource.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAA+B,aAAa,EAAE,MAAM,YAAY,CAAC;AAc7E,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/coordination/resource.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAA+B,aAAa,EAAE,MAAM,YAAY,CAAC;AAc7E,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa,CAuBvE;AAiBD,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAI5E;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAE1E;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,SAAS,aAAa,EAAE,EAAE,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,OAAO,CAEnG;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAErD;AAED,mGAAmG;AACnG,wBAAgB,YAAY,CAAC,CAAC,EAAE,SAAS,aAAa,EAAE,EAAE,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,OAAO,CAS9F","sourcesContent":["/**\n * Resource claim identity and conflict algebra.\n *\n * Two claims conflict when they name overlapping resources and at least one\n * side writes. Overlap is prefix containment on path components, so `src`\n * covers `src/a/x` but never `src/ab` — a shared character prefix that straddles\n * a component boundary is a different resource, not a nested one.\n */\n\nimport type { ClaimAccess, ClaimNamespace, ResourceClaim } from \"./types.ts\";\nimport { sequence } from \"./types.ts\";\n\nconst CLAIM_NAMESPACES: ReadonlySet<string> = new Set<ClaimNamespace>([\n\t\"filesystem\",\n\t\"git-ref\",\n\t\"contract\",\n\t\"socket\",\n\t\"database\",\n\t\"host-budget\",\n]);\nconst CLAIM_ACCESS: ReadonlySet<string> = new Set<ClaimAccess>([\"read\", \"write\"]);\nconst MAX_KEY_LENGTH = 4096;\n\nexport interface ResourceClaimInput {\n\treadonly namespace: string;\n\treadonly instanceId: string;\n\treadonly canonicalKey: string;\n\treadonly access: string;\n\treadonly generation: string;\n}\n\n/**\n * Validate and freeze one claim.\n *\n * A non-canonical key is rejected rather than normalized: normalizing\n * `a/../b` here would let one session hold `a/../b` and another hold `b`\n * while both believe their scopes are disjoint.\n */\nexport function canonicalClaim(input: ResourceClaimInput): ResourceClaim {\n\tif (!CLAIM_NAMESPACES.has(input.namespace)) {\n\t\tthrow new TypeError(`unsupported claim namespace: ${String(input.namespace)}`);\n\t}\n\tif (!CLAIM_ACCESS.has(input.access)) {\n\t\tthrow new TypeError(`unsupported claim access: ${String(input.access)}`);\n\t}\n\tif (\n\t\ttypeof input.instanceId !== \"string\" ||\n\t\tinput.instanceId.length === 0 ||\n\t\tinput.instanceId.length > MAX_KEY_LENGTH ||\n\t\tinput.instanceId.includes(\"\\u0000\")\n\t) {\n\t\tthrow new TypeError(\"claim instanceId must be a bounded non-empty string without NUL\");\n\t}\n\tassertCanonicalKey(input.canonicalKey);\n\treturn Object.freeze({\n\t\tnamespace: input.namespace as ClaimNamespace,\n\t\tinstanceId: input.instanceId,\n\t\tcanonicalKey: input.canonicalKey,\n\t\taccess: input.access as ClaimAccess,\n\t\tgeneration: sequence(input.generation),\n\t});\n}\n\nfunction assertCanonicalKey(key: unknown): asserts key is string {\n\tif (typeof key !== \"string\" || key.length === 0 || key.length > MAX_KEY_LENGTH) {\n\t\tthrow new TypeError(\"claim canonicalKey must be a bounded non-empty string\");\n\t}\n\tif (key.startsWith(\"/\")) throw new TypeError(\"claim canonicalKey must be relative\");\n\tif (key.includes(\"\\u0000\") || key.includes(\"\\\\\")) {\n\t\tthrow new TypeError(\"claim canonicalKey must use slash components without NUL\");\n\t}\n\tfor (const part of key.split(\"/\")) {\n\t\tif (part === \"\" || part === \".\" || part === \"..\") {\n\t\t\tthrow new TypeError(`claim canonicalKey is not canonical: ${key}`);\n\t\t}\n\t}\n}\n\n/** Same resource identity, or one key nested under the other. Ignores access. */\nexport function resourcesOverlap(a: ResourceClaim, b: ResourceClaim): boolean {\n\tif (a.namespace !== b.namespace || a.instanceId !== b.instanceId) return false;\n\tif (a.canonicalKey === b.canonicalKey) return true;\n\treturn a.canonicalKey.startsWith(`${b.canonicalKey}/`) || b.canonicalKey.startsWith(`${a.canonicalKey}/`);\n}\n\nexport function claimsConflict(a: ResourceClaim, b: ResourceClaim): boolean {\n\treturn resourcesOverlap(a, b) && (a.access === \"write\" || b.access === \"write\");\n}\n\nexport function claimSetsConflict(a: readonly ResourceClaim[], b: readonly ResourceClaim[]): boolean {\n\treturn a.some((x) => b.some((y) => claimsConflict(x, y)));\n}\n\n/** Stable key for exact-set comparison when binding a start to its reservation. */\nexport function claimKey(claim: ResourceClaim): string {\n\treturn JSON.stringify([claim.namespace, claim.instanceId, claim.canonicalKey, claim.access, claim.generation]);\n}\n\n/** Exact set equality by identity+access+generation; a post-hook that widened scope fails this. */\nexport function sameClaimSet(a: readonly ResourceClaim[], b: readonly ResourceClaim[]): boolean {\n\tif (a.length !== b.length) return false;\n\tconst byCodeUnit = (x: string, y: string): number => {\n\t\tif (x < y) return -1;\n\t\treturn x > y ? 1 : 0;\n\t};\n\tconst left = a.map(claimKey).sort(byCodeUnit);\n\tconst right = b.map(claimKey).sort(byCodeUnit);\n\treturn left.every((value, index) => value === right[index]);\n}\n"]}
|
|
@@ -31,8 +31,11 @@ export function canonicalClaim(input) {
|
|
|
31
31
|
if (!CLAIM_ACCESS.has(input.access)) {
|
|
32
32
|
throw new TypeError(`unsupported claim access: ${String(input.access)}`);
|
|
33
33
|
}
|
|
34
|
-
if (typeof input.instanceId !== "string" ||
|
|
35
|
-
|
|
34
|
+
if (typeof input.instanceId !== "string" ||
|
|
35
|
+
input.instanceId.length === 0 ||
|
|
36
|
+
input.instanceId.length > MAX_KEY_LENGTH ||
|
|
37
|
+
input.instanceId.includes("\u0000")) {
|
|
38
|
+
throw new TypeError("claim instanceId must be a bounded non-empty string without NUL");
|
|
36
39
|
}
|
|
37
40
|
assertCanonicalKey(input.canonicalKey);
|
|
38
41
|
return Object.freeze({
|
|
@@ -49,6 +52,9 @@ function assertCanonicalKey(key) {
|
|
|
49
52
|
}
|
|
50
53
|
if (key.startsWith("/"))
|
|
51
54
|
throw new TypeError("claim canonicalKey must be relative");
|
|
55
|
+
if (key.includes("\u0000") || key.includes("\\")) {
|
|
56
|
+
throw new TypeError("claim canonicalKey must use slash components without NUL");
|
|
57
|
+
}
|
|
52
58
|
for (const part of key.split("/")) {
|
|
53
59
|
if (part === "" || part === "." || part === "..") {
|
|
54
60
|
throw new TypeError(`claim canonicalKey is not canonical: ${key}`);
|
|
@@ -71,9 +77,9 @@ export function claimSetsConflict(a, b) {
|
|
|
71
77
|
}
|
|
72
78
|
/** Stable key for exact-set comparison when binding a start to its reservation. */
|
|
73
79
|
export function claimKey(claim) {
|
|
74
|
-
return
|
|
80
|
+
return JSON.stringify([claim.namespace, claim.instanceId, claim.canonicalKey, claim.access, claim.generation]);
|
|
75
81
|
}
|
|
76
|
-
/** Exact set equality by identity+access; a post-hook that widened scope fails this. */
|
|
82
|
+
/** Exact set equality by identity+access+generation; a post-hook that widened scope fails this. */
|
|
77
83
|
export function sameClaimSet(a, b) {
|
|
78
84
|
if (a.length !== b.length)
|
|
79
85
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../../src/coordination/resource.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAiB;IACrE,YAAY;IACZ,SAAS;IACT,UAAU;IACV,QAAQ;IACR,UAAU;IACV,aAAa;CACb,CAAC,CAAC;AACH,MAAM,YAAY,GAAwB,IAAI,GAAG,CAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAClF,MAAM,cAAc,GAAG,IAAI,CAAC;AAU5B;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAyB,EAAiB;IACxE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,SAAS,CAAC,gCAAgC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,SAAS,CAAC,6BAA6B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../../src/coordination/resource.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAiB;IACrE,YAAY;IACZ,SAAS;IACT,UAAU;IACV,QAAQ;IACR,UAAU;IACV,aAAa;CACb,CAAC,CAAC;AACH,MAAM,YAAY,GAAwB,IAAI,GAAG,CAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAClF,MAAM,cAAc,GAAG,IAAI,CAAC;AAU5B;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAyB,EAAiB;IACxE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,SAAS,CAAC,gCAAgC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,SAAS,CAAC,6BAA6B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,IACC,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ;QACpC,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;QAC7B,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,cAAc;QACxC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAClC,CAAC;QACF,MAAM,IAAI,SAAS,CAAC,iEAAiE,CAAC,CAAC;IACxF,CAAC;IACD,kBAAkB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC,MAAM,CAAC;QACpB,SAAS,EAAE,KAAK,CAAC,SAA2B;QAC5C,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,MAAM,EAAE,KAAK,CAAC,MAAqB;QACnC,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC;KACtC,CAAC,CAAC;AAAA,CACH;AAED,SAAS,kBAAkB,CAAC,GAAY,EAAyB;IAChE,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;QAChF,MAAM,IAAI,SAAS,CAAC,uDAAuD,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,qCAAqC,CAAC,CAAC;IACpF,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,SAAS,CAAC,0DAA0D,CAAC,CAAC;IACjF,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClD,MAAM,IAAI,SAAS,CAAC,wCAAwC,GAAG,EAAE,CAAC,CAAC;QACpE,CAAC;IACF,CAAC;AAAA,CACD;AAED,iFAAiF;AACjF,MAAM,UAAU,gBAAgB,CAAC,CAAgB,EAAE,CAAgB,EAAW;IAC7E,IAAI,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC/E,IAAI,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IACnD,OAAO,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,YAAY,GAAG,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC;AAAA,CAC1G;AAED,MAAM,UAAU,cAAc,CAAC,CAAgB,EAAE,CAAgB,EAAW;IAC3E,OAAO,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;AAAA,CAChF;AAED,MAAM,UAAU,iBAAiB,CAAC,CAA2B,EAAE,CAA2B,EAAW;IACpG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAAA,CAC1D;AAED,mFAAmF;AACnF,MAAM,UAAU,QAAQ,CAAC,KAAoB,EAAU;IACtD,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;AAAA,CAC/G;AAED,mGAAmG;AACnG,MAAM,UAAU,YAAY,CAAC,CAA2B,EAAE,CAA2B,EAAW;IAC/F,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CAAC;QACpD,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAAA,CACrB,CAAC;IACF,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAAA,CAC5D","sourcesContent":["/**\n * Resource claim identity and conflict algebra.\n *\n * Two claims conflict when they name overlapping resources and at least one\n * side writes. Overlap is prefix containment on path components, so `src`\n * covers `src/a/x` but never `src/ab` — a shared character prefix that straddles\n * a component boundary is a different resource, not a nested one.\n */\n\nimport type { ClaimAccess, ClaimNamespace, ResourceClaim } from \"./types.ts\";\nimport { sequence } from \"./types.ts\";\n\nconst CLAIM_NAMESPACES: ReadonlySet<string> = new Set<ClaimNamespace>([\n\t\"filesystem\",\n\t\"git-ref\",\n\t\"contract\",\n\t\"socket\",\n\t\"database\",\n\t\"host-budget\",\n]);\nconst CLAIM_ACCESS: ReadonlySet<string> = new Set<ClaimAccess>([\"read\", \"write\"]);\nconst MAX_KEY_LENGTH = 4096;\n\nexport interface ResourceClaimInput {\n\treadonly namespace: string;\n\treadonly instanceId: string;\n\treadonly canonicalKey: string;\n\treadonly access: string;\n\treadonly generation: string;\n}\n\n/**\n * Validate and freeze one claim.\n *\n * A non-canonical key is rejected rather than normalized: normalizing\n * `a/../b` here would let one session hold `a/../b` and another hold `b`\n * while both believe their scopes are disjoint.\n */\nexport function canonicalClaim(input: ResourceClaimInput): ResourceClaim {\n\tif (!CLAIM_NAMESPACES.has(input.namespace)) {\n\t\tthrow new TypeError(`unsupported claim namespace: ${String(input.namespace)}`);\n\t}\n\tif (!CLAIM_ACCESS.has(input.access)) {\n\t\tthrow new TypeError(`unsupported claim access: ${String(input.access)}`);\n\t}\n\tif (\n\t\ttypeof input.instanceId !== \"string\" ||\n\t\tinput.instanceId.length === 0 ||\n\t\tinput.instanceId.length > MAX_KEY_LENGTH ||\n\t\tinput.instanceId.includes(\"\\u0000\")\n\t) {\n\t\tthrow new TypeError(\"claim instanceId must be a bounded non-empty string without NUL\");\n\t}\n\tassertCanonicalKey(input.canonicalKey);\n\treturn Object.freeze({\n\t\tnamespace: input.namespace as ClaimNamespace,\n\t\tinstanceId: input.instanceId,\n\t\tcanonicalKey: input.canonicalKey,\n\t\taccess: input.access as ClaimAccess,\n\t\tgeneration: sequence(input.generation),\n\t});\n}\n\nfunction assertCanonicalKey(key: unknown): asserts key is string {\n\tif (typeof key !== \"string\" || key.length === 0 || key.length > MAX_KEY_LENGTH) {\n\t\tthrow new TypeError(\"claim canonicalKey must be a bounded non-empty string\");\n\t}\n\tif (key.startsWith(\"/\")) throw new TypeError(\"claim canonicalKey must be relative\");\n\tif (key.includes(\"\\u0000\") || key.includes(\"\\\\\")) {\n\t\tthrow new TypeError(\"claim canonicalKey must use slash components without NUL\");\n\t}\n\tfor (const part of key.split(\"/\")) {\n\t\tif (part === \"\" || part === \".\" || part === \"..\") {\n\t\t\tthrow new TypeError(`claim canonicalKey is not canonical: ${key}`);\n\t\t}\n\t}\n}\n\n/** Same resource identity, or one key nested under the other. Ignores access. */\nexport function resourcesOverlap(a: ResourceClaim, b: ResourceClaim): boolean {\n\tif (a.namespace !== b.namespace || a.instanceId !== b.instanceId) return false;\n\tif (a.canonicalKey === b.canonicalKey) return true;\n\treturn a.canonicalKey.startsWith(`${b.canonicalKey}/`) || b.canonicalKey.startsWith(`${a.canonicalKey}/`);\n}\n\nexport function claimsConflict(a: ResourceClaim, b: ResourceClaim): boolean {\n\treturn resourcesOverlap(a, b) && (a.access === \"write\" || b.access === \"write\");\n}\n\nexport function claimSetsConflict(a: readonly ResourceClaim[], b: readonly ResourceClaim[]): boolean {\n\treturn a.some((x) => b.some((y) => claimsConflict(x, y)));\n}\n\n/** Stable key for exact-set comparison when binding a start to its reservation. */\nexport function claimKey(claim: ResourceClaim): string {\n\treturn JSON.stringify([claim.namespace, claim.instanceId, claim.canonicalKey, claim.access, claim.generation]);\n}\n\n/** Exact set equality by identity+access+generation; a post-hook that widened scope fails this. */\nexport function sameClaimSet(a: readonly ResourceClaim[], b: readonly ResourceClaim[]): boolean {\n\tif (a.length !== b.length) return false;\n\tconst byCodeUnit = (x: string, y: string): number => {\n\t\tif (x < y) return -1;\n\t\treturn x > y ? 1 : 0;\n\t};\n\tconst left = a.map(claimKey).sort(byCodeUnit);\n\tconst right = b.map(claimKey).sort(byCodeUnit);\n\treturn left.every((value, index) => value === right[index]);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/coordination/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,8EAA8E;AAC9E,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,OAAO,MAAM,CAAA;CAAE,CAAC;AAKvE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAMjD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAEtD;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GACpB,UAAU,GACV,UAAU,GACV,SAAS,GACT,aAAa,GACb,aAAa,GACb,YAAY,GACZ,WAAW,CAAC;AAEf,0DAA0D;AAC1D,eAAO,MAAM,qBAAqB,EAAE,WAAW,CAAC,WAAW,CAAqD,CAAC;AAEjH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,CAAC;AAC3G,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,kBAAkB,EAAE,QAAQ,CAAC;IACtC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACvC;AAED,MAAM,MAAM,gBAAgB,GACzB;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAA;CAAE,GACtE;IAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC","sourcesContent":["/**\n * Wire contracts for parallel-session coordination.\n *\n * Ported from the proposed `contracts.ts` of the 2026-09-20 coordination\n * design. These are new contracts, not pre-existing OMK exports.\n *\n * The central distinction the types encode: an authorization deadline is not a\n * termination proof. A grant whose deadline passed while an external effect was\n * live becomes `quarantined` — still holding its claims — rather than free.\n */\n\n/** Canonical nonnegative decimal string. Ordering is numeric, not lexical. */\nexport type Sequence = string & { readonly __sequence: unique symbol };\n\nconst SEQUENCE_PATTERN = /^(0|[1-9][0-9]*)$/;\nconst MAX_SEQUENCE_LENGTH = 40;\n\nexport function sequence(value: unknown): Sequence {\n\tif (typeof value !== \"string\" || !SEQUENCE_PATTERN.test(value)) {\n\t\tthrow new TypeError(\"sequence must be a canonical nonnegative decimal string\");\n\t}\n\tif (value.length > MAX_SEQUENCE_LENGTH) throw new RangeError(\"sequence too long\");\n\treturn value as Sequence;\n}\n\nexport function nextSequence(value: Sequence): Sequence {\n\treturn String(BigInt(value) + 1n)
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/coordination/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,8EAA8E;AAC9E,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,OAAO,MAAM,CAAA;CAAE,CAAC;AAKvE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAMjD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAEtD;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GACpB,UAAU,GACV,UAAU,GACV,SAAS,GACT,aAAa,GACb,aAAa,GACb,YAAY,GACZ,WAAW,CAAC;AAEf,0DAA0D;AAC1D,eAAO,MAAM,qBAAqB,EAAE,WAAW,CAAC,WAAW,CAAqD,CAAC;AAEjH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,CAAC;AAC3G,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,kBAAkB,EAAE,QAAQ,CAAC;IACtC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACvC;AAED,MAAM,MAAM,gBAAgB,GACzB;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAA;CAAE,GACtE;IAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC","sourcesContent":["/**\n * Wire contracts for parallel-session coordination.\n *\n * Ported from the proposed `contracts.ts` of the 2026-09-20 coordination\n * design. These are new contracts, not pre-existing OMK exports.\n *\n * The central distinction the types encode: an authorization deadline is not a\n * termination proof. A grant whose deadline passed while an external effect was\n * live becomes `quarantined` — still holding its claims — rather than free.\n */\n\n/** Canonical nonnegative decimal string. Ordering is numeric, not lexical. */\nexport type Sequence = string & { readonly __sequence: unique symbol };\n\nconst SEQUENCE_PATTERN = /^(0|[1-9][0-9]*)$/;\nconst MAX_SEQUENCE_LENGTH = 40;\n\nexport function sequence(value: unknown): Sequence {\n\tif (typeof value !== \"string\" || !SEQUENCE_PATTERN.test(value)) {\n\t\tthrow new TypeError(\"sequence must be a canonical nonnegative decimal string\");\n\t}\n\tif (value.length > MAX_SEQUENCE_LENGTH) throw new RangeError(\"sequence too long\");\n\treturn value as Sequence;\n}\n\nexport function nextSequence(value: Sequence): Sequence {\n\treturn sequence(String(BigInt(sequence(value)) + 1n));\n}\n\n/**\n * Lifecycle of an authorized effect.\n *\n * The in-process broker currently mints `reserved`, `running`, `quarantined`,\n * `terminated` and `cancelled`. `starting` and `terminating` are reserved for\n * the out-of-process supervisor that reports those transitions over the wire;\n * nothing emits them yet, and the broker must not infer them.\n */\nexport type EffectState =\n\t| \"reserved\"\n\t| \"starting\"\n\t| \"running\"\n\t| \"terminating\"\n\t| \"quarantined\"\n\t| \"terminated\"\n\t| \"cancelled\";\n\n/** States in which a grant no longer holds its claims. */\nexport const SETTLED_EFFECT_STATES: ReadonlySet<EffectState> = new Set<EffectState>([\"terminated\", \"cancelled\"]);\n\nexport type ClaimNamespace = \"filesystem\" | \"git-ref\" | \"contract\" | \"socket\" | \"database\" | \"host-budget\";\nexport type ClaimAccess = \"read\" | \"write\";\n\n/**\n * One declared resource claim.\n *\n * `canonicalKey` must already be normalized by the caller: the broker rejects\n * rather than guesses, because silently normalizing `a/../b` would let two\n * sessions believe they hold disjoint keys.\n */\nexport interface ResourceClaim {\n\treadonly namespace: ClaimNamespace;\n\treadonly instanceId: string;\n\treadonly canonicalKey: string;\n\treadonly access: ClaimAccess;\n\treadonly generation: Sequence;\n}\n\n/**\n * Proof of admission for exactly one operation of one session incarnation.\n *\n * `authorizationDeadline` bounds the authorization only. Termination is a\n * separate, supervisor-reported fact.\n */\nexport interface GrantToken {\n\treadonly authorityEpoch: Sequence;\n\treadonly grantSequence: Sequence;\n\treadonly sessionId: string;\n\treadonly sessionIncarnation: Sequence;\n\treadonly authorizationDeadline: number;\n}\n\nexport type AdmissionOutcome =\n\t| { readonly status: \"granted\"; readonly token: GrantToken }\n\t| { readonly status: \"blocked\"; readonly blockers: readonly Sequence[] }\n\t| { readonly status: \"over-capacity\"; readonly requestedWeight: number; readonly availableWeight: number };\n"]}
|
|
@@ -19,7 +19,7 @@ export function sequence(value) {
|
|
|
19
19
|
return value;
|
|
20
20
|
}
|
|
21
21
|
export function nextSequence(value) {
|
|
22
|
-
return String(BigInt(value) + 1n);
|
|
22
|
+
return sequence(String(BigInt(sequence(value)) + 1n));
|
|
23
23
|
}
|
|
24
24
|
/** States in which a grant no longer holds its claims. */
|
|
25
25
|
export const SETTLED_EFFECT_STATES = new Set(["terminated", "cancelled"]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/coordination/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAC7C,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B,MAAM,UAAU,QAAQ,CAAC,KAAc,EAAY;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,SAAS,CAAC,yDAAyD,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,mBAAmB;QAAE,MAAM,IAAI,UAAU,CAAC,mBAAmB,CAAC,CAAC;IAClF,OAAO,KAAiB,CAAC;AAAA,CACzB;AAED,MAAM,UAAU,YAAY,CAAC,KAAe,EAAY;IACvD,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/coordination/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAC7C,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B,MAAM,UAAU,QAAQ,CAAC,KAAc,EAAY;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,SAAS,CAAC,yDAAyD,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,mBAAmB;QAAE,MAAM,IAAI,UAAU,CAAC,mBAAmB,CAAC,CAAC;IAClF,OAAO,KAAiB,CAAC;AAAA,CACzB;AAED,MAAM,UAAU,YAAY,CAAC,KAAe,EAAY;IACvD,OAAO,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAAA,CACtD;AAmBD,0DAA0D;AAC1D,MAAM,CAAC,MAAM,qBAAqB,GAA6B,IAAI,GAAG,CAAc,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC","sourcesContent":["/**\n * Wire contracts for parallel-session coordination.\n *\n * Ported from the proposed `contracts.ts` of the 2026-09-20 coordination\n * design. These are new contracts, not pre-existing OMK exports.\n *\n * The central distinction the types encode: an authorization deadline is not a\n * termination proof. A grant whose deadline passed while an external effect was\n * live becomes `quarantined` — still holding its claims — rather than free.\n */\n\n/** Canonical nonnegative decimal string. Ordering is numeric, not lexical. */\nexport type Sequence = string & { readonly __sequence: unique symbol };\n\nconst SEQUENCE_PATTERN = /^(0|[1-9][0-9]*)$/;\nconst MAX_SEQUENCE_LENGTH = 40;\n\nexport function sequence(value: unknown): Sequence {\n\tif (typeof value !== \"string\" || !SEQUENCE_PATTERN.test(value)) {\n\t\tthrow new TypeError(\"sequence must be a canonical nonnegative decimal string\");\n\t}\n\tif (value.length > MAX_SEQUENCE_LENGTH) throw new RangeError(\"sequence too long\");\n\treturn value as Sequence;\n}\n\nexport function nextSequence(value: Sequence): Sequence {\n\treturn sequence(String(BigInt(sequence(value)) + 1n));\n}\n\n/**\n * Lifecycle of an authorized effect.\n *\n * The in-process broker currently mints `reserved`, `running`, `quarantined`,\n * `terminated` and `cancelled`. `starting` and `terminating` are reserved for\n * the out-of-process supervisor that reports those transitions over the wire;\n * nothing emits them yet, and the broker must not infer them.\n */\nexport type EffectState =\n\t| \"reserved\"\n\t| \"starting\"\n\t| \"running\"\n\t| \"terminating\"\n\t| \"quarantined\"\n\t| \"terminated\"\n\t| \"cancelled\";\n\n/** States in which a grant no longer holds its claims. */\nexport const SETTLED_EFFECT_STATES: ReadonlySet<EffectState> = new Set<EffectState>([\"terminated\", \"cancelled\"]);\n\nexport type ClaimNamespace = \"filesystem\" | \"git-ref\" | \"contract\" | \"socket\" | \"database\" | \"host-budget\";\nexport type ClaimAccess = \"read\" | \"write\";\n\n/**\n * One declared resource claim.\n *\n * `canonicalKey` must already be normalized by the caller: the broker rejects\n * rather than guesses, because silently normalizing `a/../b` would let two\n * sessions believe they hold disjoint keys.\n */\nexport interface ResourceClaim {\n\treadonly namespace: ClaimNamespace;\n\treadonly instanceId: string;\n\treadonly canonicalKey: string;\n\treadonly access: ClaimAccess;\n\treadonly generation: Sequence;\n}\n\n/**\n * Proof of admission for exactly one operation of one session incarnation.\n *\n * `authorizationDeadline` bounds the authorization only. Termination is a\n * separate, supervisor-reported fact.\n */\nexport interface GrantToken {\n\treadonly authorityEpoch: Sequence;\n\treadonly grantSequence: Sequence;\n\treadonly sessionId: string;\n\treadonly sessionIncarnation: Sequence;\n\treadonly authorizationDeadline: number;\n}\n\nexport type AdmissionOutcome =\n\t| { readonly status: \"granted\"; readonly token: GrantToken }\n\t| { readonly status: \"blocked\"; readonly blockers: readonly Sequence[] }\n\t| { readonly status: \"over-capacity\"; readonly requestedWeight: number; readonly availableWeight: number };\n"]}
|
|
@@ -119,7 +119,7 @@ export declare function sanitizeConsultPayload(candidate: unknown): AdaptorchCon
|
|
|
119
119
|
export declare function sanitizeAdvisoryResult(candidate: unknown): AdaptorchAdvisoryResult | null;
|
|
120
120
|
/**
|
|
121
121
|
* Read-only/local AdaptOrch MCP tool names this bridge may ever reference,
|
|
122
|
-
* grounded in the
|
|
122
|
+
* grounded in the eleven-tool surface documented in
|
|
123
123
|
* packages/adaptorch-wpl/src/adaptorch-client.ts (cross-checked there
|
|
124
124
|
* against the AdaptOrch server's own docs) and narrowed to the two tools the
|
|
125
125
|
* read-first plan puts in scope for a reasoning-effort hint (plan 4.2): the
|