Repository navigation
feat(updater): the updater, its npm port and launcher port, and the install check - #878
Conversation
Decision takenThe build left two questions open. Both are now in #825's Decisions and #835's body, marked pending the maintainer. 1. When a rollback puts the database copy back (#827's F14).
Why (a): it is one step moved, with no new machinery. Undoing it means moving that step in the updater. 2. A new daemon that ignores
Why (a): this case almost never happens, and (b) needs a change to what the daemon reports. ADR 0042 §8 still says SIGKILL. That text is part of #827, which is parked. Written by an agent. |
Review ledgerRound 1: e6c2555 {
"findings": [
{
"id": "R1",
"role": "code",
"class": "architecture 11",
"outcome": "fixed",
"path": "src/updater/index.ts:156",
"round": "1",
"fix": "41f3b2f0ce1d73edba859133e67b80a4cbaee6b1"
},
{
"id": "R2",
"role": "code",
"class": "testing 10",
"outcome": "fixed",
"path": "src/updater/index.test.ts:240",
"round": "1",
"fix": "41f3b2f0ce1d73edba859133e67b80a4cbaee6b1"
},
{
"id": "R3",
"role": "code",
"class": "testing 10",
"outcome": "fixed",
"path": "src/updater/index.test.ts:290",
"round": "1",
"fix": "41f3b2f0ce1d73edba859133e67b80a4cbaee6b1"
},
{
"id": "R4",
"role": "code",
"class": "user data overwritten, liveness assumed",
"outcome": "fixed",
"path": "src/updater/index.ts:104",
"round": "1",
"fix": "41f3b2f0ce1d73edba859133e67b80a4cbaee6b1"
},
{
"id": "R5",
"role": "spec",
"class": "testing 7",
"outcome": "incorrect",
"path": "src/updater/index.test.ts:1116",
"round": "1"
},
{
"id": "R6",
"role": "code",
"class": "testing 2",
"outcome": "fixed",
"path": "src/updater/index.test.ts:283",
"round": "2",
"fix": "26ce7610"
},
{
"id": "R7",
"role": "code",
"class": "testing 1",
"outcome": "fixed",
"path": "src/updater/index.test.ts:324",
"round": "2",
"fix": "26ce7610"
},
{
"id": "R8",
"role": "code",
"class": "liveness assumed from socket file",
"outcome": "fixed",
"path": "src/updater/index.ts:148",
"round": "2",
"fix": "26ce7610"
},
{
"id": "R9",
"role": "claims",
"class": "accepted ADR contradicted by decided behavior",
"outcome": "open",
"path": "docs/internal/adr/0042-simlock-updates-itself-through-a-frozen-call-and-a-detached-updater.md:128",
"round": "2"
},
{
"id": "R10",
"role": "spec",
"class": "testing 7",
"outcome": "duplicate",
"path": "src/updater/index.test.ts:283",
"round": "2",
"canonicalId": "R9"
},
{
"id": "R11",
"role": "spec",
"class": "accepted ADR contradicted by decided behavior",
"outcome": "duplicate",
"path": "src/updater/index.ts:126",
"round": "2",
"canonicalId": "R9"
},
{
"id": "R12",
"role": "spec",
"class": "undocumented decision",
"outcome": "nonblocking",
"path": "src/updater/index.ts:126",
"round": "2"
},
{
"id": "R13",
"role": "spec",
"class": "testing 7",
"outcome": "incorrect",
"path": "src/updater/index.test.ts:287",
"round": "2"
},
{
"id": "R14",
"role": "spec",
"class": "testing 7",
"outcome": "incorrect",
"path": "src/updater/index.test.ts:121",
"round": "2"
},
{
"id": "R15",
"role": "code",
"class": "liveness assumed",
"outcome": "open",
"path": "src/updater/probe.ts:41",
"round": "fix-check"
}
]
}Assessment: incomplete (R15 open at the round cap) Written by an agent. |
…d pid exited, not a failed handshake (#835)
Review ledgerRound 1: e6c2555 The second fix-check was the extra round the maintainer allowed. R16 was accepted as is by the maintainer (nonblocking). {
"findings": [
{
"id": "R1",
"role": "code",
"class": "architecture 11",
"outcome": "fixed",
"path": "src/updater/index.ts:156",
"round": "1",
"fix": "41f3b2f0ce1d73edba859133e67b80a4cbaee6b1"
},
{
"id": "R2",
"role": "code",
"class": "testing 10",
"outcome": "fixed",
"path": "src/updater/index.test.ts:240",
"round": "1",
"fix": "41f3b2f0ce1d73edba859133e67b80a4cbaee6b1"
},
{
"id": "R3",
"role": "code",
"class": "testing 10",
"outcome": "fixed",
"path": "src/updater/index.test.ts:290",
"round": "1",
"fix": "41f3b2f0ce1d73edba859133e67b80a4cbaee6b1"
},
{
"id": "R4",
"role": "code",
"class": "user data overwritten, liveness assumed",
"outcome": "fixed",
"path": "src/updater/index.ts:104",
"round": "1",
"fix": "41f3b2f0ce1d73edba859133e67b80a4cbaee6b1"
},
{
"id": "R5",
"role": "spec",
"class": "testing 7",
"outcome": "incorrect",
"path": "src/updater/index.test.ts:1116",
"round": "1"
},
{
"id": "R6",
"role": "code",
"class": "testing 2",
"outcome": "fixed",
"path": "src/updater/index.test.ts:283",
"round": "2",
"fix": "26ce7610"
},
{
"id": "R7",
"role": "code",
"class": "testing 1",
"outcome": "fixed",
"path": "src/updater/index.test.ts:324",
"round": "2",
"fix": "26ce7610"
},
{
"id": "R8",
"role": "code",
"class": "liveness assumed from socket file",
"outcome": "fixed",
"path": "src/updater/index.ts:148",
"round": "2",
"fix": "26ce7610"
},
{
"id": "R9",
"role": "claims",
"class": "accepted ADR contradicted by decided behavior",
"outcome": "fixed",
"path": "docs/internal/adr/0042-simlock-updates-itself-through-a-frozen-call-and-a-detached-updater.md:128",
"round": "2",
"fix": "#835 body amended 2026-10-22 11:15; ADR 0042 b90f0337"
},
{
"id": "R10",
"role": "spec",
"class": "testing 7",
"outcome": "duplicate",
"path": "src/updater/index.test.ts:283",
"round": "2",
"canonicalId": "R9"
},
{
"id": "R11",
"role": "spec",
"class": "accepted ADR contradicted by decided behavior",
"outcome": "duplicate",
"path": "src/updater/index.ts:126",
"round": "2",
"canonicalId": "R9"
},
{
"id": "R12",
"role": "spec",
"class": "undocumented decision",
"outcome": "nonblocking",
"path": "src/updater/index.ts:126",
"round": "2"
},
{
"id": "R13",
"role": "spec",
"class": "testing 7",
"outcome": "incorrect",
"path": "src/updater/index.test.ts:287",
"round": "2"
},
{
"id": "R14",
"role": "spec",
"class": "testing 7",
"outcome": "incorrect",
"path": "src/updater/index.test.ts:121",
"round": "2"
},
{
"id": "R15",
"role": "code",
"class": "liveness assumed",
"outcome": "fixed",
"path": "src/updater/probe.ts:41",
"round": "fix-check",
"fix": "81ad9468"
},
{
"id": "R16",
"role": "code",
"class": "testing 3 liveness guards unproven",
"outcome": "nonblocking",
"path": "src/updater/index.ts",
"round": "fix-check",
"reason": "maintainer 2026-10-22: accepted as is; code verified on real sockets"
},
{
"id": "R17",
"role": "code",
"class": "testing 1 fake diverges from probe",
"outcome": "nonblocking",
"path": "src/updater/index.test.ts",
"round": "fix-check"
}
]
}Written by an agent. |
Review notesThese do not block, and nobody has verified them. Each is one reviewer's claim.
Written by an agent. |
Closes #839 ### Status Implement: done Review: passed, round 2 Hardware: n/a Gate: the stack gate (`merge-pr.sh 896`) Next: final stack sync, then the gate ### Done when - [x] `pnpm exec vitest run --project unit` on the changed `ui/src` test files passes with the lines above. - [x] `pnpm test:console --project=chromium e2e/console/update.spec.ts` passes. - [ ] CI's `pnpm check` is green, the dependency rules included. - [x] `console.mdx` documents the notice, the Update and Update now items, the `updating` state, the failure banner, the Inbox item and the footer notice. ### Completion conditions (#825) - [x] `simlock update` with no leases installs the latest and `simlock status` prints it: `e2e/update/update.e2e.test.ts` (real npm via verdaccio, #885). - [x] With a lease held the update waits, a new lease gets `DAEMON_UPDATING`, releasing it lets the update go on: `src/daemon/server.test.ts` `daemon.update` table (#885). - [x] A failed install leaves the old daemon serving and `simlock status` prints `Last update failed (installing)`: server.test.ts failed-line case and `src/cli/index.test.ts` (#885). - [x] A new version that does not start is rolled back and the failure shows with stage `restarting`: `src/updater/index.test.ts` (#878) and the server.test.ts result-file case (#885). - [x] `simlock worker update` updates a connected and an incompatible worker, routes around it, and keeps a drain: `src/gateway/service.test.ts`, `src/daemon/gateway-fleet.e2e.test.ts` (#888). - [x] `simlock status` and `simlock worker list` show the latest after the check, and nothing with `update.check: false`: `src/daemon/main.test.ts` version-check cases, CLI tables (#887, #888). - [x] The console shows `<latest> available`, Update…, `updating` with its stage, the failure banner and Inbox item, and the sidebar notice: `ui/src` model and screen tests and `e2e/console/update.spec.ts` (this PR). ## Assumptions - Assumption: Update now answers `upToDate` with `<name> already runs <version>` (the spec lists the toast for the answer; the force action had a fixed success title). - Assumption: the footer notice is a `UpdateNotice` in `shell.tsx` and `watchMode` takes a `clock`, so its 10-minute timer is testable with fake time. - Assumption: the row's stage line shows only while State reads `updating` — a red finding (disconnected, failed) hides it, as it hides the amber word. - Assumption: Update… and Update now sit after Drain/Undrain in the menu (the picked prototype's order), so the page's primary button stays Drain. - Assumption: the Inbox fact When reads `<duration> ago`, as the disconnected item's Last seen does. - Assumption: `isNewerVersion` is imported by `version-order.ts`'s own path (the spec's value import); the `past-index` rule allows that one file, because the index carries zod's `eval`, which breaks the console's CSP (maintainer-authorized 2026-10-22, #825 Decisions). ## Review Spec review: 1 blocking, 1 fixed, 0 notes. Code review: 1 blocking, 1 fixed, 1 notes. Claims review: 1 blocking, 1 fixed, 0 notes. Rounds: 2. Tokens: 45.0M. Runs: 11. Cost USD: 6.9344. Agent seconds: 3898. Assessment: complete *Written by an agent.*
Closes #835
Status
Implement: done (31/31 green) Review: passed (R16 accepted by the maintainer) Hardware: n/a Gate: waiting for the stack
Next: joins the #825 stack
Built: all Tests lines (updater table incl. database copy, starting-after-2-min and crash-loop cases; install check;
NodeNpmCli), launcher port, entry, manuals. Audited: restore order asserted (live database still the new version's during the rollback install, restored before its start); added cases for a foreign running version, a new daemon ignoringdaemon.stop, a stale-wal, a second copy under npm's root, and the launcher's detach and log append. Not tested:main.ts(the probe on the admin client; #836 tests it) andunref()in the launcher.Done when
pnpm test:changedpasses (fix run at 81ad946: 31/31 updater tests, src/ports green) with the lines above (src/updater/index.test.ts,src/ports/npm-cli.test.ts).src/daemon/relay-promises.timing.test.ts, outside this diff, rerun running). [x] manuals:src/updater/AGENTS.mdexists (src/updater/AGENTS.md), and the ports table has the two new rows (src/ports/AGENTS.md).Assumptions
runUpdateralso takesstderr— the failed-rollback message goes todaemon.logthrough it; the spec's option list had onlystdout.rolledBackin the result file is alwaystrue— it is written before the rollback starts, so it records that one is under way.FilesystemgetscopyFile— the database copy needs a byte copy and the port had none.daemon.stopfor 10 minutes is not killed — the probe knows no pid for it; the wait ends and the rollback goes on.checkInstallanswersok: falsewhennpm root -gfails.health: failedis stopped like one stillstarting.stop,listening,state) is bounded at 30 s; past itlisteningcounts as still listening — the spec gave no number.Review
Spec review: 0 blocking, 0 fixed, 1 notes. Code review: 9 blocking, 8 fixed, 1 notes. Claims review: 1 blocking, 1 fixed, 0 notes.
Rounds: 2. Tokens: 8.3M.
Runs: 19. Cost USD: 8.5517. Agent seconds: 5067.
Assessment: complete
Rejected:
parseUpdaterArgstests are unasked — [incorrect] always-in-scope items 4 and 8 ask for themWritten by an agent.