Skip to content

test(spec): repo-radius retirement walks read what git does not ignore, so a docs-built checkout no longer runs them red - #23507

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-23156-tree-walk-gitignored-upgrade-guide
Oct 14, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-23156-tree-walk-gitignored-upgrade-guide

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23156

Clause-②: no

What this changes

Four @objectstack/spec repo-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, gitignored content/docs/protocol-upgrade/ pages that gen:upgrade-guide writes. 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's SKIPPED_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.ts
  • packages/spec/src/system/compliance-families-retirement.test.ts
  • packages/spec/src/integration/connector-connection-timeout-retirement.test.ts
  • packages/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 in packages/spec/vitest.repo-tests.json. It reads the real checkout, so check:cross-package-test-inputs requires 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/src returns 28 files.
  • 27 of them walk a root that contains content/docs/protocol-upgrade/: 26 walk content, and the currency pin walks content/docs.
  • The one that does not is kernel/metadata-type-registry-allow-org-override-absence, which walks packages and examples only.
  • None of the 28 names the directory, so all 27 read it today.
  • With the generated pages on disk, 4 of 28 go red. These are the card's four, and each names only 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.

  1. A tracked fixture is not possible under content/docs/protocol-upgrade/. Both checks were measured at cb9841aa2:

    • With a fixture force-added there, node scripts/git-merge-regen.mjs --self-test (part of check:merge-driver) exits 1: "content/docs/protocol-upgrade/** — declared untracked, but git tracks 1 file(s)". scripts/regen-artifacts.mjs registers the tree as untracked.
    • With a file the generator did not write present there, gen:upgrade-guide exits 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.

  2. 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 reach packages/spec/. A protocol-upgrade name skip would not cover it. The git reading covers both outputs, plus any future gitignored generator output.

  3. The walk now equals CI's view and turbo's input hash. .gitignore is the one register of "generated, not authored", and turbo already hashes inputs through git. Tracked files are listed whatever .gitignore says, and a page not yet git added is still judged.

  4. Cost. One git ls-files per walked root, about 30 ms over the five-root radius. On a built checkout at cb9841aa2, 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 .git file 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 with gitFreeEnv().

The helper takes one directory, and each walk keeps its for (const root of WALK_ROOTS) loop over path.join(REPO_ROOT, root). That is the spelling check:cross-package-test-inputs folds into the walked roots it checks against the declared globs. A first draft that passed REPO_ROOT read 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 at content/docs/protocol-upgrade/18.md inside a throwaway git repository whose .gitignore carries the real repository's two lines for the guide's outputs. This follows the sandbox shape of scripts/check-role-word.mjs's skipped-subtree battery. The pin asserts the exact listing:

  • The fixture is not listed, and neither is the same page at packages/spec/protocol-upgrade-guide.md. A positive control first proves both files are on disk.
  • The same page is listed where git keeps it (content/docs/guides/upgrade.md, packages/spec/README.md).
  • A sibling content/docs/protocol-upgrade-draft/ (untracked, not ignored) is listed. The exclusion is a path, never a string prefix.
  • A force-added file under an ignored directory is listed, because tracked wins over .gitignore, as in CI.
  • A directory the caller skips by name is not listed, and a tracked file deleted from the work tree is not listed.
  • The real checkout ignores both generated paths (git check-ignore, with a tracked page as the control). On a docs-built checkout, nothing listed under content/ lies in the generated tree.

The retired key is assembled at runtime in the pin, because the connector walk reads every .ts under packages/.

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 names content/docs/protocol-upgrade/18.md: export-job , CreateExportJobResult,, compliance , ChangePrioritySchema,, connector .connectionTimeoutMs, currency rule B 18.md:24.

  • Single-file copy only (cb9841aa2): 3 failed | 25 passed, each naming packages/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 821bdf262 and again at the merge head 7310df0d3 with 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, at 821bdf262 and at 7310df0d3: exit 0 both times. This covers tsc, check:scripts-typecheck and check:test-typecheck.

  • Ablation, one, on the ignore (821bdf262): node scripts/ablation-replace.mjs dropped '--exclude-standard' from the listing. The anchor went 1 to 0 and the blob changed from 7e3d5ac986bd to 7d27aa5a9e14. The run ended 6 failed | 36 passed, across 5 of 5 files:

    • in the pin, the sandbox listing gains exactly content/docs/protocol-upgrade/18.md and packages/spec/protocol-upgrade-guide.md, and the real-checkout case lists the four generated pages;
    • all four walks go red on both generated outputs.

    The restore was proven: the blob equals HEAD (7e3d5ac986bd) and git diff HEAD is empty. The helper's blob is the same at 7310df0d3.

Gates (local, at the PR head 7310df0d3)

The head merges origin/main 394f65e89; the merge was clean and touched none of this PR's files.

  • node scripts/pm/dispatch-gates.mjs --commands derived 78 commands. The PM's lead at cb9841aa2 had 77; the extra one is pnpm check:pm-governed-merges. All 78 were run after a turbo build of the workspace minus docs and console, and all 78 exited 0. --ran reports: "78 derived famil(ies) accounted for — 78 run, 0 NOT-MEASURED". That includes check:api-surface ("public API surface + factory signatures unchanged") and check:cross-package-test-inputs (OK, 13 walked roots judged).
  • The local-only pnpm check:dev-prereqs: exit 0.
  • CI families: this PR's check-runs.

Changeset

None. Nothing in the diff ships. @objectstack/spec's files[] carries src/**/*.zod.ts and no *.test.ts, scripts/ or vitest.repo-tests.json. After the rebuild, listRepoWalkFiles appears in 0 files under dist/; the control retiredKey appears in 68. No .changeset/*.md is 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-retirement
    • api/rest-api-config-dead-keys-retirement
    • contracts/record-share-guest-recipient-census
    • data/cube-member-inner-name-retirement, data/cube-refresh-key-retirement, data/document-schemas-retirement, data/object-external-sharing-model-retirement
    • integration/connector-resilience-keys-retirement, integration/connector-sync-retirement, integration/connector-triggers-retirement
    • qa/requires-plugins-retirement
    • security/rls-tags-retirement
    • system/email-template-category-is-system-retirement
    • ui/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-retirement

    Each 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.json and tsup *.bundled_*.mjs exclusions 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 connector json-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-inputs requires an escaping test to be listed in the repo project, so the pin's registration comes with it.


Generated by Claude Code

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>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/vitest.repo-tests.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/vitest.repo-tests.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 142 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f9b421716dd79df6c7c3a34bbd5a3a9f425e84f4 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:spec seat 1 (#6017) · huangyiirene · session session_01YRp5z4kK6vgYkrdH97ztsy · head 7310df0d3 · 2026-10-14T15:10Z

Review owed: none beyond this record. The PR touches no contract face: test files and a test-only helper only, Clause-②: no. CI and this seat's own read clear it.

Card #23156; claim 6168033188; dev report on #23156. Scope comes from triage's reopen 6167924044, on the maintainer's 「影响车队开发速度的问题也是问题」.

1. Shape

  • Draft, base main. Line 1 is Fixes #23156; line 3 is Clause-②: no. No other closing keyword.
  • 7 files, +370 / -63. check-governed-merges --pr 23507 reads NOT governed (433 lines).
  • Labelled skip-changeset. Check Changeset read the live label and passed. The diff publishes nothing.

2. Premises, measured by the dev

  • H1. 28 files carry SKIPPED_DIRS. 27 walk a root that contains content/docs/protocol-upgrade/, and all 27 read it today. With the docs build's output on disk, the card's 4 go red. The other 23 stay green only because their matchers do not fire on today's pages.
  • H2. The route is the git-visible listing. listRepoWalkFiles runs git ls-files -z --cached --others --exclude-standard per walked directory, keeps each walk's SKIPPED_DIRS rule, and throws on a missing root or a git failure. A name skip was measured insufficient: the gitignored single-file packages/spec/protocol-upgrade-guide.md turns 3 of the 4 red on its own.
  • Red, then green.
    • At cb9841aa2, with the generator's output on disk: 4 failed / 24 passed, each red naming 18.md.
    • After the fix, with both outputs on disk: 29 of 29 files, 440 tests.
    • The full spec repo project passes, 61 / 1033, at 821bdf262 and again at 7310df0d3.

3. Gates

  • CI check-runs on 7310df0d3 at this act: 20 success, 10 skipped, 12 in progress, 0 failures. The flip waits for green.
  • Local: --ran covers 78 of 78 families, all exit 0 after a workspace build. check:dev-prereqs exits 0.
  • Ablation: dropping --exclude-standard sent all five files red (6 failed). The restore was verified.

4. Deviations, accepted

  • H2's tracked fixture is infeasible, as measured. A tracked file under content/docs/protocol-upgrade/ turns check:merge-driver red, and gen:upgrade-guide refuses a file it did not write. Instead, the fixture content/docs/protocol-upgrade/18.md, naming the retired connector.connectionTimeoutMs, lives in a throwaway git repo carrying the real .gitignore lines, beside controls. A real-checkout git check-ignore leg pins both generated paths as ignored. This keeps the card's intent: a retired key under that directory cannot turn the walks red.
  • packages/spec/vitest.repo-tests.json gains one line, registering the pin in the repo project, as check:cross-package-test-inputs requires.
  • The helper takes one directory, not (repoRoot, roots), so that check:cross-package-test-inputs reads the walk roots as it folds them.
  • main was merged once, before the PR opened.
  • The commit trailers are model-free, per AGENTS.md.

5. Out of scope
The same latent class sits in the other 23 repo-radius walks, named in the PR's Acceptance notes. None is red on today's generated pages; a future major's page, or a new walk copied from one of them, could turn one red. The fix for each is to adopt listRepoWalkFiles. They are dormant today, with no measured red, so this is an Acceptance note (承接者:无). The seat's round report names it for the next adopter.

6. Landing: Tier S through the ordinary queue, once the head is all green.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 14, 2026 15:37
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit 96a7f43 Oct 14, 2026
46 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23156-tree-walk-gitignored-upgrade-guide branch October 14, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

protocol:data protocol:system size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests tooling

Projects

None yet

2 participants