Repository navigation
fix(spec): a truncated-history build judges a commit by the anchor it records, never by origin/main - #24436
Merged
objectstack-fleet[bot] merged 2 commits intoOct 19, 2026
Conversation
…own recorded anchor, never on origin/main Claude-Session: https://claude.ai/code/session_01WnW7HvfnfHbLg1fpkHX3Mu Co-authored-by: Claude <noreply@anthropic.com>
… main that moved on or re-anchored, refusal with no readable anchor Claude-Session: https://claude.ai/code/session_01WnW7HvfnfHbLg1fpkHX3Mu Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contributor
Author
席位核对ACCEPT ·
Generated by Claude Code |
objectstack-fleet
Bot
deleted the
claude/issue-24359-schema-ratchet-commit-anchor
branch
October 19, 2026 03:54
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #24359
Clause-②: no
What changed
packages/spec/scripts/build-schemas.tscomputes ONE baseline for its three git-reading ratchets: the schema removal check, the authorable-key deletion check and the default-change check. Wheremerge-base HEAD origin/mainanswers, nothing changes: that is the path this repository's own PR and merge-queue gate runs (Type Check · source gateschecks out withfetch-depth: 0).Where history is truncated, which is every
--depth=1rebuild of a pinned commit, the old resolver picked its anchor in this order:authorable-surface.base.jsonbaseRev, but only if origin/main vouched for it;Arms 2 and 3 make the verdict about commit X a function of where main points when the build runs.
Now a truncated history always anchors on the rev the tree under test records. Its keys, manifest and defaults are still read out of git at that rev (never out of the file), and the file is still checked against them. With no anchor the tree can read, the run refuses: either the file is missing, or its
baseRevcannot be fetched. It never falls back to the tip.--update-baseis the one mode that still reads the tip in a truncated history, unchanged; moving an existing anchor still refuses there (the forward-only rule).Per ratchet in the file (measured, not assumed):
resolveSurfaceBase()resolution, so all three share the defect and all three get this change at once;lib/unemitted-schemas) and the dropped-refinement ratchet read only in-tree baselines (no git reads), so neither shares the defect and neither changes.Measured: the release commit, rebuilt the way objectui rebuilds it
The commit
@objectstack/spec@18.0.0-next.3was released from,2c9bf9430a, rebuilt withpnpm gen:schemain an objectui-shaped clone:git init, a remote, a depth-1 fetch of the pin. Itsbuild-schemas.tsis byte-identical to main's, so "after" means that commit with this script copied in. Every leg's anchor rev is85c6d76ec42aunless stated otherwise.mainfroma266dcd7e296)48aab23a9e72)71fa166e96before,e22defb13cafter)85c6d76ec42a(main still names the same anchor)85c6d76ec42a3 schema(s) left the published set with no registered removal:api/PackageInstallManifest,security/DefaultRowScope,ui/PageRecordBinding, all added on main after the release07857fe78fon the tip, as--update-basewrites it)71fa166e965a; the same 3 schemas85c6d76ec42aSo for every origin/main (the real tip, a re-anchored main, none) the release commit gets the same verdict on the same anchor. The only red after the change is a remote that will not serve the commit's own anchor, and that red says exactly that.
objectui's real failure (PR objectui#12963, the
Type Checkjob's annotation) matches the before/fetch-failure leg message for message. First "no upstream anchor was usable either … anchors on the origin/main TIP51ad8e4fba87", then❌ 2 schema(s) left the published set …. At that tip main's own anchor also named85c6d76ec42a, so the old resolver's arm 1 was lost only because the anchor fetch failed in that job.Ruling ③: the ratchet does not weaken where it guards
The ratchet's own refusal pins (the
a deleted manifest key must prove itselfblock): the bypass repro is red on a removal, a declared removal is green, and the negative control is green.71fa166e96:Tests 3 passeda99d2041d4: the whole block,9 passed, inside a 28/28 runThose cases run on walkable history, the PR gate's path, which this change does not touch.
New in the shallow block, a commit that is already on main, rebuilt in a truncated clone:
The trade, stated
A truncated history cannot prove
baseRevis upstream, so a branch that pointsbaseRevat its own commit is no longer overridden with main's anchor in a shallow job. That forged rev is refused where history is walkable:NOT an ancestor of origin/main, the existing authenticity check, which runs on this repository's PR and merge-queue gate. Both halves are pinned in one case: the shallow run uses the tree's rev, and the walkable run refuses it.Tests (all at HEAD
a99d2041d4)build-schemas-check-mode.test.ts(vitestrepoproject; the file is about 20 minutes long, so it ran in four-tslices): 92 of 92 passed (13 + 28 + 29 + 22).def-key-collisions.test.ts,published-projection-choke-point.test.tsandschema-tree-freshness.test.ts(they spawn, read or digest this script): 35/35 passed.@objectstack/speclocal project: 696 files, 21251 tests passed.pnpm --filter @objectstack/spec typecheck: exit 0.tsc -p tsconfig.scripts.json --listFilesincludes both changed files.build-schemas.tswas put on disk (hasha266dcd7e296…matched BASE's blob), the shallow block ran, then the file was restored (hash48aab23a9e72…matched HEAD's blob,git diff HEADempty). Result: 7 failed, 2 passed. The 2 are the unchanged cases (a real deletion stays red; keys come from git, not the file). The two new cases failed on the old resolver exactly as described above, with1 schema(s) left the published set … ui/ZzzPublishedOnMainAfterTheCommit. Nodist/leg: the test copiesscripts/and runs it with tsx.merge-baseanswerable, because a shallow run with no merge base now anchors on exactly the committed anchor and has no drift left to describe.Gates (local, derived with
dispatch-gates.mjs --commands --repo objectstack-ai/objectstackata99d2041d4)--ranreconciliation: 65 derived, 60 run, 5 NOT-MEASURED, 0 UNRUN (exit 0).check:authorable-surface,check:empty-state,check:liveness,check:strictness-ledger,check:variant-docs),check:nul-bytes,check:published-files,check:changeset-gate-self-tests,check:adr-0087-registration --base origin/main, andcheck:pm-dispatch-gates. That battery ran detached; its verdict line was1913 cases pass(fast tier).check:dts-closure,check:dual-build-cjs-loads,check:lean-entry-closure,check:sourcemap-no-sources-content: exit 3, prerequisite not met (no workspacedist/in this worktree).check:type-check-debt: it re-measurestscfor every package under a closure build and exceeded a 420 s bound (exit 124).CI runs all five NOT MEASURED families.
Changeset:
skip-changesetpackages/spec/scripts/**is outsidefiles[].npm pack --dry-runlists 0scripts/entries.Measured: none of this diff's symbols or strings appears in any
files[]path, nor indist/(each 0 files). Positive controls do hit:x-unprojectable-branches, which this script writes intojson-schema/, is in 6 files;PageRecordBindingis in 22dist/files.The one shipped byte sequence that moves is
json-schema/.build-input-hash-schema, a freshness digest whose inputs includescripts/**, as they do for any test-only spec change. The precedent is0d43004100, which changed only this script and carried no changeset.Acceptance notes
baseRevstill refuses withNOT an ancestor of origin/main. Stale origin/main and a forged anchor are indistinguishable without history, and that refusal is the guard. Noted, not filed; the carrier is none.packages/spec/; the branch was not merged with main.Generated by Claude Code