open-multi-agent-kit 1.2.0 → 1.2.2
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 +46 -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 +77 -0
- package/dist/core/verified-run/supervisor-adapter.d.ts.map +1 -0
- package/dist/core/verified-run/supervisor-adapter.js +164 -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
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { closeSync, existsSync, constants as fsConstants, fstatSync, fsyncSync, ftruncateSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeSync, } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { canonicalClaim, claimSetsConflict, sameClaimSet, } from "../../coordination/resource.js";
|
|
5
|
+
import { SETTLED_EFFECT_STATES, sequence } from "../../coordination/types.js";
|
|
6
|
+
import { atomicRewriteFileSync } from "../atomic-session-file.js";
|
|
7
|
+
import { acquireDurableFileLockSync, acquireDurableFileMutationLockSync, DurableFileLockBusyError, inspectDurableFileLockSync, } from "../durable-file-identity.js";
|
|
8
|
+
import { fsyncDirectorySync, writeExclusiveFileDurablySync } from "../durable-file-io.js";
|
|
9
|
+
import { canonicalJson } from "../run-journal.js";
|
|
10
|
+
import { createAuthorityClock } from "./authority-clock.js";
|
|
11
|
+
import { AuthorityLeaseHeldError, AuthorityStoreError } from "./authority-errors.js";
|
|
12
|
+
import { applyAuthorityEvents, authorityPendingReconcile, parseAuthorityEvent, snapshotFromProjection, } from "./authority-events.js";
|
|
13
|
+
import { GENESIS_PREVIOUS, materializeRecord, parseCommitted, recordLine, } from "./authority-journal.js";
|
|
14
|
+
import { assertSameCommandMeaning, authorizationStillOpen, expiryEvents, sameGrantToken } from "./authority-meaning.js";
|
|
15
|
+
import { readRunClock } from "./recovery-clock.js";
|
|
16
|
+
import { VerifiedRunError } from "./storage.js";
|
|
17
|
+
import { probeOwnedNamespace } from "./supervisor-adapter.js";
|
|
18
|
+
/**
|
|
19
|
+
* Durable authority store (WP03): the single-writer boundary for supervisor
|
|
20
|
+
* authorization state.
|
|
21
|
+
*
|
|
22
|
+
* Storage is a head-sidecar pair — `authority.events.jsonl` plus
|
|
23
|
+
* `authority.events.jsonl.head` — mirroring `ReplayLedgerStore`: the committed
|
|
24
|
+
* head is the transaction boundary, bytes after `head.size` are uncommitted
|
|
25
|
+
* and get quarantined then truncated on the next open, and every append is an
|
|
26
|
+
* expected-head CAS inside the `"mutation"` lock critical section. Cross-process
|
|
27
|
+
* exclusion is the `"authority"` scope durable-file lease held for the store's
|
|
28
|
+
* lifetime; its owner record (pid+host+token) is the writer identity, and dead
|
|
29
|
+
* owners are reclaimed by the existing reclaim protocol.
|
|
30
|
+
*
|
|
31
|
+
* Authority epoch durability: `open` appends `authority-epoch-advanced` inside
|
|
32
|
+
* the lease critical section — never the in-memory default "1" — and leaves the
|
|
33
|
+
* epoch unreconciled. Only `reconcile()` (termination-witness probes per
|
|
34
|
+
* quarantined effect, then `authority-reconciled`) reopens admission. A crash
|
|
35
|
+
* between the epoch event and reconciliation leaves `pendingReconcile` set;
|
|
36
|
+
* the next open adopts that epoch instead of advancing again.
|
|
37
|
+
*/
|
|
38
|
+
export { AuthorityLeaseHeldError, AuthorityStoreError } from "./authority-errors.js";
|
|
39
|
+
const AUTHORITY_SCOPE = "authority";
|
|
40
|
+
export const authorityStorePath = (root) => join(root, "authority.events.jsonl");
|
|
41
|
+
const EMPTY_AUTHORITY_HEAD = Object.freeze({
|
|
42
|
+
fileIdentity: null,
|
|
43
|
+
size: 0,
|
|
44
|
+
lastSeq: 0,
|
|
45
|
+
lastHash: GENESIS_PREVIOUS,
|
|
46
|
+
});
|
|
47
|
+
function authorityHeadsEqual(left, right) {
|
|
48
|
+
return (left.size === right.size &&
|
|
49
|
+
left.lastSeq === right.lastSeq &&
|
|
50
|
+
left.lastHash === right.lastHash &&
|
|
51
|
+
(left.fileIdentity === null
|
|
52
|
+
? right.fileIdentity === null
|
|
53
|
+
: right.fileIdentity !== null &&
|
|
54
|
+
left.fileIdentity.dev === right.fileIdentity.dev &&
|
|
55
|
+
left.fileIdentity.ino === right.fileIdentity.ino));
|
|
56
|
+
}
|
|
57
|
+
function parseHead(raw) {
|
|
58
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
59
|
+
throw new AuthorityStoreError("corrupt");
|
|
60
|
+
const value = raw;
|
|
61
|
+
if (Object.keys(value).sort().join(",") !== "fileIdentity,lastHash,lastSeq,size")
|
|
62
|
+
throw new AuthorityStoreError("corrupt");
|
|
63
|
+
let fileIdentity = null;
|
|
64
|
+
if (value.fileIdentity !== null) {
|
|
65
|
+
if (typeof value.fileIdentity !== "object" || Array.isArray(value.fileIdentity))
|
|
66
|
+
throw new AuthorityStoreError("corrupt");
|
|
67
|
+
const identity = value.fileIdentity;
|
|
68
|
+
if (Object.keys(identity).sort().join(",") !== "dev,ino" ||
|
|
69
|
+
typeof identity.dev !== "string" ||
|
|
70
|
+
!/^\d+$/.test(identity.dev) ||
|
|
71
|
+
typeof identity.ino !== "string" ||
|
|
72
|
+
!/^\d+$/.test(identity.ino))
|
|
73
|
+
throw new AuthorityStoreError("corrupt");
|
|
74
|
+
fileIdentity = Object.freeze({ dev: identity.dev, ino: identity.ino });
|
|
75
|
+
}
|
|
76
|
+
if (!Number.isSafeInteger(value.size) ||
|
|
77
|
+
value.size < 0 ||
|
|
78
|
+
!Number.isSafeInteger(value.lastSeq) ||
|
|
79
|
+
value.lastSeq < 0 ||
|
|
80
|
+
typeof value.lastHash !== "string" ||
|
|
81
|
+
(value.lastSeq === 0 ? value.lastHash !== GENESIS_PREVIOUS : !/^[0-9a-f]{64}$/.test(value.lastHash)))
|
|
82
|
+
throw new AuthorityStoreError("corrupt");
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
fileIdentity,
|
|
85
|
+
size: value.size,
|
|
86
|
+
lastSeq: value.lastSeq,
|
|
87
|
+
lastHash: value.lastHash,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function fileIdentityOf(stat) {
|
|
91
|
+
return Object.freeze({ dev: String(stat.dev), ino: String(stat.ino) });
|
|
92
|
+
}
|
|
93
|
+
function sameFileIdentity(left, right) {
|
|
94
|
+
return left === null ? right === null : right !== null && left.dev === right.dev && left.ino === right.ino;
|
|
95
|
+
}
|
|
96
|
+
function writeAll(fd, bytes) {
|
|
97
|
+
let offset = 0;
|
|
98
|
+
while (offset < bytes.byteLength)
|
|
99
|
+
offset += writeSync(fd, bytes, offset, bytes.byteLength - offset);
|
|
100
|
+
}
|
|
101
|
+
function appendDurably(path, bytes) {
|
|
102
|
+
const fd = openSync(path, fsConstants.O_CREAT | fsConstants.O_APPEND | fsConstants.O_WRONLY, 0o600);
|
|
103
|
+
try {
|
|
104
|
+
writeAll(fd, bytes);
|
|
105
|
+
fsyncSync(fd);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
closeSync(fd);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function publishHead(context, head) {
|
|
112
|
+
const temp = `${context.headPath}.${randomUUID()}.tmp`;
|
|
113
|
+
const fd = openSync(temp, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY, 0o600);
|
|
114
|
+
try {
|
|
115
|
+
writeAll(fd, Buffer.from(`${JSON.stringify(head)}\n`, "utf8"));
|
|
116
|
+
fsyncSync(fd);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
closeSync(fd);
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
renameSync(temp, context.headPath);
|
|
123
|
+
fsyncDirectorySync(dirname(context.headPath));
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (existsSync(temp))
|
|
127
|
+
unlinkSync(temp);
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function quarantineSuffix(context, bytes) {
|
|
132
|
+
context.hooks.beforeQuarantineFsync?.();
|
|
133
|
+
writeExclusiveFileDurablySync(`${context.path}.quarantine-${randomUUID()}`, bytes);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Load committed state. With `repair`, an uncommitted suffix is quarantined
|
|
137
|
+
* and truncated; a file whose identity no longer matches its head is accepted
|
|
138
|
+
* only when it begins with exactly the GC snapshot continuation of that head
|
|
139
|
+
* (the GC rename-landed/head-publish-crashed window), otherwise the store is
|
|
140
|
+
* tampered and opens refuse. Without `repair` the suffix is left untouched
|
|
141
|
+
* and simply not part of committed state.
|
|
142
|
+
*/
|
|
143
|
+
function loadCommitted(context, repair, cached) {
|
|
144
|
+
const fileExists = existsSync(context.path);
|
|
145
|
+
const headExists = existsSync(context.headPath);
|
|
146
|
+
const empty = {
|
|
147
|
+
head: EMPTY_AUTHORITY_HEAD,
|
|
148
|
+
parsed: parseCommitted(Buffer.alloc(0)),
|
|
149
|
+
};
|
|
150
|
+
if (!fileExists && !headExists)
|
|
151
|
+
return empty;
|
|
152
|
+
if (fileExists && !headExists) {
|
|
153
|
+
const fd = openSync(context.path, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
|
|
154
|
+
try {
|
|
155
|
+
const stat = fstatSync(fd);
|
|
156
|
+
if (!stat.isFile() || stat.size !== 0)
|
|
157
|
+
throw new AuthorityStoreError("corrupt");
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
closeSync(fd);
|
|
161
|
+
}
|
|
162
|
+
if (!repair)
|
|
163
|
+
return empty;
|
|
164
|
+
unlinkSync(context.path);
|
|
165
|
+
fsyncDirectorySync(dirname(context.path));
|
|
166
|
+
return empty;
|
|
167
|
+
}
|
|
168
|
+
if (!fileExists)
|
|
169
|
+
throw new AuthorityStoreError("corrupt");
|
|
170
|
+
let head;
|
|
171
|
+
try {
|
|
172
|
+
head = parseHead(JSON.parse(readFileSync(context.headPath, "utf8")));
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (error instanceof AuthorityStoreError || error instanceof VerifiedRunError)
|
|
176
|
+
throw error;
|
|
177
|
+
throw new AuthorityStoreError("corrupt");
|
|
178
|
+
}
|
|
179
|
+
const fd = openSync(context.path, fsConstants.O_RDWR | (fsConstants.O_NOFOLLOW ?? 0));
|
|
180
|
+
try {
|
|
181
|
+
const before = fstatSync(fd);
|
|
182
|
+
const bytes = readFileSync(fd);
|
|
183
|
+
const after = fstatSync(fd);
|
|
184
|
+
if (!before.isFile() ||
|
|
185
|
+
!after.isFile() ||
|
|
186
|
+
before.size !== after.size ||
|
|
187
|
+
before.ctimeMs !== after.ctimeMs ||
|
|
188
|
+
after.size !== bytes.byteLength)
|
|
189
|
+
throw new AuthorityStoreError("tampered");
|
|
190
|
+
const actual = fileIdentityOf(after);
|
|
191
|
+
if (!sameFileIdentity(head.fileIdentity, actual)) {
|
|
192
|
+
head = healGarbageCollect(context, head, bytes, actual, repair);
|
|
193
|
+
}
|
|
194
|
+
if (head.size > after.size)
|
|
195
|
+
throw new AuthorityStoreError("tampered");
|
|
196
|
+
const committed = Buffer.from(bytes.subarray(0, head.size));
|
|
197
|
+
const prefix = cached &&
|
|
198
|
+
sameFileIdentity(cached.head.fileIdentity, actual) &&
|
|
199
|
+
head.size >= cached.head.size &&
|
|
200
|
+
head.lastSeq >= cached.head.lastSeq
|
|
201
|
+
? cached.parsed
|
|
202
|
+
: undefined;
|
|
203
|
+
const parsed = parseCommitted(committed, prefix);
|
|
204
|
+
if (parsed.lastSeq !== head.lastSeq || parsed.lastHash !== head.lastHash)
|
|
205
|
+
throw new AuthorityStoreError("tampered");
|
|
206
|
+
if (head.size < after.size && repair) {
|
|
207
|
+
quarantineSuffix(context, bytes.subarray(head.size));
|
|
208
|
+
ftruncateSync(fd, head.size);
|
|
209
|
+
fsyncSync(fd);
|
|
210
|
+
fsyncDirectorySync(dirname(context.path));
|
|
211
|
+
}
|
|
212
|
+
return { head, parsed };
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
closeSync(fd);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The only legal file whose identity differs from its head: an atomic GC
|
|
220
|
+
* rewrite that landed but whose head publish never committed. Its first
|
|
221
|
+
* record must be exactly the snapshot continuing `head`; the healed head
|
|
222
|
+
* covers just that record, and any bytes after it are uncommitted suffix.
|
|
223
|
+
*/
|
|
224
|
+
function healGarbageCollect(context, head, bytes, actual, repair) {
|
|
225
|
+
if (!repair)
|
|
226
|
+
throw new AuthorityStoreError("tampered");
|
|
227
|
+
let healed = null;
|
|
228
|
+
try {
|
|
229
|
+
const newline = bytes.indexOf(10);
|
|
230
|
+
if (newline > 0) {
|
|
231
|
+
const first = bytes.subarray(0, newline + 1);
|
|
232
|
+
const raw = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(first).slice(0, -1));
|
|
233
|
+
const event = typeof raw === "object" && raw !== null && "event" in raw
|
|
234
|
+
? parseAuthorityEvent(raw.event)
|
|
235
|
+
: null;
|
|
236
|
+
if (event?.kind === "authority-snapshot") {
|
|
237
|
+
const record = materializeRecord(event.archivedThroughSequence + 1, event.continuesHash, event);
|
|
238
|
+
// Only the snapshot line itself must be proven: any bytes after it
|
|
239
|
+
// (including a torn tail) are uncommitted suffix and go to quarantine.
|
|
240
|
+
if (canonicalJson(raw) === canonicalJson(record) &&
|
|
241
|
+
event.archivedThroughSequence === head.lastSeq &&
|
|
242
|
+
event.continuesHash === head.lastHash) {
|
|
243
|
+
healed = Object.freeze({
|
|
244
|
+
fileIdentity: actual,
|
|
245
|
+
size: first.byteLength,
|
|
246
|
+
lastSeq: record.seq,
|
|
247
|
+
lastHash: record.hash,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
if (!(error instanceof AuthorityStoreError || error instanceof VerifiedRunError))
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
if (!healed)
|
|
258
|
+
throw new AuthorityStoreError("tampered");
|
|
259
|
+
publishHead(context, healed);
|
|
260
|
+
return healed;
|
|
261
|
+
}
|
|
262
|
+
function defaultProbe(grant) {
|
|
263
|
+
if (!grant.identity)
|
|
264
|
+
return "unknown";
|
|
265
|
+
return probeOwnedNamespace(grant.identity);
|
|
266
|
+
}
|
|
267
|
+
function nextSequenceValue(value) {
|
|
268
|
+
return sequence(String(BigInt(value) + 1n));
|
|
269
|
+
}
|
|
270
|
+
function assertNonNegativeInteger(value, label) {
|
|
271
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
272
|
+
throw new TypeError(`${label} must be a nonnegative safe integer`);
|
|
273
|
+
}
|
|
274
|
+
/** The durable single-writer authority boundary. */
|
|
275
|
+
export class AuthorityStore {
|
|
276
|
+
context;
|
|
277
|
+
capacity;
|
|
278
|
+
probe;
|
|
279
|
+
lease;
|
|
280
|
+
accepted;
|
|
281
|
+
released = false;
|
|
282
|
+
now;
|
|
283
|
+
incrementalReplay;
|
|
284
|
+
constructor(context, lease, accepted, options) {
|
|
285
|
+
this.context = context;
|
|
286
|
+
this.lease = lease;
|
|
287
|
+
this.accepted = accepted;
|
|
288
|
+
this.capacity = options.capacity;
|
|
289
|
+
this.probe = options.probe ?? defaultProbe;
|
|
290
|
+
this.now = createAuthorityClock(options.clock);
|
|
291
|
+
this.incrementalReplay = options.incrementalReplay !== false;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Take the `"authority"` scope lease and advance the persisted epoch.
|
|
295
|
+
* When the previous epoch never reconciled, this restart adopts it — the
|
|
296
|
+
* pending reconciliation must finish before new admission, and the epoch
|
|
297
|
+
* is not bumped again (review §5 fault row).
|
|
298
|
+
*/
|
|
299
|
+
static open(path, options) {
|
|
300
|
+
assertNonNegativeInteger(options.capacity, "capacity");
|
|
301
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
302
|
+
const context = { path, headPath: `${path}.head`, hooks: options.hooks ?? {} };
|
|
303
|
+
const mutation = acquireDurableFileMutationLockSync(path);
|
|
304
|
+
let lease;
|
|
305
|
+
try {
|
|
306
|
+
lease = acquireDurableFileLockSync(path, AUTHORITY_SCOPE, { timeoutMs: 0 });
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
const failure = error instanceof DurableFileLockBusyError
|
|
310
|
+
? new AuthorityLeaseHeldError(inspectDurableFileLockSync(path, AUTHORITY_SCOPE))
|
|
311
|
+
: error;
|
|
312
|
+
try {
|
|
313
|
+
mutation.release();
|
|
314
|
+
}
|
|
315
|
+
catch (cleanupError) {
|
|
316
|
+
throw new AggregateError([failure, cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError))], "Authority lease acquisition cleanup failed");
|
|
317
|
+
}
|
|
318
|
+
throw failure;
|
|
319
|
+
}
|
|
320
|
+
let store;
|
|
321
|
+
try {
|
|
322
|
+
store = new AuthorityStore(context, lease, loadCommitted(context, true), options);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
try {
|
|
326
|
+
lease.release();
|
|
327
|
+
}
|
|
328
|
+
catch (releaseError) {
|
|
329
|
+
throw new AggregateError([error, releaseError instanceof Error ? releaseError : new Error(String(releaseError))], "Authority store open cleanup failed");
|
|
330
|
+
}
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
mutation.release();
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
if (store.state.epoch === null || !authorityPendingReconcile(store.state)) {
|
|
338
|
+
// Fresh store or fully reconciled previous epoch: this restart
|
|
339
|
+
// durably advances the epoch. A pending epoch is adopted, not bumped.
|
|
340
|
+
store.commitEpochAdvance();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
try {
|
|
345
|
+
store.release();
|
|
346
|
+
}
|
|
347
|
+
catch (releaseError) {
|
|
348
|
+
throw new AggregateError([error, releaseError instanceof Error ? releaseError : new Error(String(releaseError))], "Authority store open cleanup failed");
|
|
349
|
+
}
|
|
350
|
+
throw error;
|
|
351
|
+
}
|
|
352
|
+
return store;
|
|
353
|
+
}
|
|
354
|
+
/** Read-only committed projection; performs no repair and writes nothing. */
|
|
355
|
+
static inspect(path) {
|
|
356
|
+
const context = { path, headPath: `${path}.head`, hooks: {} };
|
|
357
|
+
return loadCommitted(context, false).parsed.state;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Read-only committed journal: projection plus the hashed records it was
|
|
361
|
+
* replayed from, so callers can see transition causes (cancel-requested vs
|
|
362
|
+
* authorization-expired vs epoch restart) and termination witnesses — the
|
|
363
|
+
* projection alone intentionally does not restate them.
|
|
364
|
+
*/
|
|
365
|
+
static inspectJournal(path) {
|
|
366
|
+
const context = { path, headPath: `${path}.head`, hooks: {} };
|
|
367
|
+
const committed = loadCommitted(context, false);
|
|
368
|
+
return Object.freeze({
|
|
369
|
+
state: committed.parsed.state,
|
|
370
|
+
records: committed.parsed.records,
|
|
371
|
+
head: committed.head,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
get path() {
|
|
375
|
+
return this.context.path;
|
|
376
|
+
}
|
|
377
|
+
get head() {
|
|
378
|
+
return this.accepted.head;
|
|
379
|
+
}
|
|
380
|
+
get state() {
|
|
381
|
+
return structuredClone(this.accepted.parsed.state);
|
|
382
|
+
}
|
|
383
|
+
get lastReplay() {
|
|
384
|
+
return this.accepted.parsed.replay;
|
|
385
|
+
}
|
|
386
|
+
get pendingReconcile() {
|
|
387
|
+
return authorityPendingReconcile(this.state);
|
|
388
|
+
}
|
|
389
|
+
writerId() {
|
|
390
|
+
try {
|
|
391
|
+
return `${readRunClock().bootId}:${process.pid}`;
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
return `unknown:${process.pid}`;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Commit events as one transaction: mutation lock → committed-head CAS →
|
|
399
|
+
* reducer pre-validation → append+fsync → head publish → re-read
|
|
400
|
+
* verification. The head CAS is the fencing boundary — a store whose lease
|
|
401
|
+
* was reclaimed and taken by another writer sees a changed head and fails
|
|
402
|
+
* closed instead of authorizing. Persist failures never mint in-memory
|
|
403
|
+
* state (docs/04: no memory-only authorization after storage failure).
|
|
404
|
+
*/
|
|
405
|
+
commit(events) {
|
|
406
|
+
if (this.released)
|
|
407
|
+
throw new AuthorityStoreError("stale_owner");
|
|
408
|
+
const mutation = acquireDurableFileMutationLockSync(this.context.path);
|
|
409
|
+
try {
|
|
410
|
+
const current = loadCommitted(this.context, true, this.incrementalReplay ? this.accepted : undefined);
|
|
411
|
+
if (!authorityHeadsEqual(current.head, this.accepted.head))
|
|
412
|
+
throw new AuthorityStoreError("stale_head");
|
|
413
|
+
if (events.length === 0) {
|
|
414
|
+
this.accepted = current;
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
// Every event must be accepted by the deterministic reducer before a
|
|
418
|
+
// single byte reaches disk.
|
|
419
|
+
applyAuthorityEvents(current.parsed.state, events);
|
|
420
|
+
const lines = [];
|
|
421
|
+
let seq = current.parsed.lastSeq;
|
|
422
|
+
let previous = current.parsed.lastHash;
|
|
423
|
+
for (const event of events) {
|
|
424
|
+
seq += 1;
|
|
425
|
+
const record = materializeRecord(seq, previous, event);
|
|
426
|
+
lines.push(recordLine(record));
|
|
427
|
+
previous = record.hash;
|
|
428
|
+
}
|
|
429
|
+
const payload = Buffer.from(lines.join(""), "utf8");
|
|
430
|
+
(this.context.hooks.persistRecord ?? appendDurably)(this.context.path, payload);
|
|
431
|
+
const fd = openSync(this.context.path, fsConstants.O_RDWR | (fsConstants.O_NOFOLLOW ?? 0));
|
|
432
|
+
let head;
|
|
433
|
+
try {
|
|
434
|
+
const stat = fstatSync(fd);
|
|
435
|
+
// CAS on the file itself: for an established store the inode must be
|
|
436
|
+
// the one the committed head names; for genesis the append must have
|
|
437
|
+
// produced exactly the expected size. Anything else is a replaced or
|
|
438
|
+
// foreign file and commits must not bless it.
|
|
439
|
+
if (stat.size !== current.head.size + payload.byteLength ||
|
|
440
|
+
(current.head.fileIdentity !== null &&
|
|
441
|
+
!sameFileIdentity(current.head.fileIdentity, fileIdentityOf(stat))))
|
|
442
|
+
throw new AuthorityStoreError("stale_head");
|
|
443
|
+
head = Object.freeze({
|
|
444
|
+
fileIdentity: fileIdentityOf(stat),
|
|
445
|
+
size: stat.size,
|
|
446
|
+
lastSeq: seq,
|
|
447
|
+
lastHash: previous,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
closeSync(fd);
|
|
452
|
+
}
|
|
453
|
+
this.context.hooks.afterLedgerFsync?.();
|
|
454
|
+
publishHead(this.context, head);
|
|
455
|
+
const verified = loadCommitted(this.context, false, this.incrementalReplay ? current : undefined);
|
|
456
|
+
if (verified.parsed.lastSeq !== head.lastSeq || verified.parsed.lastHash !== head.lastHash)
|
|
457
|
+
throw new AuthorityStoreError("corrupt");
|
|
458
|
+
this.accepted = verified;
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
mutation.release();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
commitEpochAdvance() {
|
|
465
|
+
const state = this.state;
|
|
466
|
+
const epoch = nextSequenceValue(state.epoch ?? sequence("0"));
|
|
467
|
+
const transitions = [...state.grants.values()]
|
|
468
|
+
.filter((grant) => !SETTLED_EFFECT_STATES.has(grant.state))
|
|
469
|
+
.map((grant) => Object.freeze({
|
|
470
|
+
grantSequence: grant.token.grantSequence,
|
|
471
|
+
outcome: grant.effectLive ? "quarantined" : "cancelled",
|
|
472
|
+
}));
|
|
473
|
+
this.commit([
|
|
474
|
+
{ kind: "authority-epoch-advanced", authorityEpoch: epoch, writerId: this.writerId(), transitions },
|
|
475
|
+
]);
|
|
476
|
+
}
|
|
477
|
+
/** Announce a session incarnation (S2); the counter survives restarts durably. */
|
|
478
|
+
register(sessionId) {
|
|
479
|
+
if (typeof sessionId !== "string" || sessionId.length === 0)
|
|
480
|
+
throw new TypeError("sessionId must be a non-empty string");
|
|
481
|
+
const current = this.state.incarnations.get(sessionId) ?? sequence("0");
|
|
482
|
+
const incarnation = nextSequenceValue(current);
|
|
483
|
+
this.commit([{ kind: "session-registered", sessionId, incarnation }]);
|
|
484
|
+
return incarnation;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Admit an authorization: claim-overlap check, capacity check, idempotency
|
|
488
|
+
* replay, and the `grant-reserved` record in one lease/mutation-locked
|
|
489
|
+
* critical section — the check and the record are one transaction (docs/04).
|
|
490
|
+
* The lease is held for the store's lifetime, so committed state cannot
|
|
491
|
+
* move between the check and the append; the head CAS in commit() is the
|
|
492
|
+
* backstop if it ever could.
|
|
493
|
+
*/
|
|
494
|
+
acquire(input) {
|
|
495
|
+
assertNonNegativeInteger(input.now, "now");
|
|
496
|
+
const weight = input.weight ?? 1;
|
|
497
|
+
assertNonNegativeInteger(weight, "weight");
|
|
498
|
+
if (!Number.isSafeInteger(input.ttl) || input.ttl <= 0)
|
|
499
|
+
throw new TypeError("ttl must be a positive safe integer");
|
|
500
|
+
const authorizationDeadline = input.now + input.ttl;
|
|
501
|
+
if (!Number.isSafeInteger(authorizationDeadline))
|
|
502
|
+
throw new RangeError("authorization deadline overflow");
|
|
503
|
+
const incarnation = sequence(input.incarnation);
|
|
504
|
+
if (this.state.incarnations.get(input.sessionId) !== incarnation)
|
|
505
|
+
throw new AuthorityStoreError("stale_incarnation");
|
|
506
|
+
if (!/^[a-f0-9]{64}$/.test(input.intentDigest))
|
|
507
|
+
throw new TypeError("intentDigest must be a sha256 hex digest");
|
|
508
|
+
if (!Array.isArray(input.claims) || input.claims.length === 0)
|
|
509
|
+
throw new TypeError("an unknown scope must not be admitted as an empty scope");
|
|
510
|
+
const claims = Object.freeze(Array.from(input.claims, (claim) => canonicalClaim(claim)));
|
|
511
|
+
// Idempotency (I08): replayed commands return their recorded disposition
|
|
512
|
+
// without re-executing. A live tombstone returns the result; an expired
|
|
513
|
+
// one reports expiry; an in-flight grant is a duplicate only when the
|
|
514
|
+
// full intent matches — otherwise it is an ID collision (I09/I20).
|
|
515
|
+
const tombstone = this.state.tombstones.get(input.commandId);
|
|
516
|
+
if (tombstone) {
|
|
517
|
+
const grant = this.state.grants.get(tombstone.grantSequence);
|
|
518
|
+
if (!grant)
|
|
519
|
+
throw new AuthorityStoreError("corrupt");
|
|
520
|
+
assertSameCommandMeaning(grant, {
|
|
521
|
+
intentDigest: input.intentDigest,
|
|
522
|
+
sessionId: input.sessionId,
|
|
523
|
+
incarnation,
|
|
524
|
+
claims,
|
|
525
|
+
weight,
|
|
526
|
+
});
|
|
527
|
+
if (tombstone.expiresAtMs <= input.now)
|
|
528
|
+
return { status: "result-expired" };
|
|
529
|
+
return { status: "result", resultDigest: tombstone.resultDigest, token: grant.token };
|
|
530
|
+
}
|
|
531
|
+
for (const grant of this.state.grants.values()) {
|
|
532
|
+
if (grant.commandId !== input.commandId)
|
|
533
|
+
continue;
|
|
534
|
+
assertSameCommandMeaning(grant, {
|
|
535
|
+
intentDigest: input.intentDigest,
|
|
536
|
+
sessionId: input.sessionId,
|
|
537
|
+
incarnation,
|
|
538
|
+
claims,
|
|
539
|
+
weight,
|
|
540
|
+
});
|
|
541
|
+
return { status: "granted", token: grant.token, duplicate: true };
|
|
542
|
+
}
|
|
543
|
+
if (this.pendingReconcile)
|
|
544
|
+
throw new AuthorityStoreError("reconcile_pending");
|
|
545
|
+
const epoch = this.state.epoch;
|
|
546
|
+
if (epoch === null)
|
|
547
|
+
throw new AuthorityStoreError("corrupt");
|
|
548
|
+
const expired = expiryEvents(this.state, input.now, epoch);
|
|
549
|
+
const settledState = expired.length ? applyAuthorityEvents(this.state, expired) : this.state;
|
|
550
|
+
const active = [...settledState.grants.values()].filter((grant) => !SETTLED_EFFECT_STATES.has(grant.state));
|
|
551
|
+
const used = active.reduce((sum, grant) => sum + grant.weight, 0);
|
|
552
|
+
if (weight > this.capacity - used)
|
|
553
|
+
return { status: "blocked" };
|
|
554
|
+
if (active.some((grant) => claimSetsConflict([...claims], [...grant.claims])))
|
|
555
|
+
return { status: "blocked" };
|
|
556
|
+
const grantSequence = nextSequenceValue(this.state.grantCounter);
|
|
557
|
+
const grant = Object.freeze({
|
|
558
|
+
token: Object.freeze({
|
|
559
|
+
authorityEpoch: epoch,
|
|
560
|
+
grantSequence,
|
|
561
|
+
sessionId: input.sessionId,
|
|
562
|
+
sessionIncarnation: incarnation,
|
|
563
|
+
authorizationDeadline,
|
|
564
|
+
}),
|
|
565
|
+
commandId: input.commandId,
|
|
566
|
+
intentDigest: input.intentDigest,
|
|
567
|
+
claims,
|
|
568
|
+
weight,
|
|
569
|
+
state: "reserved",
|
|
570
|
+
effectLive: false,
|
|
571
|
+
dispatchId: null,
|
|
572
|
+
actualClaims: null,
|
|
573
|
+
identity: null,
|
|
574
|
+
});
|
|
575
|
+
this.commit([...expired, { kind: "grant-reserved", grant }]);
|
|
576
|
+
return { status: "granted", token: grant.token, duplicate: false };
|
|
577
|
+
}
|
|
578
|
+
resolveToken(token, authoritative) {
|
|
579
|
+
const grant = this.state.grants.get(token.grantSequence);
|
|
580
|
+
if (!grant || !sameGrantToken(grant.token, token))
|
|
581
|
+
return undefined;
|
|
582
|
+
if (authoritative) {
|
|
583
|
+
if (this.released)
|
|
584
|
+
return undefined;
|
|
585
|
+
if (token.authorityEpoch !== this.state.epoch ||
|
|
586
|
+
this.state.incarnations.get(token.sessionId) !== token.sessionIncarnation ||
|
|
587
|
+
SETTLED_EFFECT_STATES.has(grant.state))
|
|
588
|
+
return undefined;
|
|
589
|
+
}
|
|
590
|
+
else if (SETTLED_EFFECT_STATES.has(grant.state))
|
|
591
|
+
return undefined;
|
|
592
|
+
return grant;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Persisted dispatch intent. A committed intent whose spawn was never
|
|
596
|
+
* witnessed cannot be assumed un-run; on restart it quarantines like any
|
|
597
|
+
* other possibly-live effect (I01/I02).
|
|
598
|
+
*/
|
|
599
|
+
dispatchIntent(token, dispatchId, now = this.now()) {
|
|
600
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(dispatchId))
|
|
601
|
+
throw new TypeError("dispatchId is invalid");
|
|
602
|
+
const grant = this.resolveToken(token, true);
|
|
603
|
+
if (!grant || grant.state !== "reserved")
|
|
604
|
+
return false;
|
|
605
|
+
const epoch = this.state.epoch;
|
|
606
|
+
if (epoch === null)
|
|
607
|
+
return false;
|
|
608
|
+
if (!authorizationStillOpen(now, grant.token.authorizationDeadline)) {
|
|
609
|
+
this.expire(now);
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
this.commit([
|
|
613
|
+
{ kind: "dispatch-intent", grantSequence: grant.token.grantSequence, authorityEpoch: epoch, dispatchId },
|
|
614
|
+
]);
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
/** Bind an effect to its reservation; actual claims must equal the reserved set exactly. */
|
|
618
|
+
effectStarted(token, actualClaims, identity, now = this.now()) {
|
|
619
|
+
const grant = this.resolveToken(token, true);
|
|
620
|
+
if (!grant || (grant.state !== "reserved" && grant.state !== "starting"))
|
|
621
|
+
return false;
|
|
622
|
+
if (!authorizationStillOpen(now, grant.token.authorizationDeadline)) {
|
|
623
|
+
this.expire(now);
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
if (!Array.isArray(actualClaims))
|
|
627
|
+
throw new TypeError("actualClaims must be an array");
|
|
628
|
+
const claims = Object.freeze(Array.from(actualClaims, (entry) => canonicalClaim(entry)));
|
|
629
|
+
if (!sameClaimSet([...claims], [...grant.claims]))
|
|
630
|
+
return false;
|
|
631
|
+
const epoch = this.state.epoch;
|
|
632
|
+
if (epoch === null)
|
|
633
|
+
return false;
|
|
634
|
+
this.commit([
|
|
635
|
+
{
|
|
636
|
+
kind: "effect-started",
|
|
637
|
+
grantSequence: grant.token.grantSequence,
|
|
638
|
+
authorityEpoch: epoch,
|
|
639
|
+
actualClaims: claims,
|
|
640
|
+
...(identity ? { identity } : {}),
|
|
641
|
+
},
|
|
642
|
+
]);
|
|
643
|
+
return true;
|
|
644
|
+
}
|
|
645
|
+
/** Revalidate a running effect before its next irreversible boundary, without starting it again. */
|
|
646
|
+
effectAuthorized(token, actualClaims) {
|
|
647
|
+
if (this.released)
|
|
648
|
+
return false;
|
|
649
|
+
this.commit([]);
|
|
650
|
+
const now = this.now();
|
|
651
|
+
const grant = this.resolveToken(token, true);
|
|
652
|
+
if (!grant || grant.state !== "running")
|
|
653
|
+
return false;
|
|
654
|
+
if (!authorizationStillOpen(now, token.authorizationDeadline)) {
|
|
655
|
+
this.expire(now);
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
return sameClaimSet(Array.from(actualClaims, (claim) => canonicalClaim(claim)), grant.claims);
|
|
659
|
+
}
|
|
660
|
+
/** Requested cancellation is not observed termination; live effects quarantine. */
|
|
661
|
+
cancel(token) {
|
|
662
|
+
const grant = this.resolveToken(token, false);
|
|
663
|
+
if (!grant)
|
|
664
|
+
return false;
|
|
665
|
+
const epoch = this.state.epoch;
|
|
666
|
+
if (epoch === null)
|
|
667
|
+
return false;
|
|
668
|
+
this.commit([
|
|
669
|
+
{
|
|
670
|
+
kind: "cancel-requested",
|
|
671
|
+
grantSequence: grant.token.grantSequence,
|
|
672
|
+
authorityEpoch: epoch,
|
|
673
|
+
outcome: grant.effectLive ? "quarantined" : "cancelled",
|
|
674
|
+
},
|
|
675
|
+
]);
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
/** Settle lapsed authorizations; never evicts a live effect. */
|
|
679
|
+
expire(now) {
|
|
680
|
+
assertNonNegativeInteger(now, "now");
|
|
681
|
+
const epoch = this.state.epoch;
|
|
682
|
+
if (epoch === null)
|
|
683
|
+
return;
|
|
684
|
+
const events = expiryEvents(this.state, now, epoch);
|
|
685
|
+
if (events.length)
|
|
686
|
+
this.commit(events);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Trusted supervisor witness: this exact effect is gone. Accepted across
|
|
690
|
+
* superseded epochs so a restarted authority can settle what the previous
|
|
691
|
+
* one started, never to authorize new work.
|
|
692
|
+
*/
|
|
693
|
+
confirmTerminated(token) {
|
|
694
|
+
const grant = this.resolveToken(token, false);
|
|
695
|
+
if (!grant)
|
|
696
|
+
return false;
|
|
697
|
+
const epoch = this.state.epoch;
|
|
698
|
+
if (epoch === null)
|
|
699
|
+
return false;
|
|
700
|
+
this.commit([{ kind: "termination-observed", grantSequence: grant.token.grantSequence, authorityEpoch: epoch }]);
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
/** Retain the final result as an idempotency tombstone (S3, docs/04). */
|
|
704
|
+
retainResult(commandId, resultDigest, now, retentionMs) {
|
|
705
|
+
assertNonNegativeInteger(now, "now");
|
|
706
|
+
if (!Number.isSafeInteger(retentionMs) || retentionMs <= 0)
|
|
707
|
+
throw new TypeError("retentionMs must be a positive safe integer");
|
|
708
|
+
if (!/^[a-f0-9]{64}$/.test(resultDigest))
|
|
709
|
+
throw new TypeError("resultDigest must be a sha256 hex digest");
|
|
710
|
+
const grant = [...this.state.grants.values()].find((entry) => entry.commandId === commandId);
|
|
711
|
+
if (!grant)
|
|
712
|
+
throw new AuthorityStoreError("unknown_command");
|
|
713
|
+
if (!SETTLED_EFFECT_STATES.has(grant.state))
|
|
714
|
+
throw new AuthorityStoreError("unsettled");
|
|
715
|
+
if (this.state.tombstones.has(commandId))
|
|
716
|
+
throw new AuthorityStoreError("duplicate");
|
|
717
|
+
this.commit([
|
|
718
|
+
{
|
|
719
|
+
kind: "result-retained",
|
|
720
|
+
commandId,
|
|
721
|
+
grantSequence: grant.token.grantSequence,
|
|
722
|
+
resultDigest,
|
|
723
|
+
retainedAtMs: now,
|
|
724
|
+
expiresAtMs: now + retentionMs,
|
|
725
|
+
},
|
|
726
|
+
]);
|
|
727
|
+
}
|
|
728
|
+
/** Command disposition without re-execution (I08/I31). */
|
|
729
|
+
lookup(commandId, now) {
|
|
730
|
+
assertNonNegativeInteger(now, "now");
|
|
731
|
+
const tombstone = this.state.tombstones.get(commandId);
|
|
732
|
+
if (tombstone) {
|
|
733
|
+
const grant = this.state.grants.get(tombstone.grantSequence);
|
|
734
|
+
if (!grant)
|
|
735
|
+
throw new AuthorityStoreError("corrupt");
|
|
736
|
+
return tombstone.expiresAtMs <= now
|
|
737
|
+
? { status: "result-expired", token: grant.token }
|
|
738
|
+
: { status: "result", resultDigest: tombstone.resultDigest, token: grant.token };
|
|
739
|
+
}
|
|
740
|
+
for (const grant of this.state.grants.values())
|
|
741
|
+
if (grant.commandId === commandId)
|
|
742
|
+
return { status: "pending", token: grant.token };
|
|
743
|
+
return { status: "unknown" };
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Finish the restart reconciliation: every quarantined effect is compared
|
|
747
|
+
* against the real execution boundary — only an observed termination
|
|
748
|
+
* settles it ("gone"); "alive"/"unknown" stay quarantined and keep their
|
|
749
|
+
* claims (docs/04, review §6). Ends with `authority-reconciled` for the
|
|
750
|
+
* current epoch; the reconciled scope only then admits new work.
|
|
751
|
+
*/
|
|
752
|
+
reconcile(probe) {
|
|
753
|
+
const state = this.state;
|
|
754
|
+
const epoch = state.epoch;
|
|
755
|
+
if (epoch === null)
|
|
756
|
+
throw new AuthorityStoreError("corrupt");
|
|
757
|
+
if (!authorityPendingReconcile(state))
|
|
758
|
+
return;
|
|
759
|
+
const check = probe ?? this.probe;
|
|
760
|
+
const events = [];
|
|
761
|
+
for (const grant of state.grants.values()) {
|
|
762
|
+
if (grant.state !== "quarantined")
|
|
763
|
+
continue;
|
|
764
|
+
if (check(grant) === "terminated") {
|
|
765
|
+
events.push({
|
|
766
|
+
kind: "termination-observed",
|
|
767
|
+
grantSequence: grant.token.grantSequence,
|
|
768
|
+
authorityEpoch: epoch,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
events.push({ kind: "authority-reconciled", authorityEpoch: epoch });
|
|
773
|
+
this.commit(events);
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* GC (review §7): fold droppable state into an `authority-snapshot` and
|
|
777
|
+
* atomically rewrite the log to that snapshot. Retained: the epoch pair,
|
|
778
|
+
* incarnation counters, the grant counter (I31 — sequences never reissue),
|
|
779
|
+
* every unsettled grant, every settled grant still referenced by an
|
|
780
|
+
* unexpired tombstone, and the unexpired tombstones themselves. A crash
|
|
781
|
+
* between the atomic rewrite and head publish self-heals on next open via
|
|
782
|
+
* the snapshot continuation check.
|
|
783
|
+
*/
|
|
784
|
+
compact(now) {
|
|
785
|
+
assertNonNegativeInteger(now, "now");
|
|
786
|
+
if (this.released)
|
|
787
|
+
throw new AuthorityStoreError("stale_owner");
|
|
788
|
+
const mutation = acquireDurableFileMutationLockSync(this.context.path);
|
|
789
|
+
try {
|
|
790
|
+
const current = loadCommitted(this.context, true);
|
|
791
|
+
if (!authorityHeadsEqual(current.head, this.accepted.head))
|
|
792
|
+
throw new AuthorityStoreError("stale_head");
|
|
793
|
+
const state = current.parsed.state;
|
|
794
|
+
if (state.epoch === null)
|
|
795
|
+
return { dropped: 0 };
|
|
796
|
+
const keepGrant = new Set();
|
|
797
|
+
const keepTombstone = new Set();
|
|
798
|
+
for (const tombstone of state.tombstones.values()) {
|
|
799
|
+
if (tombstone.expiresAtMs > now) {
|
|
800
|
+
keepTombstone.add(tombstone.commandId);
|
|
801
|
+
keepGrant.add(tombstone.grantSequence);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const grants = [...state.grants.values()].filter((grant) => !SETTLED_EFFECT_STATES.has(grant.state) || keepGrant.has(grant.token.grantSequence));
|
|
805
|
+
const tombstones = [...state.tombstones.values()].filter((stone) => keepTombstone.has(stone.commandId));
|
|
806
|
+
const dropped = state.grants.size - grants.length + (state.tombstones.size - tombstones.length);
|
|
807
|
+
if (dropped === 0)
|
|
808
|
+
return { dropped: 0 };
|
|
809
|
+
const snapshot = snapshotFromProjection({
|
|
810
|
+
...state,
|
|
811
|
+
grants: new Map(grants.map((grant) => [grant.token.grantSequence, grant])),
|
|
812
|
+
tombstones: new Map(tombstones.map((stone) => [stone.commandId, stone])),
|
|
813
|
+
});
|
|
814
|
+
const record = materializeRecord(current.parsed.lastSeq + 1, current.parsed.lastHash, {
|
|
815
|
+
kind: "authority-snapshot",
|
|
816
|
+
archivedThroughSequence: current.parsed.lastSeq,
|
|
817
|
+
continuesHash: current.parsed.lastHash,
|
|
818
|
+
state: snapshot,
|
|
819
|
+
});
|
|
820
|
+
atomicRewriteFileSync(this.context.path, recordLine(record));
|
|
821
|
+
this.context.hooks.afterAtomicRewrite?.();
|
|
822
|
+
const fd = openSync(this.context.path, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
|
|
823
|
+
let head;
|
|
824
|
+
try {
|
|
825
|
+
const stat = fstatSync(fd);
|
|
826
|
+
head = Object.freeze({
|
|
827
|
+
fileIdentity: fileIdentityOf(stat),
|
|
828
|
+
size: stat.size,
|
|
829
|
+
lastSeq: record.seq,
|
|
830
|
+
lastHash: record.hash,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
finally {
|
|
834
|
+
closeSync(fd);
|
|
835
|
+
}
|
|
836
|
+
publishHead(this.context, head);
|
|
837
|
+
this.accepted = loadCommitted(this.context, false);
|
|
838
|
+
return { dropped };
|
|
839
|
+
}
|
|
840
|
+
finally {
|
|
841
|
+
mutation.release();
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/** Release the authority lease; further commits fail closed. */
|
|
845
|
+
release() {
|
|
846
|
+
if (this.released)
|
|
847
|
+
return;
|
|
848
|
+
this.released = true;
|
|
849
|
+
this.lease.release();
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
//# sourceMappingURL=authority-store.js.map
|