Skip to content

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

Merged
jayden1711 merged 7 commits into
mainfrom
release-notes-v0.5.3
Oct 19, 2026
Merged

jayden1711 merged 7 commits into
mainfrom
release-notes-v0.5.3

Conversation

@jayden1711

@jayden1711 jayden1711 commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

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.3 would 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.sh and scripts/machine/worklane-gpu-devices.sh are engine (scripts/**) and self-modification ("the machine setup"); docs/releasing.md is engine-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

Round 2 (head 56e9706)

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.

  1. Could the managed-apps wording still read as a recommended step?

    • Mitigated, with a residual. The step opens with "Not a required step. The default is to keep every lock. Naming a repo as a managed app is a loosening, root's alone to decide". It names every v0.5.2 lock it lifts and what still holds.
    • The Upgrading bullet says "The default is to keep every lock and review those waits" and "it is not the fix for these waits". The guardrail note says "Loosening, root-only, off by default".
    • Measured: those are the only places in CHANGELOG.md that name managed apps (grep -n -i managed-app CHANGELOG.md: lines 34, 60, 64 and 83). None of them calls it a step to take.
    • Residual (inferred): it still sits in the "After install" list, with a command ready to paste. Someone who runs every command in that list without reading would run it. It can't run by itself: it refuses a coordinator's sudo, the engine's own repo, and a machine with no updates.json (measured: the script's header). It needs <instance> <owner/repo>, which the operator must choose.
  2. Could the test-user.sh sequence leave an install half-done, or with looser caps?

    • Half-done: found, and fixed in 657c79a. An install that exits 1 at the egress step (every one but the last, with several test users) stops before its last two steps. Those are the sudoers write (install_sudoers "worklane-test-$name") and its own final check_cmd. Measured: test-user.sh install, in the order slice and Docker unit, Docker restart, egress (exit 1 on rc 3), sudoers, check.
    • The sudoers rule it skips is unchanged since v0.5.2 and already installed (measured: git diff v0.5.2 origin/main -- scripts/setup/test-user.sh doesn't touch sudoers_rule). The skipped per-instance check is not replaced by the egress check.
    • A literal "carry on" could also carry on past a different error, such as Docker not starting.
    • The note now says to go past only that one message, since any other error stops the sequence. After the last install, run sudo bash /opt/worklane/current/scripts/setup/test-user.sh check <name> for each instance, then the egress check.
    • Looser caps: no (measured). The slice is shared, and each install rewrites it from its own arguments. The notes require all four values, read from /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-max and --cpus are validated on input (^[1-9][0-9]*[KMGT]$, a CPU list), and the slice holds what passed that check (inferred).
    • The test-run refusal is per instance (active_runs for $name) and changes nothing (exit 1 before the restart).
  3. Does the mcp-broker.sh restart warning cover every unit it restarts? Yes (measured). grep -n -E 'systemctl (restart|start|stop|enable|try-restart|reload)' scripts/setup/mcp-broker.sh finds three lines:

    • enable --now and restart of worklane-mcp@<name>.service (lines 85-86);
    • a restart of worklane-<name>.service and worklane-dashboard-<name>.service, each only when active (line 92).

    The note names all three.

  4. Could the generic egress quote hide a real failure? No (measured, plus inferred where noted).

    • The pass criterion in the notes is the last line: "check, which must end OK". check_cmd prints OK only when no check failed, and otherwise prints FAILED and returns non-zero (egress-firewall.sh:300).
    • Before the reload, the failures check can print on a machine whose rule loaded are the "not covered" ones (:277, :284) and the exception ones (:292, :295). Each ends run apply again. The quote describes exactly those.
    • Correction (round 3): one claim above was false through 657c79a. The notes and this item said that on systemd 255 or later without --with-test-users, check also prints wl-test-<name> (<uid>) is not covered (:277). It doesn't. check takes its users from the record (egress-firewall.sh:274), and that install's v0.5.2 record lists only wl-exp-*/wl-score-* (v0.5.2 covered() without the flag). So only the exception line (:292) prints there. Fixed in 456d468. The conclusion, "no real failure hidden", is unchanged.
    • A not-covered user continues before its reach probe, and docker_check runs only once the exception is loaded. So neither a "reached 1.1.1.1" line nor an NFTSet= line appears before the reload (inferred: reading check_cmd).
    • A real failure, such as table ... is not loaded (:270), reached 1.1.1.1:443 (:278, :285) or not loaded at boot (:299), either doesn't end that way or survives the reload. Either way the final check doesn't end OK, so it isn't hidden.

Round 3 (head 456d468)

  • managed-apps, written from the rule. It lifts every self-modification lock whose path is under src/, scripts/, test/ or docs/ (engineSourceGlob, ENGINE_TREE in src/review.ts).
    • Measured: a read-only script takes each glob in git show v0.5.2:src/review.ts SELF_MODIFICATION_PARTS, builds a sample path, and runs the head's selfModificationPart in both scopes. Result: globs: 28, lifted for a managed app: 24, held: 4. Each of the 24 returns its part under engine and null under managed-app.
    • The After-install step now lists all 24, grouped by part, including all six sign-in, credentials or secrets globs and all four who-may-approve files. The guardrail note names the same parts.
    • The 4 held are .worklane/review.yaml, .worklane/guardrails.yaml, .github/workflows/** and .github/release-signers.
    • The "still held" list is unchanged. It matches lockedCategories('managed-app')'s self-modification globs: those four, plus .denylist*, .gitleaks*, .githooks/** and experiments.yaml (measured). Agent instructions also stay held there (roles/**, skills/**, AGENTS.md and **/CLAUDE.md, measured).
    • The default-keep advice is unchanged.
  • The egress check quote, 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"). Elsewhere apply never runs docker_check.
  • Survival (measured, in memory): head 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):

Where release.mjs keeps the notes

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 tag v0.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-parent gives 97 from both v0.5.2 and ede36e51. git diff --stat ede36e51 v0.5.2 -- . ':(exclude)dist' is empty, so the tag only adds dist/ (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 touch scripts/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.

PR Effect on an existing install In notes? Evidence
#578 none: pins bootstrap to v0.5.2 (new installs only) n/a scripts/bootstrap.sh only
#580 managed repos: more paths locked L3 (evaluator, sandbox, push gate, denylist, their tests, release rules) no, added src/review.ts
#584 managed repos: src/coordinator/** and the studies' file locked; the rest is this repo's own config no, added src/review.ts, .worklane/review.yaml
#588 none: this repo's push hook n/a scripts/push-gate-hook.mjs
#590 none: this repo's own .claude/settings.json and agents.yaml n/a files
#591 none: owner-run eval CLI n/a scripts/eval-roles.mjs, src/eval-roles.ts
#592 instance removal: a process that exited mid-check no longer counts as in flight (a narrower refusal) no, added (guardrails) scripts/setup/lib.sh instance_in_flight
#593 none: one GitHub budget per token, internal n/a src/github/budget.ts
#594 none: web n/a web/test only
#595 none: a token install drops the cached account n/a scripts/setup/claude-account.sh (runs from the engine tree)
#596 broker unit gains ProtectControlGroups=yes only after mcp-broker.sh <name> reruns no, added (optional step) scripts/setup/mcp-broker.sh; PR's "Upgrade note"
#597 none: UI nav n/a files
#598 none: opt-in effort keys; unset sends none n/a body
#599 none: tests n/a test only
#600 none: owner-run eval CLI n/a files
#601 removed instance's files in a shared group lose group write no, added (guardrails) lib.sh rechown_orphans
#602 main watch: revert PRs capped (1 per trip, 3 a day), new main_watch.* keys; dead-check warning no, added body; src/config/schema.ts
#606 alert says why there is no revert PR; a rebase-merged range reverts as one no, added (with #602) body
#607 provider runs fall back until root writes /etc/worklane/providers.json; some base_urls make the config invalid no, added (measured: no "provider" in the section on main) body's "Changelog note"; docs/providers.md
#609 none required (the updater syncs the test-run helper); new opt-in pip fetch no, added (guardrails) scripts/machine/worklane-testrun.sh is in the updater's HELPERS
#610 none: UI n/a files
#611 none: monotonic cache clocks n/a src/dashboard.ts
#618 none: comment policy, off until a repo turns it on n/a body
#619 agents' commits and PRs carry no vendor attribution no, added (guardrails) src/agent-settings.ts, src/runner.ts
#620 UI: nav tabs; old routes redirect n/a docs/feature-map.md only
#621 none: UI n/a docs only
#623 none: comment policy (off by default) n/a files
#624 none: delegation view n/a files
#627 none: UI n/a docs only
#630 none: UI n/a files
#632 none: accounts table shows usage n/a files
#634 none: comment policy (off by default) n/a src/comment-policy.ts
#635 intake bug drafts carry repro: true; no install step n/a (#643's note covers the upgrade case) body
#636 none: harness evals n/a files
#637 deploy gate: a failed post-deploy check files an issue; nothing when not configured n/a body
#638 none: chat n/a files
#640 none: waiting-comment format n/a files
#642 none: this repo's push gate n/a scripts/push-gate.mjs
#645 none n/a src/coordinator.ts
#646 none: dev script n/a scripts/dev/
#647 managed repos: src/hook.ts locked no, added src/review.ts
#648 none: setup timeouts (scripts run from the engine tree) n/a claude-account.sh, lib.sh
#649 agent runs fail closed if claude drops --settings no, added body
#650 none: UI n/a files
#651 none: opt-in CLI n/a files
#652, #653, #654, #656 none: tests and fixtures n/a test only
#657 dead-check warning names a disabled workflow n/a (with #602) files
#658 managed repos: guard's modules locked no, added src/review.ts
#659 evals exit 2 without --spend; held-out only when named no, added body
#660 stricter redaction no, added (guardrails) files
#662 none: CI n/a files
#663 viewers no longer see account names no, added (guardrails) files
#664 none: UI n/a docs only
#666 instance removal's last check fails closed no, added (guardrails) lib.sh stop_uids
#668 none n/a files
#670 none: more screenshots n/a files
#671 intake upgrade refusals yes (#643 bullets) CHANGELOG on main
#672 none: this repo's CI n/a workflows
#675 Machine page shows root's provider hosts no, added (with #607) src/health.ts, src/provider-hosts.ts
#676 none: Machine page budget n/a files
#678 none: CI n/a files
#681 none: this repo's review.yaml n/a files
#682 ops posts name no account no, added (guardrails) files
#684 none: test n/a test only
#686 egress rule covers test users by default once loaded again; Docker needs NFTSet= (test-user.sh install); new default required check for new installs no, added body's "What changed" and "Who is affected"; egress-firewall.sh
#688 review levels: renames count both paths, odd paths wait, pure moves count double; some accidental holds gone (owner-accepted) no, added body
#690 none: UI n/a files
#691 managed repos: guard's import tree locked; optional root managed-apps.sh add; old hook form until install or migrate no, added body; scripts/setup/managed-apps.sh
#692 none: test n/a test only
#694 none: test-run defaults only n/a files
#696 none n/a files
#698, #718, #719, #723, #725, #754 none: docs, and this repo's review.yaml n/a files
#749 dashboard routes answer 410; add-project waits for root's login; helper refuses switch, profile-add and login; sudoers lines for them stay no, added body's "Existing installs"; worklane-machine.cjs diff
#751 UI half of #749 no, added body
#752 dashboards.sh rerun; console sudoers rule removed yes; the sudoers line is corrected here CHANGELOG on main; lib.sh diff
#753 none: release workflow budget n/a files
#755 updates.sh rerun yes CHANGELOG on main
#756, #757 none: scripts/bench (dev only) n/a files
#761 updates.sh rerun yes CHANGELOG on main
#765 doctor warns agent hooks <name> until migrated; updates.json read as root's file no, added body's "Residual risk"
#766 none: slot writes internal; a leftover 0444 temp no longer breaks a write n/a body
#773 release refusals; reaches a machine via the updates.sh rerun yes CHANGELOG on main
#774 none: weekly CI workflow n/a workflow only
#777 none: test n/a test only
#778 egress check fails until apply runs again; subordinate uids dropped after no, added body's "Who is affected"
#779 by-hand dashboards.sh lines keep --siri/--hooks/devices no, added body
#790, #797 none: tests n/a test only

The questions in the brief

How it was verified

  • node --import ./dist/test/isolate.js --test on 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 temp HOME, WORKLANE_STATE_DIR, WORKLANE_HELD_OUT_DIR, TMPDIR and a fake claude first on PATH: # tests 115 / # pass 115 / # fail 0 (measured).
  • Fail-without (measured): with origin/main's CHANGELOG.md checked out over this branch's (git checkout origin/main -- CHANGELOG.md, restored after with git checkout HEAD -- CHANGELOG.md), changelog.test.js gives # 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 -n on both GPU scripts: exit 0 (all measured).
  • Not run: the full suite (CI runs it), release.mjs in 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.
  • Inferred: the fixer's header comment changes its bytes, so the update replaces the installed /usr/local/libexec/worklane-gpu-devices the 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.

  1. test-user.sh install without 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.slice has them, and says why (measured: the defaults in test-user.sh's header and --cpus/--memory-max parsing).
  2. egress-firewall.sh apply with 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-users or # docker: dropped keeps the mode dropped whatever the flags (measured: lib.sh egress_docker_mode, egress-firewall.sh decide). The notes say so.
  3. Skipping the egress reload leaves the old rule loaded, so it opens nothing, and check fails, 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).
  4. Running apply before test-user.sh install blocks new image pulls. That fails closed and is stated, with the fix.
  5. managed-apps.sh add is a loosening. It is labelled as one, root's alone, refused for the engine's own repo and without updates.json (measured: the script's header). It stays optional.
  6. 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).
  7. A skipped needed root step: checked every setup script changed since v0.5.2 (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/current needs none, and neither does a helper in the updater's HELPERS. 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: no sudoers lines in the diff besides the console rule's). claude-account.sh, credentials.sh, add-project.sh, tailscale.sh, instance-remove.sh and lib.sh run from the engine tree. worklane-machine, worklane-testrun and the GPU fixer are synced by the updater.
  8. The GPU note asks only for the fixer and check: it only tightens.
  9. The notes surviving the release: measured by the dry run (0 dropped) and held by the new test.
  10. Principle 9: no test, lint or guard gets an exception, skip or allowlist entry. No existing check is changed (measured: git diff origin/main -- test/ adds one test and helper, and removes nothing).

What this can never do

Compromised component

  • Agent user, coordinator, dashboard, hub viewer, other instances: nothing new reaches them. This PR is text, plus a test that reads CHANGELOG.md (inferred).
  • Someone with push access: could edit the notes to tell an owner to loosen something, such as a wrong egress or provider step. These notes are under owner review (engine-guards for docs/releasing.md, engine for 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.
  • A reader of the notes (the owner at a root shell): the main risk, covered under "Adversarial pass" above.

New dependencies

None.

…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>
@jayden1711

Copy link
Copy Markdown
Owner Author

Evaluator verdict: CHANGES NEEDED (round 1)

Reviewed head b1856ffa against origin/main c339175a, in a scratch worktree with a temp HOME, state dirs and a fake claude. No root, no release.mjs, no real state. Labels: measured = from a command run here; inferred = from reading code; guess.

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 managed-apps.sh add also lifts locks v0.5.2 already applied, and the Upgrading bullet points to it as the way to stop the new waits. The other three are smaller: one rerun can stop with an error partway through, one leaves out a restart, and one quotes a check message that most installs won't see.

Required changes

  1. managed-apps.sh add reopens locks v0.5.2 already had in that repo, and the notes don't say so (principles 3 and 6). Measured with the engine's own selfModificationPart(path, scope) on this head: in a managed-app scope, scripts/setup/**, scripts/machine/**, src/review.ts, src/merge-policy*, src/main-watch*, src/push-check*, src/updates*, src/guardrails/**, scripts/verify-release-tag*, src/dashboard-auth*, src/decide.ts and src/machine-owners.ts all return null. Under the engine scope they return their parts. v0.5.2's SELF_MODIFICATION_PARTS had all of these, with no scope, so they applied in every repo (measured: git show v0.5.2:src/review.ts). Only .github/workflows/**, .github/release-signers and the config folder's review.yaml/guardrails.yaml still hold (measured).
    • The After-install bullet is labelled well ("Optional, and a loosening that is root's alone to decide"). But "the locks on the engine's own source paths then stand aside" reads like "this release's new locks", and the Upgrading bullet "More changes wait…" ends "until root names that instance's repo as a managed app (see After install)", which presents the loosening as the fix for the new waits.
    • Fix: in the After-install bullet and in the guardrail note, say that it also lifts, for that repo, the locks v0.5.2 already applied (name them, as above). In the Upgrading bullet, say the default is to keep every lock and review the waits, and that naming a repo is the root-only loosening described below. Text only; 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's design is owner-accepted and doesn't change.
  2. test-user.sh install (the egress step) stops partway through on a machine with more than one test user, or with a test run in progress. The notes don't say either.
    • Each install reloads the rule. apply then runs check, whose docker_check FAILs for every other test user's running Docker unit that doesn't have NFTSet= yet. apply exits 3, and test-user.sh turns that into exit 1 ("fix its FAIL lines above (they may be another instance's), then run install again") before its sudoers and final-check steps (inferred: test-user.sh install, the egress_rc case; egress-firewall.sh apply_cmd/check_cmd/docker_check). So with N test users, the first N-1 installs exit 1. The end state is still right once every one has run, because the Docker unit is rewritten before the egress step and the test-user sudoers rule hasn't changed since v0.5.2 (measured: git diff v0.5.2 origin/main -- scripts/setup/test-user.sh only adds the egress parts). But someone following the notes exactly hits an error at step one.
    • Separately, this rerun always changes the Docker unit (it gains NFTSet=). It then refuses with exit 1 while that instance has a test run in progress (inferred: the active_runs branch).
    • Fix: one or two sentences. Run it when the instance has no test run going. With several test users, every install but the last may exit 1 on the others' NFTSet= FAIL lines: carry on through all of them, then egress-firewall.sh check must end OK.
  3. The optional mcp-broker.sh <name> rerun also restarts that instance's coordinator and dashboard, not just the broker (inferred: the last block of mcp-broker.sh restarts worklane-<name>.service and worklane-dashboard-<name>.service when active). That cuts off in-flight agent runs. Say so, so the owner picks a quiet moment.
  4. The quoted check failure (... subordinate uids ... are not covered: run apply again) only appears on some installs (principle 5). It shows up only when the old record lists test users, that is apply --with-test-users. Take a v0.5.2 install on systemd 255 or later applied without that flag: its record has no test users, so check instead fails with "the exception for the set egress_ok isn't loaded, so test users' Docker can't pull images: run apply again". On systemd below 255 without the flag, check may pass, and the reload changes nothing (inferred: v0.5.2's record and this head's decide/check_cmd). Quote it generically ("a FAIL line ending run apply again"), or give both lines.

Checks

Item Result
Audit: 97 merges since v0.5.2 Agree, no PR wrongly marked "no install effect" (measured: git log --merges --first-parent v0.5.2..origin/main = 97; every merge's non-test/web/docs files listed). Every PR touching scripts/setup/**, scripts/machine/**, config schema, sudoers, egress, GPU, providers or the updater has a note or a correct "n/a". I read the source diffs behind the less obvious "none" rows: #590 (src/agent-settings.ts: an error message only), #598/#618/#609 (schema additions, all opt-in), #637 (post-deploy issue only when configured), #657 (Actions 403/404 handled as "say nothing"), #694 (slot guard active only in a test run), #632 (src/service.ts refactor). Nits: #590's evidence says "files" although it touched src/; #694's description is loose.
Root helpers synced, no rerun Agree (measured): worklane-testrun, worklane-machine and worklane-gpu-devices are in the HELPERS of both v0.5.2's updater and this head's.
Sudoers line Correct (measured): the only lib.sh hunks since v0.5.2 in the sudoers region are the console rule's removal; machine_sudoers still lists claude-account switch (lib.sh:197) and profile-add/login (:154, :157); test-user.sh's sudoers_rule is unchanged.
dashboards.sh rerun No trap found (inferred): the by-hand line covers every hub option hub_keep_args keeps (client id, public URL, machine devices, --siri, --hooks, instance list); --machine-owner left out keeps the file.
egress-firewall.sh apply (no test user) Doesn't reopen anything (inferred): egress_docker_mode reads # --with-test-users from a v0.5.2 record as dropped, and decide then covers test users with no exception, whatever the flags.
test-user.sh with all four slice values Right fix (inferred): the slice's AllowedCPUs/MemoryMax/CPUWeight/IOWeight map 1:1 onto the four options, and leaving one out takes its default. See required change 2 for the other traps.
updates.sh --add-check Safe and idempotent (inferred, from the script header and the others guard): it only edits; the check name is in UPDATE_DEFAULT_CHECKS, so main runs it.
managed-apps.sh add Optional and root-only (refuses SUDO_USER=wl-*, the engine repo, no updates.json). Labelling: see required change 1.
GPU step Only tightens (inferred): the fixer, then check. The fixer's path matches FIXER in gpu-devices.sh.
Release drop, re-measured Matches the builder (measured, my own read-only script calling sectionFor/withSection/changelogSection from scripts/lib/changelog.mjs, the same calls release.mjs makes at lines 77-78): origin/main v0.5.3: 33 lines before, 30 after; dropped 8 (the four #742/#744/#760/#764 bullets and #764's four sub-bullets); head 83 before, 89 after; dropped 0; rest of the file unchanged. v0.5.2 regenerated: the Correction paragraph is kept; only the generated list lines go.
Merged with the open PRs that merge cleanly (#787, #780, #769) 0 lines dropped from v0.5.3 in each merge-tree result (measured).
New test, fail-without Fails on main's CHANGELOG (measured: git checkout origin/main -- CHANGELOG.md in my worktree, then node --import ./dist/test/isolate.js --test dist/test/changelog.test.js): # 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 the head's: 10/10. changelog + release + package-files: 28/28 pass (measured).
Structural fix (follow-up) Yes, better (principle 4): make withSection keep every line of an (unreleased) section, or have release.mjs changelog refuse when a non-generated line would go. The test only matches the exact (unreleased) spelling; a section titled any other way isn't checked. That limit also makes "What this can never do" item 3 an overclaim (inferred). File it as an issue.
v0.5.2 correction Dated (2026-10-19, #583), right after the false sentence, which is unchanged (measured: CHANGELOG.md:95, :97). It survives regeneration (above).
NVML wording gpu-devices.sh:28 header, :430 (:428 on main) echo, the fixer header, docs/test-runs.md:202 and releasing.md step 10: none says the option stops the reset. A grep for the false phrasings finds only the v0.5.2 original (measured).
Fixer code unchanged Yes (measured): git diff -U0 origin/main HEAD on both GPU scripts, minus # lines, leaves only the one echo in gpu-devices.sh. bash -n passes on both. The CI grep at testrun-gpu-real-systemd.sh:514 matches a prefix the edit keeps.
#607 install line Same as docs/providers.md:54 (measured). Idempotent; the file is read on each use (inferred).
Example providers.json Accepted by the engine's reader (measured: readProviderHosts on {"hosts": ["api.provider.example", "127.0.0.1:8080"]} gives state: ok). https://api.provider.example[/v1] and http://127.0.0.1:8080[/v1] are allowed; :8443, http://localhost:8080 and other hosts are refused. The start-log text matches the note (prefix). Nit: "one root doesn't own or others may write": the reader refuses a group-writable file too.
Protection Correct (measured: .worklane/review.yaml puts scripts/** under engine and docs/releasing.md under engine-guards). The PR has the [PROTECTED: owner merges] title, needs-owner, Risk class: protected, "What this can never do" and "Compromised component".
Adversarial pass Present, 10 attacks with results. Items 1-2 above were missed.
DCO / attribution DCO: all commits signed off (measured); no Co-Authored-By or vendor line in the commits or the body (measured).
typecheck / denylist exit 0; denylist: 1292 files clean (measured, on the head).
CI on b1856ff All green: test shards (22, 24), real-systemd jobs, denylist, dco, gitleaks, agentshield. full-ci, bootstrap and bootstrap (windows-wsl) skipped (measured: statusCheckRollup).
Owner's real state (rule A) The new test reads only the repo's CHANGELOG.md (inferred).
Scope Matches the brief: notes, docs, comments, one echo, one test. #583's fixer, path unit and tests are left out, as stated.

Merge-tree (measured, git merge-tree --write-tree, read-only)

Follow-ups (not blocking)

…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>
@jayden1711

Copy link
Copy Markdown
Owner Author

Round 2: head 56e9706

Changes since b1856ff, all release-note text in CHANGELOG.md, in one commit. Labels: measured = from a command run here; inferred = from reading code.

  1. managed-apps.sh add (required change 1). The After-install bullet now opens with "Not a required step. The default is to keep every lock. Naming a repo as a managed app is a loosening, root's alone to decide."
    • It names the locks that add lifts for that repo: this release's new ones, and also the ones v0.5.2 already applied in every repo. Those are the machine setup, the updater, the review levels, the merge policy, the main watch, the push guard, the guardrails, scripts/verify-release-tag*, src/dashboard-auth*, src/decide.ts and src/machine-owners.ts.
    • Measured: selfModificationPart(path, 'managed-app') on the built head gives null for each, and 'engine' gives its part. The same parts are in git show v0.5.2:src/review.ts.
    • Still held in that repo (measured): .github/workflows/**, .github/release-signers, review.yaml, guardrails.yaml. The same probe also shows experiments.yaml, .githooks/**, .denylist* and .gitleaks* still held, so the note lists those too.
    • The guardrail note says the same.
    • The Upgrading bullet now says: "The default is to keep every lock and review those waits." It calls naming a repo a separate root-only loosening that also lifts v0.5.2's locks, "so it is not the fix for these waits".
  2. test-user.sh install (required change 2). The step now says to run as root "at a time when no instance has a test run going". It also gives the two ways the install stops with exit 1:
    • More than one test user: every install but the last exits 1, quoting the script's message. Carry on through every one; the state is right after the last, and check shows it.
    • A test run in progress (test runs of <name> are in progress): let them finish, then run it again (inferred: test-user.sh lines 393-399 and 416-419).
  3. mcp-broker.sh <name> (required change 3). The note says it also restarts worklane-<name>.service and worklane-dashboard-<name>.service when they are running, which cuts off in-flight agent runs, so pick a quiet moment (measured: the script's last block).
  4. The check quote (required change 4). It now says "one or more FAIL lines ending run apply again", with the lines for each kind of install:
    • systemd 255 or later without --with-test-users: the exception for the set egress_ok isn't loaded, ... and wl-test-<name> (<uid>) is not covered;
    • with --with-test-users: the subordinate-uids line;
    • systemd older than 255 without the flag: check may pass, and the reload changes nothing (measured: egress-firewall.sh lines 277, 284 and 292; the per-install mapping is inferred from decide).
  5. Nits.

Checks (measured):

  • Release-survival check, through scripts/lib/changelog.mjs in memory (no release.mjs): v0.5.3: 89 lines before, 95 after; dropped 0 hand-written line(s).
  • changelog, release and package-files tests: # tests 28 / # pass 28 / # fail 0, with a temp HOME, state dirs and a fake claude.
  • denylist: 1292 files clean. DCO: all commits signed off. Push gate: 4 commit(s) and 1 tip(s) clean.
  • CI on 56e9706 is still running; the builder reports it to the orchestrator.

…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>
@jayden1711

Copy link
Copy Markdown
Owner Author

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 The dashboard's owner terminal is removed (#742), and my switch to PR numbers had changed the title. The title is back to #742 and is followed by '(Issue #742, PR #752.)'; the test itself is untouched. Measured locally: changelog, release, package-files, owner-terminal-removed, owner-terminal-rerun, hotspots and comment-policy-cli give 59/59 pass, and the survival check through scripts/lib/changelog.mjs still drops 0 lines.

…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>
@jayden1711

Copy link
Copy Markdown
Owner Author

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.

  1. Could the managed-apps wording still read as a recommended step?

    • Mitigated, with a residual. The step opens with "Not a required step. The default is to keep every lock. Naming a repo as a managed app is a loosening, root's alone to decide". It names every v0.5.2 lock it lifts and what still holds.
    • The Upgrading bullet says "The default is to keep every lock and review those waits" and "it is not the fix for these waits". The guardrail note says "Loosening, root-only, off by default".
    • Measured: those are the only places in CHANGELOG.md that name managed apps (grep -n -i managed-app CHANGELOG.md: lines 34, 60, 64 and 83). None of them calls it a step to take.
    • Residual (inferred): it still sits in the "After install" list, with a command ready to paste. Someone who runs every command in that list without reading would run it. It can't run by itself: it refuses a coordinator's sudo, the engine's own repo, and a machine with no updates.json (measured: the script's header). It needs <instance> <owner/repo>, which the operator must choose.
  2. Could the test-user.sh sequence leave an install half-done, or with looser caps?

    • Half-done: found, and fixed in 657c79a. An install that exits 1 at the egress step (every one but the last, with several test users) stops before its last two steps. Those are the sudoers write (install_sudoers "worklane-test-$name") and its own final check_cmd. Measured: test-user.sh install, in the order slice and Docker unit, Docker restart, egress (exit 1 on rc 3), sudoers, check.
    • The sudoers rule it skips is unchanged since v0.5.2 and already installed (measured: git diff v0.5.2 origin/main -- scripts/setup/test-user.sh doesn't touch sudoers_rule). The skipped per-instance check is not replaced by the egress check.
    • A literal "carry on" could also carry on past a different error, such as Docker not starting.
    • The note now says to go past only that one message, since any other error stops the sequence. After the last install, run sudo bash /opt/worklane/current/scripts/setup/test-user.sh check <name> for each instance, then the egress check.
    • Looser caps: no (measured). The slice is shared, and each install rewrites it from its own arguments. The notes require all four values, read from /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-max and --cpus are validated on input (^[1-9][0-9]*[KMGT]$, a CPU list), and the slice holds what passed that check (inferred).
    • The test-run refusal is per instance (active_runs for $name) and changes nothing (exit 1 before the restart).
  3. Does the mcp-broker.sh restart warning cover every unit it restarts? Yes (measured). grep -n -E 'systemctl (restart|start|stop|enable|try-restart|reload)' scripts/setup/mcp-broker.sh finds three lines:

    • enable --now and restart of worklane-mcp@<name>.service (lines 85-86);
    • a restart of worklane-<name>.service and worklane-dashboard-<name>.service, each only when active (line 92).

    The note names all three.

  4. Could the generic egress quote hide a real failure? No (measured, plus inferred where noted).

    • The pass criterion in the notes is the last line: "check, which must end OK". check_cmd prints OK only when no check failed, and otherwise prints FAILED and returns non-zero (egress-firewall.sh:300).
    • Before the reload, the failures check can print on a machine whose rule loaded are the "not covered" ones (:277, :284) and the exception ones (:292, :295). Each ends run apply again. The quote describes exactly those.
    • A not-covered user continues before its reach probe, and docker_check runs only once the exception is loaded. So neither a "reached 1.1.1.1" line nor an NFTSet= line appears before the reload (inferred: reading check_cmd).
    • A real failure, such as table ... is not loaded (:270), reached 1.1.1.1:443 (:278, :285) or not loaded at boot (:299), either doesn't end that way or survives the reload. Either way the final check doesn't end OK, so it isn't hidden.

@jayden1711

Copy link
Copy Markdown
Owner Author

Evaluator verdict: CHANGES NEEDED (round 2)

Reviewed head 657c79ab (current origin/main 178c44e6) in a scratch worktree, with a temp HOME, temp state dirs and a fake claude. I didn't run release.mjs, use root, or touch real state. Labels: measured = from a command run here; inferred = from reading code; guess.

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:

  • The managed-apps step gives an incomplete list of the v0.5.2 locks it lifts. The missing ones include the sign-in, credentials and secrets lock.
  • One quoted check line can't appear on the install type it is given for.

I gave both lists in round 1 and both were incomplete, so these two misses are partly mine. Round 2 only changes CHANGELOG.md (measured: git diff --stat b1856ffa 657c79ab).

Required changes

  1. managed-apps: the list of v0.5.2 locks it lifts is incomplete (principles 5 and 6). I measured it with the head's own selfModificationPart(path, 'engine' | 'managed-app'), on a sample path for each of the 28 globs in git show v0.5.2:src/review.ts:
    • 24 globs become null under managed-app. All 24 return their part under engine. The other 4 still hold.

    • The notes name 17 of the 24, by part or by path. These 7 are missing:

      • sign-in, credentials or secrets: src/github-app*, src/github-scope*, src/**/*secret*, src/console/terminal*, src/setup/app-manifest*. Only src/dashboard-auth* is named.
      • who may approve: src/intake/decide.ts and src/intake/people.ts. Only src/decide.ts and src/machine-owners.ts are named.
    • The "still held" list is exact: the same probe holds these 8 and no others: .github/workflows/**, .github/release-signers, the config folder's review.yaml, guardrails.yaml and experiments.yaml, .githooks/**, .denylist* and .gitleaks*.

    • Fix: in both places, CHANGELOG.md:60-62 (After install) and :83 (the guardrail note), state the rule instead of a hand-picked list. The rule is every self-modification lock whose path is under src/, scripts/, test/ or docs/ (engineSourceGlob in src/review.ts). Then name the v0.5.2 parts it covers by their own names:

      • the merge policy
      • the main watch
      • the push guard
      • the updater
      • the review levels (src/review.ts)
      • the guardrails (src/guardrails/**)
      • the machine setup
      • who may sign a release (scripts/verify-release-tag*)
      • sign-in, credentials or secrets (all six globs)
      • who may approve (all four files)

      Written from the rule, the list is complete by construction.

  2. The egress check quote names a line that install type never prints (principle 5). CHANGELOG.md:50 says that "on systemd 255 or later without --with-test-users" you see the exception for the set egress_ok isn't loaded, ... and wl-test-<name> (<uid>) is not covered.
    • check reads its users from the record when there is one (egress-firewall.sh:274). The notes' step applies only where the record exists.
    • v0.5.2's record lists covered users. Without the flag, those are only wl-exp-* and wl-score-* (git show v0.5.2:scripts/setup/egress-firewall.sh, line 93, and its covered).
    • So before the reload, that install prints only the exception line (inferred).
    • Drop the wl-test-<name> ... is not covered line there. The builder's Adversarial pass item 4 makes the same claim (it cites :277 for this case), and it is wrong for the same reason.
    • In the same edit, CHANGELOG.md:52 needs a qualifier. "every install but the last exits 1" holds only where the rule gives Docker its exception (systemd 255 or later, not "dropped"). On a dropped install or on systemd older than 255, apply never runs docker_check, so no install exits that way (inferred: egress-firewall.sh decide and check_cmd:287-298). Something like "(where the new rule gives Docker its exception)" fixes it.

The round-1 required changes, re-checked

# Item Result
1 managed-apps: the default is to keep every lock Met (measured: CHANGELOG.md:34 "The default is to keep every lock and review those waits … it is not the fix for these waits", :60 "Not a required step. The default is to keep every lock", :83 "Loosening, root-only, off by default").
1 managed-apps: the named locks are complete Not met. 7 of the 24 lifted v0.5.2 globs are unnamed (required change 1). The "still held" list is exact (measured).
2 test-user.sh sequence Met. The code walk is below. Required change 2 only adds a qualifier to one sentence.
3 mcp-broker restart warning Met (measured: mcp-broker.sh:85-86 enables and restarts worklane-mcp@<name>, and :91-92 restarts worklane-<name>.service and worklane-dashboard-<name>.service when active; CHANGELOG.md:59 names all three). The unit is a template shared by every instance. Rewriting it for one instance gives the others ProtectControlGroups=yes on their next restart. That only tightens, so no change is needed (inferred).
4 Generic egress quote Met ("one or more FAIL lines ending run apply again", and the pass criterion "check, which must end OK"). The --with-test-users line and the "older than 255: may pass, the reload changes nothing" sentence are right. experiments.sh:204 makes wl-exp-*/wl-score-* with useradd --system, so they get no subordinate range, and there the reload adds nothing (inferred). One line is wrong: required change 2.

test-user.sh, walked through the code (inferred: test-user.sh install_cmd :386-428, egress-firewall.sh decide/apply_cmd/check_cmd/docker_check)

The install runs in this order: slice and Docker unit, then a Docker restart (refused under a test run, :393-400), then egress apply (rc 3 becomes exit 1 with the quoted message, :419), then sudoers, then check_cmd. These cases assume systemd 255 or later and a v0.5.2 record without the flag ("exempt"):

  • N=1: apply runs join_running, and docker_check sees the one unit with NFTSet= and its cgroup in the set. It passes (rc 0), and sudoers and the final check run. No exit 1.
  • N=2 (a, then b): a's apply covers both test users. docker_check FAILs on b's running unit ("has no NFTSet=…"), so a exits 1 with exactly the quoted message, skipping sudoers and its own check. b's install passes. The notes then ask for test-user.sh check a and check b, then the egress check. That covers a's skipped final check. a's skipped sudoers rule is unchanged since v0.5.2 (measured: git diff v0.5.2 657c79ab -- scripts/setup/test-user.sh is 34 added lines and none of them touches sudoers_rule or the slice).
  • N=3: the same, and only the last install passes. Any order works. Between installs, an instance's Docker that isn't reinstalled yet is covered with no exception, so it can't pull. That fails closed.
  • A run in progress on instance b: b's install exits 1 at the restart, before the egress step, with a different message (test runs of b are in progress). The notes cover that case separately ("Let them finish, then run it again"). b's unit file is already rewritten on disk, so a later install's join_running adds b's running cgroup to the set. That is the intended end state, not a loosening.
  • Dropped installs, or systemd older than 255: docker_check doesn't run, so no install exits 1 (required change 2's qualifier).
  • The skipped final check and a real egress failure can't hide. A real failure either prints the same rc-3 message on the last install, or survives into the egress check, which must end OK. "Only past that message" plus the final checks is sound.
  • 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 adds install_userns before the egress step and doesn't touch sudoers_rule (measured: git show 4a238413:scripts/setup/test-user.sh, :490 vs :533). So the skipped steps are still only sudoers and the final check after 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.

The other checks

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)

Does #809's layout make the others' merges mechanical?

Not blocking

…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>
@jayden1711

Copy link
Copy Markdown
Owner Author

Round 3: head 456d468

Changes since 657c79a, release-note text in CHANGELOG.md only, one commit. Labels: measured = from a command run here (read-only, in memory, nothing written); inferred = from reading code.

  1. managed-apps, from the rule. The After-install step and the guardrail note now say it lifts every self-modification lock whose path is under src/, scripts/, test/ or docs/ (engineSourceGlob).
    • The After-install step lists the 24 v0.5.2 globs this covers, grouped by part: the merge policy, the main watch, the push guard, the updater, the machine setup, the review levels, the guardrails, who may sign a release, sign-in, credentials or secrets (all six globs), and who may approve (all four files).
    • Measured lock count: my own script took each glob of v0.5.2's SELF_MODIFICATION_PARTS (from git show), built a sample path for it, and ran the head's selfModificationPart in both scopes: globs: 28, lifted for a managed app: 24, held: 4. The 4 held are review.yaml, guardrails.yaml, .github/workflows/** and .github/release-signers.
    • The "still held" list and the default-keep advice are unchanged. The list matches lockedCategories('managed-app') (measured).
  2. The egress quote, from the code.
    • On systemd 255 or later without --with-test-users, check takes its users from the record (egress-firewall.sh:274), and that install's v0.5.2 record has no test user. So the notes now quote only the exception for the set egress_ok isn't loaded, so test users' Docker can't pull images: run apply again there.
    • With the flag, the quote is the subordinate-uid line, in full.
    • "Every install but the last exits 1" is now qualified: "where the new rule gives their Docker its exception (systemd 255 or later, record not "dropped")". The bullet also says that elsewhere apply doesn't check NFTSet=, so no install exits that way.
    • The body's Adversarial pass (round 2) item 4 now carries a "false through 657c79a" correction.
  3. Adversarial pass (round 3), on the same class (full list in the body): every quoted output line and every list of paths or names in the v0.5.3 section, checked against the code.
    • One more fix: the guardrail note's list of new locks came from PR titles. It now uses the head's part names, from a diff of the head's SELF_MODIFICATION_PARTS against v0.5.2's (measured). That adds three parts it had missed: the tool guard's hook commands, where the self-modification locks apply, and the sandbox's src/testrun/**. It also adds src/path-text.ts under the review levels.
    • Everything else quoted matches its source line, each cited in the body.

Checks (measured):

  • Survival, in memory through scripts/lib/changelog.mjs:
    • head: v0.5.3: 92 lines before, 98 after; dropped 0;
    • head merged with origin/main 178c44e (git merge-tree --write-tree): dropped 0.
  • Tests: changelog, release, package-files, owner-terminal-removed, owner-terminal-rerun, hotspots and comment-policy-cli: # tests 59 / # pass 59 / # fail 0.
  • typecheck exit 0; denylist 1292 files clean; DCO all commits signed off; push gate clean.

@jayden1711

Copy link
Copy Markdown
Owner Author

Evaluator verdict: PASS

Final check, round 3. Reviewed head 456d4682 against origin/main 178c44e6, in a scratch worktree with a temp HOME, temp state dirs and a fake claude. I didn't run release.mjs or any scripts/machine/ script, didn't use root, and wrote nothing outside the scratchpad. Round 3 changes only CHANGELOG.md (measured: git diff --stat 657c79ab 456d4682 shows 1 file, +9 -6).

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 selfModificationPart (measured, in memory)

My own probe parsed git show v0.5.2:src/review.ts SELF_MODIFICATION_PARTS and built a sample path for each glob. It then ran the built head's selfModificationPart with 'engine' and with 'managed-app'.

  • Result: v0.5.2 parts: 11, globs: 28, lifted: 24, held: 4. All 28 return their part under engine.
  • Lifted (24). The notes list exactly these, by part, at CHANGELOG.md:61-65:
    • merge policy 3, main watch 2, push guard 2, updater 2, machine setup 2;
    • src/review.ts, src/guardrails/**, scripts/verify-release-tag*;
    • sign-in, credentials or secrets: all 6 globs;
    • who may approve: all 4 files.
  • Held from v0.5.2 (4): .worklane/review.yaml, .worklane/guardrails.yaml, .github/workflows/** and .github/release-signers.
  • The "still hold" list (:67) is exact. It equals the head's lockedCategories('managed-app')['self-modification'], which is those 4 plus .denylist*, .gitleaks*, .githooks/** and .worklane/experiments.yaml. The guardrail note at :86 names the same 8, plus the agents' instructions, which are a separate locked category.
  • The rule is exact, with no exceptions. The notes say "every self-modification lock whose path is under src/, scripts/, test/ or docs/". I checked it against all 125 of the head's self-modification globs:
    • Under managed-app the rule disagrees with the held list for none of them: rule mismatches (prefix vs held): [].
    • No glob starts with a wildcard ([]), so none can reach into those folders without starting with them.
    • engineSourceGlob is a plain startsWith on ENGINE_TREE = ['src/', 'scripts/', 'test/', 'docs/'] (src/review.ts:138-140).

2. Egress: the quoted check lines, case by case (inferred, from reading scripts/setup/egress-firewall.sh; there's no scripts/machine/egress-firewall.sh)

Before the reload, the new script's check_cmd runs against v0.5.2's loaded rule and v0.5.2's record. That record has no # docker: line, and egress_docker_mode (lib.sh:31-39) reads it as dropped only if it says # --with-test-users.

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, apply doesn't check Docker's NFTSet=" both match decide() and check_cmd.
  • :396's test runs of <name> are in progress matches 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.ts under the review levels, which the note names.
  • No v0.5.2 part lost or changed a glob, and AGENT_INSTRUCTIONS is unchanged.
  • Since the audit, main added test/merge-idempotent.test.ts to "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)

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.

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).

Not mechanical:

Is this the last notes blocker for v0.5.3?

For what is on main: yes (inferred).

Open PRs whose notes v0.5.3 would need, if the owner wants them in:

Not blocking (optional one-line edits; the owner can take or leave them)

  1. :60: "That covers this release's new locks" reads wider than the code. 4 of this release's new globs (.githooks/**, .denylist*, .gitleaks* and experiments.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).
  2. :51: "(none where the record says # docker: dropped)" is true but not the only case. A v0.5.2 --with-test-users record says # --with-test-users at that moment, and egress_docker_mode reads it as dropped. Systemd older than 255 also gets no NFTSet= (test-user.sh:118-123). :57 already gives the outcome (inferred).

jayden1711 added a commit that referenced this pull request Oct 19, 2026
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>
@jayden1711
jayden1711 merged commit 128453a into main Oct 19, 2026
27 checks passed
@jayden1711
jayden1711 deleted the release-notes-v0.5.3 branch October 19, 2026 15:30
jayden1711 added a commit that referenced this pull request Oct 19, 2026
Brings main 128453a (#809's v0.5.3 release-notes layout among others). CHANGELOG.md takes main's text; this
PR's #726 notes go back under "Guardrail changes: what each one does" and "Upgrading" in the next commit.

Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
jayden1711 added a commit that referenced this pull request Oct 19, 2026
…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>
jayden1711 added a commit that referenced this pull request Oct 19, 2026
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>
jayden1711 added a commit that referenced this pull request Oct 19, 2026
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>
jayden1711 added a commit that referenced this pull request Oct 19, 2026
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>
jayden1711 added a commit that referenced this pull request Oct 19, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant