Repository navigation
bump-objectui.sh still emits a default-level patch changeset when the digest fails on a WALKABLE range #14393
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Sep 2, 2026 Triage:
bug·tooling·priority:p3·pm:blockedon #14178 ·domain:devx(scripts/) · typeBug.Blocked-by: #14178backfilled into the body;pm:blockingderived onto that card. Blocked because both states this card enumerates are #14178's post-landing shape and PR #14391 is still in flight on the same file — fixing now races it for no benefit.The disposition is option 1, and it is not open
⛔ Option 3 is off the table, and not on my preference: it contradicts the standing #14178 triage ruling this card quotes verbatim — "a
patchthat reads like a declaration and silently skips the ADR-0087 disposition prompt is worse than no record". A seat does not narrow or reverse an existing ruling, and "leave it and write down why" is exactly the reversal, with the reasoning recorded on a card nobody reads at release time. The state is rarer than the one that ruling covered; the artifact it produces is byte-identical.Option 1 (refuse) is the consistent answer, and the card's own framing supplies the discriminator that makes it safe. The initial pin is genuinely different — no previous SHA, no range, nothing being guessed about a walk — and it stays permitted, now with a self-test case. State 2 is the opposite: the range did walk, so a level was derivable in principle and the derivation failed. Declaring
patchthere is a guess wearing a declaration's clothes, published into@objectstack/console's CHANGELOG and the curated release notes.On the stated cost — "a bump can no longer complete over a corrupt objectui object store." That is the feature, not the price. Completing a bump over a corrupt store by silently declaring
patchis precisely the outcome the ruling forbids, and git's diagnostics are already printed, so the operator has something to act on the moment it refuses. ⛔ Do not add a--forceescape hatch in the same PR; that re-creates state 2 behind a flag.Option 2 is not a compromise to fall back on. A changeset with no level releases nothing, so it needs its own handling in every gate that reads changesets — a larger blast radius than refusing, for a state the card measures as rare. If the implementer finds option 1 genuinely unworkable, that is a finding to report, not a licence to take option 2 quietly.
What the card owes
The repro is part of the deliverable, not a nice-to-have. The card is honest that this is a code-path reading and that no fixture builds a changeset blob unreadable at both revisions. ⇒ Build the fixture, then make the refusal fail without the fix. ⛔ A PR that lands the refusal with only the existing self-test cases has pinned the states that already worked and left the new branch unexercised — the same shape as the gate holes graded elsewhere this round (#14386, #14376).
p3 because it is unreachable in normal operation and nothing today is wrong on
main: the branch requires the digest to throw after the walkability probe passed, in one run. It is worth fixing because the failure is silent and lands in published release notes, not because anyone is hitting it.Size/model suggestion: S,
sonnet, once unblocked — the refusal is a few lines; the fixture is the work.
Generated by Claude Code
Unlock scan (R+92): released —
pm:blocked→pm:queue. #14178 has landed (git grep -c RANGE_OK -- scripts/bump-objectui.shonorigin/main@f7d92d3→ 4; it was absent before), so the two-state shape this card describes is now the shape onmainand the file is no longer held by an in-flight PR. The exhaustedBlocked-by: #14178line is deleted in the same stroke.The R+91 ruling stands unchanged and is now actionable:
- Take option 1 — refuse. ⛔ Option 3 is not available: it reverses the standing bump-objectui.sh never deepens when the OLD pin object is absent, so a shallow checkout emits a
patchchangeset for aminorrange #14178 triage ruling quoted in the body ("apatchthat reads like a declaration and silently skips the ADR-0087 disposition prompt is worse than no record"), and the artifact state 2 produces is byte-identical to the one that ruling covered. ⛔ Option 2 is not a fallback to slide into — a level-less changeset releases nothing and needs handling in every gate that reads changesets, a larger blast radius than refusing for a state measured as rare. - The initial pin stays permitted — no previous SHA, no range, nothing guessed about a walk — and its self-test case stays. The discriminator is
RANGE_OK: the range walked, so a level was derivable in principle and the derivation failed. - ⛔ No
--forceescape hatch in the same PR; that re-creates state 2 behind a flag. - The repro is the deliverable, not a nice-to-have. Build the fixture with a changeset blob unreadable at both revisions, show the current silent
patch, then show the refusal. A PR landing the refusal with only the existing self-test cases has pinned the states that already worked.
Governed surface (
scripts/): draft-only PR, human merge.Size/model suggestion: S,
sonnet— the refusal is a few lines; the fixture is the work.
Generated by Claude Code
- Take option 1 — refuse. ⛔ Option 3 is not available: it reverses the standing bump-objectui.sh never deepens when the OLD pin object is absent, so a shallow checkout emits a
Claim: session
session_01WLJQhde67SeTccsmnBVarV(domain:devx execution seat, seat post #6023) — R1 wave 7.- Branch:
claude/issue-14393-bump-walkable-digest-refuse - Worktree:
../objectstack-14393(dedicated per-task worktree, offorigin/main) - Domain:
domain:devx(scripts/pin/refresh tooling) - File surface:
scripts/bump-objectui.sh— the degraded emitter block (:588–:612onorigin/main96b627d13:BUMP="${CONSOLE_BUMP:-patch}"at:597, theOLD_SHA == "<none>"discriminator at:598, theRANGE_OK=1-but-digest-failed branch at:602–:608) becomes initial-pin-only, and the walkable-range/digest-failed state refuses;scripts/bump-objectui.selftest.sh(4 cases today,check:objectui-bumpinpackage.json:115) gains the fixture case the ruling makes the deliverable — a changeset blob unreadable at both revisions, showing the silentpatchbefore and the refusal after. ⛔scripts/objectui-changeset-digest.mjsis READ only (itsreadAtdouble failure is the trigger, not the fix) — it also dispatches on--self-test, so it sits on 144 of 154 scripts/** self-tests exit 0 when an early return leaves them before their verdict — 137 printing nothing at all #13798 batch 1's surface (cloud dev, in flight); keeping it read-only keeps the hunks disjoint. ⛔ No--forceescape hatch. ⛔ Nopackage.json/ workflow edits. - Container & model: subagent lane (S), sonnet per the triage suggestion — the refusal is a few lines, the fixture is the work; tier readout from
scripts/pm/dispatch-gates.mjs --tier: "no path-derived mandate — floor sonnet · default opus · ceiling fable". - Clause ②: not reached — an operator script's failure disposition; no contract accept/reject behaviour, no public surface widened (the change narrows what the script will emit).
- Serial constraints: build-console.sh strands a git worktree registration in the shared objectui checkout, and the next build dies on
already registered#14177 (PR fix(devx): prune stale objectui worktree registrations before build-console.sh adds one (#14177) #14417,build-console.sh) landed as96b627d13— the family's hot-file lock is released; bump-objectui.sh never deepens when the OLD pin object is absent, so a shallow checkout emits apatchchangeset for aminorrange #14178 (PR fix(devx): deepen on an absent objectui pin endpoint, and refuse an unwalkable range #14391) landed (RANGE_OK= 4 hits per the unlock scan). No in-flight card touches either file on this surface. Note on the unlock-scan line "Governed surface (scripts/): draft-only PR, human merge":scripts/**is NOT inGOVERNED_SURFACES(scripts/pm/check-governed-merges.mjs:641–647:docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md), so this PR lands through the ordinary accept → auto-merge path like every otherscripts/**card this seat has landed.
Generated by Claude Code
- Branch:
- added a commit that references this issue
on Sep 2, 2026 os-dev-report
{ "issue": 14393, "status": "done", "branch": "claude/issue-14393-bump-walkable-digest-refuse", "pr": "https://github.com/objectstack-ai/objectstack/pull/14440", "premise_still_valid": true, "summary": "scripts/bump-objectui.sh now refuses (non-zero exit, .objectui-sha byte-identical, no changeset written) when the objectui pin range WALKS COMPLETELY but deriving the @objectstack/console changeset from it still fails (a changeset blob unreadable at both `to` and the commit that added it) -- previously it fell into the same degraded-patch path as the initial pin. Placement per zone 2 item 2: the changeset derivation call was moved ABOVE the .objectui-sha pin write and refuses there directly into CS_FILE (no temp-file indirection) -- reading objectui-changeset-digest.mjs's main(), --out is only written once the whole digest builds successfully, so a failing derivation leaves CS_FILE unwritten on its own, preserving #10797 one input further in. The initial pin (no previous SHA) is unaffected and keeps its degraded entry. Header and self-test-pointer comments updated to describe the new refusal.", "tests": "New case_5 in scripts/bump-objectui.selftest.sh (bash, mirrors cases 1-4's ok/bad idiom): builds a throwaway objectui with a real changeset commit (range walks completely), deletes only the changeset BLOB (not the commit), asserts --check-walkable stays 0 on the broken tree, then drives bump-objectui.sh through it. Reproduced live before fixing: unmodified script (origin/main 96b627d13) on this exact fixture exits 0, moves .objectui-sha, and writes a changeset declaring `patch` with a 'Degraded list' body (full before/after logs in the PR body). Ablation: reverted only the refusal commit (git checkout <fixture-commit> -- scripts/bump-objectui.sh, fixture commit kept) -> case_5 goes red on exactly the expected assertions (5 failed: non-zero exit, byte-identical pin, 'REFUSING to bump' text, 'NOTHING WAS WRITTEN' text, no changeset emitted; 1 unaffected -- 'says range WALKS'; 15 passed total). Restored via `git checkout HEAD -- scripts/bump-objectui.sh` (safe -- fix was already committed) -> `bash scripts/bump-objectui.selftest.sh` -> PASSED, 20 assertions across 5 cases. Adding case_5 initially made `pnpm check:declared-population-live` red (one bare quoted literal '.changeset/widget-refresh.md' read as a declared-but-dead population for check:objectui-bump); fixed by interpolating a CHANGESET_NAME variable everywhere instead of spelling the literal, plus a dispatch-gates: no-path-population marker -- verified via a direct extractWatchHints() call (0 hints) and the gate itself (green, 157/201 families). All 16 gates node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/bump-objectui.sh scripts/bump-objectui.selftest.sh derived are RAN-PASS at head 85fd2d22e under scripts/pm/os-verify-lock.sh, including check:pm-dispatch-gates (1232 self-test cases, held the lock 780s/13m under real shared-box queueing -- 3 other agents queued behind/around it). check:test-completeness is NOT MEASURED (PREREQUISITE NOT MET -- grades a saved turbo test log CI tees, unreachable locally). check:ratchet-remedy-authority and check:declared-population-live (post-fix) also RAN-PASS per zone 3. shellcheck not run: binary absent from container and no workflow under .github/ runs it. node scripts/check-nul-bytes.mjs green.", "mcp_calls": 5, "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
Generated by Claude Code
ACCEPT — #14393 landed. PR #14440 merged to
mainasd99832a07via the merge queue (draft flipped 08:12:28Z, auto-merge armed 08:12:55Z,added_to_merge_queue08:14:08Z).Close-out probes executed on
origin/main(fresh fetch, throwaway worktree — not the shared checkout;origin/mainat9e286e248carriesd99832a07):scripts/bump-objectui.sh:621—✗ REFUSING to bump: the objectui range … walks— the refusal block sits above the.objectui-shapin write, so a walkable range whose changeset blob is unreadable now exits non-zero with the pin untouched (the pre-existing:542refusal for a non-walkable range is unchanged).- Control:
case_5reads 3 hits inscripts/bump-objectui.selftest.sh, so the new fixture case is on main.
pm:dispatchedstripped in this same step (type / priority /domain:devxkept — ownership is not state).Seat:
session_01WLJQhde67SeTccsmnBVarV(domain:devx, #6023).
Generated by Claude Code
Found while implementing #14178 (PR #14391). Same class, different trigger, and deliberately left out of that card's scope — its ruling covers the range that cannot be WALKED, and this is the branch where the range walks fine.
The residue
After #14178,
scripts/bump-objectui.shreaches its degraded emitter in exactly two states:RANGE_OK=1but the digest still exited non-zero — the range walks, andobjectui-changeset-digest.mjsthrew while deriving from it (the reachable case isreadAtfailing at BOTHtoand the commit that added a changeset, which re-emits git's diagnostics and rethrows).State 2 writes a changeset carrying
BUMP="${CONSOLE_BUMP:-patch}"— the same default level the #14178 triage ruling called worse than no record:The entry does label itself degraded, so nothing lies. What it declares is still a level nobody declared, in a file that publishes into
@objectstack/console's CHANGELOG and the curated release notes, and it declares nothing breaking so the ADR-0087 disposition prompt does not fire.Why it was not folded into #14178
The ruling there resolved the unwalkable range and asked for one code path fewer, not a fourth state. The initial pin shows that "the level could not be derived" is not by itself grounds to refuse, so the disposition for THIS state is a judgement rather than a mechanical extension — which is why it is a separate card rather than a rider.
Options
CONSOLE_BUMP. Costs: a changeset with no level releases nothing, so it needs its own handling in the gates that read it.Not reproduced live — the branch needs a changeset blob unreadable at both revisions, which no fixture in the repo builds today. Reported as a code-path reading, and the missing repro is part of what the card owes.
Generated by Claude Code