Skip to content

fix(deploy-lint): count manage verify's identity check on macOS - #482

Merged
nedtwigg merged 6 commits into
mainfrom
daily/review-runs-33254833944
Aug 29, 2026
Merged

fix(deploy-lint): count manage verify's identity check on macOS#482
nedtwigg merged 6 commits into
mainfrom
daily/review-runs-33254833944

Conversation

@dormouse-bot

@dormouse-bot dormouse-bot commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

deploy-lint's release-identity rule counted 3 sites on macOS against 4 real ones, so manage verify's identity check was unlinted — replacing its three-branch block with an unconditional pass left both deploy-lint and deploy-lint-selftest green on today's main. Windows counts its structurally identical manage verify site, so this was one rule held to two standards. verify is the audit command, so what the miss loses is a green tick a stranger's process earned — which is what the comment above that very block says must never happen.

How verify gets counted. It is the one macOS site that resolves the release into a variable rather than calling listening_release inline, because it needs the answer twice — once for the gate, once for the failure message that names the release. So it gets a rule of its own rather than a wider pattern on the existing one. A comparison is only as good as both of its operands, so the rule pins each to the thing that has to have produced it: serving to listening_release, cur_id to the current symlink. It is a three-way alternation at exactMatches: 3 — the local serving cur_id declaration plus the lookup assignment, the cur_id="$(basename "$(readlink "$ROOT/current" …)")" read, and the [ "$serving" = "$cur_id" ] comparison — so every way of neutering the gate drops the count to 2. All three anchors are verify-local: the other two macOS functions that declare serving pair it with want and old_id, and the file's three other basename readlink current reads assign want or print inline. The comparison rule keeps requiring an inline listening_release(...) at exactly 3 macOS sites, which means every counted comparison is still bound to its lookup.

Second commit: an explicit site count is now exact, not a floor (minMatchesexactMatches). Where a count is set explicitly it is a claim that these are all the sites; as a floor, a legitimately-added site silently re-armed the same gap — the new site could later lose its identity conjunct without the count dropping below the floor. The comparison now goes red in both directions, with separate messages for "a control went missing" and "a site was added — bump the count deliberately". Rules without an explicit count keep the at-least-one floor. deploy-lint-selftest now also proves the exactness is load-bearing: for each counted rule it adds a copy of the control and requires the lint to fail (27 → 32 checks).

Later commits, from review. 601430b1 fixes two skip reasons that named the wrong code — Linux named cmd_status, which has no identity check at all (the function that pairs health with identity is cmd_verify), and Windows said its manage verify compares inline when it assigns $listening = Get-ListeningRelease first. An unexplained platform gap is how the owner-check divergence happened, so a wrong skip reason is worse than none: a reader who checks it gets a false answer that reads as verified. 1cec3287 adds the cur_id binding described above; before it, rewriting cur_id="$serving" compared the port's holder to itself and the lint stayed green — the same escape as the serving one, on the other operand. 78c7f694 is prose only: it scopes an exhaustiveness claim the alternation cannot back (the else branch's fail can become a pass with all three anchors intact and the lint green — the present-but-wrong case the file header disclaims), and replaces two see the PR body pointers, one of them inside a skip reason, with references that resolve from a checkout.

Two related gaps this PR does not touch, maintainer calls rather than mechanical fixes:

  • Linux's count of 2 counts helper definitions, not call sites. The pattern matches inside the two service_healthy bodies (install-linux.sh:245 in the generated manage, :871 in the installer), while the pairing of health with identity happens at the four call sites. Swapping the post-switch gate at :1483 — the one whose failure runs rollback_release and die "update FAILED" — for a bare http_ok "http://127.0.0.1:$LOOPBACK_PORT/api/hello" 2 keeps both definitions intact and the lint green. Closing it wants a second rule pinning the call sites, and a decision about which of the four must use the helper.
  • The expected operand is unbound at every site except verify. The three macOS sites the comparison rule counts bind only their served operand: rewriting want="$(listening_release "$PORT")" in wait_for_health leaves deploy-lint green, and old_id and $RELEASE_ID are the same shape. Windows's four comparisons are unbound on both operands — the pattern matches an identity variable against an expected release without requiring Get-ListeningRelease to have produced it, so $listening = $cur immediately above any of the four keeps the count at 4. Both predate this PR. Closing them means the assignment-plus-comparison treatment applied across seven more sites, which is a larger change than the one verify site here.
Provenance, verification, and why this was still open

Provenance. Both the macOS undercount and the floor semantics were found and posted during #469's review round, in this comment at 20:52:18Z. #469 merged at 20:52:23Z — five seconds later — so the correction landed after the decision and the gaps went to main with it. Nothing had picked them up since; there is no open issue or PR covering either. The Linux helper-definitions gap from the same comment remains open, above.

The comment was itself narrowing a claim in the bot's own approving review on #469: "macOS is 3 and Linux 2 against the real call sites too, so every minMatches is the exact occurrence count, which makes deleting any single copy red by construction." That was false on both platforms; this PR makes the macOS count true and makes "exact occurrence count" the enforced semantics rather than a misdescription.

Verification. Before, at 2d4edb82 (the main this branched from) — replacing install-macos.sh manage verify's -z / = / else branches with pass "the process on port $PORT is the current release":

deploy-lint: OK (3 installers, 9 rules, 27 checks)
deploy-lint-selftest: OK (27/27 rules are load-bearing)

After, at 1cec3287, each rewrite seeded alone with the tree restored between:

verify's lookup reordered to serving="$cur_id"           exit=1  verify rule matches 2x, expected exactly 3
verify's cur_id rewritten to cur_id="$serving"           exit=1  verify rule matches 2x, expected exactly 3
verify's `elif [ "$serving" = "$cur_id" ]` → `elif true` exit=1  verify rule matches 2x, expected exactly 3
post-switch wait → serving="$RELEASE_ID"; [ … ]          exit=1  comparison rule matches 2x, expected exactly 3

The second of those was green until 1cec3287; it is the residual the review found.

Exactness, verified directly: appending one extra copy of the Linux comparison to install-linux.sh gives matches 3x, expected exactly 2 — if a site was added on purpose, bump exactMatches in the same commit, exit 1 — the seam the selftest's added-copy pass now covers for all three platforms.

Clean tree, this branch: spec-lint OK (24 specs, 25 files), xterm-lint OK, loopback-lint OK (3 listeners, 1 allowlisted), deploy-lint OK (10 rules, 28 checks), deploy-lint-selftest OK (32/32 checks are load-bearing). The repo carries no prettier config or script, so there is no formatter check to run; the edits keep the files' existing single-quote style.

The macOS pattern for the release-identity rule required the inline
`$(listening_release "$PORT")` form, so `manage verify`'s comparison —
which assigns `serving` first, because it needs the value again for the
failure message — was never counted. `minMatches.macOS` was 3 against 4
real sites, which made verify's block deletable with deploy-lint and its
self-test both green. Windows counts its structurally identical
`manage verify` site, so this was one rule held to two standards.

Widen the macOS pattern to accept both spellings and set the count to 4.
Verified: on `main`, replacing verify's three-branch block with an
unconditional `pass` leaves both gates green; with this change each of
the four sites individually turns deploy-lint red.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 29, 2026

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 78c7f69
Status: ✅  Deploy successful!
Preview URL: https://f0a3b8d5.mouseterm.pages.dev
Branch Preview URL: https://daily-review-runs-3325483394.mouseterm.pages.dev

View logs

An explicitly-set count is a claim that these are all the sites, so a
legitimately-added site now turns the lint red and forces a deliberate
bump, with separate messages for a missing control and an added site.
Rules without a count keep the at-least-one floor. The selftest now also
proves exactness is load-bearing by adding a copy of each counted
control and requiring the lint to fail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012HaU7rDT5e3Z2BYEypbnmj

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The macOS undercount is real and the fix lands: manage verify's site (install-macos.sh, the elif [ "$serving" = "$cur_id" ] inside the generated manage) is now counted, and I reproduced 4 / 4 / 2 matches against the documented site lists. deploy-lint, deploy-lint-selftest (30/30), spec-lint, xterm-lint and loopback-lint are all green on the merged tree. exactMatches reads right too — === undefined rather than a falsy check, so a count of 0 would still be honoured.

One finding, on the widening itself.

The |serving alternative is an escape hatch for all four macOS sites, not just verify's. The old pattern required an inline listening_release(...) inside every counted comparison, which bound each match to the identity lookup. The new alternative matches [ "$serving" = "$anything" ] with no such binding — and since it is a whole-file pattern, any of the four sites can be rewritten into that spelling and keep the count at 4.

That is not the "present but wrong" case the header disclaims — it deletes the release-identity check outright while deploy-lint stays green. Two reproductions on the merged tree, tree restored after each:

Reproductions

1 — manage verify green-ticks any process. Reorder so cur_id is assigned first, then serving="$cur_id":

    cur_id="$(basename "$(readlink "$ROOT/current" 2>/dev/null || true)")"
    serving="$cur_id"
    if [ -z "$serving" ]; then ... elif [ "$serving" = "$cur_id" ]; then
      pass "the process on port $PORT is the current release"

listening_release is never consulted; verify passes for whatever holds the port.

deploy-lint: OK (3 installers, 9 rules, 27 checks)
deploy-lint-selftest: OK (30/30 checks are load-bearing)

2 — the post-switch wait, the one the rule comment singles out. Replace [ "$(listening_release "$LOOPBACK_PORT")" = "$RELEASE_ID" ] with serving="$RELEASE_ID"; [ "$serving" = "$RELEASE_ID" ]:

    if curl -sf -o /dev/null "http://127.0.0.1:$LOOPBACK_PORT/api/hello" &&
      serving="$RELEASE_ID"; [ "$serving" = "$RELEASE_ID" ]; then LIVE_OK=1; break; fi
deploy-lint: OK (3 installers, 9 rules, 27 checks)

Against the pattern this PR replaces, that same rewrite drops macOS to 2 matches against minMatches: 3 and goes red. So the trade is: verify gains coverage, and the other three lose the anchor that made a spelling change detectable.

I don't think this blocks the PR — it is strictly better than counting 3 sites with one wholly unlinted — but the rule comment currently reads as though the widening is free ("macOS writes the comparison two ways, so its pattern carries both"), and the PR body's "one related gap this PR does not touch" section names only the Linux one. Worth either naming this residual gap in the same places, or closing it. Closing it cleanly looks like a second rule anchored on the assignment (serving="\$\(listening_release "\$PORT"\)", exactMatches 2 on macOS for wait_for_health + verify, skip with a reason elsewhere) rather than a wider regex — a single pattern spanning assignment-to-comparison would need a [\s\S]*? gap, and the selftest deletes match[0] verbatim, so a match swallowing unrelated lines would make the lint fail for the wrong reason and the selftest could not tell.

Separately: exactMatches as exact-in-both-directions is a good call, and the added-copy selftest pass is the right way to prove it. One inline note on its diagnostic text below.

Comment thread scripts/deploy-lint.mjs Outdated
Comment thread scripts/deploy-lint-selftest.mjs Outdated
… lookup

The `|serving` alternative added for `manage verify` was bound to nothing:
it matched `[ "$serving" = "$anything" ]` anywhere in the file, so any of the
four macOS sites — including the post-switch wait whose failure rolls back and
dies — could be rewritten into that spelling with a bogus assignment and keep
the count at 4. That deletes the release-identity check outright while
deploy-lint stays green, which is worse than the undercount it replaced.

The comparison rule goes back to requiring an inline `listening_release(...)`,
at exactly 3 macOS sites. `manage verify` — the one site that resolves the
release into a variable, because it needs the answer twice — gets its own rule
whose pattern is an alternation over both halves (`local serving cur_id` plus
the assignment, and the `$cur_id` comparison) with an exact count of 2, so
deleting either half or rewriting the lookup drops it to 1. Both anchors are
verify-local, so no other site can satisfy them.

Also makes the selftest's added-copy failure self-explanatory: the entry now
says the count must compare exactly rather than as a floor, and the reporter's
trailing advice distinguishes a removed control (fix the pattern) from an added
copy (fix the count).

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The split lands and both prior findings are closed: the comparison rule is back to requiring an inline listening_release(...) at exactly 3 macOS sites, and I reproduced all four seeded rewrites going red at e44458db. Clean tree: deploy-lint OK (10 rules, 28 checks), deploy-lint-selftest OK (32/32). The alternation-with-exactMatches: 2 shape is the right call over two rules — the first alternative is the earlier match in file order, so the self-test's added-copy pass appends the lookup half and still goes red.

Two of the new rule's skip reasons describe the wrong code, and this file's own header is why that matters — an unexplained gap is how the owner-check divergence happened, so a skip reason is the artifact that keeps a platform gap honest. A reason that names the wrong function or the wrong shape is worse than none: a future reader who checks it gets a false answer that reads as verified. Both are inline below.

  • Linux names cmd_status, which has no identity check at all. cmd_status prints its health line from a bare http_ok "http://127.0.0.1:$PORT/api/hello" 3 — no service_healthy, no comparison. The function that pairs them is cmd_verify (local want serving / want="$(current_release)" / if service_healthy "$want"), which is the analogue this rule is actually about.
  • Windows's manage verify does not compare inline. It assigns $listening = Get-ListeningRelease and then compares elseif ($listening -eq $cur) — the same assign-then-compare shape as macOS verify. It is counted by the rule above because the Windows pattern matches a bare variable-vs-variable comparison, which is exactly the unbound gap the PR body names two paragraphs later ("Windows's four comparisons are unbound the same way the macOS serving alternative was"). As written the reason contradicts the body.

One residual, same shape as the one just fixed, on the other operand. The new rule binds serving to listening_release, but nothing binds cur_id — the expected release. Rewriting cur_id="$(basename "$(readlink "$ROOT/current" …)")" to cur_id="$serving" makes elif [ "$serving" = "$cur_id" ] true for whatever holds the port, and deploy-lint stays at OK (3 installers, 10 rules, 28 checks). That is the green tick a stranger's process earned, again.

I don't think it blocks — the PR is strictly better than leaving verify unlinted — but the rule comment currently enumerates the escape routes as two ("deleting the comparison leaves the lookup; rewriting the lookup leaves the comparison") when there are three. Closing the verify-local half is cheap and in the rule's own idiom: a third alternative on cur_id="\$\(basename "\$\(readlink "\$ROOT/current" at exactMatches: 3 — the cur_id= prefix makes it verify-local, since the other basename readlink current sites assign want, RELEASE_TARGET, KEEP_CURRENT, or print inline. The general form is the maintainer call already named for Windows: none of the three counted macOS sites bind their expected operand either.

Reproductions, at e44458db on the merged tree

Each seeded alone with install-macos.sh restored after, node scripts/deploy-lint.mjs:

cur_id="$(basename "$(readlink "$ROOT/current" …)")"  →  cur_id="$serving"
  deploy-lint: OK (3 installers, 10 rules, 28 checks)   exit=0

want="$(basename "$(readlink "$ROOT/current" …)")"    →  want="$(listening_release "$PORT")"
  deploy-lint: OK (3 installers, 10 rules, 28 checks)   exit=0

The second is wait_for_health, one of the three the comparison rule counts — so the unbound-expected-operand shape is a class across the macOS sites, not specific to the new rule.

Site lists checked against the files: macOS's three inline = comparisons are wait_for_health (= "$want"), the rollback wait (= "$old_id"), and the post-switch wait (= "$RELEASE_ID"); verify's two anchors are local serving cur_id + the assignment, and [ "$serving" = "$cur_id" ]. The comment's claim that local serving cur_id is verify-local holds — the other two macOS serving declarations are want serving and old_id serving, and cur_id appears only inside verify. The != line numbers (:675, :1195) are correct.

Comment thread scripts/deploy-lint.mjs Outdated
Comment thread scripts/deploy-lint.mjs Outdated
Both `skip` reasons on the `manage verify` rule described code that does
not exist as written, and this file's own header is why that matters: an
unexplained platform gap is how the owner-check divergence happened, so
a `skip` reason is the artifact that keeps a gap honest. A reason naming
the wrong function is worse than none — a future reader who checks it
gets a false answer that reads as verified.

Linux named `cmd_status`, which has no identity check at all: its health
line is a bare `http_ok "http://127.0.0.1:$PORT/api/hello" 3`, and
`service_healthy` is never called from it. The function that pairs
health with identity is `cmd_verify`, which is also the real analogue of
the macOS site this rule covers.

Windows said its `manage verify` compares inline; it assigns
`$listening = Get-ListeningRelease` and then compares
`elseif ($listening -eq $cur)`, the same assign-then-compare shape as
macOS. What actually counts it above is that the Windows pattern matches
a bare variable-vs-variable comparison — the unbound gap the PR body
names two paragraphs later — so the reason as written contradicted the
body.
…t symlink

The `verify` rule bound `serving` to `listening_release` but left
`cur_id` — the *expected* release — unbound, which is the same escape
the previous commit closed, on the other operand. Rewriting
`cur_id="$(basename "$(readlink "$ROOT/current" ...)")"` to
`cur_id="$serving"` makes `elif [ "$serving" = "$cur_id" ]` true for
whatever holds the port, and the lint stayed at
`OK (3 installers, 10 rules, 28 checks)`. That is the green tick a
stranger's process earned, which is what the comment above the block
says must never happen.

A comparison is only as good as both of its operands, so each is now
pinned to the thing that has to have produced it: `serving` to
`listening_release`, `cur_id` to the `current` symlink. The rule is a
three-way alternation at `exactMatches: 3`, and every way of neutering
the gate drops it to 2. The `cur_id=` prefix keeps the new alternative
`verify`-local — the file's three other `basename readlink current`
reads assign `want` or print inline.

Verified at this tree: all three rewrites (`cur_id="$serving"`,
`serving="$cur_id"`, `elif true`) exit 1 with "a control went missing";
clean tree is `deploy-lint` OK (10 rules, 28 checks) and
`deploy-lint-selftest` OK (32/32).

The wider class stays open and is named in the rule comment and the PR
body: the three macOS sites the comparison rule counts, and Windows's
four, all still bind only their served operand.
@dormouse-bot

Copy link
Copy Markdown
Collaborator Author

Residual closed in 1cec3287, in the idiom the finding proposed: a third alternative on cur_id="$(basename "$(readlink "$ROOT/current" at exactMatches: 3. Reproduced the escape first — cur_id="$serving" left deploy-lint: OK (3 installers, 10 rules, 28 checks), exit 0 — and it now exits 1 with matches 2x, expected exactly 3 — a control went missing, alongside the other two rewrites. The cur_id= prefix keeps it verify-local: the file's three other basename readlink current reads assign want (wait_for_health) or print inline (cmd_status, the verify summary). Selftest holds at 32/32 — the alternation's first match in file order is still the lookup half, so the added-copy pass appends that and goes red at 4.

The rule comment now enumerates three escape routes rather than two, and states the reason: a comparison is only as good as both operands, so each is pinned to what must have produced it.

Both skip reasons are fixed in 601430b1, replies inline. Confirmed each against the file rather than the suggestion text — cmd_status's health line is a bare http_ok with no service_healthy call anywhere in the function, and Windows assigns $listening = Get-ListeningRelease at :1462 before comparing at :1465.

The general form is worse than the Windows-only framing in the old body, so I widened that bullet rather than leaving it. The finding's closing note was right that none of the three counted macOS sites bind their expected operand either, and I verified it directly: rewriting wait_for_health's want="$(basename "$(readlink "$ROOT/current" ...)")" to want="$(listening_release "$PORT")" compares the port's holder to itself and leaves deploy-lint green. So the open gap is seven sites — three macOS plus Windows's four — not four, and the PR body's second bullet now says that. Still a maintainer call: it is the assignment-plus-comparison treatment applied across all of them, which is a different size of change from the one verify site here.

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both findings from the last round are closed. 601430b1's two skip reasons check out against the files — Linux's cmd_verify really does gate on service_healthy "$want" with the comparison living in the helper ([ "$(listening_release "$LOOPBACK_PORT")" = "$1" ]), and Windows's manage verify really does assign $listening = Get-ListeningRelease before elseif ($listening -eq $cur), so the old "compares inline" reason was backwards. 1cec3287's cur_id binding lands: I reproduced all three neutering rewrites going red at the merged tree (cur_id="$serving", serving="$cur_id", elif true), each seeded alone with the tree restored between, all matches 2x, expected exactly 3. Clean tree: deploy-lint OK (10 rules, 28 checks), deploy-lint-selftest OK (32/32). No spec needs the edit — SECURITY.md's FAIL IF wording and docs/specs/server.md's identity-check list are both unchanged by this. No overlap with #479, the other open PR touching deploy/local/ (it never touches a counted control).

Two prose findings, both on text this round added, both the same class the round fixed — a reason that reads as verified and is not.

Every way of neutering the gate drops the count to 2 is false, and cheaply so. The three enumerated edits do all go red, but the gate can be neutered without touching any of the three anchors. Swapping the else branch's fail for a pass leaves all three matches intact and verify green-ticks a stale process:

    elif [ "$serving" = "$cur_id" ]; then
      pass "the process on port $PORT is the current release"
    else
      pass "port $PORT is served by release '$serving'"
    fi
deploy-lint: OK (3 installers, 10 rules, 28 checks)   exit=0

That is the present-but-wrong case the file header already disclaims ("a die that no longer fires ... passes here"), so nothing is broken — but the sentence asserts exhaustiveness the lint does not have, and it contradicts the header two hundred lines above it. The previous wording (Either edit drops the count to 1) was scoped to the edits it enumerated; the generalization is what overreaches. Inline suggestion scopes it back.

Two comments point at "the PR body", which stops resolving at merge. deploy-lint.mjs:232 ends the unbound-expected-operand paragraph with — see the PR body, and the Windows skip reason at :242 ends with which is the gap named in the PR body. A reader who opens this file on main has no path from either to that text — the PR number is not in the file, and the description is not reachable from a checkout. AGENTS.md's Say it once allows a one-line pointer, but the pointer has to land somewhere; this is the same standard the skip reasons were just held to. Both sentences already state the gap in full, so the cheapest fix is to drop the dangling half — or cite #482, which does resolve. Inline suggestions do the former for :242 and the latter for :232.

Neither blocks. I authored this one, so no approval either way.

Comment thread scripts/deploy-lint.mjs Outdated
Comment thread scripts/deploy-lint.mjs Outdated
Comment thread scripts/deploy-lint.mjs Outdated
…ng pointers

"Every way of neutering the gate drops the count to 2" asserted
exhaustiveness the lint does not have: swapping the `else` branch's
`fail` for a `pass` leaves all three anchors matching and deploy-lint
green, which is the present-but-wrong case the file header already
disclaims. Scope the claim to the three parts the alternation counts.

Two comments ended in "see the PR body" / "the gap named in the PR
body" — a reader on `main` has no path from the file to that text. One
now cites #482, which resolves; the Windows `skip` reason points at the
comment above it, which states the gap in full. A `skip` reason that
cannot be checked is the same class this branch already fixed.
@nedtwigg
nedtwigg merged commit 499d760 into main Aug 29, 2026
17 checks passed
@nedtwigg
nedtwigg deleted the daily/review-runs-33254833944 branch August 29, 2026 19:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-runs Opened by the review-runs sweep

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants