Skip to content

Commit 22b5e54

Browse files
os-zhuangclaude
andauthored
fix(spec): the variant/doc gate was blind to YAML, and one exemption was recording that as a doc gap (#4287)
Audit of the seven `generated-reference-only` exemptions in variant-docs.json. That class is the weaker of the two the ledger uses: it does not claim a variant is un-authorable, only that nobody has written a guide for it — so each one is either a real gap or a mis-filing, and none had been re-checked since it was written. One was neither. protocol/objectui/widget-contract.mdx carries a 'Widget Source' section documenting all three of inline / npm / remote, and has all along — but in YAML examples, and the coverage matcher only accepted `type: 'npm'` or ``npm``. YAML does not quote scalars, so the gate saw nothing and the ledger recorded a doc gap that did not exist while the real gap — a matcher blind to half the repo's example dialect — stayed invisible. The matcher now accepts a whole YAML mapping line; that entry is governed, and the page is under the ratchet (5 → 6 governed). Two more were mis-labelled rather than gapped: tenant isolation strategy and settings-manifest handler are not-authorable, which their own reasons already said (operator-set, consumed by Setup/Studio). The rest are genuine gaps and now say so instead of naming the generated page as if that settled it. Connector authentication is called out as the one worth acting on: ADR-0097 makes it tenant-authored, and the repo has no hand-written connector page at all. Verified the new form bites and stays tight: removing one YAML variant line fails the gate; the same variant as a bare word in prose, or embedded mid-sentence, still does not count. Claude-Session: https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4 Co-authored-by: Claude <noreply@anthropic.com>
1 parent bcf1112 commit 22b5e54

3 files changed

Lines changed: 58 additions & 19 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
The variant/doc gate's coverage matcher now recognises a YAML mapping line (`type: npm`), not only the quoted (`type: 'npm'`) and backticked (`` `npm` ``) forms.
6+
7+
Found by auditing the ledger's seven `generated-reference-only` exemptions — a class that does not say "this variant is un-authorable", only "nobody wrote a guide for it". One of the seven was not a doc gap at all: `protocol/objectui/widget-contract.mdx` has documented `inline` / `npm` / `remote` in a "Widget Source" section the whole time, in YAML examples the matcher could not see. That entry is now governed, so the page is under the ratchet. Two more (tenant isolation strategy, settings-manifest handler) were reclassified `not-authorable` — their own reasons already said `operator-set` and `consumed by Setup/Studio`. The remaining exemptions now state their gap plainly instead of pointing at the generated reference as if it settled the question; connector authentication is flagged as the one worth acting on, being tenant-authored (ADR-0097) with no hand-written connector guide anywhere in the repo.
8+
9+
The YAML form is anchored to a full line (trailing `# comment` allowed), so it stays as tight as the quoted form — a bare word in prose still does not count.

packages/spec/scripts/check-variant-docs.mts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,25 @@
2626
// renamed union) both fail: no new undeclared surface, no stale claims.
2727
//
2828
// WHAT "MENTIONED" MEANS — and what this gate does NOT prove. A variant counts as
29-
// documented if the bound doc contains either `<discriminator>: '<variant>'` (the code-
30-
// sample form) or `` `<variant>` `` (the prose/table form). The second form is
31-
// deliberately loose, and for short generic variants (`object`, `api`, `value`) it can
32-
// match an unrelated sentence — so a pass here means "the page at least says the word",
33-
// NOT "the page documents it correctly". This gate catches the omission that shipped for
34-
// months; it is not a substitute for reading the page. Tightening the loose form would
35-
// cost more false failures than the precision is worth.
29+
// documented if the bound doc contains any of three forms:
30+
//
31+
// `<discriminator>: '<variant>'` the quoted code-sample form (JS/TS/JSON)
32+
// `` `<variant>` `` the prose/table form
33+
// `<discriminator>: <variant>` a whole YAML mapping line (optionally + `# comment`)
34+
//
35+
// The YAML form was added after the widget-implementation union (#4001 audit) showed the
36+
// cost of leaving it out: `protocol/objectui/widget-contract.mdx` carries a "Widget Source"
37+
// section documenting all three of `npm` / `remote` / `inline` in YAML examples — and the
38+
// gate could not see any of them, because YAML does not quote scalar values. The entry sat
39+
// exempt as "generated-reference-only", i.e. the ledger recorded a doc gap that did not
40+
// exist while the real gap — a matcher blind to half the repo's example dialect — stayed
41+
// invisible. It is anchored to a full line so it stays as tight as the quoted form.
42+
//
43+
// The prose form is deliberately loose, and for short generic variants (`object`, `api`,
44+
// `value`) it can match an unrelated sentence — so a pass here means "the page at least
45+
// says the word", NOT "the page documents it correctly". This gate catches the omission
46+
// that shipped for months; it is not a substitute for reading the page. Tightening the
47+
// loose form would cost more false failures than the precision is worth.
3648
//
3749
// Usage:
3850
// tsx check-variant-docs.mts # fail on undeclared/orphaned/undocumented
@@ -186,7 +198,10 @@ for (const e of ledger.entries) {
186198
const undocumented = u.variants.filter((v) => {
187199
const anchored = new RegExp(`${esc(u.disc)}\\s*:\\s*['"\`]${esc(v)}['"\`]`);
188200
const token = new RegExp('`' + esc(v) + '`');
189-
return !anchored.test(blob) && !token.test(blob);
201+
// YAML mapping line: unquoted scalar, so it must be anchored to the whole
202+
// line (trailing `# comment` allowed) to stay as tight as the quoted form.
203+
const yaml = new RegExp(`^\\s*${esc(u.disc)}\\s*:\\s*${esc(v)}\\s*(#.*)?$`, 'm');
204+
return !anchored.test(blob) && !token.test(blob) && !yaml.test(blob);
190205
});
191206
if (undocumented.length) {
192207
errors.push(

packages/spec/variant-docs.json

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,22 @@
1313
" generated-reference-only — the type is documented solely under content/docs/references/,",
1414
" which build-docs.ts generates FROM these schemas. Generated docs cannot drift.",
1515
" not-authorable — wire protocol, engine RPC, or a runtime-derived discriminator. Nobody",
16-
" hand-writes the variant, so no hand-written doc owes it a mention."
16+
" hand-writes the variant, so no hand-written doc owes it a mention.",
17+
"",
18+
"AUDIT NOTE (#4001 follow-up). `generated-reference-only` is the weaker of the two: it",
19+
"does not say the variant is un-authorable, only that nobody has written a guide for it.",
20+
"Every such entry was re-checked against the docs tree, which moved four of the original",
21+
"seven:",
22+
" - widget implementation became GOVERNED. protocol/objectui/widget-contract.mdx has",
23+
" documented all three variants in a `Widget Source` section the whole time; the gate",
24+
" could not see them because its matcher was blind to YAML. Fixed in the matcher.",
25+
" - tenant isolation and settings-manifest handler became NOT-AUTHORABLE. Both reasons",
26+
" already said the words (`operator-set`, `consumed by Setup/Studio`); the label just",
27+
" disagreed with them.",
28+
" - the remaining exemptions now state the gap plainly instead of pointing at the",
29+
" generated page as if it settled the question. Connector authentication is the one",
30+
" worth acting on: it is tenant-authored (ADR-0097 declarative `connectors:`) and the",
31+
" repo has no hand-written connector guide at all."
1732
],
1833
"entries": [
1934
{
@@ -53,44 +68,44 @@
5368
{
5469
"key": "type:inline|npm|remote",
5570
"label": "widget implementation",
56-
"exempt": "generated-reference-only",
57-
"reason": "Documented at content/docs/references/ui/widget.mdx, generated from WidgetManifestSchema."
71+
"docs": ["content/docs/protocol/objectui/widget-contract.mdx"],
72+
"note": "Was exempt as generated-reference-only until the #4001 audit: the page's `Widget Source` section documents all three variants, but in YAML examples, which the coverage matcher could not see. The exemption was recording a gate blind-spot as a doc gap."
5873
},
5974
{
6075
"key": "type:cron|interval|once",
6176
"label": "job schedule",
6277
"exempt": "generated-reference-only",
63-
"reason": "Documented at content/docs/references/system/job.mdx."
78+
"reason": "No hand-written guide covers the Job type; content/docs/references/system/job.mdx is generated from JobSchema. Note that automation/flows.mdx's `schedule: { type: 'cron' }` is a DIFFERENT surface — a flow start node's own config (flow.zod.ts tombstones a top-level `schedule`) — so it neither documents nor governs this union."
6479
},
6580
{
6681
"key": "type:api-key|basic|bearer|none|oauth2",
6782
"label": "connector authentication",
6883
"exempt": "generated-reference-only",
69-
"reason": "Documented at content/docs/references/integration/connector-auth.mdx."
84+
"reason": "DOCUMENTATION GAP, not a non-authorable surface — the strongest candidate among the exemptions for a real guide. Connector auth IS tenant-authored (ADR-0097 materializes a declarative `connectors:` entry naming a `provider` into a live connector at boot), but the repo has no hand-written connector page at all: the only prose is one paragraph inside automation/flows.mdx about the `connector_action` node. Until a guide exists there is nothing for this entry to bind to; when one is written, bind it and delete this exemption."
7085
},
7186
{
7287
"key": "type:api-key|basic|bearer|none",
7388
"label": "connector auth (environment-artifact projection)",
7489
"exempt": "generated-reference-only",
75-
"reason": "Narrower projection of the connector-auth union carried in the deploy artifact; same generated reference."
90+
"reason": "The declarative connector-instance auth shape (ADR-0097), reached via EnvironmentArtifactSchema.metadata.connectors[].auth. It carries a `credentialRef` rather than an inline secret, and omits `oauth2` because the authorization-code/refresh lifecycle is the enterprise tier (ADR-0015) — not because the artifact narrows it. Inherits the gap above; a connector guide would govern both."
7691
},
7792
{
7893
"key": "strategy:isolated_db|isolated_schema|shared_schema",
7994
"label": "tenant isolation strategy",
80-
"exempt": "generated-reference-only",
81-
"reason": "Documented at content/docs/references/system/tenant.mdx. Operator-set, not tenant-authored."
95+
"exempt": "not-authorable",
96+
"reason": "Chosen by the operator when a tenant is provisioned, not written into any tenant's metadata — so no hand-written authoring page owes it a mention. (Was labelled generated-reference-only, though its own reason already said `operator-set`; the generated page at content/docs/references/system/tenant.mdx remains the reference.)"
8297
},
8398
{
8499
"key": "type:cast|constant|javascript|lookup|map",
85100
"label": "sync mapping transform",
86101
"exempt": "generated-reference-only",
87-
"reason": "Generated reference only; no hand-written page covers the sync transform set yet."
102+
"reason": "DOCUMENTATION GAP. `mappings:` is authored on a stack (releases/v12.mdx records `defineMapping`), but no hand-written page covers the transform set — the only prose that names a mapping at all is protocol/kernel/config-resolution.mdx, on a different subject. Lower priority than the connector gap: the transforms are a closed, self-describing set. Bind a page here when one is written."
88103
},
89104
{
90105
"key": "kind:action|http|navigate",
91106
"label": "settings-manifest handler",
92-
"exempt": "generated-reference-only",
93-
"reason": "Generated reference only; consumed by Setup/Studio, not tenant-authored metadata."
107+
"exempt": "not-authorable",
108+
"reason": "Declared by a settings manifest that Setup/Studio ships and consumes; it is not part of the metadata a tenant authors. (Was labelled generated-reference-only, though its own reason already said so.)"
94109
},
95110
{
96111
"key": "viewKind:form|list",

0 commit comments

Comments
 (0)