Uh oh!
There was an error while loading. Please reload this page.
fix(tooling): bump-objectui.sh asks whether the pin is ON objectui main, and says so three ways - #10796
Conversation
…in, and says so three ways `bump-objectui.sh` wrote `.objectui-sha` from `git rev-parse HEAD` of the operator's objectui checkout and never asked whether that commit is on main. Bump with a feature branch checked out and the pin names a revision that is not on main -- and the script reported success. Measured on a fresh --no-tags clone of objectui, 2026-08-21: 941 remote branches, 118 branch tips not reachable from main, 291 commits present and not on main. The bump now asks, and reports THREE answers, never two: - on origin/main -> no banner; the pin line that prints anyway says "(on origin/main)", so "checked, and fine" is stated rather than inferred from silence. - not on origin/main -> a loud warning naming the branch(es) the commit IS on, and saying which situation it is: pushed onto a branch that never merged, or never pushed at all. The pin is still written -- this is a warning. - cannot be answered -> it says THAT, and borrows the wording of neither verdict. Both causes are handled: no origin/main in the checkout, and an absent commit object. Warning, not gate, deliberately: `origin/main` is only as fresh as the last fetch, so hard-failing would reject a legitimately-just-merged commit, and this script is deliberately usable offline. The hard gate is at the chokepoint that must fail closed -- cut-rc.yml re-asks the same predicate against a fresh full clone and refuses to cut (#9450 / PR #10494). It never fetches for the operator; it prints the fetch they may want. Two git behaviours the three-way split rests on, measured on git 2.43.0 and pinned in the self-test rather than asserted in prose: - `merge-base --is-ancestor <absent-object> origin/main` exits 128, an error, not the 1 that means "not an ancestor". Reading 128 as "no" invents a false alarm; reading anything-not-1 as "fine" reinstates the silent pass. - `git rev-parse HEAD` exits 0 and prints the sha even when that commit object is missing from the object store -- it resolves the ref without reading the object. So NEW_SHA arriving is no proof of presence, and the absent-object case is reachable from the DEFAULT path, not only from an explicit argument. 14 cases added to `objectui-changeset-digest.mjs --self-test`, which already drives the real shell script end to end (pnpm check:objectui-changeset). Fixes#10495 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
PM review — the judgement call goes your way, and you were right to overrule me
On the flagged call: keep the |
Uh oh!
There was an error while loading. Please reload this page.
Dequeued by a defect on |
Fixes#10495
The defect, reproduced
scripts/bump-objectui.shwrote.objectui-shafromgit rev-parse HEADof the operator's objectui checkout and never asked whether that commit is on objectuimain. Driven against a throwaway objectui with a feature branch checked out, onorigin/mainas it stands:Success, silently, on a pin that is not on main. Scale it guards against, measured on a fresh
--no-tagsclone of objectui 2026-08-21: 941 remote branches, 118 branch tips not reachable frommain, 291 commits present and not on main.The split: this warns, the release cut gates
Verified at the start of this work rather than assumed: PR #10494 merged 2026-08-21T02:49:03Z, and the hard reachability gate is live on
mainat.github/workflows/cut-rc.yml:319. The release path already fails closed, so this file is the producer half only and does not need to be the second hard gate:origin/mainin a local checkout is only as fresh as the last fetch — a hard failure here would reject a legitimately-just-merged commit, a false red on the common path.print_sdui_next_stepis built on — a reminder, not a gate).It never fetches on the operator's behalf; it prints the
git fetch origin mainthey may want. That is a deliberate difference from the--unshallowdeepen further down the same file: that one repairs an input the script is about to derive from, whereas a fetch here would only change the answer to a question the operator is being asked to judge..github/workflows/cut-rc.ymlis untouched — it is inside the #9465 fence and already has its half.Three answers, never two
origin/main(on origin/main)The third state is the point of the card, and both of its causes are handled.
merge-base --is-ancestorexits 128 on an absent object — an error, not a verdict — so reading 128 as "not an ancestor" invents a false alarm, and reading anything-not-1 as "fine" reinstates exactly the silent pass this removes. A checkout with noorigin/maincannot answer the question at all. The exits are split 0 / 1 / everything-else on purpose, and the questions are asked in the same ordercut-rc.ymlasks them, because the later ones are meaningless until the earlier ones hold.The not reachable warning separates the two situations the operator would otherwise have to go and look up — pushed onto a branch that never merged, versus never pushed at all — reports the local
origin/maintip and its age so "your ref is simply stale" is a judgeable fact rather than a hedge, and names the downstream consequence (pnpm sdui:manifestwould ratchet ADR-0082 D4 spec↔registry parity against a tree that is not on main until an RC is cut).Two git behaviours this rests on, measured not assumed
Both are pinned in the self-test rather than asserted in prose, so a future git that changes either fails the test instead of silently flipping the branch taken (git 2.43.0):
The second one is why the absent-object case is reachable from the default path and not only from an explicit argument:
rev-parseresolves the ref without reading the object, soNEW_SHAarriving is no proof the object is present. Presence is therefore asked as its own question.One judgement call, flagged for review
The card said state 1 should be silent. It adds no new line on the healthy path — but the pin line that prints anyway now carries
(on origin/main), and the two loud states carry(NOT on origin/main)/(reachability UNKNOWN)in that same slot plus a recall on the run's last line. Rationale: this card's whole defect class is a partial result that reads like a complete one, and pure silence leaves "checked and fine" indistinguishable from "this build of the script never asked" — the ambiguity the card exists to remove, one level up.cut-rc.ymlprints a positive line for the same reason. Zero added output, so it is not noise; say the word and it comes out.Evidence
After: same input, and the healthy control
A pin that is on main stays silent and exits 0 (self-test R1).
State 3 proven directly, both causes, exit codes quoted
Neither says "not reachable"; neither passes.
Self-test —
bump-objectui.shhas none, so this extends the one that already drives itscripts/objectui-changeset-digest.mjs --self-testalready builds throwaway objectui repos and runs the real shell script end to end against them, and it is gated bypnpm check:objectui-changesetinlint.yml. 14 cases added there (R1…R6), including the card's exact shape driven through the front door — no argument at all,rev-parse HEAD, feature branch checked out.Baseline
origin/main(in a comparison worktree, so it runs against a real repo root): 99 checks, exit 0. With this change: 113 checks, exit 0. Diff of the two check-name lists: nothing present in baseline and missing after — the delta is exactly the 14 new ones.Two fixtures gained
refs/remotes/origin/main, which a real checkout has; without it every pre-existing drive would take the new "cannot answer" path and bury its own assertions under reachability warnings.Ablation
Mutation:
report_objectui_reachability()returns before asking anything, so the check can never fire in any state. Confirmed on disk by anchor count, not by an editor's exit code — injected markerABLATION-10495count 1, function header still count 1, file sha256444a5aac…→e0fcf19b…. No build step is involved: the self-test copiesbump-objectui.shverbatim fromscripts/, so the mutated bytes are what runs.Predicted before running — 11 red (R1b, R2, R2b, R2c, R2d, R3, R3b, R4, R4b, R5c, R6), 3 green, 102 green total, exit 1. Green predicted for R1, because it asserts the absence of a warning and a check that never fires passes that control — which is why the control alone proves nothing — and for R5a/R5b, which are raw git measurements independent of the script.
Observed: exactly that. 11 red, 102 green, exit 1, and the 11 red case names matched the prediction one for one; the 3 green were R1, R5a, R5b. Restored afterwards: marker count 0, sha256 back to
444a5aac…— byte-identical to pre-ablation — and the self-test back to 113 green / 0 red, exit 0.Gates
Derived with
node scripts/pm/dispatch-gates.mjs(no paths passed — it takes its own change set from the merge base), re-derived after the final commit and re-run atf871db8c45on a clean tree. Exit codes captured before any pipe, verdicts quoted from what each gate printed:check:objectui-changeset✓ objectui-changeset-digest --self-test: all checks passed/✓ objectui-range --self-test: all checks passedcheck:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declared…check:doc-authoring✓ doc authoring guard: 389 files clean — no bare metadata literals.check:required-contexts✓ check-required-contexts: 6 required context name(s) pinned across 2 workflow(s)…--filter @objectstack/lint check:doc-formula-expressions✓ … 22 record-scoped formula example(s) across 416 files / 1443 TS blocks judged cleancheck:entry-guard✓ check:entry-guard: 127 scripts/ file(s) — every entry guard goes through invoked-as.mjscheck:nul-bytescheck-nul-bytes: OK (scanned 6242 text file(s)… no raw ASCII control bytes)check:entry-guardandcheck:nul-byteswere run although the derivation did not name them: this touches a.mjsentry point, and every edit gets the control-byte scan. The changed files were also scanned directly withgrep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'— no hits.doc-formula-expressionsfailed on first run withERR_MODULE_NOT_FOUNDfor@objectstack/formula/dist— an unbuilt dependency closure in a fresh worktree, not this change. Green afterpnpm --filter '@objectstack/lint^...' build.No changeset
Release/dev tooling and an internal maintenance doc; nothing published changes.
skip-changesetapplied additively viaPOST /issues/<pr>/labels.Generated by Claude Code
Generated by Claude Code