toolroll 0.9.93 → 0.9.95
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 +79 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +10 -0
- package/dist/agent-fence.js +6 -3
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +3 -1
- package/dist/assignment.js +5 -0
- package/dist/attest.d.ts +3 -0
- package/dist/attest.js +10 -0
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +32 -32
- package/dist/browser-crew.js +2 -0
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +50 -2
- package/dist/browser-workspace.js +3 -3
- package/dist/build-review.js +2 -0
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +11 -2
- package/dist/builder.js +234 -101
- package/dist/card-catalog.d.ts +209 -0
- package/dist/card-catalog.js +133 -0
- package/dist/card-cli.d.ts +43 -0
- package/dist/card-cli.js +44 -0
- package/dist/card-render.d.ts +26 -0
- package/dist/card-render.js +223 -0
- package/dist/card-sources.d.ts +68 -0
- package/dist/card-sources.js +234 -0
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +106 -9
- package/dist/chat-actions.js +473 -46
- package/dist/chat-approval.d.ts +13 -0
- package/dist/chat-approval.js +39 -9
- package/dist/chat-card-part.d.ts +11 -0
- package/dist/chat-card-part.js +18 -0
- package/dist/chat-card.d.ts +46 -0
- package/dist/chat-card.js +99 -0
- package/dist/chat-channel.d.ts +2 -0
- package/dist/chat-channel.js +37 -14
- package/dist/chat-controls.d.ts +4 -0
- package/dist/chat-controls.js +4 -0
- package/dist/chat-decide.d.ts +10 -16
- package/dist/chat-decide.js +79 -62
- package/dist/chat-delivery.js +13 -4
- package/dist/chat-flow.d.ts +0 -27
- package/dist/chat-flow.js +31 -2
- package/dist/chat-quiet.js +4 -4
- package/dist/chat-task-actions.d.ts +3 -2
- package/dist/chat-task-actions.js +19 -5
- package/dist/chat-terms.d.ts +14 -2
- package/dist/chat-terms.js +35 -0
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +3 -1
- package/dist/check-facts.js +57 -11
- package/dist/check-failure.d.ts +24 -3
- package/dist/check-failure.js +89 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-repair.d.ts +47 -0
- package/dist/check-repair.js +240 -0
- package/dist/check-report.d.ts +2 -0
- package/dist/check-report.js +1 -1
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/claim.d.ts +6 -0
- package/dist/claim.js +13 -2
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +49 -30
- package/dist/cli.js +15 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +3 -1
- package/dist/coding-provider.js +12 -7
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.d.ts +1 -1
- package/dist/coding-workspace.js +8 -8
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +92 -0
- package/dist/contracts/chat-actions.js +20 -0
- package/dist/contracts/chat-card.d.ts +91 -0
- package/dist/contracts/chat-card.js +54 -0
- package/dist/contracts/chat-content.d.ts +191 -0
- package/dist/contracts/chat-content.js +4 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -3
- package/dist/contracts/console-api.js +12 -3
- package/dist/contracts/flow-propose.d.ts +8 -1
- package/dist/contracts/flow-propose.js +3 -1
- package/dist/contracts/flow-send.d.ts +126 -0
- package/dist/contracts/flow-send.js +3 -0
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +234 -0
- package/dist/contracts/lead-tools.js +25 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +18 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/discord-chat.js +14 -0
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +25 -16
- package/dist/dispose.d.ts +19 -0
- package/dist/dispose.js +55 -9
- package/dist/easy-start.js +2 -3
- package/dist/eval-oracle.js +4 -1
- package/dist/evals.js +2 -1
- package/dist/evidence-pair.d.ts +53 -0
- package/dist/evidence-pair.js +128 -0
- package/dist/exhaustion.d.ts +17 -4
- package/dist/exhaustion.js +44 -4
- package/dist/export.js +1 -1
- package/dist/failing-first.d.ts +31 -0
- package/dist/failing-first.js +44 -0
- package/dist/fallback-schema.d.ts +8 -0
- package/dist/fallback-schema.js +40 -0
- package/dist/fallback.d.ts +138 -0
- package/dist/fallback.js +605 -0
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/findings.d.ts +224 -0
- package/dist/findings.js +449 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +75 -9
- package/dist/flaky.js +255 -0
- package/dist/fleet-schema.d.ts +17 -0
- package/dist/fleet-schema.js +135 -0
- package/dist/flow-browser.js +2 -2
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-code.d.ts +2 -2
- package/dist/flow-code.js +9 -5
- package/dist/flow-result-card.d.ts +22 -0
- package/dist/flow-result-card.js +55 -0
- package/dist/flow-send.js +12 -9
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +2 -2
- package/dist/flow-unattended.d.ts +26 -0
- package/dist/flow-unattended.js +12 -3
- package/dist/flows-ui.js +10 -2
- package/dist/follow-through-brief.d.ts +12 -0
- package/dist/follow-through-brief.js +31 -0
- package/dist/follow-through-cli.d.ts +23 -0
- package/dist/follow-through-cli.js +153 -0
- package/dist/git-incident.d.ts +16 -0
- package/dist/git-incident.js +69 -6
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/handover-note.d.ts +33 -0
- package/dist/handover-note.js +106 -0
- package/dist/invoke.d.ts +25 -2
- package/dist/invoke.js +157 -58
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-cli.js +26 -24
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +3 -2
- package/dist/lead-doors.d.ts +4 -0
- package/dist/lead-doors.js +2 -2
- package/dist/lead-progress.js +5 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +162 -34
- package/dist/lead.js +2 -2
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +16 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/modes.d.ts +6 -6
- package/dist/modes.js +3 -1
- package/dist/needs-you.d.ts +31 -9
- package/dist/needs-you.js +110 -41
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +7 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +565 -64
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/plan-login.d.ts +14 -0
- package/dist/plan-login.js +58 -0
- package/dist/planner.d.ts +1 -1
- package/dist/planner.js +4 -2
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +19 -2
- package/dist/project-delete-ui.js +29 -1
- package/dist/project-delete.js +14 -1
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/project-tools.d.ts +4 -4
- package/dist/project-tools.js +23 -7
- package/dist/proof.js +30 -5
- package/dist/protected-storage-look.d.ts +1 -0
- package/dist/protected-storage-look.js +13 -0
- package/dist/protected-storage.d.ts +70 -39
- package/dist/protected-storage.js +196 -147
- package/dist/provider-auth.js +1 -1
- package/dist/provider-capture.d.ts +34 -0
- package/dist/provider-capture.js +53 -0
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +18 -2
- package/dist/push.js +40 -16
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +76 -0
- package/dist/release-auto.js +13 -13
- package/dist/release-card.d.ts +1 -2
- package/dist/release-card.js +11 -0
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +49 -10
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +29 -22
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +20 -1
- package/dist/release.js +85 -9
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +27 -19
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +28 -4
- package/dist/scope.js +92 -28
- package/dist/scout.d.ts +1 -1
- package/dist/scout.js +2 -1
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +26 -18
- package/dist/server/chat.js +16 -14
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +73 -0
- package/dist/server/guards.js +3 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +16 -3
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-follow-through.d.ts +11 -0
- package/dist/server/render-follow-through.js +68 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +2 -0
- package/dist/server/render-release.js +3 -3
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +68 -68
- package/dist/server/render-tasks.d.ts +16 -2
- package/dist/server/render-tasks.js +177 -141
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +48 -12
- package/dist/server/settings.js +166 -34
- package/dist/server/task-views.js +66 -26
- package/dist/server/tasks.d.ts +2 -6
- package/dist/server/tasks.js +250 -321
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.d.ts +1 -0
- package/dist/settings-groups-ui.js +4 -2
- package/dist/settings-kit.js +3 -2
- package/dist/settings-proofs.js +3 -1
- package/dist/slack-chat.js +14 -1
- package/dist/steer-mailbox.d.ts +8 -1
- package/dist/steer-mailbox.js +164 -6
- package/dist/stop-proof.js +6 -4
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +89 -7
- package/dist/store.js +386 -114
- package/dist/surface.js +67 -6
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.d.ts +2 -1
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +17 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +45 -49
- package/dist/teardown-git.d.ts +18 -0
- package/dist/teardown-git.js +105 -21
- package/dist/telegram-lead.js +23 -6
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/telegram.js +12 -1
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update.js +19 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +95 -66
- package/dist/work-summary.d.ts +1 -1
- package/dist/work-summary.js +7 -2
- package/dist/workspace-ui.d.ts +7 -3
- package/dist/workspace-ui.js +38 -23
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +91 -14
- package/package.json +2 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
package/dist/store.js
CHANGED
|
@@ -9,6 +9,10 @@ import { isAcpProfile, isAcpProvider } from "./acp-agents.js";
|
|
|
9
9
|
import { ACP_CONFORMANCE_VERSION, launchFromJson } from "./acp-attest.js";
|
|
10
10
|
import { reclaimDatabase } from "./database-reclaim.js";
|
|
11
11
|
import { BASELINE_SCHEMA, BASELINE_SCHEMA_VERSION } from "./store-baseline.js";
|
|
12
|
+
import { codeMapRemoteChanged, codeMapShapeProblem, forgetCodeMap, V152_ADDED_COLUMNS, V152_SCHEMA } from "./code-map-store.js";
|
|
13
|
+
import { readStamps, stampDigest, stampRefusal, V154_SCHEMA } from "./check-stamp.js";
|
|
14
|
+
import { FINDING_SOURCES, FINDING_SOURCES_BEFORE_LEAD, V155_SCHEMA, V155_STEPS } from "./fleet-schema.js";
|
|
15
|
+
import { V156_SCHEMA, V156_STEPS } from "./fallback-schema.js";
|
|
12
16
|
import { refArchived, taskArchived } from "./task-archive.js";
|
|
13
17
|
export { refArchived, taskArchived } from "./task-archive.js";
|
|
14
18
|
import { CHAT_PROVIDERS, CHAT_SCHEMA } from "./contracts/chat-tables.js";
|
|
@@ -73,16 +77,19 @@ import { ownedProcessCount, runOwnerTag } from "./exec.js";
|
|
|
73
77
|
import { worktreeProcessOccupancy } from "./worktree.js";
|
|
74
78
|
import { processMayBeAlive } from "./process-liveness.js";
|
|
75
79
|
import { currentBootId, provenDeadByBootChange } from "./boot-identity.js";
|
|
80
|
+
import { currentMachineId, isThisMachine, liveHostAliases, rememberHostAliases, thisMachine } from "./machine-identity.js";
|
|
76
81
|
import { containerEmptiness } from "./container-state.js";
|
|
77
82
|
import { hasDisguisedText, hasForbiddenControls, validateNote } from "./decision.js";
|
|
83
|
+
import { ACTIVITY_ONLY_KINDS } from "./needs-you.js";
|
|
78
84
|
import { parseReviewContext, reviewContextCustodyProblem } from "./review-context.js";
|
|
79
85
|
import { foldReview, manualReviewOnly } from "./proof.js";
|
|
80
86
|
import { hashPassword, liveReleaseOf } from "./scope.js";
|
|
81
87
|
import { currentLocalOwner, LOCAL_MODE_CONFINES, LOCAL_OWNER_LAPSED, localModeOn, localOwnerInFlight, localOwnerStandsNow } from "./local-owner.js";
|
|
82
88
|
import { nightlyDebtHold } from "./nightly-github.js";
|
|
83
89
|
import { releaseCiHoldOf, releaseCiRunningOf } from "./release-ci-hold.js";
|
|
84
|
-
import { APPROVAL_BASES, approvalOf, machineActing, personProof, proofDetailOf, quarantinedAuthor, sealOriginOf, digestOf, canonicalProfileJson, chainFromJson, profileDigestOf, profileFromJson, scopeAuthorityOf, routeParityProblem, parseAcceptanceCriteria, exactAcceptance, exactStringList, exactSafeIntegerOrNull } from "./scope.js";
|
|
90
|
+
import { APPROVAL_BASES, approvalOf, machineActing, personProof, proofDetailOf, quarantinedAuthor, sealOriginOf, digestOf, canonicalProfileJson, chainFromJson, liveChainFromJson, CHAIN_DIGEST_VERSION, LIVE_CHAIN_VERSION, profileDigestOf, profileFromJson, scopeAuthorityOf, routeParityProblem, parseAcceptanceCriteria, exactAcceptance, exactStringList, exactSafeIntegerOrNull } from "./scope.js";
|
|
85
91
|
import { resolveScopeProfile, resolveRouteCandidates, exactPinOf, routeOfTask, agentChoicesFor } from "./agentconfig.js";
|
|
92
|
+
import { bindRunToWalk, chainForFiling, chainJsonOf, chainSealProblem, fallbackStampProblem } from "./fallback.js";
|
|
86
93
|
import { canonicalOverridesJson, canonicalRouteJson, isRiskLevel, isTaskSize, headroomFrom, makesNoPlan, legOf, overridesFromJson, phaseOfRole, recommendRoute, routeDigestOf, routeFromJson, routeProblems, routeStampProblem, ROUTE_ERA, PROVIDER_ID_LIST, } from "./phase-routing.js";
|
|
87
94
|
import { readAuthModeStrict } from "./keys.js";
|
|
88
95
|
import { modeDigestOf, modeTermsFromJson } from "./modes.js";
|
|
@@ -92,6 +99,10 @@ import { authenticate as runnerAuthenticate } from "./runner.js";
|
|
|
92
99
|
import { stampNewProjectLevel } from "./check-levels.js";
|
|
93
100
|
import { parseCheckProgressSnapshot } from "./check-progress.js";
|
|
94
101
|
import { authorityStandsSql } from "./approval-authority.js";
|
|
102
|
+
import { clearRetryEpisode, settleVanished } from "./vanished-retry.js";
|
|
103
|
+
import { issueImported } from "./task-source.js";
|
|
104
|
+
import { BUILDER_SERVICE_SCHEMA, CHECK_ONLY_SCHEMA, FAST_PREVIEW_SCHEMA, ROUTE_CHANGE_SCHEMA, V153_ADDED_COLUMNS, VANISHED_SCHEMA } from "./plain-schema.js";
|
|
105
|
+
export { V153_ADDED_COLUMNS } from "./plain-schema.js";
|
|
95
106
|
import { releasePolicyDigest, serializeReleaseCheckPurpose, storedReleaseCheckPurpose, storedReleaseTerms, readReleaseTerms, readReleasePolicy } from "./contracts/release-terms.js";
|
|
96
107
|
import { accountReachesSql, normalizeProjectAccess, projectAccessAllows, readProjectAccess } from "./project-access.js";
|
|
97
108
|
import { LEDGER_COLUMNS, LEDGER_SCHEMA, LEDGER_TABLE, LEDGER_V99_TABLE, installLedgerTriggers } from "./action-ledger.js";
|
|
@@ -107,7 +118,7 @@ import { REQUEST_BUDGET_SCHEMA } from "./request-budget.js";
|
|
|
107
118
|
import { REVIEW_SCHEMA, reviewSwitchWords } from "./review-switch.js";
|
|
108
119
|
import { SPEND_SCHEMA, billingOf, budgetStates, canPrice, claudeMachineBilling, countsToward, filersOf, monthOf, priceWork, seenBilling as seenBillingOf, spendItems, subagentFilers, usd } from "./spend.js";
|
|
109
120
|
import { DEFAULT_PERIODS, RETENTION_SCHEMA, periodWords, widenRetentionSchema } from "./retention.js";
|
|
110
|
-
import { CHECKOUT_CLEANUP_SCHEMA, LANDED_CLEANUP_SCHEMA, STORAGE_SWEEP_SCHEMA, DEFAULT_CLEANUP, cleanupWords } from "./storage.js";
|
|
121
|
+
import { CHECKOUT_CLEANUP_SCHEMA, LANDED_CLEANUP_SCHEMA, STORAGE_SWEEP_SCHEMA, DEFAULT_CLEANUP, DEFAULT_IDLE, V151_ADDED_COLUMNS, cleanupWords, idleWords } from "./storage.js";
|
|
111
122
|
import { inReadOnlyRequest } from "./read-only-request.js";
|
|
112
123
|
import { IN_RANGE, LEDGER_CHAIN_SCHEMA, safeWhole, sealLedger, verifyLedgerChain } from "./ledger-chain.js";
|
|
113
124
|
import { NO_RULES, filerFor, isProtectedWork, protectedChanges, rulesWords } from "./approval-policy.js";
|
|
@@ -116,7 +127,7 @@ import { PULL_REQUEST_SCHEMA } from "./pull-request-schema.js";
|
|
|
116
127
|
import { chatTurnAmountOf, combineReportedModels, providerTurnsOf, reportedCostUsdOf, reportedModelOf, runAmountOf } from "./reported-usage.js";
|
|
117
128
|
import { usageOf } from "./run-efficiency.js";
|
|
118
129
|
import { YOUIYOUX_FILED_VIA } from "./contracts/youiyoux-request.js";
|
|
119
|
-
import { shownTermsDigest } from "./chat-terms.js";
|
|
130
|
+
import { cardShowsScope, cardTermsDigest, shownTermsDigest } from "./chat-terms.js";
|
|
120
131
|
// PR #263 follow-up: the built-ins the Store money seal uses, captured as this module is evaluated, so code that later
|
|
121
132
|
// replaces `Object.defineProperty` and the like can't fool the seal or the lock (lockStoreMoneyMethods refuses when
|
|
122
133
|
// they've been replaced). The four sealed method references are recorded once, at the seal, in SEALED_MONEY_METHODS.
|
|
@@ -1117,7 +1128,21 @@ const REGISTERED_AT_FILING = "again, as it was filed";
|
|
|
1117
1128
|
// v149 adds, and changes no saved row (docs/plans/learning-loops.md): every learning-loops table (V149_SCHEMA, new
|
|
1118
1129
|
// tables only) — each check's recorded conditions and results, mixed-outcome observations, tracked identities, lesson
|
|
1119
1130
|
// notes and their tallies, memory use, tidy-up proposals and decision merges.
|
|
1120
|
-
|
|
1131
|
+
// v150 adds, and changes no saved row: this machine's stable id on each process witness (run_process.machine_id;
|
|
1132
|
+
// saved witnesses read NULL and keep being matched by host name) and the hostnames each machine id was seen under
|
|
1133
|
+
// (machine_host_alias, a new table; machine-identity.ts). V150_SCHEMA, V150_ADDED_COLUMNS.
|
|
1134
|
+
// v151 adds, and changes no saved row: how long a task stays idle before its clean folder goes
|
|
1135
|
+
// (V151_ADDED_COLUMNS: a saved cleanup setting reads NULL, the default).
|
|
1136
|
+
// v152 adds, and changes no saved row (docs/plans/code-map.md): the code map's tables (V152_SCHEMA, code-map-store.ts) and
|
|
1137
|
+
// a project's map switch and enrollment end (V152_ADDED_COLUMNS: a saved project reads on, not enrolled).
|
|
1138
|
+
// v153 adds, and changes no saved row (docs/plans/plain-toolroll.md Phase 2): new tables and columns only.
|
|
1139
|
+
// V153_SCHEMA, V153_ADDED_COLUMNS.
|
|
1140
|
+
// v154 adds, and changes no saved row (docs/plans/trusted-release-reports.md, H2): each check launch's execution stamp
|
|
1141
|
+
// (check_stamp, a new table; check-stamp.ts). A run from before reads no stamp: how its checks ran is unknown.
|
|
1142
|
+
// v155 adds, and changes no saved row (docs/plans/fleet-follow-through.md): findings and their answers, check repairs,
|
|
1143
|
+
// before-and-after evidence and provider captures (V155_SCHEMA, new tables only; fleet-schema.ts).
|
|
1144
|
+
// v156 adds fallback chains (V156_SCHEMA, new tables only; fallback-schema.ts), after v155.
|
|
1145
|
+
export const SCHEMA_VERSION = 156;
|
|
1121
1146
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
1122
1147
|
export const V118_MERGED_TABLES = Object.freeze({ api_token: "person", coordinator_credential: "coordinator", lead_credential: "lead" });
|
|
1123
1148
|
/** The foreign keys that named a merged table, exactly as their DDL wrote them; each names `credential(id)` instead. */
|
|
@@ -1250,7 +1275,7 @@ const V115_DROPPED_REFERENCES = /\s+REFERENCES (?:contestant|contest|tournament_
|
|
|
1250
1275
|
* unchanged), and adds the nullable column in V125_ADDED_COLUMNS, checked the same way. v126 only adds a new table
|
|
1251
1276
|
* (task_source) and the columns in V126_ADDED_COLUMNS (null or 0), checked the same way.
|
|
1252
1277
|
*/
|
|
1253
|
-
export const UPDATE_SAFE_MIGRATIONS = Object.freeze([108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149]);
|
|
1278
|
+
export const UPDATE_SAFE_MIGRATIONS = Object.freeze([108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156]);
|
|
1254
1279
|
/** v119: the columns it adds, by table, with the value every row saved before it reads (toolroll-update's HISTORY_RULES). */
|
|
1255
1280
|
export const V119_ADDED_COLUMNS = Object.freeze({
|
|
1256
1281
|
task_ref: { planning_generation: { definition: "INTEGER NOT NULL DEFAULT 0", saved: "0" } },
|
|
@@ -1876,6 +1901,41 @@ export const V149_TABLES = Object.freeze(["check_result", "check_fact", "local_o
|
|
|
1876
1901
|
export const V149_REPO_TABLES = Object.freeze(["check_fact", "local_observation", "ci_flaky_observation", "learning_watch", "lesson_note", "memory_use", "memory_tidy_proposal"]);
|
|
1877
1902
|
/** The v149 migration's steps after v148's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1878
1903
|
export const V149_STEPS = Object.freeze(["learning-tables"]);
|
|
1904
|
+
/**
|
|
1905
|
+
* v150 (machine-identity.ts): which computer a process witness belongs to, by the machine's stable id rather than its
|
|
1906
|
+
* network hostname (which follows the network on a Mac with no HostName set). machine_host_alias records each
|
|
1907
|
+
* hostname this machine read while it knew its own id, written only by a process on that machine, beside the id it
|
|
1908
|
+
* read; a witness with no id (every saved one) is this machine's when its host is one of them. Never read for any
|
|
1909
|
+
* other machine's id.
|
|
1910
|
+
*/
|
|
1911
|
+
export const V150_SCHEMA = `
|
|
1912
|
+
CREATE TABLE IF NOT EXISTS machine_host_alias (
|
|
1913
|
+
machine_id TEXT NOT NULL CHECK (length(machine_id) = 32),
|
|
1914
|
+
host TEXT NOT NULL CHECK (host <> ''),
|
|
1915
|
+
first_seen_at TEXT NOT NULL,
|
|
1916
|
+
PRIMARY KEY (machine_id, host)
|
|
1917
|
+
);
|
|
1918
|
+
`;
|
|
1919
|
+
/** The tables V150_SCHEMA adds. */
|
|
1920
|
+
export const V150_TABLES = Object.freeze(["machine_host_alias"]);
|
|
1921
|
+
/** v150: a process witness's machine id (saved witnesses read NULL: matched by host name, as before). */
|
|
1922
|
+
export const V150_ADDED_COLUMNS = Object.freeze({
|
|
1923
|
+
run_process: { machine_id: { definition: "TEXT CHECK (machine_id IS NULL OR length(machine_id) = 32)", saved: "NULL" } },
|
|
1924
|
+
});
|
|
1925
|
+
/** The v150 migration's steps after v149's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1926
|
+
export const V150_STEPS = Object.freeze(["machine-aliases", "witness-machine"]);
|
|
1927
|
+
/** The v151 migration's steps after v150's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1928
|
+
export const V151_STEPS = Object.freeze(["checkout-idle"]);
|
|
1929
|
+
/** The v152 migration's steps after v151's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1930
|
+
export const V152_STEPS = Object.freeze(["code-map-tables", "code-map-switch", "code-map-watermark"]);
|
|
1931
|
+
/** v153 (plain-toolroll.md Phase 2): the tables each slice owns. */
|
|
1932
|
+
export const V153_SCHEMA = VANISHED_SCHEMA + CHECK_ONLY_SCHEMA + BUILDER_SERVICE_SCHEMA + FAST_PREVIEW_SCHEMA + ROUTE_CHANGE_SCHEMA;
|
|
1933
|
+
/** The v153 migration's steps after v152's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1934
|
+
export const V153_STEPS = Object.freeze(["plain-tables", "plain-columns"]);
|
|
1935
|
+
/** The v154 migration's steps after v153's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1936
|
+
export const V154_STEPS = Object.freeze(["check-stamps"]);
|
|
1937
|
+
export { V155_STEPS };
|
|
1938
|
+
export { V156_STEPS };
|
|
1879
1939
|
/**
|
|
1880
1940
|
* v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
|
|
1881
1941
|
* release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
|
|
@@ -2382,10 +2442,10 @@ function lifecycleWords(value, cap) {
|
|
|
2382
2442
|
const LIFECYCLE_PHASE_WORDS = {
|
|
2383
2443
|
"agent-running": { subject: "agent working", body: "The agent is working in its own checkout. Nothing is finished yet." },
|
|
2384
2444
|
"validating-handoff": { subject: "checking the handoff", body: "The agent finished its turn; what it handed back is being checked." },
|
|
2385
|
-
"capturing-evidence": { subject: "
|
|
2445
|
+
"capturing-evidence": { subject: "saving its files", body: "The diff, checks and report are being saved." },
|
|
2386
2446
|
"committing": { subject: "committing", body: "The work is being committed on the task branch." },
|
|
2387
|
-
"verifying-proof": { subject: "
|
|
2388
|
-
"correcting-proof": { subject: "correcting the
|
|
2447
|
+
"verifying-proof": { subject: "checking the report", body: "The report is being checked against the approved requirements." },
|
|
2448
|
+
"correcting-proof": { subject: "correcting the report", body: "The report didn't match the saved result; one short correction turn is running." },
|
|
2389
2449
|
};
|
|
2390
2450
|
/** An operator's cancellation as one closed sentence: the reason, if any,
|
|
2391
2451
|
* scrubbed like every phone string and ended with a full stop unless it
|
|
@@ -2398,10 +2458,10 @@ function cancellationWords(reason) {
|
|
|
2398
2458
|
}
|
|
2399
2459
|
/** What an independent review concluded, in the words the saved result shows. */
|
|
2400
2460
|
const LIFECYCLE_REVIEW_WORDS = {
|
|
2401
|
-
verified: { subject: "checks verified", body: "Every
|
|
2402
|
-
refuted: { subject: "
|
|
2403
|
-
attested: { subject: "agent
|
|
2404
|
-
short: { subject: "
|
|
2461
|
+
verified: { subject: "checks verified", body: "Every requirement was checked against the saved result." },
|
|
2462
|
+
refuted: { subject: "the report doesn't match the result", body: "The reviewer found something that contradicts the saved result. It is not accepted as done." },
|
|
2463
|
+
attested: { subject: "only the agent's own checks", body: "The result rests on checks the agent reported, not checked independently." },
|
|
2464
|
+
short: { subject: "a requirement isn't shown", body: "Something a requirement needs is missing from the saved result." },
|
|
2405
2465
|
none: { subject: "comments only", body: "The reviewer left comments; no acceptance judgement was recorded." },
|
|
2406
2466
|
};
|
|
2407
2467
|
/** Why a task still waits after one hold lifts — the other owner, in words. */
|
|
@@ -4797,7 +4857,11 @@ function initializeStore(db, file, options = {}) {
|
|
|
4797
4857
|
}
|
|
4798
4858
|
// A current file is this build's exact shape: the checks above read, the connection gets its settings, and no DDL
|
|
4799
4859
|
// runs. Everything below runs once, on the way up from an older version (a fresh file is at v107 by now).
|
|
4800
|
-
|
|
4860
|
+
// A file from the first v155 builds (finding.source without 'lead') takes the v155 steps again, as a resumed epoch does.
|
|
4861
|
+
if (preflight === SCHEMA_VERSION && !findingSourcesStale(db)) {
|
|
4862
|
+
const missing = codeMapShapeProblem(db);
|
|
4863
|
+
if (missing !== null)
|
|
4864
|
+
throw new Error(`${file}: refusing to open — ${missing}, though the schema says v${SCHEMA_VERSION}`);
|
|
4801
4865
|
db.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;");
|
|
4802
4866
|
return new Store(db);
|
|
4803
4867
|
}
|
|
@@ -5170,6 +5234,46 @@ CREATE INDEX IF NOT EXISTS run_checkpoint_by_task ON run_checkpoint (task_ref, i
|
|
|
5170
5234
|
// v149: the learning-loops tables (new tables only; docs/plans/learning-loops.md). Idempotent, as v148's steps are.
|
|
5171
5235
|
db.exec(V149_SCHEMA);
|
|
5172
5236
|
step("learning-tables");
|
|
5237
|
+
// v150: this machine's stable id on each process witness and the hostnames it was seen under (V150_SCHEMA,
|
|
5238
|
+
// V150_ADDED_COLUMNS: saved witnesses read NULL). Idempotent, as v149's step is.
|
|
5239
|
+
db.exec(V150_SCHEMA);
|
|
5240
|
+
step("machine-aliases");
|
|
5241
|
+
for (const [column, { definition }] of Object.entries(V150_ADDED_COLUMNS["run_process"]))
|
|
5242
|
+
addColumn(db, "run_process", column, definition);
|
|
5243
|
+
step("witness-machine");
|
|
5244
|
+
// v151: the idle limit beside the cleanup setting (V151_ADDED_COLUMNS: a saved row reads NULL). Idempotent, as v150's steps are.
|
|
5245
|
+
for (const [column, { definition }] of Object.entries(V151_ADDED_COLUMNS["checkout_cleanup_setting"]))
|
|
5246
|
+
addColumn(db, "checkout_cleanup_setting", column, definition);
|
|
5247
|
+
step("checkout-idle");
|
|
5248
|
+
// v152: the code map's tables, a project's switch, and the run-id watermark, set once. Idempotent, as v151's step is.
|
|
5249
|
+
db.exec(V152_SCHEMA);
|
|
5250
|
+
step("code-map-tables");
|
|
5251
|
+
for (const [column, { definition }] of Object.entries(V152_ADDED_COLUMNS["project"]))
|
|
5252
|
+
addColumn(db, "project", column, definition);
|
|
5253
|
+
step("code-map-switch");
|
|
5254
|
+
db.exec("INSERT OR IGNORE INTO code_map_meta (id, run_watermark) SELECT 1, COALESCE(MAX(id), 0) FROM run");
|
|
5255
|
+
step("code-map-watermark");
|
|
5256
|
+
// v153: Phase 2's tables (new tables only). Idempotent, as v152's steps are.
|
|
5257
|
+
db.exec(V153_SCHEMA);
|
|
5258
|
+
step("plain-tables");
|
|
5259
|
+
for (const [table, columns] of Object.entries(V153_ADDED_COLUMNS))
|
|
5260
|
+
for (const [column, { definition }] of Object.entries(columns))
|
|
5261
|
+
addColumn(db, table, column, definition);
|
|
5262
|
+
step("plain-columns");
|
|
5263
|
+
// v154: each check launch's execution stamp (a new table). Idempotent, as v153's steps are.
|
|
5264
|
+
db.exec(V154_SCHEMA);
|
|
5265
|
+
step("check-stamps");
|
|
5266
|
+
// v155: what happens to a result after its agent finishes (new tables only). Idempotent, as v154's step is.
|
|
5267
|
+
db.exec(V155_SCHEMA);
|
|
5268
|
+
step("follow-through-tables");
|
|
5269
|
+
// A file from the first v155 builds keeps finding.source's CHECK without 'lead': rebuilt with every row, id and
|
|
5270
|
+
// counter, then its index made again. Idempotent: a widened table is untouched.
|
|
5271
|
+
widenFindingSources(db);
|
|
5272
|
+
db.exec(V155_SCHEMA);
|
|
5273
|
+
step("finding-sources");
|
|
5274
|
+
// v156: fallback chains (new tables only). Idempotent, as v155's steps are.
|
|
5275
|
+
db.exec(V156_SCHEMA);
|
|
5276
|
+
step("fallback-tables");
|
|
5173
5277
|
// Keep the epoch until reclamation succeeds, including a retry after compaction already committed. A new file has
|
|
5174
5278
|
// nothing to reclaim.
|
|
5175
5279
|
if (!fresh)
|
|
@@ -6381,6 +6485,8 @@ export class Store {
|
|
|
6381
6485
|
this.db = instrumentDatabase(trackWorkspaceWrites(db), this.telemetry);
|
|
6382
6486
|
// And on each instance: the sealed methods as its own locked properties, so nothing shadows one later.
|
|
6383
6487
|
lockStoreMoneyMethods(this);
|
|
6488
|
+
// This machine's recorded names (v150), for the custody checks that hold no store (machine-identity.ts).
|
|
6489
|
+
rememberStoredHostAliases(this.db);
|
|
6384
6490
|
}
|
|
6385
6491
|
/** TESTS ONLY: raw database access for migration fixtures. Production
|
|
6386
6492
|
* code never calls this — the typed methods are the API. */
|
|
@@ -6652,7 +6758,7 @@ export class Store {
|
|
|
6652
6758
|
this.noteLifecycle({
|
|
6653
6759
|
taskRef: ref.id, kind: "task-queued", identity: `t${ref.id}`,
|
|
6654
6760
|
subject: "Back in the queue",
|
|
6655
|
-
body: prior["state"] === "running" ? "Its
|
|
6761
|
+
body: prior["state"] === "running" ? "Its builder let it go without finishing; another attempt can start." : "Reopened for another attempt.",
|
|
6656
6762
|
link: chatControlHref("task", id),
|
|
6657
6763
|
}, now);
|
|
6658
6764
|
}
|
|
@@ -7249,8 +7355,9 @@ export class Store {
|
|
|
7249
7355
|
*/
|
|
7250
7356
|
unhold(taskRef, mutation = {}, now) {
|
|
7251
7357
|
return this.once(mutation, "unhold", () => this.transact(() => {
|
|
7358
|
+
// The operator's own hold, and the storage check's (dispose.ts holdForStorage): a person's "Try again".
|
|
7252
7359
|
const { changes } = this.db
|
|
7253
|
-
.prepare("DELETE FROM hold WHERE task_ref = ? AND owner_kind = 'operator'")
|
|
7360
|
+
.prepare("DELETE FROM hold WHERE task_ref = ? AND (owner_kind = 'operator' OR (owner_kind = 'backoff' AND owner_id = 'storage:' || task_ref))")
|
|
7254
7361
|
.run(taskRef);
|
|
7255
7362
|
if (Number(changes) === 0)
|
|
7256
7363
|
return false;
|
|
@@ -7837,9 +7944,22 @@ export class Store {
|
|
|
7837
7944
|
// whatever route it managed to compute (a stated problem is a term
|
|
7838
7945
|
// too); only a row with no route at all keeps the caller's draft
|
|
7839
7946
|
// digest, and it can never be approved.
|
|
7947
|
+
// THE FALLBACK CHAIN (fallback.ts): the project's agents, snapshotted here so the yes covers them. Only the road
|
|
7948
|
+
// that resolved its own agents: an explicit profile (a frozen flow, a pinned filing) and a deploy keep exactly those.
|
|
7949
|
+
let chain = null;
|
|
7950
|
+
if (profile !== null && options.profile === undefined && releaseTerms === null && releaseCheck === null && !scope.candidate && (ref?.deliverable ?? "branch") !== "report") {
|
|
7951
|
+
const baseAuth = readAuthModeStrict(profile.provider);
|
|
7952
|
+
const filing = baseAuth.ok ? chainForFiling(this, ref?.repo ?? null, profile, baseAuth.mode, lowered) : { ok: true, chain: null };
|
|
7953
|
+
if (filing.ok)
|
|
7954
|
+
chain = filing.chain;
|
|
7955
|
+
else {
|
|
7956
|
+
profile = null;
|
|
7957
|
+
unresolvedReason = filing.problem;
|
|
7958
|
+
}
|
|
7959
|
+
}
|
|
7840
7960
|
const digest = profile === null
|
|
7841
7961
|
? route !== null || qualityMode === "strict" ? digestOf(digestInput, null, route) : scope.digest
|
|
7842
|
-
: digestOf(digestInput, profile, route);
|
|
7962
|
+
: digestOf(digestInput, chain === null ? profile : { chain, version: LIVE_CHAIN_VERSION }, route);
|
|
7843
7963
|
// THE AUTH MODE AT FILING (atomic authority closure): the credential
|
|
7844
7964
|
// the resolved profile's provider would spend under is read strictly
|
|
7845
7965
|
// here, on every road — explicit profile or resolved — so a present
|
|
@@ -7851,6 +7971,7 @@ export class Store {
|
|
|
7851
7971
|
const filedAuth = readAuthModeStrict(profile.provider);
|
|
7852
7972
|
if (!filedAuth.ok) {
|
|
7853
7973
|
profile = null;
|
|
7974
|
+
chain = null;
|
|
7854
7975
|
unresolvedReason = filedAuth.problem;
|
|
7855
7976
|
boundDigest = digestOf(digestInput, null, route);
|
|
7856
7977
|
}
|
|
@@ -7873,9 +7994,7 @@ export class Store {
|
|
|
7873
7994
|
acceptance_json = excluded.acceptance_json, quality_mode = excluded.quality_mode,
|
|
7874
7995
|
risk_level = excluded.risk_level, proposed_route_json = excluded.proposed_route_json, route_era = excluded.route_era,
|
|
7875
7996
|
candidate = excluded.candidate, release_terms_json = excluded.release_terms_json, release_check_json = excluded.release_check_json`)
|
|
7876
|
-
.run(scope.taskId, scope.goal, scope.outOfScope, JSON.stringify(scope.touches), scope.budgetMicrousd ?? null, scope.proposedAt, boundDigest, scope.approvedAt, scope.approvedBy, scope.approvedDigest, profile === null ? null : canonicalProfileJson(profile), profile === null ? "unresolved" : "resolved", unresolvedReason, profile === null ? 1 : 2, provenance,
|
|
7877
|
-
// Fallback chains (v30-v114) are gone: no filing names one.
|
|
7878
|
-
null, scope.acceptance.length === 0 ? null : JSON.stringify(scope.acceptance), qualityMode,
|
|
7997
|
+
.run(scope.taskId, scope.goal, scope.outOfScope, JSON.stringify(scope.touches), scope.budgetMicrousd ?? null, scope.proposedAt, boundDigest, scope.approvedAt, scope.approvedBy, scope.approvedDigest, profile === null ? null : canonicalProfileJson(profile), profile === null ? "unresolved" : "resolved", unresolvedReason, profile === null ? 1 : 2, provenance, chain === null ? null : chainJsonOf(chain), scope.acceptance.length === 0 ? null : JSON.stringify(scope.acceptance), qualityMode,
|
|
7879
7998
|
// The declared risk level is gone (v115): every route is filed at
|
|
7880
7999
|
// routine risk, the value its signed terms always carried.
|
|
7881
8000
|
"routine", route === null ? null : canonicalRouteJson(route),
|
|
@@ -7947,11 +8066,17 @@ export class Store {
|
|
|
7947
8066
|
// task — console, CLI `task scope`, and direct callers alike hit
|
|
7948
8067
|
// this wall. The promise to the operator is a fresh password
|
|
7949
8068
|
// ceremony that shows WHO asked; only `basis: password` seals it.
|
|
8069
|
+
let personReadTerms = false;
|
|
7950
8070
|
if (basis !== undefined) {
|
|
8071
|
+
// A PERSON'S YES IN CHAT TO EVERY WORD (owner's decision, docs/plans/toolroll-cards.md): a chat seal carrying the
|
|
8072
|
+
// words its card showed, with the digest kept when the card was made, is a person who read them, as the
|
|
8073
|
+
// ceremony is. Nothing automatic (a mode, a grant, a schedule) ever gets through the walls below.
|
|
8074
|
+
const liveScope = basis.kind === "chat" && basis.card === "shown" ? this.getScope(taskId) : null;
|
|
8075
|
+
personReadTerms = liveScope !== null && typeof basis.shownTerms === "string" && basis.cardDigest === cardTermsDigest(basis.shownTerms) && cardShowsScope(liveScope, basis.shownTerms);
|
|
7951
8076
|
const filedByCoordinator = this.db
|
|
7952
8077
|
.prepare("SELECT 1 AS hit FROM task_ref WHERE backend = ? AND external_id = ? AND coordinator_cid IS NOT NULL")
|
|
7953
8078
|
.get(BUILT_IN, taskId);
|
|
7954
|
-
if (filedByCoordinator !== undefined)
|
|
8079
|
+
if (filedByCoordinator !== undefined && !personReadTerms)
|
|
7955
8080
|
return false;
|
|
7956
8081
|
// THE MATE QUARANTINE (mate arc, ruling 2): a scope a confirmed
|
|
7957
8082
|
// lead proposal wrote is model-authored text; mode coverage never
|
|
@@ -7960,7 +8085,7 @@ export class Store {
|
|
|
7960
8085
|
const writtenByLead = this.db
|
|
7961
8086
|
.prepare("SELECT 1 AS hit FROM task_scope WHERE task_id = ? AND proposed_via IN ('mate','coordinator','scout')")
|
|
7962
8087
|
.get(taskId);
|
|
7963
|
-
if (writtenByLead !== undefined)
|
|
8088
|
+
if (writtenByLead !== undefined && !personReadTerms)
|
|
7964
8089
|
return false;
|
|
7965
8090
|
// THE OUTSIDE-REQUEST QUARANTINE (youiyoux-intake.ts): a task filed from a youiyoux change request is text
|
|
7966
8091
|
// anyone who can write the project's folder wrote, so only a person who read every word of it approves it.
|
|
@@ -7970,12 +8095,18 @@ export class Store {
|
|
|
7970
8095
|
// from the live scope: a card that cut a word, or terms that changed since, seal nothing.
|
|
7971
8096
|
if (basis.kind !== "chat" && this.filedViaOf(taskId) === YOUIYOUX_FILED_VIA)
|
|
7972
8097
|
return false;
|
|
8098
|
+
// THE IMPORT QUARANTINE (github-import.ts): a task filed from a GitHub issue or pull request, or a revision of
|
|
8099
|
+
// one, is outside text too. Its provenance is immutable and copied through revisions, so no automatic basis
|
|
8100
|
+
// ever seals it, here, whatever road it came by.
|
|
8101
|
+
if (basis.kind !== "chat" && issueImported(this, taskId))
|
|
8102
|
+
return false;
|
|
7973
8103
|
// CHECK ACCESS (v132, check-access.ts): a scope whose checks reach beyond the fence (sign-ins, or no sandbox at
|
|
7974
|
-
// all) seals only on a person's
|
|
8104
|
+
// all) seals only on a person's own yes to those terms: the ceremony, or a chat card that showed every word —
|
|
8105
|
+
// never a mode, a grant, a plain chat tap or a carry.
|
|
7975
8106
|
const profileText = this.db.prepare("SELECT profile_json FROM task_scope WHERE task_id = ?").get(taskId)?.["profile_json"];
|
|
7976
8107
|
let sealedAccess;
|
|
7977
8108
|
// SANDBOX WIDENINGS (v133, project-sandbox.ts): extra macOS services seal, likewise,
|
|
7978
|
-
// only on a person's own
|
|
8109
|
+
// only on a person's own yes to exactly them.
|
|
7979
8110
|
let sandboxWidened = false;
|
|
7980
8111
|
try {
|
|
7981
8112
|
const stored = typeof profileText === "string" ? JSON.parse(profileText) : null;
|
|
@@ -7985,7 +8116,7 @@ export class Store {
|
|
|
7985
8116
|
catch {
|
|
7986
8117
|
sealedAccess = "unreadable";
|
|
7987
8118
|
}
|
|
7988
|
-
if (sealedAccess !== undefined || sandboxWidened)
|
|
8119
|
+
if ((sealedAccess !== undefined || sandboxWidened) && !personReadTerms)
|
|
7989
8120
|
return false;
|
|
7990
8121
|
}
|
|
7991
8122
|
// SEPARATION OF DUTIES (v102), enforced in the primitive so no road
|
|
@@ -8048,12 +8179,15 @@ export class Store {
|
|
|
8048
8179
|
if (deployGate && basis?.kind === "chat" && !(basis.card === "release" && typeof basis.cardDigest === "string" && /^[0-9a-f]{64}$/.test(basis.cardDigest)))
|
|
8049
8180
|
return false;
|
|
8050
8181
|
// The outside-request quarantine's chat road (above): the card showed every word of these live terms.
|
|
8051
|
-
if (basis?.kind === "chat" && this.filedViaOf(taskId) === YOUIYOUX_FILED_VIA
|
|
8182
|
+
if (basis?.kind === "chat" && !personReadTerms && (this.filedViaOf(taskId) === YOUIYOUX_FILED_VIA || issueImported(this, taskId))
|
|
8052
8183
|
&& !(basis.card === "terms" && typeof basis.cardDigest === "string" && basis.cardDigest === shownTermsDigest(readScope(working))))
|
|
8053
8184
|
return false;
|
|
8054
8185
|
// AUTOMATIC RELEASE SEALS (auto-approve-green.md): only on a release task (a candidate and release terms signed
|
|
8055
8186
|
// in), only naming the evidence row the plane wrote for exactly this task, kind, digest and candidate.
|
|
8056
8187
|
const automatic = basis?.kind === "auto-check" || basis?.kind === "auto-green";
|
|
8188
|
+
// A FAST START (fast-start.ts) stays a person's: no automatic release seal ever lands on one.
|
|
8189
|
+
if (automatic && this.db.prepare("SELECT 1 AS hit FROM task_ref WHERE backend = ? AND external_id = ? AND fast_start_at IS NOT NULL").get(BUILT_IN, taskId) !== undefined)
|
|
8190
|
+
return false;
|
|
8057
8191
|
if (automatic) {
|
|
8058
8192
|
if (terms.terms === null || typeof working["candidate"] !== "string" || working["candidate"] === "")
|
|
8059
8193
|
return false;
|
|
@@ -8063,18 +8197,23 @@ export class Store {
|
|
|
8063
8197
|
}
|
|
8064
8198
|
if (terms.terms !== null && basis !== undefined && basis.kind !== "chat" && !automatic)
|
|
8065
8199
|
return false;
|
|
8200
|
+
// Nor does a fast start ever anchor a release, whoever approves it.
|
|
8201
|
+
if (terms.terms !== null && this.db.prepare("SELECT 1 AS hit FROM task_ref WHERE backend = ? AND external_id = ? AND fast_start_at IS NOT NULL").get(BUILT_IN, taskId) !== undefined)
|
|
8202
|
+
return false;
|
|
8066
8203
|
if (terms.terms !== null && releaseSealProblem(this, terms.terms) !== null)
|
|
8067
8204
|
return false;
|
|
8205
|
+
// FALLBACK AGENTS (fallback.ts): a paid entry takes a person's own yes.
|
|
8206
|
+
if (chainSealProblem(this, readScope(working), basis) !== null)
|
|
8207
|
+
return false;
|
|
8068
8208
|
const changed = this.db
|
|
8069
8209
|
.prepare(`UPDATE task_scope
|
|
8070
8210
|
SET approved_at = ?, approved_by = ?, approved_digest = digest,
|
|
8071
8211
|
approved_profile_json = profile_json,
|
|
8072
8212
|
approval_basis = ?, mode_digest = ?,
|
|
8073
|
-
--
|
|
8074
|
-
--
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
approval_kind = 'profile',
|
|
8213
|
+
-- The chain seals as the profile does: copied, never
|
|
8214
|
+
-- re-resolved. No chain clears an old snapshot.
|
|
8215
|
+
approved_chain_json = proposed_chain_json,
|
|
8216
|
+
approval_kind = CASE WHEN proposed_chain_json IS NULL THEN 'profile' ELSE 'chain' END,
|
|
8078
8217
|
-- The ROUTE seals exactly as the profile does (v47):
|
|
8079
8218
|
-- copied, never re-resolved.
|
|
8080
8219
|
approved_route_json = proposed_route_json
|
|
@@ -8092,8 +8231,12 @@ export class Store {
|
|
|
8092
8231
|
// (setup review): the approval is what was stale, and it is new.
|
|
8093
8232
|
const ref = this.db.prepare("SELECT id FROM task_ref WHERE backend = ? AND external_id = ?").get(BUILT_IN, taskId);
|
|
8094
8233
|
if (ref !== undefined) {
|
|
8095
|
-
if (basis === undefined || basis.kind === "chat")
|
|
8234
|
+
if (basis === undefined || basis.kind === "chat") {
|
|
8096
8235
|
this.noteTaskAct(Number(ref["id"]), "approved", now);
|
|
8236
|
+
clearRetryEpisode(this, Number(ref["id"]));
|
|
8237
|
+
// An ordinary yes stands on its own: a fast start's window is over (its Accept backstop stays).
|
|
8238
|
+
this.db.prepare("UPDATE task_ref SET fast_start_until = NULL WHERE id = ? AND fast_start_until IS NOT NULL").run(Number(ref["id"]));
|
|
8239
|
+
}
|
|
8097
8240
|
this.db.prepare("DELETE FROM hold WHERE owner_kind = 'backoff' AND owner_id = ?").run(`stale:${Number(ref["id"])}`);
|
|
8098
8241
|
// Results routing: this yes closes the task's open exposure (as proposed, or with the model it sealed),
|
|
8099
8242
|
// and lifts the hold a switched-off rule put on the approval it replaces.
|
|
@@ -8114,20 +8257,20 @@ export class Store {
|
|
|
8114
8257
|
if (alreadyOn !== working["digest"]) {
|
|
8115
8258
|
this.noteLifecycle({
|
|
8116
8259
|
taskRef: Number(ref["id"]), kind: "scope-approved", identity: `t${Number(ref["id"])}`,
|
|
8117
|
-
subject: "
|
|
8260
|
+
subject: "Plan approved",
|
|
8118
8261
|
body: basis === undefined
|
|
8119
|
-
? `Approved by ${lifecycleWords(by, 40)}. A connected
|
|
8262
|
+
? `Approved by ${lifecycleWords(by, 40)}. A connected builder can take it next.`
|
|
8120
8263
|
: basis.kind === "chat"
|
|
8121
|
-
? `Approved by ${lifecycleWords(by, 40)} in chat. A connected
|
|
8264
|
+
? `Approved by ${lifecycleWords(by, 40)} in chat. A connected builder can take it next.`
|
|
8122
8265
|
: basis.kind === "grant"
|
|
8123
|
-
? "Approved automatically: its flow runs on its own. A connected
|
|
8266
|
+
? "Approved automatically: its flow runs on its own. A connected builder can take it next."
|
|
8124
8267
|
: basis.kind === "schedule"
|
|
8125
|
-
? "Approved by its standing order. A connected
|
|
8268
|
+
? "Approved by its standing order. A connected builder can take it next."
|
|
8126
8269
|
: basis.kind === "auto-check"
|
|
8127
8270
|
? "Checking automatically: it approves itself once CI and the release check pass."
|
|
8128
8271
|
: basis.kind === "auto-green"
|
|
8129
8272
|
? "Approved automatically: CI and the release check passed."
|
|
8130
|
-
: "Approved automatically under the operating mode. A connected
|
|
8273
|
+
: "Approved automatically under the operating mode. A connected builder can take it next.",
|
|
8131
8274
|
link: chatControlHref("task", taskId),
|
|
8132
8275
|
}, now);
|
|
8133
8276
|
}
|
|
@@ -8166,7 +8309,7 @@ export class Store {
|
|
|
8166
8309
|
JOIN task_ref ON task_ref.id = ? AND task_ref.backend = 'built-in' AND task_scope.task_id = task_ref.external_id
|
|
8167
8310
|
WHERE task_scope.approved_at IS NOT NULL
|
|
8168
8311
|
AND NOT ${authorityStandsSql("task_scope", "task_ref.repo", "?")}`)
|
|
8169
|
-
.get(taskRef, now.toISOString(), now.toISOString(), now.toISOString());
|
|
8312
|
+
.get(taskRef, now.toISOString(), now.toISOString(), now.toISOString(), now.toISOString());
|
|
8170
8313
|
if (dead !== undefined)
|
|
8171
8314
|
return false;
|
|
8172
8315
|
// A release task (release-once.md): the whole live-release predicate, beyond what the SQL can say.
|
|
@@ -9706,10 +9849,7 @@ export class Store {
|
|
|
9706
9849
|
.get(taskRef);
|
|
9707
9850
|
if (ref === undefined || String(ref["backend"]) !== BUILT_IN)
|
|
9708
9851
|
continue;
|
|
9709
|
-
|
|
9710
|
-
.prepare("UPDATE task SET state = 'queued', updated_at = ? WHERE id = ? AND state = 'running'")
|
|
9711
|
-
.run(now.toISOString(), String(ref["external_id"]));
|
|
9712
|
-
if (Number(changes) > 0)
|
|
9852
|
+
if (settleVanished(this, taskRef, now) === "queued")
|
|
9713
9853
|
requeued?.push(String(ref["external_id"]));
|
|
9714
9854
|
}
|
|
9715
9855
|
return held.map(row => String(row["lease_id"]));
|
|
@@ -10502,7 +10642,7 @@ export class Store {
|
|
|
10502
10642
|
this.noteLifecycle({
|
|
10503
10643
|
taskRef: run.taskRef, run: runId, kind: "review-requested", identity: `r${runId}`,
|
|
10504
10644
|
subject: "Independent review requested",
|
|
10505
|
-
body: `${automatic ? "Queued automatically" : `Requested by ${lifecycleWords(by, 40)}`} for
|
|
10645
|
+
body: `${automatic ? "Queued automatically" : `Requested by ${lifecycleWords(by, 40)}`} for build #${runId}'s saved result. A connected reviewer runs it next.`,
|
|
10506
10646
|
link: chatResultHref(reviewedTask, runId, "checks"),
|
|
10507
10647
|
}, now);
|
|
10508
10648
|
}
|
|
@@ -11806,6 +11946,10 @@ export class Store {
|
|
|
11806
11946
|
for (const incident of incidents)
|
|
11807
11947
|
this.resolveIncidentLocked(incident, by, now);
|
|
11808
11948
|
this.resetStrikes(taskRef);
|
|
11949
|
+
clearRetryEpisode(this, taskRef);
|
|
11950
|
+
// A person's Retry is a new person act: a fast start gets a new start window. A machine's never does.
|
|
11951
|
+
if (!machineActing())
|
|
11952
|
+
this.db.prepare("UPDATE task_ref SET fast_start_until = ? WHERE id = ? AND fast_start_until IS NOT NULL").run(new Date(now.getTime() + 30 * 60_000).toISOString(), taskRef);
|
|
11809
11953
|
this.db
|
|
11810
11954
|
.prepare("UPDATE task SET state = 'queued', updated_at = ? WHERE id = ? AND state IN ('failed','queued','done')")
|
|
11811
11955
|
.run(stamp, taskId);
|
|
@@ -11813,10 +11957,10 @@ export class Store {
|
|
|
11813
11957
|
this.resolveEpisode(`stalled:${taskRef}`, now);
|
|
11814
11958
|
this.noteLifecycle({
|
|
11815
11959
|
taskRef, kind: "task-requeued", identity: `t${taskRef}`,
|
|
11816
|
-
subject: "
|
|
11960
|
+
subject: "Queued again",
|
|
11817
11961
|
body: rejectedRun !== null
|
|
11818
|
-
? `Its last result (
|
|
11819
|
-
:
|
|
11962
|
+
? `Its last result (build #${rejectedRun}) was not accepted; the next attempt continues on the same branch under the current plan.`
|
|
11963
|
+
: `Its repeated failures are cleared; a builder can take it again.`,
|
|
11820
11964
|
link: chatControlHref("task", taskId),
|
|
11821
11965
|
}, now);
|
|
11822
11966
|
this.bumpWake();
|
|
@@ -12612,7 +12756,8 @@ export class Store {
|
|
|
12612
12756
|
* phase's exact leg. A planner may also run under the working
|
|
12613
12757
|
* proposed route, or — with no scope yet — the live recommendation,
|
|
12614
12758
|
* re-derived here from durable state. Nothing else.
|
|
12615
|
-
* fallback —
|
|
12759
|
+
* fallback — the walk's current entry of the sealed chain, exactly
|
|
12760
|
+
* (fallback.ts), and nothing else.
|
|
12616
12761
|
* legacy — only a row proven to predate routing or a task with no
|
|
12617
12762
|
* scope; never a routed row.
|
|
12618
12763
|
*/
|
|
@@ -12669,7 +12814,7 @@ export class Store {
|
|
|
12669
12814
|
return "a task with no scope plans as the word legacy — nothing spends as a routed plan leg on it";
|
|
12670
12815
|
}
|
|
12671
12816
|
if (leg.chosen === "fallback")
|
|
12672
|
-
return
|
|
12817
|
+
return fallbackStampProblem(this, run.taskRef, taskId, leg, run.parentRun ?? null);
|
|
12673
12818
|
// legacy
|
|
12674
12819
|
if (leg.routeDigest !== "legacy" && !leg.routeDigest.startsWith("profile:"))
|
|
12675
12820
|
return `a legacy stamp names a profile digest or the word legacy, not ${leg.routeDigest}`;
|
|
@@ -12760,8 +12905,9 @@ export class Store {
|
|
|
12760
12905
|
return { ok: false, reason: "unreadable", detail: scope.approvedRouteJson == null ? "the approval sealed no agent route — re-file the scope and approve it again" : "the approval's sealed agent route cannot be read — re-file the scope and approve it again" };
|
|
12761
12906
|
}
|
|
12762
12907
|
const fields = { goal: scope.goal, outOfScope: scope.outOfScope, touches: scope.touches, budgetMicrousd: scope.budgetMicrousd, acceptance: scope.acceptance, candidate: scope.candidate ?? null, qualityMode: scope.qualityMode ?? "default", releaseTerms: scope.releaseTerms ?? null, releaseCheck: scope.releaseCheck ?? null };
|
|
12763
|
-
const
|
|
12764
|
-
const
|
|
12908
|
+
const liveChain = scope.approvalKind === "chain" ? liveChainFromJson(scope.approvedChainJson ?? null) : null;
|
|
12909
|
+
const chain = liveChain ?? (scope.approvalKind === "chain" ? chainFromJson(scope.approvedChainJson ?? null) : null);
|
|
12910
|
+
const target = chain !== null ? { chain, version: liveChain !== null ? LIVE_CHAIN_VERSION : CHAIN_DIGEST_VERSION } : (scope.approvedProfile ?? null);
|
|
12765
12911
|
if (target === null)
|
|
12766
12912
|
return { ok: false, reason: "unreadable", detail: "the approval sealed no agent profile — re-file the scope and approve it again" };
|
|
12767
12913
|
if (digestOf(fields, target, route) !== scope.approvedDigest) {
|
|
@@ -13328,18 +13474,18 @@ export class Store {
|
|
|
13328
13474
|
const stopped = result.outcome === "failed" && result.reason === "interrupted" && this.stopOf(id) !== null;
|
|
13329
13475
|
if (role === "builder") {
|
|
13330
13476
|
if (stopped) {
|
|
13331
|
-
this.noteLifecycle({ taskRef, run: id, kind: "run-stopped", identity: `r${id}`, subject: `
|
|
13477
|
+
this.noteLifecycle({ taskRef, run: id, kind: "run-stopped", identity: `r${id}`, subject: `Build #${id} stopped`, body: "Its work is preserved. Resume this attempt from the task page when ready.", link: chatControlHref("recovery", taskId) }, now);
|
|
13332
13478
|
}
|
|
13333
13479
|
else if (result.outcome === "built") {
|
|
13334
13480
|
this.noteLifecycle({
|
|
13335
13481
|
taskRef, run: id, kind: "run-finished", identity: `r${id}`,
|
|
13336
|
-
subject: `
|
|
13482
|
+
subject: `Build #${id} built`,
|
|
13337
13483
|
body: result.committed === false ? "It finished without a commit; the handoff explains why." : "The result is saved locally. It is not yet independently reviewed or published.",
|
|
13338
13484
|
link: chatResultHref(taskId, id, "changes"),
|
|
13339
13485
|
}, now);
|
|
13340
13486
|
}
|
|
13341
13487
|
else if (result.outcome === "no-change") {
|
|
13342
|
-
this.noteLifecycle({ taskRef, run: id, kind: "run-finished", identity: `r${id}`, subject: `
|
|
13488
|
+
this.noteLifecycle({ taskRef, run: id, kind: "run-finished", identity: `r${id}`, subject: `Build #${id} finished with no changes`, body: "Nothing needed to change; the handoff explains why.", link: chatResultHref(taskId, id) }, now);
|
|
13343
13489
|
}
|
|
13344
13490
|
return;
|
|
13345
13491
|
}
|
|
@@ -13575,8 +13721,7 @@ export class Store {
|
|
|
13575
13721
|
// route authority it holds — the sealed leg for its phase, the working
|
|
13576
13722
|
// plan leg before approval, the sealed profile of a pre-routing row, the
|
|
13577
13723
|
// bare word `legacy` on a task with no scope — or no row opens, in words.
|
|
13578
|
-
// `fallback` provenance
|
|
13579
|
-
// nothing.
|
|
13724
|
+
// `fallback` provenance opens only the walk's current entry.
|
|
13580
13725
|
const refuse = problem => {
|
|
13581
13726
|
throw new RunAdmissionRefused(`run admission refused for task_ref ${run.taskRef} (${role}): ${problem}`);
|
|
13582
13727
|
};
|
|
@@ -13598,9 +13743,6 @@ export class Store {
|
|
|
13598
13743
|
refuse("a recovered draft is a builder's");
|
|
13599
13744
|
if (road.road === "repair" && run.parentRun === undefined)
|
|
13600
13745
|
refuse("a repair turn mends exactly one live builder attempt — none was named");
|
|
13601
|
-
if (run.route !== undefined && run.route.chosen === "fallback") {
|
|
13602
|
-
refuse("fallback chains were removed — nothing opens as `fallback`");
|
|
13603
|
-
}
|
|
13604
13746
|
// EVERY run presents (atomic authority closure): a task with no scope
|
|
13605
13747
|
// presents the bare word `legacy` for the pair it spends as; a task
|
|
13606
13748
|
// with a scope presents the leg its authority names. Nothing opens
|
|
@@ -13834,6 +13976,12 @@ export class Store {
|
|
|
13834
13976
|
if (Number(consumed.changes) !== 1)
|
|
13835
13977
|
refuse(`review request #${requestToConsume} was consumed under this admission — nothing reviews without it`);
|
|
13836
13978
|
}
|
|
13979
|
+
// ITS PLACE IN THE WALK (fallback.ts), in this same transaction: a run on a chain approval that can't take one rolls back.
|
|
13980
|
+
{
|
|
13981
|
+
const unbound = bindRunToWalk(this, id, run.now);
|
|
13982
|
+
if (unbound !== null)
|
|
13983
|
+
refuse(unbound);
|
|
13984
|
+
}
|
|
13837
13985
|
// The start fact (Telegram task updates), after every proof above so a
|
|
13838
13986
|
// refused admission says nothing: one per ATTEMPT — a builder, planner
|
|
13839
13987
|
// or scout head, or a root reviewer. A repair turn and a correction
|
|
@@ -13841,12 +13989,12 @@ export class Store {
|
|
|
13841
13989
|
if (road.road !== "repair" && road.road !== "correction" && !(role === "planner" && parent !== null)) {
|
|
13842
13990
|
const agent = lifecycleWords(`${provider ?? "claude"}${model === null ? "" : ` · ${model}`}`, 60);
|
|
13843
13991
|
const words = role === "reviewer"
|
|
13844
|
-
? { subject: `Independent review started (attempt ${reviewAttempt ?? 1})`, body: `Reviewing the saved result of
|
|
13992
|
+
? { subject: `Independent review started (attempt ${reviewAttempt ?? 1})`, body: `Reviewing the saved result of build #${run.parentRun}. Running on ${agent}.`, link: chatResultHref(taskId, run.parentRun, "checks") }
|
|
13845
13993
|
: role === "planner"
|
|
13846
|
-
? { subject:
|
|
13994
|
+
? { subject: "Planning started", body: `Drafting the plan on ${agent}.`, link: chatControlHref("planning", taskId) }
|
|
13847
13995
|
: role === "scout"
|
|
13848
|
-
? { subject:
|
|
13849
|
-
: { subject: road.road === "recovered" ? `
|
|
13996
|
+
? { subject: "Research started", body: `A read-only look on ${agent}; what comes back is a report.`, link: chatControlHref("task", taskId) }
|
|
13997
|
+
: { subject: road.road === "recovered" ? `Build #${id} resumed from its saved draft` : `Build #${id} started`, body: `Building on ${agent}.`, link: chatControlHref("task", taskId) };
|
|
13850
13998
|
this.noteLifecycle({ taskRef: run.taskRef, run: id, kind: "run-started", identity: `r${id}`, ...words }, run.now);
|
|
13851
13999
|
}
|
|
13852
14000
|
return id;
|
|
@@ -14835,7 +14983,7 @@ export class Store {
|
|
|
14835
14983
|
if (taskId === null)
|
|
14836
14984
|
return;
|
|
14837
14985
|
const words = LIFECYCLE_PHASE_WORDS[phase];
|
|
14838
|
-
this.noteLifecycle({ taskRef, run: id, kind: "run-phase", identity: `r${id}`, subject: `
|
|
14986
|
+
this.noteLifecycle({ taskRef, run: id, kind: "run-phase", identity: `r${id}`, subject: `Build #${id}: ${words.subject}`, body: words.body, link: chatControlHref("task", taskId) }, now);
|
|
14839
14987
|
});
|
|
14840
14988
|
}
|
|
14841
14989
|
/** The accepted head and the validated conclusion, once known. */
|
|
@@ -18059,6 +18207,7 @@ export class Store {
|
|
|
18059
18207
|
this.db.prepare(`INSERT INTO action_ledger(at,actor,repo,task_id,run_id,action,outcome,source,detail)
|
|
18060
18208
|
VALUES (?,?,?,NULL,NULL,'project remote recorded','recorded','access',?)`)
|
|
18061
18209
|
.run(now.toISOString(), actor, repo, JSON.stringify({ previousRemote: previous, remoteUrl }));
|
|
18210
|
+
codeMapRemoteChanged(this, repo);
|
|
18062
18211
|
}
|
|
18063
18212
|
/** The remote URL captured on admission or explicitly supplied by the owner; null when none was (or the project was removed). */
|
|
18064
18213
|
projectRemote(path) {
|
|
@@ -18067,7 +18216,10 @@ export class Store {
|
|
|
18067
18216
|
}
|
|
18068
18217
|
/** A project removed from the lists (project-admission.ts): only its row goes; its tasks, results and settings stay. */
|
|
18069
18218
|
forgetProject(path) {
|
|
18070
|
-
this.
|
|
18219
|
+
this.transact(() => {
|
|
18220
|
+
forgetCodeMap(this, path);
|
|
18221
|
+
this.db.prepare("DELETE FROM project WHERE path = ?").run(path);
|
|
18222
|
+
});
|
|
18071
18223
|
}
|
|
18072
18224
|
/** Most recently opened first — the opener page's order. */
|
|
18073
18225
|
listProjects() {
|
|
@@ -18758,10 +18910,7 @@ export class Store {
|
|
|
18758
18910
|
.get(taskRef, BUILT_IN);
|
|
18759
18911
|
if (ref === undefined)
|
|
18760
18912
|
return;
|
|
18761
|
-
|
|
18762
|
-
.prepare("UPDATE task SET state = 'queued', updated_at = ? WHERE id = ? AND state = 'running'")
|
|
18763
|
-
.run(stamp, String(ref["external_id"]));
|
|
18764
|
-
if (Number(changes) > 0)
|
|
18913
|
+
if (settleVanished(this, taskRef, now) === "queued")
|
|
18765
18914
|
requeued.push(String(ref["external_id"]));
|
|
18766
18915
|
};
|
|
18767
18916
|
const open = this.db
|
|
@@ -18847,16 +18996,11 @@ export class Store {
|
|
|
18847
18996
|
this.db
|
|
18848
18997
|
.prepare("UPDATE claim SET released_at = ?, released_by = 'recovered' WHERE lease_id = ?")
|
|
18849
18998
|
.run(stamp, leaseId);
|
|
18850
|
-
this.db
|
|
18851
|
-
.prepare(`UPDATE task SET state = 'queued', updated_at = ?
|
|
18852
|
-
WHERE state = 'running' AND id = (
|
|
18853
|
-
SELECT external_id FROM task_ref WHERE id = ? AND backend = ?
|
|
18854
|
-
)`)
|
|
18855
|
-
.run(stamp, taskRef, BUILT_IN);
|
|
18856
18999
|
this.db
|
|
18857
19000
|
.prepare(`UPDATE run SET outcome = 'failed', reason = 'interrupted', finished_at = ?
|
|
18858
19001
|
WHERE lease_id = ? AND outcome IS NULL`)
|
|
18859
19002
|
.run(stamp, leaseId);
|
|
19003
|
+
settleVanished(this, taskRef, now);
|
|
18860
19004
|
this.db
|
|
18861
19005
|
.prepare(`UPDATE worktree SET released_at = ?, verified = 0
|
|
18862
19006
|
WHERE runner = ? AND task_ref = ? AND released_at IS NULL`)
|
|
@@ -18878,14 +19022,7 @@ export class Store {
|
|
|
18878
19022
|
this.db
|
|
18879
19023
|
.prepare("UPDATE run SET outcome = 'failed', reason = 'interrupted', finished_at = ? WHERE id = ?")
|
|
18880
19024
|
.run(stamp, Number(row["run_id"]));
|
|
18881
|
-
|
|
18882
|
-
this.db
|
|
18883
|
-
.prepare(`UPDATE task SET state = 'queued', updated_at = ?
|
|
18884
|
-
WHERE state = 'running' AND id = (
|
|
18885
|
-
SELECT external_id FROM task_ref WHERE id = ? AND backend = ?
|
|
18886
|
-
)`)
|
|
18887
|
-
.run(stamp, taskRef, BUILT_IN);
|
|
18888
|
-
}
|
|
19025
|
+
settleVanished(this, taskRef, now);
|
|
18889
19026
|
this.db
|
|
18890
19027
|
.prepare("UPDATE worktree SET released_at = ?, verified = 0 WHERE runner = ? AND task_ref = ? AND released_at IS NULL")
|
|
18891
19028
|
.run(stamp, runner, taskRef);
|
|
@@ -19019,7 +19156,7 @@ export class Store {
|
|
|
19019
19156
|
if (stoppedTask !== null) {
|
|
19020
19157
|
this.noteLifecycle(run.role === "reviewer"
|
|
19021
19158
|
? { taskRef: run.taskRef, run: run.id, kind: "run-stopping", identity: `r${run.id}`, subject: `Stopping review #${run.id}`, body: "The review is being stopped. The saved result is unchanged.", link: chatControlHref("recovery", stoppedTask) }
|
|
19022
|
-
: { taskRef: run.taskRef, run: run.id, kind: "run-stopping", identity: `r${run.id}`, subject: `Stopping
|
|
19159
|
+
: { taskRef: run.taskRef, run: run.id, kind: "run-stopping", identity: `r${run.id}`, subject: `Stopping build #${run.id}`, body: "Its processes are being stopped and its work is kept. The task stays paused until this build is resumed.", link: chatControlHref("recovery", stoppedTask) }, now);
|
|
19023
19160
|
}
|
|
19024
19161
|
this.bumpWake();
|
|
19025
19162
|
return { ok: true, stop: this.stopOf(args.runId), repeated: false };
|
|
@@ -19078,6 +19215,7 @@ export class Store {
|
|
|
19078
19215
|
if (ownedProcessCount(runOwnerTag(this, id)) > 0)
|
|
19079
19216
|
return fact(id, "alive", `run #${id} still has an owned subprocess`);
|
|
19080
19217
|
const witnesses = this.db.prepare("SELECT * FROM run_process WHERE run = ? ORDER BY id").all(id);
|
|
19218
|
+
const here = storeMachine(this.db);
|
|
19081
19219
|
let incompleteSpawn = false;
|
|
19082
19220
|
if (run.providerStartedAt !== null && witnesses.length === 0 && !this.processesSummarized(id)) {
|
|
19083
19221
|
return provable(id, `run #${id} may have spawned before its process witness was recorded; exit is unproven`, "no-witness");
|
|
@@ -19087,15 +19225,17 @@ export class Store {
|
|
|
19087
19225
|
// leader. Re-probing its number could mistake reuse for the old group.
|
|
19088
19226
|
if (witness["exited_at"] !== null)
|
|
19089
19227
|
continue;
|
|
19090
|
-
const host = String(witness["host"]);
|
|
19091
19228
|
const bootId = witness["boot_id"] === null || witness["boot_id"] === undefined ? null : String(witness["boot_id"]);
|
|
19092
|
-
//
|
|
19229
|
+
// This machine by its stable id, or by one of its own names for a witness from before v150 (machine-identity.ts):
|
|
19230
|
+
// a hostname that moved with the network never makes this machine's own witness another computer's.
|
|
19231
|
+
const local = witnessIsLocal(witness, here);
|
|
19232
|
+
// A VERIFIED boot change on this machine (v53): nothing of the old
|
|
19093
19233
|
// boot survives — an incomplete spawn, a reused pid and a native
|
|
19094
19234
|
// object alike are gone. Every other case keeps the conservative
|
|
19095
19235
|
// road below.
|
|
19096
|
-
if (provenDeadByBootChange({ host, bootId }))
|
|
19236
|
+
if (local && provenDeadByBootChange({ host: String(witness["host"]), bootId }, { host: String(witness["host"]), bootId: currentBootId() }))
|
|
19097
19237
|
continue;
|
|
19098
|
-
if (
|
|
19238
|
+
if (!local)
|
|
19099
19239
|
return fact(id, "elsewhere", `run #${id}'s process belongs to another host; exit is unproven here`);
|
|
19100
19240
|
const backend = witness["containment"] === null || witness["containment"] === undefined ? null : String(witness["containment"]);
|
|
19101
19241
|
const container = witness["container"] === null || witness["container"] === undefined ? null : String(witness["container"]);
|
|
@@ -19152,8 +19292,9 @@ export class Store {
|
|
|
19152
19292
|
return this.db.prepare("SELECT 1 FROM action_ledger WHERE run_id = ? AND action IN ('workspace settled by approver', ?) LIMIT 1").get(runId, STOP_PROOF_WORKSPACE_FREE) !== undefined;
|
|
19153
19293
|
}
|
|
19154
19294
|
reserveRunProcess(runId, now, group = true) {
|
|
19155
|
-
|
|
19156
|
-
|
|
19295
|
+
recordMachineHostAliases(this.db, now);
|
|
19296
|
+
return Number(this.db.prepare("INSERT INTO run_process (run,host,process_group,observed_at,boot_id,machine_id) VALUES (?,?,?,?,?,?)")
|
|
19297
|
+
.run(runId, hostname(), group ? 1 : 0, now.toISOString(), currentBootId(), currentMachineId()).lastInsertRowid);
|
|
19157
19298
|
}
|
|
19158
19299
|
/** The run's process witnesses not yet proved exited — after first saving whatever exit evidence is available. */
|
|
19159
19300
|
openRunProcessCount(runId, now) {
|
|
@@ -19173,12 +19314,13 @@ export class Store {
|
|
|
19173
19314
|
// check uses: ESRCH, or a process now holding the pid that was born after
|
|
19174
19315
|
// the run ended. An open run has no finish time, so only ESRCH counts.
|
|
19175
19316
|
const finishedAt = this.getRun(runId)?.finishedAt ?? null;
|
|
19317
|
+
const here = storeMachine(this.db);
|
|
19176
19318
|
let recorded = 0;
|
|
19177
19319
|
for (const row of rows) {
|
|
19178
19320
|
const host = String(row["host"]), bootId = row["boot_id"] == null ? null : String(row["boot_id"]);
|
|
19179
|
-
if (
|
|
19321
|
+
if (!witnessIsLocal(row, here) || typeof row["observed_at"] !== "string" || !Number.isFinite(Date.parse(row["observed_at"])) || Date.parse(row["observed_at"]) > now.getTime())
|
|
19180
19322
|
continue;
|
|
19181
|
-
let gone = provenDeadByBootChange({ host, bootId });
|
|
19323
|
+
let gone = provenDeadByBootChange({ host, bootId }, { host, bootId: currentBootId() });
|
|
19182
19324
|
if (!gone) {
|
|
19183
19325
|
const backend = row["containment"] == null ? null : String(row["containment"]);
|
|
19184
19326
|
const container = row["container"] == null ? null : String(row["container"]);
|
|
@@ -19189,11 +19331,12 @@ export class Store {
|
|
|
19189
19331
|
gone = row["pid"] != null && !processMayBeAlive(Number(row["pid"]), row["process_group"] === 1, finishedAt === null ? undefined : { observedAt: row["observed_at"], finishedAt });
|
|
19190
19332
|
}
|
|
19191
19333
|
}
|
|
19334
|
+
const sameMachine = witnessesCarryMachine(this.db);
|
|
19192
19335
|
if (gone)
|
|
19193
19336
|
recorded += Number(this.db.prepare(`UPDATE run_process SET exited_at = ?
|
|
19194
|
-
WHERE id = ? AND run = ? AND exited_at IS NULL AND pid IS ? AND host = ? AND process_group = ?
|
|
19337
|
+
WHERE id = ? AND run = ? AND exited_at IS NULL AND pid IS ? AND host = ?${sameMachine ? " AND machine_id IS ?" : ""} AND process_group = ?
|
|
19195
19338
|
AND observed_at = ? AND boot_id IS ? AND containment IS ? AND container IS ? AND container_identity IS ?`)
|
|
19196
|
-
.run(now.toISOString(), row["id"], runId, row["pid"], row["host"], row["process_group"], row["observed_at"], row["boot_id"], row["containment"], row["container"], row["container_identity"]).changes);
|
|
19339
|
+
.run(now.toISOString(), row["id"], runId, row["pid"], row["host"], ...(sameMachine ? [row["machine_id"] ?? null] : []), row["process_group"], row["observed_at"], row["boot_id"], row["containment"], row["container"], row["container_identity"]).changes);
|
|
19197
19340
|
}
|
|
19198
19341
|
return recorded;
|
|
19199
19342
|
}
|
|
@@ -19203,12 +19346,13 @@ export class Store {
|
|
|
19203
19346
|
* yet stays open and is passed over, never holding newer runs behind it:
|
|
19204
19347
|
* every finished run is visited, a batch at a time. Never signals. */
|
|
19205
19348
|
recordFinishedRunExits(now, batch = 200) {
|
|
19349
|
+
const local = localWitnessSql(this.db, "p");
|
|
19206
19350
|
const page = this.db.prepare(`SELECT DISTINCT p.run FROM run_process p JOIN run r ON r.id = p.run
|
|
19207
|
-
WHERE p.exited_at IS NULL AND
|
|
19351
|
+
WHERE p.exited_at IS NULL AND ${local.sql} AND p.run > ? AND r.outcome IS NOT NULL AND r.finished_at IS NOT NULL
|
|
19208
19352
|
ORDER BY p.run LIMIT ?`);
|
|
19209
19353
|
let recorded = 0;
|
|
19210
19354
|
for (let after = 0;;) {
|
|
19211
|
-
const runs = page.all(
|
|
19355
|
+
const runs = page.all(...local.params, after, batch).map(row => Number(row["run"]));
|
|
19212
19356
|
for (const run of runs)
|
|
19213
19357
|
recorded += this.recordRunProcessExits(run, now);
|
|
19214
19358
|
if (runs.length < batch)
|
|
@@ -19240,10 +19384,11 @@ export class Store {
|
|
|
19240
19384
|
* entry. A sole pid-less witness has no group to prove gone and stays for
|
|
19241
19385
|
* Toolroll's own look (stop-proof.ts). Native witnesses keep their OS-only road. */
|
|
19242
19386
|
settleUnspawnedWitnesses(now, onlyRun) {
|
|
19387
|
+
const local = localWitnessSql(this.db, "p");
|
|
19243
19388
|
const candidates = this.db.prepare(`SELECT DISTINCT p.run FROM run_process p JOIN run r ON r.id = p.run
|
|
19244
19389
|
WHERE p.exited_at IS NULL AND p.pid IS NULL AND p.containment IS NULL AND p.container IS NULL
|
|
19245
|
-
AND
|
|
19246
|
-
AND (? IS NULL OR p.run = ?) ORDER BY p.run`).all(
|
|
19390
|
+
AND ${local.sql} AND r.outcome IS NOT NULL AND r.finished_at IS NOT NULL
|
|
19391
|
+
AND (? IS NULL OR p.run = ?) ORDER BY p.run`).all(...local.params, onlyRun ?? null, onlyRun ?? null);
|
|
19247
19392
|
let settled = 0;
|
|
19248
19393
|
for (const candidate of candidates) {
|
|
19249
19394
|
const runId = Number(candidate["run"]);
|
|
@@ -19278,11 +19423,12 @@ export class Store {
|
|
|
19278
19423
|
settleableUnspawnedWitnesses(run, rows) {
|
|
19279
19424
|
if (run.outcome === null || run.finishedAt === null || ownedProcessCount(runOwnerTag(this, run.id)) > 0)
|
|
19280
19425
|
return [];
|
|
19281
|
-
const
|
|
19426
|
+
const here = storeMachine(this.db);
|
|
19427
|
+
const pidless = rows.filter(row => row["exited_at"] === null && row["pid"] === null && row["containment"] === null && row["container"] === null && witnessIsLocal(row, here));
|
|
19282
19428
|
const others = rows.filter(row => !pidless.includes(row));
|
|
19283
19429
|
if (pidless.length === 0 || others.length === 0 || others.some(row => row["exited_at"] === null))
|
|
19284
19430
|
return [];
|
|
19285
|
-
const known = others.filter(row => row["pid"] !== null && row
|
|
19431
|
+
const known = others.filter(row => row["pid"] !== null && witnessIsLocal(row, here));
|
|
19286
19432
|
if (known.length === 0 || known.some(row => processMayBeAlive(Number(row["pid"]), row["process_group"] === 1, { observedAt: row["observed_at"], finishedAt: run.finishedAt, exitedAt: row["exited_at"] })))
|
|
19287
19433
|
return [];
|
|
19288
19434
|
return pidless;
|
|
@@ -19297,8 +19443,9 @@ export class Store {
|
|
|
19297
19443
|
throw new Error("spawn witness custody changed");
|
|
19298
19444
|
return;
|
|
19299
19445
|
}
|
|
19300
|
-
this.db
|
|
19301
|
-
|
|
19446
|
+
recordMachineHostAliases(this.db, now);
|
|
19447
|
+
this.db.prepare("INSERT INTO run_process (run,pid,host,process_group,observed_at,boot_id,machine_id) VALUES (?,?,?,?,?,?,?)")
|
|
19448
|
+
.run(runId, pid, hostname(), group ? 1 : 0, now.toISOString(), currentBootId(), currentMachineId());
|
|
19302
19449
|
}
|
|
19303
19450
|
/** The native OS object a witnessed spawn ran inside (v53) — recorded
|
|
19304
19451
|
* with the spawn, before the target can execute. */
|
|
@@ -19318,11 +19465,12 @@ export class Store {
|
|
|
19318
19465
|
* must independently prove gone; this never grants authority to signal. */
|
|
19319
19466
|
runCustodyProvenGone(runId) {
|
|
19320
19467
|
const rows = this.db.prepare("SELECT * FROM run_process WHERE run = ?").all(runId);
|
|
19468
|
+
const here = storeMachine(this.db);
|
|
19321
19469
|
// A summarized run's witnesses all exited, each on recorded proof.
|
|
19322
19470
|
return (rows.length > 0 || this.processesSummarized(runId)) && rows.every(row => {
|
|
19323
|
-
if (
|
|
19471
|
+
if (!witnessIsLocal(row, here))
|
|
19324
19472
|
return false;
|
|
19325
|
-
if (provenDeadByBootChange({ host: String(row["host"]), bootId: row["boot_id"] == null ? null : String(row["boot_id"]) }))
|
|
19473
|
+
if (provenDeadByBootChange({ host: String(row["host"]), bootId: row["boot_id"] == null ? null : String(row["boot_id"]) }, { host: String(row["host"]), bootId: currentBootId() }))
|
|
19326
19474
|
return true;
|
|
19327
19475
|
if (row["containment"] == null || row["container"] == null)
|
|
19328
19476
|
return false;
|
|
@@ -19398,7 +19546,7 @@ export class Store {
|
|
|
19398
19546
|
this.addRunNote(args.runId, args.by, "Resumed. The next pass takes a fresh claim, re-proves the signed scope, and inherits this attempt's preserved draft with fresh proof.", now);
|
|
19399
19547
|
const resumedTask = this.externalIdFor(args.taskRef);
|
|
19400
19548
|
if (resumedTask !== null) {
|
|
19401
|
-
this.noteLifecycle({ taskRef: args.taskRef, run: args.runId, kind: "run-resumed", identity: `r${args.runId}`, subject: `
|
|
19549
|
+
this.noteLifecycle({ taskRef: args.taskRef, run: args.runId, kind: "run-resumed", identity: `r${args.runId}`, subject: `Build #${args.runId} resumed`, body: "The next attempt continues from the saved work.", link: chatControlHref("task", resumedTask) }, now);
|
|
19402
19550
|
}
|
|
19403
19551
|
this.bumpWake();
|
|
19404
19552
|
return { ok: true, stop: this.stopOf(args.runId) };
|
|
@@ -20158,12 +20306,14 @@ export class Store {
|
|
|
20158
20306
|
.prepare(`INSERT OR IGNORE INTO push_delivery (notification, subscription, created_at)
|
|
20159
20307
|
SELECT notification.id, push_subscription.id, ?
|
|
20160
20308
|
FROM notification, push_subscription
|
|
20161
|
-
WHERE
|
|
20309
|
+
WHERE notification.push_class IS NOT NULL
|
|
20310
|
+
-- A fact that needs nothing from a person (checks in progress) stays in the activity log: no phone ping.
|
|
20311
|
+
AND notification.kind NOT IN (SELECT value FROM json_each(?))
|
|
20162
20312
|
AND notification.resolved_at IS NULL
|
|
20163
20313
|
AND (notification.recipient IS NULL OR notification.recipient = push_subscription.approver)
|
|
20164
20314
|
AND push_subscription.retired_at IS NULL
|
|
20165
20315
|
AND notification.id > push_subscription.starts_after_notification`)
|
|
20166
|
-
.run(now.toISOString());
|
|
20316
|
+
.run(now.toISOString(), JSON.stringify([...ACTIVITY_ONLY_KINDS]));
|
|
20167
20317
|
// Pings follow responsibility: a pair this person must not be alerted about retires before it is ever sent.
|
|
20168
20318
|
if (Number(changes) > 0) {
|
|
20169
20319
|
const fresh = this.db.prepare(`SELECT p.id AS pair, s.approver AS approver, n.* FROM push_delivery p
|
|
@@ -20490,7 +20640,8 @@ export class Store {
|
|
|
20490
20640
|
return null;
|
|
20491
20641
|
}
|
|
20492
20642
|
}
|
|
20493
|
-
/** The level a run
|
|
20643
|
+
/** The level a later check of this run may run at, at most: missing or unreadable reads as sandboxed, the narrowest.
|
|
20644
|
+
* Never an account of how its checks DID run: that is runCheckStamp (check-stamp.ts). */
|
|
20494
20645
|
runCheckAccess(runId) {
|
|
20495
20646
|
return checkAccessOf(this.db.prepare("SELECT sandbox_access FROM run WHERE id = ?").get(runId)?.["sandbox_access"]);
|
|
20496
20647
|
}
|
|
@@ -21773,6 +21924,11 @@ export function carryReleaseApproval(store, w, now, recheck) {
|
|
|
21773
21924
|
const problem = recheck();
|
|
21774
21925
|
if (problem !== null)
|
|
21775
21926
|
return { ok: false, why: problem };
|
|
21927
|
+
// How the release's check ran, read again with the write: a launch since the preflight counts (H2).
|
|
21928
|
+
const ran = taskCheckStamp(store, a.repo, a.tipTask);
|
|
21929
|
+
const unshown = ran === null ? null : stampRefusal(ran.reading, checkAccessOf(a.accessDigest));
|
|
21930
|
+
if (unshown !== null)
|
|
21931
|
+
return { ok: false, why: `This release's approval can't carry: ${unshown}.` };
|
|
21776
21932
|
if (store.getTask(w.taskId) !== null)
|
|
21777
21933
|
return { ok: false, why: `A task ${w.taskId} already exists.` };
|
|
21778
21934
|
const anchorRow = store.handle.prepare("SELECT * FROM task_scope WHERE task_id = ?").get(a.anchorTask);
|
|
@@ -21808,7 +21964,7 @@ export function carryReleaseApproval(store, w, now, recheck) {
|
|
|
21808
21964
|
store.recordAction({ at: now.toISOString(), actor: "toolroll", repo: a.repo, taskId: w.taskId, runId: null, action: "release carried", outcome: "carried", source: "work",
|
|
21809
21965
|
detail: `release ${a.id} · carry ${carryId} · ${a.tipSha} → ${w.to} · ${w.files} files · ${w.changedLines} lines · request ${w.requestId}` });
|
|
21810
21966
|
// One quiet task notification, no push.
|
|
21811
|
-
store.enqueueNotification({ dedupeKey: `release-carried:${a.id}:${carryId}`, kind: "release-carried", subject: `
|
|
21967
|
+
store.enqueueNotification({ dedupeKey: `release-carried:${a.id}:${carryId}`, kind: "release-carried", subject: `Release now includes ${w.to.slice(0, 7)}`,
|
|
21812
21968
|
body: `Release ${a.version} now ships ${w.to.slice(0, 7)} (${w.files} file${w.files === 1 ? "" : "s"}, ${w.changedLines} line${w.changedLines === 1 ? "" : "s"}) under ${anchor.approvedBy ?? "the"} release approval.`,
|
|
21813
21969
|
link: chatControlHref("task", w.taskId), source: { taskRef: ref.id } }, now);
|
|
21814
21970
|
return { ok: true, taskId: w.taskId, carry: releaseCarryTo(store, a.id, w.to) };
|
|
@@ -22629,7 +22785,8 @@ export function recordReleaseStep(store, entry) {
|
|
|
22629
22785
|
.run(entry.taskId, entry.tag, entry.version, entry.pr, entry.step, entry.state, at, ended ? at : null, detail, at);
|
|
22630
22786
|
}
|
|
22631
22787
|
/** A release's steps, for the release task: the newest tag recorded for it, its steps in the order they ran, and the
|
|
22632
|
-
* step it stopped at when the last thing recorded was a failure
|
|
22788
|
+
* step it stopped at when the last thing recorded was a failure and no build of the task has started since. Null: no
|
|
22789
|
+
* step recorded. */
|
|
22633
22790
|
export function releaseProgressFor(store, taskId) {
|
|
22634
22791
|
const newest = store.handle.prepare("SELECT tag FROM release_step WHERE task_id = ? ORDER BY updated_at DESC, rowid DESC LIMIT 1").get(taskId);
|
|
22635
22792
|
if (newest === undefined)
|
|
@@ -22639,9 +22796,13 @@ export function releaseProgressFor(store, taskId) {
|
|
|
22639
22796
|
const steps = rows.map(row => ({ step: String(row["step"]), state: String(row["state"]), startedAt: String(row["started_at"]),
|
|
22640
22797
|
endedAt: row["ended_at"] === null ? null : String(row["ended_at"]), detail: row["detail"] === null ? null : String(row["detail"]) }));
|
|
22641
22798
|
const last = [...rows].sort((a, b) => String(a["updated_at"]).localeCompare(String(b["updated_at"]))).at(-1);
|
|
22642
|
-
const
|
|
22799
|
+
const failed = String(last["state"]) === "failed";
|
|
22800
|
+
const live = !failed ? undefined : store.handle.prepare(`SELECT id, started_at FROM run WHERE task_ref IN (SELECT value FROM json_each(?)) AND role = 'builder'
|
|
22801
|
+
AND finished_at IS NULL AND outcome IS NULL AND started_at > ? ORDER BY id DESC LIMIT 1`).get(JSON.stringify(taskFamilyRefs(store, taskId).map(one => one.id)), String(last["updated_at"]));
|
|
22802
|
+
const building = live === undefined ? null : { run: Number(live["id"]), startedAt: String(live["started_at"]) };
|
|
22803
|
+
const stopped = failed && building === null ? { step: String(last["step"]), message: last["detail"] === null ? "" : String(last["detail"]), at: String(last["updated_at"]) } : null;
|
|
22643
22804
|
const pr = last["pr"] === null ? null : Number(last["pr"]);
|
|
22644
|
-
return { tag, version: String(last["version"]), pr, startedAt: steps[0].startedAt, updatedAt: String(last["updated_at"]), steps, stopped };
|
|
22805
|
+
return { tag, version: String(last["version"]), pr, startedAt: steps[0].startedAt, updatedAt: String(last["updated_at"]), steps, stopped, building };
|
|
22645
22806
|
}
|
|
22646
22807
|
/** The release task whose steps were recorded most recently (v146), or null when none was. */
|
|
22647
22808
|
export function latestReleaseStepTask(store) {
|
|
@@ -22878,15 +23039,17 @@ export function settleRunStopByObservation(store, args, now) {
|
|
|
22878
23039
|
const at = now.toISOString();
|
|
22879
23040
|
let what;
|
|
22880
23041
|
if (args.cause === "incomplete-spawn") {
|
|
23042
|
+
const local = localWitnessSql(db);
|
|
22881
23043
|
const changed = db.prepare(`UPDATE run_process SET exited_at = ? WHERE run = ? AND exited_at IS NULL AND pid IS NULL
|
|
22882
|
-
AND containment IS NULL AND container IS NULL AND
|
|
23044
|
+
AND containment IS NULL AND container IS NULL AND ${local.sql}`).run(at, args.runId, ...local.params).changes;
|
|
22883
23045
|
if (Number(changed) === 0)
|
|
22884
23046
|
return false;
|
|
22885
23047
|
what = `${Number(changed)} process witness${Number(changed) === 1 ? "" : "es"} with no process ID recorded ended`;
|
|
22886
23048
|
}
|
|
22887
23049
|
else if (args.cause === "no-witness") {
|
|
22888
|
-
db
|
|
22889
|
-
|
|
23050
|
+
recordMachineHostAliases(db, now);
|
|
23051
|
+
db.prepare("INSERT INTO run_process (run,host,process_group,observed_at,boot_id,exited_at,machine_id) VALUES (?,?,0,?,?,?,?)")
|
|
23052
|
+
.run(args.runId, hostname(), at, currentBootId(), at, currentMachineId());
|
|
22890
23053
|
what = "its missing process witness recorded ended";
|
|
22891
23054
|
}
|
|
22892
23055
|
else {
|
|
@@ -22901,3 +23064,112 @@ export function settleRunStopByObservation(store, args, now) {
|
|
|
22901
23064
|
return true;
|
|
22902
23065
|
});
|
|
22903
23066
|
}
|
|
23067
|
+
/**
|
|
23068
|
+
* This machine as the store knows it (v150, machine-identity.ts): its stable id and every name it is known by, the
|
|
23069
|
+
* hostnames recorded beside its own id included. Read fresh each time, so a name recorded by another process on this
|
|
23070
|
+
* machine counts at once.
|
|
23071
|
+
*/
|
|
23072
|
+
export function storeMachine(db) {
|
|
23073
|
+
const id = currentMachineId();
|
|
23074
|
+
let recorded = [];
|
|
23075
|
+
if (id !== null) {
|
|
23076
|
+
try {
|
|
23077
|
+
recorded = db.prepare("SELECT host FROM machine_host_alias WHERE machine_id = ?").all(id).map(row => String(row["host"]));
|
|
23078
|
+
}
|
|
23079
|
+
catch {
|
|
23080
|
+
recorded = [];
|
|
23081
|
+
}
|
|
23082
|
+
rememberHostAliases(id, recorded);
|
|
23083
|
+
}
|
|
23084
|
+
return thisMachine(recorded);
|
|
23085
|
+
}
|
|
23086
|
+
/** Load this machine's recorded names for the checks that hold no store; a store that can't read them loads none. */
|
|
23087
|
+
export function rememberStoredHostAliases(db) {
|
|
23088
|
+
try {
|
|
23089
|
+
storeMachine(db);
|
|
23090
|
+
}
|
|
23091
|
+
catch { /* none recorded, or no table to read: only the live names count */ }
|
|
23092
|
+
}
|
|
23093
|
+
/** Record the names this machine is known by right now beside its own id, read here at this moment (never a name a
|
|
23094
|
+
* witness, a note or another computer supplied). Nothing when this machine's id is unknown. */
|
|
23095
|
+
export function recordMachineHostAliases(db, now) {
|
|
23096
|
+
const id = currentMachineId();
|
|
23097
|
+
if (id === null)
|
|
23098
|
+
return;
|
|
23099
|
+
const names = liveHostAliases();
|
|
23100
|
+
const insert = db.prepare("INSERT OR IGNORE INTO machine_host_alias (machine_id, host, first_seen_at) VALUES (?, ?, ?)");
|
|
23101
|
+
for (const host of names)
|
|
23102
|
+
insert.run(id, host, now.toISOString());
|
|
23103
|
+
rememberHostAliases(id, names);
|
|
23104
|
+
}
|
|
23105
|
+
/** Whether a run_process row is this machine's (machine-identity.ts isThisMachine). */
|
|
23106
|
+
export function witnessIsLocal(row, here) {
|
|
23107
|
+
return isThisMachine({ host: String(row["host"]), machineId: row["machine_id"] == null ? null : String(row["machine_id"]) }, here);
|
|
23108
|
+
}
|
|
23109
|
+
/** The same rule as witnessIsLocal, as SQL over run_process (optionally aliased): its id is ours, or it has none and
|
|
23110
|
+
* its host is one of our names. With our id unknown, `machine_id = NULL` matches nothing: an id-bearing witness is
|
|
23111
|
+
* never taken for ours. */
|
|
23112
|
+
export function localWitnessSql(db, alias) {
|
|
23113
|
+
const p = alias === undefined ? "" : `${alias}.`, here = storeMachine(db);
|
|
23114
|
+
if (!witnessesCarryMachine(db))
|
|
23115
|
+
return { sql: `${p}host IN (SELECT value FROM json_each(?))`, params: [JSON.stringify(here.hosts)] };
|
|
23116
|
+
return { sql: `(${p}machine_id = ? OR (${p}machine_id IS NULL AND ${p}host IN (SELECT value FROM json_each(?))))`, params: [here.id, JSON.stringify(here.hosts)] };
|
|
23117
|
+
}
|
|
23118
|
+
/** Whether this database has v150's run_process.machine_id. An update settles leftover records on the database it is
|
|
23119
|
+
* about to back up, still at the older schema (toolroll-update.ts drain), so no query it reaches may assume the column:
|
|
23120
|
+
* there every witness has no id, and the host-name rule above is the whole rule. */
|
|
23121
|
+
export function witnessesCarryMachine(db) {
|
|
23122
|
+
return db.prepare("SELECT 1 FROM pragma_table_info('run_process') WHERE name = 'machine_id'").get() !== undefined;
|
|
23123
|
+
}
|
|
23124
|
+
// ---- Idle cleanup (checkout-cleanup.ts) ----------------------------------------------------------------------------
|
|
23125
|
+
/** Settings → Storage: how long a task stays idle before its clean folder goes (the default until someone chooses). */
|
|
23126
|
+
export function checkoutIdle(store) {
|
|
23127
|
+
const row = store.handle.prepare("SELECT idle FROM checkout_cleanup_setting WHERE id = 1").get();
|
|
23128
|
+
return row === undefined || row["idle"] === null ? DEFAULT_IDLE : String(row["idle"]);
|
|
23129
|
+
}
|
|
23130
|
+
/** Choose how long a task stays idle before its clean folder goes; the ledger keeps before → after. */
|
|
23131
|
+
export function setCheckoutIdle(store, idle, by, now) {
|
|
23132
|
+
return store.transact(() => {
|
|
23133
|
+
const before = checkoutIdle(store);
|
|
23134
|
+
store.handle.prepare("INSERT INTO checkout_cleanup_setting (id, cleanup, idle, updated_by, updated_at) VALUES (1, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET idle = excluded.idle, updated_by = excluded.updated_by, updated_at = excluded.updated_at")
|
|
23135
|
+
.run(store.checkoutCleanup(), idle, by, now.toISOString());
|
|
23136
|
+
if (before !== idle)
|
|
23137
|
+
store.recordAction({ at: now.toISOString(), actor: by, repo: null, taskId: null, runId: null, action: "idle cleanup changed", outcome: "changed", source: "policy", detail: `${idleWords(before)} → ${idleWords(idle)}` });
|
|
23138
|
+
return checkoutIdle(store);
|
|
23139
|
+
});
|
|
23140
|
+
}
|
|
23141
|
+
/** A check launch's execution stamp (check-stamp.ts), written by the code that launched it. One row per distinct launch. */
|
|
23142
|
+
export function recordCheckStamp(store, runId, stamp, now) {
|
|
23143
|
+
store.handle.prepare("INSERT OR IGNORE INTO check_stamp (run_id, digest, stamp, at) VALUES (?, ?, ?, ?)").run(runId, stampDigest(stamp), JSON.stringify(stamp), now.toISOString());
|
|
23144
|
+
}
|
|
23145
|
+
/** How a run's checks actually ran, by every launch it stamped: `unknown` when none is there or one can't be read. */
|
|
23146
|
+
export function runCheckStamp(store, runId) {
|
|
23147
|
+
return readStamps(store.handle.prepare("SELECT stamp FROM check_stamp WHERE run_id = ? ORDER BY id").all(runId).map(row => row["stamp"]));
|
|
23148
|
+
}
|
|
23149
|
+
/** The newest run of a task whose checks ran (it saved a check log), and how they ran; null when none has. */
|
|
23150
|
+
export function taskCheckStamp(store, repo, taskId) {
|
|
23151
|
+
const runs = store.handle.prepare(`SELECT r.id FROM run r JOIN task_ref t ON t.id = r.task_ref WHERE t.repo = ? AND t.external_id = ? ORDER BY r.id DESC`).all(repo, taskId);
|
|
23152
|
+
for (const row of runs) {
|
|
23153
|
+
const runId = Number(row["id"]);
|
|
23154
|
+
if (store.artifactsFor(runId).some(one => one.kind === "check-log"))
|
|
23155
|
+
return { runId, reading: runCheckStamp(store, runId) };
|
|
23156
|
+
}
|
|
23157
|
+
return null;
|
|
23158
|
+
}
|
|
23159
|
+
/** Whether this file's finding table is the first v155 builds' shape: its source CHECK without 'lead'. */
|
|
23160
|
+
function findingSourcesStale(db) {
|
|
23161
|
+
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'finding'").get();
|
|
23162
|
+
return row !== undefined && String(row["sql"]).includes(FINDING_SOURCES_BEFORE_LEAD);
|
|
23163
|
+
}
|
|
23164
|
+
/** Widen finding.source to admit 'lead' (rebuildExact: every row and id, the AUTOINCREMENT counter, what refers to it,
|
|
23165
|
+
* in one transaction). Only the exact earlier shape is rebuilt; a widened table is untouched. */
|
|
23166
|
+
function widenFindingSources(db) {
|
|
23167
|
+
if (!findingSourcesStale(db))
|
|
23168
|
+
return;
|
|
23169
|
+
const stored = String(db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'finding'").get()["sql"]);
|
|
23170
|
+
if (stored.split(FINDING_SOURCES_BEFORE_LEAD).length !== 2)
|
|
23171
|
+
throw new Error("the finding table's DDL is not a shape this migration knows — refusing to widen it");
|
|
23172
|
+
const named = (sql) => (name) => sql.replace(/^CREATE TABLE (?:IF NOT EXISTS )?"?finding"?/, `CREATE TABLE ${name}`);
|
|
23173
|
+
const columns = db.prepare("PRAGMA table_info(finding)").all().map(one => String(one["name"]));
|
|
23174
|
+
rebuildExact(db, "finding", named(stored), named(stored.replace(FINDING_SOURCES_BEFORE_LEAD, FINDING_SOURCES)), columns);
|
|
23175
|
+
}
|