Declare the populations four under-matching gates really read, and refuse a dead declaration - #14188

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare
Sep 1, 2026
Merged

Declare the populations four under-matching gates really read, and refuse a dead declaration#14188
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13519

Four of the seven gates in the census declared a population that under-matched
what they actually read. Each is repaired where the read lives — a literal
population declaration in the gate's own module body, held against the very
constant that gate reads from — and the class is closed by giving a verdict to
a fact the derivation already computes and prints as prose.

scripts/pm/dispatch-gates.mjs is not touched. No gate name was added to
any derivation, table or roster.

⭐ The lead gate: matched population 0 → 29, with the command

check:stall-guard-headroom resolves to scripts/measure-stall-guard-headroom.mjs
(not check-stall-guard-*.mjs — re-derived from package.json, as the card
warns). Its only path-shaped literal was DEFAULT_REPO — the repo slug
objectstack-ai/objectstack — which names no tracked file.

node --input-type=module -e "
import * as G from './scripts/pm/dispatch-gates.mjs';
const files = G.trackedFiles(); const tree = G.watchHintTree(files);
const e = G.discoverFamilies({ tree }).byCheck.get('check:stall-guard-headroom');
const own = (e.hints||[]).filter(h => !e.hintOrigin?.has(h));
console.log('declared:', JSON.stringify(own));
console.log('literals reaching the tree:', own.filter(h => files.some(f => G.hintCovers(h,f))).length);
console.log('matched population:', files.filter(f => G.classifyEntry(e,[f]).verdict==='matched').length);
"
before (45b9051248)after
declared literals["objectstack-ai/objectstack"]["objectstack-ai/objectstack", ".github/workflows/**"]
literals reaching the tree01 (covering 29 workflow files)
matched population over 7757 tracked files1 (its own file, by identity)30

Why it declared nothing is the interesting half, and it is not a forgotten
string. The population is declared — in check-stall-guard-budget.mjs, which
this tool imports WORKFLOW_DIR and scan from. The import follow deliberately
refuses a target that is itself a discovered gate file, so nothing was
inherited. That refusal is right; what was missing is this end of it. A gate that
reads a population through a sibling gate has to declare that population
itself. The declaration is held against the imported WORKFLOW_DIR in the gate's
own --self-test, so a moved read reds there rather than going quiet in a brief.

Per-gate verdict — 4 fit the pattern, 3 need another mechanism

gatescriptverdictmatched population
check:stall-guard-headroomscripts/measure-stall-guard-headroom.mjsfits.github/workflows/**1 → 30
check:skill-docspackages/spec/scripts/build-skill-docs.tsfitsskills/*/SKILL.md + skills/README.md2 → 14
check:service-providersscripts/check-service-providers.mjsfits — three PARENT/*/package.json patterns2 → 55
check:turbo-task-graphscripts/check-turbo-task-graph.mjsfits — the member manifests3 → 82
check:nul-bytesscripts/check-nul-bytes.mjsneeds something else2 → 2
check-comment-mask-corpusscripts/check-comment-mask-corpus.mjsneeds something else2 → 2
check:refd-timer-probescripts/check-refd-timer-probe.mjsneeds something else2 → 2

Each was re-confirmed by reading the gate's own header on this tree, independent
of the mechanical classifier. None was already fixed.

Why the last three are refused rather than forced

All three read a repo-wide population and their headers say so:
check-nul-bytes scans git ls-filesplus untracked-not-ignored;
check-refd-timer-probe's readTree does the same; check-comment-mask-corpus
walks REPO_ROOT for every authored JS/TS file. A truthful declaration for any
of them is "every file", and the derivation's own header prices that direction:
"22 leads is the same as none." A gate on every card is a gate on no card.

They do not need a declaration; they need a repo-wide / always-runs channel
in the derivation, which is the derivation side and out of this card's fence.
Filed unassigned as #14189, with the per-gate evidence.

What check:skill-docs cost to decide — the tree had already refused the easy answer

The first draft declared skills/**, which is what the idiom's usual escape
looks like. scripts/pm/bare-root-worklist.mjs reddened on it, by name, and it
was right to: it carries the measured triage for this exact root and records this
gate's population as 12 of the 50 files tracked under it (24%), so a subtree
hint names the gate for 38 files it never opens — the REFUSE-WIDE trade this
repo prices as the costlier error.

What that recorded refusal turned on is worth reading closely, because it is a
claim about one spelling: "no single spelling of this idiom reaches both"
the 11 skill entrypoints, and the root README the generator WRITES, which sits
outside every skill directory. Two literals reach both and reach nothing else:
12 of 12, precise and complete. So the declaration stands and the row's
verdict is re-pointed SPELLABLE-UNDECLAREDDECLARED-NARROWER, which is the
value defined for a gate that took the escape at a strictly narrower population
and whose bare root stays uncovered. That is one of the two resolutions the
worklist's own self-test names for this contradiction.

⛔ Its neighbour row, check-skills-token-ratchet on the same root, is not
re-pointed: that gate walks the root recursively at 49 of 50, where the precise
spelling buys one file of discrimination, and its deferral is untouched.

What check:turbo-task-graph cost to decide

Its declaration block used to say the member manifests "stay undeclared on
purpose (the enumerator owns them and declares none)". workspace-enumerator.mjs
does declare none — but its header states why, and the reason points the other
way: "each gate keeps declaring its OWN population in its OWN module body […]
What is consolidated here is the PARSE, never the DECLARATION."
The +41725
(gate, file) pairs it measured and refused are the price of the subtree
claim, ~5400 files. The claim made here is the manifests only — 79 files, every
one of which readWorkspaceScripts really opens — and --self-test holds it
against the enumerator's live answer in both directions, so a twelfth glob in
pnpm-workspace.yaml reds in this gate.

Why this class cannot recur under another gate name

Adding four declarations protects four gates. It does not stop the next gate
shipping with a declaration that reaches nothing — and that is the shape the lead
had: the declaration and its absence print identically. A family whose
declared literals all reach zero tracked files scores an ordinary silence, byte
for byte the output of a gate that declared nothing, and no one reading the brief
can tell them apart.

The derivation already computes this fact. It prints it under --residue:

0 of the 199 declare a population that reaches NOTHING in the tree, swept over 7757 tracked file(s) […] A standing repo fact, not a verdict about your paths

scripts/check-declared-population-live.mjs gives that standing fact a verdict.
No new instrument, no allowlist, no ratchet, and no gate name in it.

The rule is per-family, not per-literal: a gate may legitimately spell a slug,
a sentinel path or an example inside a message beside a live declaration, and
only a whole declaration that reaches nothing is refused.

Proven able to red, by ablation (mutation and restore both confirmed on
disk — git hash-object against the HEAD blob, before and after):

$ git checkout 45b9051248 -- scripts/measure-stall-guard-headroom.mjs
$ grep -c ROOT_DIR_WATCH_HINTS scripts/measure-stall-guard-headroom.mjs # 0
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 73d8e93d… ≠ HEAD 18e5cd8f…
$ node scripts/check-declared-population-live.mjs # exit 1
✗ check:declared-population-live: 1 of 155 declaring famil(ies) declare a population that reaches NOTHING in this tree.
check:stall-guard-headroom
declares: objectstack-ai/objectstack
$ git checkout HEAD -- scripts/measure-stall-guard-headroom.mjs
$ git diff HEAD # empty
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 18e5cd8f… = HEAD blob
$ node scripts/check-declared-population-live.mjs # exit 0

The stronger rule was implemented and refused

"A gate that enumerates a directory must carry a watch-hint declaration" is the
rule that suggests itself first. Measured at 45b9051248: of 193 discovered gate
files, 114 enumerate a directory and 86 of those carry no declaration under any
spelling of the idiom
. A gate shipping 86 findings is an allowlist with a
verdict attached — and an allowlist of gate names is the repair this lane has
already ruled against three times. Most of the 86 are not defects either: they
declare their population as ordinary path literals, invisible to a scan for the
idiom. Refused, and recorded here rather than in a follow-up, because the refusal
is part of the argument for the rule that shipped.

The second dead declaration the new gate found

The sweep found exactly two families in the fleet. The other is
check:pm-clause2-carriers, whose only path-shaped literal was the
`owner/name` placeholder inside a refusal message. That gate reads no
file in the tree at all
— its whole input is the GitHub API — so it takes the
gate's own remedy (b): the example is respelled `owner`/`name` so it
is no longer read as a declaration, and the gate now carries a
dispatch-gates: no-path-population marker with its reason. The seven-member
repair set is untouched; this is the guard's own remedy applied to the one other
carrier the guard names, and without it the guard could not ship green.

Changeset

skip-changeset: nothing here is published from any package. The diff is gate
scripts, one workflow step, one root package.json script entry, and
packages/spec/scripts/build-skill-docs.tspackages/spec's files field
publishes dist, json-schema, liveness, prompts, llms.txt, README.md,
src/**/*.zod.ts, api-surface and spec-changes.json, and not scripts/.

Gates run — at 04b7f769, the final commit

The family was derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands
(59 commands, re-derived after the last commit and unchanged), plus
node scripts/check-ratchet-remedy-authority.mjs. All 60 pass, except four
that report PREREQUISITE NOT MET because this container's workspace is not built
check-dev-prereqs (exit 1, "the workspace is not built"),
check-test-completeness (exit 3, no saved turbo log — its own text says to
record this as NOT MEASURED), check:dual-build-cjs-loads (exit 3, no dist/)
and check:type-check-debt (exit 3, closure not built). Each exit code was
captured before any pipe. None is a finding.

bare-root-worklist --self-test was a genuine red on the first draft and is
green here — see the check:skill-docs section above for what it caught.

Also run, beyond the derived family: pnpm lint over the whole repo (exit 0, no
narrowing needed), pnpm --filter @objectstack/spec exec tsc --noEmit (exit 0)
and — because --listFiles shows the edited .ts is not in that program —
tsc -p tsconfig.scripts.json --noEmit --listFiles, which does include it
(1 match) and is clean.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 da839ba11697703eb9391e36525fd5dd23242a63packageMentionDocs.

@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file tooling labels Sep 1, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 11:08
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 987fe37Sep 1, 2026
42 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-13519-gate-population-underdeclare branch September 1, 2026 11:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Declare the populations four under-matching gates really read, and refuse a dead declaration - #14188

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare
Sep 1, 2026
Merged

Declare the populations four under-matching gates really read, and refuse a dead declaration#14188
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13519

Four of the seven gates in the census declared a population that under-matched
what they actually read. Each is repaired where the read lives — a literal
population declaration in the gate's own module body, held against the very
constant that gate reads from — and the class is closed by giving a verdict to
a fact the derivation already computes and prints as prose.

scripts/pm/dispatch-gates.mjs is not touched. No gate name was added to
any derivation, table or roster.

⭐ The lead gate: matched population 0 → 29, with the command

check:stall-guard-headroom resolves to scripts/measure-stall-guard-headroom.mjs
(not check-stall-guard-*.mjs — re-derived from package.json, as the card
warns). Its only path-shaped literal was DEFAULT_REPO — the repo slug
objectstack-ai/objectstack — which names no tracked file.

node --input-type=module -e "
import * as G from './scripts/pm/dispatch-gates.mjs';
const files = G.trackedFiles(); const tree = G.watchHintTree(files);
const e = G.discoverFamilies({ tree }).byCheck.get('check:stall-guard-headroom');
const own = (e.hints||[]).filter(h => !e.hintOrigin?.has(h));
console.log('declared:', JSON.stringify(own));
console.log('literals reaching the tree:', own.filter(h => files.some(f => G.hintCovers(h,f))).length);
console.log('matched population:', files.filter(f => G.classifyEntry(e,[f]).verdict==='matched').length);
"
before (45b9051248)after
declared literals["objectstack-ai/objectstack"]["objectstack-ai/objectstack", ".github/workflows/**"]
literals reaching the tree01 (covering 29 workflow files)
matched population over 7757 tracked files1 (its own file, by identity)30

Why it declared nothing is the interesting half, and it is not a forgotten
string. The population is declared — in check-stall-guard-budget.mjs, which
this tool imports WORKFLOW_DIR and scan from. The import follow deliberately
refuses a target that is itself a discovered gate file, so nothing was
inherited. That refusal is right; what was missing is this end of it. A gate that
reads a population through a sibling gate has to declare that population
itself. The declaration is held against the imported WORKFLOW_DIR in the gate's
own --self-test, so a moved read reds there rather than going quiet in a brief.

Per-gate verdict — 4 fit the pattern, 3 need another mechanism

gatescriptverdictmatched population
check:stall-guard-headroomscripts/measure-stall-guard-headroom.mjsfits.github/workflows/**1 → 30
check:skill-docspackages/spec/scripts/build-skill-docs.tsfitsskills/*/SKILL.md + skills/README.md2 → 14
check:service-providersscripts/check-service-providers.mjsfits — three PARENT/*/package.json patterns2 → 55
check:turbo-task-graphscripts/check-turbo-task-graph.mjsfits — the member manifests3 → 82
check:nul-bytesscripts/check-nul-bytes.mjsneeds something else2 → 2
check-comment-mask-corpusscripts/check-comment-mask-corpus.mjsneeds something else2 → 2
check:refd-timer-probescripts/check-refd-timer-probe.mjsneeds something else2 → 2

Each was re-confirmed by reading the gate's own header on this tree, independent
of the mechanical classifier. None was already fixed.

Why the last three are refused rather than forced

All three read a repo-wide population and their headers say so:
check-nul-bytes scans git ls-filesplus untracked-not-ignored;
check-refd-timer-probe's readTree does the same; check-comment-mask-corpus
walks REPO_ROOT for every authored JS/TS file. A truthful declaration for any
of them is "every file", and the derivation's own header prices that direction:
"22 leads is the same as none." A gate on every card is a gate on no card.

They do not need a declaration; they need a repo-wide / always-runs channel
in the derivation, which is the derivation side and out of this card's fence.
Filed unassigned as #14189, with the per-gate evidence.

What check:skill-docs cost to decide — the tree had already refused the easy answer

The first draft declared skills/**, which is what the idiom's usual escape
looks like. scripts/pm/bare-root-worklist.mjs reddened on it, by name, and it
was right to: it carries the measured triage for this exact root and records this
gate's population as 12 of the 50 files tracked under it (24%), so a subtree
hint names the gate for 38 files it never opens — the REFUSE-WIDE trade this
repo prices as the costlier error.

What that recorded refusal turned on is worth reading closely, because it is a
claim about one spelling: "no single spelling of this idiom reaches both"
the 11 skill entrypoints, and the root README the generator WRITES, which sits
outside every skill directory. Two literals reach both and reach nothing else:
12 of 12, precise and complete. So the declaration stands and the row's
verdict is re-pointed SPELLABLE-UNDECLAREDDECLARED-NARROWER, which is the
value defined for a gate that took the escape at a strictly narrower population
and whose bare root stays uncovered. That is one of the two resolutions the
worklist's own self-test names for this contradiction.

⛔ Its neighbour row, check-skills-token-ratchet on the same root, is not
re-pointed: that gate walks the root recursively at 49 of 50, where the precise
spelling buys one file of discrimination, and its deferral is untouched.

What check:turbo-task-graph cost to decide

Its declaration block used to say the member manifests "stay undeclared on
purpose (the enumerator owns them and declares none)". workspace-enumerator.mjs
does declare none — but its header states why, and the reason points the other
way: "each gate keeps declaring its OWN population in its OWN module body […]
What is consolidated here is the PARSE, never the DECLARATION."
The +41725
(gate, file) pairs it measured and refused are the price of the subtree
claim, ~5400 files. The claim made here is the manifests only — 79 files, every
one of which readWorkspaceScripts really opens — and --self-test holds it
against the enumerator's live answer in both directions, so a twelfth glob in
pnpm-workspace.yaml reds in this gate.

Why this class cannot recur under another gate name

Adding four declarations protects four gates. It does not stop the next gate
shipping with a declaration that reaches nothing — and that is the shape the lead
had: the declaration and its absence print identically. A family whose
declared literals all reach zero tracked files scores an ordinary silence, byte
for byte the output of a gate that declared nothing, and no one reading the brief
can tell them apart.

The derivation already computes this fact. It prints it under --residue:

0 of the 199 declare a population that reaches NOTHING in the tree, swept over 7757 tracked file(s) […] A standing repo fact, not a verdict about your paths

scripts/check-declared-population-live.mjs gives that standing fact a verdict.
No new instrument, no allowlist, no ratchet, and no gate name in it.

The rule is per-family, not per-literal: a gate may legitimately spell a slug,
a sentinel path or an example inside a message beside a live declaration, and
only a whole declaration that reaches nothing is refused.

Proven able to red, by ablation (mutation and restore both confirmed on
disk — git hash-object against the HEAD blob, before and after):

$ git checkout 45b9051248 -- scripts/measure-stall-guard-headroom.mjs
$ grep -c ROOT_DIR_WATCH_HINTS scripts/measure-stall-guard-headroom.mjs # 0
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 73d8e93d… ≠ HEAD 18e5cd8f…
$ node scripts/check-declared-population-live.mjs # exit 1
✗ check:declared-population-live: 1 of 155 declaring famil(ies) declare a population that reaches NOTHING in this tree.
check:stall-guard-headroom
declares: objectstack-ai/objectstack
$ git checkout HEAD -- scripts/measure-stall-guard-headroom.mjs
$ git diff HEAD # empty
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 18e5cd8f… = HEAD blob
$ node scripts/check-declared-population-live.mjs # exit 0

The stronger rule was implemented and refused

"A gate that enumerates a directory must carry a watch-hint declaration" is the
rule that suggests itself first. Measured at 45b9051248: of 193 discovered gate
files, 114 enumerate a directory and 86 of those carry no declaration under any
spelling of the idiom
. A gate shipping 86 findings is an allowlist with a
verdict attached — and an allowlist of gate names is the repair this lane has
already ruled against three times. Most of the 86 are not defects either: they
declare their population as ordinary path literals, invisible to a scan for the
idiom. Refused, and recorded here rather than in a follow-up, because the refusal
is part of the argument for the rule that shipped.

The second dead declaration the new gate found

The sweep found exactly two families in the fleet. The other is
check:pm-clause2-carriers, whose only path-shaped literal was the
`owner/name` placeholder inside a refusal message. That gate reads no
file in the tree at all
— its whole input is the GitHub API — so it takes the
gate's own remedy (b): the example is respelled `owner`/`name` so it
is no longer read as a declaration, and the gate now carries a
dispatch-gates: no-path-population marker with its reason. The seven-member
repair set is untouched; this is the guard's own remedy applied to the one other
carrier the guard names, and without it the guard could not ship green.

Changeset

skip-changeset: nothing here is published from any package. The diff is gate
scripts, one workflow step, one root package.json script entry, and
packages/spec/scripts/build-skill-docs.tspackages/spec's files field
publishes dist, json-schema, liveness, prompts, llms.txt, README.md,
src/**/*.zod.ts, api-surface and spec-changes.json, and not scripts/.

Gates run — at 04b7f769, the final commit

The family was derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands
(59 commands, re-derived after the last commit and unchanged), plus
node scripts/check-ratchet-remedy-authority.mjs. All 60 pass, except four
that report PREREQUISITE NOT MET because this container's workspace is not built
check-dev-prereqs (exit 1, "the workspace is not built"),
check-test-completeness (exit 3, no saved turbo log — its own text says to
record this as NOT MEASURED), check:dual-build-cjs-loads (exit 3, no dist/)
and check:type-check-debt (exit 3, closure not built). Each exit code was
captured before any pipe. None is a finding.

bare-root-worklist --self-test was a genuine red on the first draft and is
green here — see the check:skill-docs section above for what it caught.

Also run, beyond the derived family: pnpm lint over the whole repo (exit 0, no
narrowing needed), pnpm --filter @objectstack/spec exec tsc --noEmit (exit 0)
and — because --listFiles shows the edited .ts is not in that program —
tsc -p tsconfig.scripts.json --noEmit --listFiles, which does include it
(1 match) and is clean.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 da839ba11697703eb9391e36525fd5dd23242a63packageMentionDocs.

@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file tooling labels Sep 1, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 11:08
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 987fe37Sep 1, 2026
42 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-13519-gate-population-underdeclare branch September 1, 2026 11:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Declare the populations four under-matching gates really read, and refuse a dead declaration - #14188

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare
Sep 1, 2026
Merged

