diff --git a/scripts/audits/14096-tenancy-ledger-namespace-regexp-census.mjs b/scripts/audits/14096-tenancy-ledger-namespace-regexp-census.mjs index 2f4013754f..4e71b194d9 100644 --- a/scripts/audits/14096-tenancy-ledger-namespace-regexp-census.mjs +++ b/scripts/audits/14096-tenancy-ledger-namespace-regexp-census.mjs @@ -94,7 +94,8 @@ const REPO_ROOT = resolve(HERE, '../..'); // --------------------------------------------------------------------------- // §1. Discover every registered platform-namespace object by AST-lite census // of `ObjectSchema.create(` calls — the same method the ledger's own -// header (`platform-object-tenancy.ts:32-37`) documents using. +// header (`packages/objectql/src/tenancy/platform-object-tenancy.ts`, its +// opening comment — lines 32-37 as measured) documents using. // --------------------------------------------------------------------------- /** Blank out `//` and `/* *\/` comments and string contents (chars only, diff --git a/scripts/audits/14423-unbound-declarations-before-count.mjs b/scripts/audits/14423-unbound-declarations-before-count.mjs index 28873a123c..1e9011ded3 100644 --- a/scripts/audits/14423-unbound-declarations-before-count.mjs +++ b/scripts/audits/14423-unbound-declarations-before-count.mjs @@ -59,7 +59,8 @@ const loadStandaloneActions = async () => [...namelessStandaloneOrphans, namedSt const { warnings, logger } = silentLogger(); -// Exactly as ObjectQLPlugin.runGovernanceInventory calls it (plugin.ts:2516), +// Exactly as ObjectQLPlugin.runGovernanceInventory calls it +// (`packages/objectql/src/plugin.ts#runGovernanceInventory`), // with an empty registered-handler set and no object-embedded actions/registry // rung, so the ENTIRE declaration set comes from `loadStandaloneActions`. await runActionGovernanceInventory({ diff --git a/scripts/check-adr-links.mjs b/scripts/check-adr-links.mjs index 09e41584ce..97d93e26ed 100644 --- a/scripts/check-adr-links.mjs +++ b/scripts/check-adr-links.mjs @@ -11,8 +11,9 @@ // checked by nothing, and records link each other heavily: most carry a // `**Builds on**:` line with 3-10 relative links in it. // -// That is not a theoretical hole. `0057-erp-authorization-core-business-units- -// and-scope-depth.md:5` pointed at `./0010-metadata-protection.md`, a file that +// That is not a theoretical hole. +// `docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md` +// (line 5 as measured) pointed at `./0010-metadata-protection.md`, a file that // has never existed (the record is `0010-metadata-protection-model.md`). It sat // there long enough that a triage comment inherited the wrong attribution and // repeated it. PD #13 sends the next author to "go read that decision"; a dead diff --git a/scripts/check-changeset-no-major.mjs b/scripts/check-changeset-no-major.mjs index b9c431b736..7f5799d91c 100644 --- a/scripts/check-changeset-no-major.mjs +++ b/scripts/check-changeset-no-major.mjs @@ -687,7 +687,8 @@ export function render(result) { // nothing final ships until `changeset pre exit`. Surface the introduced // majors for the RC curator, but do not fail. The guard re-arms once // pre-mode exits: `changeset pre exit` rewrites pre.json's mode to `"exit"` - // (@changesets/pre@2.0.2, changesets-pre.cjs.js:117), which is not `pre`. + // (@changesets/pre@2.0.2, its `changesets-pre.cjs.js` bundle, line 117 as + // pinned — a dependency file, outside this tree), which is not `pre`. case 'exempt': stdout.push( `✓ Changesets is in pre-release mode (tag: ${result.tag}) — ` + diff --git a/scripts/check-cli-test-child-env.mjs b/scripts/check-cli-test-child-env.mjs index d290b48b5d..22b37d1511 100644 --- a/scripts/check-cli-test-child-env.mjs +++ b/scripts/check-cli-test-child-env.mjs @@ -185,7 +185,8 @@ * suite stays green, and the comment claiming the built entry stays plausible. * * `@oclif/core@4.13.3` skips its TypeScript path lookup only when `isProd()`, - * which `lib/util/util.js:66` defines as a NEGATED membership test -- a leading + * which its `lib/util/util.js` (line 66 as pinned -- a dependency file, outside + * this tree) defines as a NEGATED membership test -- a leading * logical-NOT over `['development', 'test'].includes(process.env.NODE_ENV ?? '')`. * Measured directly against `Config.load()` on `packages/cli`, with * `packages/cli/dist` present, reading back the root plugin's `commandsDir`: @@ -1056,7 +1057,8 @@ const BUILT_ENTRYPOINT = 'bin/run.js'; * The `NODE_ENV` values that send `@oclif/core`'s command lookup to `src/`. * * `@oclif/core@4.13.3` skips its TypeScript path lookup only when `isProd()`, - * which `lib/util/util.js:66` defines as a NEGATED membership test: + * which its `lib/util/util.js` (line 66 as pinned -- a dependency file, outside + * this tree) defines as a NEGATED membership test: * `!['development', 'test'].includes(process.env.NODE_ENV ?? '')`. So these two * values -- and only these two -- turn the reroute ON. `production` and unset * both leave it off, which is why the rule below refuses a SET of values rather diff --git a/scripts/check-console-injection.mjs b/scripts/check-console-injection.mjs index f4c1f4bb44..6b36a221a2 100644 --- a/scripts/check-console-injection.mjs +++ b/scripts/check-console-injection.mjs @@ -99,7 +99,8 @@ * esbuild re-emits `"…definition's…"`. Same characters, different bytes, so a * literal substring search over source text misses it. * - 19/44 constant-folded concatenation. Source splits a long description as - * `'… declares ' + '`_packageId`.'` (api/protocol.zod.ts:341-342 is one); + * `'… declares ' + '`_packageId`.'` (`packages/spec/src/api/protocol.zod.ts` + * is one, lines 341-342 as measured); * the bundler folds it to one literal that exists in no source file. * * A missed probe here reads as "not expired" — a SILENT PASS, the same failure diff --git a/scripts/check-dispatcher-error-vocabulary.mjs b/scripts/check-dispatcher-error-vocabulary.mjs index 026f10fc93..ba3f271ec8 100644 --- a/scripts/check-dispatcher-error-vocabulary.mjs +++ b/scripts/check-dispatcher-error-vocabulary.mjs @@ -925,7 +925,7 @@ export const OBJECT_LITERAL_CODE_HELPER_BLINDNESS = Object.freeze({ * for the D6 field-addressed validation catalog in `domains/automation.ts`, and * [#14626]'s two newly reached positions in that same file owe NO further rows: * they are the identical catalog ternary with the identical values, so they - * derive the site keys the `:1125` instance already derives. + * derive the site keys the instance at line 1125 of that file already derives. * * ## [#14742] What moved this census, separated from what merely drifted * @@ -1196,7 +1196,8 @@ export const INLINE_LITERAL_EXPRESSION_CENSUS = Object.freeze({ }), // [#14626] The two the card predicted, recovered by the template-literal // mode. Same file, same genre, same two values — so they derive the site - // keys `:1125` already derives and owe NO further verdict rows. That is the + // keys the instance at line 1125 of that file already derives, and owe NO + // further verdict rows. That is the // measurement, not a convenience: `rowsOwed: 0` here is what says the // recovered positions surfaced nothing unregistered. Object.freeze({ diff --git a/scripts/check-doc-anchors.mjs b/scripts/check-doc-anchors.mjs index 8ccfd1c4dc..d52ae3d5e2 100644 --- a/scripts/check-doc-anchors.mjs +++ b/scripts/check-doc-anchors.mjs @@ -16,7 +16,8 @@ // heading lives in one file, the link in another, and until this script nothing // in CI related them: a rename that updates the heading and not the inbound // `#anchor` shipped green. #7465 exists because exactly that pairing had already -// drifted once — `cli.mdx:458` pointed at a heading whose text had been correct +// drifted once — `content/docs/deployment/cli.mdx` (line 458 as measured then) +// pointed at a heading whose text had been correct // in 2024 — and PR #7483 could only catch its own rename by hand, with a slug // computed in a REPL and a repo-wide grep. Neither runs on anyone else's PR. // diff --git a/scripts/check-durability-degradation-log-level.mjs b/scripts/check-durability-degradation-log-level.mjs index 38738c52d4..87a4b61783 100644 --- a/scripts/check-durability-degradation-log-level.mjs +++ b/scripts/check-durability-degradation-log-level.mjs @@ -768,8 +768,10 @@ const FAILURE_PROPAGATION_SITES = new Map([ // that fix — `67 read seam(s) … (7 … discriminated) (1 pass … through) (1 // baselined)` on both trees, measured by ablating the fix back to its // pre-#8895 `catch { continue }` and re-running. And the seam is not merely -// unseen: plant a `return []` in that same catch and the gate names it -// (`engine.ts:10084 (in cascadeDeleteRelations())`, red). So it sits IN the +// unseen: plant a `return []` in that same catch and the gate names it — red, +// at `packages/objectql/src/engine.ts#cascadeDeleteRelations` (that catch was +// on line 10084 when this was measured, a dated reading and not a pointer). +// So it sits IN the // census throughout, reported clean while it was broken and reported clean now // that it is fixed — what decides visibility is the SHAPE of the exit, never // whether the seam is correct. A real fail-open on an integrity guard was @@ -1176,24 +1178,28 @@ const FAILURE_PROPAGATION_SITES = new Map([ // THE DISCRIMINATING RUN. Raising `MAX_READ_WRAPPER_DEPTH` from 2 to 6 while // leaving `walkSameTickInclusive` in place admits 5 of the same 8 (70 seams: +8 // / -2, where the 2 are the SAME try lines re-attributed to a different -// first-matching callee, engine.ts:9407 and :10572). Saturation checked at +// first-matching callee, both in `packages/objectql/src/engine.ts`, on lines +// 9407 and 10572 as measured). Saturation checked at // depth 50: 70 and 75, i.e. unchanged. So for those 5 the miss is the DEPTH // BOUND, not the callback boundary — `walkAll` merely masks the bound by // descending lexically through nested DECLARATIONS instead of counting call // hops, which reaches the read at depth 1 no matter how many awaits are between. // -// THE 8 DELTA SEAMS, each read at its call site: -// -// | # | seam (try line → wrapper) | why today misses it | invoked now? | -// |--:|----------------------------------------------------------------|---------------------|--------------| -// | 1 | metadata-protocol protocol.ts:10243 getMetaItemCached→getMetaItem | depth bound | yes — real | -// | 2 | metadata-protocol protocol.ts:13559 saveMetaItem→getMetaItem | depth bound | yes — real | -// | 3 | metadata-protocol protocol.ts:14535 migrateStoredMetadata→saveMetaItem | depth bound | yes — real | -// | 4 | metadata-protocol protocol.ts:17213 duplicatePackage→saveMetaItem | depth bound | yes — real | -// | 5 | metadata-protocol sys-metadata-repository.ts:883 promoteDraft→dropPromotedDraftRow | CALLBACK | yes — real | -// | 6 | metadata-protocol sys-metadata-repository.ts:1353 close→terminate | CALLBACK | NO — FAKE | -// | 7 | objectql engine.ts:9237 insert→applyAutonumbers | CALLBACK | yes — real | -// | 8 | objectql lifecycle-service.ts:625 sweep→reapObject | depth bound | yes — real | +// THE 8 DELTA SEAMS, each read at its call site. ⚠️ The FILE is named as an +// anchor and the try line sits beside it as DATA: each number is a reading +// taken on the date above, not a pointer, and a `file:NNN` pointer written +// here rots silently — the whole finding of #15765. +// +// | # | file | try line | seam (wrapper) | why today misses it | invoked now? | +// |--:|-------------------------------------------------------|---------:|------------------------------------|---------------------|--------------| +// | 1 | `packages/metadata-protocol/src/protocol.ts` | 10243 | getMetaItemCached→getMetaItem | depth bound | yes — real | +// | 2 | `packages/metadata-protocol/src/protocol.ts` | 13559 | saveMetaItem→getMetaItem | depth bound | yes — real | +// | 3 | `packages/metadata-protocol/src/protocol.ts` | 14535 | migrateStoredMetadata→saveMetaItem | depth bound | yes — real | +// | 4 | `packages/metadata-protocol/src/protocol.ts` | 17213 | duplicatePackage→saveMetaItem | depth bound | yes — real | +// | 5 | `packages/metadata-protocol/src/sys-metadata-repository.ts` | 883 | promoteDraft→dropPromotedDraftRow | CALLBACK | yes — real | +// | 6 | `packages/metadata-protocol/src/sys-metadata-repository.ts` | 1353 | close→terminate | CALLBACK | NO — FAKE | +// | 7 | `packages/objectql/src/engine.ts` | 9237 | insert→applyAutonumbers | CALLBACK | yes — real | +// | 8 | `packages/objectql/src/lifecycle/lifecycle-service.ts` | 625 | sweep→reapObject | depth bound | yes — real | // // All 8 were decidable from the call site; none needed provenance. Seams 1-5, // 7 and 8 are genuine members the census does not count: every hop is an @@ -1205,7 +1211,8 @@ const FAILURE_PROPAGATION_SITES = new Map([ // // ⚠️ SEAM 6 IS A FAKE SEAM, AND IT IS THE REASON `walkAll` IS NOT THE FIX. // `close()`'s try calls `w.terminate()`. `terminate` resolves BY NAME to the -// local const arrow at sys-metadata-repository.ts:1246 — a synchronous, void, +// local const arrow in `packages/metadata-protocol/src/sys-metadata-repository.ts` +// (line 1246 as measured) — a synchronous, void, // in-memory routine whose only call is `self.watchers.delete(subscription)` on // `private readonly watchers = new Set<...>()`. `calleeName` reads that as // `delete`, and the wrapper recursion resolves `delete` to THIS FILE'S @@ -1254,8 +1261,10 @@ const FAILURE_PROPAGATION_SITES = new Map([ // | probe, with the `delete` wrapper hop refused | 72 | // // ⚠️ THE ABLATION IS THE CONTROL, NOT THE FIX. Refusing the `delete` hop -// outright drops TWO seams — sys-metadata-repository.ts:1353 `close`-> -// `terminate` (the FAKE) and :883 `promoteDraft`->`dropPromotedDraftRow` (a +// outright drops TWO seams, both in +// `packages/metadata-protocol/src/sys-metadata-repository.ts` — `close`-> +// `terminate` (the FAKE, line 1353 as measured) and +// `promoteDraft`->`dropPromotedDraftRow` (line 883, a // REAL `await this.delete(ref, …)`). In a summary that is indistinguishable // from the correct outcome, which is why the pair is pinned in the self-test. // @@ -1338,7 +1347,8 @@ const FAILURE_PROPAGATION_SITES = new Map([ // - THE ADMITTING STEP IS +6 / -0, not "+8 / -2". The 2 in the filing were // two `try` lines re-attributed to a different first-matching callee, never // seams leaving the population. That re-attribution still happens here — -// `engine.ts:9741` and `:10906` move from `resolveMasterDetailParent(s)` to +// two try lines in `packages/objectql/src/engine.ts` (9741 and 10906 as +// measured) move from `resolveMasterDetailParent(s)` to // `mediaValueShapeStrictFor` — but at depth 4, one level ABOVE the level // that admits anything, and it moves no count. The admitting step is clean. // @@ -1347,14 +1357,16 @@ const FAILURE_PROPAGATION_SITES = new Map([ // rule's vocabulary names. Chains as the recognizer actually resolves them — // two of them are NOT the tails the filing predicted: // -// | # | seam (try line -> first wrapper) | chain to the read | -// |--:|-----------------------------------------------------|-------------------| -// | 1 | metadata-protocol protocol.ts:10497 getMetaItemCached | getMetaItem -> findDraft -> lookup -> engine.findOne | -// | 2 | metadata-protocol protocol.ts:13874 saveMetaItem | getMetaItem -> findDraft -> lookup -> engine.findOne | -// | 3 | metadata-protocol protocol.ts:14850 migrateStoredMetadata | saveMetaItem -> refuseUnmintableMetaType -> metaTypeNamespaceExists -> engine.findOne | -// | 4 | metadata-protocol protocol.ts:16502 publishPackageDrafts | promoteDraftForPublish -> lockWriteRefusal -> getEffectiveLock -> engine.findOne | -// | 5 | metadata-protocol protocol.ts:17565 duplicatePackage | saveMetaItem -> refuseUnmintableMetaType -> metaTypeNamespaceExists -> engine.findOne | -// | 6 | objectql lifecycle-service.ts:664 sweep | reapObject -> archiveObject -> archivePass -> hot.find | +// (the FILE is the anchor, the try line beside it is a dated reading) +// +// | # | file | try line | first wrapper | chain to the read | +// |--:|-------------------------------------------------------|---------:|-----------------------|-------------------| +// | 1 | `packages/metadata-protocol/src/protocol.ts` | 10497 | getMetaItemCached | getMetaItem -> findDraft -> lookup -> engine.findOne | +// | 2 | `packages/metadata-protocol/src/protocol.ts` | 13874 | saveMetaItem | getMetaItem -> findDraft -> lookup -> engine.findOne | +// | 3 | `packages/metadata-protocol/src/protocol.ts` | 14850 | migrateStoredMetadata | saveMetaItem -> refuseUnmintableMetaType -> metaTypeNamespaceExists -> engine.findOne | +// | 4 | `packages/metadata-protocol/src/protocol.ts` | 16502 | publishPackageDrafts | promoteDraftForPublish -> lockWriteRefusal -> getEffectiveLock -> engine.findOne | +// | 5 | `packages/metadata-protocol/src/protocol.ts` | 17565 | duplicatePackage | saveMetaItem -> refuseUnmintableMetaType -> metaTypeNamespaceExists -> engine.findOne | +// | 6 | `packages/objectql/src/lifecycle/lifecycle-service.ts` | 664 | sweep | reapObject -> archiveObject -> archivePass -> hot.find | // // (3 and 5 reach a read through `saveMetaItem`'s OWN precondition check, not // through the `getMetaItem` tail the filing assigned them. Same verdict, and @@ -1378,8 +1390,9 @@ const FAILURE_PROPAGATION_SITES = new Map([ // walkAll @ depth 2 admits 7 (was 8 — #12358's guard deleted the fake seam) // depth 3 admits 6 // BOTH 5 <- the claim, reproduced exactly -// depth only 1 protocol.ts:16502 publishPackageDrafts -// walkAll only 2 sys-metadata-repository.ts:883, engine.ts:9571 +// depth only 1 `packages/metadata-protocol/src/protocol.ts`, line 16502 +// walkAll only 2 `packages/metadata-protocol/src/sys-metadata-repository.ts`, line 883 +// and `packages/objectql/src/engine.ts`, line 9571 // // So the NUMBER 5 survives and its DENOMINATOR does not: read it as "5 of 7", // the eighth having been the fake seam #12358 removed. And one correction the diff --git a/scripts/check-engine-double-contract.mjs b/scripts/check-engine-double-contract.mjs index e1ea8c1dc2..d5466a15d8 100644 --- a/scripts/check-engine-double-contract.mjs +++ b/scripts/check-engine-double-contract.mjs @@ -101,8 +101,9 @@ // NO on #7620 for these doubles specifically. So it asks each independent // double a behavioural question instead of handing it an implementation. // - a scoped repository that declares NO repository-only member. Measured on -// the corpus this landed against: `packages/runtime/src/action-body-identity -// .test.ts:71` is a real scoped facade (`createContext().object(name)`) +// the corpus this landed against: +// `packages/runtime/src/action-body-identity.test.ts` (line 71 as measured) +// is a real scoped facade (`createContext().object(name)`) // spelling only `find`/`count`/`insert`/`update`/`delete`, and it stays in // the ledger. Seeing it would mean reading its parameter NAMES, and `o` is // ambiguous in exactly this repo: `o: string` is the object name in twelve diff --git a/scripts/check-live-db-isolation.mjs b/scripts/check-live-db-isolation.mjs index b15e16b175..a9a5b93e83 100644 --- a/scripts/check-live-db-isolation.mjs +++ b/scripts/check-live-db-isolation.mjs @@ -214,8 +214,8 @@ const SOLE_INTERPOLATION = /^\$\{\s*([A-Za-z_$][\w$]*)\s*\}$/; * Source with block and line comments BLANKED -- replaced space-for-space * rather than deleted, so prose is never a hit and the line numbers this gate * reports are still the line numbers in the real file. Deleting the comments - * was the first spelling and it reported `…live-mysql.test.ts:45` for a - * statement that lives on line 81; a gate that points at the wrong line is a + * was the first spelling and it reported the live-mysql isolation test at line + * 45 for a statement that lives on line 81; a gate that points at the wrong line is a * gate the next author stops believing. */ export function codeOf(source) { diff --git a/scripts/check-overlay-whitelist-table.mjs b/scripts/check-overlay-whitelist-table.mjs index 85b6d7890e..ac2d332e24 100644 --- a/scripts/check-overlay-whitelist-table.mjs +++ b/scripts/check-overlay-whitelist-table.mjs @@ -173,7 +173,8 @@ * lines across 13 files. Of those, ~5 are genuine " is " assertions; * the rest are the schema-field reference row, a code sample using an invented * type, and sentences ABOUT the flag rather than about any type - * (`metadata-lifecycle.mdx:121` itself is one). Covering them mechanically + * (`content/docs/concepts/metadata-lifecycle.mdx` itself is one, line 121 as + * measured). Covering them mechanically * means a co-occurrence regex over English plus a per-site allowlist of ~17 * exceptions to catch ~5 assertions -- a worse gate than none, and the kind * whose baseline gets bulk-updated to green. They are left out on purpose, and diff --git a/scripts/check-platform-checklist.mjs b/scripts/check-platform-checklist.mjs index 9065f9fed4..71ad13dedc 100644 --- a/scripts/check-platform-checklist.mjs +++ b/scripts/check-platform-checklist.mjs @@ -1282,7 +1282,8 @@ export const NEIGHBOURING_MAP: Readonly> = Object.freeze( // half, and the cheap half is the one that removes a false signal today. // // A citation is a colon-then-digits reached one of two ways, because the ledger -// spelled it both ways: anchored to a source filename (`manifest.zod.ts:158`), +// spelled it both ways: anchored to a source filename (`.ts:158`, the +// placeholder spelling `scripts/symbol-anchors.mjs#ANCHOR_GRAMMAR` uses), // or BARE, continuing a filename named earlier in the same sentence // (`ManifestSchema id :140 and version :202`). The bare half is why a plain // "filename followed by a colon" rule is not enough — and the bare half is the diff --git a/scripts/check-runtime-services-index.mjs b/scripts/check-runtime-services-index.mjs index 8845c0d3a7..69640bc252 100644 --- a/scripts/check-runtime-services-index.mjs +++ b/scripts/check-runtime-services-index.mjs @@ -28,7 +28,8 @@ // time, and nothing reads them: `check:docs-audit-scope` derives WHICH pages the // docs-accuracy audit covers, never whether an index enumerates them. #9604 // measured the result -- `services.sms` had a page, a `meta.json` entry, a -// registered slot (`sms-plugin.ts:181`) and a canonical-source row, and was +// registered slot (`packages/services/service-sms/src/sms-plugin.ts`, line 181 +// as measured) and a canonical-source row, and was // still missing from BOTH index lists. It shipped that way and every gate was // green. #9588 was the same page drifting on a different line. // @@ -124,7 +125,8 @@ // This gate deliberately did NOT read that list until now, and the reason is // worth keeping: it was a superset by exactly one row on purpose-of-record. // `Security: packages/spec/src/contracts/security-service.ts` named a real, -// registered slot (`security-plugin.ts:1157`) for a service this chapter has no +// registered slot (`packages/plugins/plugin-security/src/security-plugin.ts`, +// line 1157 as measured) for a service this chapter has no // page for, while `services.security` was documented NOWHERE under // `content/docs/`. Whether that row should become a page, move to another // chapter, or be dropped was a product-surface question, and encoding ANY of the @@ -256,7 +258,8 @@ const IS_TEST_PATH = (rel) => // MULTILINE-AWARE ON PURPOSE. `\s*` spans newlines, so a registration whose key // sits on the line after the `(` is still found -- `plugin-audit` really is -// written that way (`audit-plugin.ts:117`), and a `^.*registerService\('x'` style +// written that way (`packages/plugins/plugin-audit/src/audit-plugin.ts`, line +// 117 as measured), and a `^.*registerService\('x'` style // line-at-a-time grep reports a confident ZERO for it. An absence measured with // the wrong tool is the failure mode this whole check exists to rule out, so the // self-test pins the split-line form explicitly. diff --git a/scripts/check-scripts-symbol-anchors.mjs b/scripts/check-scripts-symbol-anchors.mjs index 23751124f5..19c4914e44 100644 --- a/scripts/check-scripts-symbol-anchors.mjs +++ b/scripts/check-scripts-symbol-anchors.mjs @@ -7,6 +7,7 @@ * * node scripts/check-scripts-symbol-anchors.mjs * node scripts/check-scripts-symbol-anchors.mjs --list + * node scripts/check-scripts-symbol-anchors.mjs --list-unresolvable * node scripts/check-scripts-symbol-anchors.mjs --self-test * * ⚠️ THE MECHANISM IS NOT HERE. The grammar, the extractor, the comment-prose @@ -54,15 +55,31 @@ * * ## What this corpus judges, and what it declines to * - * `judgeUntrackedLineAnchors: false`. The 96 citations that name no tracked - * file are 66 bare filenames inside census tables (`engine.ts` at some line -- - * an abbreviation no resolver can bind to one of this tree's several files of - * that name), 22 continuations inheriting no path, 11 directory-qualified - * illustrations or sibling-repo paths, and 1 tilde form. They are a real defect - * class and are recorded as a follow-up, exactly as the 1,056 bare paths under - * `checkBarePaths` were for `docs/adr/**` -- but this gate cannot tell their - * author how to fix them, and a gate whose only remedy is "stop writing that" - * is the permanently-red gate this repo retired. + * `judgeUntrackedLineAnchors: false`. A citation that names no tracked file is + * SEEN, COUNTED and ENUMERATED, and judged by nothing: this gate cannot tell + * its author how to fix it, and a gate whose only remedy is "stop writing that" + * is the permanently-red gate this repo retired. They were a real defect class + * and were carded as a follow-up (#15809), exactly as the 1,056 bare paths + * under `checkBarePaths` were for `docs/adr/**`. + * + * ⭐ THAT FOLLOW-UP IS MOSTLY DONE, AND THE REST IS A LIST, NOT A NUMBER. When + * the corpus was registered it declined 96 citations; #15809 migrated 81 of + * them by the same method PR #15806 used on the tracked-target ones -- the file + * as a file-level anchor, the number kept beside it as data, the placeholder + * spelling `scripts/symbol-anchors.mjs#ANCHOR_GRAMMAR` defines for an + * illustration, and prose naming the repo for a third-party or dependency + * source no in-repo resolver could ever check. ⛔ Not one digit was repaired or + * repointed; every number that was in an anchor is still on its page, as data. + * + * The residual when that landed was 15, and every one of them was a file + * another lane held OPEN at that moment -- ⛔ not one was an ambiguity. That + * count is a DATED reading; `--list-unresolvable` is the live one. ⚠️ It is why the + * flag is still `false`: the fence #15809 was dispatched under is that it flips + * only when the residual is ZERO and a self-test pins the flip, and a residual + * of 15 would make this gate permanently red for the length of somebody else's + * pull request. `--list-unresolvable` prints the residual so the next author + * inherits a worklist rather than a count; the day it prints nothing, the flag + * is a one-line change with a case to pin it. * * `checkBarePaths: false`, for the same reason and on a measurement: judging * every bare path code span in this corpus produces 1,617 findings, nearly all @@ -213,8 +230,32 @@ export function runCheck(root = process.cwd()) { } function list(root = process.cwd()) { - const { findings, counts } = sweepCorpus(CORPUS, root); - console.log(JSON.stringify({ counts, findings, allowances: HELD_FILE_ALLOWANCES }, null, 2)); + const { findings, counts, declined } = sweepCorpus(CORPUS, root); + console.log(JSON.stringify({ counts, findings, declined, allowances: HELD_FILE_ALLOWANCES }, null, 2)); +} + +/** + * The citations this corpus DECLINES to judge, enumerated. + * + * ⚠️ A listing, never a verdict: this exits 0 whatever it prints, exactly as + * `--list` does. `runCheck()` is still the only arm that can fail. + * + * The count alone (`unresolvableLineCitation`, printed by the green line) says + * a residual exists without saying where, so nobody can work it down and + * nobody can tell a residual that SHRANK from one that moved. This prints + * `file:line -- citation` per row and a tally by shape, which is what a + * follow-up card needs to be closed rather than re-measured. + */ +function listUnresolvable(root = process.cwd()) { + const { counts, declined } = sweepCorpus(CORPUS, root); + const byShape = {}; + for (const d of declined) byShape[d.shape] = (byShape[d.shape] ?? 0) + 1; + for (const d of declined) console.log(`${d.doc}:${d.line} ${d.raw} [${d.shape}]`); + console.log( + `\n${declined.length} citation(s) name no tracked file and are not judged ` + + `(counter: ${counts.unresolvableLineCitation}) — ` + + Object.entries(byShape).sort().map(([k, v]) => `${v} ${k}`).join(', '), + ); } /* ─────────────────────────────── self-test ─────────────────────────────── */ @@ -242,11 +283,14 @@ function assert(cond, msg) { if (!cond) { console.error(`❌ check-scripts-symbo // row was retired, then 30 → 26 when the `scripts/check-adr-0087-registration.mjs` // row was retired and the array went EMPTY (#15765). Any other drop is cases that // STOPPED RUNNING; find what stopped registering instead of moving the number. +// 26 → 29 when the declined citations gained an ENUMERATION beside their count +// (#15809): three cases hold `declined` equal to `unresolvableLineCitation`, to +// the file/line/text a residual list needs, and to the shape classification. // ⚠️ An empty array does NOT make the allowance battery vacuous: its five // fixture cases run off `scripts/bad.mjs`, never off the live rows, so they are // not part of this arithmetic and must never fall out of the count. const SELF_TEST_BATTERIES = Object.freeze({ - 'check-scripts-symbol-anchors self-test': 26, + 'check-scripts-symbol-anchors self-test': 29, }); // DELETING an entry silences that battery's floor exactly as effectively as @@ -316,7 +360,7 @@ export function selfTest() { execFileSync('git', ['init', '-q'], { cwd: tmp }); execFileSync('git', ['add', '-A'], { cwd: tmp }); - const { findings, counts } = sweepCorpus(CORPUS, tmp); + const { findings, counts, declined } = sweepCorpus(CORPUS, tmp); const kinds = findings.map((f) => f.kind); const count = (k) => kinds.filter((x) => x === k).length; @@ -338,6 +382,19 @@ export function selfTest() { 'a citation naming no tracked file must not be a finding under judgeUntrackedLineAnchors: false'); check(counts.unresolvableLineCitation === 2, `both declined citations must be SEEN and counted, got ${counts.unresolvableLineCitation}`); + // ...and ENUMERATED, not merely counted (#15809). A count says a residual + // exists without saying where, so nobody can work it down and a residual + // that MOVED reads identically to one that shrank. `--list-unresolvable` + // prints this array; these three cases are what keep it equal to the + // counter rather than a second, drifting instrument. + check(declined.length === counts.unresolvableLineCitation, + `the declined ENUMERATION must equal the declined COUNT, got ${declined.length} vs ${counts.unresolvableLineCitation}`); + check(declined.every((d) => d.doc.includes('declined.mjs') && Number.isInteger(d.line) && d.raw), + 'every declined row must carry the file, the line and the citation text it was declined for'); + check( + declined.map((d) => d.shape).sort().join(',') === 'bare-filename,directory-qualified', + `the declined rows must be classified by SHAPE, got ${declined.map((d) => d.shape).sort().join(',')}`, + ); // 2. The allowance mechanism, in both directions, against the fixture. const fake = [{ file: 'scripts/bad.mjs', dated: '2026-01-01', heldBy: 'PR #1', why: 'fixture' }]; @@ -462,6 +519,7 @@ if (isEntrypoint(import.meta.url)) { ); process.exit(1); } - } else if (process.argv.includes('--list')) list(); + } else if (process.argv.includes('--list-unresolvable')) listUnresolvable(); + else if (process.argv.includes('--list')) list(); else runCheck(); } diff --git a/scripts/check-settings-bind-window.mjs b/scripts/check-settings-bind-window.mjs index e813f6563e..e5fc5ec5ea 100644 --- a/scripts/check-settings-bind-window.mjs +++ b/scripts/check-settings-bind-window.mjs @@ -91,9 +91,11 @@ * * Nested function bodies are deliberately NOT entered: a closure defined inside * a hook and handed to a collaborator runs when that collaborator calls it, not - * during the hook. `plugin-audit`'s `getLocale` (`audit-plugin.ts:200`) is the - * measured case — it is passed to `installAuditWriters` and invoked from - * `resolveWriteLocale` on CRUD writes (`audit-writers.ts:781`), i.e. long after + * during the hook. `plugin-audit`'s `getLocale` + * (`packages/plugins/plugin-audit/src/audit-plugin.ts`, line 200 as measured) + * is the measured case — it is passed to `installAuditWriters` and invoked from + * `packages/plugins/plugin-audit/src/audit-writers.ts#resolveWriteLocale` on + * CRUD writes (line 781 as measured), i.e. long after * the window closed. `packages/rest`'s `settingsServiceProvider` and * `ObjectQLPlugin`'s `getSettings` are the same shape. * diff --git a/scripts/check-tenant-audit-census.mjs b/scripts/check-tenant-audit-census.mjs index d32d4a2435..6eaa604ecb 100644 --- a/scripts/check-tenant-audit-census.mjs +++ b/scripts/check-tenant-audit-census.mjs @@ -75,8 +75,9 @@ * calls subtracted -- is present, dated, and NOT compared. * * Adopted by maintainer ruling on 2026-08-31 (option A), following the precedent - * the sibling `check-system-context-census.mjs` set at `:77-84`. The full ruling, - * the falsified alternative at `:30`, and the measurement that draws the line + * the sibling `scripts/check-system-context-census.mjs#UNENFORCED_TEXT_COUNTS` + * set (lines 77-84 there as measured). The full ruling, the falsified + * alternative (line 30 there, the ⭐ line quoted below), and the measurement that draws the line * where it is drawn are all at `UNENFORCED_SCALE_ROWS` below. * * Check B is not redundant with A. The generated region cannot lie, because it @@ -419,7 +420,8 @@ export const PROSE_COUNTS = [ * ## ⛔ What was NOT adopted, and why it is closed * * Asserting against a RECORDED BASELINE SHA instead of the live tree was proposed - * and is falsified. The sibling's `:30` refutes it in one line: + * and is falsified. `scripts/check-system-context-census.mjs` refutes it in one + * line (line 30 there as measured): * * > *"a gate that only checks what the page already says can never find what * > the page failed to say."* diff --git a/scripts/check-workflow-status-functions.mjs b/scripts/check-workflow-status-functions.mjs index 0fa4ced881..25d4175a34 100644 --- a/scripts/check-workflow-status-functions.mjs +++ b/scripts/check-workflow-status-functions.mjs @@ -373,7 +373,8 @@ function selfTest() { try { // ── 1. A violating sample must go red ──────────────────────────────────── // - // Verbatim shape of publish-smoke.yml:98 as #5343 found it. + // Verbatim shape of `.github/workflows/publish-smoke.yml` as #5343 found + // it, at line 98 then. battery('1. A violating sample must go red'); const violating = makeRoot({ '.github/workflows/publish-smoke.yml': `name: Publish Smoke diff --git a/scripts/doc-line-anchors.mjs b/scripts/doc-line-anchors.mjs index 3f6e6aeb51..e9aac66c0c 100644 --- a/scripts/doc-line-anchors.mjs +++ b/scripts/doc-line-anchors.mjs @@ -4,7 +4,7 @@ /** * doc-line-anchors -- the ONE reader for `file:line` anchors written in docs prose. * - * A docs page that cites source by `` `security-plugin.ts:1560` `` has created a + * A docs page that cites source by `` `.ts:1560` `` has created a * two-sided invariant with no owner: the line lives in one tree, the citation in * another, and nothing relates them. Measured on * `content/docs/permissions/system-context.mdx` over 19 days, **101 of its 111 @@ -20,16 +20,21 @@ * * A naive reader finds only the first and undercounts by a third. * - * FULL `` `objectql/src/engine.ts:10501` `` path + line - * CONTINUATION `` `:1409` `` line only; the file is - * the nearest FULL anchor - * to its left, which is - * how a row cites four - * sites in one file - * RANGE_END `` `2630` `` a bare number whose only - * separation from the - * anchor on its left is a - * dash: `:2483`--`2630` + * FULL `` `/.ts:10501` `` path + line + * CONTINUATION a backticked `:` and a line number, line only; the file is + * `1409` the nearest FULL anchor + * to its left, which is + * how a row cites four + * sites in one file + * RANGE_END `` `2630` `` a bare number whose only + * separation from the + * anchor on its left is a + * dash -- a continuation + * for `2483`, then `2630` + * + * ⚠️ The angle brackets and the spelled-out continuations are deliberate: this + * file is itself inside the `scripts/**` symbol-anchor corpus, so an + * illustration written anchor-shaped would be a citation of its own. * * The measurement that makes this list non-negotiable: `grep -c` for anchor-shaped * text over the previous edition of that page answered **64**, because it counts @@ -39,7 +44,7 @@ * ## Resolution is by unique suffix, and ambiguity is an ERROR * * An anchor names as little of the path as it can and still be unique -- - * `read-audit.ts:556` where the basename is unique, `objectql/src/engine.ts:10501` + * `.ts:556` where the basename is unique, `/.ts:10501` * where it is not. That is a property a gate can hold: resolve the spelling * against the tracked file list by path-suffix and REFUSE when two files match. * The previous edition had **41 of 111** anchors whose bare basename matched two @@ -60,11 +65,11 @@ import { extname } from 'node:path'; /** Extensions an anchor may name. Anything else is prose, not a citation. */ export const ANCHOR_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.mjs', '.cjs', '.js', '.jsx']; -/** `path/to/file.ts:1234` -- the whole span, nothing else. */ +/** `/.ts:1234` -- the whole span, nothing else. */ const FULL_RE = /^([A-Za-z0-9_.\-/]+)\.([a-z]+):(\d+)$/; -/** `path/to/file.ts` -- a cited file with no line. */ +/** `/.ts` -- a cited file with no line. */ const PATH_ONLY_RE = /^([A-Za-z0-9_.\-/]+)\.([a-z]+)$/; -/** `:1409` -- a continuation of the anchor to its left. */ +/** A backticked `:` and a line number, `1409` -- a continuation of the anchor to its left. */ const CONTINUATION_RE = /^:(\d+)$/; /** `2630` -- a bare number; only an anchor when a dash joins it to one. */ const BARE_NUMBER_RE = /^(\d+)$/; @@ -295,8 +300,9 @@ export function blankAnchorLineNumbers(rawText) { if (a.start < cursor) continue; out += rawText.slice(cursor, a.start); // The LAST run of digits in the span is the line number in all three shapes -- - // `path.ts:1234`, `:1234` and a bare `1234` -- and a path that itself carries - // digits (`0112-codes.ts:55`) keeps them, because the lookahead requires that + // `.ts:1234`, a backticked `:` and `1234`, and a bare `1234` -- and a + // path that itself carries digits (`0112-.ts:55`) keeps them, because + // the lookahead requires that // nothing but non-digits follows. out += rawText.slice(a.start, a.end).replace(/\d+(?=\D*$)/, '#'); cursor = a.end; diff --git a/scripts/objectui-changeset-digest.mjs b/scripts/objectui-changeset-digest.mjs index bd4aaf9d2e..f51a5a29ff 100644 --- a/scripts/objectui-changeset-digest.mjs +++ b/scripts/objectui-changeset-digest.mjs @@ -2153,7 +2153,8 @@ function selfTest() { ); // --- #6494: the ADR-0087 disposition scaffold --------------------------- - // The gate's own marker pattern (check-adr-0087-registration.mjs:416), + // The gate's own marker pattern (`scripts/check-adr-0087-registration.mjs`, + // line 416 as measured), // copied rather than imported: that module's CLI dispatch is top-level, so // importing it would RUN the gate. The copy is belt-and-braces — the REAL // gate binary judges a real artifact in the round trip below, and that, not diff --git a/scripts/pr-labels.mjs b/scripts/pr-labels.mjs index c6b9cab431..0335425712 100644 --- a/scripts/pr-labels.mjs +++ b/scripts/pr-labels.mjs @@ -21,13 +21,14 @@ * Both label writers this file replaces did exactly that, read out of their * pinned sources rather than inferred from their docs: * - * * codelytv/pr-size-labeler@v1.10.4 -- src/github.sh:68-91 - * (`github::add_label_to_pr`): GETs the PR, greps its OWN size family out + * * codelytv/pr-size-labeler@v1.10.4 -- `src/github.sh` in THAT repo, lines + * 68-91 as pinned (`github::add_label_to_pr`): GETs the PR, greps its OWN size family out * of the result, appends the new size label, then * `curl -X PUT .../issues/$pr_number/labels` with the whole set. No * mitigation of any kind: the window is the entire round trip. - * * actions/labeler@v7.0.0 -- src/labeler.ts:56,111-133 plus - * src/api/set-labels.ts: snapshots `preexistingLabels` at run start, unions + * * actions/labeler@v7.0.0 -- `src/labeler.ts` in THAT repo, lines 56 and + * 111-133 as pinned, plus its + * `src/api/set-labels.ts`: snapshots `preexistingLabels` at run start, unions * in the config matches, re-reads the live label list once and carries * forward whatever appeared in between, then calls * `client.rest.issues.setLabels` -- which IS the PUT. The re-read NARROWS @@ -67,12 +68,14 @@ * * ## What is deliberately preserved from the retired actions * - * * The size buckets compare with `<`, NOT `<=` -- `labeler.sh:50-60` uses + * * The size buckets compare with `<`, NOT `<=` -- codelytv's `src/labeler.sh`, + * lines 50-60 as pinned, uses * `[ "$total" -lt "$max" ]`. A 10-line PR is `size/s`, not `size/xs`. The * thresholds arrive in the same env names the action's inputs used, so the * workflow diff is auditable value-for-value. * * `files_to_ignore` is a space-separated list matched against the WHOLE - * path, the way `[[ $filename == $pattern ]]` did in github.sh:36. + * path, the way `[[ $filename == $pattern ]]` did in codelytv's + * `src/github.sh`, line 36 as pinned. * * Path labels are never removed, matching `sync-labels: false`. The path * half issues POST only -- it has no DELETE at all. * * The size family IS owned by this writer, so a stale `size/*` is removed @@ -80,7 +83,8 @@ * thing by grepping the family out of its PUT payload; the difference is * that its version also carried -- and could drop -- every bystander label. * - * One behaviour deliberately DIVERGES: github.sh:23 reads + * One behaviour deliberately DIVERGES: codelytv's `src/github.sh`, line 23 as + * pinned, reads * `pulls/{n}/files?per_page=100` and never paginates ("NOTE: this code is not * resilient to changes w/ > 100 files"), so a 400-file PR was sized off its * first 100 files. This paginates. A PR over 100 files may therefore get a @@ -196,7 +200,8 @@ export function matchGlob(pattern, filePath) { } // --------------------------------------------------------------------------- -// Size bucketing -- codelytv/pr-size-labeler src/labeler.sh:50-60, verbatim. +// Size bucketing -- codelytv/pr-size-labeler `src/labeler.sh`, lines 50-60 as +// pinned, verbatim. // --------------------------------------------------------------------------- /** @@ -216,7 +221,8 @@ export function sizeLabelFor(total, buckets) { return fallthrough.label; } -/** Total modifications, mirroring github.sh:5-52 with pagination added. */ +/** Total modifications, mirroring codelytv's `src/github.sh` lines 5-52 as + * pinned, with pagination added. */ export function totalModifications(files, filesToIgnore) { let total = 0; for (const file of files) { @@ -719,7 +725,8 @@ function selfTest() { const staleRead = []; const liveSet = ['skip-changeset']; - // Transcribed from github.sh:68-91 -- take the read, drop its OWN family, + // Transcribed from codelytv's `src/github.sh`, lines 68-91 as pinned -- take + // the read, drop its OWN family, // append the new size label, PUT the whole thing. const retiredPlan = [ { diff --git a/scripts/symbol-anchors.mjs b/scripts/symbol-anchors.mjs index ff836fccbd..2e94f189f3 100644 --- a/scripts/symbol-anchors.mjs +++ b/scripts/symbol-anchors.mjs @@ -32,7 +32,9 @@ * 16,000-line file has an expected lifetime measured in days. * * ⭐ And the rot can hide a SEMANTIC INVERSION. ADR-0113 cited - * `sql-driver.ts:4901` for `if (field.required) col.notNullable()`; the + * `packages/drivers/driver-sql/src/sql-driver.ts` (line 4901 as the ADR wrote + * it -- a dated reading, ⛔ not a pointer) for `if (field.required) + * col.notNullable()`; the * mechanism moved and now keys off `storage.notNull` -- the opposite predicate. * With the anchor rotted, the ADR's pre-decision Context row reads to a fresh * reader as a description of today. That is carded separately as #14193 and is @@ -96,8 +98,11 @@ * (c) READS NATURALLY. `#symbol` is the fragment syntax a reader already * knows from URLs, and it survives copy-paste into a GitHub link. * - * ⛔ A LINE NUMBER IS NOT AN ANCHOR FORM. `file.ts:4901`, `file.ts:341-400`, - * `file.ts:459–463` (en dash) and a bare continuation `:2933` are all findings. + * ⛔ A LINE NUMBER IS NOT AN ANCHOR FORM. `.ts:4901`, `.ts:341-400`, + * `.ts:459–463` (en dash) and a bare continuation -- a backticked `:` + * followed by a line spec, `2933` -- are all findings. ⚠️ The angle brackets + * are deliberate: this file is itself inside the `scripts/**` corpus, so an + * illustration written path-shaped would be a citation of its own. * * ### The one escape hatch, and who may use it * @@ -209,10 +214,10 @@ const CONTINUATION_SPAN = new RegExp('`#(?' + SYMBOL + ')`', 'g'); /* ⛔ The forms the migration deleted. Matched on the SAME text the anchor * extractor sees, so a line number cannot hide behind a spelling the extractor * normalises away. Both the full form and the bare continuation. */ -/* ⭐ A LINE SPEC is not just `:123`, and assuming it was is how a gate - * false-greens. The #13556 corpus spells one position NINE ways, and the - * census's own extractor missed the comma forms exactly as a naive rule here - * would: +/* ⭐ A LINE SPEC is not just a colon and one run of digits, and assuming it was + * is how a gate false-greens. The #13556 corpus spells one position NINE ways, + * and the census's own extractor missed the comma forms exactly as a naive rule + * here would: * * :4901 :341-400 :459–463 (en dash) :2214+ * :610,1028 :13,346-389 :29-39,147-152 :2956/2991 @@ -224,18 +229,19 @@ const CONTINUATION_SPAN = new RegExp('`#(?' + SYMBOL + ')`', 'g'); * version matched only a span that was EXACTLY an anchor, and it called a * corpus still carrying rot clean — three ways at once: a bare anchor in * running prose, an anchor sharing a span with other text - * (`` `Builder.io SDK: packages/sdks/src/types/builder-block.ts:42` ``), and + * (`` `Builder.io SDK: /.ts:42` ``), and * that slash list. A line number is rot wherever it is written; the only thing * excluded is a FENCED block, which is quoted material, not an anchor. */ const LINE_SPEC = '\\d+(?:\\s*[-–—]\\s*\\d+)?(?:\\s*[,/]\\s*\\d+(?:\\s*[-–—]\\s*\\d+)?)*\\+?'; const LINE_ANCHOR = new RegExp('(?/.ts:378` — a comment that is nothing but a path. */ const FENCED_HEADER = new RegExp('^\\s*(?://|#|\\*|/\\*)\\s*' + PATHISH + ':\\d'); /* ⭐ A ninth spelling, and the census counted none of them: a bare backticked - * number, tilde-prefixed — `` ~`326` `` for "about line 326". It carries no + * number, tilde-prefixed — a `~` then a backticked line number, `` ~`NNN` ``, + * written for "about line 326". It carries no * path, so it cannot be resolved to anything; ADR-0056 alone held 13. * * ⚠️ Only the TILDE form is judged. A bare `` `403` `` or `` `4096` `` is an @@ -370,7 +376,7 @@ export function extractAnchors(markdown) { * and the census counted those among its 343 — so skipping fences wholesale * would leave five rotted anchors behind. Everything else in a fence is * quoted material (sample code, CI output) and is NOT judged: an example - * that happens to contain `foo.ts:12` is not an anchor. */ + * that happens to contain `.ts:12` is not an anchor. */ if (inFence) { if (!FENCED_HEADER.test(text)) return; for (const m of text.matchAll(LINE_ANCHOR)) { @@ -450,7 +456,7 @@ export function extractAnchors(markdown) { * corpus unusable. Measured over the 216 tracked `scripts/**` `.mjs` files on * `5315098df`: 251 live line citations raw against 128 through this projection. * The 123 that vanish are not rot -- they are a gate's own self-test FIXTURES, - * string literals like `'p.ts:2'` and `'content/docs/other.mdx:1'` written to + * string literals like `'.ts:2'` and `'/.mdx:1'` written to * provoke that gate's own line-reporting. Judging those would red a gate for * testing itself, which is the fabrication direction * `scripts/js-comment-mask.mjs` exists to close. @@ -523,7 +529,26 @@ function trackedFiles(root) { } /** - * Sweep one corpus. Returns findings and the counts a report needs. + * The SHAPE of a declined citation -- why no resolver could bind it. Kept here + * rather than in a corpus so every corpus that waives citations reports the + * same four words, and a residual list can be grouped without re-deriving the + * classification from the raw text. + * + * Order is load-bearing: a continuation and a tilde form BOTH inherit whatever + * path preceded them on the line, so they must be named by how they were + * written, never by the path they borrowed. + */ +export function declinedShape(la) { + if (la.tilde) return 'tilde'; + if (la.continuation) return 'continuation'; + if (la.path?.includes('/')) return 'directory-qualified'; + return 'bare-filename'; +} + +/** + * Sweep one corpus. Returns findings, the counts a report needs, and the + * citations a corpus declined to judge (`declined`, empty unless the corpus + * sets `judgeUntrackedLineAnchors: false`). * * Finding kinds: * line-anchor a `path:NNN` survived the migration (RED) @@ -535,6 +560,7 @@ function trackedFiles(root) { export function sweepCorpus(corpus, root = process.cwd()) { const tracked = trackedFiles(root); const findings = []; + const declined = []; const counts = { docs: 0, anchors: 0, symbol: 0, fileLevel: 0, declaration: 0, literal: 0, crossRepo: 0, exempt: 0, continuation: 0, unresolvableLineCitation: 0 }; const sourceCache = new Map(); const readTarget = (p) => { @@ -560,19 +586,26 @@ export function sweepCorpus(corpus, root = process.cwd()) { * and a gate whose only remedy is "stop writing that" is the * permanently-red gate this repo retired. * - * Measured on `5315098df` over `scripts/**` `.mjs` comment prose: 128 - * live citations in all, of which 32 name a tracked file and 96 do not -- - * 66 bare filenames (`engine.ts:9407`, an abbreviation inside a census - * table that no resolver can bind to one of this tree's several - * `engine.ts`), 22 continuations inheriting no path of their own, 11 - * directory-qualified paths that are illustrations or sibling-repo files - * (`path/to/file.ts:1234`, `src/github.sh:68-91`), and 1 tilde form. The - * 96 are a real defect class and are recorded as a follow-up, exactly as - * the 1,056 bare paths under `checkBarePaths` were -- but they are not - * the cross-file rot #15765 measured, and folding them in would bury this - * gate's signal under a cleanup nobody ruled on. */ + * Measured on `scripts/symbol-anchors.mjs` over `scripts/**` `.mjs` + * comment prose, on the date recorded in `check-scripts-symbol-anchors`'s + * own census constant: 128 live citations in all, of which 32 named a + * tracked file and 96 did not -- bare filenames (an abbreviation inside a + * census table that no resolver can bind to one of this tree's several + * files of that name), continuations inheriting no path of their own, + * directory-qualified paths that are illustrations or sibling-repo files, + * and one tilde form. That population is a real defect class and was + * recorded as a follow-up (#15809), exactly as the 1,056 bare paths under + * `checkBarePaths` were -- but it is not the cross-file rot #15765 + * measured, and folding it in would bury this gate's signal under a + * cleanup nobody ruled on. + * + * ⭐ Declining to JUDGE is not declining to SEE. Every declined citation + * is counted AND recorded in `declined`, so a corpus can enumerate what + * it waived (`--list-unresolvable`) and the residual stays a list rather + * than a number nobody can act on. */ if (!corpus.judgeUntrackedLineAnchors && !tracked.has(la.path ?? '')) { counts.unresolvableLineCitation += 1; + declined.push({ doc: rel, line: la.line, raw: la.raw, path: la.path ?? null, shape: declinedShape(la) }); continue; } /* A marker whose CLASS is unrecognised is its own finding, never a @@ -645,7 +678,7 @@ export function sweepCorpus(corpus, root = process.cwd()) { counts[cls] += 1; } } - return { findings, counts }; + return { findings, counts, declined }; } export function formatFindings(findings) { @@ -678,8 +711,9 @@ function assert(cond, msg) { if (!cond) { console.error(`❌ symbol-anchors --se // The count is a FLOOR, not an equality — adding cases is ordinary work and must // not red. A battery BELOW its floor means cases stopped running; the remedy is // to find what stopped registering. +// 63 → 67 when `declinedShape` gained a case per arm (#15809). const SELF_TEST_BATTERIES = Object.freeze({ - 'symbol-anchors self-test': 63, + 'symbol-anchors self-test': 67, }); // DELETING an entry silences that battery's floor exactly as effectively as @@ -885,6 +919,18 @@ export function selfTest() { try { defineCorpus({ id: 'x', label: 'x', docRoots: ['a'], docProjection: 'commentProse' }); } catch { projThrew = true; } check(projThrew, 'defineCorpus must refuse a docProjection that is not callable — a skipped projection sweeps raw source while reading as though it did not'); + // 11. ⭐ The declined SHAPE, whose ordering is the whole rule (#15809): a + // continuation and a tilde form inherit whatever path preceded them on + // the line, so classifying by the path first would file them under the + // borrowed path and a residual list would name a file the author never + // wrote. All four arms, in one case each. + check(declinedShape({ tilde: true, path: 'a/b.ts' }) === 'tilde', + 'a tilde form is a TILDE even when a path preceded it on the line — it borrowed that path, it did not cite it'); + check(declinedShape({ continuation: true, path: 'a/b.ts' }) === 'continuation', + 'a continuation is a CONTINUATION even when a path preceded it on the line'); + check(declinedShape({ path: 'a/b.ts' }) === 'directory-qualified', 'a path with a slash is directory-qualified'); + check(declinedShape({ path: 'b.ts' }) === 'bare-filename', 'a path with no slash is a bare filename — the shape no resolver can bind'); + // ── The floor: every declared battery RAN, and ran its cases (#13489) ──── // // Evaluated after every battery has had its chance and BEFORE the verdict, so