toolroll 0.9.93 → 0.9.94
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 +68 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +4 -0
- package/dist/agent-fence.js +4 -1
- 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-ui.js +1 -1
- package/dist/assignment.js +5 -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-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +45 -2
- package/dist/browser-workspace.js +3 -3
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +53 -24
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +12 -2
- package/dist/chat-actions.js +25 -2
- package/dist/chat-channel.js +11 -0
- package/dist/chat-decide.d.ts +7 -2
- package/dist/chat-decide.js +37 -9
- package/dist/chat-quiet.js +3 -3
- 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 +22 -3
- package/dist/check-failure.js +57 -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-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/cli-help.d.ts +4 -1
- package/dist/cli-help.js +47 -30
- package/dist/cli.js +11 -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 +2 -0
- package/dist/coding-provider.js +3 -2
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.js +4 -4
- 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 +9 -0
- package/dist/contracts/chat-actions.js +2 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +10 -3
- package/dist/contracts/console-api.js +10 -3
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +61 -0
- package/dist/contracts/lead-tools.js +10 -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 +2 -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/dispatch.js +18 -15
- package/dist/dispose.js +8 -2
- package/dist/easy-start.js +2 -3
- package/dist/evals.js +2 -1
- 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/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/flow-card-task.js +3 -0
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/invoke.d.ts +5 -1
- package/dist/invoke.js +49 -14
- 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-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-progress.js +3 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +51 -2
- package/dist/lead.js +1 -1
- 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 +11 -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/needs-you.d.ts +27 -8
- package/dist/needs-you.js +96 -39
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +6 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +402 -52
- 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/planner.js +2 -1
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +7 -2
- package/dist/project-delete-ui.js +12 -1
- package/dist/project-delete.js +8 -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/proof.js +30 -5
- package/dist/provider-auth.js +1 -1
- 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 +39 -9
- 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 +76 -8
- 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 +6 -1
- package/dist/scope.js +22 -9
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +24 -18
- package/dist/server/chat.js +14 -12
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +23 -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 +2 -2
- 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-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 +67 -67
- package/dist/server/render-tasks.d.ts +13 -2
- package/dist/server/render-tasks.js +153 -136
- 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 +45 -12
- package/dist/server/settings.js +129 -22
- package/dist/server/task-views.js +61 -26
- package/dist/server/tasks.d.ts +2 -6
- package/dist/server/tasks.js +174 -321
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.js +1 -1
- package/dist/setup-guide.d.ts +2 -2
- 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 +80 -5
- package/dist/store.js +296 -87
- package/dist/surface.js +45 -4
- 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.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 +40 -49
- package/dist/teardown-git.d.ts +16 -0
- package/dist/teardown-git.js +63 -5
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- 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 +17 -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 +76 -62
- package/dist/work-summary.js +2 -0
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +28 -22
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +68 -13
- package/package.json +1 -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,8 @@ 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";
|
|
12
14
|
import { refArchived, taskArchived } from "./task-archive.js";
|
|
13
15
|
export { refArchived, taskArchived } from "./task-archive.js";
|
|
14
16
|
import { CHAT_PROVIDERS, CHAT_SCHEMA } from "./contracts/chat-tables.js";
|
|
@@ -73,8 +75,10 @@ import { ownedProcessCount, runOwnerTag } from "./exec.js";
|
|
|
73
75
|
import { worktreeProcessOccupancy } from "./worktree.js";
|
|
74
76
|
import { processMayBeAlive } from "./process-liveness.js";
|
|
75
77
|
import { currentBootId, provenDeadByBootChange } from "./boot-identity.js";
|
|
78
|
+
import { currentMachineId, isThisMachine, liveHostAliases, rememberHostAliases, thisMachine } from "./machine-identity.js";
|
|
76
79
|
import { containerEmptiness } from "./container-state.js";
|
|
77
80
|
import { hasDisguisedText, hasForbiddenControls, validateNote } from "./decision.js";
|
|
81
|
+
import { ACTIVITY_ONLY_KINDS } from "./needs-you.js";
|
|
78
82
|
import { parseReviewContext, reviewContextCustodyProblem } from "./review-context.js";
|
|
79
83
|
import { foldReview, manualReviewOnly } from "./proof.js";
|
|
80
84
|
import { hashPassword, liveReleaseOf } from "./scope.js";
|
|
@@ -92,6 +96,10 @@ import { authenticate as runnerAuthenticate } from "./runner.js";
|
|
|
92
96
|
import { stampNewProjectLevel } from "./check-levels.js";
|
|
93
97
|
import { parseCheckProgressSnapshot } from "./check-progress.js";
|
|
94
98
|
import { authorityStandsSql } from "./approval-authority.js";
|
|
99
|
+
import { clearRetryEpisode, settleVanished } from "./vanished-retry.js";
|
|
100
|
+
import { issueImported } from "./task-source.js";
|
|
101
|
+
import { BUILDER_SERVICE_SCHEMA, CHECK_ONLY_SCHEMA, FAST_PREVIEW_SCHEMA, ROUTE_CHANGE_SCHEMA, V153_ADDED_COLUMNS, VANISHED_SCHEMA } from "./plain-schema.js";
|
|
102
|
+
export { V153_ADDED_COLUMNS } from "./plain-schema.js";
|
|
95
103
|
import { releasePolicyDigest, serializeReleaseCheckPurpose, storedReleaseCheckPurpose, storedReleaseTerms, readReleaseTerms, readReleasePolicy } from "./contracts/release-terms.js";
|
|
96
104
|
import { accountReachesSql, normalizeProjectAccess, projectAccessAllows, readProjectAccess } from "./project-access.js";
|
|
97
105
|
import { LEDGER_COLUMNS, LEDGER_SCHEMA, LEDGER_TABLE, LEDGER_V99_TABLE, installLedgerTriggers } from "./action-ledger.js";
|
|
@@ -107,7 +115,7 @@ import { REQUEST_BUDGET_SCHEMA } from "./request-budget.js";
|
|
|
107
115
|
import { REVIEW_SCHEMA, reviewSwitchWords } from "./review-switch.js";
|
|
108
116
|
import { SPEND_SCHEMA, billingOf, budgetStates, canPrice, claudeMachineBilling, countsToward, filersOf, monthOf, priceWork, seenBilling as seenBillingOf, spendItems, subagentFilers, usd } from "./spend.js";
|
|
109
117
|
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";
|
|
118
|
+
import { CHECKOUT_CLEANUP_SCHEMA, LANDED_CLEANUP_SCHEMA, STORAGE_SWEEP_SCHEMA, DEFAULT_CLEANUP, DEFAULT_IDLE, V151_ADDED_COLUMNS, cleanupWords, idleWords } from "./storage.js";
|
|
111
119
|
import { inReadOnlyRequest } from "./read-only-request.js";
|
|
112
120
|
import { IN_RANGE, LEDGER_CHAIN_SCHEMA, safeWhole, sealLedger, verifyLedgerChain } from "./ledger-chain.js";
|
|
113
121
|
import { NO_RULES, filerFor, isProtectedWork, protectedChanges, rulesWords } from "./approval-policy.js";
|
|
@@ -1117,7 +1125,18 @@ const REGISTERED_AT_FILING = "again, as it was filed";
|
|
|
1117
1125
|
// v149 adds, and changes no saved row (docs/plans/learning-loops.md): every learning-loops table (V149_SCHEMA, new
|
|
1118
1126
|
// tables only) — each check's recorded conditions and results, mixed-outcome observations, tracked identities, lesson
|
|
1119
1127
|
// notes and their tallies, memory use, tidy-up proposals and decision merges.
|
|
1120
|
-
|
|
1128
|
+
// v150 adds, and changes no saved row: this machine's stable id on each process witness (run_process.machine_id;
|
|
1129
|
+
// saved witnesses read NULL and keep being matched by host name) and the hostnames each machine id was seen under
|
|
1130
|
+
// (machine_host_alias, a new table; machine-identity.ts). V150_SCHEMA, V150_ADDED_COLUMNS.
|
|
1131
|
+
// v151 adds, and changes no saved row: how long a task stays idle before its clean folder goes
|
|
1132
|
+
// (V151_ADDED_COLUMNS: a saved cleanup setting reads NULL, the default).
|
|
1133
|
+
// v152 adds, and changes no saved row (docs/plans/code-map.md): the code map's tables (V152_SCHEMA, code-map-store.ts) and
|
|
1134
|
+
// a project's map switch and enrollment end (V152_ADDED_COLUMNS: a saved project reads on, not enrolled).
|
|
1135
|
+
// v153 adds, and changes no saved row (docs/plans/plain-toolroll.md Phase 2): new tables and columns only.
|
|
1136
|
+
// V153_SCHEMA, V153_ADDED_COLUMNS.
|
|
1137
|
+
// v154 adds, and changes no saved row (docs/plans/trusted-release-reports.md, H2): each check launch's execution stamp
|
|
1138
|
+
// (check_stamp, a new table; check-stamp.ts). A run from before reads no stamp: how its checks ran is unknown.
|
|
1139
|
+
export const SCHEMA_VERSION = 154;
|
|
1121
1140
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
1122
1141
|
export const V118_MERGED_TABLES = Object.freeze({ api_token: "person", coordinator_credential: "coordinator", lead_credential: "lead" });
|
|
1123
1142
|
/** The foreign keys that named a merged table, exactly as their DDL wrote them; each names `credential(id)` instead. */
|
|
@@ -1250,7 +1269,7 @@ const V115_DROPPED_REFERENCES = /\s+REFERENCES (?:contestant|contest|tournament_
|
|
|
1250
1269
|
* unchanged), and adds the nullable column in V125_ADDED_COLUMNS, checked the same way. v126 only adds a new table
|
|
1251
1270
|
* (task_source) and the columns in V126_ADDED_COLUMNS (null or 0), checked the same way.
|
|
1252
1271
|
*/
|
|
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]);
|
|
1272
|
+
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]);
|
|
1254
1273
|
/** v119: the columns it adds, by table, with the value every row saved before it reads (toolroll-update's HISTORY_RULES). */
|
|
1255
1274
|
export const V119_ADDED_COLUMNS = Object.freeze({
|
|
1256
1275
|
task_ref: { planning_generation: { definition: "INTEGER NOT NULL DEFAULT 0", saved: "0" } },
|
|
@@ -1876,6 +1895,39 @@ export const V149_TABLES = Object.freeze(["check_result", "check_fact", "local_o
|
|
|
1876
1895
|
export const V149_REPO_TABLES = Object.freeze(["check_fact", "local_observation", "ci_flaky_observation", "learning_watch", "lesson_note", "memory_use", "memory_tidy_proposal"]);
|
|
1877
1896
|
/** The v149 migration's steps after v148's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1878
1897
|
export const V149_STEPS = Object.freeze(["learning-tables"]);
|
|
1898
|
+
/**
|
|
1899
|
+
* v150 (machine-identity.ts): which computer a process witness belongs to, by the machine's stable id rather than its
|
|
1900
|
+
* network hostname (which follows the network on a Mac with no HostName set). machine_host_alias records each
|
|
1901
|
+
* hostname this machine read while it knew its own id, written only by a process on that machine, beside the id it
|
|
1902
|
+
* read; a witness with no id (every saved one) is this machine's when its host is one of them. Never read for any
|
|
1903
|
+
* other machine's id.
|
|
1904
|
+
*/
|
|
1905
|
+
export const V150_SCHEMA = `
|
|
1906
|
+
CREATE TABLE IF NOT EXISTS machine_host_alias (
|
|
1907
|
+
machine_id TEXT NOT NULL CHECK (length(machine_id) = 32),
|
|
1908
|
+
host TEXT NOT NULL CHECK (host <> ''),
|
|
1909
|
+
first_seen_at TEXT NOT NULL,
|
|
1910
|
+
PRIMARY KEY (machine_id, host)
|
|
1911
|
+
);
|
|
1912
|
+
`;
|
|
1913
|
+
/** The tables V150_SCHEMA adds. */
|
|
1914
|
+
export const V150_TABLES = Object.freeze(["machine_host_alias"]);
|
|
1915
|
+
/** v150: a process witness's machine id (saved witnesses read NULL: matched by host name, as before). */
|
|
1916
|
+
export const V150_ADDED_COLUMNS = Object.freeze({
|
|
1917
|
+
run_process: { machine_id: { definition: "TEXT CHECK (machine_id IS NULL OR length(machine_id) = 32)", saved: "NULL" } },
|
|
1918
|
+
});
|
|
1919
|
+
/** The v150 migration's steps after v149's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1920
|
+
export const V150_STEPS = Object.freeze(["machine-aliases", "witness-machine"]);
|
|
1921
|
+
/** The v151 migration's steps after v150's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1922
|
+
export const V151_STEPS = Object.freeze(["checkout-idle"]);
|
|
1923
|
+
/** The v152 migration's steps after v151's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1924
|
+
export const V152_STEPS = Object.freeze(["code-map-tables", "code-map-switch", "code-map-watermark"]);
|
|
1925
|
+
/** v153 (plain-toolroll.md Phase 2): the tables each slice owns. */
|
|
1926
|
+
export const V153_SCHEMA = VANISHED_SCHEMA + CHECK_ONLY_SCHEMA + BUILDER_SERVICE_SCHEMA + FAST_PREVIEW_SCHEMA + ROUTE_CHANGE_SCHEMA;
|
|
1927
|
+
/** The v153 migration's steps after v152's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1928
|
+
export const V153_STEPS = Object.freeze(["plain-tables", "plain-columns"]);
|
|
1929
|
+
/** The v154 migration's steps after v153's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1930
|
+
export const V154_STEPS = Object.freeze(["check-stamps"]);
|
|
1879
1931
|
/**
|
|
1880
1932
|
* v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
|
|
1881
1933
|
* release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
|
|
@@ -2382,10 +2434,10 @@ function lifecycleWords(value, cap) {
|
|
|
2382
2434
|
const LIFECYCLE_PHASE_WORDS = {
|
|
2383
2435
|
"agent-running": { subject: "agent working", body: "The agent is working in its own checkout. Nothing is finished yet." },
|
|
2384
2436
|
"validating-handoff": { subject: "checking the handoff", body: "The agent finished its turn; what it handed back is being checked." },
|
|
2385
|
-
"capturing-evidence": { subject: "
|
|
2437
|
+
"capturing-evidence": { subject: "saving its files", body: "The diff, checks and report are being saved." },
|
|
2386
2438
|
"committing": { subject: "committing", body: "The work is being committed on the task branch." },
|
|
2387
|
-
"verifying-proof": { subject: "
|
|
2388
|
-
"correcting-proof": { subject: "correcting the
|
|
2439
|
+
"verifying-proof": { subject: "checking the report", body: "The report is being checked against the approved requirements." },
|
|
2440
|
+
"correcting-proof": { subject: "correcting the report", body: "The report didn't match the saved result; one short correction turn is running." },
|
|
2389
2441
|
};
|
|
2390
2442
|
/** An operator's cancellation as one closed sentence: the reason, if any,
|
|
2391
2443
|
* scrubbed like every phone string and ended with a full stop unless it
|
|
@@ -2398,10 +2450,10 @@ function cancellationWords(reason) {
|
|
|
2398
2450
|
}
|
|
2399
2451
|
/** What an independent review concluded, in the words the saved result shows. */
|
|
2400
2452
|
const LIFECYCLE_REVIEW_WORDS = {
|
|
2401
|
-
verified: { subject: "checks verified", body: "Every
|
|
2402
|
-
refuted: { subject: "
|
|
2403
|
-
attested: { subject: "agent
|
|
2404
|
-
short: { subject: "
|
|
2453
|
+
verified: { subject: "checks verified", body: "Every requirement was checked against the saved result." },
|
|
2454
|
+
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." },
|
|
2455
|
+
attested: { subject: "only the agent's own checks", body: "The result rests on checks the agent reported, not checked independently." },
|
|
2456
|
+
short: { subject: "a requirement isn't shown", body: "Something a requirement needs is missing from the saved result." },
|
|
2405
2457
|
none: { subject: "comments only", body: "The reviewer left comments; no acceptance judgement was recorded." },
|
|
2406
2458
|
};
|
|
2407
2459
|
/** Why a task still waits after one hold lifts — the other owner, in words. */
|
|
@@ -4798,6 +4850,9 @@ function initializeStore(db, file, options = {}) {
|
|
|
4798
4850
|
// A current file is this build's exact shape: the checks above read, the connection gets its settings, and no DDL
|
|
4799
4851
|
// runs. Everything below runs once, on the way up from an older version (a fresh file is at v107 by now).
|
|
4800
4852
|
if (preflight === SCHEMA_VERSION) {
|
|
4853
|
+
const missing = codeMapShapeProblem(db);
|
|
4854
|
+
if (missing !== null)
|
|
4855
|
+
throw new Error(`${file}: refusing to open — ${missing}, though the schema says v${SCHEMA_VERSION}`);
|
|
4801
4856
|
db.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;");
|
|
4802
4857
|
return new Store(db);
|
|
4803
4858
|
}
|
|
@@ -5170,6 +5225,35 @@ CREATE INDEX IF NOT EXISTS run_checkpoint_by_task ON run_checkpoint (task_ref, i
|
|
|
5170
5225
|
// v149: the learning-loops tables (new tables only; docs/plans/learning-loops.md). Idempotent, as v148's steps are.
|
|
5171
5226
|
db.exec(V149_SCHEMA);
|
|
5172
5227
|
step("learning-tables");
|
|
5228
|
+
// v150: this machine's stable id on each process witness and the hostnames it was seen under (V150_SCHEMA,
|
|
5229
|
+
// V150_ADDED_COLUMNS: saved witnesses read NULL). Idempotent, as v149's step is.
|
|
5230
|
+
db.exec(V150_SCHEMA);
|
|
5231
|
+
step("machine-aliases");
|
|
5232
|
+
for (const [column, { definition }] of Object.entries(V150_ADDED_COLUMNS["run_process"]))
|
|
5233
|
+
addColumn(db, "run_process", column, definition);
|
|
5234
|
+
step("witness-machine");
|
|
5235
|
+
// v151: the idle limit beside the cleanup setting (V151_ADDED_COLUMNS: a saved row reads NULL). Idempotent, as v150's steps are.
|
|
5236
|
+
for (const [column, { definition }] of Object.entries(V151_ADDED_COLUMNS["checkout_cleanup_setting"]))
|
|
5237
|
+
addColumn(db, "checkout_cleanup_setting", column, definition);
|
|
5238
|
+
step("checkout-idle");
|
|
5239
|
+
// v152: the code map's tables, a project's switch, and the run-id watermark, set once. Idempotent, as v151's step is.
|
|
5240
|
+
db.exec(V152_SCHEMA);
|
|
5241
|
+
step("code-map-tables");
|
|
5242
|
+
for (const [column, { definition }] of Object.entries(V152_ADDED_COLUMNS["project"]))
|
|
5243
|
+
addColumn(db, "project", column, definition);
|
|
5244
|
+
step("code-map-switch");
|
|
5245
|
+
db.exec("INSERT OR IGNORE INTO code_map_meta (id, run_watermark) SELECT 1, COALESCE(MAX(id), 0) FROM run");
|
|
5246
|
+
step("code-map-watermark");
|
|
5247
|
+
// v153: Phase 2's tables (new tables only). Idempotent, as v152's steps are.
|
|
5248
|
+
db.exec(V153_SCHEMA);
|
|
5249
|
+
step("plain-tables");
|
|
5250
|
+
for (const [table, columns] of Object.entries(V153_ADDED_COLUMNS))
|
|
5251
|
+
for (const [column, { definition }] of Object.entries(columns))
|
|
5252
|
+
addColumn(db, table, column, definition);
|
|
5253
|
+
step("plain-columns");
|
|
5254
|
+
// v154: each check launch's execution stamp (a new table). Idempotent, as v153's steps are.
|
|
5255
|
+
db.exec(V154_SCHEMA);
|
|
5256
|
+
step("check-stamps");
|
|
5173
5257
|
// Keep the epoch until reclamation succeeds, including a retry after compaction already committed. A new file has
|
|
5174
5258
|
// nothing to reclaim.
|
|
5175
5259
|
if (!fresh)
|
|
@@ -6381,6 +6465,8 @@ export class Store {
|
|
|
6381
6465
|
this.db = instrumentDatabase(trackWorkspaceWrites(db), this.telemetry);
|
|
6382
6466
|
// And on each instance: the sealed methods as its own locked properties, so nothing shadows one later.
|
|
6383
6467
|
lockStoreMoneyMethods(this);
|
|
6468
|
+
// This machine's recorded names (v150), for the custody checks that hold no store (machine-identity.ts).
|
|
6469
|
+
rememberStoredHostAliases(this.db);
|
|
6384
6470
|
}
|
|
6385
6471
|
/** TESTS ONLY: raw database access for migration fixtures. Production
|
|
6386
6472
|
* code never calls this — the typed methods are the API. */
|
|
@@ -6652,7 +6738,7 @@ export class Store {
|
|
|
6652
6738
|
this.noteLifecycle({
|
|
6653
6739
|
taskRef: ref.id, kind: "task-queued", identity: `t${ref.id}`,
|
|
6654
6740
|
subject: "Back in the queue",
|
|
6655
|
-
body: prior["state"] === "running" ? "Its
|
|
6741
|
+
body: prior["state"] === "running" ? "Its builder let it go without finishing; another attempt can start." : "Reopened for another attempt.",
|
|
6656
6742
|
link: chatControlHref("task", id),
|
|
6657
6743
|
}, now);
|
|
6658
6744
|
}
|
|
@@ -7970,6 +8056,11 @@ export class Store {
|
|
|
7970
8056
|
// from the live scope: a card that cut a word, or terms that changed since, seal nothing.
|
|
7971
8057
|
if (basis.kind !== "chat" && this.filedViaOf(taskId) === YOUIYOUX_FILED_VIA)
|
|
7972
8058
|
return false;
|
|
8059
|
+
// THE IMPORT QUARANTINE (github-import.ts): a task filed from a GitHub issue or pull request, or a revision of
|
|
8060
|
+
// one, is outside text too. Its provenance is immutable and copied through revisions, so no automatic basis
|
|
8061
|
+
// ever seals it, here, whatever road it came by.
|
|
8062
|
+
if (basis.kind !== "chat" && issueImported(this, taskId))
|
|
8063
|
+
return false;
|
|
7973
8064
|
// CHECK ACCESS (v132, check-access.ts): a scope whose checks reach beyond the fence (sign-ins, or no sandbox at
|
|
7974
8065
|
// all) seals only on a person's password ceremony — never a mode, a grant, a chat confirmation or a carry.
|
|
7975
8066
|
const profileText = this.db.prepare("SELECT profile_json FROM task_scope WHERE task_id = ?").get(taskId)?.["profile_json"];
|
|
@@ -8048,12 +8139,15 @@ export class Store {
|
|
|
8048
8139
|
if (deployGate && basis?.kind === "chat" && !(basis.card === "release" && typeof basis.cardDigest === "string" && /^[0-9a-f]{64}$/.test(basis.cardDigest)))
|
|
8049
8140
|
return false;
|
|
8050
8141
|
// 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
|
|
8142
|
+
if (basis?.kind === "chat" && (this.filedViaOf(taskId) === YOUIYOUX_FILED_VIA || issueImported(this, taskId))
|
|
8052
8143
|
&& !(basis.card === "terms" && typeof basis.cardDigest === "string" && basis.cardDigest === shownTermsDigest(readScope(working))))
|
|
8053
8144
|
return false;
|
|
8054
8145
|
// AUTOMATIC RELEASE SEALS (auto-approve-green.md): only on a release task (a candidate and release terms signed
|
|
8055
8146
|
// in), only naming the evidence row the plane wrote for exactly this task, kind, digest and candidate.
|
|
8056
8147
|
const automatic = basis?.kind === "auto-check" || basis?.kind === "auto-green";
|
|
8148
|
+
// A FAST START (fast-start.ts) stays a person's: no automatic release seal ever lands on one.
|
|
8149
|
+
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)
|
|
8150
|
+
return false;
|
|
8057
8151
|
if (automatic) {
|
|
8058
8152
|
if (terms.terms === null || typeof working["candidate"] !== "string" || working["candidate"] === "")
|
|
8059
8153
|
return false;
|
|
@@ -8063,6 +8157,9 @@ export class Store {
|
|
|
8063
8157
|
}
|
|
8064
8158
|
if (terms.terms !== null && basis !== undefined && basis.kind !== "chat" && !automatic)
|
|
8065
8159
|
return false;
|
|
8160
|
+
// Nor does a fast start ever anchor a release, whoever approves it.
|
|
8161
|
+
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)
|
|
8162
|
+
return false;
|
|
8066
8163
|
if (terms.terms !== null && releaseSealProblem(this, terms.terms) !== null)
|
|
8067
8164
|
return false;
|
|
8068
8165
|
const changed = this.db
|
|
@@ -8092,8 +8189,12 @@ export class Store {
|
|
|
8092
8189
|
// (setup review): the approval is what was stale, and it is new.
|
|
8093
8190
|
const ref = this.db.prepare("SELECT id FROM task_ref WHERE backend = ? AND external_id = ?").get(BUILT_IN, taskId);
|
|
8094
8191
|
if (ref !== undefined) {
|
|
8095
|
-
if (basis === undefined || basis.kind === "chat")
|
|
8192
|
+
if (basis === undefined || basis.kind === "chat") {
|
|
8096
8193
|
this.noteTaskAct(Number(ref["id"]), "approved", now);
|
|
8194
|
+
clearRetryEpisode(this, Number(ref["id"]));
|
|
8195
|
+
// An ordinary yes stands on its own: a fast start's window is over (its Accept backstop stays).
|
|
8196
|
+
this.db.prepare("UPDATE task_ref SET fast_start_until = NULL WHERE id = ? AND fast_start_until IS NOT NULL").run(Number(ref["id"]));
|
|
8197
|
+
}
|
|
8097
8198
|
this.db.prepare("DELETE FROM hold WHERE owner_kind = 'backoff' AND owner_id = ?").run(`stale:${Number(ref["id"])}`);
|
|
8098
8199
|
// Results routing: this yes closes the task's open exposure (as proposed, or with the model it sealed),
|
|
8099
8200
|
// and lifts the hold a switched-off rule put on the approval it replaces.
|
|
@@ -8114,20 +8215,20 @@ export class Store {
|
|
|
8114
8215
|
if (alreadyOn !== working["digest"]) {
|
|
8115
8216
|
this.noteLifecycle({
|
|
8116
8217
|
taskRef: Number(ref["id"]), kind: "scope-approved", identity: `t${Number(ref["id"])}`,
|
|
8117
|
-
subject: "
|
|
8218
|
+
subject: "Plan approved",
|
|
8118
8219
|
body: basis === undefined
|
|
8119
|
-
? `Approved by ${lifecycleWords(by, 40)}. A connected
|
|
8220
|
+
? `Approved by ${lifecycleWords(by, 40)}. A connected builder can take it next.`
|
|
8120
8221
|
: basis.kind === "chat"
|
|
8121
|
-
? `Approved by ${lifecycleWords(by, 40)} in chat. A connected
|
|
8222
|
+
? `Approved by ${lifecycleWords(by, 40)} in chat. A connected builder can take it next.`
|
|
8122
8223
|
: basis.kind === "grant"
|
|
8123
|
-
? "Approved automatically: its flow runs on its own. A connected
|
|
8224
|
+
? "Approved automatically: its flow runs on its own. A connected builder can take it next."
|
|
8124
8225
|
: basis.kind === "schedule"
|
|
8125
|
-
? "Approved by its standing order. A connected
|
|
8226
|
+
? "Approved by its standing order. A connected builder can take it next."
|
|
8126
8227
|
: basis.kind === "auto-check"
|
|
8127
8228
|
? "Checking automatically: it approves itself once CI and the release check pass."
|
|
8128
8229
|
: basis.kind === "auto-green"
|
|
8129
8230
|
? "Approved automatically: CI and the release check passed."
|
|
8130
|
-
: "Approved automatically under the operating mode. A connected
|
|
8231
|
+
: "Approved automatically under the operating mode. A connected builder can take it next.",
|
|
8131
8232
|
link: chatControlHref("task", taskId),
|
|
8132
8233
|
}, now);
|
|
8133
8234
|
}
|
|
@@ -8166,7 +8267,7 @@ export class Store {
|
|
|
8166
8267
|
JOIN task_ref ON task_ref.id = ? AND task_ref.backend = 'built-in' AND task_scope.task_id = task_ref.external_id
|
|
8167
8268
|
WHERE task_scope.approved_at IS NOT NULL
|
|
8168
8269
|
AND NOT ${authorityStandsSql("task_scope", "task_ref.repo", "?")}`)
|
|
8169
|
-
.get(taskRef, now.toISOString(), now.toISOString(), now.toISOString());
|
|
8270
|
+
.get(taskRef, now.toISOString(), now.toISOString(), now.toISOString(), now.toISOString());
|
|
8170
8271
|
if (dead !== undefined)
|
|
8171
8272
|
return false;
|
|
8172
8273
|
// A release task (release-once.md): the whole live-release predicate, beyond what the SQL can say.
|
|
@@ -9706,10 +9807,7 @@ export class Store {
|
|
|
9706
9807
|
.get(taskRef);
|
|
9707
9808
|
if (ref === undefined || String(ref["backend"]) !== BUILT_IN)
|
|
9708
9809
|
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)
|
|
9810
|
+
if (settleVanished(this, taskRef, now) === "queued")
|
|
9713
9811
|
requeued?.push(String(ref["external_id"]));
|
|
9714
9812
|
}
|
|
9715
9813
|
return held.map(row => String(row["lease_id"]));
|
|
@@ -10502,7 +10600,7 @@ export class Store {
|
|
|
10502
10600
|
this.noteLifecycle({
|
|
10503
10601
|
taskRef: run.taskRef, run: runId, kind: "review-requested", identity: `r${runId}`,
|
|
10504
10602
|
subject: "Independent review requested",
|
|
10505
|
-
body: `${automatic ? "Queued automatically" : `Requested by ${lifecycleWords(by, 40)}`} for
|
|
10603
|
+
body: `${automatic ? "Queued automatically" : `Requested by ${lifecycleWords(by, 40)}`} for build #${runId}'s saved result. A connected reviewer runs it next.`,
|
|
10506
10604
|
link: chatResultHref(reviewedTask, runId, "checks"),
|
|
10507
10605
|
}, now);
|
|
10508
10606
|
}
|
|
@@ -11806,6 +11904,10 @@ export class Store {
|
|
|
11806
11904
|
for (const incident of incidents)
|
|
11807
11905
|
this.resolveIncidentLocked(incident, by, now);
|
|
11808
11906
|
this.resetStrikes(taskRef);
|
|
11907
|
+
clearRetryEpisode(this, taskRef);
|
|
11908
|
+
// A person's Retry is a new person act: a fast start gets a new start window. A machine's never does.
|
|
11909
|
+
if (!machineActing())
|
|
11910
|
+
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
11911
|
this.db
|
|
11810
11912
|
.prepare("UPDATE task SET state = 'queued', updated_at = ? WHERE id = ? AND state IN ('failed','queued','done')")
|
|
11811
11913
|
.run(stamp, taskId);
|
|
@@ -11813,10 +11915,10 @@ export class Store {
|
|
|
11813
11915
|
this.resolveEpisode(`stalled:${taskRef}`, now);
|
|
11814
11916
|
this.noteLifecycle({
|
|
11815
11917
|
taskRef, kind: "task-requeued", identity: `t${taskRef}`,
|
|
11816
|
-
subject: "
|
|
11918
|
+
subject: "Queued again",
|
|
11817
11919
|
body: rejectedRun !== null
|
|
11818
|
-
? `Its last result (
|
|
11819
|
-
:
|
|
11920
|
+
? `Its last result (build #${rejectedRun}) was not accepted; the next attempt continues on the same branch under the current plan.`
|
|
11921
|
+
: `Its repeated failures are cleared; a builder can take it again.`,
|
|
11820
11922
|
link: chatControlHref("task", taskId),
|
|
11821
11923
|
}, now);
|
|
11822
11924
|
this.bumpWake();
|
|
@@ -13328,18 +13430,18 @@ export class Store {
|
|
|
13328
13430
|
const stopped = result.outcome === "failed" && result.reason === "interrupted" && this.stopOf(id) !== null;
|
|
13329
13431
|
if (role === "builder") {
|
|
13330
13432
|
if (stopped) {
|
|
13331
|
-
this.noteLifecycle({ taskRef, run: id, kind: "run-stopped", identity: `r${id}`, subject: `
|
|
13433
|
+
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
13434
|
}
|
|
13333
13435
|
else if (result.outcome === "built") {
|
|
13334
13436
|
this.noteLifecycle({
|
|
13335
13437
|
taskRef, run: id, kind: "run-finished", identity: `r${id}`,
|
|
13336
|
-
subject: `
|
|
13438
|
+
subject: `Build #${id} built`,
|
|
13337
13439
|
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
13440
|
link: chatResultHref(taskId, id, "changes"),
|
|
13339
13441
|
}, now);
|
|
13340
13442
|
}
|
|
13341
13443
|
else if (result.outcome === "no-change") {
|
|
13342
|
-
this.noteLifecycle({ taskRef, run: id, kind: "run-finished", identity: `r${id}`, subject: `
|
|
13444
|
+
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
13445
|
}
|
|
13344
13446
|
return;
|
|
13345
13447
|
}
|
|
@@ -13841,12 +13943,12 @@ export class Store {
|
|
|
13841
13943
|
if (road.road !== "repair" && road.road !== "correction" && !(role === "planner" && parent !== null)) {
|
|
13842
13944
|
const agent = lifecycleWords(`${provider ?? "claude"}${model === null ? "" : ` · ${model}`}`, 60);
|
|
13843
13945
|
const words = role === "reviewer"
|
|
13844
|
-
? { subject: `Independent review started (attempt ${reviewAttempt ?? 1})`, body: `Reviewing the saved result of
|
|
13946
|
+
? { 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
13947
|
: role === "planner"
|
|
13846
|
-
? { subject:
|
|
13948
|
+
? { subject: "Planning started", body: `Drafting the plan on ${agent}.`, link: chatControlHref("planning", taskId) }
|
|
13847
13949
|
: role === "scout"
|
|
13848
|
-
? { subject:
|
|
13849
|
-
: { subject: road.road === "recovered" ? `
|
|
13950
|
+
? { subject: "Research started", body: `A read-only look on ${agent}; what comes back is a report.`, link: chatControlHref("task", taskId) }
|
|
13951
|
+
: { subject: road.road === "recovered" ? `Build #${id} resumed from its saved draft` : `Build #${id} started`, body: `Building on ${agent}.`, link: chatControlHref("task", taskId) };
|
|
13850
13952
|
this.noteLifecycle({ taskRef: run.taskRef, run: id, kind: "run-started", identity: `r${id}`, ...words }, run.now);
|
|
13851
13953
|
}
|
|
13852
13954
|
return id;
|
|
@@ -14835,7 +14937,7 @@ export class Store {
|
|
|
14835
14937
|
if (taskId === null)
|
|
14836
14938
|
return;
|
|
14837
14939
|
const words = LIFECYCLE_PHASE_WORDS[phase];
|
|
14838
|
-
this.noteLifecycle({ taskRef, run: id, kind: "run-phase", identity: `r${id}`, subject: `
|
|
14940
|
+
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
14941
|
});
|
|
14840
14942
|
}
|
|
14841
14943
|
/** The accepted head and the validated conclusion, once known. */
|
|
@@ -18059,6 +18161,7 @@ export class Store {
|
|
|
18059
18161
|
this.db.prepare(`INSERT INTO action_ledger(at,actor,repo,task_id,run_id,action,outcome,source,detail)
|
|
18060
18162
|
VALUES (?,?,?,NULL,NULL,'project remote recorded','recorded','access',?)`)
|
|
18061
18163
|
.run(now.toISOString(), actor, repo, JSON.stringify({ previousRemote: previous, remoteUrl }));
|
|
18164
|
+
codeMapRemoteChanged(this, repo);
|
|
18062
18165
|
}
|
|
18063
18166
|
/** The remote URL captured on admission or explicitly supplied by the owner; null when none was (or the project was removed). */
|
|
18064
18167
|
projectRemote(path) {
|
|
@@ -18067,7 +18170,10 @@ export class Store {
|
|
|
18067
18170
|
}
|
|
18068
18171
|
/** A project removed from the lists (project-admission.ts): only its row goes; its tasks, results and settings stay. */
|
|
18069
18172
|
forgetProject(path) {
|
|
18070
|
-
this.
|
|
18173
|
+
this.transact(() => {
|
|
18174
|
+
forgetCodeMap(this, path);
|
|
18175
|
+
this.db.prepare("DELETE FROM project WHERE path = ?").run(path);
|
|
18176
|
+
});
|
|
18071
18177
|
}
|
|
18072
18178
|
/** Most recently opened first — the opener page's order. */
|
|
18073
18179
|
listProjects() {
|
|
@@ -18758,10 +18864,7 @@ export class Store {
|
|
|
18758
18864
|
.get(taskRef, BUILT_IN);
|
|
18759
18865
|
if (ref === undefined)
|
|
18760
18866
|
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)
|
|
18867
|
+
if (settleVanished(this, taskRef, now) === "queued")
|
|
18765
18868
|
requeued.push(String(ref["external_id"]));
|
|
18766
18869
|
};
|
|
18767
18870
|
const open = this.db
|
|
@@ -18847,16 +18950,11 @@ export class Store {
|
|
|
18847
18950
|
this.db
|
|
18848
18951
|
.prepare("UPDATE claim SET released_at = ?, released_by = 'recovered' WHERE lease_id = ?")
|
|
18849
18952
|
.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
18953
|
this.db
|
|
18857
18954
|
.prepare(`UPDATE run SET outcome = 'failed', reason = 'interrupted', finished_at = ?
|
|
18858
18955
|
WHERE lease_id = ? AND outcome IS NULL`)
|
|
18859
18956
|
.run(stamp, leaseId);
|
|
18957
|
+
settleVanished(this, taskRef, now);
|
|
18860
18958
|
this.db
|
|
18861
18959
|
.prepare(`UPDATE worktree SET released_at = ?, verified = 0
|
|
18862
18960
|
WHERE runner = ? AND task_ref = ? AND released_at IS NULL`)
|
|
@@ -18878,14 +18976,7 @@ export class Store {
|
|
|
18878
18976
|
this.db
|
|
18879
18977
|
.prepare("UPDATE run SET outcome = 'failed', reason = 'interrupted', finished_at = ? WHERE id = ?")
|
|
18880
18978
|
.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
|
-
}
|
|
18979
|
+
settleVanished(this, taskRef, now);
|
|
18889
18980
|
this.db
|
|
18890
18981
|
.prepare("UPDATE worktree SET released_at = ?, verified = 0 WHERE runner = ? AND task_ref = ? AND released_at IS NULL")
|
|
18891
18982
|
.run(stamp, runner, taskRef);
|
|
@@ -19019,7 +19110,7 @@ export class Store {
|
|
|
19019
19110
|
if (stoppedTask !== null) {
|
|
19020
19111
|
this.noteLifecycle(run.role === "reviewer"
|
|
19021
19112
|
? { 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
|
|
19113
|
+
: { 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
19114
|
}
|
|
19024
19115
|
this.bumpWake();
|
|
19025
19116
|
return { ok: true, stop: this.stopOf(args.runId), repeated: false };
|
|
@@ -19078,6 +19169,7 @@ export class Store {
|
|
|
19078
19169
|
if (ownedProcessCount(runOwnerTag(this, id)) > 0)
|
|
19079
19170
|
return fact(id, "alive", `run #${id} still has an owned subprocess`);
|
|
19080
19171
|
const witnesses = this.db.prepare("SELECT * FROM run_process WHERE run = ? ORDER BY id").all(id);
|
|
19172
|
+
const here = storeMachine(this.db);
|
|
19081
19173
|
let incompleteSpawn = false;
|
|
19082
19174
|
if (run.providerStartedAt !== null && witnesses.length === 0 && !this.processesSummarized(id)) {
|
|
19083
19175
|
return provable(id, `run #${id} may have spawned before its process witness was recorded; exit is unproven`, "no-witness");
|
|
@@ -19087,15 +19179,17 @@ export class Store {
|
|
|
19087
19179
|
// leader. Re-probing its number could mistake reuse for the old group.
|
|
19088
19180
|
if (witness["exited_at"] !== null)
|
|
19089
19181
|
continue;
|
|
19090
|
-
const host = String(witness["host"]);
|
|
19091
19182
|
const bootId = witness["boot_id"] === null || witness["boot_id"] === undefined ? null : String(witness["boot_id"]);
|
|
19092
|
-
//
|
|
19183
|
+
// This machine by its stable id, or by one of its own names for a witness from before v150 (machine-identity.ts):
|
|
19184
|
+
// a hostname that moved with the network never makes this machine's own witness another computer's.
|
|
19185
|
+
const local = witnessIsLocal(witness, here);
|
|
19186
|
+
// A VERIFIED boot change on this machine (v53): nothing of the old
|
|
19093
19187
|
// boot survives — an incomplete spawn, a reused pid and a native
|
|
19094
19188
|
// object alike are gone. Every other case keeps the conservative
|
|
19095
19189
|
// road below.
|
|
19096
|
-
if (provenDeadByBootChange({ host, bootId }))
|
|
19190
|
+
if (local && provenDeadByBootChange({ host: String(witness["host"]), bootId }, { host: String(witness["host"]), bootId: currentBootId() }))
|
|
19097
19191
|
continue;
|
|
19098
|
-
if (
|
|
19192
|
+
if (!local)
|
|
19099
19193
|
return fact(id, "elsewhere", `run #${id}'s process belongs to another host; exit is unproven here`);
|
|
19100
19194
|
const backend = witness["containment"] === null || witness["containment"] === undefined ? null : String(witness["containment"]);
|
|
19101
19195
|
const container = witness["container"] === null || witness["container"] === undefined ? null : String(witness["container"]);
|
|
@@ -19152,8 +19246,9 @@ export class Store {
|
|
|
19152
19246
|
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
19247
|
}
|
|
19154
19248
|
reserveRunProcess(runId, now, group = true) {
|
|
19155
|
-
|
|
19156
|
-
|
|
19249
|
+
recordMachineHostAliases(this.db, now);
|
|
19250
|
+
return Number(this.db.prepare("INSERT INTO run_process (run,host,process_group,observed_at,boot_id,machine_id) VALUES (?,?,?,?,?,?)")
|
|
19251
|
+
.run(runId, hostname(), group ? 1 : 0, now.toISOString(), currentBootId(), currentMachineId()).lastInsertRowid);
|
|
19157
19252
|
}
|
|
19158
19253
|
/** The run's process witnesses not yet proved exited — after first saving whatever exit evidence is available. */
|
|
19159
19254
|
openRunProcessCount(runId, now) {
|
|
@@ -19173,12 +19268,13 @@ export class Store {
|
|
|
19173
19268
|
// check uses: ESRCH, or a process now holding the pid that was born after
|
|
19174
19269
|
// the run ended. An open run has no finish time, so only ESRCH counts.
|
|
19175
19270
|
const finishedAt = this.getRun(runId)?.finishedAt ?? null;
|
|
19271
|
+
const here = storeMachine(this.db);
|
|
19176
19272
|
let recorded = 0;
|
|
19177
19273
|
for (const row of rows) {
|
|
19178
19274
|
const host = String(row["host"]), bootId = row["boot_id"] == null ? null : String(row["boot_id"]);
|
|
19179
|
-
if (
|
|
19275
|
+
if (!witnessIsLocal(row, here) || typeof row["observed_at"] !== "string" || !Number.isFinite(Date.parse(row["observed_at"])) || Date.parse(row["observed_at"]) > now.getTime())
|
|
19180
19276
|
continue;
|
|
19181
|
-
let gone = provenDeadByBootChange({ host, bootId });
|
|
19277
|
+
let gone = provenDeadByBootChange({ host, bootId }, { host, bootId: currentBootId() });
|
|
19182
19278
|
if (!gone) {
|
|
19183
19279
|
const backend = row["containment"] == null ? null : String(row["containment"]);
|
|
19184
19280
|
const container = row["container"] == null ? null : String(row["container"]);
|
|
@@ -19189,11 +19285,12 @@ export class Store {
|
|
|
19189
19285
|
gone = row["pid"] != null && !processMayBeAlive(Number(row["pid"]), row["process_group"] === 1, finishedAt === null ? undefined : { observedAt: row["observed_at"], finishedAt });
|
|
19190
19286
|
}
|
|
19191
19287
|
}
|
|
19288
|
+
const sameMachine = witnessesCarryMachine(this.db);
|
|
19192
19289
|
if (gone)
|
|
19193
19290
|
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 = ?
|
|
19291
|
+
WHERE id = ? AND run = ? AND exited_at IS NULL AND pid IS ? AND host = ?${sameMachine ? " AND machine_id IS ?" : ""} AND process_group = ?
|
|
19195
19292
|
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);
|
|
19293
|
+
.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
19294
|
}
|
|
19198
19295
|
return recorded;
|
|
19199
19296
|
}
|
|
@@ -19203,12 +19300,13 @@ export class Store {
|
|
|
19203
19300
|
* yet stays open and is passed over, never holding newer runs behind it:
|
|
19204
19301
|
* every finished run is visited, a batch at a time. Never signals. */
|
|
19205
19302
|
recordFinishedRunExits(now, batch = 200) {
|
|
19303
|
+
const local = localWitnessSql(this.db, "p");
|
|
19206
19304
|
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
|
|
19305
|
+
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
19306
|
ORDER BY p.run LIMIT ?`);
|
|
19209
19307
|
let recorded = 0;
|
|
19210
19308
|
for (let after = 0;;) {
|
|
19211
|
-
const runs = page.all(
|
|
19309
|
+
const runs = page.all(...local.params, after, batch).map(row => Number(row["run"]));
|
|
19212
19310
|
for (const run of runs)
|
|
19213
19311
|
recorded += this.recordRunProcessExits(run, now);
|
|
19214
19312
|
if (runs.length < batch)
|
|
@@ -19240,10 +19338,11 @@ export class Store {
|
|
|
19240
19338
|
* entry. A sole pid-less witness has no group to prove gone and stays for
|
|
19241
19339
|
* Toolroll's own look (stop-proof.ts). Native witnesses keep their OS-only road. */
|
|
19242
19340
|
settleUnspawnedWitnesses(now, onlyRun) {
|
|
19341
|
+
const local = localWitnessSql(this.db, "p");
|
|
19243
19342
|
const candidates = this.db.prepare(`SELECT DISTINCT p.run FROM run_process p JOIN run r ON r.id = p.run
|
|
19244
19343
|
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(
|
|
19344
|
+
AND ${local.sql} AND r.outcome IS NOT NULL AND r.finished_at IS NOT NULL
|
|
19345
|
+
AND (? IS NULL OR p.run = ?) ORDER BY p.run`).all(...local.params, onlyRun ?? null, onlyRun ?? null);
|
|
19247
19346
|
let settled = 0;
|
|
19248
19347
|
for (const candidate of candidates) {
|
|
19249
19348
|
const runId = Number(candidate["run"]);
|
|
@@ -19278,11 +19377,12 @@ export class Store {
|
|
|
19278
19377
|
settleableUnspawnedWitnesses(run, rows) {
|
|
19279
19378
|
if (run.outcome === null || run.finishedAt === null || ownedProcessCount(runOwnerTag(this, run.id)) > 0)
|
|
19280
19379
|
return [];
|
|
19281
|
-
const
|
|
19380
|
+
const here = storeMachine(this.db);
|
|
19381
|
+
const pidless = rows.filter(row => row["exited_at"] === null && row["pid"] === null && row["containment"] === null && row["container"] === null && witnessIsLocal(row, here));
|
|
19282
19382
|
const others = rows.filter(row => !pidless.includes(row));
|
|
19283
19383
|
if (pidless.length === 0 || others.length === 0 || others.some(row => row["exited_at"] === null))
|
|
19284
19384
|
return [];
|
|
19285
|
-
const known = others.filter(row => row["pid"] !== null && row
|
|
19385
|
+
const known = others.filter(row => row["pid"] !== null && witnessIsLocal(row, here));
|
|
19286
19386
|
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
19387
|
return [];
|
|
19288
19388
|
return pidless;
|
|
@@ -19297,8 +19397,9 @@ export class Store {
|
|
|
19297
19397
|
throw new Error("spawn witness custody changed");
|
|
19298
19398
|
return;
|
|
19299
19399
|
}
|
|
19300
|
-
this.db
|
|
19301
|
-
|
|
19400
|
+
recordMachineHostAliases(this.db, now);
|
|
19401
|
+
this.db.prepare("INSERT INTO run_process (run,pid,host,process_group,observed_at,boot_id,machine_id) VALUES (?,?,?,?,?,?,?)")
|
|
19402
|
+
.run(runId, pid, hostname(), group ? 1 : 0, now.toISOString(), currentBootId(), currentMachineId());
|
|
19302
19403
|
}
|
|
19303
19404
|
/** The native OS object a witnessed spawn ran inside (v53) — recorded
|
|
19304
19405
|
* with the spawn, before the target can execute. */
|
|
@@ -19318,11 +19419,12 @@ export class Store {
|
|
|
19318
19419
|
* must independently prove gone; this never grants authority to signal. */
|
|
19319
19420
|
runCustodyProvenGone(runId) {
|
|
19320
19421
|
const rows = this.db.prepare("SELECT * FROM run_process WHERE run = ?").all(runId);
|
|
19422
|
+
const here = storeMachine(this.db);
|
|
19321
19423
|
// A summarized run's witnesses all exited, each on recorded proof.
|
|
19322
19424
|
return (rows.length > 0 || this.processesSummarized(runId)) && rows.every(row => {
|
|
19323
|
-
if (
|
|
19425
|
+
if (!witnessIsLocal(row, here))
|
|
19324
19426
|
return false;
|
|
19325
|
-
if (provenDeadByBootChange({ host: String(row["host"]), bootId: row["boot_id"] == null ? null : String(row["boot_id"]) }))
|
|
19427
|
+
if (provenDeadByBootChange({ host: String(row["host"]), bootId: row["boot_id"] == null ? null : String(row["boot_id"]) }, { host: String(row["host"]), bootId: currentBootId() }))
|
|
19326
19428
|
return true;
|
|
19327
19429
|
if (row["containment"] == null || row["container"] == null)
|
|
19328
19430
|
return false;
|
|
@@ -19398,7 +19500,7 @@ export class Store {
|
|
|
19398
19500
|
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
19501
|
const resumedTask = this.externalIdFor(args.taskRef);
|
|
19400
19502
|
if (resumedTask !== null) {
|
|
19401
|
-
this.noteLifecycle({ taskRef: args.taskRef, run: args.runId, kind: "run-resumed", identity: `r${args.runId}`, subject: `
|
|
19503
|
+
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
19504
|
}
|
|
19403
19505
|
this.bumpWake();
|
|
19404
19506
|
return { ok: true, stop: this.stopOf(args.runId) };
|
|
@@ -20158,12 +20260,14 @@ export class Store {
|
|
|
20158
20260
|
.prepare(`INSERT OR IGNORE INTO push_delivery (notification, subscription, created_at)
|
|
20159
20261
|
SELECT notification.id, push_subscription.id, ?
|
|
20160
20262
|
FROM notification, push_subscription
|
|
20161
|
-
WHERE
|
|
20263
|
+
WHERE notification.push_class IS NOT NULL
|
|
20264
|
+
-- A fact that needs nothing from a person (checks in progress) stays in the activity log: no phone ping.
|
|
20265
|
+
AND notification.kind NOT IN (SELECT value FROM json_each(?))
|
|
20162
20266
|
AND notification.resolved_at IS NULL
|
|
20163
20267
|
AND (notification.recipient IS NULL OR notification.recipient = push_subscription.approver)
|
|
20164
20268
|
AND push_subscription.retired_at IS NULL
|
|
20165
20269
|
AND notification.id > push_subscription.starts_after_notification`)
|
|
20166
|
-
.run(now.toISOString());
|
|
20270
|
+
.run(now.toISOString(), JSON.stringify([...ACTIVITY_ONLY_KINDS]));
|
|
20167
20271
|
// Pings follow responsibility: a pair this person must not be alerted about retires before it is ever sent.
|
|
20168
20272
|
if (Number(changes) > 0) {
|
|
20169
20273
|
const fresh = this.db.prepare(`SELECT p.id AS pair, s.approver AS approver, n.* FROM push_delivery p
|
|
@@ -20490,7 +20594,8 @@ export class Store {
|
|
|
20490
20594
|
return null;
|
|
20491
20595
|
}
|
|
20492
20596
|
}
|
|
20493
|
-
/** The level a run
|
|
20597
|
+
/** The level a later check of this run may run at, at most: missing or unreadable reads as sandboxed, the narrowest.
|
|
20598
|
+
* Never an account of how its checks DID run: that is runCheckStamp (check-stamp.ts). */
|
|
20494
20599
|
runCheckAccess(runId) {
|
|
20495
20600
|
return checkAccessOf(this.db.prepare("SELECT sandbox_access FROM run WHERE id = ?").get(runId)?.["sandbox_access"]);
|
|
20496
20601
|
}
|
|
@@ -21773,6 +21878,11 @@ export function carryReleaseApproval(store, w, now, recheck) {
|
|
|
21773
21878
|
const problem = recheck();
|
|
21774
21879
|
if (problem !== null)
|
|
21775
21880
|
return { ok: false, why: problem };
|
|
21881
|
+
// How the release's check ran, read again with the write: a launch since the preflight counts (H2).
|
|
21882
|
+
const ran = taskCheckStamp(store, a.repo, a.tipTask);
|
|
21883
|
+
const unshown = ran === null ? null : stampRefusal(ran.reading, checkAccessOf(a.accessDigest));
|
|
21884
|
+
if (unshown !== null)
|
|
21885
|
+
return { ok: false, why: `This release's approval can't carry: ${unshown}.` };
|
|
21776
21886
|
if (store.getTask(w.taskId) !== null)
|
|
21777
21887
|
return { ok: false, why: `A task ${w.taskId} already exists.` };
|
|
21778
21888
|
const anchorRow = store.handle.prepare("SELECT * FROM task_scope WHERE task_id = ?").get(a.anchorTask);
|
|
@@ -21808,7 +21918,7 @@ export function carryReleaseApproval(store, w, now, recheck) {
|
|
|
21808
21918
|
store.recordAction({ at: now.toISOString(), actor: "toolroll", repo: a.repo, taskId: w.taskId, runId: null, action: "release carried", outcome: "carried", source: "work",
|
|
21809
21919
|
detail: `release ${a.id} · carry ${carryId} · ${a.tipSha} → ${w.to} · ${w.files} files · ${w.changedLines} lines · request ${w.requestId}` });
|
|
21810
21920
|
// One quiet task notification, no push.
|
|
21811
|
-
store.enqueueNotification({ dedupeKey: `release-carried:${a.id}:${carryId}`, kind: "release-carried", subject: `
|
|
21921
|
+
store.enqueueNotification({ dedupeKey: `release-carried:${a.id}:${carryId}`, kind: "release-carried", subject: `Release now includes ${w.to.slice(0, 7)}`,
|
|
21812
21922
|
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
21923
|
link: chatControlHref("task", w.taskId), source: { taskRef: ref.id } }, now);
|
|
21814
21924
|
return { ok: true, taskId: w.taskId, carry: releaseCarryTo(store, a.id, w.to) };
|
|
@@ -22629,7 +22739,8 @@ export function recordReleaseStep(store, entry) {
|
|
|
22629
22739
|
.run(entry.taskId, entry.tag, entry.version, entry.pr, entry.step, entry.state, at, ended ? at : null, detail, at);
|
|
22630
22740
|
}
|
|
22631
22741
|
/** 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
|
|
22742
|
+
* step it stopped at when the last thing recorded was a failure and no build of the task has started since. Null: no
|
|
22743
|
+
* step recorded. */
|
|
22633
22744
|
export function releaseProgressFor(store, taskId) {
|
|
22634
22745
|
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
22746
|
if (newest === undefined)
|
|
@@ -22639,9 +22750,13 @@ export function releaseProgressFor(store, taskId) {
|
|
|
22639
22750
|
const steps = rows.map(row => ({ step: String(row["step"]), state: String(row["state"]), startedAt: String(row["started_at"]),
|
|
22640
22751
|
endedAt: row["ended_at"] === null ? null : String(row["ended_at"]), detail: row["detail"] === null ? null : String(row["detail"]) }));
|
|
22641
22752
|
const last = [...rows].sort((a, b) => String(a["updated_at"]).localeCompare(String(b["updated_at"]))).at(-1);
|
|
22642
|
-
const
|
|
22753
|
+
const failed = String(last["state"]) === "failed";
|
|
22754
|
+
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'
|
|
22755
|
+
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"]));
|
|
22756
|
+
const building = live === undefined ? null : { run: Number(live["id"]), startedAt: String(live["started_at"]) };
|
|
22757
|
+
const stopped = failed && building === null ? { step: String(last["step"]), message: last["detail"] === null ? "" : String(last["detail"]), at: String(last["updated_at"]) } : null;
|
|
22643
22758
|
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 };
|
|
22759
|
+
return { tag, version: String(last["version"]), pr, startedAt: steps[0].startedAt, updatedAt: String(last["updated_at"]), steps, stopped, building };
|
|
22645
22760
|
}
|
|
22646
22761
|
/** The release task whose steps were recorded most recently (v146), or null when none was. */
|
|
22647
22762
|
export function latestReleaseStepTask(store) {
|
|
@@ -22878,15 +22993,17 @@ export function settleRunStopByObservation(store, args, now) {
|
|
|
22878
22993
|
const at = now.toISOString();
|
|
22879
22994
|
let what;
|
|
22880
22995
|
if (args.cause === "incomplete-spawn") {
|
|
22996
|
+
const local = localWitnessSql(db);
|
|
22881
22997
|
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
|
|
22998
|
+
AND containment IS NULL AND container IS NULL AND ${local.sql}`).run(at, args.runId, ...local.params).changes;
|
|
22883
22999
|
if (Number(changed) === 0)
|
|
22884
23000
|
return false;
|
|
22885
23001
|
what = `${Number(changed)} process witness${Number(changed) === 1 ? "" : "es"} with no process ID recorded ended`;
|
|
22886
23002
|
}
|
|
22887
23003
|
else if (args.cause === "no-witness") {
|
|
22888
|
-
db
|
|
22889
|
-
|
|
23004
|
+
recordMachineHostAliases(db, now);
|
|
23005
|
+
db.prepare("INSERT INTO run_process (run,host,process_group,observed_at,boot_id,exited_at,machine_id) VALUES (?,?,0,?,?,?,?)")
|
|
23006
|
+
.run(args.runId, hostname(), at, currentBootId(), at, currentMachineId());
|
|
22890
23007
|
what = "its missing process witness recorded ended";
|
|
22891
23008
|
}
|
|
22892
23009
|
else {
|
|
@@ -22901,3 +23018,95 @@ export function settleRunStopByObservation(store, args, now) {
|
|
|
22901
23018
|
return true;
|
|
22902
23019
|
});
|
|
22903
23020
|
}
|
|
23021
|
+
/**
|
|
23022
|
+
* This machine as the store knows it (v150, machine-identity.ts): its stable id and every name it is known by, the
|
|
23023
|
+
* hostnames recorded beside its own id included. Read fresh each time, so a name recorded by another process on this
|
|
23024
|
+
* machine counts at once.
|
|
23025
|
+
*/
|
|
23026
|
+
export function storeMachine(db) {
|
|
23027
|
+
const id = currentMachineId();
|
|
23028
|
+
let recorded = [];
|
|
23029
|
+
if (id !== null) {
|
|
23030
|
+
try {
|
|
23031
|
+
recorded = db.prepare("SELECT host FROM machine_host_alias WHERE machine_id = ?").all(id).map(row => String(row["host"]));
|
|
23032
|
+
}
|
|
23033
|
+
catch {
|
|
23034
|
+
recorded = [];
|
|
23035
|
+
}
|
|
23036
|
+
rememberHostAliases(id, recorded);
|
|
23037
|
+
}
|
|
23038
|
+
return thisMachine(recorded);
|
|
23039
|
+
}
|
|
23040
|
+
/** Load this machine's recorded names for the checks that hold no store; a store that can't read them loads none. */
|
|
23041
|
+
export function rememberStoredHostAliases(db) {
|
|
23042
|
+
try {
|
|
23043
|
+
storeMachine(db);
|
|
23044
|
+
}
|
|
23045
|
+
catch { /* none recorded, or no table to read: only the live names count */ }
|
|
23046
|
+
}
|
|
23047
|
+
/** Record the names this machine is known by right now beside its own id, read here at this moment (never a name a
|
|
23048
|
+
* witness, a note or another computer supplied). Nothing when this machine's id is unknown. */
|
|
23049
|
+
export function recordMachineHostAliases(db, now) {
|
|
23050
|
+
const id = currentMachineId();
|
|
23051
|
+
if (id === null)
|
|
23052
|
+
return;
|
|
23053
|
+
const names = liveHostAliases();
|
|
23054
|
+
const insert = db.prepare("INSERT OR IGNORE INTO machine_host_alias (machine_id, host, first_seen_at) VALUES (?, ?, ?)");
|
|
23055
|
+
for (const host of names)
|
|
23056
|
+
insert.run(id, host, now.toISOString());
|
|
23057
|
+
rememberHostAliases(id, names);
|
|
23058
|
+
}
|
|
23059
|
+
/** Whether a run_process row is this machine's (machine-identity.ts isThisMachine). */
|
|
23060
|
+
export function witnessIsLocal(row, here) {
|
|
23061
|
+
return isThisMachine({ host: String(row["host"]), machineId: row["machine_id"] == null ? null : String(row["machine_id"]) }, here);
|
|
23062
|
+
}
|
|
23063
|
+
/** The same rule as witnessIsLocal, as SQL over run_process (optionally aliased): its id is ours, or it has none and
|
|
23064
|
+
* its host is one of our names. With our id unknown, `machine_id = NULL` matches nothing: an id-bearing witness is
|
|
23065
|
+
* never taken for ours. */
|
|
23066
|
+
export function localWitnessSql(db, alias) {
|
|
23067
|
+
const p = alias === undefined ? "" : `${alias}.`, here = storeMachine(db);
|
|
23068
|
+
if (!witnessesCarryMachine(db))
|
|
23069
|
+
return { sql: `${p}host IN (SELECT value FROM json_each(?))`, params: [JSON.stringify(here.hosts)] };
|
|
23070
|
+
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)] };
|
|
23071
|
+
}
|
|
23072
|
+
/** Whether this database has v150's run_process.machine_id. An update settles leftover records on the database it is
|
|
23073
|
+
* about to back up, still at the older schema (toolroll-update.ts drain), so no query it reaches may assume the column:
|
|
23074
|
+
* there every witness has no id, and the host-name rule above is the whole rule. */
|
|
23075
|
+
export function witnessesCarryMachine(db) {
|
|
23076
|
+
return db.prepare("SELECT 1 FROM pragma_table_info('run_process') WHERE name = 'machine_id'").get() !== undefined;
|
|
23077
|
+
}
|
|
23078
|
+
// ---- Idle cleanup (checkout-cleanup.ts) ----------------------------------------------------------------------------
|
|
23079
|
+
/** Settings → Storage: how long a task stays idle before its clean folder goes (the default until someone chooses). */
|
|
23080
|
+
export function checkoutIdle(store) {
|
|
23081
|
+
const row = store.handle.prepare("SELECT idle FROM checkout_cleanup_setting WHERE id = 1").get();
|
|
23082
|
+
return row === undefined || row["idle"] === null ? DEFAULT_IDLE : String(row["idle"]);
|
|
23083
|
+
}
|
|
23084
|
+
/** Choose how long a task stays idle before its clean folder goes; the ledger keeps before → after. */
|
|
23085
|
+
export function setCheckoutIdle(store, idle, by, now) {
|
|
23086
|
+
return store.transact(() => {
|
|
23087
|
+
const before = checkoutIdle(store);
|
|
23088
|
+
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")
|
|
23089
|
+
.run(store.checkoutCleanup(), idle, by, now.toISOString());
|
|
23090
|
+
if (before !== idle)
|
|
23091
|
+
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)}` });
|
|
23092
|
+
return checkoutIdle(store);
|
|
23093
|
+
});
|
|
23094
|
+
}
|
|
23095
|
+
/** A check launch's execution stamp (check-stamp.ts), written by the code that launched it. One row per distinct launch. */
|
|
23096
|
+
export function recordCheckStamp(store, runId, stamp, now) {
|
|
23097
|
+
store.handle.prepare("INSERT OR IGNORE INTO check_stamp (run_id, digest, stamp, at) VALUES (?, ?, ?, ?)").run(runId, stampDigest(stamp), JSON.stringify(stamp), now.toISOString());
|
|
23098
|
+
}
|
|
23099
|
+
/** How a run's checks actually ran, by every launch it stamped: `unknown` when none is there or one can't be read. */
|
|
23100
|
+
export function runCheckStamp(store, runId) {
|
|
23101
|
+
return readStamps(store.handle.prepare("SELECT stamp FROM check_stamp WHERE run_id = ? ORDER BY id").all(runId).map(row => row["stamp"]));
|
|
23102
|
+
}
|
|
23103
|
+
/** The newest run of a task whose checks ran (it saved a check log), and how they ran; null when none has. */
|
|
23104
|
+
export function taskCheckStamp(store, repo, taskId) {
|
|
23105
|
+
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);
|
|
23106
|
+
for (const row of runs) {
|
|
23107
|
+
const runId = Number(row["id"]);
|
|
23108
|
+
if (store.artifactsFor(runId).some(one => one.kind === "check-log"))
|
|
23109
|
+
return { runId, reading: runCheckStamp(store, runId) };
|
|
23110
|
+
}
|
|
23111
|
+
return null;
|
|
23112
|
+
}
|