Uh oh!
There was an error while loading. Please reload this page.
fix(devx): refuse instead of degrading when a walkable range's changeset blob is unreadable - #14440
Conversation
…ob is unreadable Builds a throwaway objectui repo with a real changeset commit, deletes only the changeset's blob object (not the commit), and asserts --check-walkable still exits 0 on the broken tree before driving bump-objectui.sh through it. Red against the unmodified script: it still emits a degraded patch changeset and exits 0. Adds the objectui-changeset-digest.mjs + invoked-as.mjs copies the case needs, mirroring the digest script's own self-test fixtures.
…set blob is unreadable RANGE_OK=1 but objectui-changeset-digest.mjs still fails (a changeset blob unreadable at both `to` and the commit that added it) used to fall into the same degraded-patch path as the initial pin, publishing a level nobody declared into @objectstack/console's CHANGELOG. The range walked, so a level WAS derivable in principle -- this state now refuses, matching the #14178 disposition for the unwalkable-range case. Moves the changeset derivation above the pin write so a refused run leaves .objectui-sha byte-identical and no changeset file, preserving the #10797 write-ordering invariant one input further in: the digest CLI only writes its --out file once the whole digest has built successfully, so a failing derivation leaves CS_FILE unwritten on its own -- no temp-file indirection needed. The initial pin (no previous SHA, no range) is unaffected and keeps its degraded entry. Updates the header's refusal-states description and the self-test pointer comment to describe the new case.
… literal check:declared-population-live read the quoted '.changeset/widget-refresh.md' in break_changeset_blob's call as a declared population for check:objectui-bump -- the whole family's only path-shaped literal, and it reaches no tracked file since it names a path inside a disposable throwaway checkout. Builds the path from CHANGESET_NAME by interpolation everywhere it is used instead, and adds a no-path-population marker documenting why (case 5's fixture paths live only under mktemp -d).
The block above the pin write no longer opens with the first working-tree mutation on the success path -- the digest's --out write (CS_FILE) now lands first, the pin second, since the changeset derivation moved above the pin write. Reword the comment to state what actually holds: the digest only writes CS_FILE once the whole digest has built, so a refusal never leaves it behind; the invariant #10797 needs is that no read of objectui happens below this point, not that this line is literally the first write. No behaviour change.
baozhoutao
commented
Sep 2, 2026
Reworded the pin-write comment per review: it no longer claims to be "the first mutation" (the digest's
New head: Generated by Claude Code Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#14393
What changed
bump-objectui.shreached its degraded emitter (BUMP="${CONSOLE_BUMP:-patch}")in two states after #14178: the initial pin (no previous SHA — stays permitted,
unchanged here) and a range that walks completely but whose changeset
derivation (
objectui-changeset-digest.mjs) throws anyway — the reachabletrigger is a changeset blob unreadable at both
toand the commit that addedit. State 2 declared a bump level (
patch) nobody actually declared, publishedinto
@objectstack/console's CHANGELOG and the curated release notes, exactlythe shape the #14178 triage ruling forbade ("a
patchthat reads like adeclaration and silently skips the ADR-0087 disposition prompt is worse than no
record"). This PR makes state 2 refuse instead, matching the #14178
disposition for the unwalkable-range case —
RANGE_OKis the discriminator: awalkable range means a level was derivable in principle, so a failed
derivation there is an error to surface, not a guess to publish.
Placement, per the card's zone 2 item 2
The changeset derivation (
node .../objectui-changeset-digest.mjs --out "$CS_FILE") used to run after the.objectui-shapin write, so a refusalthere alone would have left the pin moved with no record — the exact
half-applied state #10797 exists to prevent, one input further in. This PR
moves the derivation call above the pin write and refuses there on failure.
It writes straight into
CS_FILErather than a temp path moved into placeafterward: reading
objectui-changeset-digest.mjs'smain(), the--outfileis only written (
writeFileSync(out, file)) once the whole digest has builtsuccessfully — a
readAtfailure throws insidebuildDigest, before thatwrite is ever reached, so a failing derivation leaves
CS_FILEunwritten onits own. There is no partial-write case left for a temp file to guard against
(confirmed by inspection of
objectui-changeset-digest.mjs:763-788and:1184-1245— read-only per the card's file-surface constraint, not modified).Reproduction (the deliverable per zone 1 / zone 3)
Built a throwaway objectui repo: commit A (previous pin), commit B adding
a
.changeset/widget-refresh.mdfile declaring"@object-ui/core": minor. The range A..Bwalks completely (
--check-walkableexits 0). Deleted only the changesetblob object added in B (not the commit) and re-confirmed the walk still
answers 0 — the walk is over commits/trees,
--check-walkablenever touchesblobs.
Before (unmodified
scripts/bump-objectui.shatorigin/main96b627d13),bump-objectui.sh --no-commit(with commit B as the argument):Exit 0.
.objectui-shamoved. A changeset landed declaringpatchwith a"Degraded list" body — exactly the silent-
patchdefect this card describes.After (this PR's
scripts/bump-objectui.sh), same fixture, same command:Exit 1.
.objectui-shabyte-identical to before the run. No changesetfile written.
This exact fixture is
case_5inscripts/bump-objectui.selftest.sh(new),mirroring the same
ok/badidiom as cases 1-4. It also asserts the fixtureis real — the blob is confirmed gone (
git cat-file -efails) and--check-walkableis re-asserted green on the broken tree before the bump iseven run, so the case cannot pass over a fixture that never reached this state.
Ablation
Reverted only the refusal commit (
git checkoutpointed at the fixture-onlycommit, for the path
scripts/bump-objectui.sh, restoring the pre-fix byteswhile the fixture commit stayed), reran
bash scripts/bump-objectui.selftest.sh:Restored (
git checkout HEAD -- scripts/bump-objectui.sh, safe — the fix wasalready committed at that point) →
bash scripts/bump-objectui.selftest.sh→✓ bump-objectui self-test PASSED — 20 assertions across 5 cases.check:declared-population-live
Adding
case_5initially introduced ONE bare quoted path literal(
.changeset/widget-refresh.mdinside thebreak_changeset_blobcall), whichcheck:declared-population-livecorrectly read as a declared population forthe
check:objectui-bumpfamily that reaches nothing in this tree (it names apath inside a disposable
mktemp -dcheckout, never a tracked file). Fixed bybuilding the path from a
CHANGESET_NAMEvariable via interpolationeverywhere it's used instead of spelling it as one bare token, plus a
dispatch-gates: no-path-populationmarker documenting why. Verified with adirect
extractWatchHintscall — zero hints now, andcheck:declared-population-liveis green (157 of 201 families declare a population, all reaching the tree).
Header / self-test-pointer comment sync
Per zone 2 item 5: the header's refusal-states description (
bump-objectui.sh:17-52)and the self-test pointer comment (
:90-105) are updated to describe the newrefusal and that case 5 needs node (unlike cases 1-4, which stay offline/no-node).
Changeset
None added.
scripts/bump-objectui.shandscripts/bump-objectui.selftest.shpublish nothing from any package. Following this repo's own convention for
scripts/**-only changes: PR #14391 (
c0eed5648, the direct #14178 precedent —same two-file surface, a considerably larger change) shipped with no
.changeset/*.mdfile. This PR's file surface is outside the closed listos-dev.mdlets a dev self-applyskip-changesetfor (scripts/pm/**, notscripts/bump-objectui*), so the label — needed forCheck Changesetto pass— is the PM's call per the card and
os-dev.md.Gates
All commands
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/bump-objectui.sh scripts/bump-objectui.selftest.shderived(16 total), run under
scripts/pm/os-verify-lock.sh, at final head85fd2d22e:node scripts/check-ci-filter-parity.mjsnode scripts/check-cross-package-test-inputs.mjsnode scripts/check-shard-attestation.mjsnode scripts/check-test-completeness.mjsturbo run testlog; CI tees it, this branch is unreachable there)node scripts/pm/bare-root-worklist.mjs --self-testpnpm check:agent-test-spellingpnpm check:bash32-floorpnpm check:cli-command-idspnpm check:cross-package-test-inputspnpm check:entry-guardpnpm check:objectui-bumppnpm check:objectui-changesetpnpm check:parse-guardpnpm check:pm-dispatch-gatesdispatch-gates self-test: 1232 cases pass(held the verify-lock 780s/13m under shared-box contention)pnpm check:pnpm-filter-targetspnpm check:watch-hint-literalAlso run per zone 3 (not in the derived 16, explicitly named in the dispatch):
pnpm check:ratchet-remedy-authority— RAN-PASS;pnpm check:declared-population-live— red once (see above), fixed, RAN-PASS after fix.
shellcheckon both bashfiles: not run — the binary is absent from this container and no workflow
under
.github/runsshellcheck.All 16 derived local gates are green (
check:test-completenessNOTMEASURED — prerequisite is a CI-only turbo test log).
node scripts/check-nul-bytes.mjsalso green.
Generated by Claude Code