Declare the populations four under-matching gates really read, and refuse a dead declaration#14188
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13519

Four of the seven gates in the census declared a population that under-matched
what they actually read. Each is repaired where the read lives — a literal
population declaration in the gate's own module body, held against the very
constant that gate reads from — and the class is closed by giving a verdict to
a fact the derivation already computes and prints as prose.

scripts/pm/dispatch-gates.mjs is not touched. No gate name was added to
any derivation, table or roster.

⭐ The lead gate: matched population 0 → 29, with the command

check:stall-guard-headroom resolves to scripts/measure-stall-guard-headroom.mjs
(not check-stall-guard-*.mjs — re-derived from package.json, as the card
warns). Its only path-shaped literal was DEFAULT_REPO — the repo slug
objectstack-ai/objectstack — which names no tracked file.

node --input-type=module -e "
import * as G from './scripts/pm/dispatch-gates.mjs';
const files = G.trackedFiles(); const tree = G.watchHintTree(files);
const e = G.discoverFamilies({ tree }).byCheck.get('check:stall-guard-headroom');
const own = (e.hints||[]).filter(h => !e.hintOrigin?.has(h));
console.log('declared:', JSON.stringify(own));
console.log('literals reaching the tree:', own.filter(h => files.some(f => G.hintCovers(h,f))).length);
console.log('matched population:', files.filter(f => G.classifyEntry(e,[f]).verdict==='matched').length);
"
before (45b9051248)after
declared literals["objectstack-ai/objectstack"]["objectstack-ai/objectstack", ".github/workflows/**"]
literals reaching the tree01 (covering 29 workflow files)
matched population over 7757 tracked files1 (its own file, by identity)30

Why it declared nothing is the interesting half, and it is not a forgotten
string. The population is declared — in check-stall-guard-budget.mjs, which
this tool imports WORKFLOW_DIR and scan from. The import follow deliberately
refuses a target that is itself a discovered gate file, so nothing was
inherited. That refusal is right; what was missing is this end of it. A gate that
reads a population through a sibling gate has to declare that population
itself. The declaration is held against the imported WORKFLOW_DIR in the gate's
own --self-test, so a moved read reds there rather than going quiet in a brief.

Per-gate verdict — 4 fit the pattern, 3 need another mechanism

gatescriptverdictmatched population
check:stall-guard-headroomscripts/measure-stall-guard-headroom.mjsfits.github/workflows/**1 → 30
check:skill-docspackages/spec/scripts/build-skill-docs.tsfitsskills/*/SKILL.md + skills/README.md2 → 14
check:service-providersscripts/check-service-providers.mjsfits — three PARENT/*/package.json patterns2 → 55
check:turbo-task-graphscripts/check-turbo-task-graph.mjsfits — the member manifests3 → 82
check:nul-bytesscripts/check-nul-bytes.mjsneeds something else2 → 2
check-comment-mask-corpusscripts/check-comment-mask-corpus.mjsneeds something else2 → 2
check:refd-timer-probescripts/check-refd-timer-probe.mjsneeds something else2 → 2

Each was re-confirmed by reading the gate's own header on this tree, independent
of the mechanical classifier. None was already fixed.

Why the last three are refused rather than forced

All three read a repo-wide population and their headers say so:
check-nul-bytes scans git ls-filesplus untracked-not-ignored;
check-refd-timer-probe's readTree does the same; check-comment-mask-corpus
walks REPO_ROOT for every authored JS/TS file. A truthful declaration for any
of them is "every file", and the derivation's own header prices that direction:
"22 leads is the same as none." A gate on every card is a gate on no card.

They do not need a declaration; they need a repo-wide / always-runs channel
in the derivation, which is the derivation side and out of this card's fence.
Filed unassigned as #14189, with the per-gate evidence.

What check:skill-docs cost to decide — the tree had already refused the easy answer

The first draft declared skills/**, which is what the idiom's usual escape
looks like. scripts/pm/bare-root-worklist.mjs reddened on it, by name, and it
was right to: it carries the measured triage for this exact root and records this
gate's population as 12 of the 50 files tracked under it (24%), so a subtree
hint names the gate for 38 files it never opens — the REFUSE-WIDE trade this
repo prices as the costlier error.

What that recorded refusal turned on is worth reading closely, because it is a
claim about one spelling: "no single spelling of this idiom reaches both"
the 11 skill entrypoints, and the root README the generator WRITES, which sits
outside every skill directory. Two literals reach both and reach nothing else:
12 of 12, precise and complete. So the declaration stands and the row's
verdict is re-pointed SPELLABLE-UNDECLAREDDECLARED-NARROWER, which is the
value defined for a gate that took the escape at a strictly narrower population
and whose bare root stays uncovered. That is one of the two resolutions the
worklist's own self-test names for this contradiction.

⛔ Its neighbour row, check-skills-token-ratchet on the same root, is not
re-pointed: that gate walks the root recursively at 49 of 50, where the precise
spelling buys one file of discrimination, and its deferral is untouched.

What check:turbo-task-graph cost to decide

Its declaration block used to say the member manifests "stay undeclared on
purpose (the enumerator owns them and declares none)". workspace-enumerator.mjs
does declare none — but its header states why, and the reason points the other
way: "each gate keeps declaring its OWN population in its OWN module body […]
What is consolidated here is the PARSE, never the DECLARATION."
The +41725
(gate, file) pairs it measured and refused are the price of the subtree
claim, ~5400 files. The claim made here is the manifests only — 79 files, every
one of which readWorkspaceScripts really opens — and --self-test holds it
against the enumerator's live answer in both directions, so a twelfth glob in
pnpm-workspace.yaml reds in this gate.

Why this class cannot recur under another gate name

Adding four declarations protects four gates. It does not stop the next gate
shipping with a declaration that reaches nothing — and that is the shape the lead
had: the declaration and its absence print identically. A family whose
declared literals all reach zero tracked files scores an ordinary silence, byte
for byte the output of a gate that declared nothing, and no one reading the brief
can tell them apart.

The derivation already computes this fact. It prints it under --residue:

0 of the 199 declare a population that reaches NOTHING in the tree, swept over 7757 tracked file(s) […] A standing repo fact, not a verdict about your paths

scripts/check-declared-population-live.mjs gives that standing fact a verdict.
No new instrument, no allowlist, no ratchet, and no gate name in it.

The rule is per-family, not per-literal: a gate may legitimately spell a slug,
a sentinel path or an example inside a message beside a live declaration, and
only a whole declaration that reaches nothing is refused.

Proven able to red, by ablation (mutation and restore both confirmed on
disk — git hash-object against the HEAD blob, before and after):

$ git checkout 45b9051248 -- scripts/measure-stall-guard-headroom.mjs
$ grep -c ROOT_DIR_WATCH_HINTS scripts/measure-stall-guard-headroom.mjs # 0
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 73d8e93d… ≠ HEAD 18e5cd8f…
$ node scripts/check-declared-population-live.mjs # exit 1
✗ check:declared-population-live: 1 of 155 declaring famil(ies) declare a population that reaches NOTHING in this tree.
check:stall-guard-headroom
declares: objectstack-ai/objectstack
$ git checkout HEAD -- scripts/measure-stall-guard-headroom.mjs
$ git diff HEAD # empty
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 18e5cd8f… = HEAD blob
$ node scripts/check-declared-population-live.mjs # exit 0

The stronger rule was implemented and refused

"A gate that enumerates a directory must carry a watch-hint declaration" is the
rule that suggests itself first. Measured at 45b9051248: of 193 discovered gate
files, 114 enumerate a directory and 86 of those carry no declaration under any
spelling of the idiom
. A gate shipping 86 findings is an allowlist with a
verdict attached — and an allowlist of gate names is the repair this lane has
already ruled against three times. Most of the 86 are not defects either: they
declare their population as ordinary path literals, invisible to a scan for the
idiom. Refused, and recorded here rather than in a follow-up, because the refusal
is part of the argument for the rule that shipped.

The second dead declaration the new gate found

The sweep found exactly two families in the fleet. The other is
check:pm-clause2-carriers, whose only path-shaped literal was the
`owner/name` placeholder inside a refusal message. That gate reads no
file in the tree at all
— its whole input is the GitHub API — so it takes the
gate's own remedy (b): the example is respelled `owner`/`name` so it
is no longer read as a declaration, and the gate now carries a
dispatch-gates: no-path-population marker with its reason. The seven-member
repair set is untouched; this is the guard's own remedy applied to the one other
carrier the guard names, and without it the guard could not ship green.

Changeset

skip-changeset: nothing here is published from any package. The diff is gate
scripts, one workflow step, one root package.json script entry, and
packages/spec/scripts/build-skill-docs.tspackages/spec's files field
publishes dist, json-schema, liveness, prompts, llms.txt, README.md,
src/**/*.zod.ts, api-surface and spec-changes.json, and not scripts/.

Gates run — at 04b7f769, the final commit

The family was derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands
(59 commands, re-derived after the last commit and unchanged), plus
node scripts/check-ratchet-remedy-authority.mjs. All 60 pass, except four
that report PREREQUISITE NOT MET because this container's workspace is not built
check-dev-prereqs (exit 1, "the workspace is not built"),
check-test-completeness (exit 3, no saved turbo log — its own text says to
record this as NOT MEASURED), check:dual-build-cjs-loads (exit 3, no dist/)
and check:type-check-debt (exit 3, closure not built). Each exit code was
captured before any pipe. None is a finding.

bare-root-worklist --self-test was a genuine red on the first draft and is
green here — see the check:skill-docs section above for what it caught.

Also run, beyond the derived family: pnpm lint over the whole repo (exit 0, no
narrowing needed), pnpm --filter @objectstack/spec exec tsc --noEmit (exit 0)
and — because --listFiles shows the edited .ts is not in that program —
tsc -p tsconfig.scripts.json --noEmit --listFiles, which does include it
(1 match) and is clean.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 da839ba11697703eb9391e36525fd5dd23242a63packageMentionDocs.

@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file tooling labels Sep 1, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 11:08
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 987fe37Sep 1, 2026
42 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-13519-gate-population-underdeclare branch September 1, 2026 11:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Declare the populations four under-matching gates really read, and refuse a dead declaration - #14188

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare
Sep 1, 2026
Merged

Declare the populations four under-matching gates really read, and refuse a dead declaration#14188
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13519

Four of the seven gates in the census declared a population that under-matched
what they actually read. Each is repaired where the read lives — a literal
population declaration in the gate's own module body, held against the very
constant that gate reads from — and the class is closed by giving a verdict to
a fact the derivation already computes and prints as prose.

scripts/pm/dispatch-gates.mjs is not touched. No gate name was added to
any derivation, table or roster.

⭐ The lead gate: matched population 0 → 29, with the command

check:stall-guard-headroom resolves to scripts/measure-stall-guard-headroom.mjs
(not check-stall-guard-*.mjs — re-derived from package.json, as the card
warns). Its only path-shaped literal was DEFAULT_REPO — the repo slug
objectstack-ai/objectstack — which names no tracked file.

node --input-type=module -e "
import * as G from './scripts/pm/dispatch-gates.mjs';
const files = G.trackedFiles(); const tree = G.watchHintTree(files);
const e = G.discoverFamilies({ tree }).byCheck.get('check:stall-guard-headroom');
const own = (e.hints||[]).filter(h => !e.hintOrigin?.has(h));
console.log('declared:', JSON.stringify(own));
console.log('literals reaching the tree:', own.filter(h => files.some(f => G.hintCovers(h,f))).length);
console.log('matched population:', files.filter(f => G.classifyEntry(e,[f]).verdict==='matched').length);
"
before (45b9051248)after
declared literals["objectstack-ai/objectstack"]["objectstack-ai/objectstack", ".github/workflows/**"]
literals reaching the tree01 (covering 29 workflow files)
matched population over 7757 tracked files1 (its own file, by identity)30

Why it declared nothing is the interesting half, and it is not a forgotten
string. The population is declared — in check-stall-guard-budget.mjs, which
this tool imports WORKFLOW_DIR and scan from. The import follow deliberately
refuses a target that is itself a discovered gate file, so nothing was
inherited. That refusal is right; what was missing is this end of it. A gate that
reads a population through a sibling gate has to declare that population
itself. The declaration is held against the imported WORKFLOW_DIR in the gate's
own --self-test, so a moved read reds there rather than going quiet in a brief.

Per-gate verdict — 4 fit the pattern, 3 need another mechanism

gatescriptverdictmatched population
check:stall-guard-headroomscripts/measure-stall-guard-headroom.mjsfits.github/workflows/**1 → 30
check:skill-docspackages/spec/scripts/build-skill-docs.tsfitsskills/*/SKILL.md + skills/README.md2 → 14
check:service-providersscripts/check-service-providers.mjsfits — three PARENT/*/package.json patterns2 → 55
check:turbo-task-graphscripts/check-turbo-task-graph.mjsfits — the member manifests3 → 82
check:nul-bytesscripts/check-nul-bytes.mjsneeds something else2 → 2
check-comment-mask-corpusscripts/check-comment-mask-corpus.mjsneeds something else2 → 2
check:refd-timer-probescripts/check-refd-timer-probe.mjsneeds something else2 → 2

Each was re-confirmed by reading the gate's own header on this tree, independent
of the mechanical classifier. None was already fixed.

Why the last three are refused rather than forced

All three read a repo-wide population and their headers say so:
check-nul-bytes scans git ls-filesplus untracked-not-ignored;
check-refd-timer-probe's readTree does the same; check-comment-mask-corpus
walks REPO_ROOT for every authored JS/TS file. A truthful declaration for any
of them is "every file", and the derivation's own header prices that direction:
"22 leads is the same as none." A gate on every card is a gate on no card.

They do not need a declaration; they need a repo-wide / always-runs channel
in the derivation, which is the derivation side and out of this card's fence.
Filed unassigned as #14189, with the per-gate evidence.

What check:skill-docs cost to decide — the tree had already refused the easy answer

The first draft declared skills/**, which is what the idiom's usual escape
looks like. scripts/pm/bare-root-worklist.mjs reddened on it, by name, and it
was right to: it carries the measured triage for this exact root and records this
gate's population as 12 of the 50 files tracked under it (24%), so a subtree
hint names the gate for 38 files it never opens — the REFUSE-WIDE trade this
repo prices as the costlier error.

What that recorded refusal turned on is worth reading closely, because it is a
claim about one spelling: "no single spelling of this idiom reaches both"
the 11 skill entrypoints, and the root README the generator WRITES, which sits
outside every skill directory. Two literals reach both and reach nothing else:
12 of 12, precise and complete. So the declaration stands and the row's
verdict is re-pointed SPELLABLE-UNDECLAREDDECLARED-NARROWER, which is the
value defined for a gate that took the escape at a strictly narrower population
and whose bare root stays uncovered. That is one of the two resolutions the
worklist's own self-test names for this contradiction.

⛔ Its neighbour row, check-skills-token-ratchet on the same root, is not
re-pointed: that gate walks the root recursively at 49 of 50, where the precise
spelling buys one file of discrimination, and its deferral is untouched.

What check:turbo-task-graph cost to decide

Its declaration block used to say the member manifests "stay undeclared on
purpose (the enumerator owns them and declares none)". workspace-enumerator.mjs
does declare none — but its header states why, and the reason points the other
way: "each gate keeps declaring its OWN population in its OWN module body […]
What is consolidated here is the PARSE, never the DECLARATION."
The +41725
(gate, file) pairs it measured and refused are the price of the subtree
claim, ~5400 files. The claim made here is the manifests only — 79 files, every
one of which readWorkspaceScripts really opens — and --self-test holds it
against the enumerator's live answer in both directions, so a twelfth glob in
pnpm-workspace.yaml reds in this gate.

Why this class cannot recur under another gate name

Adding four declarations protects four gates. It does not stop the next gate
shipping with a declaration that reaches nothing — and that is the shape the lead
had: the declaration and its absence print identically. A family whose
declared literals all reach zero tracked files scores an ordinary silence, byte
for byte the output of a gate that declared nothing, and no one reading the brief
can tell them apart.

The derivation already computes this fact. It prints it under --residue:

0 of the 199 declare a population that reaches NOTHING in the tree, swept over 7757 tracked file(s) […] A standing repo fact, not a verdict about your paths

scripts/check-declared-population-live.mjs gives that standing fact a verdict.
No new instrument, no allowlist, no ratchet, and no gate name in it.

The rule is per-family, not per-literal: a gate may legitimately spell a slug,
a sentinel path or an example inside a message beside a live declaration, and
only a whole declaration that reaches nothing is refused.

Proven able to red, by ablation (mutation and restore both confirmed on
disk — git hash-object against the HEAD blob, before and after):

$ git checkout 45b9051248 -- scripts/measure-stall-guard-headroom.mjs
$ grep -c ROOT_DIR_WATCH_HINTS scripts/measure-stall-guard-headroom.mjs # 0
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 73d8e93d… ≠ HEAD 18e5cd8f…
$ node scripts/check-declared-population-live.mjs # exit 1
✗ check:declared-population-live: 1 of 155 declaring famil(ies) declare a population that reaches NOTHING in this tree.
check:stall-guard-headroom
declares: objectstack-ai/objectstack
$ git checkout HEAD -- scripts/measure-stall-guard-headroom.mjs
$ git diff HEAD # empty
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 18e5cd8f… = HEAD blob
$ node scripts/check-declared-population-live.mjs # exit 0

The stronger rule was implemented and refused

"A gate that enumerates a directory must carry a watch-hint declaration" is the
rule that suggests itself first. Measured at 45b9051248: of 193 discovered gate
files, 114 enumerate a directory and 86 of those carry no declaration under any
spelling of the idiom
. A gate shipping 86 findings is an allowlist with a
verdict attached — and an allowlist of gate names is the repair this lane has
already ruled against three times. Most of the 86 are not defects either: they
declare their population as ordinary path literals, invisible to a scan for the
idiom. Refused, and recorded here rather than in a follow-up, because the refusal
is part of the argument for the rule that shipped.

The second dead declaration the new gate found

The sweep found exactly two families in the fleet. The other is
check:pm-clause2-carriers, whose only path-shaped literal was the
`owner/name` placeholder inside a refusal message. That gate reads no
file in the tree at all
— its whole input is the GitHub API — so it takes the
gate's own remedy (b): the example is respelled `owner`/`name` so it
is no longer read as a declaration, and the gate now carries a
dispatch-gates: no-path-population marker with its reason. The seven-member
repair set is untouched; this is the guard's own remedy applied to the one other
carrier the guard names, and without it the guard could not ship green.

Changeset

skip-changeset: nothing here is published from any package. The diff is gate
scripts, one workflow step, one root package.json script entry, and
packages/spec/scripts/build-skill-docs.tspackages/spec's files field
publishes dist, json-schema, liveness, prompts, llms.txt, README.md,
src/**/*.zod.ts, api-surface and spec-changes.json, and not scripts/.

Gates run — at 04b7f769, the final commit

The family was derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands
(59 commands, re-derived after the last commit and unchanged), plus
node scripts/check-ratchet-remedy-authority.mjs. All 60 pass, except four
that report PREREQUISITE NOT MET because this container's workspace is not built
check-dev-prereqs (exit 1, "the workspace is not built"),
check-test-completeness (exit 3, no saved turbo log — its own text says to
record this as NOT MEASURED), check:dual-build-cjs-loads (exit 3, no dist/)
and check:type-check-debt (exit 3, closure not built). Each exit code was
captured before any pipe. None is a finding.

bare-root-worklist --self-test was a genuine red on the first draft and is
green here — see the check:skill-docs section above for what it caught.

Also run, beyond the derived family: pnpm lint over the whole repo (exit 0, no
narrowing needed), pnpm --filter @objectstack/spec exec tsc --noEmit (exit 0)
and — because --listFiles shows the edited .ts is not in that program —
tsc -p tsconfig.scripts.json --noEmit --listFiles, which does include it
(1 match) and is clean.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 da839ba11697703eb9391e36525fd5dd23242a63packageMentionDocs.

@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file tooling labels Sep 1, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 11:08
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 987fe37Sep 1, 2026
42 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-13519-gate-population-underdeclare branch September 1, 2026 11:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Declare the populations four under-matching gates really read, and refuse a dead declaration - #14188

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare
Sep 1, 2026
Merged

Declare the populations four under-matching gates really read, and refuse a dead declaration#14188
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13519

Four of the seven gates in the census declared a population that under-matched
what they actually read. Each is repaired where the read lives — a literal
population declaration in the gate's own module body, held against the very
constant that gate reads from — and the class is closed by giving a verdict to
a fact the derivation already computes and prints as prose.

scripts/pm/dispatch-gates.mjs is not touched. No gate name was added to
any derivation, table or roster.

⭐ The lead gate: matched population 0 → 29, with the command

check:stall-guard-headroom resolves to scripts/measure-stall-guard-headroom.mjs
(not check-stall-guard-*.mjs — re-derived from package.json, as the card
warns). Its only path-shaped literal was DEFAULT_REPO — the repo slug
objectstack-ai/objectstack — which names no tracked file.

node --input-type=module -e "
import * as G from './scripts/pm/dispatch-gates.mjs';
const files = G.trackedFiles(); const tree = G.watchHintTree(files);
const e = G.discoverFamilies({ tree }).byCheck.get('check:stall-guard-headroom');
const own = (e.hints||[]).filter(h => !e.hintOrigin?.has(h));
console.log('declared:', JSON.stringify(own));
console.log('literals reaching the tree:', own.filter(h => files.some(f => G.hintCovers(h,f))).length);
console.log('matched population:', files.filter(f => G.classifyEntry(e,[f]).verdict==='matched').length);
"
before (45b9051248)after
declared literals["objectstack-ai/objectstack"]["objectstack-ai/objectstack", ".github/workflows/**"]
literals reaching the tree01 (covering 29 workflow files)
matched population over 7757 tracked files1 (its own file, by identity)30

Why it declared nothing is the interesting half, and it is not a forgotten
string. The population is declared — in check-stall-guard-budget.mjs, which
this tool imports WORKFLOW_DIR and scan from. The import follow deliberately
refuses a target that is itself a discovered gate file, so nothing was
inherited. That refusal is right; what was missing is this end of it. A gate that
reads a population through a sibling gate has to declare that population
itself. The declaration is held against the imported WORKFLOW_DIR in the gate's
own --self-test, so a moved read reds there rather than going quiet in a brief.

Per-gate verdict — 4 fit the pattern, 3 need another mechanism

gatescriptverdictmatched population
check:stall-guard-headroomscripts/measure-stall-guard-headroom.mjsfits.github/workflows/**1 → 30
check:skill-docspackages/spec/scripts/build-skill-docs.tsfitsskills/*/SKILL.md + skills/README.md2 → 14
check:service-providersscripts/check-service-providers.mjsfits — three PARENT/*/package.json patterns2 → 55
check:turbo-task-graphscripts/check-turbo-task-graph.mjsfits — the member manifests3 → 82
check:nul-bytesscripts/check-nul-bytes.mjsneeds something else2 → 2
check-comment-mask-corpusscripts/check-comment-mask-corpus.mjsneeds something else2 → 2
check:refd-timer-probescripts/check-refd-timer-probe.mjsneeds something else2 → 2

Each was re-confirmed by reading the gate's own header on this tree, independent
of the mechanical classifier. None was already fixed.

Why the last three are refused rather than forced

All three read a repo-wide population and their headers say so:
check-nul-bytes scans git ls-filesplus untracked-not-ignored;
check-refd-timer-probe's readTree does the same; check-comment-mask-corpus
walks REPO_ROOT for every authored JS/TS file. A truthful declaration for any
of them is "every file", and the derivation's own header prices that direction:
"22 leads is the same as none." A gate on every card is a gate on no card.

They do not need a declaration; they need a repo-wide / always-runs channel
in the derivation, which is the derivation side and out of this card's fence.
Filed unassigned as #14189, with the per-gate evidence.

What check:skill-docs cost to decide — the tree had already refused the easy answer

The first draft declared skills/**, which is what the idiom's usual escape
looks like. scripts/pm/bare-root-worklist.mjs reddened on it, by name, and it
was right to: it carries the measured triage for this exact root and records this
gate's population as 12 of the 50 files tracked under it (24%), so a subtree
hint names the gate for 38 files it never opens — the REFUSE-WIDE trade this
repo prices as the costlier error.

What that recorded refusal turned on is worth reading closely, because it is a
claim about one spelling: "no single spelling of this idiom reaches both"
the 11 skill entrypoints, and the root README the generator WRITES, which sits
outside every skill directory. Two literals reach both and reach nothing else:
12 of 12, precise and complete. So the declaration stands and the row's
verdict is re-pointed SPELLABLE-UNDECLAREDDECLARED-NARROWER, which is the
value defined for a gate that took the escape at a strictly narrower population
and whose bare root stays uncovered. That is one of the two resolutions the
worklist's own self-test names for this contradiction.

⛔ Its neighbour row, check-skills-token-ratchet on the same root, is not
re-pointed: that gate walks the root recursively at 49 of 50, where the precise
spelling buys one file of discrimination, and its deferral is untouched.

What check:turbo-task-graph cost to decide

Its declaration block used to say the member manifests "stay undeclared on
purpose (the enumerator owns them and declares none)". workspace-enumerator.mjs
does declare none — but its header states why, and the reason points the other
way: "each gate keeps declaring its OWN population in its OWN module body […]
What is consolidated here is the PARSE, never the DECLARATION."
The +41725
(gate, file) pairs it measured and refused are the price of the subtree
claim, ~5400 files. The claim made here is the manifests only — 79 files, every
one of which readWorkspaceScripts really opens — and --self-test holds it
against the enumerator's live answer in both directions, so a twelfth glob in
pnpm-workspace.yaml reds in this gate.

Why this class cannot recur under another gate name

Adding four declarations protects four gates. It does not stop the next gate
shipping with a declaration that reaches nothing — and that is the shape the lead
had: the declaration and its absence print identically. A family whose
declared literals all reach zero tracked files scores an ordinary silence, byte
for byte the output of a gate that declared nothing, and no one reading the brief
can tell them apart.

The derivation already computes this fact. It prints it under --residue:

0 of the 199 declare a population that reaches NOTHING in the tree, swept over 7757 tracked file(s) […] A standing repo fact, not a verdict about your paths

scripts/check-declared-population-live.mjs gives that standing fact a verdict.
No new instrument, no allowlist, no ratchet, and no gate name in it.

The rule is per-family, not per-literal: a gate may legitimately spell a slug,
a sentinel path or an example inside a message beside a live declaration, and
only a whole declaration that reaches nothing is refused.

Proven able to red, by ablation (mutation and restore both confirmed on
disk — git hash-object against the HEAD blob, before and after):

$ git checkout 45b9051248 -- scripts/measure-stall-guard-headroom.mjs
$ grep -c ROOT_DIR_WATCH_HINTS scripts/measure-stall-guard-headroom.mjs # 0
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 73d8e93d… ≠ HEAD 18e5cd8f…
$ node scripts/check-declared-population-live.mjs # exit 1
✗ check:declared-population-live: 1 of 155 declaring famil(ies) declare a population that reaches NOTHING in this tree.
check:stall-guard-headroom
declares: objectstack-ai/objectstack
$ git checkout HEAD -- scripts/measure-stall-guard-headroom.mjs
$ git diff HEAD # empty
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 18e5cd8f… = HEAD blob
$ node scripts/check-declared-population-live.mjs # exit 0

The stronger rule was implemented and refused

"A gate that enumerates a directory must carry a watch-hint declaration" is the
rule that suggests itself first. Measured at 45b9051248: of 193 discovered gate
files, 114 enumerate a directory and 86 of those carry no declaration under any
spelling of the idiom
. A gate shipping 86 findings is an allowlist with a
verdict attached — and an allowlist of gate names is the repair this lane has
already ruled against three times. Most of the 86 are not defects either: they
declare their population as ordinary path literals, invisible to a scan for the
idiom. Refused, and recorded here rather than in a follow-up, because the refusal
is part of the argument for the rule that shipped.

The second dead declaration the new gate found

The sweep found exactly two families in the fleet. The other is
check:pm-clause2-carriers, whose only path-shaped literal was the
`owner/name` placeholder inside a refusal message. That gate reads no
file in the tree at all
— its whole input is the GitHub API — so it takes the
gate's own remedy (b): the example is respelled `owner`/`name` so it
is no longer read as a declaration, and the gate now carries a
dispatch-gates: no-path-population marker with its reason. The seven-member
repair set is untouched; this is the guard's own remedy applied to the one other
carrier the guard names, and without it the guard could not ship green.

Changeset

skip-changeset: nothing here is published from any package. The diff is gate
scripts, one workflow step, one root package.json script entry, and
packages/spec/scripts/build-skill-docs.tspackages/spec's files field
publishes dist, json-schema, liveness, prompts, llms.txt, README.md,
src/**/*.zod.ts, api-surface and spec-changes.json, and not scripts/.

Gates run — at 04b7f769, the final commit

The family was derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands
(59 commands, re-derived after the last commit and unchanged), plus
node scripts/check-ratchet-remedy-authority.mjs. All 60 pass, except four
that report PREREQUISITE NOT MET because this container's workspace is not built
check-dev-prereqs (exit 1, "the workspace is not built"),
check-test-completeness (exit 3, no saved turbo log — its own text says to
record this as NOT MEASURED), check:dual-build-cjs-loads (exit 3, no dist/)
and check:type-check-debt (exit 3, closure not built). Each exit code was
captured before any pipe. None is a finding.

bare-root-worklist --self-test was a genuine red on the first draft and is
green here — see the check:skill-docs section above for what it caught.

Also run, beyond the derived family: pnpm lint over the whole repo (exit 0, no
narrowing needed), pnpm --filter @objectstack/spec exec tsc --noEmit (exit 0)
and — because --listFiles shows the edited .ts is not in that program —
tsc -p tsconfig.scripts.json --noEmit --listFiles, which does include it
(1 match) and is clean.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 da839ba11697703eb9391e36525fd5dd23242a63packageMentionDocs.

@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file tooling labels Sep 1, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 11:08
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 987fe37Sep 1, 2026
42 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-13519-gate-population-underdeclare branch September 1, 2026 11:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Declare the populations four under-matching gates really read, and refuse a dead declaration - #14188

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare
Sep 1, 2026
Merged

