toolroll 0.9.75 → 0.9.77
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 +241 -0
- package/README.md +11 -4
- package/THIRD_PARTY_NOTICES.md +5 -5
- package/dist/agent-fence.js +36 -8
- package/dist/approval-authority.js +6 -2
- package/dist/approval-rules-ui.d.ts +11 -0
- package/dist/approval-rules-ui.js +13 -1
- package/dist/assignment.js +3 -0
- package/dist/batch-checks.js +3 -1
- package/dist/browser/THIRD_PARTY_NOTICES.txt +5 -5
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +31 -31
- package/dist/builder.d.ts +14 -0
- package/dist/builder.js +141 -42
- package/dist/chat-decide.js +4 -0
- package/dist/check-failure.d.ts +92 -0
- package/dist/check-failure.js +267 -0
- package/dist/check-progress.d.ts +28 -1
- package/dist/check-progress.js +105 -6
- package/dist/check-report.d.ts +52 -6
- package/dist/check-report.js +193 -8
- package/dist/checkout-cleanup.js +2 -2
- package/dist/cli.js +8 -5
- package/dist/coding-handoff.d.ts +1 -0
- package/dist/coding-handoff.js +20 -11
- package/dist/coding-workspace.js +7 -5
- package/dist/commit-extract.d.ts +6 -0
- package/dist/commit-extract.js +63 -8
- package/dist/console-chrome.d.ts +41 -0
- package/dist/console-chrome.js +166 -0
- package/dist/contracts/console-api.d.ts +1 -0
- package/dist/contracts/console-api.js +1 -0
- package/dist/contracts/release-check.d.ts +12 -0
- package/dist/contracts/release-check.js +17 -0
- package/dist/contracts/verification-receipt.d.ts +20 -2
- package/dist/contracts/verification-receipt.js +15 -3
- package/dist/contracts/verification-view.d.ts +8 -0
- package/dist/controller-supervisor.d.ts +8 -0
- package/dist/controller-supervisor.js +33 -1
- package/dist/demo.js +3 -2
- package/dist/deploy-gate.d.ts +3 -0
- package/dist/deploy-gate.js +8 -2
- package/dist/desktop-update.d.ts +1 -1
- package/dist/discover.js +6 -1
- package/dist/dispose.d.ts +3 -0
- package/dist/dispose.js +6 -3
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +10 -2
- package/dist/envelope.js +2 -1
- package/dist/evidence.js +4 -1
- package/dist/exec.d.ts +7 -0
- package/dist/exec.js +7 -1
- package/dist/first-run.js +3 -1
- package/dist/git-floor.d.ts +16 -0
- package/dist/git-floor.js +36 -0
- package/dist/git-incident.d.ts +49 -0
- package/dist/git-incident.js +69 -0
- package/dist/git-views.d.ts +20 -0
- package/dist/git-views.js +120 -0
- package/dist/grant.js +7 -1
- package/dist/guides.js +4 -3
- package/dist/operate.d.ts +13 -0
- package/dist/operate.js +779 -319
- package/dist/planner.js +5 -1
- package/dist/prepared-evidence.d.ts +2 -1
- package/dist/prepared-evidence.js +8 -3
- package/dist/project-knowledge.js +6 -2
- package/dist/project-learning.js +23 -3
- package/dist/proof.js +6 -0
- package/dist/pull-request-flow.js +4 -0
- package/dist/read-executor-worker.js +8 -4
- package/dist/read-executor.d.ts +18 -1
- package/dist/read-executor.js +8 -0
- package/dist/release-adapters.d.ts +71 -2
- package/dist/release-adapters.js +471 -12
- package/dist/release-authority.d.ts +46 -0
- package/dist/release-authority.js +68 -0
- package/dist/release-auto.d.ts +184 -0
- package/dist/release-auto.js +612 -0
- package/dist/release-card.d.ts +15 -3
- package/dist/release-card.js +25 -9
- package/dist/release-ci-hold.d.ts +41 -0
- package/dist/release-ci-hold.js +53 -0
- package/dist/release-cli.js +77 -14
- package/dist/release-coverage.js +5 -2
- package/dist/release-recheck.d.ts +1 -1
- package/dist/release-recheck.js +6 -1
- package/dist/release.d.ts +207 -6
- package/dist/release.js +495 -20
- package/dist/repository-context.js +4 -6
- package/dist/result-follow-ups.js +3 -1
- package/dist/review-context.js +8 -6
- package/dist/runner.js +2 -1
- package/dist/sandbox-runtime.d.ts +8 -0
- package/dist/sandbox-runtime.js +14 -2
- package/dist/scope.d.ts +31 -2
- package/dist/scope.js +51 -4
- package/dist/scout.js +5 -1
- package/dist/secret-mint.d.ts +1 -0
- package/dist/secret-mint.js +14 -0
- package/dist/serve.js +101 -51
- package/dist/server/chat.js +5 -5
- package/dist/server/render-tasks.d.ts +12 -0
- package/dist/server/render-tasks.js +25 -14
- package/dist/server/request-context.d.ts +6 -0
- package/dist/server/route-table.js +3 -0
- package/dist/server/runtime.d.ts +2 -0
- package/dist/server/settings.js +29 -2
- package/dist/server/task-views.js +15 -2
- package/dist/setup-guide.d.ts +2 -2
- package/dist/storage.d.ts +5 -0
- package/dist/storage.js +28 -1
- package/dist/store.d.ts +11 -1
- package/dist/store.js +44 -5
- package/dist/task-base.d.ts +4 -0
- package/dist/task-base.js +22 -2
- package/dist/teardown-git.d.ts +90 -13
- package/dist/teardown-git.js +1027 -129
- package/dist/test-infra.d.ts +26 -0
- package/dist/test-infra.js +321 -0
- package/dist/up-processes.d.ts +137 -0
- package/dist/up-processes.js +165 -0
- package/dist/up-role-child.d.ts +1 -0
- package/dist/up-role-child.js +77 -0
- package/dist/verification-evidence.d.ts +1 -0
- package/dist/verification-evidence.js +13 -1
- package/dist/worktree.d.ts +4 -0
- package/dist/worktree.js +13 -7
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,246 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.77 — 2026-10-29
|
|
4
|
+
|
|
5
|
+
- **CI finishes in about a third of the time.** The unit suite runs in four
|
|
6
|
+
shards per Node version instead of one 14–21 minute job. The required
|
|
7
|
+
checks keep their names ("node 22 · ubuntu-latest", "node 24 ·
|
|
8
|
+
ubuntu-latest") and pass only when every shard passed on the tested commit
|
|
9
|
+
and the shards together ran every test file once, so main's branch
|
|
10
|
+
protection needs no change. A flaky shard reruns in minutes, not twenty.
|
|
11
|
+
- **The release check's real-model journeys start sooner.** They run in the
|
|
12
|
+
browser lanes as soon as the scripted journeys end, instead of also waiting
|
|
13
|
+
for the unit suite; the check's load budget is unchanged.
|
|
14
|
+
- **An automatic release checks while CI runs.** With "Approve automatically
|
|
15
|
+
when checks pass" on, the release files its task and runs its check beside
|
|
16
|
+
CI instead of after it. Toolroll approves it only once every pull request
|
|
17
|
+
check passed on the commit.
|
|
18
|
+
- **A release asks for your approval while CI runs.** `toolroll release` files
|
|
19
|
+
the release task and asks you right away instead of waiting for GitHub's
|
|
20
|
+
checks; after you approve, the release check runs beside CI. The approval
|
|
21
|
+
says "GitHub's checks are still running; nothing deploys until they pass."
|
|
22
|
+
Nothing is deployed, merged, tagged or published until every required check
|
|
23
|
+
passed on the exact commit, read again right before the deploy; if one
|
|
24
|
+
fails, the release stops before the deploy. A check that already failed
|
|
25
|
+
still stops the release before anyone is asked.
|
|
26
|
+
- **The console stays responsive while builds and checks run.** `toolroll up`
|
|
27
|
+
now runs two processes: the console (pages, sign-in and approvals) and the
|
|
28
|
+
worker (builds, checks, follow-ups and housekeeping). On a copy of a real
|
|
29
|
+
database, the slowest console answer during a heavy check fell from about
|
|
30
|
+
5–6 seconds to under 0.3 seconds. A worker that crashes is started again
|
|
31
|
+
and recovers its interrupted work as a restarted `up` does; a stop still
|
|
32
|
+
lets running builds finish first. A running check's progress is saved at
|
|
33
|
+
most once a second, and its final result at once. `--single-process` keeps
|
|
34
|
+
the old one-process layout.
|
|
35
|
+
- **Console pages open faster and stay quick while work runs.** The sidebar,
|
|
36
|
+
the Tasks count and the crew list are read once per database change for each
|
|
37
|
+
person instead of up to four times per page, and before a page is drawn they
|
|
38
|
+
are read on a background thread so the console keeps answering meanwhile.
|
|
39
|
+
The chat landing's lists are kept the same way, and the task page reuses its
|
|
40
|
+
"behind main" line for 15 seconds instead of asking Git twice per view. On a
|
|
41
|
+
copy of a real database: /board 348 → 26 ms, /agents 320 → 4 ms, /chat 905 →
|
|
42
|
+
19 ms, a task page 1.5 s → 0.2 s; with the database changing 40 times a
|
|
43
|
+
second, pages take 0.27–0.47 s and the console answers other requests in
|
|
44
|
+
about 1 ms (before: 0.35 s typical, up to 1.4 s). A page that re-reads after
|
|
45
|
+
a live update gets "unchanged" when nothing it shows changed.
|
|
46
|
+
- **Fixes from the first review of the above.** CI's required checks now
|
|
47
|
+
judge the shards with the verifier from the base branch, run none of the
|
|
48
|
+
change's code, and fail when a shard executed no tests, its counts don't
|
|
49
|
+
add up, or it skips tests the base always ran; a required check counts only
|
|
50
|
+
from the CI workflow, never a same-named job elsewhere. A release filed
|
|
51
|
+
while CI runs can't be approved by anyone until Toolroll itself saw CI pass,
|
|
52
|
+
and you're asked only then. A worker that crashes while a removed project is
|
|
53
|
+
still finishing recovers that project's work before anything new starts. A
|
|
54
|
+
deploy waits until the console and the worker have both exited. A worker or
|
|
55
|
+
console that refuses to start again stops `toolroll up` instead of
|
|
56
|
+
restarting forever. A check's final progress is kept until it's saved.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
- **Sandboxed commands no longer hang on macOS 26.5.** The fence denies most
|
|
60
|
+
macOS system services and, by default, asked macOS to report each denial; on
|
|
61
|
+
macOS 26.5.2 about one denied lookup in 70 never returned while that report
|
|
62
|
+
was made, so a sandboxed `npm ci`, `npm pack` or any Node program setting up
|
|
63
|
+
DNS could hang forever (a release carry until its 20-minute timeout). Denials
|
|
64
|
+
are now made without the report; nothing the fence allows or denies changed.
|
|
65
|
+
- **The console is fast again.** 0.9.76's release-approval setting was read by
|
|
66
|
+
walking a project's whole action ledger once per task row, and every page's
|
|
67
|
+
sidebar runs that list four times, so each page blocked the console for about
|
|
68
|
+
3 seconds (more with tabs open while anything builds). It now reads the
|
|
69
|
+
setting rows through an existing index: pages went from about 2 s to about
|
|
70
|
+
0.4 s on a real database. Sizing build checkouts every half hour no longer
|
|
71
|
+
freezes the console either.
|
|
72
|
+
- **A release check's result is always saved.** The release check's streamed
|
|
73
|
+
part lines now carry a tag no Toolroll version misreads as a journey's own
|
|
74
|
+
line; an older console counted them twice, built a progress snapshot it then
|
|
75
|
+
refused, and the refusal stopped the check's log and result from being saved
|
|
76
|
+
(the check read as "unavailable"). A progress update that fails to save can
|
|
77
|
+
no longer stop sealing, and a result that can't be saved says so on the run.
|
|
78
|
+
- **A renamed or dropped CI job no longer hides its failure.** Every failed
|
|
79
|
+
attempt on a commit is judged before newer runs replace older ones, so a
|
|
80
|
+
job a newer run renamed or dropped can't erase a test failure from an
|
|
81
|
+
automatic approval. The Windows containment repeat job keeps one name
|
|
82
|
+
whatever its count.
|
|
83
|
+
- **Toolroll decides what a Full release check owes.** Before the check runs,
|
|
84
|
+
Toolroll works out from its own record of the last release which parts it
|
|
85
|
+
owes (units, scripted and real-model journeys, upgrades), seals that, and
|
|
86
|
+
tells the check, which runs at least those. Every owed part needs a result,
|
|
87
|
+
whatever the check's report says it selected. CI never stands in for a
|
|
88
|
+
release's unit suite, and a journey or upgrade run that listed nothing it
|
|
89
|
+
ran is no result.
|
|
90
|
+
- **Missing evidence is unknown, not clean.** What passed only on a retry is
|
|
91
|
+
sealed in the check's receipt. If a check's report is lost or damaged, its
|
|
92
|
+
retries are unknown and nothing approves itself; the receipt's report and
|
|
93
|
+
inventory files are checked byte for byte. A check that passed only when
|
|
94
|
+
rerun after the approved setup counts as a retry, and the setup rerun only
|
|
95
|
+
happens when the check never started.
|
|
96
|
+
- **Merging checks main on GitHub itself.** A release reads main's head and
|
|
97
|
+
compares the branch through GitHub's API, never a local ref, and reads
|
|
98
|
+
main again right before merging. Releases approve themselves only while
|
|
99
|
+
main's branch protection requires branches to be up to date before
|
|
100
|
+
merging; without it, a person's release says which setting to turn on.
|
|
101
|
+
Toolroll only reads that setting.
|
|
102
|
+
- **No lazy fetch on any supported git.** Protected Git reads (the release's
|
|
103
|
+
object reader and extraction, project knowledge and learning, the task
|
|
104
|
+
base, release coverage, review screenshots, grants) run through a frozen
|
|
105
|
+
copy of the repository's config on every git version, refuse every
|
|
106
|
+
network transport and are time-bounded, so an include, a partial clone or
|
|
107
|
+
a planted filter can't fetch, run or hang anything. The deployment script
|
|
108
|
+
reads a commit's files with no filter or attribute applied. CI's oldest-git
|
|
109
|
+
lane now tests git 2.41.0 exactly.
|
|
110
|
+
- **Main's protection must actually hold.** A release now reads main's whole
|
|
111
|
+
branch protection and rulesets and counts them only when they require
|
|
112
|
+
branches to be up to date, require the release's CI checks, and can't be
|
|
113
|
+
bypassed by the account that merges ("Do not allow bypassing the above
|
|
114
|
+
settings"; no ruleset bypass for a role, team, app or organization admin).
|
|
115
|
+
A person's release names exactly what is missing, and says which new CI
|
|
116
|
+
checks main's protection doesn't require yet.
|
|
117
|
+
- **Deployment reads only verified objects, with none of the repository's
|
|
118
|
+
config.** The deployment script hashes the commit and every tree as well
|
|
119
|
+
as every file against their ids, and runs git on a copy of the
|
|
120
|
+
repository's layout with a config of its own, so a corrupted tree, a
|
|
121
|
+
config change mid-read or a custom transport helper can't substitute files
|
|
122
|
+
or fetch anything.
|
|
123
|
+
- **An unreadable last release never shrinks a check.** When the last
|
|
124
|
+
release Toolroll recorded can't be verified, the release check doesn't
|
|
125
|
+
run and says so, and the release card's change list reads "couldn't be
|
|
126
|
+
read", instead of acting as if there were no previous release.
|
|
127
|
+
- **Setup recovery needs proof the check never started.** The approved setup
|
|
128
|
+
reruns a check only when Toolroll itself found the check's program missing
|
|
129
|
+
before starting it and the shell said exactly that and nothing else. Any
|
|
130
|
+
other failure is the check's own result for a person to judge, and a
|
|
131
|
+
release after a setup rerun says why the first attempt failed.
|
|
132
|
+
- **Dispatch and coding handoffs can't be held up by a project's Git
|
|
133
|
+
config.** The worktree binding reads Git's registry as plain files, and the
|
|
134
|
+
handoff's branch read is bounded and runs outside its database
|
|
135
|
+
transactions.
|
|
136
|
+
- **A release check that's cut short still leaves its evidence.** Every
|
|
137
|
+
part of the release check (typecheck, build, units, journeys, upgrades)
|
|
138
|
+
now prints its output as it runs, each line tagged with its part (and
|
|
139
|
+
upgrade version), instead of only at the very end. Each part's end is
|
|
140
|
+
recorded with its start time, exit code, signal, timeout and whether it
|
|
141
|
+
was stopped, and a failing upgrade version's own log is printed the moment
|
|
142
|
+
it fails. Release check 3261 was stored as "exit 1" with nothing but its
|
|
143
|
+
header: a signal had ended it, the plane recorded the signal as a plain
|
|
144
|
+
exit 1, and the parts' output was still waiting to be printed. A command
|
|
145
|
+
ended by a signal, or by Toolroll's own shutdown, now says so.
|
|
146
|
+
- **No complete result, no pass.** A release's Full check counts only with
|
|
147
|
+
its valid report (carrying that run's secret), a result for every part,
|
|
148
|
+
unit file, journey group and upgrade version it owed, and a successful
|
|
149
|
+
exit. A missing, malformed, cut-short or incomplete report is an error
|
|
150
|
+
("no result"), never a pass.
|
|
151
|
+
- **Failures are classed, and only Toolroll's own infrastructure is rerun.**
|
|
152
|
+
The flat "two flakes" allowance is gone. A failing test, typecheck, build,
|
|
153
|
+
attack test or upgrade is never rerun to pass: fix the branch. A failure
|
|
154
|
+
Toolroll itself confirms as infrastructure (the check never started, the
|
|
155
|
+
worker lost the build, Toolroll's shutdown stopped it, or a GitHub job
|
|
156
|
+
whose runner failed before any of its steps ran) is checked again on the
|
|
157
|
+
same commit under the same approval, at most once per incident and twice
|
|
158
|
+
per commit, with no new approval. An unexplained failure blocks: a
|
|
159
|
+
diagnostic rerun may run, but its pass doesn't erase the failure, so you
|
|
160
|
+
decide. Real-model journeys are counted, never rerun until they pass, and
|
|
161
|
+
one that passed only on a retry is now reported (an automatic approval
|
|
162
|
+
won't approve it). `toolroll release` says which of these happened and
|
|
163
|
+
what to do next, instead of always asking for `--new`.
|
|
164
|
+
- **A password that starts with "--" works on the command line.** About one
|
|
165
|
+
minted password in 4096 began with "--", and `toolroll release publish-ok`
|
|
166
|
+
(and other commands) read it as a missing value. A `--token` or
|
|
167
|
+
`--password` value is now always the next argument, `--token=<value>`
|
|
168
|
+
works everywhere, and new passwords, invites and tokens never start with
|
|
169
|
+
"-".
|
|
170
|
+
- **CI tests the commit a release trusts.** Every pull-request job now checks
|
|
171
|
+
out the pull request's own head commit, not the merge commit GitHub makes,
|
|
172
|
+
and proves it before building.
|
|
173
|
+
- **Git 2.41 or newer is required, and tested.** Toolroll's hardened git reads
|
|
174
|
+
need `git --attr-source`, which arrived in git 2.41. `toolroll up`, `serve`
|
|
175
|
+
and `watch` won't start on an older git, and `toolroll doctor` names it,
|
|
176
|
+
each with the installed and the required version. Git 2.41 to 2.44 has no
|
|
177
|
+
`--no-lazy-fetch`: there Toolroll leaves it out and instead refuses a
|
|
178
|
+
project that is a partial clone for protected reads ("this project is a
|
|
179
|
+
partial clone; protected reads need git 2.45 or newer"), judged from the
|
|
180
|
+
frozen copy of the config git then runs with (never checked, then run),
|
|
181
|
+
and every read still refuses all network protocols.
|
|
182
|
+
Two CI jobs run the git attack and worktree tests on git 2.41 and 2.47, as
|
|
183
|
+
an unprivileged user, fail if any test is skipped, and are now required
|
|
184
|
+
for a release.
|
|
185
|
+
- **A release branch must contain main.** CI tests the pull request's own
|
|
186
|
+
commit, so `toolroll release` refuses a branch that doesn't contain main's
|
|
187
|
+
head ("the branch is behind main; update it and push"), at the gate before
|
|
188
|
+
anything is filed and again right before the merge.
|
|
189
|
+
- **Tests can't lean on a guessed git identity.** Each test's home tells git
|
|
190
|
+
never to guess a name and email from the host, so a test that commits
|
|
191
|
+
without setting one fails on a Mac just as it would on Linux CI.
|
|
192
|
+
- **Two timing-sensitive tests now wait for events.** The read-worker test and
|
|
193
|
+
the watch recovery test no longer fail when a busy machine runs slowly.
|
|
194
|
+
|
|
195
|
+
## 0.9.76 — 2026-10-28
|
|
196
|
+
|
|
197
|
+
- **Releases can approve themselves when they're green.** A new per-project
|
|
198
|
+
setting, **Release approval: Ask me / Approve automatically when checks
|
|
199
|
+
pass**, is off by default. Turning it on takes your typed password;
|
|
200
|
+
turning it off takes one click and withdraws every automatic approval not
|
|
201
|
+
yet merged. When it's on, a release task first reads "Checking
|
|
202
|
+
automatically" (`auto-check`), which lets its release check run and
|
|
203
|
+
nothing else. Once GitHub CI and the release check both pass on the exact
|
|
204
|
+
candidate, it approves itself (`auto-green`) and the release continues as
|
|
205
|
+
after your yes. You're told, not asked.
|
|
206
|
+
- **What an automatic approval must prove.** Up to two failed attempts per
|
|
207
|
+
CI job, and two failed release-check builds on the same commit, are
|
|
208
|
+
tolerated as flakes. Past that it asks you. The change must touch only
|
|
209
|
+
allowed paths: product code and its tests, test files under `test/`,
|
|
210
|
+
docs, the changelog and a version-only `package.json` bump. Scripts, CI,
|
|
211
|
+
configuration, dependencies, policy files and test helpers (found from the
|
|
212
|
+
tests' own imports) need a person. CI is read again before the deploy, the
|
|
213
|
+
CLI switch, the merge and the tag. Anything not green stops the release,
|
|
214
|
+
withdraws the approval and tells you.
|
|
215
|
+
- **A red CI after the merge holds publishing.** No tag, npm publish or
|
|
216
|
+
Homebrew update happens until a person runs
|
|
217
|
+
`toolroll release publish-ok <task>` with their password.
|
|
218
|
+
- **Stricter CI verdicts on every release, yours too.** Only checks on the
|
|
219
|
+
exact head count. Required checks must come from GitHub Actions, so
|
|
220
|
+
another app's same-name success can't hide a failure. A newer run of the
|
|
221
|
+
same workflow supersedes its older runs on the commit.
|
|
222
|
+
- **Toolroll's own git on agent checkouts runs against a frozen view.**
|
|
223
|
+
Every git command the plane runs on a checkout an agent wrote (staging,
|
|
224
|
+
committing, cleanup, pool refresh, scouts, planners, coding sessions, page
|
|
225
|
+
reads) runs against a private copy of the repository's settings taken
|
|
226
|
+
once. Changes the agent makes to them afterwards are never read.
|
|
227
|
+
- **Git settings are denied by default.** Only audited config sections and
|
|
228
|
+
variables pass. Aliases, URL rewrites that reach remote helpers, empty
|
|
229
|
+
filter names and `hook.*` are refused. Writes allow no network transport.
|
|
230
|
+
Local git-lfs installs still work.
|
|
231
|
+
- **Writes are bound to the project's recorded repository.** A checkout
|
|
232
|
+
whose `.git` points anywhere else is refused. Before git writes through a
|
|
233
|
+
view, every path it could write is walked for links and hard links. Plane
|
|
234
|
+
git writes on one checkout run one at a time. On git before 2.48, a
|
|
235
|
+
checkout added through the view is pointed at its repository, not at the
|
|
236
|
+
view.
|
|
237
|
+
- **A git incident is recorded and you're notified.** A swap seen after git
|
|
238
|
+
ran is logged and raised as an attention notification. The notification
|
|
239
|
+
names the project by Toolroll's own record, never by a path the agent
|
|
240
|
+
chose.
|
|
241
|
+
- **`discover --dirty` reads repositories through the same hardened git.**
|
|
242
|
+
- Database: schema v136 (unchanged).
|
|
243
|
+
|
|
3
244
|
## 0.9.75 — 2026-10-27
|
|
4
245
|
|
|
5
246
|
- **Opening a page only shows it.** Pages no longer run checks, tidy up or
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ npx toolroll up
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
That starts the app and a builder for your projects, then opens the console
|
|
37
|
-
at http://127.0.0.1:4180. Runs on macOS or Linux with Node.js 22.13+ and git.
|
|
37
|
+
at http://127.0.0.1:4180. Runs on macOS or Linux with Node.js 22.13+ and git 2.41+.
|
|
38
38
|
Try the sandbox first with `npx toolroll demo`: ask its scripted lead for a
|
|
39
39
|
change in Chat, approve the plan, and watch it land Ready. It never calls a
|
|
40
40
|
model, spends or reaches outside.
|
|
@@ -275,9 +275,16 @@ npx toolroll up --project-root ~/Projects # or: bunx toolroll up
|
|
|
275
275
|
|
|
276
276
|
That is the install and the setup. It needs Node 22.13 or newer on the
|
|
277
277
|
machine (Bun's runtime has no `node:sqlite`; `bunx` hands the shebang to
|
|
278
|
-
Node, so it works too)
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
Node, so it works too), and git 2.41 or newer: Toolroll reads agent-written
|
|
279
|
+
checkouts with `git --attr-source`, which older git refuses, and `toolroll
|
|
280
|
+
up`, `serve`, `watch` and `doctor` say so when the installed git is older.
|
|
281
|
+
Ubuntu 24.04 ships 2.43, which works; Debian 12 ships 2.39: install a newer
|
|
282
|
+
one there. On git older than 2.45 (no `git --no-lazy-fetch`), a project that
|
|
283
|
+
is a partial clone (`git clone --filter=…`) is refused for Toolroll's
|
|
284
|
+
protected reads: use git 2.45 or newer for those, or a full clone. CI runs
|
|
285
|
+
the git tests on 2.41 and on 2.47.
|
|
286
|
+
`npm install -g toolroll` gives you the bare `toolroll` command for later
|
|
287
|
+
(`standing-orders`, the older name, still works).
|
|
281
288
|
|
|
282
289
|
`up` prints your login once (and saves it beside the database as
|
|
283
290
|
`up-login.txt`), opens the app in your browser, and connects this machine as
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -247,14 +247,14 @@ limitations under the License.
|
|
|
247
247
|
|
|
248
248
|
## shadcn/ui — MIT
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
Dialog composition in `src/browser/ui/primitives.tsx` adapts:
|
|
251
251
|
https://github.com/shadcn-ui/ui/tree/a87a63b2ca25143d26c8bd0903e4e9bc77b3f824/apps/v4/registry/new-york-v4/ui
|
|
252
252
|
|
|
253
253
|
Source inspected 20 September 2026, revision
|
|
254
|
-
`a87a63b2ca25143d26c8bd0903e4e9bc77b3f824`,
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
`a87a63b2ca25143d26c8bd0903e4e9bc77b3f824`, file `dialog.tsx`. Adaptations
|
|
255
|
+
replace Tailwind/CVA with local class variants, use individual Radix packages,
|
|
256
|
+
and leave dialog close controls to the calling surface. Radix supplies focus
|
|
257
|
+
and dismissal behavior.
|
|
258
258
|
|
|
259
259
|
The rebuilt views (23 September 2026) add `src/browser/components/ui/`:
|
|
260
260
|
button, badge, card, tabs, radio-group, select, switch, label, input,
|
package/dist/agent-fence.js
CHANGED
|
@@ -25,13 +25,14 @@ import { INSTANCE_DIR } from "./console-instance.js";
|
|
|
25
25
|
import { ACP_AGENTS, ACP_PROVIDER_IDS } from "./acp-agents.js";
|
|
26
26
|
import { spawnSync } from "node:child_process";
|
|
27
27
|
import { createHash } from "node:crypto";
|
|
28
|
-
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
28
|
+
import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
29
29
|
import { createRequire } from "node:module";
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
31
|
import { homedir, tmpdir } from "node:os";
|
|
32
32
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
33
33
|
import { readMonitoring } from "./monitoring-settings.js";
|
|
34
34
|
import { existingFolders } from "./names.js";
|
|
35
|
+
import { ensureGitViewsRoot, gitViewsRoot } from "./git-views.js";
|
|
35
36
|
import { DEPS_FOLDER, linkedKey, readyCopy, sharedCopies } from "./shared-deps.js";
|
|
36
37
|
import { SEATBELT_PROBE, sandboxRuntimeProblem, srtSpawn } from "./sandbox-runtime.js";
|
|
37
38
|
import { protectedStorageDenies, protectedStorageNames } from "./protected-storage.js";
|
|
@@ -52,6 +53,22 @@ function inside(child, parent) {
|
|
|
52
53
|
}
|
|
53
54
|
export class CheckoutBindingError extends Error {
|
|
54
55
|
}
|
|
56
|
+
/** One small file read only as the regular file it is: never through a link, never blocking on a FIFO; null otherwise. */
|
|
57
|
+
function regularText(path) {
|
|
58
|
+
let fd = null;
|
|
59
|
+
try {
|
|
60
|
+
fd = openSync(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK);
|
|
61
|
+
const held = fstatSync(fd);
|
|
62
|
+
return held.isFile() && held.size <= 64 * 1024 ? readFileSync(fd, "utf8") : null;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (fd !== null)
|
|
69
|
+
closeSync(fd);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
55
72
|
export const HARDENED_GIT_ARGS = ["--no-optional-locks", "-c", "core.fsmonitor=false", "-c", "core.hooksPath=/dev/null"];
|
|
56
73
|
const GIT_ENV_NAMES = ["PATH", "HOME", "TMPDIR", "TMP", "TEMP", "SystemRoot", "LANG", "LC_ALL", "CODEX_HOME"];
|
|
57
74
|
export function checkoutGitEnv(base = process.env) {
|
|
@@ -85,18 +102,20 @@ export function trustedWorktreeBinding(repository, worktree, poolRoots = []) {
|
|
|
85
102
|
}
|
|
86
103
|
if (!lstatSync(join(main, ".git")).isDirectory() || !lstatSync(registry).isDirectory())
|
|
87
104
|
throw fail();
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
// The trusted checkout's own worktree registry, never the candidate's configurable Git pointer — read as files, with
|
|
106
|
+
// no git at all (eval-harness review 2, finding 6): the repository's config is shared with every build, so an
|
|
107
|
+
// include naming a FIFO could hold a git read (and dispatch) up; a FIFO or link planted as a registry file is never
|
|
108
|
+
// opened as one (regularText).
|
|
92
109
|
for (const entry of readdirSync(registry)) {
|
|
93
110
|
const path = join(registry, entry);
|
|
94
111
|
if (!lstatSync(path).isDirectory() || !inside(real(path), real(registry)))
|
|
95
112
|
continue;
|
|
96
|
-
|
|
113
|
+
const pointer = regularText(join(path, "gitdir"));
|
|
114
|
+
if (pointer === null || real(pointer.trim()) !== join(checkout, ".git"))
|
|
97
115
|
continue;
|
|
98
116
|
const gitdir = real(path);
|
|
99
|
-
|
|
117
|
+
const common = regularText(join(gitdir, "commondir"));
|
|
118
|
+
if (common === null || worktreeGitDir(checkout) !== gitdir || real(join(gitdir, common.trim())) !== real(join(main, ".git")))
|
|
100
119
|
throw fail();
|
|
101
120
|
return { main, gitdir };
|
|
102
121
|
}
|
|
@@ -175,6 +194,13 @@ export function agentFence(options) {
|
|
|
175
194
|
fence.add(join(state, entry));
|
|
176
195
|
}
|
|
177
196
|
}
|
|
197
|
+
// The plane's private views of repositories for its own git (git-views.ts): made first, so no build is the one to
|
|
198
|
+
// create them, and denied by name whether or not that worked (a sandbox that can't cover the path refuses to launch).
|
|
199
|
+
try {
|
|
200
|
+
ensureGitViewsRoot(home);
|
|
201
|
+
}
|
|
202
|
+
catch { /* fenced by name below either way */ }
|
|
203
|
+
fence.add(gitViewsRoot(home));
|
|
178
204
|
for (const secrets of existingFolders(home, { dot: true }))
|
|
179
205
|
fence.add(secrets);
|
|
180
206
|
// v104: the folder the audit stream writes to holds the whole instance's history.
|
|
@@ -662,7 +688,9 @@ export function fencedGitEnv(base = process.env) {
|
|
|
662
688
|
/** The repository a git worktree belongs to (its `.git` file names the main repository's git folder). */
|
|
663
689
|
function worktreeGitDir(worktree) {
|
|
664
690
|
try {
|
|
665
|
-
|
|
691
|
+
// The candidate's own pointer file: a FIFO or link there is never opened (review 2, finding 6).
|
|
692
|
+
const text = regularText(join(worktree, ".git"));
|
|
693
|
+
const pointer = text === null ? undefined : /^gitdir:\s*(.+)$/m.exec(text)?.[1]?.trim();
|
|
666
694
|
return pointer === undefined ? null : real(isAbsolute(pointer) ? pointer : join(worktree, pointer));
|
|
667
695
|
}
|
|
668
696
|
catch {
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* An allowlist, never a deny-list: a person's yes (`password`, `chat`) stands unless a machine made that seal; a mode
|
|
6
6
|
* or a flow grant stands while its signature does; a standing order's firing (`schedule`, or an old firing stored as
|
|
7
|
-
* `password`) stands while its approver can still approve in the project
|
|
7
|
+
* `password`) stands while its approver can still approve in the project; an automatic release seal (`auto-check`,
|
|
8
|
+
* `auto-green`: docs/plans/auto-approve-green.md) stands on its own evidence row while the project's setting is on and
|
|
9
|
+
* the person who turned it on can still approve there. Everything else — the lead (`lead`, which
|
|
8
10
|
* phase 1 never lets stand), a null basis, a word this code doesn't write — stands on nothing.
|
|
9
11
|
*
|
|
10
12
|
* The seal's origin is read from the acts recorded AT the seal's own instant, exactly as scope.ts sealOriginOf reads
|
|
@@ -12,6 +14,7 @@
|
|
|
12
14
|
*/
|
|
13
15
|
import { grantStandsSql } from "./flow-unattended.js";
|
|
14
16
|
import { accountReachesSql } from "./project-access.js";
|
|
17
|
+
import { autoSealSql } from "./release-authority.js";
|
|
15
18
|
/** The proofs a person gives (src/person-proof.ts), as an SQL list. */
|
|
16
19
|
const PERSON_PROOFS = "('password-now', 'sso-now', 'fresh-password', 'fresh-sso', 'session', 'cli-login', 'chat', 'passkey')";
|
|
17
20
|
/** SQL over a `task_scope` alias and a repo expression: whether that row's approval authority stands at `now` (an SQL
|
|
@@ -25,7 +28,8 @@ export const authorityStandsSql = (scope, repo, now) => `COALESCE((((COALESCE(${
|
|
|
25
28
|
WHERE om.repo = ${repo} AND om.revoked_at IS NULL AND om.absolute_expiry > ${now} AND om.digest = ${scope}.mode_digest
|
|
26
29
|
AND signer.revoked_at IS NULL AND signer.role = 'approver' AND ${accountReachesSql("signer", repo)}))
|
|
27
30
|
OR (${scope}.approval_basis = 'grant' AND ${grantStandsSql(`${scope}.mode_digest`, now, repo)})
|
|
28
|
-
OR (${scope}.approval_basis = 'schedule' AND ${approverStandsSql(scope, repo)})
|
|
31
|
+
OR (${scope}.approval_basis = 'schedule' AND ${approverStandsSql(scope, repo)})
|
|
32
|
+
OR (${scope}.approval_basis IN ('auto-check', 'auto-green') AND ${autoSealSql(scope, repo)} AND ${approverStandsSql(scope, repo)}))
|
|
29
33
|
AND ${releaseStandsSql(scope, now)}), 0)`;
|
|
30
34
|
/** SQL: a `carried` seal stands only on its own carry row — this task, this approved digest, this candidate, and the
|
|
31
35
|
* reference `<authority>:<carry>` the carry transaction wrote (release-once.md). */
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { type Html } from "./html.js";
|
|
8
8
|
import type { ApprovalRules } from "./approval-policy.js";
|
|
9
|
+
/** The rules' style, and the same for the Releases section under its own class (so a page holds one `.approval-rules`
|
|
10
|
+
* form, not two). */
|
|
9
11
|
export declare const APPROVAL_RULES_CSS: string;
|
|
10
12
|
export type ApprovalRulesView = {
|
|
11
13
|
repo: string;
|
|
@@ -20,6 +22,15 @@ export type ApprovalRulesView = {
|
|
|
20
22
|
};
|
|
21
23
|
/** The rules in plain words, for anyone who can see the project. */
|
|
22
24
|
export declare function rulesSummary(rules: ApprovalRules): string;
|
|
25
|
+
/** Settings → Approval rules → Releases (docs/plans/auto-approve-green.md): who approves this project's releases. */
|
|
26
|
+
export type ReleaseApprovalView = {
|
|
27
|
+
repo: string;
|
|
28
|
+
auto: boolean;
|
|
29
|
+
setBy: string | null;
|
|
30
|
+
at: string | null;
|
|
31
|
+
canChange: boolean;
|
|
32
|
+
};
|
|
33
|
+
export declare function releaseApprovalHtml(view: ReleaseApprovalView): Html;
|
|
23
34
|
export declare function approvalRulesHtml(view: ApprovalRulesView, notice: {
|
|
24
35
|
said?: string | null;
|
|
25
36
|
problem?: string | null;
|
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
* ledger, before → after.
|
|
6
6
|
*/
|
|
7
7
|
import { html, postForm, reauth } from "./html.js";
|
|
8
|
-
|
|
8
|
+
const RULES_CSS = `.approval-rules{max-width:720px;min-width:0}.approval-rules fieldset{border:0;padding:0;margin:18px 0 0;min-width:0}` +
|
|
9
9
|
`.approval-rules legend{font-weight:600;font-size:.875rem;margin-bottom:8px}.approval-rules .choice{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;align-items:start;margin:0 0 10px;font-weight:400}` +
|
|
10
10
|
`.approval-rules .choice input{margin:.2rem 0 0}.approval-rules .choice strong{display:block;font-size:.875rem;font-weight:500}.approval-rules .choice small{display:block;color:var(--muted-foreground);font-size:.8125rem;margin-top:2px}` +
|
|
11
11
|
`.approval-rules textarea{min-height:88px;font-family:var(--font-mono);font-size:.8125rem}.approval-rules .paths{margin:4px 0 0 26px}` +
|
|
12
12
|
`.approval-rules .current{margin:0 0 4px}.approval-rules .step-up{margin-top:16px}.approval-rules button[type=submit]{margin-top:14px}`;
|
|
13
|
+
/** The rules' style, and the same for the Releases section under its own class (so a page holds one `.approval-rules`
|
|
14
|
+
* form, not two). */
|
|
15
|
+
export const APPROVAL_RULES_CSS = RULES_CSS + RULES_CSS.replaceAll(".approval-rules", ".release-approval");
|
|
13
16
|
/** The rules in plain words, for anyone who can see the project. */
|
|
14
17
|
export function rulesSummary(rules) {
|
|
15
18
|
const parts = [];
|
|
@@ -21,6 +24,15 @@ export function rulesSummary(rules) {
|
|
|
21
24
|
parts.push(`Work touching ${rules.protectedPaths.join(", ")} is protected: two people approve it, never a subagent or an operating mode.`);
|
|
22
25
|
return parts.length === 0 ? "No approval rules: anyone who can approve on this project can approve its work, including their own." : parts.join(" ");
|
|
23
26
|
}
|
|
27
|
+
export function releaseApprovalHtml(view) {
|
|
28
|
+
const since = view.setBy === null ? "" : html ` <span class="meta">Changed by ${view.setBy} on ${(view.at ?? "").slice(0, 10)}.</span>`;
|
|
29
|
+
const current = html `<p class="current">${view.auto ? "Releases approve themselves once the pull request's CI and the release check pass on the exact commit. Anything red still asks you." : "Every release waits for your approval."}${since}</p>`;
|
|
30
|
+
const heading = html `<h2>Release approval</h2>`;
|
|
31
|
+
if (!view.canChange)
|
|
32
|
+
return html `<section class="release-approval">${heading}${current}<p class="meta">An approver of this project sets this.</p></section>`;
|
|
33
|
+
const choice = (value, title, hint) => html `<label class="choice"><input type="radio" name="action" value="${value}"${(view.auto ? "auto" : "ask") === value ? html ` checked` : ""}><span><strong>${title}</strong><small>${hint}</small></span></label>`;
|
|
34
|
+
return html `<section class="release-approval">${heading}${current}${postForm("/settings/release-approval", html `<fieldset aria-label="Release approval">${choice("ask", "Ask me", "A release deploys only after you approve it.")}${choice("auto", "Approve automatically when checks pass", "You're told when a release approves itself. A failed check, red CI or a CI failure after the deploy still stops and asks you.")}</fieldset><div class="step-up"><label>Your Toolroll password (to approve automatically)<input type="password" name="password" autocomplete="current-password"></label></div><button type="submit">Save</button>`, { hidden: { repo: view.repo } })}</section>`;
|
|
35
|
+
}
|
|
24
36
|
export function approvalRulesHtml(view, notice) {
|
|
25
37
|
const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
|
|
26
38
|
const changed = view.rules.updatedBy === null ? "" : html ` <span class="meta">Changed by ${view.rules.updatedBy} on ${(view.rules.updatedAt ?? "").slice(0, 10)}.</span>`;
|
package/dist/assignment.js
CHANGED
|
@@ -140,6 +140,9 @@ function buildChecks(store, root, runId) {
|
|
|
140
140
|
if (receipt.result.failure === "coverage-incomplete")
|
|
141
141
|
return { status: "failed", exitCode: null, command: receipt.command.command, logArtifactId: receipt.log.artifactId, level,
|
|
142
142
|
detail: "Checks didn't cover everything this release owed, or a required test went missing." };
|
|
143
|
+
if (receipt.result.failure === "no-result")
|
|
144
|
+
return { status: "failed", exitCode: null, command: receipt.command.command, logArtifactId: receipt.log.artifactId, level,
|
|
145
|
+
detail: "Checks gave no complete result, so they can't count as a pass." };
|
|
143
146
|
// The sealed receipt names the grant that ran: the quick one, or the full one.
|
|
144
147
|
const sealedLevel = typeof receipt.command?.repo === "string" && receipt.command.repo.startsWith("quick:") ? "quick" : "full";
|
|
145
148
|
const words = sealedLevel === "quick" ? "Quick checks" : "Checks";
|
package/dist/batch-checks.js
CHANGED
|
@@ -3,6 +3,7 @@ import { mkdtempSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { run as execRun } from "./exec.js";
|
|
6
|
+
import { hardenedGit } from "./teardown-git.js";
|
|
6
7
|
import { approvedCommandShell, SETUP_ENV_ALLOWLIST, SETUP_ENV_DENYLIST } from "./builder.js";
|
|
7
8
|
import { runWithIsolatedDatabase } from "./child-database.js";
|
|
8
9
|
import { boundStreamHeadTail, redactSecretLines, scanForSecrets, storeEvidence } from "./evidence.js";
|
|
@@ -86,7 +87,8 @@ function finishOne(store, one, repo, task, status, detail, at) {
|
|
|
86
87
|
async function checkCohort(store, root, repo, cohort, deps) {
|
|
87
88
|
const now = deps.now ?? (() => new Date());
|
|
88
89
|
const runner = deps.runner ?? execRun;
|
|
89
|
-
|
|
90
|
+
// The project's repository and checkouts of builds' commits, both agent-writable: the hardened git (teardown-git.ts).
|
|
91
|
+
const git = deps.git ?? hardenedGit(execRun);
|
|
90
92
|
const command = store.liveVerifyCommand(repo);
|
|
91
93
|
if (command === null)
|
|
92
94
|
return 0;
|
|
@@ -247,14 +247,14 @@ limitations under the License.
|
|
|
247
247
|
|
|
248
248
|
## shadcn/ui — MIT
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
Dialog composition in `src/browser/ui/primitives.tsx` adapts:
|
|
251
251
|
https://github.com/shadcn-ui/ui/tree/a87a63b2ca25143d26c8bd0903e4e9bc77b3f824/apps/v4/registry/new-york-v4/ui
|
|
252
252
|
|
|
253
253
|
Source inspected 20 September 2026, revision
|
|
254
|
-
`a87a63b2ca25143d26c8bd0903e4e9bc77b3f824`,
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
`a87a63b2ca25143d26c8bd0903e4e9bc77b3f824`, file `dialog.tsx`. Adaptations
|
|
255
|
+
replace Tailwind/CVA with local class variants, use individual Radix packages,
|
|
256
|
+
and leave dialog close controls to the calling surface. Radix supplies focus
|
|
257
|
+
and dismissal behavior.
|
|
258
258
|
|
|
259
259
|
The rebuilt views (23 September 2026) add `src/browser/components/ui/`:
|
|
260
260
|
button, badge, card, tabs, radio-group, select, switch, label, input,
|