looply-cli 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +137 -3
- package/dist/.src-hash +1 -0
- package/dist/acceptance-observe.d.ts +108 -0
- package/dist/acceptance-observe.d.ts.map +1 -0
- package/dist/acceptance-observe.js +152 -0
- package/dist/acceptance-observe.js.map +1 -0
- package/dist/agent-cmd.d.ts +131 -0
- package/dist/agent-cmd.d.ts.map +1 -0
- package/dist/agent-cmd.js +154 -0
- package/dist/agent-cmd.js.map +1 -0
- package/dist/agent-contained.d.ts +114 -0
- package/dist/agent-contained.d.ts.map +1 -0
- package/dist/agent-contained.js +292 -0
- package/dist/agent-contained.js.map +1 -0
- package/dist/agent-driver.d.ts +130 -0
- package/dist/agent-driver.d.ts.map +1 -0
- package/dist/agent-driver.js +170 -0
- package/dist/agent-driver.js.map +1 -0
- package/dist/agent-envelope.d.ts +94 -0
- package/dist/agent-envelope.d.ts.map +1 -0
- package/dist/agent-envelope.js +214 -0
- package/dist/agent-envelope.js.map +1 -0
- package/dist/agent-exec.d.ts +182 -0
- package/dist/agent-exec.d.ts.map +1 -0
- package/dist/agent-exec.js +125 -0
- package/dist/agent-exec.js.map +1 -0
- package/dist/agent-runtime.d.ts +23 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/agent-runtime.js +143 -0
- package/dist/agent-runtime.js.map +1 -0
- package/dist/approval-gate-read.d.ts +66 -0
- package/dist/approval-gate-read.d.ts.map +1 -0
- package/dist/approval-gate-read.js +138 -0
- package/dist/approval-gate-read.js.map +1 -0
- package/dist/approval-resolution.d.ts +87 -0
- package/dist/approval-resolution.d.ts.map +1 -0
- package/dist/approval-resolution.js +100 -0
- package/dist/approval-resolution.js.map +1 -0
- package/dist/approval-resumes.d.ts +102 -0
- package/dist/approval-resumes.d.ts.map +1 -0
- package/dist/approval-resumes.js +167 -0
- package/dist/approval-resumes.js.map +1 -0
- package/dist/ask-brain.d.ts +167 -0
- package/dist/ask-brain.d.ts.map +1 -0
- package/dist/ask-brain.js +355 -0
- package/dist/ask-brain.js.map +1 -0
- package/dist/autonomy-authority.d.ts +80 -0
- package/dist/autonomy-authority.d.ts.map +1 -0
- package/dist/autonomy-authority.js +82 -0
- package/dist/autonomy-authority.js.map +1 -0
- package/dist/autonomy-controller.d.ts +321 -0
- package/dist/autonomy-controller.d.ts.map +1 -0
- package/dist/autonomy-controller.js +517 -0
- package/dist/autonomy-controller.js.map +1 -0
- package/dist/autonomy-store.d.ts +327 -0
- package/dist/autonomy-store.d.ts.map +1 -0
- package/dist/autonomy-store.js +581 -0
- package/dist/autonomy-store.js.map +1 -0
- package/dist/autonomy-tick.d.ts +257 -0
- package/dist/autonomy-tick.d.ts.map +1 -0
- package/dist/autonomy-tick.js +616 -0
- package/dist/autonomy-tick.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +2727 -0
- package/dist/bin.js.map +1 -0
- package/dist/brain-advice.d.ts +21 -0
- package/dist/brain-advice.d.ts.map +1 -0
- package/dist/brain-advice.js +51 -0
- package/dist/brain-advice.js.map +1 -0
- package/dist/candidate-replay-runtime.d.ts +125 -0
- package/dist/candidate-replay-runtime.d.ts.map +1 -0
- package/dist/candidate-replay-runtime.js +188 -0
- package/dist/candidate-replay-runtime.js.map +1 -0
- package/dist/checkout-carry.d.ts +76 -0
- package/dist/checkout-carry.d.ts.map +1 -0
- package/dist/checkout-carry.js +125 -0
- package/dist/checkout-carry.js.map +1 -0
- package/dist/citation-readers.d.ts +26 -0
- package/dist/citation-readers.d.ts.map +1 -0
- package/dist/citation-readers.js +102 -0
- package/dist/citation-readers.js.map +1 -0
- package/dist/command-receipts.d.ts +79 -0
- package/dist/command-receipts.d.ts.map +1 -0
- package/dist/command-receipts.js +143 -0
- package/dist/command-receipts.js.map +1 -0
- package/dist/command-runner.d.ts +32 -0
- package/dist/command-runner.d.ts.map +1 -0
- package/dist/command-runner.js +108 -0
- package/dist/command-runner.js.map +1 -0
- package/dist/compatible-sets.d.ts +67 -0
- package/dist/compatible-sets.d.ts.map +1 -0
- package/dist/compatible-sets.js +81 -0
- package/dist/compatible-sets.js.map +1 -0
- package/dist/compile-check.d.ts +31 -0
- package/dist/compile-check.d.ts.map +1 -0
- package/dist/compile-check.js +34 -0
- package/dist/compile-check.js.map +1 -0
- package/dist/compile-store.d.ts +16 -0
- package/dist/compile-store.d.ts.map +1 -0
- package/dist/compile-store.js +21 -0
- package/dist/compile-store.js.map +1 -0
- package/dist/completion.d.ts +106 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +131 -0
- package/dist/completion.js.map +1 -0
- package/dist/config.d.ts +549 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +763 -0
- package/dist/config.js.map +1 -0
- package/dist/control-file.d.ts +142 -0
- package/dist/control-file.d.ts.map +1 -0
- package/dist/control-file.js +144 -0
- package/dist/control-file.js.map +1 -0
- package/dist/coordinator-state.d.ts +131 -0
- package/dist/coordinator-state.d.ts.map +1 -0
- package/dist/coordinator-state.js +380 -0
- package/dist/coordinator-state.js.map +1 -0
- package/dist/coordinator.d.ts +95 -0
- package/dist/coordinator.d.ts.map +1 -0
- package/dist/coordinator.js +97 -0
- package/dist/coordinator.js.map +1 -0
- package/dist/cursor-store.d.ts +53 -0
- package/dist/cursor-store.d.ts.map +1 -0
- package/dist/cursor-store.js +98 -0
- package/dist/cursor-store.js.map +1 -0
- package/dist/daemon-cmd.d.ts +239 -0
- package/dist/daemon-cmd.d.ts.map +1 -0
- package/dist/daemon-cmd.js +1751 -0
- package/dist/daemon-cmd.js.map +1 -0
- package/dist/daemon-lock.d.ts +140 -0
- package/dist/daemon-lock.d.ts.map +1 -0
- package/dist/daemon-lock.js +352 -0
- package/dist/daemon-lock.js.map +1 -0
- package/dist/daemon-recovery.d.ts +78 -0
- package/dist/daemon-recovery.d.ts.map +1 -0
- package/dist/daemon-recovery.js +161 -0
- package/dist/daemon-recovery.js.map +1 -0
- package/dist/daemon.d.ts +368 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +601 -0
- package/dist/daemon.js.map +1 -0
- package/dist/declared-response-shape.d.ts +93 -0
- package/dist/declared-response-shape.d.ts.map +1 -0
- package/dist/declared-response-shape.js +200 -0
- package/dist/declared-response-shape.js.map +1 -0
- package/dist/declared-surfaces.d.ts +16 -0
- package/dist/declared-surfaces.d.ts.map +1 -0
- package/dist/declared-surfaces.js +19 -0
- package/dist/declared-surfaces.js.map +1 -0
- package/dist/depends-review.d.ts +113 -0
- package/dist/depends-review.d.ts.map +1 -0
- package/dist/depends-review.js +110 -0
- package/dist/depends-review.js.map +1 -0
- package/dist/doctor.d.ts +91 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +71 -0
- package/dist/doctor.js.map +1 -0
- package/dist/edge-approval.d.ts +105 -0
- package/dist/edge-approval.d.ts.map +1 -0
- package/dist/edge-approval.js +100 -0
- package/dist/edge-approval.js.map +1 -0
- package/dist/evidence-cache.d.ts +45 -0
- package/dist/evidence-cache.d.ts.map +1 -0
- package/dist/evidence-cache.js +90 -0
- package/dist/evidence-cache.js.map +1 -0
- package/dist/evidence-cmd.d.ts +133 -0
- package/dist/evidence-cmd.d.ts.map +1 -0
- package/dist/evidence-cmd.js +347 -0
- package/dist/evidence-cmd.js.map +1 -0
- package/dist/evidence-facts.d.ts +40 -0
- package/dist/evidence-facts.d.ts.map +1 -0
- package/dist/evidence-facts.js +114 -0
- package/dist/evidence-facts.js.map +1 -0
- package/dist/exclusive-lock.d.ts +52 -0
- package/dist/exclusive-lock.d.ts.map +1 -0
- package/dist/exclusive-lock.js +150 -0
- package/dist/exclusive-lock.js.map +1 -0
- package/dist/exec-domains.d.ts +140 -0
- package/dist/exec-domains.d.ts.map +1 -0
- package/dist/exec-domains.js +287 -0
- package/dist/exec-domains.js.map +1 -0
- package/dist/exemption-store.d.ts +197 -0
- package/dist/exemption-store.d.ts.map +1 -0
- package/dist/exemption-store.js +266 -0
- package/dist/exemption-store.js.map +1 -0
- package/dist/explicit-start-gate.d.ts +96 -0
- package/dist/explicit-start-gate.d.ts.map +1 -0
- package/dist/explicit-start-gate.js +103 -0
- package/dist/explicit-start-gate.js.map +1 -0
- package/dist/fs-trouble.d.ts +70 -0
- package/dist/fs-trouble.d.ts.map +1 -0
- package/dist/fs-trouble.js +98 -0
- package/dist/fs-trouble.js.map +1 -0
- package/dist/gate.d.ts +278 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +278 -0
- package/dist/gate.js.map +1 -0
- package/dist/git-driver.d.ts +519 -0
- package/dist/git-driver.d.ts.map +1 -0
- package/dist/git-driver.js +900 -0
- package/dist/git-driver.js.map +1 -0
- package/dist/git-push-executor.d.ts +41 -0
- package/dist/git-push-executor.d.ts.map +1 -0
- package/dist/git-push-executor.js +142 -0
- package/dist/git-push-executor.js.map +1 -0
- package/dist/glob.d.ts +26 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +93 -0
- package/dist/glob.js.map +1 -0
- package/dist/goal-adaptive.d.ts +295 -0
- package/dist/goal-adaptive.d.ts.map +1 -0
- package/dist/goal-adaptive.js +393 -0
- package/dist/goal-adaptive.js.map +1 -0
- package/dist/goal-compile.d.ts +342 -0
- package/dist/goal-compile.d.ts.map +1 -0
- package/dist/goal-compile.js +444 -0
- package/dist/goal-compile.js.map +1 -0
- package/dist/goal-depends-executor.d.ts +55 -0
- package/dist/goal-depends-executor.d.ts.map +1 -0
- package/dist/goal-depends-executor.js +202 -0
- package/dist/goal-depends-executor.js.map +1 -0
- package/dist/goal-execution-evidence.d.ts +135 -0
- package/dist/goal-execution-evidence.d.ts.map +1 -0
- package/dist/goal-execution-evidence.js +159 -0
- package/dist/goal-execution-evidence.js.map +1 -0
- package/dist/goal-gap.d.ts +140 -0
- package/dist/goal-gap.d.ts.map +1 -0
- package/dist/goal-gap.js +197 -0
- package/dist/goal-gap.js.map +1 -0
- package/dist/goal-met-target.d.ts +98 -0
- package/dist/goal-met-target.d.ts.map +1 -0
- package/dist/goal-met-target.js +92 -0
- package/dist/goal-met-target.js.map +1 -0
- package/dist/goal-ownership.d.ts +416 -0
- package/dist/goal-ownership.d.ts.map +1 -0
- package/dist/goal-ownership.js +576 -0
- package/dist/goal-ownership.js.map +1 -0
- package/dist/goal-runs.d.ts +82 -0
- package/dist/goal-runs.d.ts.map +1 -0
- package/dist/goal-runs.js +120 -0
- package/dist/goal-runs.js.map +1 -0
- package/dist/goal-store.d.ts +146 -0
- package/dist/goal-store.d.ts.map +1 -0
- package/dist/goal-store.js +294 -0
- package/dist/goal-store.js.map +1 -0
- package/dist/goal-view.d.ts +156 -0
- package/dist/goal-view.d.ts.map +1 -0
- package/dist/goal-view.js +499 -0
- package/dist/goal-view.js.map +1 -0
- package/dist/governed-approvals.d.ts +152 -0
- package/dist/governed-approvals.d.ts.map +1 -0
- package/dist/governed-approvals.js +246 -0
- package/dist/governed-approvals.js.map +1 -0
- package/dist/governed-broker.d.ts +169 -0
- package/dist/governed-broker.d.ts.map +1 -0
- package/dist/governed-broker.js +196 -0
- package/dist/governed-broker.js.map +1 -0
- package/dist/governed-run.d.ts +220 -0
- package/dist/governed-run.d.ts.map +1 -0
- package/dist/governed-run.js +406 -0
- package/dist/governed-run.js.map +1 -0
- package/dist/grant-reconcile.d.ts +63 -0
- package/dist/grant-reconcile.d.ts.map +1 -0
- package/dist/grant-reconcile.js +121 -0
- package/dist/grant-reconcile.js.map +1 -0
- package/dist/index-for-head.d.ts +56 -0
- package/dist/index-for-head.d.ts.map +1 -0
- package/dist/index-for-head.js +103 -0
- package/dist/index-for-head.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/init-detect.d.ts +39 -0
- package/dist/init-detect.d.ts.map +1 -0
- package/dist/init-detect.js +188 -0
- package/dist/init-detect.js.map +1 -0
- package/dist/integration-deps.d.ts +62 -0
- package/dist/integration-deps.d.ts.map +1 -0
- package/dist/integration-deps.js +251 -0
- package/dist/integration-deps.js.map +1 -0
- package/dist/integration-runtime.d.ts +458 -0
- package/dist/integration-runtime.d.ts.map +1 -0
- package/dist/integration-runtime.js +681 -0
- package/dist/integration-runtime.js.map +1 -0
- package/dist/integration.d.ts +45 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +87 -0
- package/dist/integration.js.map +1 -0
- package/dist/integrator.d.ts +84 -0
- package/dist/integrator.d.ts.map +1 -0
- package/dist/integrator.js +193 -0
- package/dist/integrator.js.map +1 -0
- package/dist/lane-claims.d.ts +174 -0
- package/dist/lane-claims.d.ts.map +1 -0
- package/dist/lane-claims.js +218 -0
- package/dist/lane-claims.js.map +1 -0
- package/dist/lane-coverage.d.ts +87 -0
- package/dist/lane-coverage.d.ts.map +1 -0
- package/dist/lane-coverage.js +129 -0
- package/dist/lane-coverage.js.map +1 -0
- package/dist/ledger-replay.d.ts +115 -0
- package/dist/ledger-replay.d.ts.map +1 -0
- package/dist/ledger-replay.js +155 -0
- package/dist/ledger-replay.js.map +1 -0
- package/dist/ledger-store.d.ts +68 -0
- package/dist/ledger-store.d.ts.map +1 -0
- package/dist/ledger-store.js +111 -0
- package/dist/ledger-store.js.map +1 -0
- package/dist/loop-safety-observer.d.ts +132 -0
- package/dist/loop-safety-observer.d.ts.map +1 -0
- package/dist/loop-safety-observer.js +76 -0
- package/dist/loop-safety-observer.js.map +1 -0
- package/dist/loop-safety-store.d.ts +339 -0
- package/dist/loop-safety-store.d.ts.map +1 -0
- package/dist/loop-safety-store.js +800 -0
- package/dist/loop-safety-store.js.map +1 -0
- package/dist/machine-store.d.ts +54 -0
- package/dist/machine-store.d.ts.map +1 -0
- package/dist/machine-store.js +149 -0
- package/dist/machine-store.js.map +1 -0
- package/dist/mcp-main.d.ts +2 -0
- package/dist/mcp-main.d.ts.map +1 -0
- package/dist/mcp-main.js +116 -0
- package/dist/mcp-main.js.map +1 -0
- package/dist/mcp-server.d.ts +72 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +259 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/model-routing.d.ts +75 -0
- package/dist/model-routing.d.ts.map +1 -0
- package/dist/model-routing.js +108 -0
- package/dist/model-routing.js.map +1 -0
- package/dist/model-transport.d.ts +188 -0
- package/dist/model-transport.d.ts.map +1 -0
- package/dist/model-transport.js +625 -0
- package/dist/model-transport.js.map +1 -0
- package/dist/notify-cadence.d.ts +55 -0
- package/dist/notify-cadence.d.ts.map +1 -0
- package/dist/notify-cadence.js +68 -0
- package/dist/notify-cadence.js.map +1 -0
- package/dist/notify-channel.d.ts +69 -0
- package/dist/notify-channel.d.ts.map +1 -0
- package/dist/notify-channel.js +151 -0
- package/dist/notify-channel.js.map +1 -0
- package/dist/notify.d.ts +71 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +125 -0
- package/dist/notify.js.map +1 -0
- package/dist/observation-inventory.d.ts +65 -0
- package/dist/observation-inventory.d.ts.map +1 -0
- package/dist/observation-inventory.js +139 -0
- package/dist/observation-inventory.js.map +1 -0
- package/dist/owned-children.d.ts +45 -0
- package/dist/owned-children.d.ts.map +1 -0
- package/dist/owned-children.js +64 -0
- package/dist/owned-children.js.map +1 -0
- package/dist/packet-for-goal.d.ts +19 -0
- package/dist/packet-for-goal.d.ts.map +1 -0
- package/dist/packet-for-goal.js +32 -0
- package/dist/packet-for-goal.js.map +1 -0
- package/dist/pair-cmd.d.ts +85 -0
- package/dist/pair-cmd.d.ts.map +1 -0
- package/dist/pair-cmd.js +205 -0
- package/dist/pair-cmd.js.map +1 -0
- package/dist/partial-asker.d.ts +96 -0
- package/dist/partial-asker.d.ts.map +1 -0
- package/dist/partial-asker.js +117 -0
- package/dist/partial-asker.js.map +1 -0
- package/dist/pdf-text-lines.d.ts +17 -0
- package/dist/pdf-text-lines.d.ts.map +1 -0
- package/dist/pdf-text-lines.js +70 -0
- package/dist/pdf-text-lines.js.map +1 -0
- package/dist/pdf-text-worker.d.ts +2 -0
- package/dist/pdf-text-worker.d.ts.map +1 -0
- package/dist/pdf-text-worker.js +81 -0
- package/dist/pdf-text-worker.js.map +1 -0
- package/dist/pdf-text.d.ts +56 -0
- package/dist/pdf-text.d.ts.map +1 -0
- package/dist/pdf-text.js +64 -0
- package/dist/pdf-text.js.map +1 -0
- package/dist/pending-action.d.ts +154 -0
- package/dist/pending-action.d.ts.map +1 -0
- package/dist/pending-action.js +148 -0
- package/dist/pending-action.js.map +1 -0
- package/dist/preemptor.d.ts +40 -0
- package/dist/preemptor.d.ts.map +1 -0
- package/dist/preemptor.js +49 -0
- package/dist/preemptor.js.map +1 -0
- package/dist/programme-mode.d.ts +29 -0
- package/dist/programme-mode.d.ts.map +1 -0
- package/dist/programme-mode.js +50 -0
- package/dist/programme-mode.js.map +1 -0
- package/dist/progress-view.d.ts +14 -0
- package/dist/progress-view.d.ts.map +1 -0
- package/dist/progress-view.js +114 -0
- package/dist/progress-view.js.map +1 -0
- package/dist/project-graph-builder.d.ts +81 -0
- package/dist/project-graph-builder.d.ts.map +1 -0
- package/dist/project-graph-builder.js +246 -0
- package/dist/project-graph-builder.js.map +1 -0
- package/dist/project-graph-runtime.d.ts +86 -0
- package/dist/project-graph-runtime.d.ts.map +1 -0
- package/dist/project-graph-runtime.js +112 -0
- package/dist/project-graph-runtime.js.map +1 -0
- package/dist/project-index-builder.d.ts +96 -0
- package/dist/project-index-builder.d.ts.map +1 -0
- package/dist/project-index-builder.js +298 -0
- package/dist/project-index-builder.js.map +1 -0
- package/dist/project-index-facts.d.ts +51 -0
- package/dist/project-index-facts.d.ts.map +1 -0
- package/dist/project-index-facts.js +117 -0
- package/dist/project-index-facts.js.map +1 -0
- package/dist/project-index-store.d.ts +97 -0
- package/dist/project-index-store.d.ts.map +1 -0
- package/dist/project-index-store.js +140 -0
- package/dist/project-index-store.js.map +1 -0
- package/dist/project-memory-store.d.ts +127 -0
- package/dist/project-memory-store.d.ts.map +1 -0
- package/dist/project-memory-store.js +302 -0
- package/dist/project-memory-store.js.map +1 -0
- package/dist/proposal-store.d.ts +14 -0
- package/dist/proposal-store.d.ts.map +1 -0
- package/dist/proposal-store.js +19 -0
- package/dist/proposal-store.js.map +1 -0
- package/dist/prover.d.ts +80 -0
- package/dist/prover.d.ts.map +1 -0
- package/dist/prover.js +137 -0
- package/dist/prover.js.map +1 -0
- package/dist/provider-identity-store.d.ts +34 -0
- package/dist/provider-identity-store.d.ts.map +1 -0
- package/dist/provider-identity-store.js +72 -0
- package/dist/provider-identity-store.js.map +1 -0
- package/dist/provider-identity.d.ts +104 -0
- package/dist/provider-identity.d.ts.map +1 -0
- package/dist/provider-identity.js +167 -0
- package/dist/provider-identity.js.map +1 -0
- package/dist/provider-limit.d.ts +79 -0
- package/dist/provider-limit.d.ts.map +1 -0
- package/dist/provider-limit.js +148 -0
- package/dist/provider-limit.js.map +1 -0
- package/dist/provider-preflight.d.ts +171 -0
- package/dist/provider-preflight.d.ts.map +1 -0
- package/dist/provider-preflight.js +395 -0
- package/dist/provider-preflight.js.map +1 -0
- package/dist/provider-state.d.ts +120 -0
- package/dist/provider-state.d.ts.map +1 -0
- package/dist/provider-state.js +182 -0
- package/dist/provider-state.js.map +1 -0
- package/dist/providers.d.ts +132 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +161 -0
- package/dist/providers.js.map +1 -0
- package/dist/publication-recorder.d.ts +117 -0
- package/dist/publication-recorder.d.ts.map +1 -0
- package/dist/publication-recorder.js +124 -0
- package/dist/publication-recorder.js.map +1 -0
- package/dist/pursuit-delivery.d.ts +24 -0
- package/dist/pursuit-delivery.d.ts.map +1 -0
- package/dist/pursuit-delivery.js +38 -0
- package/dist/pursuit-delivery.js.map +1 -0
- package/dist/queue-store.d.ts +132 -0
- package/dist/queue-store.d.ts.map +1 -0
- package/dist/queue-store.js +185 -0
- package/dist/queue-store.js.map +1 -0
- package/dist/queue-summary.d.ts +14 -0
- package/dist/queue-summary.d.ts.map +1 -0
- package/dist/queue-summary.js +82 -0
- package/dist/queue-summary.js.map +1 -0
- package/dist/queue-view.d.ts +20 -0
- package/dist/queue-view.d.ts.map +1 -0
- package/dist/queue-view.js +163 -0
- package/dist/queue-view.js.map +1 -0
- package/dist/queue-write-lease.d.ts +106 -0
- package/dist/queue-write-lease.d.ts.map +1 -0
- package/dist/queue-write-lease.js +282 -0
- package/dist/queue-write-lease.js.map +1 -0
- package/dist/rails-home.d.ts +162 -0
- package/dist/rails-home.d.ts.map +1 -0
- package/dist/rails-home.js +452 -0
- package/dist/rails-home.js.map +1 -0
- package/dist/rails-migrations.d.ts +75 -0
- package/dist/rails-migrations.d.ts.map +1 -0
- package/dist/rails-migrations.js +199 -0
- package/dist/rails-migrations.js.map +1 -0
- package/dist/rails-stores.d.ts +49 -0
- package/dist/rails-stores.d.ts.map +1 -0
- package/dist/rails-stores.js +100 -0
- package/dist/rails-stores.js.map +1 -0
- package/dist/reaper.d.ts +69 -0
- package/dist/reaper.d.ts.map +1 -0
- package/dist/reaper.js +152 -0
- package/dist/reaper.js.map +1 -0
- package/dist/record-store.d.ts +36 -0
- package/dist/record-store.d.ts.map +1 -0
- package/dist/record-store.js +78 -0
- package/dist/record-store.js.map +1 -0
- package/dist/recovery-io.d.ts +50 -0
- package/dist/recovery-io.d.ts.map +1 -0
- package/dist/recovery-io.js +122 -0
- package/dist/recovery-io.js.map +1 -0
- package/dist/recovery.d.ts +146 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +110 -0
- package/dist/recovery.js.map +1 -0
- package/dist/redraw.d.ts +34 -0
- package/dist/redraw.d.ts.map +1 -0
- package/dist/redraw.js +62 -0
- package/dist/redraw.js.map +1 -0
- package/dist/rejected-evidence.d.ts +255 -0
- package/dist/rejected-evidence.d.ts.map +1 -0
- package/dist/rejected-evidence.js +324 -0
- package/dist/rejected-evidence.js.map +1 -0
- package/dist/report-cmd.d.ts +32 -0
- package/dist/report-cmd.d.ts.map +1 -0
- package/dist/report-cmd.js +201 -0
- package/dist/report-cmd.js.map +1 -0
- package/dist/repos-cmd.d.ts +80 -0
- package/dist/repos-cmd.d.ts.map +1 -0
- package/dist/repos-cmd.js +240 -0
- package/dist/repos-cmd.js.map +1 -0
- package/dist/request-body-shape.d.ts +93 -0
- package/dist/request-body-shape.d.ts.map +1 -0
- package/dist/request-body-shape.js +407 -0
- package/dist/request-body-shape.js.map +1 -0
- package/dist/requirement-claims.d.ts +89 -0
- package/dist/requirement-claims.d.ts.map +1 -0
- package/dist/requirement-claims.js +92 -0
- package/dist/requirement-claims.js.map +1 -0
- package/dist/retention-apply-runtime.d.ts +105 -0
- package/dist/retention-apply-runtime.d.ts.map +1 -0
- package/dist/retention-apply-runtime.js +269 -0
- package/dist/retention-apply-runtime.js.map +1 -0
- package/dist/reverify.d.ts +41 -0
- package/dist/reverify.d.ts.map +1 -0
- package/dist/reverify.js +69 -0
- package/dist/reverify.js.map +1 -0
- package/dist/round-records.d.ts +65 -0
- package/dist/round-records.d.ts.map +1 -0
- package/dist/round-records.js +61 -0
- package/dist/round-records.js.map +1 -0
- package/dist/round-runner.d.ts +924 -0
- package/dist/round-runner.d.ts.map +1 -0
- package/dist/round-runner.js +1693 -0
- package/dist/round-runner.js.map +1 -0
- package/dist/run-approval.d.ts +180 -0
- package/dist/run-approval.d.ts.map +1 -0
- package/dist/run-approval.js +199 -0
- package/dist/run-approval.js.map +1 -0
- package/dist/run-cmd.d.ts +359 -0
- package/dist/run-cmd.d.ts.map +1 -0
- package/dist/run-cmd.js +4418 -0
- package/dist/run-cmd.js.map +1 -0
- package/dist/run-delivery.d.ts +71 -0
- package/dist/run-delivery.d.ts.map +1 -0
- package/dist/run-delivery.js +104 -0
- package/dist/run-delivery.js.map +1 -0
- package/dist/run-ended.d.ts +75 -0
- package/dist/run-ended.d.ts.map +1 -0
- package/dist/run-ended.js +27 -0
- package/dist/run-ended.js.map +1 -0
- package/dist/run-ending-locator.d.ts +125 -0
- package/dist/run-ending-locator.d.ts.map +1 -0
- package/dist/run-ending-locator.js +133 -0
- package/dist/run-ending-locator.js.map +1 -0
- package/dist/run-launcher.d.ts +459 -0
- package/dist/run-launcher.d.ts.map +1 -0
- package/dist/run-launcher.js +652 -0
- package/dist/run-launcher.js.map +1 -0
- package/dist/run-lease.d.ts +208 -0
- package/dist/run-lease.d.ts.map +1 -0
- package/dist/run-lease.js +301 -0
- package/dist/run-lease.js.map +1 -0
- package/dist/run-refusal-marks.d.ts +189 -0
- package/dist/run-refusal-marks.d.ts.map +1 -0
- package/dist/run-refusal-marks.js +249 -0
- package/dist/run-refusal-marks.js.map +1 -0
- package/dist/run-start-marks.d.ts +153 -0
- package/dist/run-start-marks.d.ts.map +1 -0
- package/dist/run-start-marks.js +269 -0
- package/dist/run-start-marks.js.map +1 -0
- package/dist/scan-cmd.d.ts +14 -0
- package/dist/scan-cmd.d.ts.map +1 -0
- package/dist/scan-cmd.js +270 -0
- package/dist/scan-cmd.js.map +1 -0
- package/dist/scan-events.d.ts +50 -0
- package/dist/scan-events.d.ts.map +1 -0
- package/dist/scan-events.js +91 -0
- package/dist/scan-events.js.map +1 -0
- package/dist/scan.d.ts +191 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +417 -0
- package/dist/scan.js.map +1 -0
- package/dist/scope-rule.d.ts +148 -0
- package/dist/scope-rule.d.ts.map +1 -0
- package/dist/scope-rule.js +121 -0
- package/dist/scope-rule.js.map +1 -0
- package/dist/self-repo-id.d.ts +18 -0
- package/dist/self-repo-id.d.ts.map +1 -0
- package/dist/self-repo-id.js +70 -0
- package/dist/self-repo-id.js.map +1 -0
- package/dist/serve-cmd.d.ts +115 -0
- package/dist/serve-cmd.d.ts.map +1 -0
- package/dist/serve-cmd.js +505 -0
- package/dist/serve-cmd.js.map +1 -0
- package/dist/serve.d.ts +167 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +336 -0
- package/dist/serve.js.map +1 -0
- package/dist/server-client.d.ts +766 -0
- package/dist/server-client.d.ts.map +1 -0
- package/dist/server-client.js +475 -0
- package/dist/server-client.js.map +1 -0
- package/dist/session-adapter-runtime.d.ts +195 -0
- package/dist/session-adapter-runtime.d.ts.map +1 -0
- package/dist/session-adapter-runtime.js +373 -0
- package/dist/session-adapter-runtime.js.map +1 -0
- package/dist/session-checkpoint-runtime.d.ts +62 -0
- package/dist/session-checkpoint-runtime.d.ts.map +1 -0
- package/dist/session-checkpoint-runtime.js +268 -0
- package/dist/session-checkpoint-runtime.js.map +1 -0
- package/dist/session-control-transport.d.ts +106 -0
- package/dist/session-control-transport.d.ts.map +1 -0
- package/dist/session-control-transport.js +232 -0
- package/dist/session-control-transport.js.map +1 -0
- package/dist/session-observation-store.d.ts +98 -0
- package/dist/session-observation-store.d.ts.map +1 -0
- package/dist/session-observation-store.js +155 -0
- package/dist/session-observation-store.js.map +1 -0
- package/dist/session-observe-runtime.d.ts +123 -0
- package/dist/session-observe-runtime.d.ts.map +1 -0
- package/dist/session-observe-runtime.js +317 -0
- package/dist/session-observe-runtime.js.map +1 -0
- package/dist/session-store.d.ts +98 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +116 -0
- package/dist/session-store.js.map +1 -0
- package/dist/sessions-cmd.d.ts +202 -0
- package/dist/sessions-cmd.d.ts.map +1 -0
- package/dist/sessions-cmd.js +609 -0
- package/dist/sessions-cmd.js.map +1 -0
- package/dist/settings-overlay.d.ts +53 -0
- package/dist/settings-overlay.d.ts.map +1 -0
- package/dist/settings-overlay.js +175 -0
- package/dist/settings-overlay.js.map +1 -0
- package/dist/setup-prompt.d.ts +34 -0
- package/dist/setup-prompt.d.ts.map +1 -0
- package/dist/setup-prompt.js +92 -0
- package/dist/setup-prompt.js.map +1 -0
- package/dist/setup.d.ts +103 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +283 -0
- package/dist/setup.js.map +1 -0
- package/dist/shared-grants.d.ts +164 -0
- package/dist/shared-grants.d.ts.map +1 -0
- package/dist/shared-grants.js +242 -0
- package/dist/shared-grants.js.map +1 -0
- package/dist/signals.d.ts +55 -0
- package/dist/signals.d.ts.map +1 -0
- package/dist/signals.js +85 -0
- package/dist/signals.js.map +1 -0
- package/dist/source-cmd.d.ts +201 -0
- package/dist/source-cmd.d.ts.map +1 -0
- package/dist/source-cmd.js +684 -0
- package/dist/source-cmd.js.map +1 -0
- package/dist/source-verdict.d.ts +99 -0
- package/dist/source-verdict.d.ts.map +1 -0
- package/dist/source-verdict.js +182 -0
- package/dist/source-verdict.js.map +1 -0
- package/dist/spend-ledger.d.ts +350 -0
- package/dist/spend-ledger.d.ts.map +1 -0
- package/dist/spend-ledger.js +546 -0
- package/dist/spend-ledger.js.map +1 -0
- package/dist/stale-run-sweep.d.ts +79 -0
- package/dist/stale-run-sweep.d.ts.map +1 -0
- package/dist/stale-run-sweep.js +116 -0
- package/dist/stale-run-sweep.js.map +1 -0
- package/dist/start-attempts.d.ts +385 -0
- package/dist/start-attempts.d.ts.map +1 -0
- package/dist/start-attempts.js +613 -0
- package/dist/start-attempts.js.map +1 -0
- package/dist/status-cmd.d.ts +28 -0
- package/dist/status-cmd.d.ts.map +1 -0
- package/dist/status-cmd.js +116 -0
- package/dist/status-cmd.js.map +1 -0
- package/dist/stop-follow-up.d.ts +107 -0
- package/dist/stop-follow-up.d.ts.map +1 -0
- package/dist/stop-follow-up.js +172 -0
- package/dist/stop-follow-up.js.map +1 -0
- package/dist/surface-lease.d.ts +118 -0
- package/dist/surface-lease.d.ts.map +1 -0
- package/dist/surface-lease.js +485 -0
- package/dist/surface-lease.js.map +1 -0
- package/dist/sync.d.ts +54 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +93 -0
- package/dist/sync.js.map +1 -0
- package/dist/syncing-ledger.d.ts +45 -0
- package/dist/syncing-ledger.d.ts.map +1 -0
- package/dist/syncing-ledger.js +53 -0
- package/dist/syncing-ledger.js.map +1 -0
- package/dist/temp-fixtures.d.ts +68 -0
- package/dist/temp-fixtures.d.ts.map +1 -0
- package/dist/temp-fixtures.js +38 -0
- package/dist/temp-fixtures.js.map +1 -0
- package/dist/token-window.d.ts +84 -0
- package/dist/token-window.d.ts.map +1 -0
- package/dist/token-window.js +143 -0
- package/dist/token-window.js.map +1 -0
- package/dist/tool-bridge.d.ts +177 -0
- package/dist/tool-bridge.d.ts.map +1 -0
- package/dist/tool-bridge.js +329 -0
- package/dist/tool-bridge.js.map +1 -0
- package/dist/tool-call-record.d.ts +197 -0
- package/dist/tool-call-record.d.ts.map +1 -0
- package/dist/tool-call-record.js +276 -0
- package/dist/tool-call-record.js.map +1 -0
- package/dist/tool-protocol.d.ts +135 -0
- package/dist/tool-protocol.d.ts.map +1 -0
- package/dist/tool-protocol.js +104 -0
- package/dist/tool-protocol.js.map +1 -0
- package/dist/tool-worker.d.ts +3 -0
- package/dist/tool-worker.d.ts.map +1 -0
- package/dist/tool-worker.js +478 -0
- package/dist/tool-worker.js.map +1 -0
- package/dist/toolchain-mirror.d.ts +86 -0
- package/dist/toolchain-mirror.d.ts.map +1 -0
- package/dist/toolchain-mirror.js +307 -0
- package/dist/toolchain-mirror.js.map +1 -0
- package/dist/trusted-git.d.ts +243 -0
- package/dist/trusted-git.d.ts.map +1 -0
- package/dist/trusted-git.js +920 -0
- package/dist/trusted-git.js.map +1 -0
- package/dist/ui.d.ts +76 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +118 -0
- package/dist/ui.js.map +1 -0
- package/dist/untrusted-exec.d.ts +275 -0
- package/dist/untrusted-exec.d.ts.map +1 -0
- package/dist/untrusted-exec.js +479 -0
- package/dist/untrusted-exec.js.map +1 -0
- package/dist/untrusted-gate.d.ts +73 -0
- package/dist/untrusted-gate.d.ts.map +1 -0
- package/dist/untrusted-gate.js +226 -0
- package/dist/untrusted-gate.js.map +1 -0
- package/dist/uploader.d.ts +153 -0
- package/dist/uploader.d.ts.map +1 -0
- package/dist/uploader.js +204 -0
- package/dist/uploader.js.map +1 -0
- package/dist/verifier.d.ts +66 -0
- package/dist/verifier.d.ts.map +1 -0
- package/dist/verifier.js +170 -0
- package/dist/verifier.js.map +1 -0
- package/dist/verify-apply.d.ts +26 -0
- package/dist/verify-apply.d.ts.map +1 -0
- package/dist/verify-apply.js +36 -0
- package/dist/verify-apply.js.map +1 -0
- package/dist/verify-cmd.d.ts +44 -0
- package/dist/verify-cmd.d.ts.map +1 -0
- package/dist/verify-cmd.js +125 -0
- package/dist/verify-cmd.js.map +1 -0
- package/dist/verify-pass.d.ts +73 -0
- package/dist/verify-pass.d.ts.map +1 -0
- package/dist/verify-pass.js +103 -0
- package/dist/verify-pass.js.map +1 -0
- package/dist/welcome.d.ts +43 -0
- package/dist/welcome.d.ts.map +1 -0
- package/dist/welcome.js +116 -0
- package/dist/welcome.js.map +1 -0
- package/dist/workspace-provisioner.d.ts +65 -0
- package/dist/workspace-provisioner.d.ts.map +1 -0
- package/dist/workspace-provisioner.js +66 -0
- package/dist/workspace-provisioner.js.map +1 -0
- package/dist/workspace-quarantine.d.ts +64 -0
- package/dist/workspace-quarantine.d.ts.map +1 -0
- package/dist/workspace-quarantine.js +156 -0
- package/dist/workspace-quarantine.js.map +1 -0
- package/dist/workspace-residue-sweep.d.ts +113 -0
- package/dist/workspace-residue-sweep.d.ts.map +1 -0
- package/dist/workspace-residue-sweep.js +158 -0
- package/dist/workspace-residue-sweep.js.map +1 -0
- package/dist/workspace.d.ts +639 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +2026 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +52 -4
- package/win/CredProbe.cs +64 -0
- package/win/FakeClaude.cs +56 -0
- package/win/HandleProbe.cs +116 -0
- package/win/TokenProbe.cs +53 -0
- package/win/appcontainer.ps1 +500 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.nodeSafetyFs = exports.LoopSafetyStore = void 0;
|
|
37
|
+
const core_1 = require("@looply/core");
|
|
38
|
+
function isObservation(v) {
|
|
39
|
+
if (typeof v !== 'object' || v === null)
|
|
40
|
+
return false;
|
|
41
|
+
const o = v;
|
|
42
|
+
return (typeof o['goalId'] === 'string' &&
|
|
43
|
+
typeof o['goalVersion'] === 'number' &&
|
|
44
|
+
typeof o['ordinal'] === 'number' &&
|
|
45
|
+
typeof o['outcome'] === 'string' &&
|
|
46
|
+
typeof o['progressFingerprint'] === 'string');
|
|
47
|
+
}
|
|
48
|
+
function isIncident(v) {
|
|
49
|
+
if (typeof v !== 'object' || v === null)
|
|
50
|
+
return false;
|
|
51
|
+
const o = v;
|
|
52
|
+
return (typeof o['id'] === 'string' &&
|
|
53
|
+
typeof o['goalId'] === 'string' &&
|
|
54
|
+
typeof o['goalVersion'] === 'number' &&
|
|
55
|
+
typeof o['kind'] === 'string' &&
|
|
56
|
+
typeof o['because'] === 'string');
|
|
57
|
+
}
|
|
58
|
+
class LoopSafetyStore {
|
|
59
|
+
stateDir;
|
|
60
|
+
fs;
|
|
61
|
+
now;
|
|
62
|
+
migrate;
|
|
63
|
+
/** Distinguishes two writes from ONE process; the pid distinguishes processes. */
|
|
64
|
+
static writes = 0;
|
|
65
|
+
observations = [];
|
|
66
|
+
incidents = [];
|
|
67
|
+
state = 'ok';
|
|
68
|
+
opened = false;
|
|
69
|
+
/**
|
|
70
|
+
* ⚠️ `migrate` RUNS BEFORE ANY READ, AND ITS REFUSAL LEAVES THE STORE
|
|
71
|
+
* UNREADABLE — which is a REFUSAL TO START, not a silent pass.
|
|
72
|
+
*
|
|
73
|
+
* The history's home moved out of the repository (see `rails-home.ts`). During
|
|
74
|
+
* a move, "I could not read the old history" and "there is no old history"
|
|
75
|
+
* look the same, and this store must never confuse them: an empty history
|
|
76
|
+
* discharges every hold it holds. A `repeated_failure` hold raised an hour ago
|
|
77
|
+
* would vanish, the run it was stopping would start, and nothing would say why.
|
|
78
|
+
*
|
|
79
|
+
* Omitted, there is no migration and nothing changes.
|
|
80
|
+
*/
|
|
81
|
+
constructor(stateDir,
|
|
82
|
+
/**
|
|
83
|
+
* ⚠️ `readDir` IS OPTIONAL, so every existing fake still satisfies this.
|
|
84
|
+
* A store that cannot list the ordinal claims simply falls back to deriving
|
|
85
|
+
* the next attempt number from the observations, which is what it did
|
|
86
|
+
* before — weaker, and never wrong in a direction that loses a hold.
|
|
87
|
+
*/
|
|
88
|
+
fs, now = () => Date.now(), migrate) {
|
|
89
|
+
this.stateDir = stateDir;
|
|
90
|
+
this.fs = fs;
|
|
91
|
+
this.now = now;
|
|
92
|
+
this.migrate = migrate;
|
|
93
|
+
}
|
|
94
|
+
get path() {
|
|
95
|
+
return `${this.stateDir}/loop-safety.json`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The append-only history of attempts.
|
|
99
|
+
*
|
|
100
|
+
* 🔴 A SEPARATE FILE FROM THE INCIDENTS, AND APPEND-ONLY, BECAUSE A REWRITE
|
|
101
|
+
* LOSES A CONCURRENT WRITER'S WORK. Two writers for one goal on one machine is
|
|
102
|
+
* ordinary — the run recording what it achieved, and the Start gate spending a
|
|
103
|
+
* retry — and a whole-document rewrite meant whichever loaded first clobbered
|
|
104
|
+
* the other. For the spent retry that was survivable (the marker file is the
|
|
105
|
+
* authority). For an OBSERVATION it was not: a shorter history holds LATER, so
|
|
106
|
+
* the machine keeps working, which is the direction that costs a person money.
|
|
107
|
+
*
|
|
108
|
+
* The shape is `ledger-store.ts`'s, deliberately: events appended as JSONL,
|
|
109
|
+
* never rewritten, with the small mutable thing kept as a projection beside
|
|
110
|
+
* it. ⚠️ NOT `command-receipts.ts` — that one rewrites its whole document too,
|
|
111
|
+
* and an earlier draft of this file's own handoff cited it as the append-only
|
|
112
|
+
* precedent. It is not one.
|
|
113
|
+
*
|
|
114
|
+
* NEVER COMPACT THIS FILE. `ledger-store` says the same of its own events and
|
|
115
|
+
* for the same reason: a compaction is a rewrite, and a concurrent append
|
|
116
|
+
* during one is exactly the loss this change exists to prevent.
|
|
117
|
+
*
|
|
118
|
+
* ⚠️ THE COST THIS TRADE BUYS, STATED PLAINLY RATHER THAN LEFT TO BE FOUND.
|
|
119
|
+
* The whole file is read and parsed on every observation, because the verdict
|
|
120
|
+
* must be a fact about the machine rather than about one process. So the read
|
|
121
|
+
* grows with the machine's LIFETIME, not with its current workload: one record
|
|
122
|
+
* per attempt, forever, re-parsed each time.
|
|
123
|
+
*
|
|
124
|
+
* That is affordable at the scale this runs at — attempts are minutes apart
|
|
125
|
+
* and bounded by the very brake this file feeds — and it is the honest
|
|
126
|
+
* successor to the limit it replaced, not the absence of one. If a machine
|
|
127
|
+
* ever accumulates enough attempts for this to matter, the answer is a second
|
|
128
|
+
* ledger file rolled by date with the old one left in place, NOT a compaction
|
|
129
|
+
* of this one.
|
|
130
|
+
*/
|
|
131
|
+
get eventsPath() {
|
|
132
|
+
return `${this.stateDir}/loop-safety-events.jsonl`;
|
|
133
|
+
}
|
|
134
|
+
/** Where an override's exclusive-create claim lives. One file per incident. */
|
|
135
|
+
/**
|
|
136
|
+
* Where incidents are written now: APPEND-ONLY, one per line.
|
|
137
|
+
*
|
|
138
|
+
* 🔴 `persist()` REWROTE THE WHOLE DOCUMENT from an `open()`-time snapshot,
|
|
139
|
+
* and Phase 1 turned that into a hold-erasing race. Moving this file to the
|
|
140
|
+
* project state directory is what lets a hold raised in one workspace be seen
|
|
141
|
+
* in another — and the same change put two concurrent writers on it. Two runs
|
|
142
|
+
* raising holds for two different pursuits each wrote a document without the
|
|
143
|
+
* other's incident, so one brake vanished. Unlike an override, nothing
|
|
144
|
+
* recovers it.
|
|
145
|
+
*
|
|
146
|
+
* Observations were made append-only long ago for exactly this reason.
|
|
147
|
+
* Incidents simply never were, because until Phase 1 there could not be two
|
|
148
|
+
* writers.
|
|
149
|
+
*/
|
|
150
|
+
get incidentsPath() {
|
|
151
|
+
return `${this.stateDir}/loop-safety-incidents.jsonl`;
|
|
152
|
+
}
|
|
153
|
+
/** A short unique token for an incident id. Filename-safe by construction. */
|
|
154
|
+
incidentToken() {
|
|
155
|
+
return `${this.now().toString(36)}${Math.floor(Math.random() * 1e6).toString(36)}`;
|
|
156
|
+
}
|
|
157
|
+
overridePath(incidentId) {
|
|
158
|
+
return `${this.stateDir}/loop-safety-overrides/${incidentId}.json`;
|
|
159
|
+
}
|
|
160
|
+
/** Read what is on disk. Never throws: an unreadable file is a state, not a crash. */
|
|
161
|
+
async open() {
|
|
162
|
+
this.opened = true;
|
|
163
|
+
if (this.migrate !== undefined) {
|
|
164
|
+
const moved = await this.migrate();
|
|
165
|
+
if (!moved.ok) {
|
|
166
|
+
this.state = 'unreadable';
|
|
167
|
+
return this.state;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
let text;
|
|
171
|
+
try {
|
|
172
|
+
text = await this.fs.readFile(this.path);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
this.state = 'unreadable';
|
|
176
|
+
return this.state;
|
|
177
|
+
}
|
|
178
|
+
if (text === null) {
|
|
179
|
+
this.incidents = [];
|
|
180
|
+
await this.loadIncidents();
|
|
181
|
+
await this.loadObservations();
|
|
182
|
+
await this.foldInSpentRetries();
|
|
183
|
+
this.state = 'ok';
|
|
184
|
+
return this.state;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(text);
|
|
188
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
189
|
+
this.state = 'unreadable';
|
|
190
|
+
return this.state;
|
|
191
|
+
}
|
|
192
|
+
this.incidents = Array.isArray(parsed.incidents) ? parsed.incidents.filter(isIncident) : [];
|
|
193
|
+
/**
|
|
194
|
+
* ⚠️ OBSERVATIONS ARE READ FROM THE LEDGER, AND A DOCUMENT WRITTEN BY AN
|
|
195
|
+
* OLDER BUILD MAY STILL CARRY SOME. They are taken as the history's start
|
|
196
|
+
* so an upgrade does not silently discharge a pursuit that was three
|
|
197
|
+
* barren attempts deep, and the ledger is appended after them.
|
|
198
|
+
*/
|
|
199
|
+
const carried = Array.isArray(parsed.observations)
|
|
200
|
+
? parsed.observations.filter(isObservation)
|
|
201
|
+
: [];
|
|
202
|
+
await this.loadIncidents();
|
|
203
|
+
await this.loadObservations(carried);
|
|
204
|
+
await this.foldInSpentRetries();
|
|
205
|
+
this.state = 'ok';
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
/**
|
|
209
|
+
* Written temp-then-rename, so a torn file should be impossible. Garbage
|
|
210
|
+
* here is a fact about the machine rather than a race — and guessing
|
|
211
|
+
* "nothing has happened" would silently discharge every hold this file
|
|
212
|
+
* holds, which is the one direction that must never fail open.
|
|
213
|
+
*/
|
|
214
|
+
this.state = 'unreadable';
|
|
215
|
+
}
|
|
216
|
+
return this.state;
|
|
217
|
+
}
|
|
218
|
+
status() {
|
|
219
|
+
return this.state;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The active incident for a pursuit, or null.
|
|
223
|
+
*
|
|
224
|
+
* ⚠️ AN UNREADABLE FILE ANSWERS `null`, AND THAT IS DELIBERATE — but it is
|
|
225
|
+
* NOT the same as failing open. The caller (`autonomy-tick`) treats an
|
|
226
|
+
* unreadable store as a hold in its own right, because a machine that cannot
|
|
227
|
+
* read its safety history must not spend a night on the assumption that it is
|
|
228
|
+
* empty. Deciding that here would hide it inside a getter.
|
|
229
|
+
*/
|
|
230
|
+
/**
|
|
231
|
+
* Whether this store could read its history.
|
|
232
|
+
*
|
|
233
|
+
* 🔴 EXPOSED BECAUSE THE RULE ABOVE HAD A SECOND CALLER THAT DID NOT KNOW IT.
|
|
234
|
+
* `activeIncident` answers null on an unreadable file and says, correctly,
|
|
235
|
+
* that deciding there "would hide it inside a getter" — but that only works
|
|
236
|
+
* while every caller applies the rule. `autonomy-tick` did; the coordinator's
|
|
237
|
+
* `holdsFor` did not, and returned NO HOLDS for an unreadable history, which
|
|
238
|
+
* `decideSchedule` reads as "nothing is held" and answers with `start`.
|
|
239
|
+
*
|
|
240
|
+
* `open()` already returns this; a store that has been opened elsewhere had
|
|
241
|
+
* no way to be asked again.
|
|
242
|
+
*/
|
|
243
|
+
readable() {
|
|
244
|
+
return this.state === 'ok';
|
|
245
|
+
}
|
|
246
|
+
activeIncident(goalId, goalVersion) {
|
|
247
|
+
return (this.incidents.find((i) => i.goalId === goalId &&
|
|
248
|
+
i.goalVersion === goalVersion &&
|
|
249
|
+
i.overriddenByStartId === undefined) ?? null);
|
|
250
|
+
}
|
|
251
|
+
/** Every incident ever derived for this pursuit, newest last. History, not state. */
|
|
252
|
+
history(goalId) {
|
|
253
|
+
return this.incidents.filter((i) => i.goalId === goalId);
|
|
254
|
+
}
|
|
255
|
+
observationsFor(goalId) {
|
|
256
|
+
return this.observations.filter((o) => o.goalId === goalId);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The number to give the next attempt for this pursuit.
|
|
260
|
+
*
|
|
261
|
+
* ⚠️ NOT `observationsFor(...).length + 1`, WHICH IS WHAT THIS REPLACED. The
|
|
262
|
+
* history is bounded at `MAX_SAFETY_OBSERVATIONS`, so a count SATURATES: past
|
|
263
|
+
* sixteen attempts every one of them is numbered seventeen, and the evidence
|
|
264
|
+
* a person reads to understand why their machine stopped becomes three
|
|
265
|
+
* identical lines. No detector reads the ordinal, so nothing went wrong — it
|
|
266
|
+
* was only unreadable, which is the kind of defect a test suite never has an
|
|
267
|
+
* opinion about.
|
|
268
|
+
*
|
|
269
|
+
* Counting on from the LAST one survives trimming, because trimming keeps the
|
|
270
|
+
* newest. It restarts at one for a pursuit with no history, which is correct:
|
|
271
|
+
* a person who has never seen this Goal run should be shown attempt one.
|
|
272
|
+
*/
|
|
273
|
+
nextOrdinalFor(goalId) {
|
|
274
|
+
const mine = this.observationsFor(goalId);
|
|
275
|
+
const last = mine[mine.length - 1];
|
|
276
|
+
return last === undefined ? 1 : last.ordinal + 1;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* The largest attempt number anybody has already claimed for this goal, or 0.
|
|
280
|
+
*
|
|
281
|
+
* ⚠️ CLAIMS, NOT OBSERVATIONS. See `mintOrdinal`. An unreadable directory
|
|
282
|
+
* answers 0, which makes the caller fall back to the observation-derived
|
|
283
|
+
* start — the same answer it had before, rather than a refusal.
|
|
284
|
+
*/
|
|
285
|
+
async highestClaim(goalId) {
|
|
286
|
+
let names;
|
|
287
|
+
try {
|
|
288
|
+
names = (await this.fs.readDir?.(`${this.stateDir}/loop-safety-ordinals`)) ?? [];
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return 0;
|
|
292
|
+
}
|
|
293
|
+
let top = 0;
|
|
294
|
+
const prefix = `${goalId}-`;
|
|
295
|
+
for (const name of names) {
|
|
296
|
+
if (!name.startsWith(prefix) || !name.endsWith('.json'))
|
|
297
|
+
continue;
|
|
298
|
+
const n = Number.parseInt(name.slice(prefix.length, -'.json'.length), 10);
|
|
299
|
+
if (Number.isInteger(n) && n > top)
|
|
300
|
+
top = n;
|
|
301
|
+
}
|
|
302
|
+
return top;
|
|
303
|
+
}
|
|
304
|
+
/** Where an ordinal's exclusive-create claim lives. One file per attempt. */
|
|
305
|
+
ordinalPath(goalId, ordinal) {
|
|
306
|
+
return `${this.stateDir}/loop-safety-ordinals/${goalId}-${ordinal}.json`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* TAKE THE NEXT ATTEMPT NUMBER, EXCLUSIVELY.
|
|
310
|
+
*
|
|
311
|
+
* 🔴 `nextOrdinalFor` IS A READ, AND TWO RUNS READ THE SAME ANSWER.
|
|
312
|
+
*
|
|
313
|
+
* It derives the next number from the last observation on disk. That was
|
|
314
|
+
* sound while one run owned one checkout. With N workspaces two runs on one
|
|
315
|
+
* goal start within the same second, both read "the last attempt was 3", and
|
|
316
|
+
* both record attempt 4 — so the evidence a person is shown to explain why
|
|
317
|
+
* their machine stopped contains two different attempt fours and no five.
|
|
318
|
+
* `MAX_SAME_FAILURE_REPEATS` still fires correctly, because no detector reads
|
|
319
|
+
* the ordinal; what breaks is the only part of a hold a person can act on.
|
|
320
|
+
*
|
|
321
|
+
* ⚠️ THE SAME PRIMITIVE THE OVERRIDE MARKER USES, three files away: the
|
|
322
|
+
* filesystem arbitrates, never a read-then-write, because the two claimants
|
|
323
|
+
* may be two processes. On EEXIST the candidate simply advances — losing a
|
|
324
|
+
* race means somebody else has that number, which is exactly the information
|
|
325
|
+
* needed to pick the next one.
|
|
326
|
+
*
|
|
327
|
+
* ⚠️ GAPS ARE FINE, DUPLICATES ARE NOT. A run that mints 4 and then crashes
|
|
328
|
+
* burns 4, and the next attempt is 5. Nothing counts ordinals — the history's
|
|
329
|
+
* length is what detectors read — so a gap costs a person nothing, while a
|
|
330
|
+
* duplicate costs them the ability to tell two attempts apart.
|
|
331
|
+
*/
|
|
332
|
+
async mintOrdinal(goalId) {
|
|
333
|
+
if (!this.opened)
|
|
334
|
+
throw new Error('LoopSafetyStore.mintOrdinal before open()');
|
|
335
|
+
/**
|
|
336
|
+
* ⚠️ PATH SAFETY, NOT VALIDATION THEATRE: the goal id becomes a filename.
|
|
337
|
+
* A goal id that cannot be a filename gets the unsynchronised answer rather
|
|
338
|
+
* than a thrown error — this is bookkeeping for a person to read, and no
|
|
339
|
+
* run should ever end because its goal was named unusually.
|
|
340
|
+
*/
|
|
341
|
+
if (/[^A-Za-z0-9_-]/.test(goalId))
|
|
342
|
+
return this.nextOrdinalFor(goalId);
|
|
343
|
+
await this.fs.mkdir(`${this.stateDir}/loop-safety-ordinals`);
|
|
344
|
+
/**
|
|
345
|
+
* ⚠️ RELOAD FIRST. `nextOrdinalFor` reads the snapshot taken at `open()`,
|
|
346
|
+
* and a run opens the store at its first second and mints hours later. The
|
|
347
|
+
* EEXIST loop below would climb past a stale start eventually, but starting
|
|
348
|
+
* from the truth means it almost never has to.
|
|
349
|
+
*/
|
|
350
|
+
await this.loadObservations();
|
|
351
|
+
/**
|
|
352
|
+
* 🔴 THE START IS THE HIGHEST CLAIM ON DISK, NOT THE LAST OBSERVATION — AND
|
|
353
|
+
* WITHOUT THAT THE WHOLE MECHANISM WEDGED AFTER 64 MINTS.
|
|
354
|
+
*
|
|
355
|
+
* A claim file is written for every MINT; an OBSERVATION is only recorded
|
|
356
|
+
* when the attempt counts. An excluded attempt — awaiting approval, an
|
|
357
|
+
* ambiguous governed action, a blocked environment — therefore leaves a
|
|
358
|
+
* claim above the last observation, permanently. Starting from the last
|
|
359
|
+
* observation meant the loop walked the same burnt range every time, and
|
|
360
|
+
* after 64 such attempts it exhausted the window and fell through to the
|
|
361
|
+
* unsynchronised answer FOR EVER. MEASURED: 70 mints with no observations
|
|
362
|
+
* returned 1..64 and then 1, 1, 1, 1 — two concurrent runs both handed
|
|
363
|
+
* ordinal 1, which is exactly what this function exists to prevent, and
|
|
364
|
+
* silently.
|
|
365
|
+
*
|
|
366
|
+
* Reading the claims answers the real question — what has anyone taken —
|
|
367
|
+
* rather than a proxy for it.
|
|
368
|
+
*/
|
|
369
|
+
const from = Math.max(this.nextOrdinalFor(goalId), (await this.highestClaim(goalId)) + 1);
|
|
370
|
+
/**
|
|
371
|
+
* Bounded, because an unbounded loop against a filesystem is a hang. The
|
|
372
|
+
* bound is the number of runs that could have taken a number between this
|
|
373
|
+
* one's read and its write; sixty-four is far past any real machine, and
|
|
374
|
+
* exhausting it yields the unsynchronised answer rather than failing the run.
|
|
375
|
+
*/
|
|
376
|
+
for (let ordinal = from; ordinal < from + 64; ordinal++) {
|
|
377
|
+
try {
|
|
378
|
+
await this.fs.createExclusive(this.ordinalPath(goalId, ordinal), `${JSON.stringify({ goalId, ordinal, pid: process.pid, at: new Date(this.now()).toISOString() })}\n`);
|
|
379
|
+
return ordinal;
|
|
380
|
+
}
|
|
381
|
+
catch (e) {
|
|
382
|
+
if (e.code !== 'EEXIST')
|
|
383
|
+
return this.nextOrdinalFor(goalId);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return from;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Record what one attempt achieved, and say whether that makes it unsafe to
|
|
390
|
+
* continue.
|
|
391
|
+
*
|
|
392
|
+
* The verdict is computed from what is now on disk rather than from the
|
|
393
|
+
* caller's belief, so a caller that forgets to persist cannot produce a
|
|
394
|
+
* verdict about a history that does not exist.
|
|
395
|
+
*/
|
|
396
|
+
async observe(o) {
|
|
397
|
+
if (!this.opened)
|
|
398
|
+
throw new Error('LoopSafetyStore.observe before open()');
|
|
399
|
+
/**
|
|
400
|
+
* ⚠️ THERE USED TO BE AN `unreadable` GUARD HERE, AND ITS REASON DIED WITH
|
|
401
|
+
* THE REWRITE. It said "refuse to rewrite a file we could not read" — true
|
|
402
|
+
* and load-bearing while `observe` rewrote the whole document, and false the
|
|
403
|
+
* moment the history became an append-only ledger. A mutation battery found
|
|
404
|
+
* it: deleting the guard changed no test, because there was no longer
|
|
405
|
+
* anything for it to protect.
|
|
406
|
+
*
|
|
407
|
+
* What it did instead was DISCARD EVIDENCE. An unreadable incidents document
|
|
408
|
+
* is no reason to stop recording what a run achieved: appending cannot
|
|
409
|
+
* corrupt anything, and `raise()` still refuses to create an incident from a
|
|
410
|
+
* document it could not read, so nothing is decided on bad information.
|
|
411
|
+
*
|
|
412
|
+
* A guard whose stated reason has stopped being true is worse than no guard.
|
|
413
|
+
* It reads as protection and is not, and the next person to touch this file
|
|
414
|
+
* would have reasoned from the comment rather than from the code.
|
|
415
|
+
*/
|
|
416
|
+
/**
|
|
417
|
+
* Append, then RE-READ before judging. The re-read is what makes the verdict
|
|
418
|
+
* a fact about the machine rather than about this process: another writer's
|
|
419
|
+
* attempt is on disk and not in this object's memory, and a detector that
|
|
420
|
+
* counted only its own caller's attempts would hold a pursuit late by
|
|
421
|
+
* exactly as many attempts as the other writer made.
|
|
422
|
+
*/
|
|
423
|
+
/**
|
|
424
|
+
* 🔴 LED BY A NEWLINE, NOT MERELY TERMINATED BY ONE — and a test written to
|
|
425
|
+
* check a torn append is what found this. A crash part-way through a write
|
|
426
|
+
* leaves a fragment with no trailing newline; the NEXT record then appends
|
|
427
|
+
* straight onto it and the two become one unparseable line, so a single
|
|
428
|
+
* torn write cost TWO attempts rather than one.
|
|
429
|
+
*
|
|
430
|
+
* Leading each record instead means a fragment always ends up alone on its
|
|
431
|
+
* line and is skipped by itself. The blank line this leaves in the ordinary
|
|
432
|
+
* case is skipped by the same rule, which is why this needs no read of the
|
|
433
|
+
* file first and so stays safe against another writer appending at the same
|
|
434
|
+
* moment.
|
|
435
|
+
*/
|
|
436
|
+
/**
|
|
437
|
+
* ⚠️ `mkdir` FIRST, WHICH `raise` AND `record` BOTH DO AND THIS DID NOT.
|
|
438
|
+
*
|
|
439
|
+
* It never mattered while the directory was inside a checkout that had to
|
|
440
|
+
* exist for the run to start. The machine-level home does not: on a fresh
|
|
441
|
+
* machine the first thing that touches it is this append, and an ENOENT
|
|
442
|
+
* here is not caught anywhere on the path from `runLoop`.
|
|
443
|
+
*/
|
|
444
|
+
await this.fs.mkdir(this.stateDir);
|
|
445
|
+
await this.fs.appendFile(this.eventsPath, `\n${JSON.stringify(o)}\n`);
|
|
446
|
+
await this.loadObservations();
|
|
447
|
+
// Named explicitly rather than relying on the caller's history happening to
|
|
448
|
+
// hold one pursuit: this store holds every goal on the machine.
|
|
449
|
+
return (0, core_1.evaluateLoopSafety)({
|
|
450
|
+
observations: this.observations,
|
|
451
|
+
goalVersion: o.goalVersion,
|
|
452
|
+
goalId: o.goalId,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Write down that autonomy has stopped, and why.
|
|
457
|
+
*
|
|
458
|
+
* Idempotent on the pursuit: a second derivation while one is already active
|
|
459
|
+
* returns the existing incident rather than stacking. Two holds for one
|
|
460
|
+
* stuck condition would need two overrides to clear, and a person would have
|
|
461
|
+
* to press the button twice for reasons nobody could explain.
|
|
462
|
+
*/
|
|
463
|
+
async raise(input) {
|
|
464
|
+
if (!this.opened)
|
|
465
|
+
throw new Error('LoopSafetyStore.raise before open()');
|
|
466
|
+
if (this.state === 'unreadable')
|
|
467
|
+
return null;
|
|
468
|
+
const existing = this.activeIncident(input.goalId, input.goalVersion);
|
|
469
|
+
if (existing !== null)
|
|
470
|
+
return existing;
|
|
471
|
+
const at = new Date(this.now()).toISOString();
|
|
472
|
+
const incident = {
|
|
473
|
+
// Derived from the pursuit and the moment, so two machines cannot mint
|
|
474
|
+
// the same id and a person can find it in a log.
|
|
475
|
+
/**
|
|
476
|
+
* 🔴 NOT `incidents.length + 1`, AND THE REASON IS WORSE THAN A COLLISION.
|
|
477
|
+
*
|
|
478
|
+
* That counted each writer's OWN snapshot, so two concurrent raises minted
|
|
479
|
+
* the identical id. The override marker is keyed by id, is permanent, and
|
|
480
|
+
* is exclusive-create — so an id reused by a LATER generation finds a
|
|
481
|
+
* months-old marker waiting for it, and a genuinely new stuck-loop hold is
|
|
482
|
+
* discharged at birth. Measured: raise, override, lose the document to a
|
|
483
|
+
* concurrent writer, raise again — and the fresh hold reads as already
|
|
484
|
+
* overridden. The brake applies and releases itself, silently.
|
|
485
|
+
*
|
|
486
|
+
* Unique per raise, and within [A-Za-z0-9_-] because it becomes a filename.
|
|
487
|
+
*/
|
|
488
|
+
id: `safety-${input.goalId}-${input.goalVersion}-${this.incidentToken()}`,
|
|
489
|
+
goalId: input.goalId,
|
|
490
|
+
goalVersion: input.goalVersion,
|
|
491
|
+
kind: input.kind,
|
|
492
|
+
because: input.because,
|
|
493
|
+
evidence: [...input.evidence],
|
|
494
|
+
triggeredAt: at,
|
|
495
|
+
};
|
|
496
|
+
this.incidents.push(incident);
|
|
497
|
+
// APPENDED, never rewritten. See `incidentsPath`.
|
|
498
|
+
await this.fs.mkdir(this.stateDir);
|
|
499
|
+
/**
|
|
500
|
+
* 🔴 LED BY A NEWLINE, LIKE THE EVENTS FILE, AND FOR THE SAME REASON.
|
|
501
|
+
*
|
|
502
|
+
* This was terminated by one only. A crash part-way through a write leaves
|
|
503
|
+
* a fragment with no trailing newline; the NEXT incident then appends
|
|
504
|
+
* straight onto it and the two become one unparseable line — so a torn
|
|
505
|
+
* write costs TWO incidents rather than one, and an incident is a HOLD. The
|
|
506
|
+
* events file worked this out and wrote it down; incidents never followed,
|
|
507
|
+
* because until Phase 1 there could not be two writers here.
|
|
508
|
+
*
|
|
509
|
+
* `loadIncidents` already skips blank lines, so the extra newline in the
|
|
510
|
+
* ordinary case costs nothing.
|
|
511
|
+
*/
|
|
512
|
+
await this.fs.appendFile(this.incidentsPath, `\n${JSON.stringify(incident)}\n`);
|
|
513
|
+
return incident;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* A PERSON SPENDS AN INCIDENT'S ONE RETRY.
|
|
517
|
+
*
|
|
518
|
+
* 🔴 EXCLUSIVE CREATE, AND NOTHING ELSE. Two Starts arriving together must not
|
|
519
|
+
* both believe they may proceed, and the filesystem is what arbitrates —
|
|
520
|
+
* never a read-then-write, and never an in-memory flag, because the two Starts
|
|
521
|
+
* may be two processes. The same Start asking twice gets the same answer, so
|
|
522
|
+
* a dropped response cannot cost a person their retry.
|
|
523
|
+
*
|
|
524
|
+
* ⚠️ ONE RETRY, NOT "SAFETY OFF". The incident is marked as spent rather than
|
|
525
|
+
* deleted, and the observation history is left exactly where it is. If the
|
|
526
|
+
* authorised Run also achieves nothing, the detectors see the whole history
|
|
527
|
+
* and raise a NEW incident — which is the difference between a person saying
|
|
528
|
+
* "try once more" and a person disabling a brake.
|
|
529
|
+
*/
|
|
530
|
+
async override(incidentId, startId) {
|
|
531
|
+
if (!this.opened)
|
|
532
|
+
throw new Error('LoopSafetyStore.override before open()');
|
|
533
|
+
if (this.state === 'unreadable') {
|
|
534
|
+
return { ok: false, because: 'the safety history could not be read' };
|
|
535
|
+
}
|
|
536
|
+
if (/[^A-Za-z0-9_-]/.test(incidentId)) {
|
|
537
|
+
// Path safety, not validation theatre: the id becomes a filename.
|
|
538
|
+
return { ok: false, because: `${incidentId} is not a usable incident id` };
|
|
539
|
+
}
|
|
540
|
+
const found = this.incidents.find((i) => i.id === incidentId);
|
|
541
|
+
if (found === undefined)
|
|
542
|
+
return { ok: false, because: 'no such safety incident' };
|
|
543
|
+
if (found.overriddenByStartId !== undefined && found.overriddenByStartId !== startId) {
|
|
544
|
+
return { ok: false, because: 'another Start already took this one retry' };
|
|
545
|
+
}
|
|
546
|
+
await this.fs.mkdir(`${this.stateDir}/loop-safety-overrides`);
|
|
547
|
+
try {
|
|
548
|
+
await this.fs.createExclusive(this.overridePath(incidentId), `${JSON.stringify({ incidentId, startId, at: new Date(this.now()).toISOString() }, null, 2)}\n`);
|
|
549
|
+
}
|
|
550
|
+
catch (e) {
|
|
551
|
+
if (e.code !== 'EEXIST')
|
|
552
|
+
throw e;
|
|
553
|
+
// Somebody holds it. The SAME Start asking again is a dropped response,
|
|
554
|
+
// not a rival — anything else is the second Start and must be refused.
|
|
555
|
+
const held = await this.readOverride(incidentId);
|
|
556
|
+
if (held === null) {
|
|
557
|
+
/**
|
|
558
|
+
* FAILS CLOSED, AND SAYS HOW TO GET OUT. We cannot tell whether the one
|
|
559
|
+
* retry was spent, and guessing "not spent" would hand out a second.
|
|
560
|
+
* But refusing without naming a way forward leaves a person stuck at a
|
|
561
|
+
* sentence — amending the Goal starts a new version, which this hold
|
|
562
|
+
* does not cover, and that is the door they cannot see from here.
|
|
563
|
+
*/
|
|
564
|
+
return {
|
|
565
|
+
ok: false,
|
|
566
|
+
because: 'the record of this retry cannot be read, so it cannot be given out again — change the goal to start a fresh version',
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (held !== startId)
|
|
570
|
+
return { ok: false, because: 'another Start already took this one retry' };
|
|
571
|
+
}
|
|
572
|
+
const spent = { ...found, overriddenByStartId: startId };
|
|
573
|
+
this.incidents = this.incidents.map((i) => (i.id === incidentId ? spent : i));
|
|
574
|
+
/**
|
|
575
|
+
* NO DOCUMENT WRITE. The exclusively-created marker above IS the record that
|
|
576
|
+
* this retry was spent, and `foldInSpentRetries` reads it back at every
|
|
577
|
+
* open — so persisting here would add nothing but a second whole-document
|
|
578
|
+
* rewrite racing every concurrent raise.
|
|
579
|
+
*/
|
|
580
|
+
return { ok: true, incident: spent };
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Read the attempt history from the append-only ledger.
|
|
584
|
+
*
|
|
585
|
+
* Bounded IN MEMORY rather than on disk: the file is never rewritten, so the
|
|
586
|
+
* only place a bound can be applied without a rewrite is here. `carried` is
|
|
587
|
+
* whatever an older build left in the document, and goes first because it is
|
|
588
|
+
* older than anything in the ledger.
|
|
589
|
+
*
|
|
590
|
+
* 🔴 A BAD LINE IS SKIPPED, NOT FATAL — and that is the opposite of how this
|
|
591
|
+
* class treats a bad INCIDENTS document, deliberately. A torn final line is
|
|
592
|
+
* what a crash mid-append leaves, and it costs one observation, which is the
|
|
593
|
+
* bounded loss this design already accepts. Refusing to read the history over
|
|
594
|
+
* it would report NO active incident and so discharge every hold on the
|
|
595
|
+
* machine, which is the one direction that must never fail open. The
|
|
596
|
+
* incidents document still fails closed; it is what holds are made of.
|
|
597
|
+
*/
|
|
598
|
+
async loadObservations(carried = []) {
|
|
599
|
+
let text = null;
|
|
600
|
+
try {
|
|
601
|
+
text = await this.fs.readFile(this.eventsPath);
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
text = null;
|
|
605
|
+
}
|
|
606
|
+
const fromLedger = [];
|
|
607
|
+
for (const line of (text ?? '').split('\n')) {
|
|
608
|
+
if (line.trim() === '')
|
|
609
|
+
continue;
|
|
610
|
+
try {
|
|
611
|
+
const parsed = JSON.parse(line);
|
|
612
|
+
if (isObservation(parsed))
|
|
613
|
+
fromLedger.push(parsed);
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// A torn write. See above: one attempt, not the whole history.
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* 🔴 BOUNDED PER PURSUIT, AND A GLOBAL BOUND HERE WAS A REAL DEFECT. The
|
|
621
|
+
* daemon runs up to `MAX_CONTROLLERS_PER_TICK` controllers, so several goals
|
|
622
|
+
* interleave attempts into this one ledger. Keeping the newest sixteen
|
|
623
|
+
* ACROSS ALL OF THEM let a busy pursuit evict a quiet one's history before
|
|
624
|
+
* it ever reached the three barren attempts `no_progress` needs — so the
|
|
625
|
+
* brake switched itself off on the machine doing the most work, silently,
|
|
626
|
+
* with nothing anywhere going red.
|
|
627
|
+
*
|
|
628
|
+
* Per goal, the window means what it says: sixteen attempts of THIS pursuit.
|
|
629
|
+
* Memory stays bounded at sixteen times the number of goals the ledger
|
|
630
|
+
* mentions, which is a machine's real workload rather than its uptime.
|
|
631
|
+
*/
|
|
632
|
+
const byGoal = new Map();
|
|
633
|
+
for (const o of [...carried, ...fromLedger]) {
|
|
634
|
+
const mine = byGoal.get(o.goalId);
|
|
635
|
+
if (mine === undefined)
|
|
636
|
+
byGoal.set(o.goalId, [o]);
|
|
637
|
+
else
|
|
638
|
+
mine.push(o);
|
|
639
|
+
}
|
|
640
|
+
const kept = [];
|
|
641
|
+
for (const mine of byGoal.values()) {
|
|
642
|
+
kept.push(...(mine.length > core_1.MAX_SAFETY_OBSERVATIONS
|
|
643
|
+
? mine.slice(mine.length - core_1.MAX_SAFETY_OBSERVATIONS)
|
|
644
|
+
: mine));
|
|
645
|
+
}
|
|
646
|
+
this.observations = kept;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Mark every incident whose retry has already been taken.
|
|
650
|
+
*
|
|
651
|
+
* 🔴 THE MARKER FILE IS THE AUTHORITY; THE FIELD IS A CACHE. `persist()`
|
|
652
|
+
* rewrites the whole document, so a writer that loaded before another
|
|
653
|
+
* writer's write clobbers it — and the daemon spending a retry while a run
|
|
654
|
+
* records what it achieved is not an exotic interleaving, it is what happens
|
|
655
|
+
* every time somebody presses Start as a run ends. The field really is lost
|
|
656
|
+
* there; it is measured in `loop-safety-race.spec.ts`.
|
|
657
|
+
*
|
|
658
|
+
* What cannot be lost is `loop-safety-overrides/<id>`, because it is written
|
|
659
|
+
* with an exclusive create and never rewritten. So the document is believed
|
|
660
|
+
* about WHAT was raised, and the marker files about WHICH retries are spent.
|
|
661
|
+
* Reading them here keeps `activeIncident` synchronous for its callers.
|
|
662
|
+
*
|
|
663
|
+
* Bounded by the incident count rather than by anything a person can grow: a
|
|
664
|
+
* pursuit has one active incident at a time, and only unspent ones are asked
|
|
665
|
+
* about.
|
|
666
|
+
*/
|
|
667
|
+
/**
|
|
668
|
+
* Fold the append-only incident log over whatever the legacy document held.
|
|
669
|
+
*
|
|
670
|
+
* A malformed line is SKIPPED rather than fatal: an append can be torn by a
|
|
671
|
+
* crash mid-write, and discarding every hold on the strength of one truncated
|
|
672
|
+
* last line would be the fail-open this whole file exists to prevent.
|
|
673
|
+
*/
|
|
674
|
+
async loadIncidents() {
|
|
675
|
+
let text;
|
|
676
|
+
try {
|
|
677
|
+
text = await this.fs.readFile(this.incidentsPath);
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
this.state = 'unreadable';
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (text === null)
|
|
684
|
+
return;
|
|
685
|
+
const known = new Set(this.incidents.map((i) => i.id));
|
|
686
|
+
for (const line of text.split('\n')) {
|
|
687
|
+
if (line.trim() === '')
|
|
688
|
+
continue;
|
|
689
|
+
try {
|
|
690
|
+
const parsed = JSON.parse(line);
|
|
691
|
+
if (!isIncident(parsed))
|
|
692
|
+
continue;
|
|
693
|
+
if (known.has(parsed.id))
|
|
694
|
+
continue;
|
|
695
|
+
known.add(parsed.id);
|
|
696
|
+
this.incidents.push(parsed);
|
|
697
|
+
}
|
|
698
|
+
catch {
|
|
699
|
+
// A torn final line. Everything before it still counts.
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async foldInSpentRetries() {
|
|
704
|
+
const unspent = this.incidents.filter((i) => i.overriddenByStartId === undefined);
|
|
705
|
+
if (unspent.length === 0)
|
|
706
|
+
return;
|
|
707
|
+
const spent = await Promise.all(unspent.map(async (i) => ({ id: i.id, startId: await this.readOverride(i.id) })));
|
|
708
|
+
const byId = new Map(spent.filter((x) => x.startId !== null).map((x) => [x.id, x.startId]));
|
|
709
|
+
if (byId.size === 0)
|
|
710
|
+
return;
|
|
711
|
+
this.incidents = this.incidents.map((i) => {
|
|
712
|
+
const startId = byId.get(i.id);
|
|
713
|
+
return startId === undefined ? i : { ...i, overriddenByStartId: startId };
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
async readOverride(incidentId) {
|
|
717
|
+
let text;
|
|
718
|
+
try {
|
|
719
|
+
text = await this.fs.readFile(this.overridePath(incidentId));
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
if (text === null)
|
|
725
|
+
return null;
|
|
726
|
+
try {
|
|
727
|
+
const parsed = JSON.parse(text);
|
|
728
|
+
return typeof parsed.startId === 'string' ? parsed.startId : null;
|
|
729
|
+
}
|
|
730
|
+
catch {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
async persist() {
|
|
735
|
+
await this.fs.mkdir(this.stateDir);
|
|
736
|
+
/**
|
|
737
|
+
* Temp-then-rename, with a name PRIVATE TO THIS WRITER. A shared
|
|
738
|
+
* `loop-safety.json.tmp` is some other writer's half-written file, and
|
|
739
|
+
* renaming that over the real one is exactly the corruption the rename
|
|
740
|
+
* exists to prevent.
|
|
741
|
+
*/
|
|
742
|
+
const tmp = `${this.path}.${process.pid}.${(LoopSafetyStore.writes += 1)}.tmp`;
|
|
743
|
+
// Incidents ONLY. Observations live in the append-only ledger; writing them
|
|
744
|
+
// here as well would reintroduce the clobber under a second name.
|
|
745
|
+
const body = { incidents: this.incidents };
|
|
746
|
+
await this.fs.writeFile(tmp, `${JSON.stringify(body, null, 2)}\n`);
|
|
747
|
+
await this.fs.rename(tmp, this.path);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
exports.LoopSafetyStore = LoopSafetyStore;
|
|
751
|
+
/**
|
|
752
|
+
* The real filesystem, in the shape this store needs.
|
|
753
|
+
*
|
|
754
|
+
* `createExclusive` is the whole argument for a separate factory: it is the one
|
|
755
|
+
* operation that must be atomic against another PROCESS, and it is spelled with
|
|
756
|
+
* the `wx` flag rather than a read-then-write for exactly that reason.
|
|
757
|
+
*/
|
|
758
|
+
exports.nodeSafetyFs = {
|
|
759
|
+
readFile: async (path) => {
|
|
760
|
+
const { readFile } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
761
|
+
try {
|
|
762
|
+
return await readFile(path, 'utf8');
|
|
763
|
+
}
|
|
764
|
+
catch (e) {
|
|
765
|
+
if (e.code === 'ENOENT')
|
|
766
|
+
return null;
|
|
767
|
+
throw e;
|
|
768
|
+
}
|
|
769
|
+
},
|
|
770
|
+
appendFile: async (path, data) => {
|
|
771
|
+
const { appendFile } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
772
|
+
await appendFile(path, data, 'utf8');
|
|
773
|
+
},
|
|
774
|
+
writeFile: async (path, data) => {
|
|
775
|
+
const { writeFile } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
776
|
+
await writeFile(path, data, 'utf8');
|
|
777
|
+
},
|
|
778
|
+
rename: async (from, to) => {
|
|
779
|
+
const { rename } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
780
|
+
await rename(from, to);
|
|
781
|
+
},
|
|
782
|
+
mkdir: async (path) => {
|
|
783
|
+
const { mkdir } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
784
|
+
await mkdir(path, { recursive: true });
|
|
785
|
+
},
|
|
786
|
+
readDir: async (path) => {
|
|
787
|
+
const { readdir } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
788
|
+
try {
|
|
789
|
+
return await readdir(path);
|
|
790
|
+
}
|
|
791
|
+
catch {
|
|
792
|
+
return [];
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
createExclusive: async (path, data) => {
|
|
796
|
+
const { writeFile } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
797
|
+
await writeFile(path, data, { encoding: 'utf8', flag: 'wx' });
|
|
798
|
+
},
|
|
799
|
+
};
|
|
800
|
+
//# sourceMappingURL=loop-safety-store.js.map
|