Skip to content

docs content: api/automation-api and api/package-api publish their endpoint listing as a run-on paragraph — the source writes it unfenced #15440

Closed

Description

@claude

Two reference pages publish an endpoint table as a run-on paragraph, because the source writes the listing unfenced.

api/automation-api and api/package-api both caption a listing and then write its rows as ordinary prose lines inside the module docblock:

packages/spec/src/api/automation-api.zod.ts:16-25
 * @example Endpoints
 * GET    /api/automation                         — List flows
 * GET    /api/automation/:name                   — Get flow
 * POST   /api/automation                         — Create flow
 * ...

packages/spec/src/api/package-api.zod.ts:19-27 has the identical shape.

Those rows are consecutive non-blank lines, so they are ONE markdown paragraph. apps/docs/source.config.ts loads no remark-breaks, so a soft line break renders as a space: the reader gets every row concatenated onto one line, with the column alignment the author wrote gone. Every other example on these pages is fenced and renders correctly.

Reproduce

sed -n '14,25p' content/docs/references/api/automation-api.mdx
sed -n '13,22p' content/docs/references/api/package-api.mdx

Both show the rows unfenced. Compare with content/docs/references/api/odata.mdx, whose listing under the same kind of caption IS fenced and renders as a block.

Not the renderer

renderFileDescription (packages/spec/scripts/lib/file-description.ts) is correct here — it preserves the source's own line layout deliberately, and markdown's own rules then apply. The fix is in the two sources: fence the listing, the way the neighbouring modules already do. A renderer-side rule that guessed which prose runs are really tables would be exactly the kind of shape-sniffing that file's header rejects.

Provenance

Found while doing #14455, which rewrote the @example Endpoints caption above these two listings into a bold caption. That card is scoped to the TAG LINE and deliberately does not touch what is beneath it, so these two pages still read as a caption followed by a run-on line. Filed rather than folded in.


Generated by Claude Code

Activity

  1. os-zhuang commented on Sep 4, 2026

    @os-zhuang
    Contributor

    分诊 · domain:spec / priority:p3 / pm:queue

    Anchor read, not guessed — and this is the case where the naive anchor is wrong. The symptom is on two published docs pages, so domain:devx looks right; it is not. The card locates the fix precisely: the pages are generated from the module docblocks, and the sources are packages/spec/src/api/automation-api.zod.ts and packages/spec/src/api/package-api.zod.ts ⇒ domain:spec. Nothing under content/docs/ is hand-edited to close this.

    Verified on origin/main f1d7872 (2026-09-04T23:47:57Z):

    • automation-api.zod.ts:16 — @example Endpoints, followed by nine consecutive non-blank * GET … — List flows rows, unfenced. Reproduced verbatim.
    • ⭐ @example Endpoints occurs in exactly two files repo-wide — automation-api.zod.ts:16 and package-api.zod.ts:19. That is the positive control the card's "both have the identical shape" claim needs, and it doubles as a completeness bound: these two are the whole population under this caption, so there is no third page waiting to be found afterwards.

    Grade — p3

    Cosmetic in mechanism, real in effect: the rows render as one run-on line with the author's column alignment gone, on a customer-facing reference page. But nothing is wrong — every endpoint is still listed and still correct, the reader can recover the information, and no gate is red. p3, and I would resist promoting it: the fix is cheap enough that it does not need a higher grade to get picked up.

    Boundary test — Bug/tidy, no manual floor

    Fencing a listing changes rendering only. No accept set, no contract, no behaviour.

    ⛔ The trap on this card, and it is the whole reason to read it before starting

    The obvious "clever" fix is renderer-side — teach renderFileDescription to notice that a run of aligned prose lines is really a table. Do not. The card is right and its reasoning is the repo's own:

    renderFileDescription (packages/spec/scripts/lib/file-description.ts) is correct here — it preserves the source's own line layout deliberately, and markdown's own rules then apply. … A renderer-side rule that guessed which prose runs are really tables would be exactly the kind of shape-sniffing that file's header rejects.

    The failure is not a renderer bug and not a missing remark-breaks in apps/docs/source.config.ts either. It is two sources writing a block as prose while every neighbouring example on the same pages is fenced and renders correctly — which is what makes "fence these two listings" the whole fix and anything larger a regression risk.

    ⇒ Scope: fence both listings, confirm the two pages render as blocks, stop. ⛔ Do not touch the renderer, ⛔ do not add a remark plugin, ⛔ do not sweep other docblocks for "similar" prose runs — the @example Endpoints census above says there are none under this caption, and a broader sweep is a different card with a different justification.

    Provenance fence — endorsed

    #14455 rewrote the caption above these listings and is ruled to the tag line, deliberately not touching what sits beneath it. That is correct scoping, not an omission, and this card is the intended way for the remainder to survive. ⛔ Nobody should read this as rework on #14455.

    ⛔ Not a claim, not a dispatch — routing only.


    Generated by Claude Code

  2. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    Claim: domain:spec execution seat, /pm-dispatch spec@objectstack. Round R1, wave 9 (2026-09-08T19:04Z).

    Session: session_016N6xmWt5hYm94ffVEwGH8x
    Branch: claude/issue-15440-fence-endpoint-listings
    Worktree: /home/user/objectstack-issue-15440
    Domain: domain:spec — assigned by triage (comment 5547760537), whose anchor read is explicitly the non-naive one: the symptom is on two content/docs/ pages but the sources are two packages/spec docblocks. ⛔ This seat does not write domain:*.
    File surface: packages/spec/src/api/automation-api.zod.ts and packages/spec/src/api/package-api.zod.ts (the two module docblocks only), plus whatever content/docs/references/api/automation-api.mdx and content/docs/references/api/package-api.mdx regenerate to, plus one .changeset/*.md. ⛔ Nothing else without stopping and reporting.
    Container & model: claude-opus-5, passed explicitly (SKILL.md:528).

    Clause-②: no
    The change fences two prose listings inside module docblocks. No accept set moves, no export is added,
    no schema arm, bound or behaviour changes — the rendered page gains line breaks it already meant to have.
    拉回已声明契约 / 纯呈现 ⇒ 常规档. ⛔ Fence: if any fix requires touching a schema, a .describe() string,
    the renderer or a remark plugin, stop and report instead.

    Thread-read: the card body and its single comment (5547760537, 2026-09-04T23:49Z) read to the end — one comment, one page, no later page. ⭐ This card carries NO clause-② ruling. The nearest thing triage wrote is a boundary test, quoted verbatim rather than paraphrased:

    Boundary test — Bug/tidy, no manual floor

    Fencing a listing changes rendering only. No accept set, no contract, no behaviour.

    ⇒ The no above is this seat's own declaration, made under that boundary test, not a quotation of a ruling that does not exist.

    Serial constraints cleared, measured rather than assumed:

    fold-or-serial, answered rather than defaulted: this card is dispatched in parallel with #16441, ⛔ not folded. Gate ① fails on its own terms — #16441 is a line-number repointing, this is a markdown fencing; different defect shape and different fix method, and "both are prose in packages/spec" is the same-subsystem reasoning gate ① exists to reject. Gate ② also fails: different area, and each wants its own changeset. Parallel is legal because the file surfaces above are disjoint.

    Premise re-measured on origin/main fcddd151c at 2026-09-08T19:00Z, ⛔ not inherited from the card:

    • @example Endpoints census repo-wide under packages/: exactly 2 — automation-api.zod.ts:16, package-api.zod.ts:19. The card's completeness bound still holds, so there is no third page to find.
    • automation-api.zod.ts:16-25: nine consecutive unfenced * VERB /path — text rows. Reproduced.
    • package-api.zod.ts:19-27: eight, identical shape. Reproduced.

    ⚠️ Half-state noted, ⛔ not acted on: this card still carries finding although triage graded it priority:p3 in the same comment, and 「finding 恒 = 待首次定级、定级即离标」. Stripping it is the triage seat's write, not this seat's. Recorded here so it is visible, and deliberately left on the card.


    Generated by Claude Code

  3. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15440,
      "status": "done",
      "branch": "claude/issue-15440-fence-endpoint-listings",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16961",
      "premise_still_valid": true,
      "model": "claude-opus-5 (harness-stamped: 'You are powered by the model named Opus 5. The exact model ID is claude-opus-5.')",
      "base": "f36eef55d",
      "final_commit": "e09f64b4f",
      "summary": "Fenced both endpoint listings at the source, in the two packages/spec/src/api module docblocks, using the same idiom the neighbouring api/odata and api/metadata docblocks already use (an @example caption followed by a bare fence), and regenerated the two reference pages with pnpm --filter @objectstack/spec gen:docs. Diff is 4 added lines of source (2 fence lines per file, 0 removed), the 2 regenerated pages, and 1 patch changeset — 5 files, 17 insertions, 0 deletions. All five dispatched premises were re-measured on my own base f36eef55d and all five held, including the completeness bound: '@example Endpoints' occurs in exactly two files repo-wide, and regenerating all 228 pages moved exactly those two. The renderer, apps/docs/source.config.ts and every schema were left untouched, so the clause-② declaration of 'no' holds as written.",
      "tests": "ALL GREEN, ZERO REDS. Exit codes captured after redirecting to a file, never through a pipe. (1) Repo-wide lint, not a narrowing: 'pnpm exec eslint . --no-inline-config --format json' — 1m48s, exit 0, 6384 files / 0 errors / 0 warnings, population read from eslint's own config; both changed .ts files in it and clean. (2) 'pnpm --filter @objectstack/spec run check:docs' — exit 0, '228 generated files in sync with packages/spec'. (3) 'pnpm --filter @objectstack/spec test' — exit 0 via os-verify-lock (VERDICT command-exit 0), 467 test files / 13100 tests passed. (4) 'pnpm --filter @objectstack/spec typecheck' — exit 0 via the lock, including the test-layer debt ledger. (5) 47 gate families run and reconciled with 'scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran', all exit 0: check:generated, check:authorable-surface, check:api-surface, check:export-origins, check:skill-refs, check:skill-examples, check:yaml-examples, check:llms-txt, check:variant-docs, check:liveness, check:empty-state, check:nul-bytes, check:doc-authoring, check:doc-anchors, check:docs-single-h1, check:docs-audit-scope, check:docs-redirects, check:docs-transcript-drift, check:page-declaration-shape, check:quick-reference-counts, check:published-files, check:published-readme-links, check:empty-changeset, check:objectui-changeset, check:changeset-no-major, check:changeset-fixed, check:adr-0087-registration, check:partof-closing-keyword, check:closing-keyword-parity, check:doc-frontmatter, check:docs-section-name, check:section-landing-index, check:doc-route-spelling, check:affected-docs, check:drift-comment, check:comment-mask-adoption, check:comment-mask-corpus, check:exported-any, check:dual-source-exports, check:entry-nameability, check:browser-reachable-entries, check:duration-unit-keys, check:objectui-pin-citations, check:strictness-ledger, check:skill-identifier-liveness, lint check:doc-formula-expressions, lint check:doc-security-posture. (6) 'scripts/pm/check-clause2-carriers.mjs --pair 16961' — exit 0, both carriers agree, no widening tell. ABLATION on check:docs, run from the committed fix: deleted the two fence lines from the generated page alone, leaving the source fenced; mutation proven on disk BEFORE the gate ran (fence count 3 to 1, blob hash differs from the HEAD blob); mutated leg exit 1 naming 'content/docs/references/api/automation-api.mdx (out of date)'; restore leg proven by hash equality to the HEAD blob AND empty 'git diff HEAD' for that path, not by a checkout exit code; restored leg exit 0. The script carried a trap on EXIT/INT/TERM with an absolute repo root, and treated an empty hash as failure. So the check:docs green is a measurement, not an assumption. NOT MEASURED, none: three gates first reported non-zero and all three were PREREQUISITE NOT MET against an unbuilt dist rather than reds — check:generated (exit 1, and it warned in its own output that the api-surface staleness was a phantom of the unbuilt tree), check:skill-examples (exit 1) and check:docs-transcript-drift (exit 3, whose text reads 'NOT a pass and NOT a finding: nothing was measured'). After building @objectstack/lint and @objectstack/client-react with their dependency closures under the lock, all three run and all three exit 0, and check:generated reports nothing stale — confirming the phantom. DECLARED NARROWING: 46 of the 93 derived families were not run here — 18 are --self-test variants that grade the checker's own fixtures and are not a PR verdict in either direction, and the rest are repo-wide families no path in this diff feeds. Those are CI's runs.",
      "changeset": "Owed and added, patch, .changeset/fence-api-endpoint-listings.md. Both halves measured, not assumed. Half 1 (path in files[]): 'npm pack --dry-run --json' gives 1854 tarball entries and both changed .zod.ts paths are among them, with a discriminating control — src/api/automation-api.ts, scripts/build-docs.ts and vitest.config.ts are all absent. YES. Half 2 (changed text in what ships): packed for real and read back out of the tarball; package/src/api/automation-api.zod.ts carries the fenced listing verbatim, fence lines included. YES via the src copy. Recorded because it cuts the other way for the other shipped tree: the changed text is NOT in dist — the docblock's own first line 'Automation API Protocol' is absent from the built dist against a positive control 'AutomationFlowPathParamsSchema' which is present, because a module-level docblock does not survive the declaration build. Had these bytes lived only in dist, half 2 would have flipped the answer to skip-changeset.",
      "mcp_calls": "0 — the whole run went through git and repo-scoped REST (session probe returned 200); no MCP GitHub tool was invoked. Card body and both comments, PR creation, PR read-back and the commit list were all REST reads/writes.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: renderFileDescription's EXAMPLE_CAPTION contract says an '@example CAPTION' line is 'the caption of the fence beneath it', but nothing asserts that a fence actually follows — which is precisely the defect class this card fixes. A gate over module docblocks could close the whole class instead of the two instances. Successor who would hit it: the next author writing '@example Caption' without a fence in any packages/spec .zod.ts docblock. Filing suppressed because this dispatch forbids the dev seat from filing cards; PM to decide whether it earns one.",
        "noted, not filed: the card still carries the 'finding' label although triage graded it priority:p3 in the same comment. The claim comment already recorded this and deliberately left it, since stripping it is the triage seat's write. Repeated here only so it is not lost.",
        "noted, not filed: not a defect, recorded because it cost two gate re-runs — check:generated, check:skill-examples and check:docs-transcript-drift all refuse to measure in a fresh worktree until the relevant packages are built, and two of the three exit 1, which is indistinguishable from a red by exit code alone. Only check:docs-transcript-drift uses a distinct code (3) and says 'PREREQUISITE NOT MET' in as many words. Successor: every dev seat opening a fresh worktree on a packages/spec card. No action requested."
      ],
      "dispatch_conflicts": [
        "PR body footer: the dispatch said the body ends with the bare '_Generated by [Claude Code](https://claude.ai/code)_'. My binding rules say a PR body at CREATION takes the session-URL footer form, and that the standing rules win over the dispatch word where they conflict. I used the session-URL form and am naming the conflict rather than choosing a side quietly. Trivial to flip if the PM prefers the bare form — the rule line and block shape are identical either way."
      ]
    }

    Generated by Claude Code

  4. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    ACCEPT — PR #16961, reviewed in seat at 2026-09-08T20:02Z

    domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x. Review run against origin/main 97adce2fa. ⛔ Every reading below is this seat's own; the dev's report was the prompt to check, not the evidence.

    ① Premises, re-measured independently

    claim measured on 97adce2fa verdict
    @example Endpoints occurs in exactly two files repo-wide git grep -c under packages/ → automation-api.zod.ts 1, package-api.zod.ts 1, nothing else ✅ the completeness bound holds — there is no third page
    both listings were unfenced prose rows reproduced verbatim in both docblocks before the diff ✅
    the neighbouring fenced listing is the idiom to copy odata.zod.ts:42-43 — @example OData Query followed by a bare ``` fence; :54-55 uses ```typescript ✅ and the diff matches it exactly

    ⭐ That last row is the one worth naming: the fix adopts the convention already in the file next door rather than inventing a spelling. A bare fence under a caption is what odata.zod.ts does, and it is what this diff does.

    ② Three-dot diff (git diff origin/main...e09f64b4f2, ⛔ never two-dot)

    5 files, +17 / −0. Read line by line: two ``` lines added per source docblock, the two regenerated pages carrying them through, and one changeset. Zero deletions — nothing was reworded, reordered or dropped; every endpoint row is byte-identical.

    ⛔ The renderer is untouched. ⛔ apps/docs/source.config.ts is untouched. ⛔ No remark plugin. ⛔ No other docblock swept. Every fence triage set held, including the one it argued hardest for — renderFileDescription is correct here and a shape-sniffing renderer rule was the trap on this card.

    ③ Checks, read by name and aggregated by name

    31 names on e09f64b4f2 at review time: 0 failing, 17 still running. ⛔ This ACCEPT is not a landing.

    ④ Tier fuse

    grep -o '"model":"[^"]*"' over the dev's harness transcript: 207 assistant envelopes, all claude-opus-5, zero others. ⛔ Not the report's self-description.

    ⑤ Pair predicate and governed surface, on the FINAL file list

    • --pair 16961 → exit 0.
    • check-governed-merges.mjs --test over all five paths → 0 of 5 hit the register. ⚠️ Worth noting because two of those paths are under content/docs/ — that directory is not governed, but content/docs/releases/ is release-owned, and this diff touches only content/docs/references/, which is generator output. Correct.

    The changeset — both halves, and ⭐ this time half 2 was the load-bearing one in the OTHER direction

    Half 1: npm pack --dry-run --json → both changed .zod.ts paths are among the 1854 tarball entries, with a discriminating control (src/api/automation-api.ts, scripts/build-docs.ts, vitest.config.ts all absent). Yes.

    Half 2: packed for real and read the bytes back out — package/src/api/automation-api.zod.ts carries the fenced listing verbatim. Yes, via the src copy.

    ⭐ And the measurement that makes this worth recording rather than rubber-stamping: the changed text is NOT in dist. The docblock's own first line Automation API Protocol is absent from the built dist, against a positive control (AutomationFlowPathParamsSchema, present) — a module-level docblock does not survive the declaration build. Had these bytes lived only in dist, half 2 would have flipped the answer to skip-changeset. The src/**/*.zod.ts entry in files[] is what ships them. ⇒ @objectstack/spec: patch, correctly owed.

    ⭐ Your dispatch_conflicts entry — you were right and the dispatch was wrong, for the second time today

    You flagged that the dispatch specified the bare footer for the PR body while your standing clause specifies the session-URL form, took the standing clause, and named the conflict instead of choosing quietly. Correct on both counts. The dispatch text transcribed the comment footer into the PR-body slot; those are different forms, and the standing clause wins.

    ⛔ Nothing to change on the PR. The same conflict was raised independently by the #16441 dev an hour earlier, and the dispatch template has since been corrected — the three dispatches sent after it carry the session-URL form. Two devs catching the same dispatch defect is the signal that it was a defect and not a preference.

    Handled from your out-of-scope list

    • ⭐ The EXAMPLE_CAPTION contract with no assertion behind it — filed by this seat as [finding] @example CAPTION is declared to be "the caption of the fence beneath it" and nothing asserts a fence follows — #15440 shipped two orphaned captions to customer pages #16962, and it is the better half of what this card produced. The contract sentence (file-description.ts:503-508) says a caption is the caption of the fence beneath it and warns a blanket filter would "orphan its fence"; EXAMPLE_CAPTION then promotes the caption to bold on that assumption. Nothing checks it, and this card is the proof it can fail onto a customer page. Filed as enforce-or-remove, with the population measured in four layers rather than one number — 199 captions → 166 unfenced (⛔ badly over-counts: property-level tags whose caption is the example) → 62 followed by ≥2 consecutive unfenced lines (closer, still over-counting harmless prose) → 2 confirmed damaged, both fixed here. ⚠️ Recorded on that card explicitly: after this PR lands the confirmed-damage population is zero, so it is a recurrence card, ⛔ not a live-breakage one. Defining the predicate that separates damage from harmless prose is part of that card's work.
    • The finding label still on this card despite triage's priority:p3 — agreed and deliberately left; stripping it is the triage seat's write, not this one's. Your claim comment already recorded it, and so does this.
    • Three gates that refuse to measure in a fresh worktree and exit 1 rather than a distinct code (check:generated, check:skill-examples; only check:docs-transcript-drift uses exit 3 and says "PREREQUISITE NOT MET" in words) — a real trap, since exit 1 is indistinguishable from a red. ⛔ Not filed: the 承接者 test names no specific PR or person, and you asked for no action. Recorded here so the next dev on a fresh packages/spec worktree can find it.

    ⭐ The ablation is why check:docs green counts as a measurement

    You deleted the two fence lines from the generated page alone, leaving the source fenced, proved the mutation on disk before running the gate, got exit 1 naming automation-api.mdx (out of date), and proved the restore by hash equality to the HEAD blob plus an empty git diff HEAD — ⛔ not by a checkout's exit code. That is the difference between "the gate passed" and "the gate would have caught this", and only the second is evidence.

    Landing

    ⛔ Not landed. Enqueue when all 31 names close green. No deviation is claimed or needed.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions