fix(tooling): resolve the changeset LEVEL axis package from its manifest, so the 51 nested packages are candidates it can refuse - #16969
Conversation
…t any depth wip Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
…ure mode wip Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
ACCEPT in substance — PR #16969, reviewed against the tree, ⛔ not against the reportFlip + arm withheld until the six running legs report. Both open questions are answered below. What I re-drove on the head
⭐ Three things that make this the right fix rather than a wider regex1. Shallowest-first is load-bearing, and the reason is the trap my own brief warned about — from the other side. 2. The new failure mode the widening owes is closed and pinned as an invariant, verbatim from the self-test:
⇒ A nested dir with no readable manifest is now reported in 3. ⛔ #16692 cannot be silently closed, and that is mechanical. The battery carries a control asserting
⭐ And the second ablation is the one I most wanted: implementing option B (one optional group segment) — the repair that looks done — fails exactly 2 assertions (three-levels-deep, and shallowest-first) while passing every other new one. ⇒ the near-miss is mechanically excluded rather than argued against. The pair, and the two extra legsLeg A nested Whole-tree effect: 22 package dirs matched before, 72 after, 0 lost, 50 gained. ⛔ Open question 1 — blast radius. Answer: A and B. ⛔ Explicitly not C.7 of 150 replayed merges flip 0 → 1, 0 flip the other way. Six are The seventh is the real one, and I verified it myself rather than taking it: PR #16650, merged 2026-09-07T21:13:32Z — body carries a line-start ⇒ Declared And I ran option B's sweep myself, over the open board: all 23 open PRs (control: 23 of 23 returned a readable file list), cross-referencing every workspace manifest's directory against each PR's moved Result: ZERO open PRs carry the shape this widening will newly refuse.
⇒ Nobody on the open board is surprised by this landing. That is the answer B asked for, and it is a measurement. ⛔ Open question 2 — who posts on #16692. Answer: A, this seat posts it.Correct reading of the brief: #16692 was read-only for you, its ROOT axis is untouched and pinned as untouched, so this does not close it. ⇒ Acting on a card that is not yours would have been the wrong move; I will cross-post there that the DEPTH axis landed and the ROOT axis is still open. The prose rewrite, acceptedNine occurrences of Two gates NOT MEASURED, declared
Generated by Claude Code |
Part of #16713
Clause-②: no —
scripts/**only. No published surface moves: thefiles[]union across every workspace manifest isdist,README.md,CHANGELOG.md,api-surface,json-schema,liveness,llms.txt,prompts,spec-changes.json,src/**/*.zod.ts— all package-relative, none of which can reach a repo-rootscripts/file — and zero package/app/example sources import this gate. Henceskip-changeset, not a missing changeset.What moved
scripts/check-changeset-no-major.mjs, one file, +294 / -36.The LEVEL axis decided which package a diff "grew" through a path pattern whose package half was exactly one path segment wide. This workspace is not flat: 51 of its 74 packages sit at
packages/GROUP/NAME/, so the segment afterpackages/is the GROUP and no/src/follows it. For all 51 the axis recorded no package, and aneeds:contract-reviewcarrier paired with apatchprinted a tick.That tick was not a weaker verdict — it was no verdict, rendered identically to a pass. And it escaped the one instrument built to catch exactly this:
unreadableexists so a package the reading cannot NAME is never mistaken for a package the diff did not TOUCH (#4690), and it stayed empty here, because a nested package was never a candidate. A gate cannot report a limb it never grew.The reading is now the manifest set instead of a depth:
publishedSourceOwners(path)enumerates, shape only and at any depth, every ancestorDunderpackages/for which the path readsD/src/**, ordered shallowest first.packagesTouchedwalks that list and takes the first candidate whosepackage.jsonis readable in the HEAD tree, caching one manifest read per directory rather than per changed file.unreadable. That is the new failure mode the widening owes, and it is asserted as an invariant: a matching path lands inpackagesor inunreadable, never in neither.bin/**is deliberately still unread. WHICH roots ship is #16692's axis, and a control in the new battery pins it as unread so this change cannot silently answer a different card.Why shallowest-first, and why not option B
Both were settled by measurement, not preference.
Shallowest-first — this repo really contains a two-candidate path.
packages/create-objectstack/src/templates/blank/src/objects/note.object.tsisD/src/**for BOTHpackages/create-objectstackand the scaffold template dir under it, which carries its own manifest (objectstack-blank,private: true, template CONTENT that create-objectstack ships, not a workspace member). Resolving to the NEAREST manifest would name the private template and drop the real package — a regression against the one-segment reading being replaced. Shallowest-first returns the old answer on every path the old pattern matched.Not option B (one optional group segment). Ablation below shows option B satisfies every nested-package assertion — it looks repaired — and fails only the three-levels-deep case and the two-candidate ordering. That is the recurrence the triage seat predicted, made mechanical instead of prose.
Measured over the whole tree
Population: 23 flat + 51 nested = 74 workspace packages, counted by path depth over
git ls-tree -r origin/main -- packages/onpackage.jsonrows. A naivepackages/**/package.jsoncount gives 75; the extra ispackages/create-objectstack/src/templates/blank/package.json, the scaffold template above —private: true, not a workspace member. The 74-package count is the one used here.Before/after pair, with both controls
Driven through the real exported
scan/packagesTouched/judgeLevel/renderLevelof BOTH file versions (the pre-change blob at07c56b5388and this branch's HEAD) over identical temp git repositories. Every exit code captured before any pipe.packages/drivers/driver-sql/src/**,patch, declared yesclean, touched{packages:[],unreadable:[]}enforce, touched{packages:["@objectstack/driver-sql"]}packages/cli/src/**,patch, declared yesenforceenforcecontent/docs/**only,patch, declared yescleancleanminorclean, touched[]clean, touched["@objectstack/driver-sql"]unreadable:["packages/newgroup/newpkg"]Leg B is the load-bearing one: the nested leg turning red proves nothing unless the flat leg still reds in the same harness. Leg D separates "the axis was widened" from "the gate now refuses everything" — the package IS newly seen and the verdict still passes, one word apart. Leg C would be the only failure of an implementation that simply always enforced.
Blast radius, measured BEFORE the shape was chosen
Window: the 150 most recently merged PRs, each replayed at its merge commit against that commit's parent, driving both file versions' own
judgeLevel. 150 measured, 0 skipped.7 verdicts move exit 0 → exit 1. 0 move 1 → 0. 0 unreadable entries anywhere in the window.
clean→enforce@objectstack/driver-sql,@objectstack/driver-tursonot-measured-moot→not-measured-material@objectstack/service-automationnot-measured-moot→not-measured-material@objectstack/plugin-authnot-measured-moot→not-measured-material@objectstack/plugin-approvalsnot-measured-moot→not-measured-material@objectstack/plugin-securitynot-measured-moot→not-measured-material@objectstack/plugin-webhooksnot-measured-moot→not-measured-material@objectstack/honoRead the seven as two different facts:
not-measured-material: no declaration was readable and apatchnow sits on a package the PR grew. The remedy the gate itself prints is one declaration line in the PR body, which clears the red with no push — not a level change.Clause-②set to yes as a durable body line (not a strippable carrier label, so this reading is stable and reproducible) and graded both nested driver packagespatch. That is precisely the shape this axis exists to refuse, printed green because it could not see the packages.No tolerance, no allowlist, no grandfathering is proposed, and none is in the diff. The number is a reading for the maintainer and the routing is the PM's.
needs:contract-reviewcarrier is stripped at review pass, so the labels read today understate how many of those PRs declared yes at the time. The bias runs toward thenot-measured-*lanes rather than towardenforce; #16650's reading is unaffected because its declaration is a body line.Ablation — the new battery can actually fail
Both legs: commit first, mutate, prove the mutation reached disk (injected-marker count and removed-text count, plus a
git hash-objectdiffering from the HEAD blob), run, restore viagit checkout HEAD -- PATH, prove restoration bygit diff HEADbeing empty and the blob hash matching HEAD again. Both restored byte-identical to3b0273c4e035fd07087f93379d89408a1bf35eae.got clean. That is the card's finding reproduced inside the self-test. All other batteries stayed green.Batteries
The LEVEL axis: #16044's two heads, one word apart (#16055)The GRAIN: a PR-scoped declaration judged at PR scope (#16361)The wiring: these fixtures must actually run on every PRTHE DEPTH: a nested package is a candidate the axis can refuse (#16713)SELF_TEST_BATTERY_FLOORNo existing battery was disturbed. The roster floor moves 15 → 16 because the file's own invariant requires it: "DELETING an entry silences that battery's floor exactly as effectively as zeroing it, so the roster's own size is pinned too." Leaving it at 15 beside 16 declared batteries would let any one of them be deleted without redding.
Gates
All exit codes captured before any pipe.
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 33 commands; all 33 run, all exit 0. Reconciled with--ran:33 derived, 33 run, 0 NOT-MEASURED, 0 UNRUN.node scripts/check-changeset-no-major.mjs --self-test→ exit 0,225 assertions(204 on the branch point).pnpm lint(the whole repo,eslint . --no-inline-config) → exit 0. Run in full, so no narrowing claim is needed.scripts/were run rather than read as cleared: 5 exit 0;check:dts-closureandcheck:published-readme-exportsexit 3 = PREREQUISITE NOT MET (no package has adist/; they read a closure build). NOT MEASURED, not a finding, and declared to CI — this diff moves no package, so there is no affected-package closure to build for them.grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'→ no matches (pnpm check:nul-bytesalso exit 0).Gate readings quoted from the gates' own verdict lines, at final commit
a6c3fb8c5b.验收备注
The triage seat's acceptance criteria, quoted verbatim and answered one by one (the ruling is left in its original language deliberately — rewriting a quoted ruling rewrites the ruling):
Taken. The package is resolved by locating its manifest in the HEAD tree, at any depth; what remains shape-based is only
D/src/**, which is the definition of published source and [finding]judgeLevelcounts a package as "grown" only throughpackages/*/src/**, so a shippedbin/target is a published surface the changeset level axis cannot see #16692's axis, not this card's. Option B was implemented as an ablation and reds against the new battery, so B is now mechanically excluded rather than argued against.Both are in the self-test. The pre-existing flat assertion is untouched (its battery still registers 56), and the flat leg is additionally re-driven inside the new battery so a nested red is readable as a widening rather than as a gate that stopped discriminating.
Pinned as leg C, plus leg D (the same nested diff graded
minorstays green while the package IS seen).Reading: not shared.
git grep -n 'PUBLISHED_SOURCE_PATH|packagesTouched|judgeLevel'over the whole tree returns hits inscripts/check-changeset-no-major.mjsand nowhere else. The workflows (pr-automation.yml,cut-rc.yml,release.yml) invoke the script as a command; no file imports these symbols.Not done by this seat, and this is the half left open — hence
Part ofrather than a closing keyword. The DEPTH axis is landed here; the ROOT axis ([finding]judgeLevelcounts a package as "grown" only throughpackages/*/src/**, so a shippedbin/target is a published surface the changeset level axis cannot see #16692) is untouched by design and is pinned as untouched (packages/cli/bin/os.mjsowns nothing). This PR makes no claim on [finding]judgeLevelcounts a package as "grown" only throughpackages/*/src/**, so a shippedbin/target is a published surface the changeset level axis cannot see #16692 and does not alter its state. Routing that cross-post, and routing the seven-PR blast-radius reading above, are the PM's.Out-of-scope observations — noted, not filed
packages/spec/liveness/README.mdalready records the same class of defect in a different tool ("A pathspec globpackages/*/srcnever matched the nested …"), andscripts/check-duration-unit-keys.tscarries a'packages/*/src/**'scan surface of its own. Whether that gate is blind the same way is not measured here and is outside this card. Carrier: any future audit of one-segmentpackages/*globs across the gate farm; there is no PR in flight that touches those files.packages/*/src/**topackages/**/src/**in the same file, because after this change the old glob understates the axis's reach and would tell a nested-package author their package is not judged. One historical comment that quotes a since-removed message is left at the old spelling on purpose.Generated by Claude Code