Repository navigation
fix(devx): assert the seed escape depth a tree-scoped walk is rooted at - #18340
Merged
os-try-charles merged 4 commits intoSep 16, 2026
Merged
Conversation
`check:cross-package-test-inputs` computed a seed's escape depth for the escape verdict and never asserted it, so a directory walk descending on a loop variable was invisible to all five limbs at once: it names no literal (limb 3 blind), adds no glob (limbs 4 and 5 blind), and inside a package that already carries an entry the two package-scoped limbs are already satisfied. A tree-scoped absence pin -- the shape the ADR-0049 retirement playbook mandates -- could land with its whole radius unhashed and the gate exited 0 naming nothing. Adds the sixth limb, judged on the SEED a descent is rooted at rather than on the names the descent produces, which is what limb 3 has already proved it cannot enumerate. The mandate is narrowed first (the triage ruling) and the narrowed radius is what is asserted: a pin descends from named roots, every root and every directory below it is reached by a declared glob, and it does not descend from the repo root -- the radius the table's positive-glob grammar cannot spell. Three pre-existing census and corpus sweeps in other packages walk wider than their entries declare. They are recorded in the shrink-only ACCEPTED_WALK_RADII with what closes each, and the same pass reds a ledger entry nothing owes any more, so it cannot grow quietly. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
…ed-escape-depth-assertion
… judged at The mandate docblock said no declaration reaches `docs/`, and the limb itself disagrees: `@objectstack/spec` declares one FILE under `docs/audits/`, which reaches into `docs/` and no further. Re-stated at the grain the limb judges -- a root's CHILDREN -- with the reading that shows it, and the three paired red/green/reverse readings that landed the limb recorded beside it, each named with the tree they were taken against. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
…ch hints The ledger spelled three test paths and one nested root whole, and named the bare recursive packages glob in backticks inside a reason string. Every quoted span in a gate's module body is read by the dispatch derivation as a path that gate watches -- a backticked span INSIDE a quoted sentence included -- so those four literals put five watch hints on this gate that it does not read. Two of them turned pins red in `check:pm-dispatch-gates`: the one holding that no hint of this gate reaches a TSX test file (the bare packages glob reaches every file under it), and the one holding that its covering hint is inherited from the declaration module rather than declared here. Rows are segments joined at runtime and reasons name globs in words. Measured: this gate's hint population is 140 with 0 declared here, on `main` and on this branch alike. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 16, 2026
os-try-charles
marked this pull request as ready for review
September 16, 2026 00:16
os-try-charles
deleted the
claude/issue-15565-seed-escape-depth-assertion
branch
September 16, 2026 00:42
This was referenced Sep 16, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
… the whitelist against turbo.json (objectstack-ai#18869) Fixes objectstack-ai#18342 Clause-②: no (narrowing) `repoRelativeLiterals()` recognised a repo-relative path literal only when it started at `packages|apps|examples|content|scripts|skills`, while its own header note promised "every top-level directory a declared glob can name". The note was false, so a test naming a whole `docs/...` path inside one quoted string was invisible to the flat collector and this gate stayed green over an undeclared cross-package read. ## What landed - The alternation is hoisted into `RECOGNISED_TOP_LEVEL_DIRECTORIES` and the regex is built from it, so the list is the single place a directory is added. `docs` and `.claude` join it. - `turboRootTopLevelNames()` reads back what `turbo.json` actually declares and splits it against the **git index** into directories and repo-root files. - A new self-test battery reconciles the two, so the header note is now checked rather than promised: the next top-level directory to gain a `$TURBO_ROOT$` input reds this gate instead of arriving unnoticed. Registered in `SELF_TEST_BATTERIES`; floor raised 12 to 13. - `turbo.json` was read as the criterion and **not modified**. ## Acceptance 2 — the census, taken on this branch's base Every `$TURBO_ROOT$/...` entry's first segment, against the old whitelist. Counts are mine, re-taken at `f9a9e76fee`; they match the dispatching seat's independent reading. | first segment | entries | old whitelist | disposition | |---|---:|---|---| | `packages` / `scripts` / `examples` / `content` / `skills` | 86 / 55 / 18 / 16 / 4 | recognised | unchanged | | `docs` | 3 | **not recognised** | **added** | | `.claude` | 1 | **not recognised** | **added** | | `.github` | 1 | **not recognised** | **deferred, declared — see below** | | `sdui.manifest.json` | 1 | not recognised | a repo-root **file**, not a directory — see below | `apps` is declared 0 times and stays in the list: the note says a glob *can* name it, and `apps/` is a real top-level directory. Not a defect, untouched. ## Why `.github` is deferred rather than added Recognising `.github` flat turns this gate **red on two literals that are over-collection rather than reads**: | file | line | literal | what it actually is | |---|---|---|---| | `packages/cli/test/scaffold-ci-script-parity.test.ts` | 17 | `.github/workflows/ci.yml` | inside a **block comment**. The file's real read is line 74, a reconstructed path under `packages/`, already declared | | `packages/create-objectstack/src/template-consistency.test.ts` | 376 | `.github/workflows/ci.yml` | `expect(scaffolded).toContain(...)` — an assertion about the **scaffolded project's** contents | Neither test opens this repo's own workflow. Both literals collide with it because a scaffolded project's CI file has the same repo-relative spelling, and `statSync` then confirms the collision as a real file. The declaration this gate would demand folds every edit of our CI workflow into two packages' test hashes, to hash a file neither one reads. Per the dispatch's stop-line I have **not** fixed those two and have **not** shrunk the whitelist to chase green. `.github` sits in `TOP_LEVEL_DIRECTORIES_PENDING` carrying that reason, and the battery pins the map's exact keys, so the deferral is declared and loud rather than silent. **The disposition is the dispatching seat's call.** Worth recording: `.github` is **not invisible** meanwhile. The reconstruction half already rosters `.github/workflows/scaffold-e2e.yml` for `create-objectstack`, declared on `create-objectstack#test`. What is deferred is the **flat** spelling alone. ## The fourth shape — `sdui.manifest.json`, a repo-root file The note promises "every top-level **directory**". `sdui.manifest.json` is a repo-root file, so it is a different shape, and my judgement is to **scope the note to directories and make the omission loud** rather than fold it into the same regex. The cost of the alternative is concrete. A directory anchors the match on its own name followed by `/`, which is what separates `docs/x.md` from prose. A repo-root file's whole repo-relative path is a **bare filename**, indistinguishable from any other quoted string; the cheapest rule that would reach `sdui.manifest.json` — collect a quoted dotted word — also collects `package.json`, which `statSync` then confirms as a real repo-root file. Every test that merely names that string would roster it, and the over-collection this collector calls harmless stops being harmless: it would force a declaration on a file nothing reads. So `TURBO_ROOT_FILE_INPUTS` pins the repo-root files `turbo.json` declares today and the battery asserts the live reading equals it — a **second** one reds here instead of arriving unnoticed. ## Readings All taken at base `f9a9e76fee`. Gate exit codes captured before any pipe. **Collector, fed directly (printed, not counted).** Before to after: ``` 'docs/audits/2026-07-unknown-key-strictness-ledger.md' [] -> ['docs/audits/...'] 'docs/qa/platform-checklist/areas/grid.json' [] -> ['docs/qa/...'] '.claude/skills/spec-property-retirement/SKILL.md' [] -> ['.claude/skills/...'] '.github/workflows/scaffold-e2e.yml' [] -> [] (pending, declared) 'sdui.manifest.json' [] -> [] (repo-root file) 'scripts/js-comment-mask.mjs' ['scripts/js-comment-mask.mjs'] (unchanged, lit) 'content/docs/deployment/cli.mdx' ['content/docs/deployment/cli.mdx'] (unchanged, lit) 'zzqq/nope.md' [] -> [] (dark) '.zzqq/nope.md' [] -> [] (dark) ``` **DARK, whole tree, at literal granularity** — every (package, literal) the collector rosters, dumped from `findEscapingPackages()` before and after: - before **219** rows, after **220** rows - **removed: 0** — every already-visible literal keeps its verdict, verbatim - added: exactly one, the bright control proving the new list sees something real — `@objectstack/plugin-auth docs/qa/platform-checklist/areas/identity-auth.json` (named by `checklist-refusal-envelope-consistency.test.ts`, already inside that package's declared `docs/qa/platform-checklist/areas/*.json`) - whole-tree gate output is byte-identical before and after: `OK: 29 package(s) read outside themselves ... 13 walked root(s) judged, 3 on ACCEPTED_WALK_RADII; 2023 test file(s) ...` **LIT, both directions, one per directory.** Each injects a flat literal into a test that already escapes, in a package whose declared globs are known, so no `turbo.json` edit is needed. Mutations restored after every leg and the restore proven by blob hash, not by exit code: | leg | host | literal | expected | got | |---|---|---|---|---| | `docs` undeclared | plugin-auth | `docs/protocol-upgrade-guide.md` | red | **exit 1**, names the file | | `docs` declared | plugin-auth | `docs/qa/platform-checklist/areas/automation.json` | green | **exit 0** | | `.claude` undeclared | spec | `.claude/launch.json` | red | **exit 1**, names the file | | `.claude` declared | spec | `.claude/skills/spec-property-retirement/SKILL.md` | green | **exit 0** | | `.github` pending | plugin-auth | `.github/workflows/ci.yml` | green | **exit 0** | | `.github` promoted | plugin-auth | same, `.github` temporarily in the list | red | **exit 1**, names all three | The last pair is the honest reading for the deferred case: the flat collector stays green on `.github` **because the entry is pending**, not because it cannot see — promoting the entry reds it immediately. **Self-test:** 229 cases before, **255** after — exactly the +26 the new battery pins. All 14 batteries registered, the 12 pre-existing ones intact (13 were declared before; floor 12 to 13). ## Changeset `skip-changeset`, measured rather than assumed: no package's `files[]` ships anything under `scripts/` (checked across every `packages/**`, `apps/*` and `examples/*` manifest, with a positive control reading `packages/spec`'s real `files[]` back). Nothing published moves. ## Acceptance notes — noted, not filed - `packages/drivers/driver-sql/src/live-dialect-matrix.budget.test.ts:107` genuinely reads `join(repoRoot(), '.github/workflows/ci.yml')`, and this gate cannot see that file **at all**: its `repoRoot()` (line 38) is a hand-rolled `for(;;)` walk rather than one of the recognised `findUp` seed spellings, so the file yields no escape and contributes nothing. That read is undeclared today. It is a **different mechanism** from this card (seed recognition, not the top-level whitelist) and is reported to the dispatching seat rather than fixed here. Refs: objectstack-ai#18340, objectstack-ai#15565, and the `skills/` precedent recorded in this gate's own header note. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ Co-authored-by: Claude <noreply@anthropic.com>
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 #15565
Clause-②: no
Reason: no spec surface is narrowed or widened here. The diff is one gate script under
scripts/;packages/specis read by the gate, never edited.What this adds
A sixth limb to
check:cross-package-test-inputs, judged on the SEED a directory descent is rooted at rather than on the names the descent produces.The five existing limbs are package-scoped or name-scoped, and a
readdirSyncdescending on a loop variable is neither: it names no literal (limb 3 blind), adds no glob (limbs 4 and 5 blind), and inside a package that already carries a table entry the two package-scoped limbs are already satisfied. So the tree-scoped absence pin the ADR-0049 retirement playbook mandates could land inpackages/specwith its whole radius unhashed while this gate exited 0 naming nothing.The input was already there: the escape verdict is computed from the seed's DEPTH, and an argument this scan cannot read costs the NAME and keeps the DEPTH.
The narrowed mandate, in the source
Triage ruled 「先收窄 mandate,再断言收窄后的半径」 and the maintainer's batch ruling returned the card to the queue on the grounds that it restores an invariant with no product-semantics fork. The narrowed mandate is written into the gate's own header docblock (not only here), and it is what the limb asserts:
Stated at the grain the limb judges at: declaring one FILE under
docs/audits/reaches INTOdocs/and no further, so a walk rooted atdocs/is judged by its children. The header records both bounds it accepts (directory REACH rather than file coverage; dot-directories skipped as the pins skip them).Measurements — every one paired, none recalled
Baseline re-taken on
objectstack-ai/objectstackata09725dde(today'smain), because the card's readings were8e18931dd. Each limb was fired by one anchored mutation, the gate's exit code captured to disk before anything read it, and each file restored by blob equality against HEAD plus an emptygit diff HEAD.@objectstack/metadata-protocolentryscripts/js-comment-mask.mjsto a testturbo.jsonturbo.jsonmissing input$TURBO_ROOT$inputThat last row is the card's claim, re-taken rather than recalled. The same mutation on this branch:
docs) added to the live spec: the rest of the incident-response, training and change-management families — every remaining key and all fifteen defs — has zero readers; whole-def enforce-or-remove is the open question left after #14477 #15513 pin's folded array and nothing else: exit 1, naming the sevendocs/subtrees no glob reaches, and NOTdocs/audits.docs/**on the entry and inturbo.json: exit 0, and the printed walked-root count moves 13 to 14. The green is a root that was JUDGED, not one that stopped resolving.Ablation discipline: no
sed -i/perl -i. Every mutation went through an anchored replacement that exits non-zero when the anchor is absent or not unique, printing the occurrence-count delta and the byte-length delta; every restore is proved bygit hash-objectequality with the HEAD blob and an emptygit diff HEAD, never by an exit code; every driver carriestrap … EXIT INT TERM.What the limb accepts today, and why it is not the allowance the ruling refused
ACCEPTED_WALK_RADIIrecords THREE pre-existing census and corpus sweeps in other packages — none of them a retirement pin, each already carrying its own written bound in its test's header. The MANDATED shape is enforced from the first run: the live pin's five roots are all reached by@objectstack/spec's globs, and a new pin whose root they do not reach reds. Option C — a dated allowance for the mandated shape — is what triage refused, and is not what this is. Buying the three radius widenings instead would be three cost decisions nobody has ruled on, in three packages outside this card.A defect this branch found in its own first draft
The ledger originally spelled three test paths and one nested root whole, and named the bare recursive packages glob in backticks inside a reason string.
scripts/pm/dispatch-gates.mjsreads every quoted span in a gate's MODULE BODY as a path that gate watches — a backticked span INSIDE a quoted sentence included — so those four literals put five watch hints on this gate that it does not read, and two of them turned pins red incheck:pm-dispatch-gates. Rows are now segments joined at runtime and reasons name globs in words. Measured both sides: this gate's hint population is 140 with 0 declared here, onmainand on this branch alike.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 33 runnable commands for this path. All 33 run green. The--ranreconciliation and the NOT-MEASURED families CI owns are listed in the dispatch report.No changeset: nothing published moves. Measured rather than assumed — 70 manifests carry a
files[], none names anything under the repo-rootscripts/directory, and the positive control is that all 70 namedist.Acceptance notes
repoRelativeLiterals()'s top-level alternation ispackages|apps|examples|content|scripts|skills— it has nodocs, whiledocs/...IS a declarable glob (@objectstack/specdeclares one audit file there). So a test that quotes a wholedocs/...path is invisible to the flat collector, the same shape as the missingskills/the gate's own header records. Measured with a lit control: thedocs/literal collects nothing, thescripts/literal collects. Filed separately rather than widened here — it is a different limb's data set.symlinkSyncis absent fromNON_DESCENT_CALLEESwhile its siblingscpSync/renameSync/rmSyncare present, so a symlinked directory reads as a walk root. On this tree that isscriptsfor@objectstack/spec, already declared and arguably a real input (the test runs a build through that symlink). Noted, not filed: no PR and no author is heading for this file.Authored by the
domain:devxexecutor seat in sessionsession_017ef78bLdybu3AffehKkhfk(https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk) — recorded in prose because this body was edited after creation, and the edit channel appends its own footer block.Generated by Claude Code