From ef75471b2f8290fefaa10d328bc39b25dfc4046e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 08:35:29 +0000 Subject: [PATCH] fix(devx): route the elevation census page to os-regen and pin the gate that guards it Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC --- .gitattributes | 18 ++++++++ package.json | 4 +- scripts/check-system-context-census.mjs | 60 +++++++++++++++++++++++++ scripts/regen-artifacts.mjs | 50 +++++++++++++++++++++ 4 files changed, 131 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 0652470e1d..09fb0ff8ed 100644 --- a/.gitattributes +++ b/.gitattributes @@ -75,6 +75,23 @@ # hand-written measurement of how each type got where it is — and is NOT. # Regenerating a Note would fabricate a verdict, which that README calls worse # than a missing row. +# +# The elevation census page joined at #13646 — a generated `file:line` anchor +# table whose correct merged values are on NEITHER side of a conflict (measured on +# #13625: five conflicted anchors resolved to 4408/5771/6019/6382/6575 against +# branch 4407/5770/… and main 4284/5647/…), so no text merge and no hand merge can +# reach them. ⚠️ It is routed as the FILE and NOT as `content/docs/permissions/**`: +# unlike `content/docs/references/**` above, which is generated whole, that +# directory is 22 hand-written prose pages around one generated one, and the glob +# would defer the prose to OURS. See NOT_DRIVER_MANAGED for that entry. +# +# This is also the row where the header's own warning is answered rather than +# accepted: deferring is safe here because `scripts/check-system-context-census.mjs` +# still reddens on every PR from the required `Lint & Repo Gates` job — it +# RE-DERIVES the census from the tree, so it catches the stale anchors a merge +# leaves behind even when nothing conflicted, which is the majority case (#13625: +# 18 anchors stale, 5 marked). The driver removes hand-merge rounds; it is never +# the only signal. packages/spec/spec-changes.json merge=os-regen packages/spec/liveness/state-counts.md merge=os-regen @@ -89,3 +106,4 @@ packages/spec/api-surface-signatures.json merge=os-regen docs/protocol-upgrade-guide.md merge=os-regen docs/audits/2026-07-unknown-key-strictness-ledger.counts.md merge=os-regen content/docs/references/** merge=os-regen +content/docs/permissions/system-context.mdx merge=os-regen diff --git a/package.json b/package.json index 9eda6b811d..6e289ca708 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,9 @@ "check:tenant-chokepoint": "node scripts/check-tenant-chokepoint.mjs --self-test && node scripts/check-tenant-chokepoint.mjs", "check:stall-guard": "node scripts/run-with-stall-guard.mjs --self-test", "check:stall-guard-budget": "node scripts/check-stall-guard-budget.mjs --self-test && node scripts/check-stall-guard-budget.mjs", - "check:stall-guard-headroom": "node scripts/measure-stall-guard-headroom.mjs --self-test" + "check:stall-guard-headroom": "node scripts/measure-stall-guard-headroom.mjs --self-test", + "gen:system-context-census": "node scripts/check-system-context-census.mjs --fix", + "check:system-context-census": "node scripts/check-system-context-census.mjs --self-test && node scripts/check-system-context-census.mjs" }, "keywords": [ "objectstack", diff --git a/scripts/check-system-context-census.mjs b/scripts/check-system-context-census.mjs index a411cab3b2..a967358bb3 100644 --- a/scripts/check-system-context-census.mjs +++ b/scripts/check-system-context-census.mjs @@ -119,6 +119,36 @@ * of zero files, a declared-count pattern that matches nothing, an UNENFORCED row * that vanished or lost its date, and a ledger row that locates nothing are all * exit 1 naming what could not be read. + * + * ## ⭐ Wiring, and why the self-test asserts it (#13646) + * + * This gate IS the "regenerate and diff" instrument for the page: it re-derives the + * census from the tree and reddens when the committed anchors disagree. That makes + * it the only thing standing between the page and the failure mode that has no + * other signal -- an anchor going stale because the file it CITES moved, in a merge + * that produced no conflict at all. + * + * Measured on `cc837dbfec` by shifting `plugin-sharing/src/sharing-service.ts` down + * 29 lines (main's real delta in the #13625 window) with the page untouched, which + * is the branch-never-touched-that-file case git merges clean and silent: + * + * gate on the shifted tree exit 1, 16 findings, naming every one of the five + * sharing-service anchors and the ledger row + * `--fix` then the gate 109 sites re-anchored, exit 0 + * + * So the anchors are recoverable and the loss is loud -- PROVIDED the gate is + * scheduled. Nothing asserted that it was. `check-self-test-wired` is conditional + * in the wrong direction here: it requires that a script CI runs also has its + * `--self-test` run, so deleting BOTH invocations from `lint.yml` retires this gate + * with every check still green. The self-test therefore reads the workflow text and + * asserts both legs, the way `check-doc-frontmatter`, `check-aggregator-roster` and + * `check-ci-filter-parity` each assert their own -- a gate that exists and is not + * scheduled is the dormant shape seen from the other side. + * + * ⚠️ The pin deliberately needs NO workflow edit: `lint.yml` already invokes both + * legs, in the required `Lint & Repo Gates` job, on a trigger set that includes + * `merge_group` and with no `paths:` filter. It is the repo's busiest file and the + * assertion reads it rather than adding to it. */ import { readFileSync, writeFileSync } from 'node:fs'; @@ -1501,6 +1531,36 @@ function selfTest() { refusalText ); + // ── WIRING: this gate, and its self-test, really run in CI ────────────────── + // + // ⭐ The half a clean tree cannot show, and the reason this block exists. Every + // other case above judges the RULES; this one judges whether anything runs them. + // `check-self-test-wired` is conditional in the wrong direction for that -- it + // requires "if CI runs the script, CI runs its --self-test too", so deleting BOTH + // lines from `lint.yml` leaves it green and silently retires the only instrument + // that catches a stale anchor. Measured: the census is what reddens when a cited + // file moves underneath a page nobody edited, so its scheduling is load-bearing, + // not incidental. + // + // Asserted against the workflow TEXT, following the precedent `check-doc-frontmatter`, + // `check-aggregator-roster` and `check-ci-filter-parity` set -- and, like the second + // docs root that gate added, this needed NO workflow edit: `lint.yml` already invokes + // both legs, and it is the repo's busiest file. + const SELF = 'scripts/check-system-context-census.mjs'; + let lintYml = null; + try { + lintYml = readFileSync(join(ROOT, '.github/workflows/lint.yml'), 'utf8'); + } catch (err) { + t(`WIRING: .github/workflows/lint.yml is readable`, false, err.code ?? err.message); + } + if (lintYml !== null) { + t( + 'WIRING: lint.yml invokes this gate directly (the GATE INVOCATION IDIOM, not a package.json fence)', + lintYml.includes(`node ${SELF}\n`) + ); + t('WIRING: lint.yml runs the --self-test leg too', lintYml.includes(`node ${SELF} --self-test`)); + } + process.stdout.write( failures === 0 ? '\ncheck-system-context-census --self-test: all cases passed\n' diff --git a/scripts/regen-artifacts.mjs b/scripts/regen-artifacts.mjs index 0c85e9c7c8..156b00dbe1 100644 --- a/scripts/regen-artifacts.mjs +++ b/scripts/regen-artifacts.mjs @@ -179,6 +179,45 @@ export const REGEN_ARTIFACTS = Object.freeze([ gen: 'gen:liveness-counts', check: 'check:liveness', }, + // #13646. The elevation census page — a generated `file:line` anchor table, and + // the first row here owned by ROOT tooling rather than by `packages/spec` (the + // owner field #13585 added exists for exactly this). + // + // ⚠️ The row is the FILE, not `content/docs/permissions/**`, and the difference + // is safety rather than tidiness. Its routed sibling `content/docs/references/**` + // is a whole generated tree; `content/docs/permissions/` is 22 hand-written prose + // pages with ONE generated page among them, so the directory glob would hand 21 + // prose files to a driver that resolves to OURS — laundering away a sibling's + // prose edit, which is the exact trade `migrations/registry.ts` is kept out of + // this table for. The glob is recorded in NOT_DRIVER_MANAGED below. + // + // Why it belongs here at all: two PRs that each ran `--fix` against their own + // tree write correct-for-themselves line numbers into the same rows, and the + // merged tree's correct values equal NEITHER side — measured on #13625's merge, + // where the five conflicted anchors resolved to `4408/5771/6019/6382/6575` + // against branch `4407/5770/…` and main `4284/5647/…`. A text merge cannot reach + // that answer from either input, so this is a deferral-and-regenerate shape. + // + // ⭐ And the deferral is safe in the direction that matters, which is the + // question `os-regen-merge.sh` raises about every path here — a driver that + // exits 0 trades a loud failure for a silent one unless something else still + // reddens. Here something does, on every PR: `check-system-context-census.mjs` + // runs in the required `Lint & Repo Gates` job with no `paths:` filter and on + // `merge_group`, it re-derives the census from the tree rather than reading the + // page back, and its scheduling is pinned by its own `--self-test` (#13646). The + // driver is therefore the cheap half here and never the only signal. + // + // No `readsDist`/`readsSchemaTree`: the census is an AST walk over `src/`, so a + // merged tree is the whole prerequisite. `gen` cannot launder a POPULATION change + // either — `--fix` re-anchors a pure shift and REFUSES when a site arrived or + // vanished, leaving the page untouched and the gate red (measured: exit 1, zero + // anchors rewritten, `[declared-count] ruling-sites says 109, the census says 110`). + { + path: 'content/docs/permissions/system-context.mdx', + gen: 'gen:system-context-census', + check: 'check:system-context-census', + owner: ROOT_OWNER, + }, ]); /** @@ -238,6 +277,17 @@ export const NOT_DRIVER_MANAGED = Object.freeze([ + 'registries and the other one is `scripts/adr-anchors.json` (#7301). Splitting this one too is ' + 'a follow-up with its own measurement, not a rider.', }, + { + path: 'content/docs/permissions/**', + why: + 'the DIRECTORY is not what #13646 routed, and recording that is the point of this ledger. ' + + '22 of its 23 pages are hand-written permissions prose; exactly one — `system-context.mdx`, ' + + 'declared above — is a generated anchor table. Routing the tree the way its sibling ' + + '`content/docs/references/**` is routed reads as symmetry and is not: that sibling is ' + + 'generated whole, this one would defer 21 prose files to OURS and lose the other side\'s ' + + 'edits silently. Route the generated FILE; leave the neighbours to text-merge, which is ' + + 'correct for prose and always was.', + }, { path: 'docs/audits/**', why: