Repository navigation
test(spec): repo-radius retirement walks read what git does not ignore, so a docs-built checkout no longer runs them red - #23507
Conversation
The four tree-scoped absence walks (export-job family, compliance families, connector connectionTimeoutMs, currency-mode closure rule B) walked the raw filesystem, so a checkout that had built the docs read the generated, gitignored content/docs/protocol-upgrade/ pages and went red on correct sources; the publish lane's gitignored packages/spec/protocol-upgrade-guide.md reddened three of them the same way. They now take their file list from scripts/lib/repo-walk-testkit.ts: `git ls-files --cached --others --exclude-standard` per root, narrowed by each walk's unchanged skipDir rule, loud on a missing root or a git failure. The pin builds a throwaway repository carrying the real .gitignore lines, with a page naming a retired key under content/docs/protocol-upgrade/ (not listed) beside the same page where git keeps it (listed), and checks the real checkout ignores both generated outputs. Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
The connector walk reads every .ts under packages/, so the pin's own comment spelling the member read made the pin its offender. Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
…olds its roots check:cross-package-test-inputs reads a walk's roots from the first argument of the descent call. listRepoWalkFiles(REPO_ROOT, WALK_ROOTS) read as a walk from the repo root and went red; each walk now keeps its for-root-of-WALK_ROOTS loop over path.join(REPO_ROOT, root), and the helper lists one directory with git's cwd there, returning absolute paths. Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
…e-guide Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check
What this run could not see
Coarse fallback — 142 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
席位核对ACCEPT · Review owed: none beyond this record. The PR touches no contract face: test files and a test-only helper only, Card #23156; claim 1. Shape
2. Premises, measured by the dev
3. Gates
4. Deviations, accepted
5. Out of scope 6. Landing: Tier S through the ordinary queue, once the head is all green. Generated by Claude Code |
Fixes #23156
Clause-②: no
What this changes
Four
@objectstack/specrepo-project tree walks read the raw filesystem. On a checkout that had built the docs, they went red on correct sources, because they read the generated, gitignoredcontent/docs/protocol-upgrade/pages thatgen:upgrade-guidewrites. Those pages name retired keys and defs, because that is their job.The four walks now take their file list from one shared test-only helper,
packages/spec/scripts/lib/repo-walk-testkit.ts(listRepoWalkFiles). The helper lists what git does not ignore:git ls-files -z --cached --others --exclude-standard, run in each walked directory. Each walk'sSKIPPED_DIRS/ dot-directory rule still applies below the root, so the change only narrows the set and never widens it.packages/spec/src/api/export-job-family-retirement.test.tspackages/spec/src/system/compliance-families-retirement.test.tspackages/spec/src/integration/connector-connection-timeout-retirement.test.tspackages/spec/src/data/currency-mode-family-closure.pin.test.ts(the corpus for rules A and B)The pin is
packages/spec/scripts/lib/repo-walk-testkit.test.ts, registered inpackages/spec/vitest.repo-tests.json. It reads the real checkout, socheck:cross-package-test-inputsrequires that registration.No product code, no docs generator, no
.gitignore.H1: which walks read the directory (measured at
cb9841aa2)git grep -l SKIPPED_DIRS packages/spec/srcreturns 28 files.content/docs/protocol-upgrade/: 26 walkcontent, and the currency pin walkscontent/docs.kernel/metadata-type-registry-allow-org-override-absence, which walkspackagesandexamplesonly.content/docs/protocol-upgrade/18.md. The other 23 stay green, because their matchers do not fire on the current page content. They have the same latent defect. Each is a separate walk closure, not a one-line fold, so they are left for the seat (see Acceptance notes).H2: the route, and why
Route: read what git does not ignore (tracked, plus untracked-not-ignored), not a skip by directory name.
A tracked fixture is not possible under
content/docs/protocol-upgrade/. Both checks were measured atcb9841aa2:node scripts/git-merge-regen.mjs --self-test(part ofcheck:merge-driver) exits 1: "content/docs/protocol-upgrade/** — declared untracked, but git tracks 1 file(s)".scripts/regen-artifacts.mjsregisters the tree as untracked.gen:upgrade-guideexits 1: "holds files this generator did not write (fixture.md); refusing to replace it". That breaks the docs build.So the fixture is necessarily gitignored and untracked, and both routes leave such a fixture green. The H2 premise, that the fixture is tracked and so only a name skip can leave it green, does not hold.
A name skip leaves a sibling red. The publish lane's single-file copy,
packages/spec/protocol-upgrade-guide.md, is gitignored too. Generated locally (build-upgrade-guide.ts --out), it turns 3 of the same 4 red: export-job, compliance and connector. The currency pin's roots do not reachpackages/spec/. Aprotocol-upgradename skip would not cover it. The git reading covers both outputs, plus any future gitignored generator output.The walk now equals CI's view and turbo's input hash.
.gitignoreis the one register of "generated, not authored", and turbo already hashes inputs through git. Tracked files are listed whatever.gitignoresays, and a page not yetgit added is still judged.Cost. One
git ls-filesper walked root, about 30 ms over the five-root radius. On a built checkout atcb9841aa2, the five-root radius gained 0 files and lost 1,537, every one of them ignored (packages/spec/json-schema/and the generated migration registry).The helper is loud and never returns empty. A missing root, a root that is a file, or a git failure (for example, a
.gitfile pointing nowhere, exit 128) throws. A path git lists that is gone from disk is left out, and any other stat fault is re-raised. The git child runs withgitFreeEnv().The helper takes one directory, and each walk keeps its
for (const root of WALK_ROOTS)loop overpath.join(REPO_ROOT, root). That is the spellingcheck:cross-package-test-inputsfolds into the walked roots it checks against the declared globs. A first draft that passedREPO_ROOTread as a walk from the repo root and turned that gate red.The fixture and its pin
The fixture is a page naming the retired key
connector.connectionTimeoutMs. It is an excerpt of the generated protocol-18 line that turned the connector walk red. It sits atcontent/docs/protocol-upgrade/18.mdinside a throwaway git repository whose.gitignorecarries the real repository's two lines for the guide's outputs. This follows the sandbox shape ofscripts/check-role-word.mjs's skipped-subtree battery. The pin asserts the exact listing:packages/spec/protocol-upgrade-guide.md. A positive control first proves both files are on disk.content/docs/guides/upgrade.md,packages/spec/README.md).content/docs/protocol-upgrade-draft/(untracked, not ignored) is listed. The exclusion is a path, never a string prefix..gitignore, as in CI.git check-ignore, with a tracked page as the control). On a docs-built checkout, nothing listed undercontent/lies in the generated tree.The retired key is assembled at runtime in the pin, because the connector walk reads every
.tsunderpackages/.Evidence
All runs below went through
scripts/pm/os-verify-lock.sh.Red first (
cb9841aa2, generated pages on disk): 28 walk files,4 failed | 24 passed. Each red namescontent/docs/protocol-upgrade/18.md: export-job, CreateExportJobResult,, compliance, ChangePrioritySchema,, connector.connectionTimeoutMs, currency rule B18.md:24.Single-file copy only (
cb9841aa2):3 failed | 25 passed, each namingpackages/spec/protocol-upgrade-guide.md.Green after (
821bdf262, both generated outputs on disk): the 28 walk files plus the pin,29 passed (29),440 passed (440).Full spec repo project, run at
821bdf262and again at the merge head7310df0d3with both outputs regenerated on disk:61 passed (61),1033 passed (1033)both times.Spec local project: it contains none of the changed files. All five changed test files are listed in
vitest.repo-tests.json, and only they import the helper.pnpm --filter @objectstack/spec typecheck, at821bdf262and at7310df0d3: exit 0 both times. This coverstsc,check:scripts-typecheckandcheck:test-typecheck.Ablation, one, on the ignore (
821bdf262):node scripts/ablation-replace.mjsdropped'--exclude-standard'from the listing. The anchor went 1 to 0 and the blob changed from7e3d5ac986bdto7d27aa5a9e14. The run ended6 failed | 36 passed, across 5 of 5 files:content/docs/protocol-upgrade/18.mdandpackages/spec/protocol-upgrade-guide.md, and the real-checkout case lists the four generated pages;The restore was proven: the blob equals HEAD (
7e3d5ac986bd) andgit diff HEADis empty. The helper's blob is the same at7310df0d3.Gates (local, at the PR head
7310df0d3)The head merges
origin/main394f65e89; the merge was clean and touched none of this PR's files.node scripts/pm/dispatch-gates.mjs --commandsderived 78 commands. The PM's lead atcb9841aa2had 77; the extra one ispnpm check:pm-governed-merges. All 78 were run after a turbo build of the workspace minus docs and console, and all 78 exited 0.--ranreports: "78 derived famil(ies) accounted for — 78 run, 0 NOT-MEASURED". That includescheck:api-surface("public API surface + factory signatures unchanged") andcheck:cross-package-test-inputs(OK, 13 walked roots judged).pnpm check:dev-prereqs: exit 0.Changeset
None. Nothing in the diff ships.
@objectstack/spec'sfiles[]carriessrc/**/*.zod.tsand no*.test.ts,scripts/orvitest.repo-tests.json. After the rebuild,listRepoWalkFilesappears in 0 files underdist/; the controlretiredKeyappears in 68. No.changeset/*.mdis touched. Label:skip-changeset.Acceptance notes
Latent, same class, not folded in (outside this card's file surface). These 23 repo-radius walks read the gitignored guide today. They stay green only because the current pages do not match their matchers. A future major's page could turn any of them red, and so could a new walk copied from them:
ai/agent-lifecycle-retirement,ai/agent-memory-store-retirementapi/rest-api-config-dead-keys-retirementcontracts/record-share-guest-recipient-censusdata/cube-member-inner-name-retirement,data/cube-refresh-key-retirement,data/document-schemas-retirement,data/object-external-sharing-model-retirementintegration/connector-resilience-keys-retirement,integration/connector-sync-retirement,integration/connector-triggers-retirementqa/requires-plugins-retirementsecurity/rls-tags-retirementsystem/email-template-category-is-system-retirementui/element-flat-binding-retirement,ui/element-text-variant-heading-retirement,ui/form-field-public-picker-retirement,ui/master-detail-detail-sort-field-retirement,ui/object-grid-resizable-columns-retirement,ui/page-header-breadcrumb-retirement,ui/view-data-api-provider-retirement,ui/view-item-owner-hidden-retirement,ui/view-list-tabs-retirementEach fix is the same move: replace the walk closure with
listRepoWalkFiles. Carrier: none.Redundant exclusions, kept on purpose. In the four walks, the
json-schema/,spec-changes.jsonand tsup*.bundled_*.mjsexclusions are now unreachable through the git listing. They are kept, and the two comments that described the walk as a filesystem walk were corrected. The connectorjson-schema/entry's reason is the generated content, not the ignore. The compliance tsup guard keeps its own narrowness test.File surface.
packages/spec/vitest.repo-tests.json(+1 line) is outside the claim's named files.check:cross-package-test-inputsrequires an escaping test to be listed in the repo project, so the pin's registration comes with it.Generated by Claude Code