Skip to content

Commit fef76a4

Browse files
committed
fix(docs-audit): scope the route-matcher narrowing to the two hits that were wrong
The first cut refused a concrete example value in ANY parameter with no static segment behind it in the tail. Measured over the 228 declared route tails against all 195 hand-written docs, that reads -36.3% and takes the good rows with it: `/data/:object` stops matching a page that writes `POST /api/v1/data/accounts`, which is the leniency working as designed. Only the tail's LAST segment has nothing behind it in the pattern, so only there does a concrete value have to end the documented path. Re-measured: 571 -> 524 rows, -8.2%, zero tails going from matching some page to matching none, and the 42 rows arm 2 drops are one shape — a page documenting a LONGER route, or a monorepo source path (`packages/core`) matched as the wire route `/api/v1/packages/:id`. README carries both narrowings, the rejected variant with its number, and the comment-provenance clause. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
1 parent c7182b8 commit fef76a4

2 files changed

Lines changed: 102 additions & 20 deletions

File tree

‎scripts/docs-audit/README.md‎

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ Each anchor kind names its own origin, from the same field the JSON publishes as
8787
| kind | the clause |
8888
|:--|:--|
8989
| `symbol` | `a field of interface MetaOverlayCacheKey` · `a method of class RestServer` · `a top-level function` |
90-
| `route` | `a path literal in RestServer` · `bridged from symbol enforceEnvironmentOwnership — its route-source handler names it` |
90+
| `route` | `a path literal in RestServer` · `a path literal in a comment in meta` · `bridged from symbol enforceEnvironmentOwnership — its route-source handler names it` |
9191
| `sdk` | `the route ledger binds it to GET /api/v1/ui/view/:object/:type` |
92-
| `literal` | `a string literal in cacheKeyOf` |
92+
| `literal` | `a string literal in cacheKeyOf` · `a string literal in a comment in cacheKeyOf` |
9393
| `command` | `read off packages/cli/src/commands/environments/bind.ts` |
9494
| `rule` | `a @docs-rule block in packages/objectql/src/engine.ts` |
9595

@@ -108,6 +108,65 @@ cannot start deciding with it without going red. Container-qualified *discrimina
108108
the separate, later step below (option D), and it decides from the declarations directly,
109109
never by parsing this clause back out.
110110

111+
### A row says whether its anchor came from CODE or from PROSE (#16696)
112+
113+
A comment line is a changed line, so a path written in a JSDoc mints a `route` anchor like
114+
any other. That is deliberate and stays: a doc comment that newly documents a real route is
115+
sometimes exactly the signal that the page documenting it needs re-reading, and ⛔ excluding
116+
comments from anchor sources is the one repair ruled out. What was missing is the reader's
117+
half — the row did not say which kind of line it found.
118+
119+
Measured on PR #16694: seven of that run's nine hand-written rows rode
120+
`/environments/:environmentId`, and that anchor entered the diff on **exactly one added
121+
line**, English prose inside a JSDoc block in `packages/client/src/index.ts`. Every row read
122+
`a path literal in meta`, indistinguishable from a registration. A reader who opens seven
123+
pages and finds seven non-answers stops opening them, and then the list fails on the PR
124+
where it is right.
125+
126+
So the clause carries the strength beside the location: `a path literal in a comment in meta`
127+
against `a path literal in meta`. Like every other clause it is **publication, not
128+
discrimination** — the mask is read only to word the clause, nothing is admitted or dropped
129+
by it, and `--self-test` pins both directions: the comment-only anchor is still an anchor,
130+
and a code occurrence still produces a clause that says nothing about comments.
131+
132+
### A route anchor must name the route the page names (#16696)
133+
134+
The doc-side matcher lets a page spell a parameter three ways — `:type`, `{type}`, or a
135+
concrete example value — so `GET /api/v1/meta/object/account/history` counts as documenting
136+
`/:type/:name/history`. Both leniencies used to be unconditional, and each one alone is
137+
enough to list a page for a route it does not document. `protocol/kernel/metadata-service.mdx`
138+
was listed via `/environments/:environmentId`, a string that page does not contain, on:
139+
140+
```
141+
:204 `/pub/v1/environments/:id/artifact[?commit=<id>]` — a DIFFERENT parameter name
142+
:213 'https://cloud.example.com/pub/v1/environments/env_42/artifact?commit=cmt_1a2b'
143+
— a concrete value with the tail's right edge open
144+
```
145+
146+
Two narrowings, one per hit. A parameter **written as a parameter** must name the same
147+
parameter, either spelling. A **concrete value filling the tail's last segment** must end the
148+
documented path — every earlier parameter is still bounded by the segments to its right, so
149+
`GET /api/v1/data/accounts` still matches `/data/:object` while
150+
`GET /api/v1/data/account/123` no longer does, because that is `/data/:object/:id` and that
151+
is the tail which lists it.
152+
153+
Measured over the **228** distinct route tails declared in this repo's **28** route-ledger
154+
files against all **195** hand-written docs: tail×page rows **571 → 524**, −8.2%, with **zero**
155+
tails going from matching some page to matching none. Arm 1 accounts for 5 of the 47 dropped
156+
rows; the other 42 are one shape — `/packages/:id` matched eleven pages on the monorepo
157+
source paths `packages/core`, `packages/spec`, `packages/plugins` …, and `/meta/:type`
158+
matched `api/environment-routing.mdx` on the prose *"data/meta/AI/automation"*.
159+
160+
⛔ Requiring **every** match to sit at the end of the documented path — the way the same tail
161+
is matched against a ledger row — was measured and rejected: −36.3%, and it deletes
162+
`api/environment-routing.mdx`, the most on-target page of that run, whose every occurrence is
163+
`/api/v1/environments/:environmentId/...` with a segment after it. A route PREFIX written
164+
with the route's own parameter named IS a page documenting that route.
165+
166+
⛔ **No claim is made here about how often either shape occurs.** One PR is nine rows, not a
167+
population; the corpus figures above are about the matcher over the declared route surface,
168+
which is a different statement and is the only one measured.
169+
111170
### A data property is qualified by its declaring container (#13713)
112171

113172
Option D of the same ruling, and the half that *does* move rows. It landed once the spec

‎scripts/docs-audit/affected-docs.mjs‎

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
257257
'#11178: WHY a row is unreachable, and the two causes that printed as one': 28,
258258
'`causes` GETS THE SAME TREATMENT, AT BOTH ENDS (#11867)': 16,
259259
'the ROUTE SOURCE concept: two kinds, and the runtime-registration guard (#11857)': 24,
260-
'ROUTE-ANCHOR PRECISION, IN BOTH DIRECTIONS (#16696)': 16,
260+
'ROUTE-ANCHOR PRECISION, IN BOTH DIRECTIONS (#16696)': 20,
261261
});
262262

263263
// DELETING an entry silences that battery's floor exactly as effectively as
@@ -1593,20 +1593,34 @@ function routeTailOf(literal) {
15931593
* that writes `/api/v1/cloud/environments/:id` is documenting the control-plane route
15941594
* it names, not the data-plane one this anchor came from. Either spelling of the
15951595
* right name still counts — `:environmentId` and `{environmentId}` are one parameter.
1596-
* 2. A CONCRETE EXAMPLE VALUE is admitted only where a STATIC segment still follows in
1597-
* the tail. That is the precondition above, made mechanical: a static segment to the
1598-
* right is what bounds the match, and a parameter with none is unbounded — filling it
1599-
* with `[A-Za-z0-9_%-]+` degrades the whole pattern to a prefix test on everything
1600-
* under `/environments/`, which is how `env_42` inside `/pub/v1/…/artifact` got in.
1601-
* `/:type/:name/history` keeps both concrete values, because `history` bounds them.
1602-
*
1603-
* ⛔ WHAT WAS MEASURED AND REJECTED, so nobody re-derives it: requiring the match to sit
1604-
* at the END of the documented path (the way the same tail is matched against a ledger
1605-
* row, `route.endsWith(tail)`). It kills both bad hits — and also kills
1606-
* `api/environment-routing.mdx`, the single most on-target page in that run, whose every
1607-
* occurrence is `/api/v1/environments/:environmentId/...` with a segment after it, plus
1596+
* 2. A CONCRETE EXAMPLE VALUE filling the tail's LAST segment must END the documented
1597+
* path. Every earlier parameter is bounded by the rest of the pattern — the segments
1598+
* to its right still have to be there — but the last one has nothing behind it, so a
1599+
* value there degrades the whole pattern to a prefix test: `/environments/<anything>`,
1600+
* which is how `env_42` inside `/pub/v1/…/artifact` got in. A page writing
1601+
* `GET /api/v1/data/accounts` still matches `/data/:object`, because the path ends
1602+
* where the anchor does; a page writing `GET /api/v1/data/account/123` no longer
1603+
* does, because it is documenting `/data/:object/:id` — a different route, and the
1604+
* one that lists it when the diff touches it.
1605+
*
1606+
* MEASURED, on `c7182b80c`, over the 228 distinct route tails declared in this repo's 28
1607+
* route-ledger files against all 195 hand-written docs (the denominator is
1608+
* `handwritten-docs.json`, the same one the tool's recall figures use): tail×page rows
1609+
* 571 → 524, −8.2%, with ZERO tails going from matching some page to matching none. Arm 1
1610+
* accounts for 5 of the 47 dropped rows and arm 2 for 42, and the 42 are one shape —
1611+
* a page documenting a LONGER route than the anchor, or not a route at all:
1612+
* `/packages/:id` matched eleven pages on the monorepo source paths `packages/core`,
1613+
* `packages/spec`, `packages/plugins` …, and `/meta/:type` matched
1614+
* `api/environment-routing.mdx` on the prose "data/meta/AI/automation".
1615+
*
1616+
* ⛔ WHAT WAS MEASURED AND REJECTED, so nobody re-derives it: requiring EVERY match to sit
1617+
* at the end of the documented path (the way the same tail is matched against a ledger
1618+
* row, `route.endsWith(tail)`). That reads −36.3% and it kills `api/environment-routing.mdx`,
1619+
* the single most on-target page in the #16694 run, whose every occurrence is
1620+
* `/api/v1/environments/:environmentId/...` with a segment after it — plus
16081621
* `publish-and-preview.mdx`, `single-project-mode.mdx` and `http-protocol.mdx`. A route
1609-
* prefix written with its own parameter named IS a page documenting that route.
1622+
* PREFIX written with the route's own parameter named IS a page documenting that route;
1623+
* that is why arm 2 is scoped to a concrete VALUE in the LAST position and nothing else.
16101624
*/
16111625
function routePatternFor(tail) {
16121626
const isParam = (s) => s.startsWith(':') || (s.startsWith('{') && s.endsWith('}'));
@@ -1618,9 +1632,10 @@ function routePatternFor(tail) {
16181632
if (!isParam(s)) return quote(s);
16191633
const name = quote(paramNameOf(s));
16201634
const named = `:${name}|\\{${name}\\}`;
1621-
// Arm 2: is anything static left to the RIGHT of this segment to bound a value?
1622-
const bounded = segs.slice(i + 1).some((rest) => !isParam(rest));
1623-
return bounded ? `(?:${named}|[A-Za-z0-9_%-]+)` : `(?:${named})`;
1635+
// Arm 2: only the LAST segment has nothing behind it in the pattern to bound a
1636+
// concrete value, so only there does the documented path have to end.
1637+
const value = i === segs.length - 1 ? '[A-Za-z0-9_%-]+(?![\\w/-])' : '[A-Za-z0-9_%-]+';
1638+
return `(?:${named}|${value})`;
16241639
})
16251640
.join('/');
16261641
return new RegExp(`/${body}(?![\\w-])`);
@@ -5842,7 +5857,15 @@ function selfTest() {
58425857
['/environments/:environmentId', 'artifact route (`/pub/v1/environments/:id/artifact[?commit=<id>]`) serves', false,
58435858
'a DIFFERENT parameter name is a different route — #16696 finding 1, hit A'],
58445859
['/environments/:environmentId', "path: 'https://cloud.example.com/pub/v1/environments/env_42/artifact?commit=cmt_1a2b',", false,
5845-
'a concrete value cannot fill an UNBOUNDED trailing parameter — #16696 finding 1, hit B'],
5860+
'a concrete value in the LAST segment must end the documented path — #16696 finding 1, hit B'],
5861+
['/data/:object', 'Create a record with `POST /api/v1/data/accounts` and read it back.', true,
5862+
'POSITIVE CONTROL for arm 2: a concrete value that DOES end the path still matches'],
5863+
['/data/:object', 'Incoming API request: GET /api/v1/data/account/123', false,
5864+
'the same page text one segment longer is documenting /data/:object/:id, not this route'],
5865+
['/data/:object/:id', 'Incoming API request: GET /api/v1/data/account/123', true,
5866+
'…and THAT tail is the one that lists it — the row moves, the page is not lost'],
5867+
['/packages/:id', 'see [`packages/core`](https://github.com/o/r/blob/main/packages/core/src/x.ts)', false,
5868+
'a monorepo source path is not the wire route /api/v1/packages/:id'],
58465869
['/environments/:environmentId', 'Route REST, metadata, automation, AI, and package calls through /api/v1/environments/:environmentId/....', true,
58475870
'POSITIVE CONTROL: the page that DOES name it stays listed (api/environment-routing.mdx:3)'],
58485871
['/environments/:environmentId', '| `auto` | Registers both unscoped `/api/v1/...` and scoped `/api/v1/environments/:environmentId/...` routes. |', true,

0 commit comments

Comments
 (0)