Sweep the nameless YAML form-section examples and give the gate a real YAML parser arm - #13761
Conversation
…chor Sweep of the 13 nameless form-section mappings in YAML fences under content/docs/protocol/objectui/**, re-derived with the `yaml` parser on 787d757. A nameless section has no i18n anchor -- its heading resolves through `objects.OBJECT._sections.NAME.label`, so it renders its authored label in every locale. Names are the snake_case of each label; the one deviation is the section labelled "Name" in layout-dsl.mdx, named `full_name` so the teaching example does not read `name: name` beside a `name` field. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Widens the gate from a TS-fence-only rule to two arms over one corpus. YAML fences are handed to the `yaml` package -- the same parser the docs build resolves -- and judged on its AST, never on a regex. The AST, not `toJS()`: a JS projection collapses duplicate keys, and duplicate keys are the normal shape of a teaching fence that concatenates two snippets. Judging the projection would have hidden 4 real nameless sections behind the key that won. Decides what #10830 deferred. `yaml` recovers from nearly everything, so "did it throw" is not the question. A fence whose every error is SEMANTIC (DUPLICATE_KEY -- a complete, unguessed tree) is JUDGED and counted as recovered; a fence with any SYNTAX error is NOT judged, but is counted and printed, because a finding drawn from a guessed tree is a fabricated finding. That skip population is empty on this tree, so --self-test carries it rather than the corpus. Also corrects the docblock's stale census: 21-across-16 was true at 4019e16 and is not true now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33382565799 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Stand-down: this queue failure is not this PR's — re-queuing once, and ⛔ not changing the PR
Why it is not this PR's
⭐ The reason line is the whole diagnosis, and the triage workflow says so itself: a timeout and an Corroborated independently: the same test file ejected #13687 in the same 24h window, an ⛔ No fix exists to port, so there is nothing to carry into this PR#13767 was filed minutes ago by the triage workflow and is explicit that it is "a NAME, not a What happens nowOne re-queue, and only one. ⛔ If it ejects again on this same signature, the second failure is Generated by Claude Code |
Fixes #11887
Both halves of the declared gap the card names: the sweep, and a real parser arm on
scripts/check-docs-section-name.mjsso the swept surface stays swept.1. The census, re-derived — and the card's number had moved
yamlpackage (the sameparser the census used), walking every
sections:sequence for mapping items:sections:4019e16cd(the card's ref)787d75740(branch point)⭐ The instrument is validated against the card's own arithmetic, not merely asserted.
Re-running this parser over
4019e16cdreturns exactly the 21-across-16 the card reports,so the card's count was sound and the drop to 13 is a real change in the tree — #13337 and
#13532 rewrote
layout-dsl.mdx(17 nameless → 5) after the card was filed. The card'swarning about its own hand-rolled 51 stands; nothing here is hand-counted.
non-parsing fences as "elided fragments". They are not, at either ref: they fail on
DUPLICATE_KEY, becauseconcept.mdxandindex.mdxeach concatenate two teachingsnippets into one fence, so
layout:appears twice. That distinction is the whole of §3.The gate's own docblock carried the stale
21 across 16too, and moves in this PR — aspredicted.
2. The sweep — per item, before → after
Names are the snake_case of each label. Line numbers are at the branch point.
protocol/objectui/concept.mdx- label: Contact Information- name: contact_informationprotocol/objectui/concept.mdx- label: Address- name: addressprotocol/objectui/concept.mdx- label: Basic Info- name: basic_infoprotocol/objectui/concept.mdx- label: Basic Info- name: basic_infoprotocol/objectui/concept.mdx- label: Advanced Settings- name: advanced_settingsprotocol/objectui/index.mdx- label: Basic Information- name: basic_informationprotocol/objectui/index.mdx- label: Contact Info- name: contact_infoprotocol/objectui/index.mdx- fields: [name, email, status](no label)- name: basic_informationprotocol/objectui/layout-dsl.mdx- label: Contact Information- name: contact_informationprotocol/objectui/layout-dsl.mdx- label: Basic Info- name: basic_infoprotocol/objectui/layout-dsl.mdx- label: Billing Information- name: billing_informationprotocol/objectui/layout-dsl.mdx- label: Name- name: full_name⬅ deviation, see belowprotocol/objectui/layout-dsl.mdx- label: Address- name: addressThe one deviation from mechanical derivation.
label: Namewould derivename: name.That section groups
first_nameandlast_name, and- name: namein a page that alsoteaches
nameas a field is a teaching hazard, so it isfull_name. Every other row isthe label, lowercased and underscored.
⛔ Not a schema change.
namestays.optional()inpackages/spec(#10709,reaffirmed #10830). Nothing under
packages/spec/src/**is touched. A nameless sectionstays legal metadata; this is about examples.
3. The gate's YAML arm — a parser, and a decision
YAML fences are handed to the
yamlpackage and judged on its AST.⛔ The AST, not
toJS(). A projection to plain JS collapses duplicate keys, andduplicate keys are the normal shape of a multi-snippet teaching fence. Judging the
projection would have hidden 4 real nameless sections behind the key that won — the
three in
concept.mdx's progressive-disclosure fence and one inindex.mdx. That is not ahypothetical: it is the difference between 9 and 13 in the table above.
⭐ What an unparseable fence means — the decision #10830 explicitly deferred.
yamlisa recovering parser: it returns a tree for almost any input, including one it guessed at.
So "did it throw" is not the question. The error codes are:
SEMANTIC_ERROR_CODES, todayDUPLICATE_KEYalone) — the parse iscomplete and nothing was guessed; the document is just not a valid mapping.
⇒ JUDGED, and counted separately so the recovery is visible.
MISSING_CHAR,BAD_INDENT,TAB_AS_INDENT, …) — theparser recovered by guessing, so a finding drawn from that tree could be fabricated.
⇒ NOT judged, but counted and printed as a declared skip — the posture this file
already had for its out-of-scope population.
rather than staying silent. It is held by
--self-testfixtures instead of by the corpus,because a boundary with no live population is the kind that rots unnoticed.
The arm also brings its own floors (
FLOOR_YAML_FENCES,FLOOR_YAML_SECTIONS) and its ownYAML_CENSUS_ANCHORS, so an evaporated YAML corpus refuses rather than reading clean.4. The two self-test cases this inverts
The suite pinned
a YAML sections: fence is counted out of scope(1) and...and produces no finding(0). This arm makes both wrong by design. They are replaced in place, notdeleted, with a comment saying so, plus new cases for the duplicate-key shape, the
syntax-error skip, non-mapping seq items, the negative controls, and the two new refusals.
--self-testgoes 25 → 54 cases, all passing (base count measured by running thebranch-point copy of the script, not recalled).
5. Reverse verification — the arm is load-bearing
The gate is plain
.mjsrun bynodefrom the repo; its first-party imports are allrelative
./*.mjssiblings and there is noscripts/package.json, so there is no buildstep in the resolution path and no rebuild leg. Every leg proved its mutation reached disk
and its restore by observed state, never by an exit code.
name:back out ofconcept.mdxconcept.mdx:117, correct label, YAML-shaped fix textnamed: true)--self-testexit 1, 8 of 54 cases failRestore proven on both files by:
git diff HEADempty, blob hash equal to HEAD's, ablationmarker absent, swept text present. Both scripts carried
trap ... EXIT INT TERMwithabsolute paths. One earlier attempt at leg B hit an anchor miss and did not run; it is
reported rather than quietly retried until something landed.
6. Out-of-scope finding
Filed #13759 (unassigned):
layout-dsl.mdxalso teaches three form sections through asingular
section:mapping (theos:check-yaml FormSectionSchema key=sectionfences),and all three are nameless. Same defect class, different selector — judging it means
deciding which YAML keys introduce a section at all, which neither #10830 nor #11887
answered. ⛔ Not fixed here.
7. Verification
All 33 gate families derived by
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackat the final commit, plus both convention-triggered obligationsfor editing a gate script. 32 GREEN, 1 NOT MEASURED
(
check-test-completeness.mjsexits 3 — its own text: "Nothing was measured … It is NOT afinding"; it parses vitest summary output that no local run produced).
pnpm check:yaml-examplesis green, which is what validates the editedos:check-yamlfences against the live
FormSectionSchema.pnpm lint(repo-wide,--no-inline-config)exit 0 — no narrowing. The gate script has no separate test suite;
git grepfinds--self-testinlint.ymlas its only other reference.No changeset: this releases nothing from any package. The predecessor PR #10830 landed the
identical file surface (
content/docs/**+ this same script) with none.Generated by Claude Code