Repository navigation
Commit 42ec951
docs(permissions): generate the system-context site register; behaviour rows key their reads by symbol name (#26522)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9KWDJajrYvamoD37G6YZHjx)_
Fixes #26442
Part of #26431
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:`system-context.mdx` 的行为表不再手写文件路径。每行在新的 `Symbol` 列里只写符号名(如
`registerAuthzMiddleware`)。页尾新增一个生成区「Site register」,每个有 isSystem
读点的符号一行,写完整的
`path#symbol`、该符号里的读点数和归属的行号。`check-system-context-census.mjs` 加了
`--write` 来重生这个区,检查时要求它和树上算出来的一字不差。
- 为什么改:页面 30 天里被改了 84 次,其中 12 次只是重构把函数挪到新文件后手改路径。现在挪文件只需跑
`--write`,登记表变一行,行文不动。本 PR 开发期间 main 正好又挪了一次(`resolveOneHopRelated` 挪进
`engine-master-detail-related.ts`),合并后跑 `--write`,登记表变了一行,没有手改任何一行。
- 风险与回滚:只动这个文档页和它的门禁脚本,不动任何 isSystem 读点和发布内容;回滚即
revert。检查只增不减:同一个符号里多加或删掉一个读点,原来门禁不红,现在登记表的读点数会变,不重生就红。
- 先看:`scripts/check-system-context-census.mjs` 里新加的 `resolveRowKeys` 和
`evaluateRegister`(F 段);再看页面 `## The table` 一节的 `Symbol` 列和页尾 `## Site
register`。
- 两分钟验证:`pnpm check:system-context-census`,预期自测 all cases passed,门禁最后一句以
`the generated register (132 symbol(s)) matches the tree.` 结尾。
- 需要你做的:无,席位落地。另请 `domain:server` 确认符号清单(见下方 Server-lane confirmation)。
## What changes
**Generate, not retire.** The card offered two ways. This PR takes the
generated one. The page is read: four docs pages link to it
(`permissions/authorization.mdx`, `permissions/index.mdx`,
`permissions/record-view-auditing.mdx`,
`protocol/objectql/security.mdx`), two source comments quote it
(`packages/objectql/src/engine.ts`,
`security-plugin-authz-middleware.ts`), and #26466 is about to link its
row 1b. Its value is the per-row behaviour prose, which no generator can
write. So the census half is split off and generated, and the prose
stays hand-written. This is the `tenant-audit-census.mdx` split.
**The page** (`content/docs/permissions/system-context.mdx`)
- In the six behaviour tables, the `Anchor` column (and section 6's
`Site`) becomes `Symbol`. A cell names its symbols by name, for example
`registerAuthzMiddleware`. Which file holds each one is no longer
written by hand.
- Four names are held by more than one file in the census:
`isUserContextWrite` (3 files), and `onInsert`, `onUpdate` and `start`
(2 each). For these the row keeps the full `path#symbol` anchor (rows
12, 23c, 23d, 25, 26, 26b, 62). The shared resolver still checks them,
and they still move the old way. Three non-read citations stay as full
anchors, as before (`READONLY_CLASS_REASONS`,
`assertEngineOwnedWriteAllowed`, `registerCrudNodes`).
- A new `## Site register` section holds a generated region, marked like
the tenant-audit page's. It has one line per census symbol: ``
`path#symbol` ``, its read count, and the rows whose `Symbol` cell names
it. There are 132 lines and no total line, which keeps #22927's
no-totals ruling.
- The prose above the tables (the two callouts, "Maintaining this
table", "What CI holds") now says what is generated and what is a
human's call. The check table there grows from four rows to six.
**The gate** (`scripts/check-system-context-census.mjs`)
- New check **F, SITE REGISTER** (`evaluateRegister`). Every census read
must be owned by a row whose `Symbol` cell names it
(`[read-without-a-row]`). Every bare key must name exactly one file's
symbol (`[row-key-no-read]`, `[row-key-ambiguous]`). Every row must key
at least one read (`[row-without-a-read]`, `[row-without-a-key]`). The
committed region must equal what `--write` renders (`[register-drift]`,
which names the first differing line and the fix command). Missing or
duplicated markers and a renamed key column are refusals
(`[no-register]`, `[no-row-keys]`).
- New `--write`. It splices only the region, so every byte outside the
marker pair stays the same. It refuses, writing nothing, while any read
has no row: the generator never assigns a read to a row.
- `--fix` still refuses with exit 2, and now names `--write`.
- Checks A to E are unchanged: RESOLUTION, POPULATION, CLASSIFICATION,
the dated decomposition and ROW REFERENCES. The register's anchors
satisfy POPULATION, so a stale register reds A and B as well as F.
- Self-test: two new batteries, ⭐ THE SITE REGISTER (12 cases) and ⭐ THE
REAL PAGE (5 cases). The `--fix` battery is rewritten to "only `--write`
writes, and only the register" (5 cases). The precision battery gains a
case showing the register now catches an added read (3 cases). The
roster floor moves from 16 to 18.
- Not changed: the root alias `check:system-context-census`, `lint.yml`,
and any `isSystem` read. So this PR is not Tier H.
## Why
The gate's `--fix` was retired (exit 2), so any change to an anchor was
a hand edit. Measured on `origin/main` for the 30 days to `00a4593ad7`:
**84** commits touched the page, and **12** of them changed nothing on
it but the path inside an anchor. For example: `bdfad1fc7`, `366e4aa69`,
`2e7127bc3` (objectql `engine.ts` cuts), and `df893f6d7`, `c15fd9a72`,
`82f5a111b`, `dd124e946`, `77a77e42f`, `3e514912a` (plugin-security
`security-plugin.ts` cuts).
With this PR, a symbol move is `node
scripts/check-system-context-census.mjs --write`, which changes one
register line and no row.
## Server-lane confirmation (domain:server)
The triage line asks `domain:server` to confirm the symbol list. This PR
does not change any `isSystem` read. It only moves where each row writes
its file. Please confirm two things:
1. Each row's `Symbol` cell names the reads that row describes. The
conversion was mechanical: a full anchor whose `path#symbol` is a census
read became its bare name when the name is unique, and nothing else
changed. A row-by-row old/new listing is in the page diff, in the last
cell of every table row.
2. The register's `Rows` column is right. Each register line says which
rows own the symbol. Five symbols are owned by more than one row:
`registerAuthzMiddleware` (rows 1, 2, 30), `insert` in `engine.ts` (21,
22), `update` in `engine.ts` (19, 20), `refusePackageRequest` (52, 54)
and `handleMetadataRequest` (50, 52).
## Tests
<details><summary>Red demonstration on a real tree: an
<code>isSystem</code> read ADDED inside a keyed symbol, without
regenerating (head 8b1b4d8 vs <code>origin/main</code>
00a4593)</summary>
`scripts/ablation-replace.mjs` added `const probeSecondRead =
execCtx?.isSystem;` inside
`packages/objectql/src/engine-driver-options.ts#buildDriverOptions` (row
24's key). The file was restored, and `git diff HEAD` was empty
afterwards.
- On `origin/main` 00a4593 (a detached control worktree):
`check-system-context-census` **exit 0**. This is the precision gap the
page declared.
- On this branch: **exit 1**, with exactly one finding:
```
::error::[register-drift] the generated site register on the page is not what the tree produces -- a read was added, deleted or moved, or a row key changed, and the region was not regenerated. First difference at register line 10:
committed : | `packages/objectql/src/engine-driver-options.ts#buildDriverOptions` | 1 | 24 |
census : | `packages/objectql/src/engine-driver-options.ts#buildDriverOptions` | 2 | 24 |
Fix: node scripts/check-system-context-census.mjs --write
```
</details>
<details><summary>Red demonstration: a keyed symbol MOVED to another
file, then regenerated with zero row edits (head 8b1b4d8)</summary>
`isPerfDisclosurePrincipal` (row 13) was moved from
`packages/observability/src/perf-timing.ts` into a new tracked file,
`perf-timing-moved.ts`. The page was not touched.
- Gate: **exit 1**, with `[register-drift]`, `[unresolved-symbol]`,
`[file-without-a-row]` and `[anchor-is-not-a-read-site]`, one each. The
drift line names `perf-timing-moved.ts#isPerfDisclosurePrincipal | 1 |
13`.
- `--write`: exit 0. The page diff is exactly one register line
(`-perf-timing.ts#…` / `+perf-timing-moved.ts#…`), and row 13 is
unchanged.
- Gate after `--write`: **exit 0**. The tree was restored afterwards,
with `git diff HEAD` empty and the status clean.
The same thing happened for real during this card. `origin/main` moved
`resolveOneHopRelated` into `engine-master-detail-related.ts`. The merge
conflicted only on the old anchor cell, which was resolved to the
bare-key side. The gate then went red (`[register-drift]`,
`[file-without-a-row]`, `[anchor-is-not-a-read-site]`), and `--write`
changed one register line (commit 4d2bbfe).
</details>
<details><summary>Self-test and gate on HEAD</summary>
```
check-system-context-census --self-test: all cases passed
check-system-context-census: OK — 153 elevation read sites in 22 packages across 83 files, living in 132 symbol(s); … 141 key(s) in 85 row(s) own every read, and the generated register (132 symbol(s)) matches the tree.
```
</details>
## Acceptance notes
- **Card acceptance.** A symbol move needs no hand edit of the table:
shown above, twice. `check-system-context-census` is a drift check over
generated output plus the row-ownership rules. Its anchor checks are
kept.
- **The register's read count is a tripwire, not a row requirement.**
`--write` clears a count change without touching the row, as the old
site-total generator did. The page and the gate header both say so.
Whether the row still describes the symbol is the reviewer's call.
- **Pinned keys still move by hand.** Seven rows pin a name that two
files share. A refactor that moves one of those files still means
editing the anchor. A new collision is refused by name the day it
appears (`[row-key-ambiguous]`).
- **Prose citations still move by hand.** Anchors in prose outside the
tables (for example the "What `isSystem` does not do" table,
`enforceAuth`, `stampSystemInsertOwner`) are citations. The shared
resolver still holds them.
- **Not merge-driver routed.** Like `tenant-audit-census.mdx`, the page
text-merges. A merge that leaves the region wrong is F's drift refusal
on the PR and in the queue, as happened here.
- **Stale comments outside this card's file surface (not edited here).**
Three comments now say something false:
- `.gitattributes` L97–108 says "nothing on it is generated".
- `scripts/regen-artifacts.mjs`, `NOT_DRIVER_MANAGED` row
`content/docs/permissions/**`, says it "is plain prose now". That row
also prescribes routing any generated page here;
`tenant-audit-census.mdx` is not routed either.
- `scripts/git-merge-regen.mjs` `MIXED_COMPARATORS` describes `--fix`
rewriting line anchors. That was already stale before this PR.
These are left to the seat.
## Verification
Readings on head `4d2bbfe38`, which includes the merge of `origin/main`
at `3ebac9f5f`:
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: **64** derived commands, every exit
captured before any pipe. All 64 exited 0. `check:pm-dispatch-gates` ran
its fast tier (1935 cases, 394s). Two families first exited 1 and 3
because the merge left `packages/spec`'s build older than its sources
(`check:docs`: json-schema older than src; `check:skill-examples`: exit
3, prerequisite). After `pnpm --filter @objectstack/spec build`, both
exited 0.
- `--ran`: `64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN` (a DERIVED
zero: all 64 recorded an exit code and none is 3).
- `pnpm exec eslint --no-inline-config
scripts/check-system-context-census.mjs`: exit 0.
- NOT MEASURED locally: CI's `Build Docs` and `Plan Test Core shards`
steps, which the derivation names as path-scheduled with no local
invocation. CI runs them on this PR.
Changeset: `skip-changeset`. The diff is a docs page and a repo-internal
gate script, and nothing publishes.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (eslint on the changed
script: exit 0; the full run is CI's)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset. (nothing
publishes: `skip-changeset`)
- [x] 6. `packages/spec` touched: n/a.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English, except the 维护者速读
block.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01ADHkmSHnQFa1PEAAXJ9hmc
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmSHnQFa1PEAAXJ9hmc)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 48ee8b6 commit 42ec951
2 files changed
Lines changed: 1038 additions & 175 deletions
0 commit comments