Skip to content

Commit 8a56ad5

Browse files
claude[bot]claude
andauthored
fix(docs-audit): read the route bridge's handler window off code, not prose (#9502)
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja Co-authored-by: Claude <noreply@anthropic.com>
1 parent 502a028 commit 8a56ad5

1 file changed

Lines changed: 117 additions & 4 deletions

File tree

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

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@
144144
import { execSync } from 'node:child_process';
145145
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
146146
import { join, relative } from 'node:path';
147+
// The one answer to "is this span a comment, or code?" (#9367). Dependency-free and
148+
// side-effect-free on import, so the no-install contract this script runs under holds.
149+
import { maskComments } from '../js-comment-mask.mjs';
147150

148151
const repoRoot = execSync('git rev-parse --show-toplevel').toString().trim();
149152
const args = process.argv.slice(2);
@@ -944,9 +947,40 @@ function symbolAnchorsFromSource(text, changed) {
944947
* mentions. This is the mechanical half of the SDK bridge: a changed protocol method
945948
* appears in the handler of the route it serves, which the ledger then binds to a client
946949
* method the docs actually name.
950+
*
951+
* ⛔ READ OFF CODE, NEVER OFF PROSE (#9432). Everything this function claims is a claim
952+
* about what a handler DOES: the premise the bridge tests is "this symbol IS this route's
953+
* implementation, so the handler that implements the route mentions it". A name that a
954+
* handler mentions only in an English sentence satisfies a bare token scan without
955+
* satisfying that premise — and an implementation comment naming a neighbouring symbol is
956+
* ordinary, careful writing, not a smell. Measured on `40d5b2d4c` (#9405, a
957+
* `metadata-protocol` batch-publish change): BOTH route anchors the run produced came from
958+
* prose and nothing else — `promoteDraftForPublish` from two comment lines in the publish
959+
* handler, and `publishPackageDrafts` from two more that put the state-machine route (and
960+
* `meta.getLegalNextStates` through the ledger) on the advisory for a diff that never
961+
* touched a state machine. Comments contribute 4329 of the 7649 identifier slots this scan
962+
* sees on today's tree, so the surface is more than half prose.
963+
*
964+
* The mask covers the `path:` scan too, not just the identifier scan, and that is the same
965+
* rule rather than a second one: a route registration written inside a JSDoc `@example` is
966+
* an illustration, not a registration (`route-manager.ts` has exactly one, minting a
967+
* phantom `/api/users/:id`), and a `path:` line inside a comment must not truncate the
968+
* previous handler's window either. `maskComments` is the projection — this scan reasons in
969+
* LINE POSITIONS (a window is 150 lines past a site) and in what precedes `path:` on a
970+
* line, and blanking keeps both while deleting would move columns. The #9367 lazy-matcher
971+
* hazard does not apply: the one lazy quantifier here is `(.*?)` inside a per-LINE regex,
972+
* bounded by a line, never dragged across the file. Cost measured: 115 ms to mask the 19
973+
* registrar files (888 KB), taking a whole run from 387 ms to 480 ms.
974+
*
975+
* Measured cost in RECALL, which is the question the card said to answer before assuming:
976+
* ZERO, over the last 60 commits touching `packages/`. Three runs changed at all, and every
977+
* row that moved is a wrong row — including the one that looked most like a real loss,
978+
* `api/client-sdk.mdx` on `40d5b2d4c`: the hunks that made `promoteDraftForPublish` an
979+
* anchor there are DOC-COMMENT-only, so `publishItem`'s behaviour never moved, and the page
980+
* names none of the three symbols that did.
947981
*/
948982
function parseRegistrarSource(text) {
949-
const lines = text.split('\n');
983+
const lines = maskComments(text).split('\n');
950984
const sites = [];
951985
for (let i = 0; i < lines.length; i++) {
952986
const m = lines[i].match(/(?:^|[\s{,(])path\s*:\s*([`'"])(.*?)\1/);
@@ -1404,6 +1438,84 @@ function selfTest() {
14041438
check('symbolAnchorsFromSource.bridgeable', label, `${name} @ line ${line}`, want, bridgeableAt(src, line).has(name));
14051439
}
14061440

1441+
// ---- the handler-window scan reads CODE, never PROSE (#9432) ---------------
1442+
// #9294 removed CONTAINER names from the bridge, which closed the specimen it measured.
1443+
// The residue is one layer down and is what this block pins: a LEAF symbol named only in
1444+
// an English comment inside a handler window bridged just the same.
1445+
//
1446+
// MEASURED FAILURE (40d5b2d4c, #9405 — a `metadata-protocol` batch-publish change): BOTH
1447+
// route anchors that run produced were prose and nothing else.
1448+
//
1449+
// promoteDraftForPublish → /:type/:name/publish rest-server.ts:5324,5376 (comments)
1450+
// publishPackageDrafts → /:name/state/:field rest-server.ts:5694,5722 (comments)
1451+
//
1452+
// The second one carried `content/docs/protocol/objectql/state-machine.mdx` onto the
1453+
// advisory (and `meta.getLegalNextStates` through the ledger) for a diff that went
1454+
// nowhere near a state machine. Neither name is called by the handler that named it;
1455+
// both sentences are ordinary implementation commentary about a neighbouring door.
1456+
//
1457+
// ⚠️ PINNED IN BOTH DIRECTIONS, for the reason #9294's block states: an exclusion-only
1458+
// test passes just as happily on a fix that breaks the bridge outright, which would trade
1459+
// a precision bug for the recall hole the bridge exists to fill. So the comment-named leaf
1460+
// must NOT bridge, the code-named leaf MUST, and the raw scan must still SEE the prose
1461+
// name — that last one is the counterfactual, and it is what stops this block going green
1462+
// because the fixture drifted into carrying no comment at all.
1463+
const commentaryRegistrar = [
1464+
'export class RestServer {',
1465+
' private registerPublishRoutes() {',
1466+
' this.routeManager.register({',
1467+
" method: 'POST',",
1468+
' path: `${metaPath}/:type/:name/publish`,',
1469+
' handler: async (req, res) => {',
1470+
' // Promotion is authoring. `promoteDraftForPublish` flips the',
1471+
" // `sys_metadata` row state: 'draft' → 'active', and ADR-0027",
1472+
' // (E)(5) defines sealing a publish as exactly that.',
1473+
" const base = 'https://acme.test//v1'; // a URL is not a comment opener",
1474+
' return this.publishMetaItem(base, req.params); // seals the draft',
1475+
' },',
1476+
' });',
1477+
' }',
1478+
'',
1479+
' /**',
1480+
' * @example',
1481+
' * manager.register({',
1482+
" * method: 'GET',",
1483+
" * path: '/api/users/:id',",
1484+
' * handler: getUserHandler,',
1485+
' * });',
1486+
' */',
1487+
' private describeRegistration() {}',
1488+
'}',
1489+
].join('\n');
1490+
const commentary = parseRegistrarSource(commentaryRegistrar);
1491+
const publishIds = commentary.get('/:type/:name/publish');
1492+
check('parseRegistrarSource', 'a leaf the handler actually CALLS is still seen — the bridge is untouched where its premise holds', 'publishMetaItem', true, !!publishIds?.has('publishMetaItem'));
1493+
check('parseRegistrarSource', 'a leaf named only in an English comment is NOT this route\'s implementation', 'promoteDraftForPublish', false, !!publishIds?.has('promoteDraftForPublish'));
1494+
check('parseRegistrarSource', 'a `//` inside a STRING opens no comment — this scan rides the shared scanner, not a private regex', 'base + publishMetaItem', true, !!publishIds?.has('base') && !!publishIds?.has('publishMetaItem'));
1495+
check('parseRegistrarSource', 'a `path:` inside a JSDoc @example is an illustration, not a registration', 'tails', JSON.stringify(['/:type/:name/publish']), JSON.stringify([...commentary.keys()]));
1496+
1497+
// The counterfactual, twice: the PRE-FIX scan, verbatim, over the same fixture. Both
1498+
// halves must still be there to be excluded, or this block proves nothing.
1499+
const rawWindowIds = (src, siteFragment) => {
1500+
const ls = src.split('\n');
1501+
const start = ls.findIndex((l) => l.includes(siteFragment));
1502+
const ids = new Set();
1503+
for (let j = start; j >= 0 && j < Math.min(ls.length, start + REGISTRAR_HANDLER_WINDOW); j++) {
1504+
for (const m of ls[j].matchAll(/[A-Za-z_$][\w$]*/g)) ids.add(m[0]);
1505+
}
1506+
return ids;
1507+
};
1508+
check('parseRegistrarSource', 'counterfactual: the bare-token scan DID read the prose name out of that window', 'promoteDraftForPublish', true, rawWindowIds(commentaryRegistrar, ':type/:name/publish').has('promoteDraftForPublish'));
1509+
check('parseRegistrarSource', 'counterfactual: that @example line DID match the registration-site regex', '/api/users/:id', '/api/users/:id', routeTailOf(commentaryRegistrar.split('\n').find((l) => l.includes('/api/users/:id')).match(/(?:^|[\s{,(])path\s*:\s*([`'"])(.*?)\1/)?.[2] ?? ''));
1510+
1511+
// End to end through the same selection step the bridge runs: a diff confined to the
1512+
// commented-about symbol selects no route, and one on the called symbol still selects its
1513+
// own. The commented-about name is a doc anchor either way — it enters `symbolAnchors`
1514+
// from its OWN declaration, which this hop never touched — so nothing is lost but the
1515+
// false bridge.
1516+
check('bridge', 'a diff touching only the symbol the comment MENTIONS selects no route', 'tails', JSON.stringify([]), JSON.stringify(tailsSelectedBy(new Set(['promoteDraftForPublish']), commentary)));
1517+
check('bridge', 'a diff touching the symbol the handler CALLS still selects the publish route', 'tails', JSON.stringify(['/:type/:name/publish']), JSON.stringify(tailsSelectedBy(new Set(['publishMetaItem']), commentary)));
1518+
14071519
// ---- the CLI command anchor kind (#9230) ----------------------------------
14081520
// The recall class: a CLI-surface change derives `MetaResync` / a lowercase `resync`,
14091521
// and neither reaches the page that documents the command. The phrase does. Both halves
@@ -1762,9 +1874,10 @@ for (const name of [...symbolAnchors].sort()) {
17621874
// TWO KINDS OF SYMBOL ARE DOC ANCHORS BUT NOT BRIDGE SYMBOLS. The bridge's premise is
17631875
// "this name IS some route's implementation, so the handler that implements that route
17641876
// mentions it" — and `parseRegistrarSource` tests that premise by scanning a handler
1765-
// window for the BARE IDENTIFIER. Any name a handler mentions for some OTHER reason
1766-
// satisfies the scan without satisfying the premise, and mints a route (and, through
1767-
// the ledger, an sdk) anchor from a diff that never came near that route.
1877+
// window for the BARE IDENTIFIER (over comment-masked source since #9432, so a name a
1878+
// handler only WRITES ABOUT never satisfies it). Any name a handler mentions for some
1879+
// OTHER reason satisfies the scan without satisfying the premise, and mints a route (and,
1880+
// through the ledger, an sdk) anchor from a diff that never came near that route.
17681881
//
17691882
// 1. A SCREAMING_SNAKE constant is a data table, not a route's implementation: it is
17701883
// referenced by handlers that merely consult it. `ERROR_CODE_LEDGER` names a real

0 commit comments

Comments
 (0)