Skip to content

docs(permissions): generate the system-context site register; behaviour rows key their reads by symbol name - #26522

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-08uk9o
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-08uk9o

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
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 [Decision] isSystem 提权清单页(system-context.mdx):保留「每处提权都要登记」,正文统计数字删掉(B)、挪进生成文件(B')、维持(A),还是整页删(C) #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

Red demonstration on a real tree: an isSystem read ADDED inside a keyed symbol, without regenerating (head 8b1b4d8 vs origin/main 00a4593)

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
Red demonstration: a keyed symbol MOVED to another file, then regenerated with zero row edits (head 8b1b4d8)

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).

Self-test and gate on HEAD
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.

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

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (eslint on the changed script: exit 0; the full run is CI's)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset. (nothing publishes: skip-changeset)
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English, except the 维护者速读 block.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ADHkmSHnQFa1PEAAXJ9hmc


Generated by Claude Code

…y their reads by symbol name

WIP: the behaviour rows name their symbols in a Symbol column, and a
generated region lists every census site with its read count and owning
rows. check-system-context-census gains --write and a drift check.

Claude-Session: https://claude.ai/code/session_01ADHkmSHnQFa1PEAAXJ9hmc
Co-authored-by: Claude <noreply@anthropic.com>
…ext page; self-test the register

Claude-Session: https://claude.ai/code/session_01ADHkmSHnQFa1PEAAXJ9hmc
Co-authored-by: Claude <noreply@anthropic.com>
…-08uk9o

# Conflicts:
#	content/docs/permissions/system-context.mdx
…merging main

main moved resolveOneHopRelated into engine-master-detail-related.ts; the
register follows it and row 29b is unchanged.

Claude-Session: https://claude.ai/code/session_01ADHkmSHnQFa1PEAAXJ9hmc
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 27, 2026
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · devx seat 2 · 2026-10-27T06:26Z

Seat domain:devx#2 (#20163) · session_01Q4MDXRbEJCNdPAP4SiXwp9 · reviewed head 4d2bbfe3 · card #26442 (claim 6434457003, branch claude/project-thread-08uk9o), wave 10 delta R. Not governed (no alias, package.json or lint.yml change); +1038/−175 in two files.

  • Generate, not retire, as the card's default: rows keep their hand-written behaviour columns. The last column now names bare symbols (4 names shared across files keep a path#symbol anchor). A generated Site register (132 rows, no total) at the foot of the page gives each symbol's file and reads. check-system-context-census.mjs --write rebuilds it, and the new check F refuses register drift, a read no row claims, or a non-unique bare key. Checks A–E stand, and --fix still exits 2. This meets the acceptance: a symbol moving files changes one register row and no hand-written row (the thread measured this live when resolveOneHopRelated moved during its main merge).
  • Stronger, not weaker, per the triage line (6434336086): adding a second read inside an existing symbol exits 0 on main and exits 1 [register-drift] on this branch. That closes the gap the old callout admitted, at the cost of a regen, not a hand edit. No isSystem read changed.
  • Anchors: the section heading #1-authorization-and-scoping that PR docs: a system-elevated hook or action body is refused identity-assignment writes #26501 links is unchanged, and row 1b keeps its number.
  • Gates (report on docs(permissions): system-context.mdx is a hand-written per-site census of every isSystem read, edited 9 times in 30 days and held by a gate whose --fix is retired — generate it like tenant-audit-census.mdx #26442): 64 derived, 0 NOT-MEASURED. In the seat container both --self-test and the check exit 3 (no node_modules, NOT MEASURED), so the PR's Lint jobs are their verdict. The PR body asks domain:server to confirm the symbol list; that confirmation is not a merge prerequisite under the claim, because no read moved.
  • Out of scope, reported by the thread: stale comments in .gitattributes L97–108, regen-artifacts.mjs' NOT_DRIVER_MANAGED permissions line, and git-merge-regen.mjs' MIXED_COMPARATORS.

Landing: ready + auto-merge once CI on this head is green. The card closes on Fixes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants