You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] Nothing enforces the form-section name convention in content/docs/** examples — os:check passes because name is .optional() #10830
Found while implementing #10709 (PR #10827). Filing separately — adding a gate is a
different defect class from adding a name attribute, and it would widen that PR's
verification surface.
The observation
Form-section examples in content/docs/** can drift from the name convention
indefinitely without any gate reddening. Two mechanisms look like they would catch it,
and neither can:
packages/lint's translation-section-name-missing
(packages/lint/src/validate-translatable-sections.ts:95) walks app metadata — collectionEntries, walkPageComponents, viewContainerSites. It never sees an .mdx code fence. Its severity is 'warning' in any case.
{/* os:check */} fencesare type-checked against the live spec by packages/spec/scripts/check-skill-examples.ts. But name is .optional() in packages/spec/src/ui/component.zod.ts:825, so a nameless section type-checks
clean. content/docs/ui/forms.mdx's first block carries an os:check marker and
still shipped a nameless section.
So the docs surface that teaches the convention is the one surface where the convention
is unenforced.
Why it looks like a recurring class rather than a one-off
The same defect has now been counted three times, by hand, each time with a different
number:
8 of 16 across 5 pages (pre-sweep, measured at b6bb2ee454^)
—
Each pass corrected its predecessor's instrument and introduced a subtler version of the
same error: #10579's scan was single-line, #10709's was multi-line but still required {
to begin a fresh line, so it missed forms.mdx's sections: [{ inline form. A mechanical
check would settle the count once instead of re-deriving it per card.
Possible shapes (not a recommendation — this needs triage)
Or narrower: have check:skill-examples apply a docs-only strictness overlay for keys
that are .optional() in the schema but conventional in authored examples.
Both are speculative on the cost side; the underlying observation (nothing enforces it)
is the part that is measured.
⚠️ Not proposing that name become required in the schema — it is .optional()
deliberately (a nameless section is legal and renders its authored label), and #10709's
own body treats that as settled. This is about the docs examples, not the contract.
Triage first-touch: promoted to queue, type Task. The class has been hand-counted three times with three different numbers — that is the evidence a mechanical check pays for itself. Dispatch constraints: ① choose the enforcement shape by measurement, and prefer a shape that lands outside packages/spec/** (e.g. the bracket-matching census parser from PR #10827 as a standalone docs gate) — if measurement argues for the check:skill-examples strictness-overlay shape instead, STOP and hand to the spec seat, since packages/spec/scripts/** is spec-lane surface (contract-first single owner); ② ⛔ name stays .optional() in the schema — that is settled in #10709 and not reopened here; this card is docs-examples enforcement only. Size/model suggestion: M.
Triage's two constraints govern and are carried verbatim into the dispatch:
① choose the enforcement shape by measurement, and prefer a shape that lands outside packages/spec/** (e.g. the bracket-matching census parser from PR #10827 as a standalone docs gate) — if measurement argues for the check:skill-examples strictness-overlay shape instead, STOP and hand to the spec seat, since packages/spec/scripts/** is spec-lane surface (contract-first single owner);
② ⛔ name stays .optional() in the schema — settled in #10709, not reopened here; this card is docs-examples enforcement only.
Why this one earns a gate rather than another sweep
The card's strongest argument is its own history — the same defect hand-counted three times, three different numbers, each pass correcting its predecessor's instrument and introducing a subtler version of the same error:
Three hand-derivations, three answers, and each instrument was defeated by a spelling the previous one had not imagined. That is exactly the case where a mechanical check pays for itself: it settles the count once instead of re-deriving it per card, and the next spelling reddens instead of silently escaping.
⭐ Note the structural irony the card names, which is the real reason this matters: the docs surface that teaches the convention is the one surface where the convention is unenforced.packages/lint's translation-section-name-missing walks app metadata and never sees an .mdx fence (and is 'warning' severity anyway); {/* os:check */} fences are type-checked against the live spec, but name is .optional() at component.zod.ts:825, so a nameless section type-checks clean — content/docs/ui/forms.mdx's first block carries an os:check marker and still shipped one.
Zone 1
✅ Prefer the standalone docs-gate shape, outside packages/spec/**. PR docs(ui): name the 5 remaining nameless form sections in content/docs/ui #10827's bracket-matching census parser (~80 lines, in that PR's description) is the starting point — and bracket-matching rather than line-matching is the point, since line-matching is what defeated both earlier passes.
⛔ If measurement argues for the check:skill-examples strictness-overlay instead — STOP and report.packages/spec/scripts/** is the spec lane's surface under contract-first single ownership; that hand-off is triage's call, not yours and not mine.
Re-derive the 8-of-16 census on current main. It was measured at b6bb2ee454^ and PR docs(ui): name the 5 remaining nameless form sections in content/docs/ui #10827 then swept it — so the live population today is plausibly zero, which changes what the gate's green means and makes the anti-vacuity work load-bearing rather than decorative.
That name is still .optional() at component.zod.ts:825, and that os:check fences still type-check without it.
⚠️If the population is now zero, the gate's verdict must report its judged population so a zero is a measurement rather than a silence — the failure mode this whole card is about, one level up. PR #11667 (scripts/check-agent-test-spelling.mjs) solved exactly this and is worth reading: it judges 6 separators, reports that number, and its zero means something because of it.
Model tier: opus. Live --tier run reports no path-derived mandate; triage suggested M.
{
"issue": 10830,
"status": "done",
"branch": "claude/issue-10830-docs-section-name-gate",
"pr": "https://github.com/objectstack-ai/objectstack/pull/11890",
"premise_still_valid": true,
"summary": "Added scripts/check-docs-section-name.mjs, a standalone docs gate (outside packages/spec/**, per Zone 1 ruling 1) that bracket-matches `sections: [ ... ]` arrays in TS-family fences under content/docs/** and requires `name` on every section object literal; wired it into lint.yml as a direct `node scripts/...` step next to the react-page-adapter step. Zone 1 ruling 2 was NOT triggered: measurement argued AGAINST the check:skill-examples overlay, because only one of the eight affected pages carries an os:check marker, so an overlay would have reached a SMALLER corpus than a standalone sweep. Zone 1 ruling 3 honoured: no contract file is in the diff; `name` stays .optional(). ⭐ ZONE 2 FALSIFIED, and in the load-bearing direction: the live population is NOT zero, it is 6 nameless literals — and all 6 sit OUTSIDE content/docs/ui/**, in content/docs/concepts/architecture.mdx and content/docs/protocol/objectui/{concept,index}.mdx. The real corpus is 22 literals across 8 pages, not 16 across 5. All three earlier hand-passes (#10579, #10709, PR #10827) scoped to content/docs/ui/**, so the card's history has a FOURTH instrument error of the same class — this one in the SCOPE rather than the matcher, and shared by all three passes rather than corrected by each. PR #10827 was correct about what it measured; it just never looked outside ui/. The 6 sites are swept in this PR using the convention read off the 16 named siblings (snake_case from the label, placed first). CENSUS_ANCHORS pins architecture.mdx, so re-narrowing the gate's scope back to ui/ makes it REFUSE rather than pass. Second premise holds with a correction: `name` is still .optional() but at component.zod.ts:853, not :825 — the line drifted, the fact did not.",
"tests": "ALL EVIDENCE AT FINAL COMMIT 4fbb12069. Exit codes captured before any pipe (`cmd > file 2>&1; e=$?`); no verdict is read from a bare $? after a tail.\n\nGATE FAMILIES: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no paths — it derived its own 5-path change set) named 35 families. 34 green at 4fbb12069, including check:doc-anchors, check:doc-authoring, check:docs-audit-scope, check:docs-redirects, check:role-word, check:published-readme-links, check:react-page-adapter-contract, check:cross-package-test-inputs, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check:agent-test-spelling, check:required-contexts, check:step-collectors, check:aggregator-roster, check:shard-attestation, check:workflow-status-functions, check:node-version, check:pnpm-acquisition, check:type-check-coverage, check:nul-bytes, check-doc-frontmatter, check-section-landing-index, the 4 @objectstack/spec families (empty-state, liveness, strictness-ledger, variant-docs) and the 2 @objectstack/lint families (doc-formula-expressions, doc-security-posture).\n\nFAIL-BEFORE / PASS-AFTER (same tree, same script): before the sweep `EXIT=1 6 form-section example(s) ... have no name`; after `EXIT=0 0 nameless ... 22 section literal(s) JUDGED` across 8 pages.\n\nPLANTED POSITIVE CONTROL on a real page: a nameless section injected into content/docs/ui/forms.mdx under `trap ... EXIT INT TERM`. Proven on disk BEFORE reading the result — planted marker occurrences AFTER=1, sha CHANGED=YES. Gate EXIT=1 naming `content/docs/ui/forms.mdx:68`, and `grep -n` confirms the plant really landed on line 68, so the reported line is measured, not assumed. Restore byte-identical (sha 378540337f78 == baseline), post-restore gate EXIT=0.\n\nABLATION — 3 legs. ⚠️ NO BUILD/dist STEP APPLIES AND THIS IS NOT A SKIPPED STEP: the gate is a plain scripts/*.mjs executed directly by node, with no package `exports` resolution and no dist/ anywhere in its path, so there is no stale-artifact hazard to rebuild past. What replaces it is stated per leg. Each leg: mutate -> PROVE ON DISK -> `node --check` the mutant -> run -> restore -> prove restored. All under `trap '<restore>' EXIT INT TERM`.\n On-disk proof per leg (never an editor exit code): anchor occurrences BEFORE >=1, anchor occurrences AFTER = 0, injected-text occurrences AFTER >= 1, and file sha256 CHANGED = YES. A leg whose anchor count was 0 would have aborted as a void reading rather than run.\n `node --check` passed on every mutant BEFORE it ran, so none of the reds below can be a load error wearing the shape of proof.\n LEG A (rule ablated, `named` always true): self-test 9 of 25 cases fail.\n LEG B (scope narrowed back to content/docs/ui — the narrowing all three hand-passes made): 12 of 25 fail, including `a clean corpus passes — got 2 (REFUSED), want 0`. This is the sharp leg: it proves the census anchor guarding against re-narrowing actually bites.\n LEG C (bracket matcher degraded to line matching — the #10709 defeat): 5 of 25 fail, including `an inline sections: [{ is judged (the #10709 miss)`.\n Restore leg proven for all three: BYTE-IDENTICAL RESTORE = YES (sha back to 11c6832362...), and post-restore self-test EXIT=0 each time. Final tree sha equals baseline sha.\n ⚠️ HONEST NOTE — the FIRST ablation run was VOID and is reported rather than quietly re-run: all three legs reddened by `TypeError: Cannot read properties of undefined (reading 'file')`, i.e. the harness crashed on `findings[0]` over an empty array. A crash-red reads exactly like assertion-red. The self-test was fixed (NONE / at() fallbacks, with the reason recorded in the file next to them) and the ablation re-run; only the re-run is quoted above.\n\nTWO DECLARED NARROWINGS (measurements, not skips):\n 1. check:type-check-debt --re-measure REFUSED, not run. Evidence, all from the tool: (a) its sibling leg was green in the SAME invocation — `✓ --self-test 47+59+29+28+19 case(s) hold` then `OK — 65/78 packages type-checked, 13 in DEBT (436 frozen raw errors), 1 exempt`; (b) the gate names the refusing leg itself and refuses because 52 workspace deps have no built type entry point, warning it would 'silently measure a DIFFERENT WORLD'; (c) INVARIANCE — the ledger's population is packages/**, and `git diff --name-only $(git merge-base HEAD origin/main)...HEAD | grep -c '^packages/'` = 0, so no ledgered number can move.\n 2. `pnpm lint` (repo-wide eslint) narrowed to the diff. (a) Population read from eslint's OWN config, not guessed: run per-path, the 3 .mdx files and lint.yml each return `File ignored because no matching configuration was supplied`, so exactly 1 of 5 changed files is in eslint's population; (b) count from --format json: 1 file linted, errorCount=0, warningCount=0; (c) invariance for untouched files quoted from eslint.config.mjs:327 — 'this repo runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file', so per-file verdicts are independent and this diff cannot move an untouched file's result.\n\nCONTROL CHARACTERS: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over all 5 changed files -> exit 1, no matches; check:nul-bytes green over 6,615 files.\n\nCLOSING-KEYWORD AUDIT: PR body carries exactly one closing keyword, `Fixes #10830`; #11887 is referenced with the negation-safe form ('#11887 is not addressed here'). No commit message carries a card trailer, so the squash is safe.\n\nskip-changeset: applied via the additive endpoint and READ BACK after the bots settled — labels now ['documentation','ci/cd','size/l','skip-changeset'], so it survived the size-labeler's group write.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #11887: 21 of 24 form-section examples in YAML fences under content/docs/protocol/objectui/** have no `name` — outside this gate's declared TS-fence scope (a YAML block is a different parser, not a bigger regex). The gate DECLARES and COUNTS this gap in its verdict line (`16 YAML fence(s) declared out of scope`) rather than skipping it silently. ⚠️ The card records two numbers on purpose: a hand-rolled indentation walker said 51, a real `yaml` parse said 21 — an instrument error of exactly the class #10830 is about, caught by re-measuring with a parser.",
"not filed, reported here as a PM judgment call: `packages/lint`'s `translation-section-name-missing` (validate-translatable-sections.ts) is 'warning' severity over app metadata. Whether that should become an error is a contract-adjacent question the card explicitly fenced off (`name` stays .optional()), so I did not file it as a defect — flagging it only because it is the third mechanism a reader will assume already covers this."
]
}
Activity
huangyiirene commentedon Aug 21, 2026
Triage first-touch: promoted to queue, type Task. The class has been hand-counted three times with three different numbers — that is the evidence a mechanical check pays for itself. Dispatch constraints: ① choose the enforcement shape by measurement, and prefer a shape that lands outside
packages/spec/**(e.g. the bracket-matching census parser from PR #10827 as a standalone docs gate) — if measurement argues for thecheck:skill-examplesstrictness-overlay shape instead, STOP and hand to the spec seat, sincepackages/spec/scripts/**is spec-lane surface (contract-first single owner); ② ⛔namestays.optional()in the schema — that is settled in #10709 and not reopened here; this card is docs-examples enforcement only. Size/model suggestion: M.Generated by Claude Code
os-steve commentedon Aug 24, 2026
Claim:
domain:devxPM seat, sessionsession_015ahemw8RcTgqtxrj15PEZx, branchclaude/issue-10830-docs-section-name-gate.Triage's two constraints govern and are carried verbatim into the dispatch:
Why this one earns a gate rather than another sweep
The card's strongest argument is its own history — the same defect hand-counted three times, three different numbers, each pass correcting its predecessor's instrument and introducing a subtler version of the same error:
ui/pages"forms.mdxentirely; scan was single-line{to begin a fresh line, so it missedforms.mdx's inlinesections: [{b6bb2ee454^)Three hand-derivations, three answers, and each instrument was defeated by a spelling the previous one had not imagined. That is exactly the case where a mechanical check pays for itself: it settles the count once instead of re-deriving it per card, and the next spelling reddens instead of silently escaping.
⭐ Note the structural irony the card names, which is the real reason this matters: the docs surface that teaches the convention is the one surface where the convention is unenforced.
packages/lint'stranslation-section-name-missingwalks app metadata and never sees an.mdxfence (and is'warning'severity anyway);{/* os:check */}fences are type-checked against the live spec, butnameis.optional()atcomponent.zod.ts:825, so a nameless section type-checks clean —content/docs/ui/forms.mdx's first block carries anos:checkmarker and still shipped one.Zone 1
packages/spec/**. PR docs(ui): name the 5 remaining nameless form sections in content/docs/ui #10827's bracket-matching census parser (~80 lines, in that PR's description) is the starting point — and bracket-matching rather than line-matching is the point, since line-matching is what defeated both earlier passes.check:skill-examplesstrictness-overlay instead — STOP and report.packages/spec/scripts/**is the spec lane's surface under contract-first single ownership; that hand-off is triage's call, not yours and not mine.namestays.optional()in the schema. A nameless section is legal and renders its authored label; [finding] form-sectionname: the ui/ census is 7 of 15 across 5 pages, not "three of five" — #10579 missed forms.mdx entirely #10709 settled it. This card is docs-examples enforcement only, and ⛔ nothing in it touches the contract.Zone 2 — assumptions to falsify
main. It was measured atb6bb2ee454^and PR docs(ui): name the 5 remaining nameless form sections in content/docs/ui #10827 then swept it — so the live population today is plausibly zero, which changes what the gate's green means and makes the anti-vacuity work load-bearing rather than decorative.nameis still.optional()atcomponent.zod.ts:825, and thatos:checkfences still type-check without it.scripts/check-agent-test-spelling.mjs) solved exactly this and is worth reading: it judges 6 separators, reports that number, and its zero means something because of it.Model tier: opus. Live
--tierrun reports no path-derived mandate; triage suggested M.Generated by Claude Code
claude commentedon Aug 24, 2026
{ "issue": 10830, "status": "done", "branch": "claude/issue-10830-docs-section-name-gate", "pr": "https://github.com/objectstack-ai/objectstack/pull/11890", "premise_still_valid": true, "summary": "Added scripts/check-docs-section-name.mjs, a standalone docs gate (outside packages/spec/**, per Zone 1 ruling 1) that bracket-matches `sections: [ ... ]` arrays in TS-family fences under content/docs/** and requires `name` on every section object literal; wired it into lint.yml as a direct `node scripts/...` step next to the react-page-adapter step. Zone 1 ruling 2 was NOT triggered: measurement argued AGAINST the check:skill-examples overlay, because only one of the eight affected pages carries an os:check marker, so an overlay would have reached a SMALLER corpus than a standalone sweep. Zone 1 ruling 3 honoured: no contract file is in the diff; `name` stays .optional(). ⭐ ZONE 2 FALSIFIED, and in the load-bearing direction: the live population is NOT zero, it is 6 nameless literals — and all 6 sit OUTSIDE content/docs/ui/**, in content/docs/concepts/architecture.mdx and content/docs/protocol/objectui/{concept,index}.mdx. The real corpus is 22 literals across 8 pages, not 16 across 5. All three earlier hand-passes (#10579, #10709, PR #10827) scoped to content/docs/ui/**, so the card's history has a FOURTH instrument error of the same class — this one in the SCOPE rather than the matcher, and shared by all three passes rather than corrected by each. PR #10827 was correct about what it measured; it just never looked outside ui/. The 6 sites are swept in this PR using the convention read off the 16 named siblings (snake_case from the label, placed first). CENSUS_ANCHORS pins architecture.mdx, so re-narrowing the gate's scope back to ui/ makes it REFUSE rather than pass. Second premise holds with a correction: `name` is still .optional() but at component.zod.ts:853, not :825 — the line drifted, the fact did not.", "tests": "ALL EVIDENCE AT FINAL COMMIT 4fbb12069. Exit codes captured before any pipe (`cmd > file 2>&1; e=$?`); no verdict is read from a bare $? after a tail.\n\nGATE FAMILIES: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no paths — it derived its own 5-path change set) named 35 families. 34 green at 4fbb12069, including check:doc-anchors, check:doc-authoring, check:docs-audit-scope, check:docs-redirects, check:role-word, check:published-readme-links, check:react-page-adapter-contract, check:cross-package-test-inputs, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check:agent-test-spelling, check:required-contexts, check:step-collectors, check:aggregator-roster, check:shard-attestation, check:workflow-status-functions, check:node-version, check:pnpm-acquisition, check:type-check-coverage, check:nul-bytes, check-doc-frontmatter, check-section-landing-index, the 4 @objectstack/spec families (empty-state, liveness, strictness-ledger, variant-docs) and the 2 @objectstack/lint families (doc-formula-expressions, doc-security-posture).\n\nFAIL-BEFORE / PASS-AFTER (same tree, same script): before the sweep `EXIT=1 6 form-section example(s) ... have no name`; after `EXIT=0 0 nameless ... 22 section literal(s) JUDGED` across 8 pages.\n\nPLANTED POSITIVE CONTROL on a real page: a nameless section injected into content/docs/ui/forms.mdx under `trap ... EXIT INT TERM`. Proven on disk BEFORE reading the result — planted marker occurrences AFTER=1, sha CHANGED=YES. Gate EXIT=1 naming `content/docs/ui/forms.mdx:68`, and `grep -n` confirms the plant really landed on line 68, so the reported line is measured, not assumed. Restore byte-identical (sha 378540337f78 == baseline), post-restore gate EXIT=0.\n\nABLATION — 3 legs. ⚠️ NO BUILD/dist STEP APPLIES AND THIS IS NOT A SKIPPED STEP: the gate is a plain scripts/*.mjs executed directly by node, with no package `exports` resolution and no dist/ anywhere in its path, so there is no stale-artifact hazard to rebuild past. What replaces it is stated per leg. Each leg: mutate -> PROVE ON DISK -> `node --check` the mutant -> run -> restore -> prove restored. All under `trap '<restore>' EXIT INT TERM`.\n On-disk proof per leg (never an editor exit code): anchor occurrences BEFORE >=1, anchor occurrences AFTER = 0, injected-text occurrences AFTER >= 1, and file sha256 CHANGED = YES. A leg whose anchor count was 0 would have aborted as a void reading rather than run.\n `node --check` passed on every mutant BEFORE it ran, so none of the reds below can be a load error wearing the shape of proof.\n LEG A (rule ablated, `named` always true): self-test 9 of 25 cases fail.\n LEG B (scope narrowed back to content/docs/ui — the narrowing all three hand-passes made): 12 of 25 fail, including `a clean corpus passes — got 2 (REFUSED), want 0`. This is the sharp leg: it proves the census anchor guarding against re-narrowing actually bites.\n LEG C (bracket matcher degraded to line matching — the #10709 defeat): 5 of 25 fail, including `an inline sections: [{ is judged (the #10709 miss)`.\n Restore leg proven for all three: BYTE-IDENTICAL RESTORE = YES (sha back to 11c6832362...), and post-restore self-test EXIT=0 each time. Final tree sha equals baseline sha.\n ⚠️ HONEST NOTE — the FIRST ablation run was VOID and is reported rather than quietly re-run: all three legs reddened by `TypeError: Cannot read properties of undefined (reading 'file')`, i.e. the harness crashed on `findings[0]` over an empty array. A crash-red reads exactly like assertion-red. The self-test was fixed (NONE / at() fallbacks, with the reason recorded in the file next to them) and the ablation re-run; only the re-run is quoted above.\n\nTWO DECLARED NARROWINGS (measurements, not skips):\n 1. check:type-check-debt --re-measure REFUSED, not run. Evidence, all from the tool: (a) its sibling leg was green in the SAME invocation — `✓ --self-test 47+59+29+28+19 case(s) hold` then `OK — 65/78 packages type-checked, 13 in DEBT (436 frozen raw errors), 1 exempt`; (b) the gate names the refusing leg itself and refuses because 52 workspace deps have no built type entry point, warning it would 'silently measure a DIFFERENT WORLD'; (c) INVARIANCE — the ledger's population is packages/**, and `git diff --name-only $(git merge-base HEAD origin/main)...HEAD | grep -c '^packages/'` = 0, so no ledgered number can move.\n 2. `pnpm lint` (repo-wide eslint) narrowed to the diff. (a) Population read from eslint's OWN config, not guessed: run per-path, the 3 .mdx files and lint.yml each return `File ignored because no matching configuration was supplied`, so exactly 1 of 5 changed files is in eslint's population; (b) count from --format json: 1 file linted, errorCount=0, warningCount=0; (c) invariance for untouched files quoted from eslint.config.mjs:327 — 'this repo runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file', so per-file verdicts are independent and this diff cannot move an untouched file's result.\n\nCONTROL CHARACTERS: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over all 5 changed files -> exit 1, no matches; check:nul-bytes green over 6,615 files.\n\nCLOSING-KEYWORD AUDIT: PR body carries exactly one closing keyword, `Fixes #10830`; #11887 is referenced with the negation-safe form ('#11887 is not addressed here'). No commit message carries a card trailer, so the squash is safe.\n\nskip-changeset: applied via the additive endpoint and READ BACK after the bots settled — labels now ['documentation','ci/cd','size/l','skip-changeset'], so it survived the size-labeler's group write.", "open_questions": [], "out_of_scope_findings": [ "filed as #11887: 21 of 24 form-section examples in YAML fences under content/docs/protocol/objectui/** have no `name` — outside this gate's declared TS-fence scope (a YAML block is a different parser, not a bigger regex). The gate DECLARES and COUNTS this gap in its verdict line (`16 YAML fence(s) declared out of scope`) rather than skipping it silently. ⚠️ The card records two numbers on purpose: a hand-rolled indentation walker said 51, a real `yaml` parse said 21 — an instrument error of exactly the class #10830 is about, caught by re-measuring with a parser.", "not filed, reported here as a PM judgment call: `packages/lint`'s `translation-section-name-missing` (validate-translatable-sections.ts) is 'warning' severity over app metadata. Whether that should become an error is a contract-adjacent question the card explicitly fenced off (`name` stays .optional()), so I did not file it as a defect — flagging it only because it is the third mechanism a reader will assume already covers this." ] }Generated by Claude Code
feat(gate): give check-docs-section-name a real YAML parser arm
Sweep the nameless YAML form-section examples and give the gate a rea…