open-multi-agent-kit 0.99.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -1
- package/README.md +3 -3
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +1 -0
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/register-bundled-coding-agent.d.ts +10 -0
- package/dist/bun/register-bundled-coding-agent.d.ts.map +1 -0
- package/dist/bun/register-bundled-coding-agent.js +12 -0
- package/dist/bun/register-bundled-coding-agent.js.map +1 -0
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +2 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/neo-cli.d.ts +9 -0
- package/dist/commands/neo-cli.d.ts.map +1 -0
- package/dist/commands/neo-cli.js +61 -0
- package/dist/commands/neo-cli.js.map +1 -0
- package/dist/coordination/awareness.d.ts +32 -0
- package/dist/coordination/awareness.d.ts.map +1 -0
- package/dist/coordination/awareness.js +55 -0
- package/dist/coordination/awareness.js.map +1 -0
- package/dist/coordination/broker.d.ts +79 -0
- package/dist/coordination/broker.d.ts.map +1 -0
- package/dist/coordination/broker.js +195 -0
- package/dist/coordination/broker.js.map +1 -0
- package/dist/coordination/index.d.ts +16 -0
- package/dist/coordination/index.d.ts.map +1 -0
- package/dist/coordination/index.js +16 -0
- package/dist/coordination/index.js.map +1 -0
- package/dist/coordination/integration.d.ts +59 -0
- package/dist/coordination/integration.d.ts.map +1 -0
- package/dist/coordination/integration.js +126 -0
- package/dist/coordination/integration.js.map +1 -0
- package/dist/coordination/operation.d.ts +106 -0
- package/dist/coordination/operation.d.ts.map +1 -0
- package/dist/coordination/operation.js +179 -0
- package/dist/coordination/operation.js.map +1 -0
- package/dist/coordination/resource.d.ts +33 -0
- package/dist/coordination/resource.d.ts.map +1 -0
- package/dist/coordination/resource.js +89 -0
- package/dist/coordination/resource.js.map +1 -0
- package/dist/coordination/session.d.ts +74 -0
- package/dist/coordination/session.d.ts.map +1 -0
- package/dist/coordination/session.js +143 -0
- package/dist/coordination/session.js.map +1 -0
- package/dist/coordination/types.d.ts +68 -0
- package/dist/coordination/types.d.ts.map +1 -0
- package/dist/coordination/types.js +26 -0
- package/dist/coordination/types.js.map +1 -0
- package/dist/core/agent-session.d.ts +29 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +135 -37
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bundled-skills.d.ts +4 -0
- package/dist/core/bundled-skills.d.ts.map +1 -0
- package/dist/core/bundled-skills.js +32 -0
- package/dist/core/bundled-skills.js.map +1 -0
- package/dist/core/cli-diagnostics.d.ts +6 -0
- package/dist/core/cli-diagnostics.d.ts.map +1 -0
- package/dist/core/cli-diagnostics.js +20 -0
- package/dist/core/cli-diagnostics.js.map +1 -0
- package/dist/core/compaction/control-state.d.ts +41 -0
- package/dist/core/compaction/control-state.d.ts.map +1 -0
- package/dist/core/compaction/control-state.js +85 -0
- package/dist/core/compaction/control-state.js.map +1 -0
- package/dist/core/compaction/fallback.d.ts +60 -0
- package/dist/core/compaction/fallback.d.ts.map +1 -0
- package/dist/core/compaction/fallback.js +117 -0
- package/dist/core/compaction/fallback.js.map +1 -0
- package/dist/core/compaction/index.d.ts +2 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +2 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/context-budget-headroom-candidates.d.ts +16 -1
- package/dist/core/context-budget-headroom-candidates.d.ts.map +1 -1
- package/dist/core/context-budget-headroom-candidates.js +31 -13
- package/dist/core/context-budget-headroom-candidates.js.map +1 -1
- package/dist/core/context-budget-headroom-types.d.ts +7 -1
- package/dist/core/context-budget-headroom-types.d.ts.map +1 -1
- package/dist/core/context-budget-headroom-types.js +0 -1
- package/dist/core/context-budget-headroom-types.js.map +1 -1
- package/dist/core/context-budget-headroom.d.ts +22 -0
- package/dist/core/context-budget-headroom.d.ts.map +1 -1
- package/dist/core/context-budget-headroom.js +46 -2
- package/dist/core/context-budget-headroom.js.map +1 -1
- package/dist/core/context-budget-v2-global-pass.d.ts +21 -0
- package/dist/core/context-budget-v2-global-pass.d.ts.map +1 -0
- package/dist/core/context-budget-v2-global-pass.js +203 -0
- package/dist/core/context-budget-v2-global-pass.js.map +1 -0
- package/dist/core/context-budget-v2-input-validation.d.ts +4 -0
- package/dist/core/context-budget-v2-input-validation.d.ts.map +1 -0
- package/dist/core/context-budget-v2-input-validation.js +79 -0
- package/dist/core/context-budget-v2-input-validation.js.map +1 -0
- package/dist/core/context-budget-v2-observability.d.ts +15 -0
- package/dist/core/context-budget-v2-observability.d.ts.map +1 -0
- package/dist/core/context-budget-v2-observability.js +35 -0
- package/dist/core/context-budget-v2-observability.js.map +1 -0
- package/dist/core/context-budget-v2-planned-items.d.ts +17 -0
- package/dist/core/context-budget-v2-planned-items.d.ts.map +1 -0
- package/dist/core/context-budget-v2-planned-items.js +59 -0
- package/dist/core/context-budget-v2-planned-items.js.map +1 -0
- package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
- package/dist/core/context-budget-v2-planner.js +69 -51
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-scoring.d.ts +7 -1
- package/dist/core/context-budget-v2-scoring.d.ts.map +1 -1
- package/dist/core/context-budget-v2-scoring.js.map +1 -1
- package/dist/core/context-budget-v2-selection.d.ts +22 -3
- package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
- package/dist/core/context-budget-v2-selection.js +41 -59
- package/dist/core/context-budget-v2-selection.js.map +1 -1
- package/dist/core/context-budget-v2-tiers.d.ts.map +1 -1
- package/dist/core/context-budget-v2-tiers.js +9 -42
- package/dist/core/context-budget-v2-tiers.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +14 -2
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js +6 -1
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/extensions/bundled-virtual-modules.d.ts +15 -0
- package/dist/core/extensions/bundled-virtual-modules.d.ts.map +1 -0
- package/dist/core/extensions/bundled-virtual-modules.js +63 -0
- package/dist/core/extensions/bundled-virtual-modules.js.map +1 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +2 -42
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +6 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +10 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/loadout-runtime.d.ts +2 -8
- package/dist/core/loadout-runtime.d.ts.map +1 -1
- package/dist/core/loadout-runtime.js.map +1 -1
- package/dist/core/mcp/client.d.ts +27 -6
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +80 -20
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/manager.d.ts +10 -1
- package/dist/core/mcp/manager.d.ts.map +1 -1
- package/dist/core/mcp/manager.js +68 -9
- package/dist/core/mcp/manager.js.map +1 -1
- package/dist/core/mcp/protocol.d.ts +9 -3
- package/dist/core/mcp/protocol.d.ts.map +1 -1
- package/dist/core/mcp/protocol.js +61 -15
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/stdio-transport.d.ts +12 -1
- package/dist/core/mcp/stdio-transport.d.ts.map +1 -1
- package/dist/core/mcp/stdio-transport.js +30 -2
- package/dist/core/mcp/stdio-transport.js.map +1 -1
- package/dist/core/mcp-descriptor-injection.d.ts +26 -0
- package/dist/core/mcp-descriptor-injection.d.ts.map +1 -0
- package/dist/core/mcp-descriptor-injection.js +25 -0
- package/dist/core/mcp-descriptor-injection.js.map +1 -0
- package/dist/core/mcp-public-presets.d.ts +1 -3
- package/dist/core/mcp-public-presets.d.ts.map +1 -1
- package/dist/core/mcp-public-presets.js +3 -15
- package/dist/core/mcp-public-presets.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +24 -3
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/neo/catalog.d.ts +20 -0
- package/dist/core/neo/catalog.d.ts.map +1 -0
- package/dist/core/neo/catalog.js +50 -0
- package/dist/core/neo/catalog.js.map +1 -0
- package/dist/core/neo/setup.d.ts +3 -0
- package/dist/core/neo/setup.d.ts.map +1 -0
- package/dist/core/neo/setup.js +47 -0
- package/dist/core/neo/setup.js.map +1 -0
- package/dist/core/provider-default-models.d.ts +2 -0
- package/dist/core/provider-default-models.d.ts.map +1 -1
- package/dist/core/provider-default-models.js +2 -0
- package/dist/core/provider-default-models.js.map +1 -1
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +2 -0
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/core/provider-error-classification.d.ts +57 -0
- package/dist/core/provider-error-classification.d.ts.map +1 -0
- package/dist/core/provider-error-classification.js +103 -0
- package/dist/core/provider-error-classification.js.map +1 -0
- package/dist/core/provider-resilience.d.ts +10 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +36 -3
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/provider-usage-commandcode.d.ts +9 -0
- package/dist/core/provider-usage-commandcode.d.ts.map +1 -0
- package/dist/core/provider-usage-commandcode.js +198 -0
- package/dist/core/provider-usage-commandcode.js.map +1 -0
- package/dist/core/provider-usage-devin.d.ts +5 -3
- package/dist/core/provider-usage-devin.d.ts.map +1 -1
- package/dist/core/provider-usage-devin.js +15 -5
- package/dist/core/provider-usage-devin.js.map +1 -1
- package/dist/core/provider-usage-types.d.ts +1 -1
- package/dist/core/provider-usage-types.d.ts.map +1 -1
- package/dist/core/provider-usage-types.js.map +1 -1
- package/dist/core/provider-usage.d.ts.map +1 -1
- package/dist/core/provider-usage.js +6 -0
- package/dist/core/provider-usage.js.map +1 -1
- package/dist/core/reasoning-router-resolver.d.ts +7 -0
- package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
- package/dist/core/reasoning-router-resolver.js +21 -3
- package/dist/core/reasoning-router-resolver.js.map +1 -1
- package/dist/core/reasoning-router-v4.d.ts +8 -5
- package/dist/core/reasoning-router-v4.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4.js +8 -5
- package/dist/core/reasoning-router-v4.js.map +1 -1
- package/dist/core/resource-admission.d.ts +36 -0
- package/dist/core/resource-admission.d.ts.map +1 -1
- package/dist/core/resource-admission.js +59 -0
- package/dist/core/resource-admission.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +2 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/run-usage-ledger.d.ts +47 -0
- package/dist/core/run-usage-ledger.d.ts.map +1 -0
- package/dist/core/run-usage-ledger.js +162 -0
- package/dist/core/run-usage-ledger.js.map +1 -0
- package/dist/core/run-usage-operation.d.ts +8 -0
- package/dist/core/run-usage-operation.d.ts.map +1 -0
- package/dist/core/run-usage-operation.js +12 -0
- package/dist/core/run-usage-operation.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +5 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-compaction-service.d.ts +15 -0
- package/dist/core/session-compaction-service.d.ts.map +1 -1
- package/dist/core/session-compaction-service.js +17 -4
- package/dist/core/session-compaction-service.js.map +1 -1
- package/dist/core/session-failure-cause.d.ts.map +1 -1
- package/dist/core/session-failure-cause.js +14 -5
- package/dist/core/session-failure-cause.js.map +1 -1
- package/dist/core/session-prompt-lifecycle.d.ts +6 -0
- package/dist/core/session-prompt-lifecycle.d.ts.map +1 -1
- package/dist/core/session-prompt-lifecycle.js +47 -2
- package/dist/core/session-prompt-lifecycle.js.map +1 -1
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +4 -2
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/subagent-lane-authority.d.ts +28 -0
- package/dist/core/subagent-lane-authority.d.ts.map +1 -0
- package/dist/core/subagent-lane-authority.js +119 -0
- package/dist/core/subagent-lane-authority.js.map +1 -0
- package/dist/core/subagent-lane-contract.d.ts +114 -0
- package/dist/core/subagent-lane-contract.d.ts.map +1 -0
- package/dist/core/subagent-lane-contract.js +18 -0
- package/dist/core/subagent-lane-contract.js.map +1 -0
- package/dist/core/subagent-lane-launcher.d.ts +3 -6
- package/dist/core/subagent-lane-launcher.d.ts.map +1 -1
- package/dist/core/subagent-lane-launcher.js +53 -12
- package/dist/core/subagent-lane-launcher.js.map +1 -1
- package/dist/core/subagent-orchestration.d.ts +4 -17
- package/dist/core/subagent-orchestration.d.ts.map +1 -1
- package/dist/core/subagent-orchestration.js.map +1 -1
- package/dist/core/todo-runtime-state.d.ts +12 -0
- package/dist/core/todo-runtime-state.d.ts.map +1 -1
- package/dist/core/todo-runtime-state.js +23 -0
- package/dist/core/todo-runtime-state.js.map +1 -1
- package/dist/core/verified-run/broker.d.ts.map +1 -1
- package/dist/core/verified-run/broker.js +4 -1
- package/dist/core/verified-run/broker.js.map +1 -1
- package/dist/core/workload-permit-pool.d.ts +1 -1
- package/dist/core/workload-permit-pool.d.ts.map +1 -1
- package/dist/core/workload-permit-pool.js +3 -0
- package/dist/core/workload-permit-pool.js.map +1 -1
- package/dist/guardrails/strict-evidence-approval-adapter.d.ts +34 -0
- package/dist/guardrails/strict-evidence-approval-adapter.d.ts.map +1 -0
- package/dist/guardrails/strict-evidence-approval-adapter.js +70 -0
- package/dist/guardrails/strict-evidence-approval-adapter.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -18
- package/dist/main.js.map +1 -1
- package/dist/metacognition/calibration-selective.d.ts +78 -0
- package/dist/metacognition/calibration-selective.d.ts.map +1 -0
- package/dist/metacognition/calibration-selective.js +161 -0
- package/dist/metacognition/calibration-selective.js.map +1 -0
- package/dist/metacognition/calibration.d.ts +62 -0
- package/dist/metacognition/calibration.d.ts.map +1 -0
- package/dist/metacognition/calibration.js +122 -0
- package/dist/metacognition/calibration.js.map +1 -0
- package/dist/metacognition/checkpoint.d.ts +60 -0
- package/dist/metacognition/checkpoint.d.ts.map +1 -0
- package/dist/metacognition/checkpoint.js +57 -0
- package/dist/metacognition/checkpoint.js.map +1 -0
- package/dist/metacognition/context7.d.ts +48 -0
- package/dist/metacognition/context7.d.ts.map +1 -0
- package/dist/metacognition/context7.js +154 -0
- package/dist/metacognition/context7.js.map +1 -0
- package/dist/metacognition/decision.d.ts +44 -0
- package/dist/metacognition/decision.d.ts.map +1 -0
- package/dist/metacognition/decision.js +111 -0
- package/dist/metacognition/decision.js.map +1 -0
- package/dist/metacognition/evaluation.d.ts +73 -0
- package/dist/metacognition/evaluation.d.ts.map +1 -0
- package/dist/metacognition/evaluation.js +97 -0
- package/dist/metacognition/evaluation.js.map +1 -0
- package/dist/metacognition/index.d.ts +31 -0
- package/dist/metacognition/index.d.ts.map +1 -0
- package/dist/metacognition/index.js +31 -0
- package/dist/metacognition/index.js.map +1 -0
- package/dist/metacognition/knowledge-action.d.ts +43 -0
- package/dist/metacognition/knowledge-action.d.ts.map +1 -0
- package/dist/metacognition/knowledge-action.js +60 -0
- package/dist/metacognition/knowledge-action.js.map +1 -0
- package/dist/metacognition/knowledge.d.ts +84 -0
- package/dist/metacognition/knowledge.d.ts.map +1 -0
- package/dist/metacognition/knowledge.js +165 -0
- package/dist/metacognition/knowledge.js.map +1 -0
- package/dist/metacognition/obligations.d.ts +82 -0
- package/dist/metacognition/obligations.d.ts.map +1 -0
- package/dist/metacognition/obligations.js +139 -0
- package/dist/metacognition/obligations.js.map +1 -0
- package/dist/metacognition/observation-validity.d.ts +83 -0
- package/dist/metacognition/observation-validity.d.ts.map +1 -0
- package/dist/metacognition/observation-validity.js +118 -0
- package/dist/metacognition/observation-validity.js.map +1 -0
- package/dist/metacognition/observe.d.ts +45 -0
- package/dist/metacognition/observe.d.ts.map +1 -0
- package/dist/metacognition/observe.js +59 -0
- package/dist/metacognition/observe.js.map +1 -0
- package/dist/metacognition/policy.d.ts +53 -0
- package/dist/metacognition/policy.d.ts.map +1 -0
- package/dist/metacognition/policy.js +155 -0
- package/dist/metacognition/policy.js.map +1 -0
- package/dist/metacognition/predictions.d.ts +78 -0
- package/dist/metacognition/predictions.d.ts.map +1 -0
- package/dist/metacognition/predictions.js +181 -0
- package/dist/metacognition/predictions.js.map +1 -0
- package/dist/metacognition/retrieval.d.ts +53 -0
- package/dist/metacognition/retrieval.d.ts.map +1 -0
- package/dist/metacognition/retrieval.js +170 -0
- package/dist/metacognition/retrieval.js.map +1 -0
- package/dist/metacognition/risk.d.ts +48 -0
- package/dist/metacognition/risk.d.ts.map +1 -0
- package/dist/metacognition/risk.js +165 -0
- package/dist/metacognition/risk.js.map +1 -0
- package/dist/metacognition/route-economics.d.ts +95 -0
- package/dist/metacognition/route-economics.d.ts.map +1 -0
- package/dist/metacognition/route-economics.js +110 -0
- package/dist/metacognition/route-economics.js.map +1 -0
- package/dist/metacognition/runtime-bridge.d.ts +54 -0
- package/dist/metacognition/runtime-bridge.d.ts.map +1 -0
- package/dist/metacognition/runtime-bridge.js +140 -0
- package/dist/metacognition/runtime-bridge.js.map +1 -0
- package/dist/metacognition/skills.d.ts +42 -0
- package/dist/metacognition/skills.d.ts.map +1 -0
- package/dist/metacognition/skills.js +220 -0
- package/dist/metacognition/skills.js.map +1 -0
- package/dist/metacognition/state.d.ts +110 -0
- package/dist/metacognition/state.d.ts.map +1 -0
- package/dist/metacognition/state.js +52 -0
- package/dist/metacognition/state.js.map +1 -0
- package/dist/metacognition/validation.d.ts +19 -0
- package/dist/metacognition/validation.d.ts.map +1 -0
- package/dist/metacognition/validation.js +58 -0
- package/dist/metacognition/validation.js.map +1 -0
- package/dist/metacognition/verification.d.ts +93 -0
- package/dist/metacognition/verification.d.ts.map +1 -0
- package/dist/metacognition/verification.js +132 -0
- package/dist/metacognition/verification.js.map +1 -0
- package/dist/metacognition/verifier.d.ts +49 -0
- package/dist/metacognition/verifier.d.ts.map +1 -0
- package/dist/metacognition/verifier.js +88 -0
- package/dist/metacognition/verifier.js.map +1 -0
- package/dist/modes/acp/acp-agent.d.ts +24 -0
- package/dist/modes/acp/acp-agent.d.ts.map +1 -0
- package/dist/modes/acp/acp-agent.js +133 -0
- package/dist/modes/acp/acp-agent.js.map +1 -0
- package/dist/modes/acp/acp-mode.d.ts +4 -0
- package/dist/modes/acp/acp-mode.d.ts.map +1 -0
- package/dist/modes/acp/acp-mode.js +34 -0
- package/dist/modes/acp/acp-mode.js.map +1 -0
- package/dist/modes/acp/acp-session.d.ts +4 -0
- package/dist/modes/acp/acp-session.d.ts.map +1 -0
- package/dist/modes/acp/acp-session.js +76 -0
- package/dist/modes/acp/acp-session.js.map +1 -0
- package/dist/modes/acp/acp-transport.d.ts +5 -0
- package/dist/modes/acp/acp-transport.d.ts.map +1 -0
- package/dist/modes/acp/acp-transport.js +90 -0
- package/dist/modes/acp/acp-transport.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/observation/identity.d.ts +14 -0
- package/dist/observation/identity.d.ts.map +1 -0
- package/dist/observation/identity.js +29 -0
- package/dist/observation/identity.js.map +1 -0
- package/dist/observation/index.d.ts +13 -0
- package/dist/observation/index.d.ts.map +1 -0
- package/dist/observation/index.js +13 -0
- package/dist/observation/index.js.map +1 -0
- package/dist/observation/observe-mode.d.ts +46 -0
- package/dist/observation/observe-mode.d.ts.map +1 -0
- package/dist/observation/observe-mode.js +83 -0
- package/dist/observation/observe-mode.js.map +1 -0
- package/dist/observation/store.d.ts +53 -0
- package/dist/observation/store.d.ts.map +1 -0
- package/dist/observation/store.js +126 -0
- package/dist/observation/store.js.map +1 -0
- package/dist/observation/types.d.ts +72 -0
- package/dist/observation/types.d.ts.map +1 -0
- package/dist/observation/types.js +9 -0
- package/dist/observation/types.js.map +1 -0
- package/dist/observation/view.d.ts +39 -0
- package/dist/observation/view.d.ts.map +1 -0
- package/dist/observation/view.js +173 -0
- package/dist/observation/view.js.map +1 -0
- package/docs/audit-revalidation-2026-09-17.md +77 -0
- package/docs/devin-harness.md +33 -4
- package/docs/ecraf-normalization.md +83 -0
- package/docs/metacognition.md +51 -0
- package/docs/model-catalog-refresh.md +110 -1
- package/docs/models.md +1 -1
- package/docs/neo.md +134 -0
- package/docs/providers.md +125 -6
- package/docs/run-usage-ledger.md +56 -0
- package/docs/runtime-algorithms.md +151 -3
- package/docs/skills.md +1 -1
- package/docs/usage.md +2 -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/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/subagent/adaptive-agent-runtime.ts +14 -1
- package/examples/extensions/subagent/graph-result.ts +48 -0
- package/examples/extensions/subagent/index.ts +246 -111
- package/examples/extensions/subagent/managed-process-tree.ts +42 -0
- package/examples/extensions/subagent/managed-process.test.ts +24 -0
- package/examples/extensions/subagent/managed-process.ts +93 -112
- package/examples/extensions/subagent/subagent-runtime-types.ts +17 -1
- package/examples/extensions/subagent/subagent-stream.ts +161 -0
- package/examples/extensions/terminal-browser/README.md +54 -0
- package/examples/extensions/terminal-browser/bridge-protocol.ts +29 -0
- package/examples/extensions/terminal-browser/bridge.ts +219 -0
- package/examples/extensions/terminal-browser/browser-surface.ts +288 -0
- package/examples/extensions/terminal-browser/index.ts +215 -0
- package/examples/extensions/terminal-browser/placeholders.ts +49 -0
- 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 +8 -7
- package/resources/neo/skills/omk-browser/SKILL.md +32 -0
- package/resources/neo/skills/omk-code-review/SKILL.md +24 -0
- package/resources/neo/skills/omk-computeruse/SKILL.md +34 -0
- package/resources/neo/skills/omk-mcp-setup/SKILL.md +48 -0
- package/resources/neo/skills/omk-research/SKILL.md +24 -0
- package/resources/neo/skills/omk-site/SKILL.md +28 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neo-cli.js","sourceRoot":"","sources":["../../src/commands/neo-cli.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { NEO_MCP_PRESETS, NEO_SKILL_NAMES, neoMcpConfig, selectNeoMcpPresets } from \"../core/neo/catalog.ts\";\nimport { createNeoMcpConfig } from \"../core/neo/setup.ts\";\n\nexport interface NeoCliContext {\n\treadonly packageDir: string;\n\treadonly cwd: string;\n\treadonly home: string;\n\treadonly output: (text: string) => void;\n}\n\n/** Local, keyless distribution/setup commands. Never starts a server or a browser. */\nexport function runNeoCli(args: readonly string[], context: NeoCliContext): number {\n\tconst [command = \"list\", ...rest] = args;\n\ttry {\n\t\tif (command === \"list\" && rest.length === 0) {\n\t\t\tconst skills = NEO_SKILL_NAMES.map((name) => ({\n\t\t\t\tname,\n\t\t\t\tpackaged: existsSync(join(context.packageDir, \"resources\", \"neo\", \"skills\", name, \"SKILL.md\")),\n\t\t\t}));\n\t\t\tcontext.output(\n\t\t\t\tJSON.stringify(\n\t\t\t\t\t{\n\t\t\t\t\t\tbundle: \"neo-v1\",\n\t\t\t\t\t\tskills,\n\t\t\t\t\t\tmcp: { offered: NEO_MCP_PRESETS, connected: null, connectionStatus: \"not_probed\" },\n\t\t\t\t\t\tnote: \"Packaged does not mean selected. Offered does not mean installed, enabled, connected, or approved.\",\n\t\t\t\t\t},\n\t\t\t\t\tnull,\n\t\t\t\t\t2,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn skills.every((skill) => skill.packaged) ? 0 : 1;\n\t\t}\n\t\tif (command === \"mcp-config\") {\n\t\t\tcontext.output(JSON.stringify(neoMcpConfig(rest, false), null, 2));\n\t\t\treturn 0;\n\t\t}\n\t\tif (command === \"setup\") {\n\t\t\tif (\n\t\t\t\trest.filter((arg) => arg === \"--apply\").length > 1 ||\n\t\t\t\trest.filter((arg) => arg === \"--global\").length > 1\n\t\t\t) {\n\t\t\t\tthrow new Error(\"Duplicate setup flag\");\n\t\t\t}\n\t\t\tconst apply = rest.includes(\"--apply\");\n\t\t\tconst root = rest.includes(\"--global\") ? context.home : context.cwd;\n\t\t\tconst ids = rest.filter((arg) => arg !== \"--apply\" && arg !== \"--global\");\n\t\t\tconst presets = selectNeoMcpPresets(ids);\n\t\t\tif (!apply) {\n\t\t\t\tcontext.output(\n\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstatus: \"dry_run\",\n\t\t\t\t\t\t\ttarget: join(root, \".omk\", \"mcp.json\"),\n\t\t\t\t\t\t\tpresets,\n\t\t\t\t\t\t\tconfig: neoMcpConfig(ids, false),\n\t\t\t\t\t\t\tapproval:\n\t\t\t\t\t\t\t\t\"--apply creates enabled entries. On the next OMK startup npx may download and execute the pinned servers, which can access the network. No server is started by this command.\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\t2,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tconst target = createNeoMcpConfig(root, ids);\n\t\t\tcontext.output(\n\t\t\t\tJSON.stringify(\n\t\t\t\t\t{\n\t\t\t\t\t\tstatus: \"configured_not_connected\",\n\t\t\t\t\t\ttarget,\n\t\t\t\t\t\tpresets: ids,\n\t\t\t\t\t\tnext: \"Restart OMK, inspect the MCP connection/tool roster, and run a read-only health check before acting. Existing .omk/mcp.json in a project can override global entries.\",\n\t\t\t\t\t},\n\t\t\t\t\tnull,\n\t\t\t\t\t2,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn 0;\n\t\t}\n\t\tthrow new Error(\"Usage: omk neo [list | mcp-config <presets...> | setup <presets...> [--global] [--apply]]\");\n\t} catch (error) {\n\t\tcontext.output(\n\t\t\tJSON.stringify({ status: \"blocked\", error: error instanceof Error ? error.message : \"Neo command failed\" }),\n\t\t);\n\t\treturn 1;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change awareness: what a change invalidates, and who may proceed next.
|
|
3
|
+
*
|
|
4
|
+
* Both functions are deliberately conservative. Invalidation treats an
|
|
5
|
+
* unmapped dependent as affected, because "we have no edge for it" is not
|
|
6
|
+
* evidence that it is unaffected. Frontier admission lets an unrelated
|
|
7
|
+
* follower pass a blocked leader, but never lets a later request that
|
|
8
|
+
* conflicts with an older waiting one jump ahead of it.
|
|
9
|
+
*/
|
|
10
|
+
import type { ResourceClaim } from "./types.ts";
|
|
11
|
+
export interface WaitingRequest {
|
|
12
|
+
readonly requestId: string;
|
|
13
|
+
readonly claims: readonly ResourceClaim[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Transitive closure over reverse dependencies.
|
|
17
|
+
*
|
|
18
|
+
* `unknownDependents` are nodes whose dependencies could not be resolved; they
|
|
19
|
+
* join the affected set rather than being assumed safe. Reaching a fixpoint
|
|
20
|
+
* terminates on cycles because the set only grows and is bounded by the graph.
|
|
21
|
+
*/
|
|
22
|
+
export declare function invalidate(dependencies: Readonly<Record<string, readonly string[]>>, changed: readonly string[], unknownDependents?: readonly string[]): Set<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Requests admissible right now, in queue order.
|
|
25
|
+
*
|
|
26
|
+
* A request is admitted only when it conflicts neither with the active claims
|
|
27
|
+
* nor with any earlier waiting request. Accumulating earlier claims — whether
|
|
28
|
+
* or not they were admitted — is what stops a stream of later readers from
|
|
29
|
+
* starving an older writer. Capacity fairness is not modelled here.
|
|
30
|
+
*/
|
|
31
|
+
export declare function admissibleFrontier(waiting: readonly WaitingRequest[], active: readonly ResourceClaim[]): string[];
|
|
32
|
+
//# sourceMappingURL=awareness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/coordination/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACzB,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,EACzD,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,iBAAiB,GAAE,SAAS,MAAM,EAAO,GACvC,GAAG,CAAC,MAAM,CAAC,CAab;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,EAAE,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,EAAE,CAYjH","sourcesContent":["/**\n * Change awareness: what a change invalidates, and who may proceed next.\n *\n * Both functions are deliberately conservative. Invalidation treats an\n * unmapped dependent as affected, because \"we have no edge for it\" is not\n * evidence that it is unaffected. Frontier admission lets an unrelated\n * follower pass a blocked leader, but never lets a later request that\n * conflicts with an older waiting one jump ahead of it.\n */\n\nimport { claimSetsConflict } from \"./resource.ts\";\nimport type { ResourceClaim } from \"./types.ts\";\n\nexport interface WaitingRequest {\n\treadonly requestId: string;\n\treadonly claims: readonly ResourceClaim[];\n}\n\n/**\n * Transitive closure over reverse dependencies.\n *\n * `unknownDependents` are nodes whose dependencies could not be resolved; they\n * join the affected set rather than being assumed safe. Reaching a fixpoint\n * terminates on cycles because the set only grows and is bounded by the graph.\n */\nexport function invalidate(\n\tdependencies: Readonly<Record<string, readonly string[]>>,\n\tchanged: readonly string[],\n\tunknownDependents: readonly string[] = [],\n): Set<string> {\n\tconst affected = new Set<string>([...changed, ...unknownDependents]);\n\tfor (;;) {\n\t\tlet grew = false;\n\t\tfor (const [node, deps] of Object.entries(dependencies)) {\n\t\t\tif (affected.has(node)) continue;\n\t\t\tif (deps.some((dep) => affected.has(dep))) {\n\t\t\t\taffected.add(node);\n\t\t\t\tgrew = true;\n\t\t\t}\n\t\t}\n\t\tif (!grew) return affected;\n\t}\n}\n\n/**\n * Requests admissible right now, in queue order.\n *\n * A request is admitted only when it conflicts neither with the active claims\n * nor with any earlier waiting request. Accumulating earlier claims — whether\n * or not they were admitted — is what stops a stream of later readers from\n * starving an older writer. Capacity fairness is not modelled here.\n */\nexport function admissibleFrontier(waiting: readonly WaitingRequest[], active: readonly ResourceClaim[]): string[] {\n\tconst chosen: string[] = [];\n\tconst reserved: ResourceClaim[] = [...active];\n\tconst earlier: ResourceClaim[] = [];\n\tfor (const request of waiting) {\n\t\tif (!claimSetsConflict(request.claims, reserved) && !claimSetsConflict(request.claims, earlier)) {\n\t\t\tchosen.push(request.requestId);\n\t\t\treserved.push(...request.claims);\n\t\t}\n\t\tearlier.push(...request.claims);\n\t}\n\treturn chosen;\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change awareness: what a change invalidates, and who may proceed next.
|
|
3
|
+
*
|
|
4
|
+
* Both functions are deliberately conservative. Invalidation treats an
|
|
5
|
+
* unmapped dependent as affected, because "we have no edge for it" is not
|
|
6
|
+
* evidence that it is unaffected. Frontier admission lets an unrelated
|
|
7
|
+
* follower pass a blocked leader, but never lets a later request that
|
|
8
|
+
* conflicts with an older waiting one jump ahead of it.
|
|
9
|
+
*/
|
|
10
|
+
import { claimSetsConflict } from "./resource.js";
|
|
11
|
+
/**
|
|
12
|
+
* Transitive closure over reverse dependencies.
|
|
13
|
+
*
|
|
14
|
+
* `unknownDependents` are nodes whose dependencies could not be resolved; they
|
|
15
|
+
* join the affected set rather than being assumed safe. Reaching a fixpoint
|
|
16
|
+
* terminates on cycles because the set only grows and is bounded by the graph.
|
|
17
|
+
*/
|
|
18
|
+
export function invalidate(dependencies, changed, unknownDependents = []) {
|
|
19
|
+
const affected = new Set([...changed, ...unknownDependents]);
|
|
20
|
+
for (;;) {
|
|
21
|
+
let grew = false;
|
|
22
|
+
for (const [node, deps] of Object.entries(dependencies)) {
|
|
23
|
+
if (affected.has(node))
|
|
24
|
+
continue;
|
|
25
|
+
if (deps.some((dep) => affected.has(dep))) {
|
|
26
|
+
affected.add(node);
|
|
27
|
+
grew = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!grew)
|
|
31
|
+
return affected;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Requests admissible right now, in queue order.
|
|
36
|
+
*
|
|
37
|
+
* A request is admitted only when it conflicts neither with the active claims
|
|
38
|
+
* nor with any earlier waiting request. Accumulating earlier claims — whether
|
|
39
|
+
* or not they were admitted — is what stops a stream of later readers from
|
|
40
|
+
* starving an older writer. Capacity fairness is not modelled here.
|
|
41
|
+
*/
|
|
42
|
+
export function admissibleFrontier(waiting, active) {
|
|
43
|
+
const chosen = [];
|
|
44
|
+
const reserved = [...active];
|
|
45
|
+
const earlier = [];
|
|
46
|
+
for (const request of waiting) {
|
|
47
|
+
if (!claimSetsConflict(request.claims, reserved) && !claimSetsConflict(request.claims, earlier)) {
|
|
48
|
+
chosen.push(request.requestId);
|
|
49
|
+
reserved.push(...request.claims);
|
|
50
|
+
}
|
|
51
|
+
earlier.push(...request.claims);
|
|
52
|
+
}
|
|
53
|
+
return chosen;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=awareness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"awareness.js","sourceRoot":"","sources":["../../src/coordination/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAQlD;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACzB,YAAyD,EACzD,OAA0B,EAC1B,iBAAiB,GAAsB,EAAE,EAC3B;IACd,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,GAAG,OAAO,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC;IACrE,SAAS,CAAC;QACT,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;YACzD,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YACjC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC3C,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACnB,IAAI,GAAG,IAAI,CAAC;YACb,CAAC;QACF,CAAC;QACD,IAAI,CAAC,IAAI;YAAE,OAAO,QAAQ,CAAC;IAC5B,CAAC;AAAA,CACD;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAkC,EAAE,MAAgC,EAAY;IAClH,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAoB,CAAC,GAAG,MAAM,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;QAC/B,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;YACjG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAC/B,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["/**\n * Change awareness: what a change invalidates, and who may proceed next.\n *\n * Both functions are deliberately conservative. Invalidation treats an\n * unmapped dependent as affected, because \"we have no edge for it\" is not\n * evidence that it is unaffected. Frontier admission lets an unrelated\n * follower pass a blocked leader, but never lets a later request that\n * conflicts with an older waiting one jump ahead of it.\n */\n\nimport { claimSetsConflict } from \"./resource.ts\";\nimport type { ResourceClaim } from \"./types.ts\";\n\nexport interface WaitingRequest {\n\treadonly requestId: string;\n\treadonly claims: readonly ResourceClaim[];\n}\n\n/**\n * Transitive closure over reverse dependencies.\n *\n * `unknownDependents` are nodes whose dependencies could not be resolved; they\n * join the affected set rather than being assumed safe. Reaching a fixpoint\n * terminates on cycles because the set only grows and is bounded by the graph.\n */\nexport function invalidate(\n\tdependencies: Readonly<Record<string, readonly string[]>>,\n\tchanged: readonly string[],\n\tunknownDependents: readonly string[] = [],\n): Set<string> {\n\tconst affected = new Set<string>([...changed, ...unknownDependents]);\n\tfor (;;) {\n\t\tlet grew = false;\n\t\tfor (const [node, deps] of Object.entries(dependencies)) {\n\t\t\tif (affected.has(node)) continue;\n\t\t\tif (deps.some((dep) => affected.has(dep))) {\n\t\t\t\taffected.add(node);\n\t\t\t\tgrew = true;\n\t\t\t}\n\t\t}\n\t\tif (!grew) return affected;\n\t}\n}\n\n/**\n * Requests admissible right now, in queue order.\n *\n * A request is admitted only when it conflicts neither with the active claims\n * nor with any earlier waiting request. Accumulating earlier claims — whether\n * or not they were admitted — is what stops a stream of later readers from\n * starving an older writer. Capacity fairness is not modelled here.\n */\nexport function admissibleFrontier(waiting: readonly WaitingRequest[], active: readonly ResourceClaim[]): string[] {\n\tconst chosen: string[] = [];\n\tconst reserved: ResourceClaim[] = [...active];\n\tconst earlier: ResourceClaim[] = [];\n\tfor (const request of waiting) {\n\t\tif (!claimSetsConflict(request.claims, reserved) && !claimSetsConflict(request.claims, earlier)) {\n\t\t\tchosen.push(request.requestId);\n\t\t\treserved.push(...request.claims);\n\t\t}\n\t\tearlier.push(...request.claims);\n\t}\n\treturn chosen;\n}\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process admission broker for parallel sessions.
|
|
3
|
+
*
|
|
4
|
+
* Safety property: a grant that may still own a live external effect keeps
|
|
5
|
+
* holding its claims. Expiry, cancellation and authority restart are
|
|
6
|
+
* authorization events; only a trusted supervisor reporting termination frees
|
|
7
|
+
* the claims. Treating a lapsed deadline as "the effect stopped" is exactly how
|
|
8
|
+
* two sessions end up writing the same path.
|
|
9
|
+
*
|
|
10
|
+
* Bounded reference semantics: a single trusted state machine, canonical claim
|
|
11
|
+
* keys, and every shared effect crossing this broker. No IPC, no OS fencing,
|
|
12
|
+
* no persistence — those belong to the supervisor this broker reports to.
|
|
13
|
+
*/
|
|
14
|
+
import type { EffectState, GrantToken, ResourceClaim, Sequence } from "./types.ts";
|
|
15
|
+
export interface AdmissionBrokerOptions {
|
|
16
|
+
/** Total admissible weight of concurrently active grants. */
|
|
17
|
+
readonly capacity: number;
|
|
18
|
+
}
|
|
19
|
+
export interface AcquireInput {
|
|
20
|
+
readonly sessionId: string;
|
|
21
|
+
readonly incarnation: Sequence | string;
|
|
22
|
+
readonly claims: readonly ResourceClaim[];
|
|
23
|
+
readonly now: number;
|
|
24
|
+
readonly ttl: number;
|
|
25
|
+
readonly weight?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface StartInput {
|
|
28
|
+
readonly token: GrantToken;
|
|
29
|
+
readonly now: number;
|
|
30
|
+
/** Claims the operation actually resolved to after approval hooks. */
|
|
31
|
+
readonly actualClaims: readonly ResourceClaim[];
|
|
32
|
+
}
|
|
33
|
+
export declare class AdmissionBroker {
|
|
34
|
+
private readonly capacity;
|
|
35
|
+
private authorityEpoch;
|
|
36
|
+
private grantCounter;
|
|
37
|
+
private readonly incarnations;
|
|
38
|
+
private readonly grants;
|
|
39
|
+
constructor(options: AdmissionBrokerOptions);
|
|
40
|
+
/**
|
|
41
|
+
* Announce a session incarnation. Re-registering does not free the previous
|
|
42
|
+
* incarnation's grants: a restarted session cannot assume its predecessor's
|
|
43
|
+
* effects stopped.
|
|
44
|
+
*/
|
|
45
|
+
register(sessionId: string): Sequence;
|
|
46
|
+
/** Grants still holding their claims, including quarantined ones. */
|
|
47
|
+
private activeGrants;
|
|
48
|
+
acquire(input: AcquireInput): GrantToken | null;
|
|
49
|
+
/** Resolve a token that is still authoritative for new action. */
|
|
50
|
+
private authoritative;
|
|
51
|
+
/** Resolve a token for settlement only; accepts superseded epochs. */
|
|
52
|
+
private settleable;
|
|
53
|
+
/**
|
|
54
|
+
* Bind an effect to its reservation. The actual claims must equal the
|
|
55
|
+
* reserved set exactly — an approval hook that widened scope has to be
|
|
56
|
+
* re-admitted, not silently allowed to run under the old grant.
|
|
57
|
+
*/
|
|
58
|
+
start(input: StartInput): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Apply lapsed authorization deadlines. A reservation that never started is
|
|
61
|
+
* released; a running effect is quarantined and keeps its claims.
|
|
62
|
+
*/
|
|
63
|
+
expire(now: number): void;
|
|
64
|
+
/** Withdraw authorization. Requested cancellation is not observed termination. */
|
|
65
|
+
cancel(token: GrantToken): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Trusted supervisor event: this exact effect is gone. Accepted for
|
|
68
|
+
* superseded epochs so a restarted authority can still settle what the
|
|
69
|
+
* previous one started, never to authorize new work.
|
|
70
|
+
*/
|
|
71
|
+
confirmTerminated(token: GrantToken): boolean;
|
|
72
|
+
/** Fence the authority. Live effects stay quarantined until confirmed. */
|
|
73
|
+
restart(): void;
|
|
74
|
+
stateOf(token: GrantToken): EffectState | undefined;
|
|
75
|
+
claimsOf(token: GrantToken): readonly ResourceClaim[];
|
|
76
|
+
/** Invariant: active grants fit capacity and never pairwise conflict. */
|
|
77
|
+
isSafe(): boolean;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=broker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker.d.ts","sourceRoot":"","sources":["../../src/coordination/broker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAYnF,MAAM,WAAW,sBAAsB;IACtC,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,QAAQ,CAAC,YAAY,EAAE,SAAS,aAAa,EAAE,CAAC;CAChD;AAQD,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+B;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IAEnD,YAAY,OAAO,EAAE,sBAAsB,EAG1C;IAED;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAOpC;IAED,qEAAqE;IACrE,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,GAAG,IAAI,CAuC9C;IAED,kEAAkE;IAClE,OAAO,CAAC,aAAa;IASrB,sEAAsE;IACtE,OAAO,CAAC,UAAU;IAMlB;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAShC;IAED;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAOxB;IAED,kFAAkF;IAClF,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAKjC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAM5C;IAED,0EAA0E;IAC1E,OAAO,IAAI,IAAI,CAKd;IAED,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,CAGlD;IAED,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,aAAa,EAAE,CAGpD;IAED,yEAAyE;IACzE,MAAM,IAAI,OAAO,CAShB;CACD","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"]}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process admission broker for parallel sessions.
|
|
3
|
+
*
|
|
4
|
+
* Safety property: a grant that may still own a live external effect keeps
|
|
5
|
+
* holding its claims. Expiry, cancellation and authority restart are
|
|
6
|
+
* authorization events; only a trusted supervisor reporting termination frees
|
|
7
|
+
* the claims. Treating a lapsed deadline as "the effect stopped" is exactly how
|
|
8
|
+
* two sessions end up writing the same path.
|
|
9
|
+
*
|
|
10
|
+
* Bounded reference semantics: a single trusted state machine, canonical claim
|
|
11
|
+
* keys, and every shared effect crossing this broker. No IPC, no OS fencing,
|
|
12
|
+
* no persistence — those belong to the supervisor this broker reports to.
|
|
13
|
+
*/
|
|
14
|
+
import { claimSetsConflict, sameClaimSet } from "./resource.js";
|
|
15
|
+
import { nextSequence, SETTLED_EFFECT_STATES, sequence } from "./types.js";
|
|
16
|
+
function assertNonNegativeInteger(value, label) {
|
|
17
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
18
|
+
throw new TypeError(`${label} must be a nonnegative safe integer`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class AdmissionBroker {
|
|
22
|
+
capacity;
|
|
23
|
+
authorityEpoch = sequence("1");
|
|
24
|
+
grantCounter = sequence("0");
|
|
25
|
+
incarnations = new Map();
|
|
26
|
+
grants = new Map();
|
|
27
|
+
constructor(options) {
|
|
28
|
+
assertNonNegativeInteger(options.capacity, "capacity");
|
|
29
|
+
this.capacity = options.capacity;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Announce a session incarnation. Re-registering does not free the previous
|
|
33
|
+
* incarnation's grants: a restarted session cannot assume its predecessor's
|
|
34
|
+
* effects stopped.
|
|
35
|
+
*/
|
|
36
|
+
register(sessionId) {
|
|
37
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
38
|
+
throw new TypeError("sessionId must be a non-empty string");
|
|
39
|
+
}
|
|
40
|
+
const next = nextSequence(this.incarnations.get(sessionId) ?? sequence("0"));
|
|
41
|
+
this.incarnations.set(sessionId, next);
|
|
42
|
+
return next;
|
|
43
|
+
}
|
|
44
|
+
/** Grants still holding their claims, including quarantined ones. */
|
|
45
|
+
activeGrants() {
|
|
46
|
+
return [...this.grants.values()].filter((g) => !SETTLED_EFFECT_STATES.has(g.state));
|
|
47
|
+
}
|
|
48
|
+
acquire(input) {
|
|
49
|
+
assertNonNegativeInteger(input.now, "now");
|
|
50
|
+
const weight = input.weight ?? 1;
|
|
51
|
+
assertNonNegativeInteger(weight, "weight");
|
|
52
|
+
if (!Number.isSafeInteger(input.ttl) || input.ttl <= 0) {
|
|
53
|
+
throw new TypeError("ttl must be a positive safe integer");
|
|
54
|
+
}
|
|
55
|
+
const incarnation = sequence(input.incarnation);
|
|
56
|
+
if (this.incarnations.get(input.sessionId) !== incarnation) {
|
|
57
|
+
throw new TypeError("stale session incarnation");
|
|
58
|
+
}
|
|
59
|
+
if (input.claims.length === 0) {
|
|
60
|
+
throw new TypeError("an unknown scope must not be admitted as an empty scope");
|
|
61
|
+
}
|
|
62
|
+
// Settle lapsed authorizations first; this never evicts a live effect.
|
|
63
|
+
this.expire(input.now);
|
|
64
|
+
const active = this.activeGrants();
|
|
65
|
+
const used = active.reduce((sum, g) => sum + g.weight, 0);
|
|
66
|
+
if (weight + used > this.capacity)
|
|
67
|
+
return null;
|
|
68
|
+
const claims = [...input.claims];
|
|
69
|
+
if (active.some((g) => claimSetsConflict(claims, g.claims)))
|
|
70
|
+
return null;
|
|
71
|
+
this.grantCounter = nextSequence(this.grantCounter);
|
|
72
|
+
const token = Object.freeze({
|
|
73
|
+
authorityEpoch: this.authorityEpoch,
|
|
74
|
+
grantSequence: this.grantCounter,
|
|
75
|
+
sessionId: input.sessionId,
|
|
76
|
+
sessionIncarnation: incarnation,
|
|
77
|
+
authorizationDeadline: input.now + input.ttl,
|
|
78
|
+
});
|
|
79
|
+
this.grants.set(token.grantSequence, {
|
|
80
|
+
token,
|
|
81
|
+
claims: Object.freeze(claims),
|
|
82
|
+
weight,
|
|
83
|
+
state: "reserved",
|
|
84
|
+
effectLive: false,
|
|
85
|
+
});
|
|
86
|
+
return token;
|
|
87
|
+
}
|
|
88
|
+
/** Resolve a token that is still authoritative for new action. */
|
|
89
|
+
authoritative(token) {
|
|
90
|
+
const grant = this.grants.get(token.grantSequence);
|
|
91
|
+
if (grant === undefined || !sameToken(grant.token, token))
|
|
92
|
+
return undefined;
|
|
93
|
+
if (token.authorityEpoch !== this.authorityEpoch)
|
|
94
|
+
return undefined;
|
|
95
|
+
if (this.incarnations.get(token.sessionId) !== token.sessionIncarnation)
|
|
96
|
+
return undefined;
|
|
97
|
+
if (SETTLED_EFFECT_STATES.has(grant.state))
|
|
98
|
+
return undefined;
|
|
99
|
+
return grant;
|
|
100
|
+
}
|
|
101
|
+
/** Resolve a token for settlement only; accepts superseded epochs. */
|
|
102
|
+
settleable(token) {
|
|
103
|
+
const grant = this.grants.get(token.grantSequence);
|
|
104
|
+
if (grant === undefined || !sameToken(grant.token, token))
|
|
105
|
+
return undefined;
|
|
106
|
+
return SETTLED_EFFECT_STATES.has(grant.state) ? undefined : grant;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Bind an effect to its reservation. The actual claims must equal the
|
|
110
|
+
* reserved set exactly — an approval hook that widened scope has to be
|
|
111
|
+
* re-admitted, not silently allowed to run under the old grant.
|
|
112
|
+
*/
|
|
113
|
+
start(input) {
|
|
114
|
+
assertNonNegativeInteger(input.now, "now");
|
|
115
|
+
const grant = this.authoritative(input.token);
|
|
116
|
+
if (grant === undefined || grant.state !== "reserved")
|
|
117
|
+
return false;
|
|
118
|
+
if (input.now >= grant.token.authorizationDeadline)
|
|
119
|
+
return false;
|
|
120
|
+
if (!sameClaimSet(input.actualClaims, grant.claims))
|
|
121
|
+
return false;
|
|
122
|
+
grant.state = "running";
|
|
123
|
+
grant.effectLive = true;
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Apply lapsed authorization deadlines. A reservation that never started is
|
|
128
|
+
* released; a running effect is quarantined and keeps its claims.
|
|
129
|
+
*/
|
|
130
|
+
expire(now) {
|
|
131
|
+
assertNonNegativeInteger(now, "now");
|
|
132
|
+
for (const grant of this.activeGrants()) {
|
|
133
|
+
if (now >= grant.token.authorizationDeadline) {
|
|
134
|
+
grant.state = grant.effectLive ? "quarantined" : "cancelled";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Withdraw authorization. Requested cancellation is not observed termination. */
|
|
139
|
+
cancel(token) {
|
|
140
|
+
const grant = this.settleable(token);
|
|
141
|
+
if (grant === undefined)
|
|
142
|
+
return false;
|
|
143
|
+
grant.state = grant.effectLive ? "quarantined" : "cancelled";
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Trusted supervisor event: this exact effect is gone. Accepted for
|
|
148
|
+
* superseded epochs so a restarted authority can still settle what the
|
|
149
|
+
* previous one started, never to authorize new work.
|
|
150
|
+
*/
|
|
151
|
+
confirmTerminated(token) {
|
|
152
|
+
const grant = this.settleable(token);
|
|
153
|
+
if (grant === undefined)
|
|
154
|
+
return false;
|
|
155
|
+
grant.effectLive = false;
|
|
156
|
+
grant.state = "terminated";
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
/** Fence the authority. Live effects stay quarantined until confirmed. */
|
|
160
|
+
restart() {
|
|
161
|
+
this.authorityEpoch = nextSequence(this.authorityEpoch);
|
|
162
|
+
for (const grant of this.activeGrants()) {
|
|
163
|
+
grant.state = grant.effectLive ? "quarantined" : "cancelled";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
stateOf(token) {
|
|
167
|
+
const grant = this.grants.get(token.grantSequence);
|
|
168
|
+
return grant !== undefined && sameToken(grant.token, token) ? grant.state : undefined;
|
|
169
|
+
}
|
|
170
|
+
claimsOf(token) {
|
|
171
|
+
const grant = this.grants.get(token.grantSequence);
|
|
172
|
+
return grant !== undefined && sameToken(grant.token, token) ? grant.claims : [];
|
|
173
|
+
}
|
|
174
|
+
/** Invariant: active grants fit capacity and never pairwise conflict. */
|
|
175
|
+
isSafe() {
|
|
176
|
+
const active = this.activeGrants();
|
|
177
|
+
if (active.reduce((sum, g) => sum + g.weight, 0) > this.capacity)
|
|
178
|
+
return false;
|
|
179
|
+
for (const [index, grant] of active.entries()) {
|
|
180
|
+
for (const other of active.slice(index + 1)) {
|
|
181
|
+
if (claimSetsConflict(grant.claims, other.claims))
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function sameToken(a, b) {
|
|
189
|
+
return (a.authorityEpoch === b.authorityEpoch &&
|
|
190
|
+
a.grantSequence === b.grantSequence &&
|
|
191
|
+
a.sessionId === b.sessionId &&
|
|
192
|
+
a.sessionIncarnation === b.sessionIncarnation &&
|
|
193
|
+
a.authorizationDeadline === b.authorizationDeadline);
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=broker.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel-session coordination kernel.
|
|
3
|
+
*
|
|
4
|
+
* Pure, deterministic, bounded. Implements the P0 shared by the 2026-09-20
|
|
5
|
+
* coordination design and the Jev audit: bind admission to actual claims, and
|
|
6
|
+
* keep a possibly-live effect's claims held until termination is observed.
|
|
7
|
+
* No IPC, no process control, no persistence.
|
|
8
|
+
*/
|
|
9
|
+
export * from "./awareness.ts";
|
|
10
|
+
export * from "./broker.ts";
|
|
11
|
+
export * from "./integration.ts";
|
|
12
|
+
export * from "./operation.ts";
|
|
13
|
+
export * from "./resource.ts";
|
|
14
|
+
export * from "./session.ts";
|
|
15
|
+
export * from "./types.ts";
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/coordination/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC","sourcesContent":["/**\n * Parallel-session coordination kernel.\n *\n * Pure, deterministic, bounded. Implements the P0 shared by the 2026-09-20\n * coordination design and the Jev audit: bind admission to actual claims, and\n * keep a possibly-live effect's claims held until termination is observed.\n * No IPC, no process control, no persistence.\n */\nexport * from \"./awareness.ts\";\nexport * from \"./broker.ts\";\nexport * from \"./integration.ts\";\nexport * from \"./operation.ts\";\nexport * from \"./resource.ts\";\nexport * from \"./session.ts\";\nexport * from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel-session coordination kernel.
|
|
3
|
+
*
|
|
4
|
+
* Pure, deterministic, bounded. Implements the P0 shared by the 2026-09-20
|
|
5
|
+
* coordination design and the Jev audit: bind admission to actual claims, and
|
|
6
|
+
* keep a possibly-live effect's claims held until termination is observed.
|
|
7
|
+
* No IPC, no process control, no persistence.
|
|
8
|
+
*/
|
|
9
|
+
export * from "./awareness.js";
|
|
10
|
+
export * from "./broker.js";
|
|
11
|
+
export * from "./integration.js";
|
|
12
|
+
export * from "./operation.js";
|
|
13
|
+
export * from "./resource.js";
|
|
14
|
+
export * from "./session.js";
|
|
15
|
+
export * from "./types.js";
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/coordination/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC","sourcesContent":["/**\n * Parallel-session coordination kernel.\n *\n * Pure, deterministic, bounded. Implements the P0 shared by the 2026-09-20\n * coordination design and the Jev audit: bind admission to actual claims, and\n * keep a possibly-live effect's claims held until termination is observed.\n * No IPC, no process control, no persistence.\n */\nexport * from \"./awareness.ts\";\nexport * from \"./broker.ts\";\nexport * from \"./integration.ts\";\nexport * from \"./operation.ts\";\nexport * from \"./resource.ts\";\nexport * from \"./session.ts\";\nexport * from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Candidate publication with read-version preconditions and bound receipts.
|
|
3
|
+
*
|
|
4
|
+
* A publication is admitted only when the snapshot that was verified is still
|
|
5
|
+
* the snapshot being published. Three separate facts are checked, because any
|
|
6
|
+
* one of them alone lets a stale candidate through:
|
|
7
|
+
* - every key the proposal read is still at the version it read,
|
|
8
|
+
* - the parent revision has not advanced,
|
|
9
|
+
* - the receipt is bound to this candidate, contract, check set and
|
|
10
|
+
* environment rather than to an earlier composition of them.
|
|
11
|
+
*
|
|
12
|
+
* Absence is a dependency: reading a key that did not exist records version 0,
|
|
13
|
+
* so a later creation invalidates the proposal instead of silently racing it.
|
|
14
|
+
*
|
|
15
|
+
* Bounded model: one accepted snapshot pointer, file effects abstracted as a
|
|
16
|
+
* single update. Real multi-file atomicity needs immutable candidates plus a
|
|
17
|
+
* Git CAS/outbox protocol; this class does not provide that.
|
|
18
|
+
*/
|
|
19
|
+
export type PublicationOutcome = "accepted" | "already-accepted" | "id-collision" | "stale" | "invalid-binding" | "unverified";
|
|
20
|
+
export type WriteValue = string | null;
|
|
21
|
+
export interface ChangeProposal {
|
|
22
|
+
readonly proposalId: string;
|
|
23
|
+
/** Every key touched, with the version observed at propose time. */
|
|
24
|
+
readonly readVersions: readonly (readonly [string, number])[];
|
|
25
|
+
/** `null` deletes the key. */
|
|
26
|
+
readonly writes: readonly (readonly [string, WriteValue])[];
|
|
27
|
+
}
|
|
28
|
+
export interface StagedChange {
|
|
29
|
+
readonly proposal: ChangeProposal;
|
|
30
|
+
readonly parentRevision: number;
|
|
31
|
+
readonly candidateDigest: string;
|
|
32
|
+
readonly contractDigest: string;
|
|
33
|
+
readonly checkDigest: string;
|
|
34
|
+
readonly environmentDigest: string;
|
|
35
|
+
}
|
|
36
|
+
export interface VerificationReceipt {
|
|
37
|
+
readonly candidateDigest: string;
|
|
38
|
+
readonly contractDigest: string;
|
|
39
|
+
readonly checkDigest: string;
|
|
40
|
+
readonly environmentDigest: string;
|
|
41
|
+
readonly verdict: "pass" | "fail" | "inconclusive";
|
|
42
|
+
readonly pendingEffects: number;
|
|
43
|
+
}
|
|
44
|
+
export declare class IntegrationPublisher {
|
|
45
|
+
private readonly values;
|
|
46
|
+
private readonly versions;
|
|
47
|
+
private readonly acceptedFingerprints;
|
|
48
|
+
private currentRevision;
|
|
49
|
+
constructor(initial: Readonly<Record<string, string>>);
|
|
50
|
+
get revision(): number;
|
|
51
|
+
snapshot(): Record<string, string>;
|
|
52
|
+
/** Record the versions of every key this change reads or writes. */
|
|
53
|
+
propose(proposalId: string, reads: readonly string[], writes: Readonly<Record<string, WriteValue>>): ChangeProposal;
|
|
54
|
+
private isCurrent;
|
|
55
|
+
private applied;
|
|
56
|
+
stage(proposal: ChangeProposal, contractDigest?: string, checkDigest?: string, environmentDigest?: string): StagedChange | null;
|
|
57
|
+
publish(staged: StagedChange, receipt: VerificationReceipt): PublicationOutcome;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=integration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../src/coordination/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,MAAM,MAAM,kBAAkB,GAC3B,UAAU,GACV,kBAAkB,GAClB,cAAc,GACd,OAAO,GACP,iBAAiB,GACjB,YAAY,CAAC;AAEhB,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,oEAAoE;IACpE,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;IAC9D,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;CAC5D;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,cAAc,CAAC;IACnD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CAChC;AAWD,qBAAa,oBAAoB;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA6B;IAClE,OAAO,CAAC,eAAe,CAAK;IAE5B,YAAY,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAKpD;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEjC;IAED,oEAAoE;IACpE,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,GAAG,cAAc,CAgBlH;IAED,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,OAAO;IASf,KAAK,CACJ,QAAQ,EAAE,cAAc,EACxB,cAAc,SAAgB,EAC9B,WAAW,SAAc,EACzB,iBAAiB,SAAW,GAC1B,YAAY,GAAG,IAAI,CAUrB;IAED,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAyB9E;CACD","sourcesContent":["/**\n * Candidate publication with read-version preconditions and bound receipts.\n *\n * A publication is admitted only when the snapshot that was verified is still\n * the snapshot being published. Three separate facts are checked, because any\n * one of them alone lets a stale candidate through:\n * - every key the proposal read is still at the version it read,\n * - the parent revision has not advanced,\n * - the receipt is bound to this candidate, contract, check set and\n * environment rather than to an earlier composition of them.\n *\n * Absence is a dependency: reading a key that did not exist records version 0,\n * so a later creation invalidates the proposal instead of silently racing it.\n *\n * Bounded model: one accepted snapshot pointer, file effects abstracted as a\n * single update. Real multi-file atomicity needs immutable candidates plus a\n * Git CAS/outbox protocol; this class does not provide that.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { canonical } from \"../metacognition/validation.ts\";\n\nexport type PublicationOutcome =\n\t| \"accepted\"\n\t| \"already-accepted\"\n\t| \"id-collision\"\n\t| \"stale\"\n\t| \"invalid-binding\"\n\t| \"unverified\";\n\nexport type WriteValue = string | null;\n\nexport interface ChangeProposal {\n\treadonly proposalId: string;\n\t/** Every key touched, with the version observed at propose time. */\n\treadonly readVersions: readonly (readonly [string, number])[];\n\t/** `null` deletes the key. */\n\treadonly writes: readonly (readonly [string, WriteValue])[];\n}\n\nexport interface StagedChange {\n\treadonly proposal: ChangeProposal;\n\treadonly parentRevision: number;\n\treadonly candidateDigest: string;\n\treadonly contractDigest: string;\n\treadonly checkDigest: string;\n\treadonly environmentDigest: string;\n}\n\nexport interface VerificationReceipt {\n\treadonly candidateDigest: string;\n\treadonly contractDigest: string;\n\treadonly checkDigest: string;\n\treadonly environmentDigest: string;\n\treadonly verdict: \"pass\" | \"fail\" | \"inconclusive\";\n\treadonly pendingEffects: number;\n}\n\nfunction digest(value: unknown): string {\n\treturn createHash(\"sha256\").update(canonical(value), \"utf8\").digest(\"hex\");\n}\n\nfunction byKey(a: readonly [string, unknown], b: readonly [string, unknown]): number {\n\tif (a[0] < b[0]) return -1;\n\treturn a[0] > b[0] ? 1 : 0;\n}\n\nexport class IntegrationPublisher {\n\tprivate readonly values = new Map<string, string>();\n\tprivate readonly versions = new Map<string, number>();\n\tprivate readonly acceptedFingerprints = new Map<string, string>();\n\tprivate currentRevision = 0;\n\n\tconstructor(initial: Readonly<Record<string, string>>) {\n\t\tfor (const [key, value] of Object.entries(initial)) {\n\t\t\tthis.values.set(key, value);\n\t\t\tthis.versions.set(key, 1);\n\t\t}\n\t}\n\n\tget revision(): number {\n\t\treturn this.currentRevision;\n\t}\n\n\tsnapshot(): Record<string, string> {\n\t\treturn Object.fromEntries(this.values);\n\t}\n\n\t/** Record the versions of every key this change reads or writes. */\n\tpropose(proposalId: string, reads: readonly string[], writes: Readonly<Record<string, WriteValue>>): ChangeProposal {\n\t\tif (typeof proposalId !== \"string\" || proposalId.length === 0) {\n\t\t\tthrow new TypeError(\"proposalId must be a non-empty string\");\n\t\t}\n\t\tconst touched = new Set<string>([...reads, ...Object.keys(writes)]);\n\t\treturn Object.freeze({\n\t\t\tproposalId,\n\t\t\treadVersions: Object.freeze(\n\t\t\t\t[...touched].map((key) => Object.freeze([key, this.versions.get(key) ?? 0] as const)).sort(byKey),\n\t\t\t),\n\t\t\twrites: Object.freeze(\n\t\t\t\tObject.entries(writes)\n\t\t\t\t\t.map(([key, value]) => Object.freeze([key, value] as const))\n\t\t\t\t\t.sort(byKey),\n\t\t\t),\n\t\t});\n\t}\n\n\tprivate isCurrent(proposal: ChangeProposal): boolean {\n\t\tconst declared = new Map(proposal.readVersions);\n\t\tif (proposal.writes.some(([key]) => !declared.has(key))) return false;\n\t\treturn proposal.readVersions.every(([key, version]) => (this.versions.get(key) ?? 0) === version);\n\t}\n\n\tprivate applied(proposal: ChangeProposal): Map<string, string> {\n\t\tconst next = new Map(this.values);\n\t\tfor (const [key, value] of proposal.writes) {\n\t\t\tif (value === null) next.delete(key);\n\t\t\telse next.set(key, value);\n\t\t}\n\t\treturn next;\n\t}\n\n\tstage(\n\t\tproposal: ChangeProposal,\n\t\tcontractDigest = \"contract-v1\",\n\t\tcheckDigest = \"checks-v1\",\n\t\tenvironmentDigest = \"env-v1\",\n\t): StagedChange | null {\n\t\tif (!this.isCurrent(proposal)) return null;\n\t\treturn Object.freeze({\n\t\t\tproposal,\n\t\t\tparentRevision: this.currentRevision,\n\t\t\tcandidateDigest: digest(Object.fromEntries(this.applied(proposal))),\n\t\t\tcontractDigest,\n\t\t\tcheckDigest,\n\t\t\tenvironmentDigest,\n\t\t});\n\t}\n\n\tpublish(staged: StagedChange, receipt: VerificationReceipt): PublicationOutcome {\n\t\tconst proposal = staged.proposal;\n\t\tconst fingerprint = digest({\n\t\t\tproposalId: proposal.proposalId,\n\t\t\treadVersions: proposal.readVersions,\n\t\t\twrites: proposal.writes,\n\t\t});\n\t\tconst seen = this.acceptedFingerprints.get(proposal.proposalId);\n\t\tif (seen !== undefined) return seen === fingerprint ? \"already-accepted\" : \"id-collision\";\n\t\tif (staged.parentRevision !== this.currentRevision || !this.isCurrent(proposal)) return \"stale\";\n\t\tif (!sameBinding(staged, receipt)) return \"invalid-binding\";\n\t\tif (receipt.verdict !== \"pass\" || receipt.pendingEffects !== 0) return \"unverified\";\n\n\t\t// Recompute the candidate: guards a staged payload swapped after verification.\n\t\tconst rebuilt = this.stage(proposal, staged.contractDigest, staged.checkDigest, staged.environmentDigest);\n\t\tif (rebuilt === null || rebuilt.candidateDigest !== staged.candidateDigest) return \"invalid-binding\";\n\n\t\tfor (const [key, value] of proposal.writes) {\n\t\t\tif (value === null) this.values.delete(key);\n\t\t\telse this.values.set(key, value);\n\t\t\tthis.versions.set(key, (this.versions.get(key) ?? 0) + 1);\n\t\t}\n\t\tthis.currentRevision += 1;\n\t\tthis.acceptedFingerprints.set(proposal.proposalId, fingerprint);\n\t\treturn \"accepted\";\n\t}\n}\n\nfunction sameBinding(staged: StagedChange, receipt: VerificationReceipt): boolean {\n\treturn (\n\t\tstaged.candidateDigest === receipt.candidateDigest &&\n\t\tstaged.contractDigest === receipt.contractDigest &&\n\t\tstaged.checkDigest === receipt.checkDigest &&\n\t\tstaged.environmentDigest === receipt.environmentDigest\n\t);\n}\n"]}
|