Declare the populations four under-matching gates really read, and refuse a dead declaration#14188
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13519

Four of the seven gates in the census declared a population that under-matched
what they actually read. Each is repaired where the read lives — a literal
population declaration in the gate's own module body, held against the very
constant that gate reads from — and the class is closed by giving a verdict to
a fact the derivation already computes and prints as prose.

scripts/pm/dispatch-gates.mjs is not touched. No gate name was added to
any derivation, table or roster.

⭐ The lead gate: matched population 0 → 29, with the command

check:stall-guard-headroom resolves to scripts/measure-stall-guard-headroom.mjs
(not check-stall-guard-*.mjs — re-derived from package.json, as the card
warns). Its only path-shaped literal was DEFAULT_REPO — the repo slug
objectstack-ai/objectstack — which names no tracked file.

node --input-type=module -e "
import * as G from './scripts/pm/dispatch-gates.mjs';
const files = G.trackedFiles(); const tree = G.watchHintTree(files);
const e = G.discoverFamilies({ tree }).byCheck.get('check:stall-guard-headroom');
const own = (e.hints||[]).filter(h => !e.hintOrigin?.has(h));
console.log('declared:', JSON.stringify(own));
console.log('literals reaching the tree:', own.filter(h => files.some(f => G.hintCovers(h,f))).length);
console.log('matched population:', files.filter(f => G.classifyEntry(e,[f]).verdict==='matched').length);
"
before (45b9051248)after
declared literals["objectstack-ai/objectstack"]["objectstack-ai/objectstack", ".github/workflows/**"]
literals reaching the tree01 (covering 29 workflow files)
matched population over 7757 tracked files1 (its own file, by identity)30

Why it declared nothing is the interesting half, and it is not a forgotten
string. The population is declared — in check-stall-guard-budget.mjs, which
this tool imports WORKFLOW_DIR and scan from. The import follow deliberately
refuses a target that is itself a discovered gate file, so nothing was
inherited. That refusal is right; what was missing is this end of it. A gate that
reads a population through a sibling gate has to declare that population
itself. The declaration is held against the imported WORKFLOW_DIR in the gate's
own --self-test, so a moved read reds there rather than going quiet in a brief.

Per-gate verdict — 4 fit the pattern, 3 need another mechanism

gatescriptverdictmatched population
check:stall-guard-headroomscripts/measure-stall-guard-headroom.mjsfits.github/workflows/**1 → 30
check:skill-docspackages/spec/scripts/build-skill-docs.tsfitsskills/*/SKILL.md + skills/README.md2 → 14
check:service-providersscripts/check-service-providers.mjsfits — three PARENT/*/package.json patterns2 → 55
check:turbo-task-graphscripts/check-turbo-task-graph.mjsfits — the member manifests3 → 82
check:nul-bytesscripts/check-nul-bytes.mjsneeds something else2 → 2
check-comment-mask-corpusscripts/check-comment-mask-corpus.mjsneeds something else2 → 2
check:refd-timer-probescripts/check-refd-timer-probe.mjsneeds something else2 → 2

Each was re-confirmed by reading the gate's own header on this tree, independent
of the mechanical classifier. None was already fixed.

Why the last three are refused rather than forced

All three read a repo-wide population and their headers say so:
check-nul-bytes scans git ls-filesplus untracked-not-ignored;
check-refd-timer-probe's readTree does the same; check-comment-mask-corpus
walks REPO_ROOT for every authored JS/TS file. A truthful declaration for any
of them is "every file", and the derivation's own header prices that direction:
"22 leads is the same as none." A gate on every card is a gate on no card.

They do not need a declaration; they need a repo-wide / always-runs channel
in the derivation, which is the derivation side and out of this card's fence.
Filed unassigned as #14189, with the per-gate evidence.

What check:skill-docs cost to decide — the tree had already refused the easy answer

The first draft declared skills/**, which is what the idiom's usual escape
looks like. scripts/pm/bare-root-worklist.mjs reddened on it, by name, and it
was right to: it carries the measured triage for this exact root and records this
gate's population as 12 of the 50 files tracked under it (24%), so a subtree
hint names the gate for 38 files it never opens — the REFUSE-WIDE trade this
repo prices as the costlier error.

What that recorded refusal turned on is worth reading closely, because it is a
claim about one spelling: "no single spelling of this idiom reaches both"
the 11 skill entrypoints, and the root README the generator WRITES, which sits
outside every skill directory. Two literals reach both and reach nothing else:
12 of 12, precise and complete. So the declaration stands and the row's
verdict is re-pointed SPELLABLE-UNDECLAREDDECLARED-NARROWER, which is the
value defined for a gate that took the escape at a strictly narrower population
and whose bare root stays uncovered. That is one of the two resolutions the
worklist's own self-test names for this contradiction.

⛔ Its neighbour row, check-skills-token-ratchet on the same root, is not
re-pointed: that gate walks the root recursively at 49 of 50, where the precise
spelling buys one file of discrimination, and its deferral is untouched.

What check:turbo-task-graph cost to decide

Its declaration block used to say the member manifests "stay undeclared on
purpose (the enumerator owns them and declares none)". workspace-enumerator.mjs
does declare none — but its header states why, and the reason points the other
way: "each gate keeps declaring its OWN population in its OWN module body […]
What is consolidated here is the PARSE, never the DECLARATION."
The +41725
(gate, file) pairs it measured and refused are the price of the subtree
claim, ~5400 files. The claim made here is the manifests only — 79 files, every
one of which readWorkspaceScripts really opens — and --self-test holds it
against the enumerator's live answer in both directions, so a twelfth glob in
pnpm-workspace.yaml reds in this gate.

Why this class cannot recur under another gate name

Adding four declarations protects four gates. It does not stop the next gate
shipping with a declaration that reaches nothing — and that is the shape the lead
had: the declaration and its absence print identically. A family whose
declared literals all reach zero tracked files scores an ordinary silence, byte
for byte the output of a gate that declared nothing, and no one reading the brief
can tell them apart.

The derivation already computes this fact. It prints it under --residue:

0 of the 199 declare a population that reaches NOTHING in the tree, swept over 7757 tracked file(s) […] A standing repo fact, not a verdict about your paths

scripts/check-declared-population-live.mjs gives that standing fact a verdict.
No new instrument, no allowlist, no ratchet, and no gate name in it.

The rule is per-family, not per-literal: a gate may legitimately spell a slug,
a sentinel path or an example inside a message beside a live declaration, and
only a whole declaration that reaches nothing is refused.

Proven able to red, by ablation (mutation and restore both confirmed on
disk — git hash-object against the HEAD blob, before and after):

$ git checkout 45b9051248 -- scripts/measure-stall-guard-headroom.mjs
$ grep -c ROOT_DIR_WATCH_HINTS scripts/measure-stall-guard-headroom.mjs # 0
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 73d8e93d… ≠ HEAD 18e5cd8f…
$ node scripts/check-declared-population-live.mjs # exit 1
✗ check:declared-population-live: 1 of 155 declaring famil(ies) declare a population that reaches NOTHING in this tree.
check:stall-guard-headroom
declares: objectstack-ai/objectstack
$ git checkout HEAD -- scripts/measure-stall-guard-headroom.mjs
$ git diff HEAD # empty
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 18e5cd8f… = HEAD blob
$ node scripts/check-declared-population-live.mjs # exit 0

The stronger rule was implemented and refused

"A gate that enumerates a directory must carry a watch-hint declaration" is the
rule that suggests itself first. Measured at 45b9051248: of 193 discovered gate
files, 114 enumerate a directory and 86 of those carry no declaration under any
spelling of the idiom
. A gate shipping 86 findings is an allowlist with a
verdict attached — and an allowlist of gate names is the repair this lane has
already ruled against three times. Most of the 86 are not defects either: they
declare their population as ordinary path literals, invisible to a scan for the
idiom. Refused, and recorded here rather than in a follow-up, because the refusal
is part of the argument for the rule that shipped.

The second dead declaration the new gate found

The sweep found exactly two families in the fleet. The other is
check:pm-clause2-carriers, whose only path-shaped literal was the
`owner/name` placeholder inside a refusal message. That gate reads no
file in the tree at all
— its whole input is the GitHub API — so it takes the
gate's own remedy (b): the example is respelled `owner`/`name` so it
is no longer read as a declaration, and the gate now carries a
dispatch-gates: no-path-population marker with its reason. The seven-member
repair set is untouched; this is the guard's own remedy applied to the one other
carrier the guard names, and without it the guard could not ship green.

Changeset

skip-changeset: nothing here is published from any package. The diff is gate
scripts, one workflow step, one root package.json script entry, and
packages/spec/scripts/build-skill-docs.tspackages/spec's files field
publishes dist, json-schema, liveness, prompts, llms.txt, README.md,
src/**/*.zod.ts, api-surface and spec-changes.json, and not scripts/.

Gates run — at 04b7f769, the final commit

The family was derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands
(59 commands, re-derived after the last commit and unchanged), plus
node scripts/check-ratchet-remedy-authority.mjs. All 60 pass, except four
that report PREREQUISITE NOT MET because this container's workspace is not built
check-dev-prereqs (exit 1, "the workspace is not built"),
check-test-completeness (exit 3, no saved turbo log — its own text says to
record this as NOT MEASURED), check:dual-build-cjs-loads (exit 3, no dist/)
and check:type-check-debt (exit 3, closure not built). Each exit code was
captured before any pipe. None is a finding.

bare-root-worklist --self-test was a genuine red on the first draft and is
green here — see the check:skill-docs section above for what it caught.

Also run, beyond the derived family: pnpm lint over the whole repo (exit 0, no
narrowing needed), pnpm --filter @objectstack/spec exec tsc --noEmit (exit 0)
and — because --listFiles shows the edited .ts is not in that program —
tsc -p tsconfig.scripts.json --noEmit --listFiles, which does include it
(1 match) and is clean.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 da839ba11697703eb9391e36525fd5dd23242a63packageMentionDocs.

@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file tooling labels Sep 1, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 11:08
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 987fe37Sep 1, 2026
42 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-13519-gate-population-underdeclare branch September 1, 2026 11:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Declare the populations four under-matching gates really read, and refuse a dead declaration - #14188

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare
Sep 1, 2026
Merged

Declare the populations four under-matching gates really read, and refuse a dead declaration#14188
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13519

Four of the seven gates in the census declared a population that under-matched
what they actually read. Each is repaired where the read lives — a literal
population declaration in the gate's own module body, held against the very
constant that gate reads from — and the class is closed by giving a verdict to
a fact the derivation already computes and prints as prose.

scripts/pm/dispatch-gates.mjs is not touched. No gate name was added to
any derivation, table or roster.

⭐ The lead gate: matched population 0 → 29, with the command

check:stall-guard-headroom resolves to scripts/measure-stall-guard-headroom.mjs
(not check-stall-guard-*.mjs — re-derived from package.json, as the card
warns). Its only path-shaped literal was DEFAULT_REPO — the repo slug
objectstack-ai/objectstack — which names no tracked file.

node --input-type=module -e "
import * as G from './scripts/pm/dispatch-gates.mjs';
const files = G.trackedFiles(); const tree = G.watchHintTree(files);
const e = G.discoverFamilies({ tree }).byCheck.get('check:stall-guard-headroom');
const own = (e.hints||[]).filter(h => !e.hintOrigin?.has(h));
console.log('declared:', JSON.stringify(own));
console.log('literals reaching the tree:', own.filter(h => files.some(f => G.hintCovers(h,f))).length);
console.log('matched population:', files.filter(f => G.classifyEntry(e,[f]).verdict==='matched').length);
"
before (45b9051248)after
declared literals["objectstack-ai/objectstack"]["objectstack-ai/objectstack", ".github/workflows/**"]
literals reaching the tree01 (covering 29 workflow files)
matched population over 7757 tracked files1 (its own file, by identity)30

Why it declared nothing is the interesting half, and it is not a forgotten
string. The population is declared — in check-stall-guard-budget.mjs, which
this tool imports WORKFLOW_DIR and scan from. The import follow deliberately
refuses a target that is itself a discovered gate file, so nothing was
inherited. That refusal is right; what was missing is this end of it. A gate that
reads a population through a sibling gate has to declare that population
itself. The declaration is held against the imported WORKFLOW_DIR in the gate's
own --self-test, so a moved read reds there rather than going quiet in a brief.

Per-gate verdict — 4 fit the pattern, 3 need another mechanism

gatescriptverdictmatched population
check:stall-guard-headroomscripts/measure-stall-guard-headroom.mjsfits.github/workflows/**1 → 30
check:skill-docspackages/spec/scripts/build-skill-docs.tsfitsskills/*/SKILL.md + skills/README.md2 → 14
check:service-providersscripts/check-service-providers.mjsfits — three PARENT/*/package.json patterns2 → 55
check:turbo-task-graphscripts/check-turbo-task-graph.mjsfits — the member manifests3 → 82
check:nul-bytesscripts/check-nul-bytes.mjsneeds something else2 → 2
check-comment-mask-corpusscripts/check-comment-mask-corpus.mjsneeds something else2 → 2
check:refd-timer-probescripts/check-refd-timer-probe.mjsneeds something else2 → 2

Each was re-confirmed by reading the gate's own header on this tree, independent
of the mechanical classifier. None was already fixed.

Why the last three are refused rather than forced

All three read a repo-wide population and their headers say so:
check-nul-bytes scans git ls-filesplus untracked-not-ignored;
check-refd-timer-probe's readTree does the same; check-comment-mask-corpus
walks REPO_ROOT for every authored JS/TS file. A truthful declaration for any
of them is "every file", and the derivation's own header prices that direction:
"22 leads is the same as none." A gate on every card is a gate on no card.

They do not need a declaration; they need a repo-wide / always-runs channel
in the derivation, which is the derivation side and out of this card's fence.
Filed unassigned as #14189, with the per-gate evidence.

What check:skill-docs cost to decide — the tree had already refused the easy answer

The first draft declared skills/**, which is what the idiom's usual escape
looks like. scripts/pm/bare-root-worklist.mjs reddened on it, by name, and it
was right to: it carries the measured triage for this exact root and records this
gate's population as 12 of the 50 files tracked under it (24%), so a subtree
hint names the gate for 38 files it never opens — the REFUSE-WIDE trade this
repo prices as the costlier error.

What that recorded refusal turned on is worth reading closely, because it is a
claim about one spelling: "no single spelling of this idiom reaches both"
the 11 skill entrypoints, and the root README the generator WRITES, which sits
outside every skill directory. Two literals reach both and reach nothing else:
12 of 12, precise and complete. So the declaration stands and the row's
verdict is re-pointed SPELLABLE-UNDECLAREDDECLARED-NARROWER, which is the
value defined for a gate that took the escape at a strictly narrower population
and whose bare root stays uncovered. That is one of the two resolutions the
worklist's own self-test names for this contradiction.

⛔ Its neighbour row, check-skills-token-ratchet on the same root, is not
re-pointed: that gate walks the root recursively at 49 of 50, where the precise
spelling buys one file of discrimination, and its deferral is untouched.

What check:turbo-task-graph cost to decide

Its declaration block used to say the member manifests "stay undeclared on
purpose (the enumerator owns them and declares none)". workspace-enumerator.mjs
does declare none — but its header states why, and the reason points the other
way: "each gate keeps declaring its OWN population in its OWN module body […]
What is consolidated here is the PARSE, never the DECLARATION."
The +41725
(gate, file) pairs it measured and refused are the price of the subtree
claim, ~5400 files. The claim made here is the manifests only — 79 files, every
one of which readWorkspaceScripts really opens — and --self-test holds it
against the enumerator's live answer in both directions, so a twelfth glob in
pnpm-workspace.yaml reds in this gate.

Why this class cannot recur under another gate name

Adding four declarations protects four gates. It does not stop the next gate
shipping with a declaration that reaches nothing — and that is the shape the lead
had: the declaration and its absence print identically. A family whose
declared literals all reach zero tracked files scores an ordinary silence, byte
for byte the output of a gate that declared nothing, and no one reading the brief
can tell them apart.

The derivation already computes this fact. It prints it under --residue:

0 of the 199 declare a population that reaches NOTHING in the tree, swept over 7757 tracked file(s) […] A standing repo fact, not a verdict about your paths

scripts/check-declared-population-live.mjs gives that standing fact a verdict.
No new instrument, no allowlist, no ratchet, and no gate name in it.

The rule is per-family, not per-literal: a gate may legitimately spell a slug,
a sentinel path or an example inside a message beside a live declaration, and
only a whole declaration that reaches nothing is refused.

Proven able to red, by ablation (mutation and restore both confirmed on
disk — git hash-object against the HEAD blob, before and after):

$ git checkout 45b9051248 -- scripts/measure-stall-guard-headroom.mjs
$ grep -c ROOT_DIR_WATCH_HINTS scripts/measure-stall-guard-headroom.mjs # 0
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 73d8e93d… ≠ HEAD 18e5cd8f…
$ node scripts/check-declared-population-live.mjs # exit 1
✗ check:declared-population-live: 1 of 155 declaring famil(ies) declare a population that reaches NOTHING in this tree.
check:stall-guard-headroom
declares: objectstack-ai/objectstack
$ git checkout HEAD -- scripts/measure-stall-guard-headroom.mjs
$ git diff HEAD # empty
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 18e5cd8f… = HEAD blob
$ node scripts/check-declared-population-live.mjs # exit 0

The stronger rule was implemented and refused

"A gate that enumerates a directory must carry a watch-hint declaration" is the
rule that suggests itself first. Measured at 45b9051248: of 193 discovered gate
files, 114 enumerate a directory and 86 of those carry no declaration under any
spelling of the idiom
. A gate shipping 86 findings is an allowlist with a
verdict attached — and an allowlist of gate names is the repair this lane has
already ruled against three times. Most of the 86 are not defects either: they
declare their population as ordinary path literals, invisible to a scan for the
idiom. Refused, and recorded here rather than in a follow-up, because the refusal
is part of the argument for the rule that shipped.

The second dead declaration the new gate found

The sweep found exactly two families in the fleet. The other is
check:pm-clause2-carriers, whose only path-shaped literal was the
`owner/name` placeholder inside a refusal message. That gate reads no
file in the tree at all
— its whole input is the GitHub API — so it takes the
gate's own remedy (b): the example is respelled `owner`/`name` so it
is no longer read as a declaration, and the gate now carries a
dispatch-gates: no-path-population marker with its reason. The seven-member
repair set is untouched; this is the guard's own remedy applied to the one other
carrier the guard names, and without it the guard could not ship green.

Changeset

skip-changeset: nothing here is published from any package. The diff is gate
scripts, one workflow step, one root package.json script entry, and
packages/spec/scripts/build-skill-docs.tspackages/spec's files field
publishes dist, json-schema, liveness, prompts, llms.txt, README.md,
src/**/*.zod.ts, api-surface and spec-changes.json, and not scripts/.

Gates run — at 04b7f769, the final commit

The family was derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands
(59 commands, re-derived after the last commit and unchanged), plus
node scripts/check-ratchet-remedy-authority.mjs. All 60 pass, except four
that report PREREQUISITE NOT MET because this container's workspace is not built
check-dev-prereqs (exit 1, "the workspace is not built"),
check-test-completeness (exit 3, no saved turbo log — its own text says to
record this as NOT MEASURED), check:dual-build-cjs-loads (exit 3, no dist/)
and check:type-check-debt (exit 3, closure not built). Each exit code was
captured before any pipe. None is a finding.

bare-root-worklist --self-test was a genuine red on the first draft and is
green here — see the check:skill-docs section above for what it caught.

Also run, beyond the derived family: pnpm lint over the whole repo (exit 0, no
narrowing needed), pnpm --filter @objectstack/spec exec tsc --noEmit (exit 0)
and — because --listFiles shows the edited .ts is not in that program —
tsc -p tsconfig.scripts.json --noEmit --listFiles, which does include it
(1 match) and is clean.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 da839ba11697703eb9391e36525fd5dd23242a63packageMentionDocs.

@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file tooling labels Sep 1, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 11:08
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 987fe37Sep 1, 2026
42 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-13519-gate-population-underdeclare branch September 1, 2026 11:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Declare the populations four under-matching gates really read, and refuse a dead declaration - #14188

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare
Sep 1, 2026
Merged

Declare the populations four under-matching gates really read, and refuse a dead declaration#14188
baozhoutao merged 3 commits into
mainfrom
claude/issue-13519-gate-population-underdeclare

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13519

Four of the seven gates in the census declared a population that under-matched
what they actually read. Each is repaired where the read lives — a literal
population declaration in the gate's own module body, held against the very
constant that gate reads from — and the class is closed by giving a verdict to
a fact the derivation already computes and prints as prose.

scripts/pm/dispatch-gates.mjs is not touched. No gate name was added to
any derivation, table or roster.

⭐ The lead gate: matched population 0 → 29, with the command

check:stall-guard-headroom resolves to scripts/measure-stall-guard-headroom.mjs
(not check-stall-guard-*.mjs — re-derived from package.json, as the card
warns). Its only path-shaped literal was DEFAULT_REPO — the repo slug
objectstack-ai/objectstack — which names no tracked file.

node --input-type=module -e "
import * as G from './scripts/pm/dispatch-gates.mjs';
const files = G.trackedFiles(); const tree = G.watchHintTree(files);
const e = G.discoverFamilies({ tree }).byCheck.get('check:stall-guard-headroom');
const own = (e.hints||[]).filter(h => !e.hintOrigin?.has(h));
console.log('declared:', JSON.stringify(own));
console.log('literals reaching the tree:', own.filter(h => files.some(f => G.hintCovers(h,f))).length);
console.log('matched population:', files.filter(f => G.classifyEntry(e,[f]).verdict==='matched').length);
"
before (45b9051248)after
declared literals["objectstack-ai/objectstack"]["objectstack-ai/objectstack", ".github/workflows/**"]
literals reaching the tree01 (covering 29 workflow files)
matched population over 7757 tracked files1 (its own file, by identity)30

Why it declared nothing is the interesting half, and it is not a forgotten
string. The population is declared — in check-stall-guard-budget.mjs, which
this tool imports WORKFLOW_DIR and scan from. The import follow deliberately
refuses a target that is itself a discovered gate file, so nothing was
inherited. That refusal is right; what was missing is this end of it. A gate that
reads a population through a sibling gate has to declare that population
itself. The declaration is held against the imported WORKFLOW_DIR in the gate's
own --self-test, so a moved read reds there rather than going quiet in a brief.

Per-gate verdict — 4 fit the pattern, 3 need another mechanism

gatescriptverdictmatched population
check:stall-guard-headroomscripts/measure-stall-guard-headroom.mjsfits.github/workflows/**1 → 30
check:skill-docspackages/spec/scripts/build-skill-docs.tsfitsskills/*/SKILL.md + skills/README.md2 → 14
check:service-providersscripts/check-service-providers.mjsfits — three PARENT/*/package.json patterns2 → 55
check:turbo-task-graphscripts/check-turbo-task-graph.mjsfits — the member manifests3 → 82
check:nul-bytesscripts/check-nul-bytes.mjsneeds something else2 → 2
check-comment-mask-corpusscripts/check-comment-mask-corpus.mjsneeds something else2 → 2
check:refd-timer-probescripts/check-refd-timer-probe.mjsneeds something else2 → 2

Each was re-confirmed by reading the gate's own header on this tree, independent
of the mechanical classifier. None was already fixed.

Why the last three are refused rather than forced

All three read a repo-wide population and their headers say so:
check-nul-bytes scans git ls-filesplus untracked-not-ignored;
check-refd-timer-probe's readTree does the same; check-comment-mask-corpus
walks REPO_ROOT for every authored JS/TS file. A truthful declaration for any
of them is "every file", and the derivation's own header prices that direction:
"22 leads is the same as none." A gate on every card is a gate on no card.

They do not need a declaration; they need a repo-wide / always-runs channel
in the derivation, which is the derivation side and out of this card's fence.
Filed unassigned as #14189, with the per-gate evidence.

What check:skill-docs cost to decide — the tree had already refused the easy answer

The first draft declared skills/**, which is what the idiom's usual escape
looks like. scripts/pm/bare-root-worklist.mjs reddened on it, by name, and it
was right to: it carries the measured triage for this exact root and records this
gate's population as 12 of the 50 files tracked under it (24%), so a subtree
hint names the gate for 38 files it never opens — the REFUSE-WIDE trade this
repo prices as the costlier error.

What that recorded refusal turned on is worth reading closely, because it is a
claim about one spelling: "no single spelling of this idiom reaches both"
the 11 skill entrypoints, and the root README the generator WRITES, which sits
outside every skill directory. Two literals reach both and reach nothing else:
12 of 12, precise and complete. So the declaration stands and the row's
verdict is re-pointed SPELLABLE-UNDECLAREDDECLARED-NARROWER, which is the
value defined for a gate that took the escape at a strictly narrower population
and whose bare root stays uncovered. That is one of the two resolutions the
worklist's own self-test names for this contradiction.

⛔ Its neighbour row, check-skills-token-ratchet on the same root, is not
re-pointed: that gate walks the root recursively at 49 of 50, where the precise
spelling buys one file of discrimination, and its deferral is untouched.

What check:turbo-task-graph cost to decide

Its declaration block used to say the member manifests "stay undeclared on
purpose (the enumerator owns them and declares none)". workspace-enumerator.mjs
does declare none — but its header states why, and the reason points the other
way: "each gate keeps declaring its OWN population in its OWN module body […]
What is consolidated here is the PARSE, never the DECLARATION."
The +41725
(gate, file) pairs it measured and refused are the price of the subtree
claim, ~5400 files. The claim made here is the manifests only — 79 files, every
one of which readWorkspaceScripts really opens — and --self-test holds it
against the enumerator's live answer in both directions, so a twelfth glob in
pnpm-workspace.yaml reds in this gate.

Why this class cannot recur under another gate name

Adding four declarations protects four gates. It does not stop the next gate
shipping with a declaration that reaches nothing — and that is the shape the lead
had: the declaration and its absence print identically. A family whose
declared literals all reach zero tracked files scores an ordinary silence, byte
for byte the output of a gate that declared nothing, and no one reading the brief
can tell them apart.

The derivation already computes this fact. It prints it under --residue:

0 of the 199 declare a population that reaches NOTHING in the tree, swept over 7757 tracked file(s) […] A standing repo fact, not a verdict about your paths

scripts/check-declared-population-live.mjs gives that standing fact a verdict.
No new instrument, no allowlist, no ratchet, and no gate name in it.

The rule is per-family, not per-literal: a gate may legitimately spell a slug,
a sentinel path or an example inside a message beside a live declaration, and
only a whole declaration that reaches nothing is refused.

Proven able to red, by ablation (mutation and restore both confirmed on
disk — git hash-object against the HEAD blob, before and after):

$ git checkout 45b9051248 -- scripts/measure-stall-guard-headroom.mjs
$ grep -c ROOT_DIR_WATCH_HINTS scripts/measure-stall-guard-headroom.mjs # 0
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 73d8e93d… ≠ HEAD 18e5cd8f…
$ node scripts/check-declared-population-live.mjs # exit 1
✗ check:declared-population-live: 1 of 155 declaring famil(ies) declare a population that reaches NOTHING in this tree.
check:stall-guard-headroom
declares: objectstack-ai/objectstack
$ git checkout HEAD -- scripts/measure-stall-guard-headroom.mjs
$ git diff HEAD # empty
$ git hash-object scripts/measure-stall-guard-headroom.mjs # 18e5cd8f… = HEAD blob
$ node scripts/check-declared-population-live.mjs # exit 0

The stronger rule was implemented and refused

"A gate that enumerates a directory must carry a watch-hint declaration" is the
rule that suggests itself first. Measured at 45b9051248: of 193 discovered gate
files, 114 enumerate a directory and 86 of those carry no declaration under any
spelling of the idiom
. A gate shipping 86 findings is an allowlist with a
verdict attached — and an allowlist of gate names is the repair this lane has
already ruled against three times. Most of the 86 are not defects either: they
declare their population as ordinary path literals, invisible to a scan for the
idiom. Refused, and recorded here rather than in a follow-up, because the refusal
is part of the argument for the rule that shipped.

The second dead declaration the new gate found

The sweep found exactly two families in the fleet. The other is
check:pm-clause2-carriers, whose only path-shaped literal was the
`owner/name` placeholder inside a refusal message. That gate reads no
file in the tree at all
— its whole input is the GitHub API — so it takes the
gate's own remedy (b): the example is respelled `owner`/`name` so it
is no longer read as a declaration, and the gate now carries a
dispatch-gates: no-path-population marker with its reason. The seven-member
repair set is untouched; this is the guard's own remedy applied to the one other
carrier the guard names, and without it the guard could not ship green.

Changeset

skip-changeset: nothing here is published from any package. The diff is gate
scripts, one workflow step, one root package.json script entry, and
packages/spec/scripts/build-skill-docs.tspackages/spec's files field
publishes dist, json-schema, liveness, prompts, llms.txt, README.md,
src/**/*.zod.ts, api-surface and spec-changes.json, and not scripts/.

Gates run — at 04b7f769, the final commit

The family was derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands
(59 commands, re-derived after the last commit and unchanged), plus
node scripts/check-ratchet-remedy-authority.mjs. All 60 pass, except four
that report PREREQUISITE NOT MET because this container's workspace is not built
check-dev-prereqs (exit 1, "the workspace is not built"),
check-test-completeness (exit 3, no saved turbo log — its own text says to
record this as NOT MEASURED), check:dual-build-cjs-loads (exit 3, no dist/)
and check:type-check-debt (exit 3, closure not built). Each exit code was
captured before any pipe. None is a finding.

bare-root-worklist --self-test was a genuine red on the first draft and is
green here — see the check:skill-docs section above for what it caught.

Also run, beyond the derived family: pnpm lint over the whole repo (exit 0, no
narrowing needed), pnpm --filter @objectstack/spec exec tsc --noEmit (exit 0)
and — because --listFiles shows the edited .ts is not in that program —
tsc -p tsconfig.scripts.json --noEmit --listFiles, which does include it
(1 match) and is clean.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 da839ba11697703eb9391e36525fd5dd23242a63packageMentionDocs.

@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file tooling labels Sep 1, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 11:08
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 987fe37Sep 1, 2026
42 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-13519-gate-population-underdeclare branch September 1, 2026 11:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@baozhoutao@claude