Status 2026-09-12
What is left, the earlier body kept below for context:
Layers 1 and 2 are on main (PRs 708 to 721): toolutil.Card is the one card writer, docs/development/markdown-card.md defines it, every formatter is migrated, and both gates read zero. Layer 3, what remains: (1) flip the card and bool-time contexts into check-md-escaping and make the runtime registry gate fail rather than log (fill mdGateExceptions, turn mdGateLog into a failure); (2) remove the FmtMd* constants from internal/toolutil/markdown.go and their last caller in cmd/gen_llms; (3) the English docs and the Spanish site page on the card; (4) the four findings that stand between here and the flip: a HintAction id in keys that the catalog does not have (and a test validating every HintAction literal against the catalog), the bool-time context judging five values that are not Markdown, a declaration for the elicitationtools consent lines, and the leftovers in prompts and the default surface's own output.
The Markdown a tool returns is what a model reads, and the shape of it is currently decided per file rather than by any rule. Measured on the tree today:
- 27 packages render a single-object card as a
| Field | Value | table.
- 76 render one as a bulleted list of
- **Label**: value.
- 4 files use both shapes, so the same package answers two questions in two shapes.
internal/toolutil/markdown.go has helpers for a URL, pagination, a list summary, a description and an empty result, and the FmtMd* constants for the commonest bulleted labels. It has no helper for a field row, which is why the choice is made again in every formatter and why the two shapes drifted apart.
What surfaced this
A formatter that appends to a bulleted card was given table rows. A pipe row with no header and no delimiter row is not a table in GFM, so those lines rendered as literal | Runners Token | glrt-… | text in the middle of the card. It was caught and fixed on the 1:1 branch and never reached main, but nothing would have caught it: no gate compares a formatter's output shape against the section it writes into, and the test asserted only that a substring was present.
What the audit should settle
- One rule for a one-object card, table or list, and a helper in
toolutil that implements it so a formatter cannot pick the other by accident.
- The four files that use both, resolved to whichever the rule says.
- Whether a gate is worth it: a check that a formatter never emits a pipe row without a preceding delimiter row would have caught the defect above mechanically, in the same spirit as
make check-md-escaping.
- A sweep for values that should go through an existing helper and do not: booleans printed with
%v instead of BoolEmoji, labels re-spelled instead of using the FmtMd* constant, timestamps not passed through FormatTime.
Queued deliberately after the 1:1 field work, not before it.
Status 2026-09-12
What is left, the earlier body kept below for context:
Layers 1 and 2 are on main (PRs 708 to 721): toolutil.Card is the one card writer, docs/development/markdown-card.md defines it, every formatter is migrated, and both gates read zero. Layer 3, what remains: (1) flip the card and bool-time contexts into check-md-escaping and make the runtime registry gate fail rather than log (fill mdGateExceptions, turn mdGateLog into a failure); (2) remove the FmtMd* constants from internal/toolutil/markdown.go and their last caller in cmd/gen_llms; (3) the English docs and the Spanish site page on the card; (4) the four findings that stand between here and the flip: a HintAction id in keys that the catalog does not have (and a test validating every HintAction literal against the catalog), the bool-time context judging five values that are not Markdown, a declaration for the elicitationtools consent lines, and the leftovers in prompts and the default surface's own output.
The Markdown a tool returns is what a model reads, and the shape of it is currently decided per file rather than by any rule. Measured on the tree today:
| Field | Value |table.- **Label**: value.internal/toolutil/markdown.gohas helpers for a URL, pagination, a list summary, a description and an empty result, and theFmtMd*constants for the commonest bulleted labels. It has no helper for a field row, which is why the choice is made again in every formatter and why the two shapes drifted apart.What surfaced this
A formatter that appends to a bulleted card was given table rows. A pipe row with no header and no delimiter row is not a table in GFM, so those lines rendered as literal
| Runners Token | glrt-… |text in the middle of the card. It was caught and fixed on the 1:1 branch and never reached main, but nothing would have caught it: no gate compares a formatter's output shape against the section it writes into, and the test asserted only that a substring was present.What the audit should settle
toolutilthat implements it so a formatter cannot pick the other by accident.make check-md-escaping.%vinstead ofBoolEmoji, labels re-spelled instead of using theFmtMd*constant, timestamps not passed throughFormatTime.Queued deliberately after the 1:1 field work, not before it.