Repository navigation
Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] - #809
Conversation
…o where it claimed it did (#583) Measured on a machine with v0.5.2 applied with --no-dynamic-nodes and rebooted: `sudo nvidia-smi -L` put nvidia-uvm and nvidia-uvm-tools back to root:root 0666 with ModifyDeviceFiles: 0 in effect. The installer's header, its apply message, the fixer's header and docs/test-runs.md said the option makes NVML change no node. Each now says it doesn't, and what to do after a root NVML call until #583's fixer lands: run the fixer, then check. Text only: no command, unit or mode changes. Test (fails without): none; comments, one echoed message and docs only, no behaviour change. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…nd notes the release keeps An audit of every PR merged since v0.5.2 found install-affecting changes missing from the v0.5.3 notes: provider hosts in root's providers.json (#607), the egress rule's reload for test users and subordinate uids (#686, #778), the dashboard no longer taking a Claude credential (#749, #751), the broker's ProtectControlGroups (#596), managed apps and the wider locks (#691, #765), the settings check (#649) and others. They are added, with exact root commands. The sudoers line now says one rule is removed and the machine helper's rules are unchanged. v0.5.2's "NVML changes no node at all" gets a dated correction in place (#583), and v0.5.3 says what to do instead. release.mjs changelog keeps only what sits above the first heading it writes itself, and the hand- written "### Guardrail changes" notes sat below one, so a release would have dropped all eight of them (measured with scripts/lib/changelog.mjs). They now sit under "Guardrail changes: what each one does", ended by an empty generated heading. The repo test that guards this read only the lines above that heading; a new test requires every line of an unreleased section to survive. Test (fails without): test/changelog.test.ts "an unreleased section is all hand-written, so the release keeps every line of it, under any heading". Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…ged-apps step is named a loosening Found in this PR's adversarial pass: a reader following the egress step could run test-user.sh install without one of its options, which puts that option's default back (the last 8 CPUs, 6G, weight 10), a looser cap than the machine may have had. The step now asks for all four values from the slice file and says why. The optional managed-apps step now says it is a loosening that is root's to decide. Test (fails without): none; release-note text only. test/changelog.test.ts still passes with it. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Evaluator verdict: CHANGES NEEDED (round 1)Reviewed head Short answer to "does every install end up correct?" Mostly yes, and nothing in the notes loosens a machine by default. The audit, the release-drop fix, the NVML correction and the providers step all check out. Four root-step notes need fixing first. The one that matters most: the notes don't say that Required changes
Checks
Merge-tree (measured,
|
…gress, broker and check notes say what really happens Review round 1 on #809: - managed-apps.sh add also lifts, for that repo, locks v0.5.2 already applied in every repo (machine setup, updater, review levels, merge policy, main watch, push guard, guardrails, verify-release-tag, dashboard-auth, decide, machine-owners: measured with selfModificationPart under both scopes). The notes name them, say the default is to keep every lock, and no longer offer it as the fix for the new waits. - test-user.sh install exits 1 on every test user but the last (the others' Docker has no NFTSet= yet), and while a test run is in progress. Both are said, with what to do. - The optional mcp-broker.sh rerun also restarts the instance's coordinator and dashboard. - The egress check's failure is quoted per install type, not with a line most installs won't see. - PR numbers throughout (issue #583 named as an issue); a group-writable providers.json is refused too. Test (fails without): none; release-note text only. test/changelog.test.ts passes, and the release-survival check drops 0 lines. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Round 2: head 56e9706Changes since b1856ff, all release-note text in
Checks (measured):
|
…ith issue/PR named after it CI on 56e9706 failed test/owner-terminal-removed.test.ts ("one placeholder version ... the Upgrading bullet"): it finds that bullet by its title, "The dashboard's owner terminal is removed (#742)", and the round-2 switch to PR numbers had changed it to #752. The title keeps #742, and the bullet names the pair (issue #742, PR #752) right after it, so the test stays as it is. Test (fails without): test/owner-terminal-removed.test.ts "one placeholder version: the CHANGELOG (Upgrading and After install), ..." (failed on 56e9706 in CI shard 2/4). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
|
Round 2 follow-up, head 9511c9b: CI on 56e9706 failed in test/owner-terminal-removed.test.ts ('the Upgrading bullet'), measured from run 39222123503, shard 2/4. That test finds the bullet by its title |
…ssage, then checks each instance Found in round 2's adversarial pass. With several test users, every test-user.sh install but the last exits 1 at the egress step (the others' Docker has no NFTSet= yet). An operator told to "carry on" could also carry on past a different error, and each install that exits there skips its own final check (test-user.sh check_cmd after the sudoers step), which the egress check doesn't replace. The note now says to go past only that message, and to run test-user.sh check <name> for each instance after the last install. Test (fails without): none; release-note text only. test/changelog.test.ts passes, and the release-survival check drops 0 lines. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Adversarial pass (round 2)These attacks read the round-2 notes as an operator following them literally. Each was checked read-only against the scripts in the worktree, with no root and no real state. One gap was found and fixed in 657c79a, a text-only change to CHANGELOG.md.
|
Evaluator verdict: CHANGES NEEDED (round 2)Reviewed head Short answer to the main question. An operator who follows these notes literally ends up with every install correct. Nothing in them loosens a machine by default, and I found no missed root step (inferred, from the code walk below). Two statements are still wrong, and both are text fixes:
I gave both lists in round 1 and both were incomplete, so these two misses are partly mine. Round 2 only changes Required changes
The round-1 required changes, re-checked
test-user.sh, walked through the code (inferred:
|
| Item | Result |
|---|---|
Release survival (in memory, scripts/lib/changelog.mjs, nothing written) |
0 dropped (measured: 657c79ab v0.5.3: 89 lines before, 95 after; dropped 0; the rest of the file is unchanged). v0.5.2 regenerated: only its generated list lines change, and the Correction paragraph stays. Merged with origin/main: 0 dropped (measured). |
| New test, fail-without | With origin/main's CHANGELOG.md checked out over the head's in my worktree: not ok 8 - an unreleased section is all hand-written…, # tests 10 / # pass 9 / # fail 1. Restored, the head gives changelog + release + package-files + owner-terminal-removed # tests 36 / # pass 36 / # fail 0 (measured). |
| PR numbers | Consistent. All 33 numbers in the v0.5.3 section are merged PRs except #583 (written "issue #583") and #742 (whose title a test pins, followed by "(Issue #742, PR #752.)") (measured: gh api repos/.../issues/<n>). |
| providers.json group-writable | Right (measured: :18 "one root doesn't own or its group or others may write", :79 "not group- or other-writable" match src/provider-hosts.ts:140). |
| "Can never do" item 3 | True (inferred). The test's regex ^## v(\d+\.\d+\.\d+) \(unreleased\) matches any heading that starts that way, and the remaining gap is named, with #811 as the structural fix (#811 is open, measured). |
| Protection | Correct (measured): [PROTECTED: owner merges], needs-owner, Risk class: protected, and real "What this can never do" and "Compromised component" sections. scripts/** is engine and docs/releasing.md is engine-guards. |
| Adversarial pass | Present in the body and in issuecomment-6263652230, with 4 attacks, results and one fix (657c79a). Item 4's "not covered" claim is wrong for one install type (required change 2). |
| DCO / attribution | DCO: all commits signed off (measured, scripts/check-dco.mjs origin/main 657c79ab). All 6 commits have only Signed-off-by trailers, and there is no attribution line in the commits or the PR body (measured). |
| typecheck / denylist | exit 0; denylist: 1292 files clean (measured, on the head). |
| CI on 657c79a | All green (measured: gh pr checks 809; runs 39227425238 and 39227425250 are pull_request on 657c79ab…). bootstrap and bootstrap (windows-wsl) were skipped. |
| Owner's real state (rule A) | The new test reads only the repo's CHANGELOG.md (inferred). |
| Audit window | Ends at #797. #793 and #804 merged after it. #804 is scripts/bench, and #793 is src/ only (reconciliation, the App bot read once), so neither needs an install step (inferred from git diff c339175a origin/main). |
Merge-tree (measured, git merge-tree --write-tree, read-only)
- origin/main (178c44e): clean, and 0 v0.5.3 lines dropped.
- Clean: ci: a .worklane/ change is loaded with the pinned engine release; a config it refuses fails an advisory check [PROTECTED: owner merges] #812, Slot dir: a slot path is an opaque SlotPath that only writeSlotFile and the slot readers take; tsc rejects it at any other writer [PROTECTED: owner merges] #806, Review levels: a .gitattributes mark can't hide a change's lines or content; adding one waits for the owner (#693) [PROTECTED: owner merges] #802, docs/design: amend #698 decision 1: turning auto_merge on is root-only; off stays on every surface [PROTECTED: owner merges] #775 and bench: run driver, patch export, grading inputs, aggregates and setup README [PROTECTED: owner merges] #759. Each drops 0 lines.
- CHANGELOG.md conflicts: Approvals: one role rule on every surface, decide only as root or the coordinator user, money paths read from main (#715) [PROTECTED: owner merges] #805, doctor prints a paste-ready required_check_sources block; a new project's config starts pinned (after #788) [PROTECTED: owner merges] #803, Pin this repo's required checks to the workflow files that post them (after #788 and the engine upgrade) [PROTECTED: owner merges] #794, Engine updater counts a required check only from its pinned workflow or App [PROTECTED: owner merges] #789, Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788, Project commands run as the agent user only in a sandbox that hides its credentials; their output is redacted (#713) [PROTECTED: owner merges] #787, Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780, Dashboard policy changes go through root's helper (set-policy), never a loosening (#726 part 2) [PROTECTED: owner merges] #770, Single machine: policy.yaml is root's, and the engine refuses one that isn't (#726 part 1) [PROTECTED: owner merges] #769 and Remove the desktop app, its source, Tauri lockfile and docs; keep the desktop check one more release as a no-op (#744) [PROTECTED: owner merges] #746. Unlike round 1, Project commands run as the agent user only in a sandbox that hides its credentials; their output is redacted (#713) [PROTECTED: owner merges] #787, Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780 and Single machine: policy.yaml is root's, and the engine refuses one that isn't (#726 part 1) [PROTECTED: owner merges] #769 now conflict, because they have added CHANGELOG lines since. Remove the desktop app, its source, Tauri lockfile and docs; keep the desktop check one more release as a no-op (#744) [PROTECTED: owner merges] #746 also conflicts in
scripts/setup/lib.shand the two test files, as it does against main alone.
Does #809's layout make the others' merges mechanical?
-
Single machine: policy.yaml is root's, and the engine refuses one that isn't (#726 part 1) [PROTECTED: owner merges] #769, Project commands run as the agent user only in a sandbox that hides its credentials; their output is redacted (#713) [PROTECTED: owner merges] #787 and Approvals: one role rule on every surface, decide only as root or the coordinator user, money paths read from main (#715) [PROTECTED: owner merges] #805: yes, with one rule. Each adds its guardrail bullet right below
### Guardrail changes. After Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809, that heading is the trailing one thatrelease.mjsregenerates. Measured in memory: a stand-in bullet placed under the trailing heading is dropped (1), and the same bullet under### Guardrail changes: what each one doesdrops 0. The rule: put each guardrail bullet under "what each one does". Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's test fails the merged branch if someone doesn't (inferred: it calls the same functions). Their Upgrading and After-install additions merge as "keep both". -
Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780: only partly. Its Upgrading bullets are "keep both". Its After-install bullet, though, is the same
test-user.sh installcommand, with the same four values, for the same users as Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's egress step. Merged as two bullets, an operator runs it twice. Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780's bullet also lacks Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's caveats: "no test run going", every install but the last exits 1, and go only past that message. Whoever merges second should make them one step:- one install per test user, with Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's caveats;
- then
test-user.sh check <name>per instance, with Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780'sPASS a sandboxed run is in a user namespace of its ownline; - then the egress
check.
Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780 also cites the egress work as Egress firewall: cover test-run users by default, narrow exception for rootless-Docker image pulls (v0.5.2) #543/Egress firewall: drop the test user's subordinate uid range too, not only its own uid #740 (issues), where Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809 uses egress-firewall: test users covered by default, Docker's image pulls the one exception (#543) [PROTECTED: owner merges] #686/egress-firewall: drop each covered user's subordinate uids too, not only its own uid (#740) [PROTECTED: owner merges] #778. Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780's context still carries the old "No new sudoers rules" line that Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809 replaced.
This is a merge step for the second PR, not a change to Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809 (inferred).
Not blocking
- Release: an (unreleased) CHANGELOG section survives release.mjs whole, by design (no heading-layout rule) #811 tracks the structural fix (an unreleased section survives the release whole, by design). Until it lands, the rule for Single machine: policy.yaml is root's, and the engine refuses one that isn't (#726 part 1) [PROTECTED: owner merges] #769, Project commands run as the agent user only in a sandbox that hides its credentials; their output is redacted (#713) [PROTECTED: owner merges] #787 and Approvals: one role rule on every surface, decide only as root or the coordinator user, money paths read from main (#715) [PROTECTED: owner merges] #805 above rests on Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's test.
…le; egress check quotes what prints Review round 2 on #809: - managed-apps.sh add lifts every self-modification lock whose path is under src/, scripts/, test/ or docs/ (engineSourceGlob). The notes now say that rule and list all 24 of v0.5.2's 28 lock globs it lifts, by part (sign-in, credentials or secrets and who may approve were incomplete), measured with the head's selfModificationPart in both scopes on a sample path per glob. - The guardrail note names the parts v0.5.2 didn't have by their own names in SELF_MODIFICATION_PARTS. - egress check before the reload: check reads its users from the record (egress-firewall.sh:274), and a v0.5.2 record without --with-test-users lists no test user, so only the exception line prints there. The "every install but the last exits 1" sentence now holds only where Docker gets the exception. Test (fails without): none; release-note text only. test/changelog.test.ts passes, and the release-survival check drops 0 lines. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Round 3: head 456d468Changes since 657c79a, release-note text in
Checks (measured):
|
Evaluator verdict: PASSFinal check, round 3. Reviewed head Labels: measured = from a command I ran here; inferred = from reading code; guess. Both round-2 required changes are met, and every check below passes. I have two optional wording points (end of this comment). Neither blocks. 1. Locks: v0.5.2's globs against the head's
|
| systemd | v0.5.2 flag | decide() |
What check prints before the reload |
Notes |
|---|---|---|---|---|
| 255 or later | none | exempt, nftset=1 | The users come from the record (:274): wl-exp-*/wl-score-* only (v0.5.2 covered()). They are system users, so they have no subuid range (experiments.sh:204, useradd --system). The old rule has no socket cgroupv2 line, so it prints :292 FAIL the exception for the set egress_ok isn't loaded, so test users' Docker can't pull images: run apply again |
:50 quotes exactly this, and no "not covered" line. Matches |
| 255 or later | --with-test-users |
dropped, no nftset | The test users' uids are covered by the old rule. Their subuid ranges aren't, so it prints :284 FAIL <user>'s subordinate uids <lo>-<hi> (/etc/subuid) are not covered: run apply again, then the :297 PASS (no exception) |
:50 quotes <user>'s subordinate uids <range> (/etc/subuid) are not covered: run apply again. Matches |
| older than 255 | --with-test-users |
dropped | the same subuid FAIL lines | The same quote, which :50 doesn't tie to a systemd version. Matches |
| older than 255 | none | exempt, no tests | No test user, no subuid range, no exception branch, so it can pass | :50 says "check may pass, and the reload changes nothing there". Matches |
"Exits 1" (:52). test-user.sh install runs apply, whose check_cmd runs docker_check only when nftset is set (:287-290), that is systemd 255 or later and not dropped. There, every test user's running Docker unit not yet reinstalled has no NFTSet=, so apply exits 3. test-user.sh:419 then prints the egress rule is loaded, with <user> in it, but its check failed: fix its FAIL lines above (they may be another instance's), then run install again and exits 1.
- The notes quote that line up to "another instance's)". That is an exact prefix of the real line.
- The qualifier "(systemd 255 or later, record not "dropped")" and the sentence "On a "dropped" install or on systemd older than 255,
applydoesn't check Docker'sNFTSet=" both matchdecide()andcheck_cmd. :396'stest runs of <name> are in progressmatches as well.
3. The new guardrail locks (measured: git diff v0.5.2 456d4682 -- src/review.ts, plus src/hook-imports.ts and src/path-text.ts)
- The diff adds 10 parts, all named at :86:
- the evaluator and the agent runner;
- the sandbox and test runs;
- the agents' tool guard;
- its hook commands;
- where the self-modification locks apply;
- the push gate, denylist and secret scan;
- the tests of the harness controls;
- the release procedure and the design rules;
- the coordinator;
- the experiment studies.
- It adds one glob to an existing part:
src/path-text.tsunder the review levels, which the note names. - No v0.5.2 part lost or changed a glob, and
AGENT_INSTRUCTIONSis unchanged. - Since the audit, main added
test/merge-idempotent.test.tsto "the tests of the harness controls" (Merge: idempotent across a crash: merge.attempted before the API call, then reconciled, so the engine never reverts its own good merge (#733) [PROTECTED: owner merges] #793; measured:git diff 456d4682...origin/main -- src/review.ts). That is an existing part the note names, so the list stays complete after the merge.
4. Spot-check of the builder's cited lines (measured: sed -n on the head)
| Cited | Source | Quote in the notes |
|---|---|---|
src/install.ts:103 |
name: `agent hooks ${instance}` |
agent hooks <name> (:68). Matches |
worklane-update.cjs:484 |
reason: `required check "${name}" hasn't run on this commit` |
required check "desktop" hasn't run on this commit (:41). Matches |
src/claude-credential-guard.ts:30 |
REMOVED_CREDENTIAL_PATHS: /api/claude-account/token, /profile-add and /login-code |
"the account switch's token, a profile's token, the relayed sign-in's code" answer 410 (:25). Matches |
web/src/components/HealthPanel.tsx:192 |
No provider hosts: {file} doesn't exist, so provider runs are off (root writes it: docs/providers.md) |
The quote at :45 is an exact prefix. Matches |
src/provider-hosts.ts:204-207 |
no ${h.file}: no provider host is allowed, so provider runs are off (…), provider hosts: …, provider ${label} refused: ${why}; … |
:46, as exact prefixes. Matches |
lib.sh:154, :157, :197 |
the claude-account profile-add, login and switch sudoers lines |
:70. Matches |
mcp-broker.sh:85-86, :91-92 |
it restarts worklane-mcp@, then worklane-<name> and worklane-dashboard-<name> when they are active |
:59. Matches |
5. Release survival (measured, in memory with scripts/lib/changelog.mjs changelogSection → withSection → sectionFor; nothing written)
- Head:
456d4682:CHANGELOG.md v0.5.3: 79 lines before, 87 after; dropped 0. The rest of the file is unchanged. - Head merged with origin/main (
git merge-tree --write-tree origin/main 456d4682gives tree9cd6de02, the same tree the builder reported):dropped 0. - Control, to show the probe catches a drop:
origin/main:CHANGELOG.mdgivesdropped 8(the Remove the browser owner terminal, its sudoers rule and root helper (deprecate, then remove; keep the loopback break-glass and the device gate) #742, Remove the desktop app, its required CI check and its Tauri lockfile (deprecate, then remove) #744, Updater: keep the root updater itself in step with the engine it installs, so updater fixes need no root rerun of updates.sh #760 and Updater: refuse a release whose signed tag object names a different version than its ref (downgrade by re-pushing an old signed tag) #764 bullets and Updater: refuse a release whose signed tag object names a different version than its ref (downgrade by re-pushing an old signed tag) #764's 4 sub-bullets). - Where another PR's bullet survives: a stand-in guardrail bullet under the trailing
### Guardrail changesisDROPPED. Under### Guardrail changes: what each one doesit iskept.
6. The usual checks
| Item | Result |
|---|---|
| DCO | DCO: all commits signed off (measured, scripts/check-dco.mjs origin/main 456d4682). |
| Attribution | None in the 7 commits or the PR body: only Signed-off-by trailers, and grep finds no co-authored, generated with, claude code or anthropic (measured). |
| Protection | [PROTECTED: owner merges], needs-owner, **Risk class:** protected, and real "What this can never do" and "Compromised component" sections (measured). Protected paths: scripts/** (engine, self-modification "the machine setup"), and docs/releasing.md (engine-guards, self-modification "the release procedure"). |
| Adversarial pass | Present: the body's "Adversarial pass", "(round 2)" and "(round 3)" sections, and issuecomment-6264313239. Round 3 found and fixed the guardrail note's list of new locks, and I confirmed that fix above. Round 2 item 4 carries a "false through 657c79a" correction. |
| Tests | changelog, release, package-files, owner-terminal-removed and review: # tests 55 / # pass 55 / # fail 0 (measured, temp HOME, state dirs, TMPDIR and a fake claude). |
| typecheck / denylist | npm run typecheck exits 0; denylist: 1292 files clean (measured). |
| CI on 456d468 | All green (measured: gh run list --commit 456d4682…, pull_request runs of ci, ci slow, gitleaks and new-deps all success). bootstrap and bootstrap (windows-wsl) were skipped. |
| merge-tree with origin/main 178c44e | Clean (measured). |
| Owner's real state (rule A) | The only test reads the repo's CHANGELOG.md, unchanged since round 2 (inferred). |
Open PRs against main + #809 (measured: git merge-tree --write-tree --name-only of each head against 456d4682)
Main hasn't touched CHANGELOG.md since #809's base, so #809's file is main+#809's. Against main alone, every one of these is clean except #746.
- Clean after Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809: ci: a .worklane/ change is loaded with the pinned engine release; a config it refuses fails an advisory check [PROTECTED: owner merges] #812, Slot dir: a slot path is an opaque SlotPath that only writeSlotFile and the slot readers take; tsc rejects it at any other writer [PROTECTED: owner merges] #806, Review levels: a .gitattributes mark can't hide a change's lines or content; adding one waits for the owner (#693) [PROTECTED: owner merges] #802, docs/design: amend #698 decision 1: turning auto_merge on is root-only; off stays on every surface [PROTECTED: owner merges] #775 and bench: run driver, patch export, grading inputs, aggregates and setup README [PROTECTED: owner merges] #759.
CHANGELOG.mdconflicts after Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809: Approvals: one role rule on every surface, decide only as root or the coordinator user, money paths read from main (#715) [PROTECTED: owner merges] #805, doctor prints a paste-ready required_check_sources block; a new project's config starts pinned (after #788) [PROTECTED: owner merges] #803, Pin this repo's required checks to the workflow files that post them (after #788 and the engine upgrade) [PROTECTED: owner merges] #794, Engine updater counts a required check only from its pinned workflow or App [PROTECTED: owner merges] #789, Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788, Project commands run as the agent user only in a sandbox that hides its credentials; their output is redacted (#713) [PROTECTED: owner merges] #787, Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780, Dashboard policy changes go through root's helper (set-policy), never a loosening (#726 part 2) [PROTECTED: owner merges] #770, Single machine: policy.yaml is root's, and the engine refuses one that isn't (#726 part 1) [PROTECTED: owner merges] #769 and Remove the desktop app, its source, Tauri lockfile and docs; keep the desktop check one more release as a no-op (#744) [PROTECTED: owner merges] #746. doctor prints a paste-ready required_check_sources block; a new project's config starts pinned (after #788) [PROTECTED: owner merges] #803 and Pin this repo's required checks to the workflow files that post them (after #788 and the engine upgrade) [PROTECTED: owner merges] #794 stack on Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788, and Dashboard policy changes go through root's helper (set-policy), never a loosening (#726 part 2) [PROTECTED: owner merges] #770 stacks on Single machine: policy.yaml is root's, and the engine refuses one that isn't (#726 part 1) [PROTECTED: owner merges] #769.
Mechanical: keep both sides, and keep each guardrail bullet directly above "Removed: a sudoers rule and a root helper (#752)", which puts it under "what each one does" (measured placement above). #809's new test fails the merged branch if a bullet lands under the trailing heading (inferred).
- Single machine: policy.yaml is root's, and the engine refuses one that isn't (#726 part 1) [PROTECTED: owner merges] #769, Project commands run as the agent user only in a sandbox that hides its credentials; their output is redacted (#713) [PROTECTED: owner merges] #787 and Approvals: one role rule on every surface, decide only as root or the coordinator user, money paths read from main (#715) [PROTECTED: owner merges] #805: each adds its guardrail bullet in that spot. Approvals: one role rule on every surface, decide only as root or the coordinator user, money paths read from main (#715) [PROTECTED: owner merges] #805's and Project commands run as the agent user only in a sandbox that hides its credentials; their output is redacted (#713) [PROTECTED: owner merges] #787's Upgrading bullets also merge as keep-both.
- Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788 (and doctor prints a paste-ready required_check_sources block; a new project's config starts pinned (after #788) [PROTECTED: owner merges] #803, Pin this repo's required checks to the workflow files that post them (after #788 and the engine upgrade) [PROTECTED: owner merges] #794 on top of it): the same pattern.
- Engine updater counts a required check only from its pinned workflow or App [PROTECTED: owner merges] #789: its After-install bullet goes after the "main channel never replaces its updater" line, and its guardrail bullet after Updater: a release installs only under its own signed name, as the one tag object verified, with replace refs off [PROTECTED: owner merges] #773's. The context differs only in Updater: keep the root updater itself in step with the engine it installs, so updater fixes need no root rerun of updates.sh #760 → Updater: replace itself from a verified release tag after a healthy install, never with an older one [PROTECTED: owner merges] #761 and the sudoers line.
Not mechanical:
-
Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780 (round 7): its After-install bullet is the same
test-user.sh installwith the same four slice values, for the same users, as Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's egress step. The second to merge should make it one step:- one install per test user, with Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's caveats (no test run going; where Docker gets the exception, every install but the last exits 1; go only past that message);
- then
test-user.sh check <name>per instance, with Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780'sPASS a sandboxed run is in a user namespace of its own; - then the egress
check.
Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780 also cites Egress firewall: cover test-run users by default, narrow exception for rootless-Docker image pulls (v0.5.2) #543/Egress firewall: drop the test user's subordinate uid range too, not only its own uid #740 where Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809 uses egress-firewall: test users covered by default, Docker's image pulls the one exception (#543) [PROTECTED: owner merges] #686/egress-firewall: drop each covered user's subordinate uids too, not only its own uid (#740) [PROTECTED: owner merges] #778.
-
Dashboard policy changes go through root's helper (set-policy), never a loosening (#726 part 2) [PROTECTED: owner merges] #770: it adds sudoers rules (
set-policy, rerunmachine-helper.sh). Its change replaces the old "No new sudoers rules" line, which Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809 rewrote. So Dashboard policy changes go through root's helper (set-policy), never a loosening (#726 part 2) [PROTECTED: owner merges] #770 must rewrite Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's sudoers bullet (:70): rules are now added, the rerun is required, and Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's facts stay (the console rule removed bydashboards.sh; the three stale helper lines). Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's newdocs/releasing.mdstep 9 line, "0.5.3: no", would also become false. merge-tree doesn't flag it, because Dashboard policy changes go through root's helper (set-policy), never a loosening (#726 part 2) [PROTECTED: owner merges] #770 doesn't touch that file (measured), so this is a semantic conflict. -
Remove the desktop app, its source, Tauri lockfile and docs; keep the desktop check one more release as a no-op (#744) [PROTECTED: owner merges] #746: it already conflicts with main in 4 files. Its desktop-updater bullets duplicate Updater: a retired required check (desktop) that no longer runs doesn't block updates; ships before the desktop job's removal (#744) [PROTECTED: owner merges] #755's, which is on main. It also contradicts :39 ("A later release removes the desktop window and its CI job"), so it needs a rebase and a rewrite, not a keep-both.
Is this the last notes blocker for v0.5.3?
For what is on main: yes (inferred).
- Main's push CI on
178c44e6is green (measured: ci and gitleakssuccess). - main+Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809 drops 0 lines (measured above).
- The audit runs through Test: list scripts/bench/hidden.ts on the changed-files reader list (main red after #688 + #757) #797. The two PRs merged since then need no install step (inferred): bench: hidden.ts counts added lines starting "++ "; pin its two lines on the reader list (#797 follow-up) [PROTECTED: owner merges] #804 is
scripts/bench, and Merge: idempotent across a crash: merge.attempted before the API call, then reconciled, so the engine never reverts its own good merge (#733) [PROTECTED: owner merges] #793 adds one test to a lock part the notes already name. - Two things remain:
- The owner's own checklist steps 1-3 in
docs/releasing.md. - Issue Remove voice and Siri: spoken approvals, the Siri gateway, its ports and device tokens (deprecate, then remove) #743, which plans its voice/Siri deprecation notice for "Release N (0.5.3)". Nothing is built for it and no PR is open, so it's the owner's call whether that moves to the next release.
- The owner's own checklist steps 1-3 in
Open PRs whose notes v0.5.3 would need, if the owner wants them in:
- Single machine: policy.yaml is root's, and the engine refuses one that isn't (#726 part 1) [PROTECTED: owner merges] #769, Dashboard policy changes go through root's helper (set-policy), never a loosening (#726 part 2) [PROTECTED: owner merges] #770, Approvals: one role rule on every surface, decide only as root or the coordinator user, money paths read from main (#715) [PROTECTED: owner merges] #805, Project commands run as the agent user only in a sandbox that hides its credentials; their output is redacted (#713) [PROTECTED: owner merges] #787, Test runs: each run in a user namespace of its own; slirp4netns as the test user; a run can't join Docker's namespaces (#545) [PROTECTED: owner merges] #780, Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788, doctor prints a paste-ready required_check_sources block; a new project's config starts pinned (after #788) [PROTECTED: owner merges] #803 and Engine updater counts a required check only from its pinned workflow or App [PROTECTED: owner merges] #789 already carry v0.5.3 notes. Each has to land under Release notes v0.5.3: every install-affecting change since v0.5.2, the v0.5.2 NVML correction, notes the release keeps [PROTECTED: owner merges] #809's layout as described above. Dashboard policy changes go through root's helper (set-policy), never a loosening (#726 part 2) [PROTECTED: owner merges] #770 also flips step 9 to "yes".
- Review levels: a .gitattributes mark can't hide a change's lines or content; adding one waits for the owner (#693) [PROTECTED: owner merges] #802 has no notes yet. If it's in, it needs an Upgrading line and a guardrail note:
- a new always-L3
diff-hiding-attributescategory, so more waits in every managed repo; - binary files counted by their real lines.
- a new always-L3
- Remove the desktop app, its source, Tauri lockfile and docs; keep the desktop check one more release as a no-op (#744) [PROTECTED: owner merges] #746 conflicts with :39 and Updater: a retired required check (desktop) that no longer runs doesn't block updates; ships before the desktop job's removal (#744) [PROTECTED: owner merges] #755's bullet (above).
- Pin this repo's required checks to the workflow files that post them (after #788 and the engine upgrade) [PROTECTED: owner merges] #794 is after the release by design: it waits until the engine pin accepts its key, per ci: a .worklane/ change is loaded with the pinned engine release; a config it refuses fails an advisory check [PROTECTED: owner merges] #812's body.
- ci: a .worklane/ change is loaded with the pinned engine release; a config it refuses fails an advisory check [PROTECTED: owner merges] #812, Slot dir: a slot path is an opaque SlotPath that only writeSlotFile and the slot readers take; tsc rejects it at any other writer [PROTECTED: owner merges] #806, docs/design: amend #698 decision 1: turning auto_merge on is root-only; off stays on every surface [PROTECTED: owner merges] #775 and bench: run driver, patch export, grading inputs, aggregates and setup README [PROTECTED: owner merges] #759 need no notes. ci: a .worklane/ change is loaded with the pinned engine release; a config it refuses fails an advisory check [PROTECTED: owner merges] #812 is CI-only, docs/design: amend #698 decision 1: turning auto_merge on is root-only; off stays on every surface [PROTECTED: owner merges] #775 is the design doc, bench: run driver, patch export, grading inputs, aggregates and setup README [PROTECTED: owner merges] #759 is bench, and Slot dir: a slot path is an opaque SlotPath that only writeSlotFile and the slot readers take; tsc rejects it at any other writer [PROTECTED: owner merges] #806 is an internal type (that one is a guess from its title).
Not blocking (optional one-line edits; the owner can take or leave them)
- :60: "That covers this release's new locks" reads wider than the code. 4 of this release's new globs (
.githooks/**,.denylist*,.gitleaks*andexperiments.yaml) still hold, as :67 says right after (measured above). "…this release's new locks under those folders" would be exact. The error overstates the loosening, so it can't lead anyone to loosen more (inferred). - :51: "(none where the record says
# docker: dropped)" is true but not the only case. A v0.5.2--with-test-usersrecord says# --with-test-usersat that moment, andegress_docker_modereads it as dropped. Systemd older than 255 also gets noNFTSet=(test-user.sh:118-123). :57 already gives the outcome (inferred).
Every other open PR adds its v0.5.3 notes at the end of the Upgrading list and at the top of the Guardrail changes list, and #809 rewrites a dozen lines of the section. The #693 notes sat at those same spots, so each of those PRs would conflict with this one in CHANGELOG.md (measured with git merge-tree). The Upgrading bullet now goes before the `console-devices` bullet, and the guardrail note's own section before "After install". Both stay above the first heading the release tool writes, so `scripts/lib/changelog.mjs` keeps them (checked in memory, nothing written). Test (fails without): none; a placement change, checked with git merge-tree and the release tool in memory. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…me never carries, forwards or moves one The owner's decision (round 9). Rounds 5 to 8 each fixed a rename window that came from keying root's kill switches by instance name. Now: - Root's pointer /etc/<cli>/policy-root/<name>.id holds a random id (minted at takeover, or by the first OFF, as an atomic link so two first OFFs get one id); the switches and the disarmed spare live in by_id/<id>/. - A rename's start points the new name at the same id (`policy alias`, instance_alias). An OFF given to either name, at any step, is both names'. The old name's removal drops its pointer (a registry item after its users), --abort the new one's (with the new name, or itself before the users step), and a removal drops the id's directory once no name points at it. - Fail closed: a pointer that isn't a plain file, holds no id or can't be read, an id directory missing or unlistable, or a taken-over instance (its record there) with no pointer, all read off. `policy own` without a flag (the coordinator's route) never mints over a recorded instance; an admin's --retake does. - `set on` is refused while another name shares the id (it would clear that name's OFF). - doctor: a broken or missing pointer fails; a shared id warns. - Deleted: the forward record, carry, move, forward_records_of, instance_can_run and their abort gate. userPresence stays: OFF on a name whose user lookup fails is never refused as "no instance". - Kept: OFF takes no lock and reads no policy.yaml, needs no new space, races add no files and never fail, ON only through the lock and never undoes a done OFF. - CHANGELOG: #726's notes under "Guardrail changes: what each one does" and "Upgrading" (main's #809 layout). No migration: no install has a switch yet (#769 is unmerged). Tests: policy-kill's id tests (rename at every step for either name, --abort and removals, pointer attacks and the shared-id ON refusal, one id for racing first OFFs, a re-added name, A to B to A, lib.sh's reader). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Brings #769's round 9 (switches keyed by a stable instance id; origin/main 128453a with #809's v0.5.3 layout). - docs/design.md: #769's text, with this PR's dashboard sentences in the #726 paragraph. - CHANGELOG.md: #769's (main's layout, #726's notes), with "Merge with #809" (a): #809's "No sudoers rule is added" bullet replaced by this PR's set-policy rules and their rerun. docs/releasing.md step 9: (d), "0.5.3: yes (set-policy, #726)". The #726 guardrail bullet's dashboard part follows in this PR's next commit. - scripts/ci/wizard-e2e.mjs: screen 8 finds the switch through root's id pointer. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
A conflict in CHANGELOG.md only, resolved mechanically: main's side (#809's v0.5.3 layout, its renumbered entries), with this PR's two #727 bullets added, one in After install beside the updater rerun (#761) and one under "Guardrail changes: what each one does", above the trailing "### Guardrail changes" heading. Checked with scripts/lib/changelog.mjs's sectionFor in memory: no v0.5.3 bullet of either side dropped (#643 is main's #671; main's own sudoers line replaces this side's "No new sudoers rules"). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
A conflict in CHANGELOG.md only, resolved mechanically: main's side (#809's v0.5.3 layout, its renumbered entries), with this PR's two #727 bullets added, one in Upgrading and one under "Guardrail changes: what each one does", above the trailing "### Guardrail changes" heading. Checked with scripts/lib/changelog.mjs's sectionFor in memory: no v0.5.3 bullet of either side dropped (#643 is main's #671). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…nto issue-545-slirp-test-user Conflicts: - src/project-env.ts RESERVED_NAMES: both lists kept (#780's glibc unsafe-environment names and LIBMOUNT_*, LANGUAGE; #787's SHELLOPTS, BASHOPTS, GLOBIGNORE, EXECIGNORE, BASH_*). - CHANGELOG.md, at the coordinator's anchors: - #780's Upgrading bullets go before "The dashboard never takes a Claude credential (#749, #751)". The bash-names bullet now says #713 refuses SHELLOPTS, BASHOPTS and BASH_* at the config check. - #780's test-user.sh install step is folded into #809's egress bullet: the installs with #809's caveats, then test-user.sh check per instance, then the egress check. - #780's "No new sudoers rules" line is dropped, since #809's is fuller. - #780's guardrail bullet goes before "The host egress rule covers the test-run users by default". Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Intent: finish v0.5.3's release notes before it is cut. An audit of all 97 PRs merged since v0.5.2 (table below) found install-affecting changes missing from the
## v0.5.3 (unreleased)section, among them #607 (provider runs stop until root writes/etc/worklane/providers.json) and #686/#778 (the egress rule must be loaded again). This PR adds them, each with its exact root command, and rewrites the sudoers line so it tells the whole truth. It corrects v0.5.2's false "NVML changes no node at all" in place, with a dated note, and in the four other places that said it (#583). It also fixes a measured problem:release.mjs changelog 0.5.3would have dropped every hand-written "Guardrail changes" note. The notes now sit where the release keeps them, and a new test holds that. No behaviour changes: release-note text, docs, comments, one echoed message and one test.Risk class: protected.
scripts/setup/gpu-devices.shandscripts/machine/worklane-gpu-devices.shareengine(scripts/**) and self-modification ("the machine setup");docs/releasing.mdisengine-guards. The owner merges.Part of #583 (text only: the issue's fixer, path unit and tests are not here).
Labels: measured = from a command or output cited here; inferred = reasoned, not run; guess = weakest.
What changed
CHANGELOG.md, v0.5.3 (unreleased). Its sections are now Upgrading (from v0.5.2), After install and Guardrail changes: what each one does, ended by an empty### Guardrail changesheading thatrelease.mjsfills in (why: "Where release.mjs keeps the notes" below).dashboards.shlines now keep--siri/--hooks/devices), providers: a provider's base_url only to a host root allows in /etc/<cli>/providers.json [PROTECTED: owner merges] #607 (provider runs fall back until root allows hosts; somebase_urls now make the config invalid, and the coordinator then exits at start, so fix them before the install), claude credentials: the dashboard never takes one; the four paths that did are gone (#711 part 2, server) [PROTECTED: owner merges] #749/web: the Claude account and sign-in panels take no credential; root's commands instead (#711 part 2, UI) [PROTECTED: owner merges] #751 (the dashboard takes no Claude credential: 410s, the root commands instead, add-project waits, staged profiles needconfirm-profile), runner: a run stops unless claude's init event shows its --settings were applied [PROTECTED: owner merges] #649 (a run stops unlessclaudeapplied its--settings), evals: no paid model call without --spend, no held-out read unless a store is named (#612) [PROTECTED: owner merges] #659 (--spend), main watch: one revert PR per trip and three a day; warn once of a required check that never runs [PROTECTED: owner merges] #602/main watch: an alert with no revert PR says why; a rebase-merged PR's commits revert as one range [PROTECTED: owner merges] #606 (revert caps), and the wider locks in every managed repo (review: lock the evaluator, sandbox, push gate and denylist, their tests and the release rules as self-modification #580, review: lock src/coordinator/** and the experiment studies' file, hold #580's gap paths in review.yaml now, default effort high #584, review: lock src/hook.ts (the agents' tool guard) as self-modification [PROTECTED: owner merges] #647, review: lock the modules the agents' tool guard decides with [PROTECTED: owner merges] #658, review: lock the tool guard's whole import tree; engine-source locks stand aside only where root names a managed app [PROTECTED: owner merges] #691, Lock scope follow-ups: old cli.js hook installs flagged and migrated, one githubRepoOf, updates.json read as root's file, readLockScope takes only the instance and its repo [PROTECTED: owner merges] #765, Review levels: a renamed file counts under its old and its new path, in changeFiles and every path reader [PROTECTED: owner merges] #688).providers.jsonwith the exactsudo install -o root -g root -m 0644 providers.json /etc/worklane/providers.jsonline from docs/providers.md, and how the Machine page and the coordinator's start log show it (worklane doctorhas no provider check: measured,grep -c -i provider src/doctor.tsprints0). The egress rule's reload for egress-firewall: test users covered by default, Docker's image pulls the one exception (#543) [PROTECTED: owner merges] #686/egress-firewall: drop each covered user's subordinate uids too, not only its own uid (#740) [PROTECTED: owner merges] #778:test-user.sh installwith all four slice values, oregress-firewall.sh apply, thencheck. Optional:updates.sh --add-check "egress firewall (real systemd)", themcp-broker.shrerun (MCP broker: ProtectControlGroups back on; real-systemd proof in ci-slow (part of #551) [PROTECTED: owner merges] #596) andmanaged-apps.sh add(review: lock the tool guard's whole import tree; engine-source locks stand aside only where root names a managed app [PROTECTED: owner merges] #691; named as a loosening that is root's alone). Theagent hooksdoctor warning (Lock scope follow-ups: old cli.js hook installs flagged and migrated, one githubRepoOf, updates.json read as root's file, readLockScope takes only the instance and its repo [PROTECTED: owner merges] #765). The GPU step after a root NVML call (GPU devices: close the after-boot window (a path unit and a stat-first fixer) and stop saying --no-dynamic-nodes stops NVML resets (v0.5.3, protected) #583)./etc/sudoers.d/worklane-console, by thedashboards.shrerun, nevermachine-helper.sh); the machine helper's rules are unchanged and still listclaude-account switch,profile-addandlogin, which the helper now refuses (claude credentials: the dashboard never takes one; the four paths that did are gone (#711 part 2, server) [PROTECTED: owner merges] #749).CHANGELOG.md, v0.5.2: a Correction (2026-10-19, GPU devices: close the after-boot window (a path unit and a stat-first fixer) and stop saying --no-dynamic-nodes stops NVML resets (v0.5.3, protected) #583) paragraph right after the false sentence, which stays as it was. It goes in the Highlights, above the generated lists, so a regeneration keeps it (measured below).scripts/setup/gpu-devices.sh:28(header) and:428(theechowhen there is nonvidia-cdi-refresh.service), the fixer's header (scripts/machine/worklane-gpu-devices.sh),docs/test-runs.md(the--no-dynamic-nodesbullet: line 202 on this base, line 199 in GPU devices: close the after-boot window (a path unit and a stat-first fixer) and stop saying --no-dynamic-nodes stops NVML resets (v0.5.3, protected) #583's text) anddocs/releasing.mdstep 10 now say the option doesn't stop the reset, and what to do until GPU devices: close the after-boot window (a path unit and a stat-first fixer) and stop saying --no-dynamic-nodes stops NVML resets (v0.5.3, protected) #583's fixer lands. None of them repeats the false phrase. The echoed line is the only text a command prints; no test pins it (measured:grep -rnof the test tree for it finds nothing).docs/releasing.mdstep 9: a0.5.3: noline saying why.test/changelog.test.ts: a new test, "an unreleased section is all hand-written, so the release keeps every line of it, under any heading". No existing test is changed.Round 2 (head 56e9706)
managed-apps.sh add: the notes now name the v0.5.2-era locks it lifts in that repo. Measured withselfModificationPart(path, 'engine' | 'managed-app')on the built head: the machine setup, the updater, the review levels, the merge policy, the main watch, the push guard, the guardrails,verify-release-tag,dashboard-auth,decideandmachine-ownersall givenullundermanaged-app. Still held there:.github/workflows/**,.github/release-signers, the config folder'sreview.yaml,guardrails.yamlandexperiments.yaml,.githooks/**,.denylist*and.gitleaks*. The v0.5.2 parts were checked againstgit show v0.5.2:src/review.ts. The notes say the default is to keep every lock, and the Upgrading bullet no longer offers it as the fix for the new waits.test-user.sh install: every install but the last exits 1 with several test users, and it refuses while a test run is in progress. Both are stated, with what to do.mcp-broker.shrerun also restarts the coordinator and dashboard (measured: the script's last block).checkfailure is quoted per install type (measured:egress-firewall.shlines 277, 284, 292).scripts/lib/changelog.mjsin memory):v0.5.3: 89 lines before, 95 after; dropped 0. Re-run on 657c79a: the same result.(#742)(a test finds the bullet by it), followed by "(Issue Remove the browser owner terminal, its sudoers rule and root helper (deprecate, then remove; keep the loopback break-glass and the device gate) #742, PR Remove the browser owner terminal, its sudoers rule and root helper; a dashboards.sh rerun deletes them; the device gate stays (#742) [PROTECTED: owner merges] #752.)". 657c79a: the fix from the adversarial pass below.Adversarial pass (round 2)
These attacks read the round-2 notes as an operator following them literally. Each was checked read-only against the scripts in the worktree, with no root and no real state. One gap was found and fixed in 657c79a, a text-only change to CHANGELOG.md.
Could the managed-apps wording still read as a recommended step?
grep -n -i managed-app CHANGELOG.md: lines 34, 60, 64 and 83). None of them calls it a step to take.updates.json(measured: the script's header). It needs<instance> <owner/repo>, which the operator must choose.Could the
test-user.shsequence leave an install half-done, or with looser caps?install_sudoers "worklane-test-$name") and its own finalcheck_cmd. Measured:test-user.shinstall, in the order slice and Docker unit, Docker restart, egress (exit 1 on rc 3), sudoers, check.git diff v0.5.2 origin/main -- scripts/setup/test-user.shdoesn't touchsudoers_rule). The skipped per-instance check is not replaced by the egresscheck.sudo bash /opt/worklane/current/scripts/setup/test-user.sh check <name>for each instance, then the egresscheck./etc/systemd/system/worklane-test.slice.slice()writes exactly those four keys from the arguments as given, in v0.5.0, v0.5.1 and v0.5.2 (git show <tag>:scripts/setup/test-user.sh: 4 keys each) and on this head, so re-reading the file between installs gives the same values.--memory-maxand--cpusare validated on input (^[1-9][0-9]*[KMGT]$, a CPU list), and the slice holds what passed that check (inferred).active_runsfor$name) and changes nothing (exit 1 before the restart).Does the
mcp-broker.shrestart warning cover every unit it restarts? Yes (measured).grep -n -E 'systemctl (restart|start|stop|enable|try-restart|reload)' scripts/setup/mcp-broker.shfinds three lines:enable --nowandrestartofworklane-mcp@<name>.service(lines 85-86);restartofworklane-<name>.serviceandworklane-dashboard-<name>.service, each only when active (line 92).The note names all three.
Could the generic egress quote hide a real failure? No (measured, plus inferred where noted).
check, which must endOK".check_cmdprintsOKonly when no check failed, and otherwise printsFAILEDand returns non-zero (egress-firewall.sh:300).checkcan print on a machine whose rule loaded are the "not covered" ones (:277,:284) and the exception ones (:292,:295). Each endsrun apply again. The quote describes exactly those.--with-test-users,checkalso printswl-test-<name> (<uid>) is not covered(:277). It doesn't.checktakes its users from the record (egress-firewall.sh:274), and that install's v0.5.2 record lists onlywl-exp-*/wl-score-*(v0.5.2covered()without the flag). So only the exception line (:292) prints there. Fixed in 456d468. The conclusion, "no real failure hidden", is unchanged.continues before its reach probe, anddocker_checkruns only once the exception is loaded. So neither a "reached 1.1.1.1" line nor anNFTSet=line appears before the reload (inferred: readingcheck_cmd).table ... is not loaded(:270),reached 1.1.1.1:443(:278,:285) ornot loaded at boot(:299), either doesn't end that way or survives the reload. Either way the finalcheckdoesn't endOK, so it isn't hidden.Round 3 (head 456d468)
src/,scripts/,test/ordocs/(engineSourceGlob,ENGINE_TREEinsrc/review.ts).git show v0.5.2:src/review.tsSELF_MODIFICATION_PARTS, builds a sample path, and runs the head'sselfModificationPartin both scopes. Result:globs: 28, lifted for a managed app: 24, held: 4. Each of the 24 returns its part underengineandnullundermanaged-app..worklane/review.yaml,.worklane/guardrails.yaml,.github/workflows/**and.github/release-signers.lockedCategories('managed-app')'s self-modification globs: those four, plus.denylist*,.gitleaks*,.githooks/**andexperiments.yaml(measured). Agent instructions also stay held there (roles/**,skills/**,AGENTS.mdand**/CLAUDE.md, measured).checkquote, from the code. On systemd 255 or later without the flag, only the exception line prints (see the correction under "Adversarial pass (round 2)" item 4). The sentence "every install but the last exits 1" is now qualified: only where the new rule gives Docker its exception (systemd 255 or later, not "dropped"). Elsewhereapplynever runsdocker_check.v0.5.3: 92 lines before, 98 after; dropped 0. Head merged with origin/main 178c44e (git merge-tree --write-tree, tree 9cd6de02): the same, 0 dropped.Adversarial pass (round 3): quoted lines and lists not derived from the code
Every quoted output line and every list of paths or names in the v0.5.3 section was checked against the code (read-only):
SELF_MODIFICATION_PARTSagainst v0.5.2's. It added three parts the note had missed: the agents' tool guard's hook commands (src/install.ts,src/instance.ts), where the self-modification locks apply (src/managed-apps.ts), and the sandbox'ssrc/testrun/**. It also addssrc/path-text.tsunder the review levels.test-user.sh's rc-3 message (test-user.sh:419) andtest runs of <name> are in progress(:396).egress-firewall.sh:284) and its exception line (:292).checkendsOK(:300).web/src/components/HealthPanel.tsx:192) and the coordinator's start lines (src/provider-hosts.ts:204-207).agent hooks <name>(src/install.ts:103).required check "desktop" hasn't run on this commit(worklane-update.cjs:484).REMOVED_CREDENTIAL_PATHS,src/claude-credential-guard.ts:30).grep -l claude-credential-guard src).lib.sh:154,:157,:197).test-user.shheader andslice(), same in v0.5.0 to v0.5.2).mcp-broker.sh:85-86,:92).src/provider-hosts.ts, docs/providers.md).Where release.mjs keeps the notes
release.mjs changelog X.Y.Zregenerates a section withscripts/lib/changelog.mjswithSection. That keeps onlyhandWritten: the text above the first heading the tool writes itself (Features,Fixes,Security and guardrails,Reverted in this release,Guardrail changes). It drops everything below it, and drops the whole section if it has no such heading (measured: reading the code, and the two runs below).### Guardrail changes, which is one of those headings. I never ranrelease.mjs(brief). Instead a scratch script calls the same two library functions on the file, with the version and one stand-in PR, and writes nothing (work/release-notes-v0.5.3/dryrun.mjsin the session scratchpad). Measured on origin/main's CHANGELOG.md:v0.5.3: 33 lines before, 30 after; dropped 8 hand-written line(s). All four bullets were dropped (Remove the browser owner terminal, its sudoers rule and root helper (deprecate, then remove; keep the loopback break-glass and the device gate) #742, Remove the desktop app, its required CI check and its Tauri lockfile (deprecate, then remove) #744, Updater: keep the root updater itself in step with the engine it installs, so updater fixes need no root rerun of updates.sh #760, Updater: refuse a release whose signed tag object names a different version than its ref (downgrade by re-pushing an old signed tag) #764), with Updater: refuse a release whose signed tag object names a different version than its ref (downgrade by re-pushing an old signed tag) #764's four sub-bullets.v0.5.3: 83 lines before, 89 after; dropped 0 hand-written line(s). The headings after regeneration areUpgrading (from v0.5.2),After install,Guardrail changes: what each one does,Features,Guardrail changes. For v0.5.2 the same run drops only the generated list lines it replaces (Pin the engine to v0.5.1 (#558),### Fixesand its one entry, because the stand-in PR list differs), and keeps the correction paragraph.(unreleased)section and requires every non-empty line to survive. On older, dated sections the lines below that heading are generated lists, so the new rule can't apply there. It also checks a fixture that has the bug and one that has the fix.scripts/lib/changelog.mjs). That makes the drop impossible rather than tested.Audit: every PR merged since v0.5.2
The list is
git log --merges --first-parent --format=%s v0.5.2..origin/main: 97 merges, #578 to #774 (measured). The tagv0.5.2(0b98938) isn't on main's history, as expected. Its parent is ede36e5 (#577), which is the merge-base with main.rev-list --count --merges --first-parentgives 97 from bothv0.5.2andede36e51.git diff --stat ede36e51 v0.5.2 -- . ':(exclude)dist'is empty, so the tag only addsdist/(all measured).Evidence: each PR's changed files (
git diff --name-only <merge>^1 <merge>, test/ and web/ left out) and its PR body, read in full for every PR whose files touchscripts/setup,scripts/machine, config schemas, sudoers or the updater (measured). "In notes" means the## v0.5.3 (unreleased)section on origin/main (c339175). "Added" means this PR adds the note.scripts/bootstrap.shonlysrc/review.tssrc/coordinator/**and the studies' file locked; the rest is this repo's own configsrc/review.ts,.worklane/review.yamlscripts/push-gate-hook.mjs.claude/settings.jsonandagents.yamlscripts/eval-roles.mjs,src/eval-roles.tsscripts/setup/lib.shinstance_in_flightsrc/github/budget.tsscripts/setup/claude-account.sh(runs from the engine tree)ProtectControlGroups=yesonly aftermcp-broker.sh <name>rerunsscripts/setup/mcp-broker.sh; PR's "Upgrade note"effortkeys; unset sends nonelib.shrechown_orphansmain_watch.*keys; dead-check warningsrc/config/schema.ts/etc/worklane/providers.json; somebase_urls make the config invalidfetchscripts/machine/worklane-testrun.shis in the updater'sHELPERSsrc/dashboard.tssrc/agent-settings.ts,src/runner.tssrc/comment-policy.tsrepro: true; no install stepscripts/push-gate.mjssrc/coordinator.tsscripts/dev/src/hook.tslockedsrc/review.tsclaude-account.sh,lib.shclaudedrops--settingssrc/review.ts--spend; held-out only when namedlib.shstop_uidssrc/health.ts,src/provider-hosts.tsNFTSet=(test-user.sh install); new default required check for new installsegress-firewall.shmanaged-apps.sh add; old hook form untilinstallormigratescripts/setup/managed-apps.shworklane-machine.cjsdiffdashboards.shrerun; console sudoers rule removedlib.shdiffupdates.shrerunscripts/bench(dev only)updates.shrerunagent hooks <name>until migrated;updates.jsonread as root's fileupdates.shreruncheckfails untilapplyruns again; subordinate uids dropped afterdashboards.shlines keep--siri/--hooks/devicesThe questions in the brief
scripts/ci/egress-real-systemd.sh), so they need nothing on an install. What does reach an install is the new default (test users covered) and Docker'sNFTSet=, which take effect when the rule is loaded again: a note is added (measured: the PR body;egress-firewall.shdecide())./etc/worklane/egress.usersexists, the record since before Egress firewall: cover test-run users by default, narrow exception for rootless-Docker image pulls (v0.5.2) #543: measured in v0.5.2's script), the boot unit loads the old/etc/worklane/egress.nft, so the ranges aren't dropped andcheckfails untilapplyruns again.test-user.sh installreloads it itself. A machine with no record has no rule, and nothing changes for it (measured: the PR body and the scripts).machine_sudoersstill listsclaude-account switch(lib.sh:197), andprofile-addandlogin(lib.sh:154, :157). The helper refuses all three (measured: lib.sh, and the claude credentials: the dashboard never takes one; the four paths that did are gone (#711 part 2, server) [PROTECTED: owner merges] #749 diff ofworklane-machine.cjs). The update replaces the helper, since/usr/local/libexec/worklane-machineis in theHELPERSof both v0.5.2's updater and main's (measured:git show v0.5.2:scripts/machine/worklane-update.cjs). So no rerun is needed, and the lines grant nothing.dashboards.shrerun. Nothing else changes sudoers since v0.5.2: the onlysudoerslines ingit diff ede36e51 origin/main -- scripts/setup scripts/machineare the console rule's removal and its messages (measured). So there is no machine-helper rerun, and the line now says all of this.managed-apps.json, which is added as an optional, root-only loosening.src/only).--no-replace-objects). Review levels: a renamed file counts under its old and its new path, in changeFiles and every path reader [PROTECTED: owner merges] #688 also changedverifyRelease(--no-renames -z); it reaches a machine through the sameupdates.shrerun (inferred).How it was verified
node --import ./dist/test/isolate.js --teston changelog, release, package-files, update-checks, setup-install-check, testrun-gpu, testrun-gpu-helper, testrun-network, testrun-pip-fetch, merge-policy and owner-terminal-removed, with a tempHOME,WORKLANE_STATE_DIR,WORKLANE_HELD_OUT_DIR,TMPDIRand a fakeclaudefirst onPATH:# tests 115 / # pass 115 / # fail 0(measured).CHANGELOG.mdchecked out over this branch's (git checkout origin/main -- CHANGELOG.md, restored after withgit checkout HEAD -- CHANGELOG.md),changelog.test.jsgives# tests 10 / # pass 9 / # fail 1,not ok 8 - an unreleased section is all hand-written…:v0.5.3 (unreleased): the release would drop these lines. With this branch's:# tests 10 / # pass 10 / # fail 0.npm run typecheck: exits 0.npm run check:denylist:denylist: 1292 files clean.node scripts/check-dco.mjs origin/main HEAD:DCO: all commits signed off.node scripts/push-gate.mjs --range origin/main..HEAD:push gate: 3 commit(s) and 1 tip(s) clean.bash -non both GPU scripts: exit 0 (all measured).release.mjsin any mode, anything as root. The GPU wording rests on GPU devices: close the after-boot window (a path unit and a stat-first fixer) and stop saying --no-dynamic-nodes stops NVML resets (v0.5.3, protected) #583's measurement on the owner's machine, which I didn't repeat./usr/local/libexec/worklane-gpu-devicesthe same way it replaces any helper whose bytes differ. It runs exactly as before.Adversarial pass
Attack: could a reader follow these notes and loosen anything, or skip a needed root step? Each attack was tried against the text and the scripts it names, by reading and by the dry run; no root and no real state.
test-user.sh installwithout the old options loosens the test runs' caps: each option left out takes its default (the last 8 CPUs,6G, weight 10). Found and fixed (commit 3): the step now asks for all four values, exactly as/etc/systemd/system/worklane-test.slicehas them, and says why (measured: the defaults intest-user.sh's header and--cpus/--memory-maxparsing).egress-firewall.sh applywith no flags on a "dropped" install (old--with-test-users) could reopen Docker's routes. It doesn't:decide()reads the record first, and a pre-Egress firewall: cover test-run users by default, narrow exception for rootless-Docker image pulls (v0.5.2) #543# --with-test-usersor# docker: droppedkeeps the mode dropped whatever the flags (measured:lib.shegress_docker_mode,egress-firewall.shdecide). The notes say so.checkfails, which the notes quote. No route opens (inferred from egress-firewall: drop each covered user's subordinate uids too, not only its own uid (#740) [PROTECTED: owner merges] #778's "Who is affected"; the boot unit loads the stored rule file).applybeforetest-user.sh installblocks new image pulls. That fails closed and is stated, with the fix.managed-apps.sh addis a loosening. It is labelled as one, root's alone, refused for the engine's own repo and withoutupdates.json(measured: the script's header). It stays optional.providers.json: the notes give exact entries, say there are no wildcards, and point to the rules. A reader can't widen beyond what root lists. Listing a shared multi-tenant host is providers: a provider's base_url only to a host root allows in /etc/<cli>/providers.json [PROTECTED: owner merges] #607's stated residual (inferred).git diff --stat ede36e51 origin/main -- scripts/setup scripts/machine, measured). Units, sudoers or root files a script writes need a rerun. A script run from/opt/worklane/currentneeds none, and neither does a helper in the updater'sHELPERS. Covered:dashboards.sh(existing),updates.sh(existing),egress-firewall.sh/test-user.sh(added),mcp-broker.sh(added, optional),managed-apps.sh(added, optional),providers.json(added).machine-helper.sh's rules and the test-user sudoers rule are unchanged (measured: nosudoerslines in the diff besides the console rule's).claude-account.sh,credentials.sh,add-project.sh,tailscale.sh,instance-remove.shandlib.shrun from the engine tree.worklane-machine,worklane-testrunand the GPU fixer are synced by the updater.check: it only tightens.git diff origin/main -- test/adds one test and helper, and removes nothing).What this can never do
scripts/is comments plus one echoed message (measured:git diff origin/main -- scripts/).release.mjsdrop a line of a section titled exactly## vX.Y.Z (unreleased)without a test failing. A section titled any other way isn't checked by the new test (Release: an (unreleased) CHANGELOG section survives release.mjs whole, by design (no heading-layout rule) #811, the structural fix, would cover it).Compromised component
CHANGELOG.md(inferred).engine-guardsfor docs/releasing.md,enginefor scripts), and every command in them points at a script that refuses the unsafe cases (dropped mode kept, engine repo refused, root-owned files only) (inferred). They could also weaken the new test; it is reviewed like any test.New dependencies
None.