Filed out of scope from #15079. Found while running that card's derived gate union: two gates that are each green in isolation, where running one first reds the other.
Reproduce
In a clean checkout, in this order:
pnpm --filter @objectstack/spec run check:skill-examples # compiles marked examples
pnpm check:docs-audit-scope # now FAILS
✗ self-test "every contract declaration admitted is a packages/spec API declaration":
kind=contract → expected scanRouteSurface=true, got false
✗ affected-docs self-test failed (1 case(s)).
rm -rf packages/spec/.examples-build and re-running check:docs-audit-scope is green again — measured, both directions.
Why
check:skill-examples writes the marked doc/skill examples out as compilable TypeScript under packages/spec/.examples-build/ (gitignored, .gitignore:68). scripts/docs-audit/affected-docs.mjs's scanRouteSurface() walks source files on the FILESYSTEM, so those emitted files enter the walk, and five of them declare a path: beside an HTTP method and are therefore admitted as kind-contract route sources:
packages/spec/.examples-build/docs__api_declarative-endpoints__1.ts
packages/spec/.examples-build/docs__api_declarative-endpoints__2.ts
packages/spec/.examples-build/docs__getting-started_quick-reference__2.ts
packages/spec/.examples-build/docs__protocol_kernel_http-protocol__1.ts
packages/spec/.examples-build/skills__objectstack-api_SKILL__1.ts
The self-test's pin — liveKind('contract').every((f) => f.startsWith('packages/spec/src/api/')) — is exactly the shape assertion those five violate. The pin is correct; the walk's population is what includes a scratch directory.
Status on CI today, stated rather than assumed
Green, by step ordering only: in .github/workflows/lint.yml, pnpm check:docs-audit-scope runs at line 1975 and pnpm --filter @objectstack/spec run check:skill-examples at line 5227, so the scratch directory does not exist yet when the scope gate runs. That is a latent coupling, not a design: reordering the steps, splitting them across jobs differently, or any local run in the natural order (gate union alphabetical, or "run the docs gates after the spec gates") reds a gate that has nothing wrong with it. The cost lands on whoever is holding an unrelated diff at the time — it did here.
Candidate fixes (a lane call, not asserted)
- Exclude
**/.examples-build/** from the scanRouteSurface / source walk in scripts/docs-audit/affected-docs.mjs, beside the existing isTestFile exclusions. Narrowest, and the walk already has the notion of "not a real source input".
- Or have
check-skill-examples.ts emit into a path outside the repo tree (or clean up after itself).
The first is preferable: it makes the walk's population a stated property of the walk rather than a property of which other gates have run.
Refs
scripts/docs-audit/affected-docs.mjs (the walk and the self-test pin) · packages/spec/scripts/check-skill-examples.ts (the emitter) · .gitignore:68 · found from #15079
Filed out of scope from #15079. Found while running that card's derived gate union: two gates that are each green in isolation, where running one first reds the other.
Reproduce
In a clean checkout, in this order:
rm -rf packages/spec/.examples-buildand re-runningcheck:docs-audit-scopeis green again — measured, both directions.Why
check:skill-exampleswrites the marked doc/skill examples out as compilable TypeScript underpackages/spec/.examples-build/(gitignored,.gitignore:68).scripts/docs-audit/affected-docs.mjs'sscanRouteSurface()walks source files on the FILESYSTEM, so those emitted files enter the walk, and five of them declare apath:beside an HTTP method and are therefore admitted as kind-contractroute sources:The self-test's pin —
liveKind('contract').every((f) => f.startsWith('packages/spec/src/api/'))— is exactly the shape assertion those five violate. The pin is correct; the walk's population is what includes a scratch directory.Status on CI today, stated rather than assumed
Green, by step ordering only: in
.github/workflows/lint.yml,pnpm check:docs-audit-scoperuns at line 1975 andpnpm --filter @objectstack/spec run check:skill-examplesat line 5227, so the scratch directory does not exist yet when the scope gate runs. That is a latent coupling, not a design: reordering the steps, splitting them across jobs differently, or any local run in the natural order (gate union alphabetical, or "run the docs gates after the spec gates") reds a gate that has nothing wrong with it. The cost lands on whoever is holding an unrelated diff at the time — it did here.Candidate fixes (a lane call, not asserted)
**/.examples-build/**from thescanRouteSurface/ source walk inscripts/docs-audit/affected-docs.mjs, beside the existingisTestFileexclusions. Narrowest, and the walk already has the notion of "not a real source input".check-skill-examples.tsemit into a path outside the repo tree (or clean up after itself).The first is preferable: it makes the walk's population a stated property of the walk rather than a property of which other gates have run.
Refs
scripts/docs-audit/affected-docs.mjs(the walk and the self-test pin) ·packages/spec/scripts/check-skill-examples.ts(the emitter) ·.gitignore:68· found from #15079