Skip to content

Sweep the nameless YAML form-section examples and give the gate a real YAML parser arm - #13761

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-11887-yaml-section-name-arm
Aug 31, 2026
Merged

Sweep the nameless YAML form-section examples and give the gate a real YAML parser arm#13761
os-project-manager merged 2 commits into
mainfrom
claude/issue-11887-yaml-section-name-arm

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #11887

Both halves of the declared gap the card names: the sweep, and a real parser arm on
scripts/check-docs-section-name.mjs so the swept surface stays swept.

1. The census, re-derived — and the card's number had moved

⚠️ Neither number in the card is today's. Re-derived with the yaml package (the same
parser the census used), walking every sections: sequence for mapping items:

ref yaml fences w/ sections: named NAMELESS
4019e16cd (the card's ref) 16 3 21
787d75740 (branch point) 10 6 13
this branch, after the sweep 10 19 0

The instrument is validated against the card's own arithmetic, not merely asserted.
Re-running this parser over 4019e16cd returns 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's
warning about its own hand-rolled 51 stands; nothing here is hand-counted.

⚠️ One thing in the card is wrong, and it changes the design. The card describes its two
non-parsing fences as "elided fragments". They are not, at either ref: they fail on
DUPLICATE_KEY, because concept.mdx and index.mdx each concatenate two teaching
snippets into one fence, so layout: appears twice. That distinction is the whole of §3.

The gate's own docblock carried the stale 21 across 16 too, and moves in this PR — as
predicted.

2. The sweep — per item, before → after

Names are the snake_case of each label. Line numbers are at the branch point.

page line before after
protocol/objectui/concept.mdx 117 - label: Contact Information - name: contact_information
protocol/objectui/concept.mdx 123 - label: Address - name: address
protocol/objectui/concept.mdx 571 - label: Basic Info - name: basic_info
protocol/objectui/concept.mdx 577 - label: Basic Info - name: basic_info
protocol/objectui/concept.mdx 579 - label: Advanced Settings - name: advanced_settings
protocol/objectui/index.mdx 168 - label: Basic Information - name: basic_information
protocol/objectui/index.mdx 372 - label: Contact Info - name: contact_info
protocol/objectui/index.mdx 432 - fields: [name, email, status] (no label) - name: basic_information
protocol/objectui/layout-dsl.mdx 345 - label: Contact Information - name: contact_information
protocol/objectui/layout-dsl.mdx 373 - label: Basic Info - name: basic_info
protocol/objectui/layout-dsl.mdx 376 - label: Billing Information - name: billing_information
protocol/objectui/layout-dsl.mdx 437 - label: Name - name: full_namedeviation, see below
protocol/objectui/layout-dsl.mdx 445 - label: Address - name: address

The one deviation from mechanical derivation. label: Name would derive name: name.
That section groups first_name and last_name, and - name: name in a page that also
teaches name as a field is a teaching hazard, so it is full_name. Every other row is
the label, lowercased and underscored.

Not a schema change. name stays .optional() in packages/spec (#10709,
reaffirmed #10830). Nothing under packages/spec/src/** is touched. A nameless section
stays legal metadata; this is about examples.

3. The gate's YAML arm — a parser, and a decision

YAML fences are handed to the yaml package and judged on its AST.

The AST, not toJS(). A projection to plain JS collapses duplicate keys, and
duplicate 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 in index.mdx. That is not a
hypothetical: it is the difference between 9 and 13 in the table above.

What an unparseable fence means — the decision #10830 explicitly deferred. yaml is
a 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 (SEMANTIC_ERROR_CODES, today DUPLICATE_KEY alone) — the parse is
    complete and nothing was guessed; the document is just not a valid mapping.
    JUDGED, and counted separately so the recovery is visible.
  • SYNTAX (everything else: MISSING_CHAR, BAD_INDENT, TAB_AS_INDENT, …) — the
    parser 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.

⚠️ That skip population is empty on this tree, and the verdict says so in those words
rather than staying silent. It is held by --self-test fixtures 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 own
YAML_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, not
deleted
, 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-test goes 25 → 54 cases, all passing (base count measured by running the
branch-point copy of the script, not recalled).

5. Reverse verification — the arm is load-bearing

The gate is plain .mjs run by node from the repo; its first-party imports are all
relative ./*.mjs siblings and there is no scripts/package.json, so there is no build
step 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.

leg mutation result
A strip one swept name: back out of concept.mdx gate exit 1 — names concept.mdx:117, correct label, YAML-shaped fix text
B break the arm (named: true) --self-test exit 1, 8 of 54 cases fail
B2 broken arm and the corpus defect reinstated gate exit 0 ⬅ the arm is exactly what catches it

Restore proven on both files by: git diff HEAD empty, blob hash equal to HEAD's, ablation
marker absent, swept text present. Both scripts carried trap ... EXIT INT TERM with
absolute 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.mdx also teaches three form sections through a
singular section: mapping (the os:check-yaml FormSectionSchema key=section fences),
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/objectstack at the final commit, plus both convention-triggered obligations
for editing a gate script. 32 GREEN, 1 NOT MEASURED
(check-test-completeness.mjs exits 3 — its own text: "Nothing was measured … It is NOT a
finding"; it parses vitest summary output that no local run produced).
pnpm check:yaml-examples is green, which is what validates the edited os:check-yaml
fences against the live FormSectionSchema. pnpm lint (repo-wide, --no-inline-config)
exit 0 — no narrowing. The gate script has no separate test suite; git grep finds
--self-test in lint.yml as 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

claude added 2 commits August 31, 2026 09:22
…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
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33382565799 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Temporal Conformance (live PG + MySQL) — 失败步骤: Run driver-sql suite against both live servers

    × drops the orphaned generated column, and keeps the one still carrying a constraint 6198ms
    FAIL  src/sql-driver-13056-orphan-shadow-column-cleanup.test.ts > a retired shadow-carried UNIQUE leaves no column behind (#13056) > drops the orphaned generated column, and keeps the one still carryi
      ↳ 失败原因: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 4 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 31, 2026
@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Stand-down: this queue failure is not this PR's — re-queuing once, and ⛔ not changing the PR

domain:devx PM seat (#6023), session session_01Pk26oZ12t5N1hwGW1m1MgC. Evicted from the merge
queue at 10:43:41Z (queue build
33382565799); the PR is
open, merged=false, so this is a real ejection and ⛔ not the queue closing out a merge.

Why it is not this PR's

failing job Temporal Conformance (live PG + MySQL)Run driver-sql suite against both live servers
failing test packages/drivers/driver-sql/src/sql-driver-13056-orphan-shadow-column-cleanup.test.ts
this PR's diff 3 .mdx under content/docs/protocol/objectui/ + scripts/check-docs-section-name.mjs — ⛔ zero files under packages/
failure reason Error: Test timed out in 5000ms. — the case measured 6198ms against a 5000ms limit

⭐ The reason line is the whole diagnosis, and the triage workflow says so itself: a timeout and an
assertion produce an identical FAIL line and opposite conclusions. This is a timeout, on an
async case driving two live database servers, in a package this diff does not touch and cannot
reach. ⇒ Load/timing, ⛔ not a behaviour change from this PR.

Corroborated independently: the same test file ejected #13687 in the same 24h window, an
unrelated PR — aggregated as #13767.

⛔ 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
diagnosis"
— no cause decided, no fix proposed, no PR open against it. ⇒ There is no change to
port, and ⛔ widening a docs PR with a driver-sql timing fix would cross into another lane
(packages/drivers/** is domain:engine) on top of being out of scope. The diagnosis this seat can
supply is posted on #13767 instead, where its readers are.

What happens now

One re-queue, and only one. ⛔ If it ejects again on this same signature, the second failure is
treated as real and this PR stops moving until #13767 has an answer — ⛔ no second speculative
re-queue, since every blind one rebuilds the whole queue behind it. ⛔ The PR is not being
changed to get green, and ⛔ no test is being skipped, quarantined or disabled.


Generated by Claude Code

@os-project-manager
os-project-manager added this pull request to the merge queue Aug 31, 2026
Merged via the queue into main with commit 8ab4ace Aug 31, 2026
35 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-11887-yaml-section-name-arm branch August 31, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants