Skip to content

docs content: page titles carry no search intent — median 14 characters, 325 of 403 under 20 #12237

Description

@os-zhuang

One-liner

Doc titles are bare feature nouns — Objects, Fields, Actions, Documentation. Median length is 14 characters; 325 of 403 are 20 characters or less. With | ObjectStack appended a typical SERP title is ~30 characters against a ~60-character budget, and the half that is ours carries no term anyone searches for.

Measured

title length:  n=403  median=14  max=50   <=20 chars: 325
e.g.  "Objects" · "Fields" · "Actions as Tools" · "Documentation" · "AI Overview"

Expected

Adopt one rule and apply it: <primary keyword> — <qualifier>, 50–60 characters including the | ObjectStack suffix. The primary keyword is the phrase a developer would type, not our internal noun:

now shape to aim for
Object Metadata Object metadata: define objects, fields and relationships
Actions as Tools Expose actions as MCP tools for AI agents
Documentation ObjectStack documentation: metadata-driven app framework

⚠️ This is maintainer-voice territory. The PR must open with the rule plus the full 403-row before/after table so the pattern can be judged in one read, and it stays open for maintainer review — do not self-merge it. If the rule is rejected, only the table is thrown away, not per-file work.

Note the Sidebar title and the SEO title do not have to be the same string — check whether content/docs/**/meta.json or the frontmatter can carry a separate short label for navigation before lengthening every sidebar entry.

Acceptance

  • one written rule, in the PR body, with the before/after table
  • sidebar navigation still reads cleanly (short labels preserved, by whatever mechanism)
  • no title exceeds 60 characters including the site suffix
  • pnpm check:doc-anchors and the link checker stay green

Source

Found in an SEO review of the docs site (apps/docs) run on 2026-08-25, measured against the local dev server and against production. The canonical origin is https://objectstack.ai — maintainer ruling recorded in #10659:

这个仓的文档站规范 URL 是 https://objectstack.ai

Activity

  1. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    ContributorAuthor

    Held — hard serial on content/docs/**. #12236 is editing the bodies of the same 403 files right now; frontmatter rewrites on top of an unlanded 205-file diff would conflict across the whole tree. Known trap to carry into the dispatch: #12236 is demoting 76 body H1s whose wording differs from the frontmatter title, and its report is asked to name any of those that read better than the title — that list is an input to this card, so read #12236's report before writing the mapping table.

  2. self-assigned this
    on Aug 25, 2026
  3. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    ContributorAuthor

    Claim: PM loop round 3 (epic PM for #12243)
    Session: session_f9f0958b-ab68-46cc-801c-216aa7ee2107
    Branch: claude/issue-12237-page-titles
    Worktree: objectstack-issue-12237
    Domain: domain:devx
    File surface: content/docs/**/*.mdx frontmatter title only, plus meta.json sidebar labels if a separate short label is needed (stop on breach; explain in the report)
    Container & model: L declared on the card, dispatched as mode:subagent with model: opus. ⚠️ DEVIATION, declared: an L card belongs on mode:cloud so it survives the PM session; cloud dispatch is not available from this seat. The risk is loss on session end, mitigated by the standing draft-PR-early requirement
    Clause-②: no
    Serial constraints cleared: content/docs/** is a hot file surface. PR #12262 (153 files) merged at 16:57:27Z, so the base is clean. #12238 (descriptions) edits the SAME frontmatter block in the SAME files and is held behind this card — hard serial, one content card at a time.

  4. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    ContributorAuthor

    os-dev claim (dispatched by the PM comment above)

  5. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    ContributorAuthor

    os-dev-report

    {
      "issue": 12237,
      "status": "needs_decision",
      "branch": "claude/issue-12237-page-titles",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12312",
      "premise_still_valid": true,
      "summary": "The defect is real and the card's statistics are RIGHT this time - I could not falsify them. Re-derived frontmatter-aware (reading only the leading --- block, so a title: line inside a body YAML fence can never count): n=403 median=14 max=50 <=20:325, exactly the card. The naive control (grep '^title:' over whole files) gives 405/14/50/327, so the fence-blind method missed by 2 hits and did NOT move the headline - reported as a null result rather than dressed up as a catch. Independently corroborated by two gates on the branch: check-doc-frontmatter prints 'content/docs 403' and check-docs-single-h1 prints '180 page(s) under content/docs/ (2 subtree(s) excluded)'. What the card's framing hides is the SPLIT: 214 of the 403 are under content/docs/references/** (generated, #12249) and 9 under content/docs/releases/** (CLAUDE.md hard stop, #12250), both barred here. THE EDITABLE SURFACE IS 180 FILES, NOT 403 - 55% of the population is unreachable from this card, and the barred references/** subtree (median 11, 207/214 under 20 chars) is what drags the headline median down. On the 180 I can touch, median is 18. TWO THINGS BLOCK MASS-EDITING, which is why this is needs_decision rather than done. (1) THE SIDEBAR PRECONDITION THE CARD SET RESOLVES NEGATIVE. The frontmatter title is FOUR consumers at once: the SERP <title> (generateMetadata + root layout template '%s | ObjectStack'), the on-page <h1> (<DocsTitle>{page.data.title}</DocsTitle>), the sidebar nav label (fumadocs-core buildFile(): name: title ?? pathToName(...)), and the llms.txt heading (getLLMText()). No field separates them: pageSchema declares exactly title/description/icon/full/_openapi and compiles to z.core.$strip, so an invented sidebarTitle: in frontmatter is SILENTLY DROPPED, not rejected; meta.json's title names the FOLDER, not a child page; and meta.json pages' [Label](url) form emits a bare link node with no $ref, losing description, icon and the page-tree binding. So lengthening a title to 50-60 chars lengthens the nav entry by exactly as much, which is the thing the card told me to check before doing. The fix is ~10 lines in apps/docs/source.config.ts + apps/docs/lib/source.ts - inside the epic's territory, outside this card's file surface, and on a file contended by cards in flight - so it is FILED (#12311, attached as a sub-issue of #12243), not taken. (2) TWO OF THE CARD'S THREE WORKED EXAMPLES BREACH THE CARD'S OWN 60-CHAR BOX: 'ObjectStack documentation: metadata-driven app framework' renders at 70 and 'Object metadata: define objects, fields and relationships' at 71. '50-60 chars INCLUDING the 14-char suffix' leaves a 36-46 char budget for the frontmatter string; the examples were written to 50-60 EXCLUDING it. The rule in the PR body is restated at 36-46 and every one of the 180 proposals obeys it. WHAT SHIPPED: 4 files, each 1+/1-. Every file in the diff either has NO sidebar exposure or SHORTENS its label, so there is zero nav regression by construction, not by judgement. buildFolder() omits a folder's index.mdx from children unless meta.json pages names 'index', and the folder label comes from meta.json title - enumerated across all 19 editable folders, EXACTLY 3 pages qualify (content/docs/index.mdx, protocol/objectql/index.mdx, protocol/objectui/index.mdx); the other 16 folders list 'index' explicitly. The 4th file, protocol/objectui/record-alert.mdx, was already 64 chars rendered - over the card's own hard limit BEFORE this PR - and its replacement is 52, so its sidebar label gets shorter too. Worth noting: protocol/objectql/meta.json ('Data Protocol') and protocol/objectui/meta.json ('UI Protocol') already demonstrate the short-label/long-title split the card is asking to generalise. The PR body carries the rule, the four-consumer mechanism finding with source citations, the re-derived statistics and the COMPLETE 180-row before/after table (median 32 -> 54, max 64 -> 60, 180/180 in the 50-60 band, 0 over 60, 0 duplicates). Left draft with an explicit do-not-merge line. description untouched (#12238's block). CORRECTION to a fact carried into this card from #12239: 'AI-built' is NOT zero - it appears 35 times across 18 files repo-wide (packages/objectql, packages/metadata-protocol, docs/adr). It is zero under content/docs/** and apps/docs/**, presumably the surface that was measured. The no-fourth-spelling ruling still stands, but it stands on taste, not on absence. The vocabulary anchor for titles is 'AI-written', from the homepage title landed in #12284.",
      "tests": "All at pushed head ed3afdfda, git status --porcelain empty, re-run IN FULL at that final commit. GATE FAMILY re-derived from the actual diff, not from the dispatch list: 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' (4 paths vs merge base, three-dot). Its FIRST run warned 'STALE TREE - at least 3 commit(s) behind origin/main' and named .github/workflows/lint.yml as changed in that range, so origin/main was fetched and merged (fast-forward af58a6fbc -> 5ce5f8c12) and the derivation re-run before anything was trusted - 24 families matched. 25 GATES RUN (the 24 plus check:nul-bytes), EVERY EXIT CODE CAPTURED BEFORE ANY PIPE (each redirected to its own file, $? read immediately, never through tail), ALL exit=0: check:doc-anchors, check:doc-authoring, check:docs-audit-scope, check:docs-redirects, check:docs-single-h1, check:published-readme-links, check:react-page-adapter-contract, check:release-notes, check:role-word, check:cross-package-test-inputs, check:nul-bytes, check-ci-filter-parity, check-cross-package-test-inputs, check-doc-frontmatter, check-doc-route-spelling, check-docs-section-name, check-section-landing-index, @objectstack/lint check:doc-formula-expressions, @objectstack/lint check:doc-security-posture, @objectstack/spec check:docs, @objectstack/spec check:skill-examples, @objectstack/spec check:empty-state, @objectstack/spec check:liveness, @objectstack/spec check:strictness-ledger, @objectstack/spec check:variant-docs. VERDICT LINES quoted rather than exit codes: 'check-doc-anchors: 278 internal #fragment link(s) across 408 source file(s) all resolve to a real heading' (the card's fourth acceptance box); 'check-docs-single-h1: 180 page(s) under content/docs/ carry no body-level heading (2 subtree(s) excluded)'; 'check-doc-frontmatter: 2 content root(s) verified, each against its own floor - content/docs 403, content/blog 3.' FOUR OF THE 25 FIRST REPORTED exit=1 AND ARE NOT RECORDED AS FAILURES - each printed 'PREREQUISITE NOT MET' or a missing-build-artifact banner whose own text says 'Nothing was measured ... It is NOT a finding' (doc-formula-expressions wanted @objectstack/formula built, doc-security-posture wanted @objectstack/lint built, spec check:docs wanted packages/spec/json-schema which is gitignored, check:skill-examples wanted packages/spec/dist .d.ts). Re-run green after 'pnpm exec turbo run build --filter=@objectstack/{formula,lint,spec,client,client-react} --concurrency=2' (33 tasks, exit 0) and 'pnpm --filter @objectstack/spec gen:schema' (exit 0, and it rewrote nothing tracked - git status stayed clean). Recorded so the first reading is not mistaken for a red that got quietly dropped. NO ABLATION was run: this diff adds no gate and asserts no new failure mode, so there is nothing whose failability needs proving - the mechanism claims are proved by reading fumadocs-core's own compiled builder, cited line by line in the PR body. EDIT CONFIRMATION ON DISK: each of the 4 title rewrites was applied by an anchored replace that then re-read the file and asserted the new line present exactly once AND the old line absent (exit 9 otherwise) - not by the editor's exit code. Control-character self-scan over the 4 edited files: grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' returned no matches. TWO DECLARED NARROWINGS, both also in the PR body: (1) os-verify-lock.sh ran UNLOCKED on every heavy command - this macOS host has no usable flock; the script's own disclosure is pasted verbatim rather than paraphrased: 'VERDICT command-exit 0 UNLOCKED (declared) no usable flock on this host, so the shared verify lock was NEVER taken and NOTHING was serialized'. Same host property #12236's dev reported. (2) 'pnpm lint' narrowed to the diff, with all three required measurements: population read from ESLint ITSELF ('eslint --print-config content/docs/index.mdx' prints 'undefined'); file count from --format json (4 files linted, 0 errors, 4 warnings, and all four warnings are 'File ignored because no matching configuration was supplied'); invariance for untouched files - eslint.config.mjs states at line 328 that this repo 'never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file', so an .mdx frontmatter change cannot move any untouched file's verdict. PR VERIFIED against GitHub after creation: draft=true, base=main, 4 files, every one 1+/1-, body 34.7 KB stored and byte-identical to what was written except one added blank line (the <title>/<h1>/<DocsTitle> fragments survived the sanitizer), signature footer present in session-URL form. skip-changeset applied via the additive POST /labels endpoint (HTTP 200) and READ BACK after the size-labeler had run: ['documentation','size/xs','skip-changeset'].",
      "open_questions": [
        {
          "question": "The frontmatter title is one string serving four consumers (SERP <title>, on-page <h1>, sidebar nav label, llms.txt heading), and no field separates them. Applying the 50-60 char rule to the remaining 176 pages therefore lengthens 176 sidebar entries by the same amount. Which way?",
          "options": [
            "A - Land #12311 first (~10 lines: pageSchema.extend({ sidebarTitle }) in apps/docs/source.config.ts + a pageTree.transformers file() hook in apps/docs/lib/source.ts), then apply the 180-row table with a short sidebarTitle wherever the long title would hurt the nav.",
            "B - Accept long sidebar labels: apply the table as-is, no code change. The nav becomes 176 wrapped multi-line entries.",
            "C - Reject the rule for body pages; keep titles as nav labels and buy the SERP signal elsewhere (description, JSON-LD, OG) via #12238 / #12240.",
            "D - Apply the rule only to section landing pages (the ~19 index.mdx files), leaving leaf pages short."
          ],
          "recommendation": "A. REAL BUSINESS NEED: measured, not speculative - the repo ALREADY needs this split and already fakes it. protocol/objectql/meta.json says 'Data Protocol' while the page says 'ObjectQL: The Data Protocol', and protocol/objectui/meta.json does the same; those two are the only pages where the folder-title mechanism happens to reach, and someone reached for it. B is measurably bad: the sidebar carries 8 parallel entries (Field/Object/View/Page/Dashboard/Report/App/Doc Metadata) whose parallelism is a real information-architecture asset that 50-60 char sentences destroy. LONG-TERM SOUNDNESS: A is the contract-first answer - one field per consumer, declared where it is read. B and D are the consumer-side-tolerance shape this repo rejects, overloading one string and hoping each reader copes; D additionally invents a boundary ('landing pages get search intent, leaf pages do not') that nothing in the spec or the tree supports. C forfeits the strongest on-page signal a document has to avoid a ten-line change. AI-WRITTEN CODE HARD TO GET WRONG: A is the only option that fails loudly - today an invented sidebarTitle: is SILENTLY STRIPPED by z.core.$strip, so an agent writing what looks like the obvious fix gets a green build and no nav change; declaring the field turns a silent no-op into a working key. B/C/D leave that trap armed. STARTUP SCOPE DISCIPLINE: A is ~10 lines in two files using an idiom the same file already uses for the blog collection - the smallest change that unblocks 176 rows, and it adds no new capability surface, just names one that four consumers are already sharing. The one honest cost of A is that it puts a docs-site code change in front of a docs-content card; if that ordering is unacceptable, C is the next best, and B should not be chosen at all."
        },
        {
          "question": "The rule's own numbers need confirming before 176 files are rewritten: the card says '50-60 characters including the ` | ObjectStack` suffix' but two of its three worked examples render at 70 and 71. Is the band or the examples authoritative?",
          "options": [
            "A - The band is authoritative: frontmatter titles get 36-46 chars. (What the 180-row table implements: median 54 rendered, max 60, none over.)",
            "B - The examples are authoritative: 50-60 chars BEFORE the suffix, i.e. 64-74 rendered, and the 'no title exceeds 60' acceptance box is dropped."
          ],
          "recommendation": "A. Google truncates display titles around 60 characters, so a 71-character rendered title loses its tail - and the tail is exactly the qualifier the rule exists to add, which makes B self-defeating on its own goal. A is also the only reading under which the card's own fourth acceptance box ('no title exceeds 60 characters including the site suffix') survives, and under which the one page already in breach today (record-alert.mdx at 64) is a defect rather than the intended shape. Flagging it rather than silently picking, because the card states both and they cannot both hold."
        },
        {
          "question": "Do the 180 proposed strings carry the maintainer's voice? They are hand-authored copy, and that is the part no gate can check.",
          "options": [
            "A - Approve the table as written; a follow-up card applies it.",
            "B - Approve the rule but rewrite specific rows.",
            "C - Reject the rule."
          ],
          "recommendation": "No recommendation - this is the judgement the PR was deliberately left draft for. The table is in the PR body precisely so a rejection costs one table and not 176 files of rework, per the dispatch. Two deliberate choices worth an explicit yes/no: the separator is ' \\u2014 ' throughout (the rule as literally written, and the 4 titles already using it) even though 2 of the card's 3 examples use ': '; and 'ObjectStack' never appears in a title since the suffix carries it, with one declared exception where the product name IS the query (getting-started/index.mdx keeps 'What is ObjectStack?')."
        }
      ],
      "out_of_scope_findings": [
        "filed as #12311 (attached as a sub-issue of epic #12243): docs frontmatter carries no short sidebar label - one title serves the SERP <title>, the on-page <h1>, the sidebar nav entry and the llms.txt heading; pageSchema is z.core.$strip so an invented sidebarTitle: is silently dropped, and meta.json's title names only the folder. Blocks 176 of this card's 180 rows. Unassigned, with the ~10-line apps/docs fix and its acceptance boxes written out.",
        "NOT filed, recorded here as a correction rather than a new card: 'AI-built' is not absent from the repo as #12239's dev reported - 35 occurrences across 18 files under packages/** and docs/adr/**. It is absent from content/docs/** and apps/docs/**, which is presumably what was measured. No action wanted; the no-fourth-spelling ruling is unaffected, but the next card that inherits 'AI-built appears zero times' as a fact should inherit this instead.",
        "NOT filed, standing repo fact surfaced by the gate derivation and already visible to every card: 'pnpm --filter @objectstack/spec run check:skill-refs' is reported by dispatch-gates as THE LAYOUT MOVED - its declared population ('lib/file-description', 'lib/generated-output', '@objectstack/spec') matches nothing in the tree, so it scores a quiet green for every card. Flagged only because the derivation calls it 'a real miss, worth triaging'; it is unrelated to this card and I did not investigate it."
      ]
    }
    
  6. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    ContributorAuthor

    PM rulings — Q1 and Q2 answered here; Q3 goes to the maintainer

    Q2 first, because it is my error. The band is authoritative — frontmatter titles get 36–46 characters.

    The card stated both and they cannot both hold: "50–60 characters including the | ObjectStack suffix" alongside worked examples that render at 70 and 71. The examples were written to 50–60 excluding the suffix. The band wins, on the dev's own argument: Google truncates display titles near 60, so a 71-character title loses precisely the qualifier the rule exists to add — option B is self-defeating against its own goal. It is also the only reading under which the card's fourth acceptance box survives, and under which protocol/objectui/record-alert.mdx (64 characters today, before this PR) reads as a pre-existing defect rather than the intended shape. The 180-row table already implements the band; nothing to redo.

    Q1: option A — land #12311 first — but conditional on Q3, and the condition is not a formality.

    The dev's case is the strongest kind: the repo already needs this split and already fakes it. protocol/objectql/meta.json says Data Protocol while the page says ObjectQL: The Data Protocol; protocol/objectui/meta.json does the same. Those two are the only pages the folder-title mechanism happens to reach, and someone reached for it. That is a measured pull, not a speculative one.

    It is also contract-first in the sense this repo means it: today an agent writing the obvious fix — sidebarTitle: in frontmatter — gets it silently stripped by z.core.$strip, a green build, and no nav change. That is the declared-≠-enforced shape the Prime Directives exist to kill. Options B and D leave that trap armed; B additionally destroys the eight parallel … Metadata sidebar entries, whose parallelism is a real information-architecture asset.

    ⛔ But #12311 is not dispatched yet, and will not be until Q3 comes back. If the maintainer rejects the rule (Q3 option C), #12311 has no consumer and becomes a field built for nobody — the exact "no unpulled capability" failure this repo's startup-scope rule names. Sequencing: Q3 approve-or-revise ⇒ #12311 dispatches immediately, then the table applies on top. Q3 reject ⇒ #12311 closes with it.

    What the card got wrong, and what it got right

    Right, and I want this on the record because it is a null result reported as a null result: the statistics are mine and they held. 403 / median 14 / 325 ≤20, re-derived frontmatter-aware. The naive control gives 405/14/50/327 — the fence-blind method missed by 2 hits and did not move the headline. After three of my measurements were corrected in this epic, the dev checked the fourth, found it sound, and said so plainly instead of manufacturing a catch.

    Wrong, and it changes the shape of the work: the card said 403 pages. The editable surface is 180. 214 are under content/docs/references/** (generated — #12249) and 9 under releases/** (CLAUDE.md hard stop — #12250), both barred from this card. So 55% of the population this card quotes is unreachable from it, and the barred references/** subtree (median 11, 207 of 214 under 20 characters) is what drags the headline median down. On the 180 pages actually in reach the median is 18, not 14. Every downstream card that inherits "403 docs pages" — #12238 especially — inherits a number that is 2.2× the reachable surface.

  7. 21 remaining items

  8. claude commented on Sep 8, 2026

    @claude
    Contributor

    Passed over at round 4 by the domain:devx execution PM seat (session_012GKcPZbMoGq7WPzKLfRBTU), and recording it rather than leaving a silent skip. ⛔ No state change: this card stays pm:queue, unassigned, not claimed.

    By take order this card was first — it is the lane's only priority:p1 and it carries pm:blocking. It was not taken for one measured reason.

    The serial constraint, measured

    All 19 open PRs' changed-file lists fetched paged to exhaustion at 2026-09-08T20:1xZ (control: 19 of 19 returned a non-empty list) and filtered for content/docs:

    PR touches content/docs
    #15334 ✅
    #16783 ✅
    #16860 ✅
    #16883 ✅
    #16961 ✅

    ⇒ Five live PRs are in that tree right now. This card rewrites the frontmatter title of 403 pages. ⛔ A diff that touches every page in content/docs/** collides with all five, and the collision is not hypothetical — this seat lost a merge-queue slot today to a two-row conflict in a single file (PR #16916, dequeued 17:25:36Z).

    ⚠️ And the cost is asymmetric: a conflict on a 403-file rewrite is not resolved the way a two-line one is. Rebasing it repeatedly, against a tree five other PRs keep moving, would burn more than the card is worth per attempt.

    ⇒ This card needs a quiet window in content/docs/**, and one does not exist today. It is not blocked by a ruling and nothing is wrong with it.

    ⚠️ Two other things the next seat should weigh, ⛔ neither of them a reason it was skipped

    1. ⭐ Its PR is terminal for an execution seat. The card says so itself: the PR must open with the rule plus the full 403-row before/after table, «it stays open for maintainer review — do not self-merge it». ⇒ whoever dispatches this must fence the dev accordingly: the deliverable is the rule and the table, and the PR ⛔ must not be flipped, armed or enqueued by the seat. That is unusual for this lane and easy to get wrong.
    2. The risk is bounded and the card says how: «If the rule is rejected, only the table is thrown away, not per-file work.» ⇒ the rule must be judged before the 403 rows are written, not after.

    Re-check before the next dispatch attempt

    Whether content/docs/** has gone quiet — ⛔ re-take the scan rather than trusting this comment, since the five PRs above will land and others will open. If it stays busy for another round or two, that is itself worth reporting: a p1 pm:blocking card that cannot find a window is a scheduling fact, not a backlog failure.


    Generated by Claude Code

  9. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Release: 5413886618 (os-zhuang, session_f9f0958b-ab68-46cc-801c-216aa7ee2107, the epic-PM dispatch for #12243), reclaimed on the maintainer's order by domain:devx seat 2 (#20163), session_018mA64scZ8fmpiPkrHVAwXj. Cause: that dispatch delivered PR #12312 (squash e18c870b, 4 of the 180 approved rows) and never released the card. The card went back to pm:queue on 2026-09-02 with the old assignee still set, which is a half-state. As the lane's only open p1, it holds every p2/p3 tooling and contract-hygiene card in domain:devx under NORTH-STAR 优先级 rule 3. Destination: the Claim: below.

    谁的指令: the maintainer
    原话:

    按我的令回收并派发
    在哪说: the chat of session session_018mA64scZ8fmpiPkrHVAwXj, before this seat's post #20163 was opened (2026-09-27T04:07:54Z). The maintainer picked this option in answer to the seat's question: "reclaim #12237's stale claim on your order?" The option text was written by the seat.

    Claim: PM loop round 1 (maintainer-ordered reclaim + dispatch, seat domain:devx#2)
    Session: session_018mA64scZ8fmpiPkrHVAwXj
    Account: os-litant
    Branch: claude/issue-12237-title-rule-rollout
    Worktree: objectstack-issue-12237 (in the dev's cloud container)
    Domain: domain:devx
    Seat: domain:devx#2
    File surface: authored content/docs/**/*.mdx, frontmatter only: the title: line, plus a navTitle: line wherever the sidebar label must stay short. ⛔ Not content/docs/references/** or content/docs/releases/** (generated / release-owned). ⛔ Not description: (#12238). ⛔ Not page bodies. ⛔ Not apps/docs/**. If a row needs anything outside this surface, the dev stops and explains it in the report.
    Container & model: L (card size/l), mode:cloud, model: opus. dispatch-gates --tier answers "no path-derived mandate … floor sonnet · default opus · ceiling fable". The default tier is chosen because the rollout needs per-row judgement: re-matching rows whose page path changed, and deciding where a navTitle: is needed.
    Clause-②: no
    Thread-read: 5591230177
    Serial constraints cleared: open-PR census at 2026-09-27T04:15Z covered all 8 open PRs, with file lists paged to exhaustion. Authored content/docs is touched only by PR #20004 (#19879, seat 1) and PR #19957 (#17158), both in the body of content/docs/api/error-catalog.mdx, and by the Version Packages PR #17076 (deployment/self-hosting.mdx, upgrading.mdx). Those are frontmatter-vs-body edits and do not collide textually. Of the 25 open pm:dispatched cards' latest claims, only #19543 (5851923574, spec seat) names content/docs/**, and only for the bodies of pages that teach the list route. That is ordinary concurrency. SINGLE_CLAIM_PATHS holds only .objectui-sha. #15403 (the generator half, domain:spec) is pm:blocked behind this card and not in flight.

    The four-part takeover, in one comment

    ① The Release: line above names the holder's claim 5413886618 and its session, with the three provenance fields.
    ② Assignee: os-zhuang → os-litant, written in the same act as pm:queue → pm:dispatched.
    ③ The Claim: above opens a new branch. The holder's branch claude/issue-12237-page-titles is gone from the remote: git ls-remote --heads origin has 0 heads matching issue-12237. Its only PR, #12312, is merged.
    ④ Handover record: the holder's last delivered state is squash e18c870b on main. Status on origin/main 2bbebf5: the approved table in PR #12312's body parses to 176 non-landed rows. 169 of them still read their approved before title, and 0 read their after. 7 are paths my instrument missed because its "generated" test also hit the word in a page's description (for example content/docs/api/index.mdx, ui/index.mdx). Those 7 pages exist, so the dev re-derives that split. navTitle: has 0 uses in authored pages. ⛔ These counts are the seat's leads, not the dev's facts.


    Generated by Claude Code

  10. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 12237,
      "status": "done",
      "branch": "claude/issue-12237-title-rule-rollout",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20170",
      "session": "session_018bR89KaSkoZVqgBnUYXtyD",
      "premise_still_valid": true,
      "summary": "Applied the maintainer-approved title rule to authored content/docs pages, frontmatter only: 167 approved rows verbatim, 1 approved row re-worded because the required check:role-word gate rejects it (permissions/administrator-guide.mdx: 'roles' -> 'access'), and 1 authored page the table never listed authored per the rule (permissions/tenant-audit-census.mdx); every one of those 169 pages also declares navTitle with its previous title verbatim, so the sidebar and footer prev/next are unchanged. 8 approved rows (kernel/runtime-services/*-service.mdx) are NOT applied: check:runtime-services-index pins title to the services.NAME accessor, and satisfying both needs a gate change outside the file surface; they are byte-identical to base and held for a ruling. Zone 2 leads re-derived on base 84880f9: the PR #12312 table parses to 180 rows (4 landed + 176 not), 176/176 still carry their approved before and 0 their after (the seat's 169+7 split was an instrument artefact; a file-head marker test finds no generated authored page); 1 authored page is absent from the table; navTitle had 0 uses; check-docs-nav-label reads apps/docs only, so a navTitle frontmatter line is not judged by it (run anyway, green). Page-tree membership measured by replaying fumadocs-core 16.14.4 buildFolder over all 19 authored meta.json: 180/181 authored pages are tree nodes (162 leaves + 18 folder indexes, which flattenTree puts in footer prev/next), so all 169 lengthened pages needed navTitle. Title read points: 12 on 5 faces (SERP/OG/twitter metadata x4, h1, JSON-LD TechArticle x2 + breadcrumb leaf, llms.txt + getLLMText, OG image) plus the fumadocs search index; the dispatch's '11 / 6' was close but not exact. Rendered stats over 181 authored pages: median 32 -> 54, max 56 -> 60, in 50-60 band 6 -> 173, over 60 0 -> 0, duplicates 0 -> 0; the 8 below 50 are exactly the held rows. origin/main did not move from base 84880f9 during the run, so no merge was needed. navTitle kept as spelled; no measured reason to rename.",
      "tests": "At head 2594589, git status clean. dispatch-gates --commands (169 paths vs merge base 84880f9, three-dot) derived 55 commands; ran them plus node scripts/check-docs-nav-label.mjs and its --self-test = 57, every exit code captured before any pipe: ALL 57 exit 0. dispatch-gates --ran: '55 derived famil(ies) accounted for - 55 run, 0 NOT-MEASURED'. Verdicts: 'check-doc-anchors: 376 internal #fragment link(s) across 411 source file(s) all resolve to a real heading'; 'check-doc-frontmatter: 2 content root(s) verified ... content/docs 406, content/blog 3.'; 'check-docs-single-h1: 406 page(s) under content/docs/ carry no body-level heading'; check-runtime-services-index green with the 8 held rows unchanged; check-nul-bytes OK over 9564 files. First run on the verbatim table (head d694b59): check:role-word exit 1 (administrator-guide 'roles', ADR-0090 D3) and check:runtime-services-index exit 1 (8 title-premise findings) - real findings, fixed by re-wording 1 row and restoring 8 files to base; check:skill-examples exit 3 PREREQUISITE NOT MET (stale client-react .d.ts, nothing measured) - cleared by building @objectstack/client-react under os-verify-lock (VERDICT command-exit 0) and re-run green. Prereq builds under os-verify-lock: spec+lint+formula (4 tasks, exit 0), client-react (34 tasks, exit 0). fumadocs-mdx regenerated the source index at exit 0. Edits applied by an anchored script asserting exactly one old title line in the leading frontmatter block, the new line present once and the old absent, and the page body byte-identical after the write; control-byte self-scan over the changed files returned nothing. No ablation: the diff adds no gate or failure mode. NOT MEASURED: the card's 'link checker' (.github/workflows/check-links.yml, lychee, advisory) - lychee absent in this container; the diff changes no link, link text or path; it runs on the PR in CI. A full next build of apps/docs was not run locally. pnpm lint not owed: .mdx is outside the ESLint population.",
      "gates": [
        "exit 0 :: node scripts/check-ci-filter-parity.mjs",
        "exit 0 :: node scripts/check-closing-keyword-parity.mjs",
        "exit 0 :: node scripts/check-closing-keyword-parity.mjs --self-test",
        "exit 0 :: node scripts/check-comment-mask-corpus.mjs",
        "exit 0 :: node scripts/check-doc-frontmatter.mjs",
        "exit 0 :: node scripts/check-doc-frontmatter.mjs --self-test",
        "exit 0 :: node scripts/check-doc-route-spelling.mjs --advisory",
        "exit 0 :: node scripts/check-doc-route-spelling.mjs --self-test",
        "exit 0 :: node scripts/check-docs-section-name.mjs",
        "exit 0 :: node scripts/check-docs-section-name.mjs --self-test",
        "exit 0 :: node scripts/check-section-landing-index.mjs",
        "exit 0 :: node scripts/check-section-landing-index.mjs --self-test",
        "exit 0 :: node scripts/check-system-context-census.mjs",
        "exit 0 :: node scripts/check-system-context-census.mjs --self-test",
        "exit 0 :: node scripts/check-tenant-audit-census.mjs",
        "exit 0 :: node scripts/check-tenant-audit-census.mjs --self-test",
        "exit 0 :: node scripts/docs-audit/check-drift-comment.mjs",
        "exit 0 :: pnpm --filter @objectstack/lint run check:doc-formula-expressions",
        "exit 0 :: pnpm --filter @objectstack/lint run check:doc-security-posture",
        "exit 0 :: pnpm --filter @objectstack/spec run check:docs",
        "exit 0 :: pnpm --filter @objectstack/spec run check:empty-state",
        "exit 0 :: pnpm --filter @objectstack/spec run check:liveness",
        "exit 0 :: pnpm --filter @objectstack/spec run check:skill-docs",
        "exit 0 :: pnpm --filter @objectstack/spec run check:skill-examples",
        "exit 0 :: pnpm --filter @objectstack/spec run check:strictness-ledger",
        "exit 0 :: pnpm --filter @objectstack/spec run check:variant-docs",
        "exit 0 :: pnpm --filter @objectstack/spec run check:yaml-examples",
        "exit 0 :: pnpm check:cli-examples-parity",
        "exit 0 :: pnpm check:corpus-claim-drift",
        "exit 0 :: pnpm check:cross-package-test-inputs",
        "exit 0 :: pnpm check:doc-anchors",
        "exit 0 :: pnpm check:doc-authoring",
        "exit 0 :: pnpm check:docs-audit-scope",
        "exit 0 :: pnpm check:docs-image-tag",
        "exit 0 :: pnpm check:docs-redirects",
        "exit 0 :: pnpm check:docs-single-h1",
        "exit 0 :: pnpm check:docs-spec-enumerations",
        "exit 0 :: pnpm check:docs-transcript-drift",
        "exit 0 :: pnpm check:driver-memory-census",
        "exit 0 :: pnpm check:error-status-conformance",
        "exit 0 :: pnpm check:gitlink-declared",
        "exit 0 :: pnpm check:lockstep-package-count",
        "exit 0 :: pnpm check:merge-driver",
        "exit 0 :: pnpm check:nul-bytes",
        "exit 0 :: pnpm check:overlay-whitelist-table",
        "exit 0 :: pnpm check:pm-widening-tells",
        "exit 0 :: pnpm check:published-readme-links",
        "exit 0 :: pnpm check:quick-reference-counts",
        "exit 0 :: pnpm check:react-page-adapter-contract",
        "exit 0 :: pnpm check:refd-timer-probe",
        "exit 0 :: pnpm check:role-word",
        "exit 0 :: pnpm check:runtime-services-index",
        "exit 0 :: pnpm check:skill-identifier-liveness",
        "exit 0 :: pnpm check:vendor-version-stamps",
        "exit 0 :: pnpm check:watch-hint-literal",
        "exit 0 :: node scripts/check-docs-nav-label.mjs",
        "exit 0 :: node scripts/check-docs-nav-label.mjs --self-test"
      ],
      "line_budget": "169 files, +338 / -169 = 507 changed lines (dispatch-gates reading), under the 5,000 human-merge threshold",
      "files_changed": 169,
      "deviations": [
        "8 approved rows not applied (kernel/runtime-services/{audit,data,email,queue,settings,sharing,sms,storage}-service.mdx): check:runtime-services-index requires title == services.NAME; the fix is a gate edit outside the file surface, so stopped and reported (open_questions[0]).",
        "1 approved row re-worded: permissions/administrator-guide.mdx 'Administrator guide to permissions and roles' -> 'Administrator guide to permissions and access' (59 rendered), because check:role-word (ADR-0090 D3) rejects the approved text; listed in the PR's 'needs maintainer voice review' section with the approved text beside it.",
        "1 unlisted authored page authored per the rule: permissions/tenant-audit-census.mdx -> 'Tenant-audit census - tenancy write call sites' (60 rendered, navTitle 'Tenant-Audit Census'); in the same review section.",
        "Link checker (lychee) and a full apps/docs next build not run locally; declared NOT MEASURED in the PR body.",
        "Commit trailers use the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude) instead of the harness reminder's model-named Co-Authored-By, per AGENTS.md precedence."
      ],
      "mcp_calls": "0 - no MCP GitHub tool was called; reads went through unauthenticated public REST/curl",
      "api_writes": "3 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, each executed by fleet-write.yml as objectstack-fleet[bot]) carrying 4 writes: POST /repos/objectstack-ai/objectstack/pulls (draft PR #20170); POST /repos/objectstack-ai/objectstack/issues/20170/labels (skip-changeset); POST /repos/objectstack-ai/objectstack/issues/20170/assignees (os-litant); POST /repos/objectstack-ai/objectstack/issues/12237/comments (this report). Plus 2 git pushes (not REST).",
      "open_questions": [
        {
          "question": "The 8 approved kernel/runtime-services/*-service.mdx titles contradict check:runtime-services-index, which holds title == services.NAME as the premise of its five chapter enumerations. How should they land?",
          "options": [
            "A - a small gate change: read the accessor from navTitle (navTitle: services.audit etc.) and apply the 8 approved titles; sidebar shows the accessor, search faces get the approved title.",
            "B - exempt this chapter from the rule: keep title: services.NAME (26-31 rendered) and record the exemption beside the rule.",
            "C - leave as held with no decision (the 8 stay short)."
          ],
          "recommendation": "A. Real need: the maintainer approved these 8 titles, and each already leads with the accessor a developer types, so the search faces gain the qualifier without losing the keyword. Long-term: the gate keeps a strict premise (the page must declare its accessor), it just reads it where the navigation label now lives; no consumer-side tolerance. AI-error: stays loud - a page that drops or misspells its accessor still goes red. Startup scope: one premise line in one existing gate plus its self-test and 8 frontmatter rows; no new gate, no new key. B is the zero-code fallback if the maintainer judges the bare accessor to be the better title for an API page."
        },
        {
          "question": "30 approved rows have no ' - ' separator (e.g. 'Natural language queries over your data'), while the ruling says 'separator throughout'. Applied verbatim because the table was approved as it stands. Re-word them?",
          "options": [
            "A - keep verbatim (approved text).",
            "B - maintainer names rows to re-word in a follow-up."
          ],
          "recommendation": "A: the ruling approved the table as it stands, and all 30 are inside the 36-46 band; any re-wording is the maintainer's voice, not a seat's."
        },
        {
          "question": "protocol/objectql/index.mdx and protocol/objectui/index.mdx (lengthened by #12312) carry no navTitle, so the footer prev/next shows their long titles; #12312's 'meta.json title wins' holds for the sidebar only. Add navTitle?",
          "options": [
            "A - leave as is (40-42 characters, readable).",
            "B - add navTitle carrying a short label in a follow-up."
          ],
          "recommendation": "A: both labels are short enough to read in the footer, and no approved short label exists for them; not worth a row of its own."
        }
      ],
      "out_of_scope_findings": [
        "carrier: none - #12312's claim that its two folder-index pages were 'not in the page tree' holds for the sidebar only; fumadocs flattenTree puts folder.index into the footer prev/next list. Noted in the PR's Acceptance notes, not filed (observation, no defect class).",
        "carrier: #15403 (domain:spec) - pre-existing duplicate titles among generated references/** pages (Mapping, Notification, Sharing, Misc, Analytics, Plugin, 6 pairs). Noted, not filed."
      ]
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    PM review — PR #20170: REWORK, round 1 (one item) · 2026-09-27T05:03Z

    domain:devx seat 2, session_018mA64scZ8fmpiPkrHVAwXj. I am the reviewer of record. Everything below was read on GitHub and the tree, not taken from the report (5852704142).

    Checklist, verified at head 2594589 (merge base 84880f9)

    check reading
    Shape Draft, base main. The first body line is Fixes #12237, and it is the body's only closing keyword. Assignee os-litant.
    Scope 169 files, all authored content/docs/**/*.mdx. Each is +2/−1 modified. 0 under references/** or releases/**.
    Diff, per file (base vs head, parsed) 0 body changes. 0 frontmatter changes other than title: / navTitle:. navTitle equals the old title on all 169. 167 new titles equal the approved after verbatim. The 2 that do not (permissions/administrator-guide.mdx, permissions/tenant-audit-census.mdx) are the declared deviations, listed in the PR for voice review. Rendered 50–60, 0 over 60.
    Changeset skip-changeset: docs-only, no .changeset/*.md.
    CI on 2594589 36 runs: 26 success, 10 skipped, 0 other. Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Governed Surface Queue Guard, Check Documentation Links and Build Docs are all success.
    Governed / size No governed path. 507 changed lines.

    Why REWORK: the 8 held rows

    The held rows' blocker is real. scripts/check-runtime-services-index.mjs :420-422 reds unless title is services.<name>. With them held, though, Fixes #12237 would close the card with 8 maintainer-approved rows unapplied. The fix fits in this PR, so the answers are:

    • open_questions[0] → A. This is a PM answer: moving an existing gate's premise field, which neither weakens it nor adds a gate. So it is not escalated. Each kernel/runtime-services/*-service.mdx page declares navTitle: services.<name> and takes its approved title verbatim. The gate reads the accessor premise from navTitle:
      • navTitle absent, or not services.<name> matching the filename, stays red, and the message names the fix.
      • The gate's header prose that states the title: premise is rewritten to match, because a gate's own description must be true.
      • The --self-test batteries cover: navTitle absent → red; wrong → red; right with a long title → green.
      • An ablation proves the premise check still fires: mutate one page's navTitle, see red, restore, with a trap.
      • git grep every other reader of these pages' title (other scripts/, apps/docs, the kernel/index.mdx services table) and report what you find.
    • open_questions[1] → A. Keep the 30 rows without the separator verbatim. The ruling approved the table as it stands.
    • open_questions[2] → A. Leave the two docs(content): propose a search-intent title rule, land it on the four pages with no sidebar cost #12312 folder-index pages as they are.

    File surface, amended by this seat for this round: the claim's surface plus scripts/check-runtime-services-index.mjs. That covers its built-in --self-test and any test file that names it. ⛔ Nothing else.

    After the push: re-run the gate union at the new head. Post a second os-dev-report, and in it give the exact PR-body edits (the table rows, stats and deviations). The seat writes those edits into the body; ⛔ the dev does not PATCH it.


    Generated by Claude Code

  12. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 12237,
      "status": "done",
      "round": 2,
      "branch": "claude/issue-12237-title-rule-rollout",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20170",
      "session": "session_018bR89KaSkoZVqgBnUYXtyD",
      "premise_still_valid": true,
      "summary": "REWORK r1 (review 5852831074) done. scripts/check-runtime-services-index.mjs now reads the accessor premise from the page's navTitle (leading frontmatter block only; one layer of matching YAML quotes stripped as syntax) instead of title. Absent, blank, body-only or mismatched navTitle is red, and so is the old title-only spelling. Each finding names the fix. The header prose stating the title premise is rewritten, with the reason. The 8 kernel/runtime-services/*-service.mdx pages take their approved titles verbatim and declare navTitle: services.NAME. The whole approved table now lands: 175 rows verbatim, 1 re-worded (administrator-guide, check:role-word), 1 unlisted page authored per the rule (tenant-audit-census). Rendered stats over 181 authored pages: median 32 -> 54, min 19 -> 50, max 56 -> 60, in 50-60 6 -> 181, over 60 0, duplicates 0, navTitle 0 -> 177. open_questions[1] and [2] answered A by the PM: 30 separator-less rows kept verbatim, the two #12312 folder-index pages left alone. origin/main merged twice (369bcbe, then e2c4e12); neither merge touched this PR's files. Other readers of these pages' title (git grep over scripts/, packages/, apps/docs, .github/, content/docs): none parses it. apps/docs reads page.data.title generically, which is the intended change. kernel/index.mdx names the accessors and not the page titles. One stale worked example sits in the header prose of scripts/check-docs-single-h1.mjs:72-73 ('title: services.audit'); it is outside the surface, its logic is unaffected and it is green, so it is recorded, not edited.",
      "tests": "Head c69d5c7, git status clean. Self-test: 'check-runtime-services-index --self-test: 59 assertions' green. New floored battery 'The accessor premise lives in navTitle' has 7 cases: absent, wrong, old title-only, body-only and blank are red; long title and quoted value are green. Roster floor 8 -> 9. The fixture default title is now long, so every existing green case is also long-title-green. Live tree BEFORE the 8 page edits: the new gate exit 1 with exactly 8 'declares no navTitle' findings. After: exit 0. ABLATION (committed state a48497d, trap EXIT/INT/TERM, REPO_ROOT absolute): scripts/ablation-replace.mjs set 'navTitle: services.sms' to 'navTitle: services.text' in sms-service.mdx. Landing proven by the tool: anchor 1 -> 0, blob 2001694b2859 -> 67f9af393d4d. The gate exited 1 with 'frontmatter navTitle is \"services.text\", expected \"services.sms\" to match the filename -- set `navTitle: services.sms`, or rename the page if the accessor really changed'. Restore: blob 2001694b2859 == HEAD blob (git hash-object compared, non-empty), git diff HEAD empty, git status empty. UNION at c69d5c7: dispatch-gates --commands (178 paths vs merge base e2c4e12) derived 74; ran them plus check-docs-nav-label and its --self-test = 76, all exit 0, each exit captured before any pipe. dispatch-gates --ran: '74 derived famil(ies) accounted for - 74 run, 0 NOT-MEASURED (a DERIVED zero - all 74 recorded an exit code and none of them is 3)'. Verdicts: check-doc-anchors '376 internal #fragment link(s) across 411 source file(s) all resolve'; check-doc-frontmatter 'content/docs 406, content/blog 3'; check-docs-single-h1 '406 page(s)'; check-docs-nav-label green; check-self-test-wired and bare-root-worklist --self-test (both name this script) green; check-nul-bytes OK over 9566 files. Prereq build under os-verify-lock: spec+lint+formula+client-react, 34 tasks, VERDICT command-exit 0. Carried from r1, unchanged: lychee is not installed locally (CI 'Check Documentation Links' was success on r1 head 2594589 per the review), and no local next build ('Build Docs' was success in CI on 2594589).",
      "gates": [
        "exit 0 :: node scripts/check-ci-filter-parity.mjs",
        "exit 0 :: node scripts/check-closing-keyword-parity.mjs",
        "exit 0 :: node scripts/check-closing-keyword-parity.mjs --self-test",
        "exit 0 :: node scripts/check-comment-mask-corpus.mjs",
        "exit 0 :: node scripts/check-declaration-mirrors.mjs",
        "exit 0 :: node scripts/check-declaration-mirrors.mjs --self-test",
        "exit 0 :: node scripts/check-doc-frontmatter.mjs",
        "exit 0 :: node scripts/check-doc-frontmatter.mjs --self-test",
        "exit 0 :: node scripts/check-doc-route-spelling.mjs --advisory",
        "exit 0 :: node scripts/check-doc-route-spelling.mjs --self-test",
        "exit 0 :: node scripts/check-docs-section-name.mjs",
        "exit 0 :: node scripts/check-docs-section-name.mjs --self-test",
        "exit 0 :: node scripts/check-scripts-symbol-anchors.mjs",
        "exit 0 :: node scripts/check-scripts-symbol-anchors.mjs --self-test",
        "exit 0 :: node scripts/check-section-landing-index.mjs",
        "exit 0 :: node scripts/check-section-landing-index.mjs --self-test",
        "exit 0 :: node scripts/check-self-test-wired.mjs",
        "exit 0 :: node scripts/check-self-test-wired.mjs --self-test",
        "exit 0 :: node scripts/check-self-test-workflow-commands.mjs",
        "exit 0 :: node scripts/check-self-test-workflow-commands.mjs --self-test",
        "exit 0 :: node scripts/check-system-context-census.mjs",
        "exit 0 :: node scripts/check-system-context-census.mjs --self-test",
        "exit 0 :: node scripts/check-tenant-audit-census.mjs",
        "exit 0 :: node scripts/check-tenant-audit-census.mjs --self-test",
        "exit 0 :: node scripts/check-whole-set-label-write.mjs",
        "exit 0 :: node scripts/check-whole-set-label-write.mjs --self-test",
        "exit 0 :: node scripts/docs-audit/check-drift-comment.mjs",
        "exit 0 :: node scripts/pm/bare-root-worklist.mjs --self-test",
        "exit 0 :: pnpm --filter @objectstack/lint run check:doc-formula-expressions",
        "exit 0 :: pnpm --filter @objectstack/lint run check:doc-security-posture",
        "exit 0 :: pnpm --filter @objectstack/spec run check:docs",
        "exit 0 :: pnpm --filter @objectstack/spec run check:empty-state",
        "exit 0 :: pnpm --filter @objectstack/spec run check:liveness",
        "exit 0 :: pnpm --filter @objectstack/spec run check:skill-docs",
        "exit 0 :: pnpm --filter @objectstack/spec run check:skill-examples",
        "exit 0 :: pnpm --filter @objectstack/spec run check:strictness-ledger",
        "exit 0 :: pnpm --filter @objectstack/spec run check:variant-docs",
        "exit 0 :: pnpm --filter @objectstack/spec run check:yaml-examples",
        "exit 0 :: pnpm check:agent-test-spelling",
        "exit 0 :: pnpm check:bash32-floor",
        "exit 0 :: pnpm check:cli-command-ids",
        "exit 0 :: pnpm check:cli-examples-parity",
        "exit 0 :: pnpm check:corpus-claim-drift",
        "exit 0 :: pnpm check:cross-package-test-inputs",
        "exit 0 :: pnpm check:doc-anchors",
        "exit 0 :: pnpm check:doc-authoring",
        "exit 0 :: pnpm check:docs-audit-scope",
        "exit 0 :: pnpm check:docs-image-tag",
        "exit 0 :: pnpm check:docs-redirects",
        "exit 0 :: pnpm check:docs-single-h1",
        "exit 0 :: pnpm check:docs-spec-enumerations",
        "exit 0 :: pnpm check:docs-transcript-drift",
        "exit 0 :: pnpm check:driver-memory-census",
        "exit 0 :: pnpm check:entry-guard",
        "exit 0 :: pnpm check:error-status-conformance",
        "exit 0 :: pnpm check:gitlink-declared",
        "exit 0 :: pnpm check:lockstep-package-count",
        "exit 0 :: pnpm check:merge-driver",
        "exit 0 :: pnpm check:nul-bytes",
        "exit 0 :: pnpm check:overlay-whitelist-table",
        "exit 0 :: pnpm check:parse-guard",
        "exit 0 :: pnpm check:pm-dispatch-gates",
        "exit 0 :: pnpm check:pm-widening-tells",
        "exit 0 :: pnpm check:pnpm-filter-targets",
        "exit 0 :: pnpm check:published-readme-links",
        "exit 0 :: pnpm check:quick-reference-counts",
        "exit 0 :: pnpm check:ratchet-remedy-authority",
        "exit 0 :: pnpm check:react-page-adapter-contract",
        "exit 0 :: pnpm check:refd-timer-probe",
        "exit 0 :: pnpm check:role-word",
        "exit 0 :: pnpm check:runtime-services-index",
        "exit 0 :: pnpm check:skill-identifier-liveness",
        "exit 0 :: pnpm check:vendor-version-stamps",
        "exit 0 :: pnpm check:watch-hint-literal",
        "exit 0 :: node scripts/check-docs-nav-label.mjs",
        "exit 0 :: node scripts/check-docs-nav-label.mjs --self-test"
      ],
      "line_budget": "178 files (177 content/docs mdx + 1 script), +446 / -194 = 640 changed lines vs merge base e2c4e12 (dispatch-gates reading); under 5,000",
      "files_changed": 178,
      "deviations": [
        "The union loop (76 commands) ran past the ~10-minute foreground cap and the harness moved it to the background. I stayed in the turn and waited in the foreground on its results file until all 76 rows landed, then read the logs. No result was read before completion.",
        "Carried from r1: permissions/administrator-guide.mdx re-worded (check:role-word), and permissions/tenant-audit-census.mdx authored per the rule. Both are in the PR's voice-review section.",
        "Carried from r1: lychee and a local next build were not run (NOT MEASURED locally; both CI jobs were green on r1 head).",
        "Commit trailers use the AGENTS.md model-free pair, not the harness's model-named Co-Authored-By."
      ],
      "pr_body_edits": "Unified diff below, from the CURRENT stored PR body (md5 482c5105cc0158c2357e109a7be35279) to the new body (md5 6f36d778d30cca5ee31c23f177f7a245, 36,574 bytes). Checked: `patch` of the stored body with this diff reproduces the new body byte-for-byte. Edits: Summary bullets (175 verbatim; the gate-premise bullet replaces 'NOT applied'; 178-file scope line); the 'Held' section is replaced by 'The runtime-services gate' (mechanism, red cases, self-test, ablation, other-readers finding); the review section drops its held clause; stats 'after' row reads 181/54/50/60/181/0/0/0/177 and the 'under 50 = held rows' sentence is removed; the complete table has the 8 held rows as applied (navTitle yes) and is headed 177 pages; Verification has head c69d5c7, 74-derived --ran, the new gate verdict and the round-1 record; Acceptance notes cite review 5852831074 for the two PM answers.",
      "mcp_calls": "0",
      "api_writes": "1 relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches, executed by fleet-write.yml as objectstack-fleet[bot]) carrying 1 write: POST /repos/objectstack-ai/objectstack/issues/12237/comments (this report). PR body NOT patched. Plus 2 git pushes this round (not REST): a48497d (which also carried the first main merge 4ef8cf5) and c69d5c7.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none - scripts/check-docs-single-h1.mjs:72-73 header prose uses 'audit-service.mdx title: services.audit' as a worked example; after this PR that page's title is 'services.audit — the audit log service API' (navTitle services.audit). The logic is unaffected and green; only the illustration is stale. Noted, not filed (prose drift)."
      ]
    }

    PR body diff (apply to the stored body; reproduces the new body byte-for-byte):

    --- a/pr-body.md
    +++ b/pr-body.md
    @@ -5,10 +5,10 @@
     
     Applies the maintainer-approved page-title rule to the authored docs pages that still carried their pre-rule title, and keeps every navigation label exactly as it was through `navTitle`.
     
    -- **167 approved rows applied verbatim** from the table in PR #12312, each with a `navTitle:` line holding the page's previous title.
    +- **175 approved rows applied verbatim** from the table in PR #12312, each with a `navTitle:` line holding the page's previous title.
     - **1 approved row re-worded** because a required gate rejects the approved text (`check:role-word`), and **1 authored page the table never listed** authored per the rule. Both are listed under *Needs maintainer voice review* below.
    -- **8 approved rows NOT applied** (the `kernel/runtime-services/*-service.mdx` chapter): `check:runtime-services-index` pins those titles to the `services.NAME` accessor, and no frontmatter-only change can satisfy both. Held for a ruling, see below.
    -- Frontmatter only: every file is `+2/-1` (`title:` replaced, `navTitle:` added). No `description:`, no page body, nothing under `references/**` or `releases/**`, nothing in `apps/docs/**` or any `meta.json`.
    +- **One gate premise moved, not loosened:** `check:runtime-services-index` used to hold each `kernel/runtime-services/NAME-service.mdx` to `title: services.NAME`. It now reads that accessor premise from the page's `navTitle`, so the chapter's 8 approved titles land verbatim with `navTitle: services.NAME`. See *The runtime-services gate* below. (PM answer to the round-1 open question, review comment `5852831074` on #12237.)
    +- 177 docs files, frontmatter only: every one is `+2/-1` (`title:` replaced, `navTitle:` added). No `description:`, no page body, nothing under `references/**` or `releases/**`, nothing in `apps/docs/**` or any `meta.json`. Plus one gate script, `scripts/check-runtime-services-index.mjs` (file surface amended by the seat for this round).
     
     ⛔ **Draft, and it stays open for maintainer review — do not self-merge** (card body). The landing path after review is the seat's call.
     
    @@ -44,44 +44,42 @@
     
     ## Statistics, re-derived
     
    -Population: every `content/docs/**/*.mdx` outside `references/**` and `releases/**`, **181 pages**. Rendered length = `title` + 14. Base `84880f9`, head `2594589`.
    +Population: every `content/docs/**/*.mdx` outside `references/**` and `releases/**`, **181 pages**. Rendered length = `title` + 14. Base `84880f9`, head `c69d5c7`.
     
     | | pages | median | min | max | in 50–60 | over 60 | under 50 | duplicate titles | `navTitle` declared |
     |---|--:|--:|--:|--:|--:|--:|--:|--:|--:|
     | before | 181 | 32 | 19 | 56 | 6 | 0 | 175 | 0 | 0 |
    -| after | 181 | 54 | 26 | 60 | 173 | 0 | 8 | 0 | 169 |
    +| after | 181 | 54 | 50 | 60 | 181 | 0 | 0 | 0 | 177 |
     
    -The 8 pages under 50 after are exactly the 8 held runtime-services rows. Duplicates were also checked against the 225 generated `references/**` + `releases/**` titles: no new title collides with any page.
    +Duplicates were also checked against the 225 generated `references/**` + `releases/**` titles: no new title collides with any page.
     
     **Premise check on the approved table** (PR #12312 body, section 6): it parses to **180 rows** — 4 landed with #12312 and **176 not landed**. On base `84880f9`, **176 of 176** pages still carry exactly the table's *before*, **0** carry its *after*, **0** paths are missing. The generated-page test reads the file head for a marker, never prose: no authored page carries one (the nine prose hits of the word "generated" in a page head, e.g. `api/index.mdx`, `ui/index.mdx`, are body text). One authored page is absent from the table: `permissions/tenant-audit-census.mdx` (added after #12312; its frontmatter is authored, only a body region is generated by `scripts/tenant-audit-census.mjs`).
     
     ## Needs maintainer voice review — not covered by the approved table
     
    -Every row here is **applied in this diff** except the held block, which is applied nowhere.
    +Every row here is **applied in this diff**.
     
     | page | before | approved *after* | applied | rendered | why |
     |---|---|---|---|--:|---|
     | `permissions/administrator-guide.mdx` | Administrator Guide | Administrator guide to permissions and roles | **Administrator guide to permissions and access** | 59 | the approved text fails the required `check:role-word` gate: "role" is a reserved word (ADR-0090 D3). Smallest change that clears it. |
     | `permissions/tenant-audit-census.mdx` | Tenant-Audit Census | — (page not in the table) | **Tenant-audit census — tenancy write call sites** | 60 | authored page the table never listed; written per the rule. `navTitle: Tenant-Audit Census`. |
     
    -### Held: 8 approved rows that a gate forbids (applied nowhere)
    +## The runtime-services gate
     
    -`scripts/check-runtime-services-index.mjs` (part of `Lint & Repo Gates`) checks, as the premise of its other five enumerations, that each `kernel/runtime-services/NAME-service.mdx` declares `title: services.NAME`. Applying the approved rows turned it red (`frontmatter title is "services.audit — the audit log service API", expected "services.audit" to match the filename`, 8 findings). Satisfying it needs a change to that gate (for example reading the accessor from `navTitle`), which is outside this card's file surface, so these 8 files are byte-identical to base.
    +`scripts/check-runtime-services-index.mjs` checks first that each chapter page declares the accessor its filename claims; the other five enumerations rest on that premise. It used to read `title`. The title rule lengthens `title` for the search-facing surfaces, so the premise now reads `navTitle`, which is the page-tree label and, on these 8 pages, the bare accessor.
     
    -| page | current title (kept) | approved *after* (not applied) | rendered if applied |
    -|---|---|---|--:|
    -| `kernel/runtime-services/audit-service.mdx` | services.audit | services.audit — the audit log service API | 56 |
    -| `kernel/runtime-services/data-service.mdx` | services.data | services.data — the record CRUD service API | 57 |
    -| `kernel/runtime-services/email-service.mdx` | services.email | services.email — the outbound mail API | 52 |
    -| `kernel/runtime-services/queue-service.mdx` | services.queue | services.queue — the job queue service API | 56 |
    -| `kernel/runtime-services/settings-service.mdx` | services.settings | services.settings — the settings API | 50 |
    -| `kernel/runtime-services/sharing-service.mdx` | services.sharing | services.sharing — the record share API | 53 |
    -| `kernel/runtime-services/sms-service.mdx` | services.sms | services.sms — the text message service API | 57 |
    -| `kernel/runtime-services/storage-service.mdx` | services.storage | services.storage — the file store API | 51 |
    +- Read from the page's **leading frontmatter block only**; one layer of matching YAML quotes is syntax, not value. The `title` is not read at all.
    +- Still red: `navTitle` absent, blank, present only in the body, or not `services.NAME`. A page that still says `title: services.NAME` with no `navTitle` is red too. Each finding names the fix (`add \`navTitle: services.sms\` to the page's leading frontmatter block`).
    +- The header prose that stated the `title:` premise is rewritten to say what the gate now does and why.
    +- `--self-test`: new floored battery *The accessor premise lives in navTitle*, 7 cases (absent, wrong, old title-only spelling, body-only line and blank are red; a long title and a quoted value are green). Roster floor 8 to 9. The fixture's default `title` is now a long search-intent string, so every existing green case is also a green with a long title. 59 assertions.
    +- **Ablation, trapped:** `scripts/ablation-replace.mjs` set `navTitle: services.sms` to `navTitle: services.text` in `sms-service.mdx` (anchor 1 to 0, blob `2001694b2859` to `67f9af393d4d`). The gate exited **1**: `frontmatter navTitle is "services.text", expected "services.sms" to match the filename -- set \`navTitle: services.sms\`, or rename the page if the accessor really changed`. Restored under an EXIT/INT/TERM trap to blob `2001694b2859`, which equals HEAD; `git diff HEAD` empty.
    +- The live tree before the 8 pages were edited: the new gate went red with exactly 8 `declares no navTitle` findings.
    +
    +**Other readers of these pages' `title`** (`git grep` over `scripts/`, `packages/`, `apps/docs`, `.github/`, `content/docs`): no other gate or script parses it. `apps/docs` reads it generically through `page.data.title`, which is the intended change. `content/docs/kernel/index.mdx` names the accessors in its services table and does not read the page titles. One piece of stale prose: the header of `scripts/check-docs-single-h1.mjs` (lines 72-73) uses `audit-service.mdx` `title: services.audit` as a worked example. Its logic is unaffected and it is green, but the example no longer matches the page. That file is outside this PR's surface, so it is noted here and not edited.
     
     ## The complete table
     
    -All 169 authored pages this PR changes, plus the 8 held rows (177 rows). Rendered = `title` + ` | ObjectStack`. `navTitle` = the page's previous title, declared verbatim. ✳️ = in the review section above.
    +All 177 authored pages this PR changes. Rendered = `title` + ` | ObjectStack`. `navTitle` = the page's previous title, declared verbatim. ✳️ = in the review section above.
     
     | page | before | after | rendered | `navTitle` |
     |---|---|---|--:|:-:|
    @@ -183,16 +181,16 @@
     | `kernel/contracts/storage-service.mdx` | IStorageService Contract | IStorageService — the file storage contract | 57 | yes |
     | `kernel/events.mdx` | Events & Hooks | Kernel events and hooks — the full list | 53 | yes |
     | `kernel/index.mdx` | Kernel & Services | Kernel and services — the runtime core | 52 | yes |
    -| `kernel/runtime-services/audit-service.mdx` | services.audit | ⏸ held — unchanged | 28 | no |
    -| `kernel/runtime-services/data-service.mdx` | services.data | ⏸ held — unchanged | 27 | no |
    -| `kernel/runtime-services/email-service.mdx` | services.email | ⏸ held — unchanged | 28 | no |
    +| `kernel/runtime-services/audit-service.mdx` | services.audit | services.audit — the audit log service API | 56 | yes |
    +| `kernel/runtime-services/data-service.mdx` | services.data | services.data — the record CRUD service API | 57 | yes |
    +| `kernel/runtime-services/email-service.mdx` | services.email | services.email — the outbound mail API | 52 | yes |
     | `kernel/runtime-services/examples.mdx` | Runtime Service Examples | Runtime service examples — copyable code | 54 | yes |
     | `kernel/runtime-services/index.mdx` | Runtime Service APIs | Runtime service APIs — the services object | 56 | yes |
    -| `kernel/runtime-services/queue-service.mdx` | services.queue | ⏸ held — unchanged | 28 | no |
    -| `kernel/runtime-services/settings-service.mdx` | services.settings | ⏸ held — unchanged | 31 | no |
    -| `kernel/runtime-services/sharing-service.mdx` | services.sharing | ⏸ held — unchanged | 30 | no |
    -| `kernel/runtime-services/sms-service.mdx` | services.sms | ⏸ held — unchanged | 26 | no |
    -| `kernel/runtime-services/storage-service.mdx` | services.storage | ⏸ held — unchanged | 30 | no |
    +| `kernel/runtime-services/queue-service.mdx` | services.queue | services.queue — the job queue service API | 56 | yes |
    +| `kernel/runtime-services/settings-service.mdx` | services.settings | services.settings — the settings API | 50 | yes |
    +| `kernel/runtime-services/sharing-service.mdx` | services.sharing | services.sharing — the record share API | 53 | yes |
    +| `kernel/runtime-services/sms-service.mdx` | services.sms | services.sms — the text message service API | 57 | yes |
    +| `kernel/runtime-services/storage-service.mdx` | services.storage | services.storage — the file store API | 51 | yes |
     | `kernel/runtime-services/versioning.mdx` | Runtime Service API Versioning | Runtime service API versioning policy | 51 | yes |
     | `kernel/services-checklist.mdx` | Kernel Services Checklist | Kernel services checklist for reviewers | 53 | yes |
     | `kernel/services.mdx` | Service Registry | Service registry — resolve and override | 53 | yes |
    @@ -265,28 +263,39 @@
     
     ## Verification
     
    -All at head `2594589`, `git status --porcelain` empty, `origin/main` unchanged since base `84880f9` (fetched before the gate run; no merge needed).
    +All at head `c69d5c7`, `git status --porcelain` empty. `origin/main` was merged in twice during the rework round (now at merge base `e2c4e12`); neither merge touched this PR's files.
     
    -Gate list derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (169 paths vs merge base, three-dot; 507 changed lines, under the 5,000 human-merge threshold), then reconciled: `--ran` answered `55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED`. Each command's exit code was captured before any pipe:
    +Gate list derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (178 paths vs merge base `e2c4e12`, three-dot; 640 changed lines, under the 5,000 human-merge threshold), then reconciled: `--ran` answered `74 derived famil(ies) accounted for — 74 run, 0 NOT-MEASURED (a DERIVED zero — all 74 recorded an exit code and none of them is 3)`. Each command's exit code was captured before any pipe:
     
     ```
     exit 0  node scripts/check-ci-filter-parity.mjs
     exit 0  node scripts/check-closing-keyword-parity.mjs
     exit 0  node scripts/check-closing-keyword-parity.mjs --self-test
     exit 0  node scripts/check-comment-mask-corpus.mjs
    +exit 0  node scripts/check-declaration-mirrors.mjs
    +exit 0  node scripts/check-declaration-mirrors.mjs --self-test
     exit 0  node scripts/check-doc-frontmatter.mjs
     exit 0  node scripts/check-doc-frontmatter.mjs --self-test
     exit 0  node scripts/check-doc-route-spelling.mjs --advisory
     exit 0  node scripts/check-doc-route-spelling.mjs --self-test
     exit 0  node scripts/check-docs-section-name.mjs
     exit 0  node scripts/check-docs-section-name.mjs --self-test
    +exit 0  node scripts/check-scripts-symbol-anchors.mjs
    +exit 0  node scripts/check-scripts-symbol-anchors.mjs --self-test
     exit 0  node scripts/check-section-landing-index.mjs
     exit 0  node scripts/check-section-landing-index.mjs --self-test
    +exit 0  node scripts/check-self-test-wired.mjs
    +exit 0  node scripts/check-self-test-wired.mjs --self-test
    +exit 0  node scripts/check-self-test-workflow-commands.mjs
    +exit 0  node scripts/check-self-test-workflow-commands.mjs --self-test
     exit 0  node scripts/check-system-context-census.mjs
     exit 0  node scripts/check-system-context-census.mjs --self-test
     exit 0  node scripts/check-tenant-audit-census.mjs
     exit 0  node scripts/check-tenant-audit-census.mjs --self-test
    +exit 0  node scripts/check-whole-set-label-write.mjs
    +exit 0  node scripts/check-whole-set-label-write.mjs --self-test
     exit 0  node scripts/docs-audit/check-drift-comment.mjs
    +exit 0  node scripts/pm/bare-root-worklist.mjs --self-test
     exit 0  pnpm --filter @objectstack/lint run check:doc-formula-expressions
     exit 0  pnpm --filter @objectstack/lint run check:doc-security-posture
     exit 0  pnpm --filter @objectstack/spec run check:docs
    @@ -297,6 +306,9 @@
     exit 0  pnpm --filter @objectstack/spec run check:strictness-ledger
     exit 0  pnpm --filter @objectstack/spec run check:variant-docs
     exit 0  pnpm --filter @objectstack/spec run check:yaml-examples
    +exit 0  pnpm check:agent-test-spelling
    +exit 0  pnpm check:bash32-floor
    +exit 0  pnpm check:cli-command-ids
     exit 0  pnpm check:cli-examples-parity
     exit 0  pnpm check:corpus-claim-drift
     exit 0  pnpm check:cross-package-test-inputs
    @@ -309,15 +321,20 @@
     exit 0  pnpm check:docs-spec-enumerations
     exit 0  pnpm check:docs-transcript-drift
     exit 0  pnpm check:driver-memory-census
    +exit 0  pnpm check:entry-guard
     exit 0  pnpm check:error-status-conformance
     exit 0  pnpm check:gitlink-declared
     exit 0  pnpm check:lockstep-package-count
     exit 0  pnpm check:merge-driver
     exit 0  pnpm check:nul-bytes
     exit 0  pnpm check:overlay-whitelist-table
    +exit 0  pnpm check:parse-guard
    +exit 0  pnpm check:pm-dispatch-gates
     exit 0  pnpm check:pm-widening-tells
    +exit 0  pnpm check:pnpm-filter-targets
     exit 0  pnpm check:published-readme-links
     exit 0  pnpm check:quick-reference-counts
    +exit 0  pnpm check:ratchet-remedy-authority
     exit 0  pnpm check:react-page-adapter-contract
     exit 0  pnpm check:refd-timer-probe
     exit 0  pnpm check:role-word
    @@ -333,9 +350,9 @@
     - `check-doc-anchors: 376 internal #fragment link(s) across 411 source file(s) all resolve to a real heading` (the card's acceptance gate)
     - `check-doc-frontmatter: 2 content root(s) verified, each against its own floor — content/docs 406, content/blog 3.`
     - `check-docs-nav-label`: `navTitle` named in code by 2 file(s) only; resolver executed; plugin wired into the docs loader; JSON-LD breadcrumb leg green. It reads `apps/docs/**` only, so a `navTitle:` frontmatter line is not judged by it; run as a guard that the mechanism this PR relies on is intact.
    -- `check-runtime-services-index: 8 chapter page(s) vs meta.json "pages" …` green, with the 8 held rows unchanged.
    +- `check-runtime-services-index --self-test: 59 assertions …` and `check-runtime-services-index: 8 chapter page(s) vs meta.json "pages" …` both green, with the 8 approved titles applied.
     
    -First run, for the record: `check:skill-examples` exited **3** (`PREREQUISITE NOT MET`, stale `client-react` declarations; nothing measured), cleared by building `@objectstack/client-react` and re-run green. `check:role-word` and `check:runtime-services-index` exited **1** on the verbatim table; both are real findings, handled as described above, and green at this head.
    +For the record, round 1: on the verbatim table `check:role-word` and `check:runtime-services-index` exited **1**. Both were real findings: the first is handled by the re-worded row, the second by the gate change above. `check:skill-examples` exited **3** (`PREREQUISITE NOT MET`, stale `client-react` declarations, nothing measured) until `@objectstack/client-react` was built. All green at this head.
     
     **NOT MEASURED locally:** the card's "link checker" is `.github/workflows/check-links.yml` (lychee, advisory lane); lychee is not installed in this container. It runs on this PR in CI. This diff changes no link, link text or file path, only two frontmatter keys. A full `next build` of `apps/docs` was not run locally; `fumadocs-mdx` regenerated the source index at exit 0, and `check-doc-frontmatter` parses every page with the `yaml` version the build resolves.
     
    @@ -345,8 +362,8 @@
     
     ## Acceptance notes
     
    -- **30 approved rows have no ` — ` separator** (for example `ai/natural-language-queries.mdx` → `Natural language queries over your data`), although the ruling says "separator ` — ` throughout". The ruling approves the table "as they stand", so they are applied verbatim; re-wording them is the maintainer's call.
    -- **The two folder-index pages #12312 lengthened show the long title in the footer previous/next links**: `protocol/objectql/index.mdx` and `protocol/objectui/index.mdx` carry no `navTitle`, and `flattenTree()` puts a folder index into that list. #12312 judged them "not in the page tree / `meta.json` title wins", which holds for the sidebar only. Not changed here (no approved short label exists for them). Carrier: none.
    +- **30 approved rows have no ` — ` separator** (for example `ai/natural-language-queries.mdx` → `Natural language queries over your data`), although the ruling says "separator ` — ` throughout". The ruling approves the table "as they stand", so they are applied verbatim (PM answer in review comment `5852831074`); re-wording them is the maintainer's call.
    +- **The two folder-index pages #12312 lengthened show the long title in the footer previous/next links**: `protocol/objectql/index.mdx` and `protocol/objectui/index.mdx` carry no `navTitle`, and `flattenTree()` puts a folder index into that list. #12312 judged them "not in the page tree / `meta.json` title wins", which holds for the sidebar only. Left as they are, per the PM answer in review comment `5852831074`.
     - The #12236 trap note (76 demoted body H1s as a mapping-table input) is moot for the applied rows: the table was approved as it stands.
     - Pre-existing duplicate titles exist among the generated `references/**` pages (e.g. `Mapping`, `Sharing`, `Plugin`); that is the generator half, #15403.
     

    Generated by Claude Code

  13. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    PM review — PR #20170: ACCEPT (round 2, head c69d5c7) · 2026-09-27T05:52Z

    domain:devx seat 2, session_018mA64scZ8fmpiPkrHVAwXj, reviewer of record. Report 5853052036. Every reading below was taken on GitHub or the tree. None is copied from the report.

    check reading
    Shape Draft, base main. The first body line is Fixes #12237, and it is the body's only closing keyword.
    Scope 178 files vs merge base e2c4e12: 177 authored content/docs/**/*.mdx + scripts/check-runtime-services-index.mjs, the one path added to the claim's surface by 5852831074. 0 under references/** or releases/**.
    Docs diff, per file 0 body changes. 0 frontmatter changes other than title: / navTitle:. navTitle equals the previous title on all 177. 175 titles are the approved after, verbatim. The 2 that are not (administrator-guide, tenant-audit-census) are the declared rows in the PR's voice-review section. Rendered 50–60, 0 over 60.
    Gate diff, read in full The premise moved from title to navTitle. It is read from the leading frontmatter block only, which is stricter than the old whole-text match. Absent, blank, body-only or mismatched is red, and each finding names the fix. The only widening is one layer of matching YAML quotes, which is YAML syntax. The header prose was rewritten. New floored battery (7 cases); roster floor 8 → 9.
    Gate, run by the seat Detached scratch worktree at c69d5c7: node scripts/check-runtime-services-index.mjs --self-test exit 0, and the gate itself exit 0 ("8 chapter page(s) … all enumerations agree"). The ablation's red leg is the dev's (blob-proved), not re-run here.
    CI on c69d5c7 32 runs: 25 success, 7 skipped, 0 other. The red Test Core on a48497d was the superseded head's cancelled shards, so no action.
    Changeset / governed / size skip-changeset: docs plus a root scripts/ gate, nothing published. No governed path. 640 changed lines.
    Report mcp_calls 0. Writes only through the relay. Deviations are declared, including the union run that outlived the foreground cap: the dev waited on its results file inside the turn.

    Generated by Claude Code

  14. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Landed — PR #20170 squashed as 5983d112 · 2026-09-27T15:14Z

    domain:devx seat 2, session_018mA64scZ8fmpiPkrHVAwXj. Landing record for claim 5852551816.


    Generated by Claude Code

  15. added a commit that references this issue on Sep 28, 2026
    5983d11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions