Skip to content

refactor(plugin-security)!: delete the permission-set projection, the seed-name lookup and the refusal sink (C3 stage 7a) - #23368

Merged
os-zhuang merged 8 commits into
mainfrom
claude/issue-22975-7a-recut
Oct 14, 2026
Merged

os-zhuang merged 8 commits into
mainfrom
claude/issue-22975-7a-recut

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Refs #22975
Clause-②: no (narrowing)

C3 (#15204) stage 7a. It deletes the permission-set projection, the seed-name lookup and the seed refusal sink, with the suites that pin only them. Stage 8 left all three without a live caller, and #23308 (PR #23334) moved the one live behaviour the projection still carried, the environment kernel's security-catalog echo, into its own module. It is the first half of the stage-7 pair; 7b (#23302) has landed, and 8-post follows. #22975 stays open.

维护者速读(草稿)

改了什么

删除权限集「投影器」及其两个配套模块(种子名称查找、种子拒绝汇报),以及只测试它们的旧测试,共约 6,500 行(30 个文件,+350 / −6,539)。新增一个集成测试,证明「发布权限集不会再写出任何 sys_permission_set 行」。同时撤下 liveness 登记里指向已删文件的那一条,并为仍在使用的「权限集锁」补回一个被旧测试顺带覆盖的用例。

为什么改

投影器原本把每个权限集定义复制成一行 sys_permission_set 记录。阶段 8 已经下线了这张表,投影器从此没有调用方,只是死代码。它身上唯一还在用的功能(环境内核上让安全目录找到元数据门写入的权限集和岗位)已由 #23308 搬到独立模块。所以现在可以干净地删掉。

风险与代价(含回滚)

  • 对运行中的部署:没有行为变化。被删的代码在阶段 8 之后已不被调用。新测试在全新数据库上实测:包发布、可写基础包直接发布、重启后的启动流程,都留下零行。
  • 对外部代码:@objectstack/plugin-security 去掉了 11 个导出(函数与类型)。如有人直接导入它们,升级时会编译失败。changeset 逐个写明了替代方案(大多是「直接删掉调用」)。
  • 回滚:整体 revert 本 PR 即可,没有数据迁移。

席位意见

你要做的

用 os-zhuang 或 hotlong 批准本 PR(Tier H,按行数超过 3,000)。批准同时确认:本 PR 改写了 #23258 尚未发布的更新说明 .changeset/23233-permission-echo-names-runtime-package.md 里的一句(「仍是 managed_by: 'admin' 的 sys_permission_set 行」改为「不再写任何 sys_permission_set 行」),因此 Check Changeset 按设计保持红(它不在合并队列的五个必查项里)。

What this changes

  • Deleted modules: permission-set-projection.ts (the ADR-0094 projector, its data-door write-through and its boot reconcile), seed-name-lookup.ts and seed-refusal-sink.ts, with their exports from the package entry and the projection's ADR-anchor JSON.

  • Deleted suites, each pinning only deleted code:

    • permission-set-projection.test.ts
    • permission-set-projection-heal-by-id.test.ts
    • packaged-permission-set-restore-leg.test.ts
    • permission-set-duplicate-name-refusal.test.ts
    • seed-name-lookup-degradation-audible.test.ts

    The plugin-security: permission sets an org owns (or that live in a writable runtime package) are reported and enforced as "locked by the code package" — the lock reads the package id without the row's provenance #21789 echo-stamp cases of the old projection suite are not kept here: they already live in security-catalog-echo.test.ts (fix(plugin-security): an environment kernel's security catalog serves permission sets and positions saved through the metadata door again (S8 regression) #23308), which pins the echo where it now lives.

  • Re-pinned: packaged-permission-set-lock.test.ts moves onto the metadata door's lock gate. That includes the classifier's refusal of a SchemaRegistry that answers with a non-list (packaged-permission-set-lock.ts, the Array.isArray branch). The old pin 5 was its only direct pin; it now sits beside the throwing-registry case, and it measured red with the branch ablated.

  • Re-homed: tryFind becomes a private helper of packaged-permission-set-overlay-detection.ts, its one live caller, and that reading's logger option asks only for warn.

  • The security middleware loses the writesExecutedByDataDoor stand-down, which only the retired data door could arm.

  • The epic's zero-row acceptance (6129253247): packages/cli/test/permission-set-zero-row.integration.test.ts. On a fresh SQLite file, on both the host kernel and an environment kernel, it runs:

    • a package-door publish (a draft, then publishPackageDrafts);
    • a writable base's direct publish;
    • a second boot over the stored definitions.

    The rows are counted from the file through the test's own connection, so a table that does not exist counts as zero. A control writes one row and reads it back as one.

  • Liveness (Q3 → B): the permission-set-projection HIGH_RISK_CLASSES entry in packages/spec/scripts/liveness/proof-registry.mts is removed, together with its README mirror row. Its proofRef pointed at a dogfood file that no longer exists, and no @proof tag carries it. The permission.tabPermissions entry no longer cites the deleted file.

  • A pending changeset this PR makes false: .changeset/23233-permission-echo-names-runtime-package.md no longer says the set keeps a managed_by: 'admin' row. No row is written once the projector is gone.

  • D's transitional text: the header of package-uninstall-revokes-named-grants.dogfood.test.ts no longer describes a projector row. The authorization.mdx sentences were already removed on main by docs(permissions): the hand-written pages describe the security catalog main serves, not the four retired row objects #23322, so nothing is left to change there.

  • Generated, regenerated with the repo's own tools:

    • the tenant-audit census page and counts (node scripts/tenant-audit-census.mjs --write; main 226 → 223 sites), with the prose figures the gate holds to the census updated;
    • scripts/engine-double-contract.pinned.json (--write);
    • the entries of the deleted suites removed from engine-double-contract.baseline.json and objectql-double-limit.baseline.json. Both gates name that removal; neither has a write flag.
  • Changeset: @objectstack/plugin-security minor (a narrowing on the v18 prerelease line). The ADR-0087 marker is not-required (runtime-export-retired …) for the 11 retired exports, each with its FROM → TO. @objectstack/spec patch.

Acceptance notes

  • The zero-row result already held on main after S8. On S8's parent 99e944daf2, measured with this same test file, every leg goes red on both kernels:

    • after the two publishes, expected 2 to be +0 (the projector's admin rows);
    • with those rows deleted, the boot over the stored definitions re-creates both (expected 2 to be +0 after the boot).

    That tree is the writer put back.

  • Not here:

    • ADR-0094 still names the projector. That is governed text, and the seat files the card at this PR's landing.
    • The ADR anchor sweep is green (check:adr-symbol-anchors exit 0), so no ADR line anchors into a deleted file and no anchor fix was needed.
    • For 8-post's comment sweep: the prose that still names the deleted modules:
      • the spec docblocks permission.zod.ts:824, metadata-plugin.zod.ts:1166 and error-code-ledger.zod.ts:1270;
      • scripts/check-durability-degradation-log-level.mjs:156;
      • the source of docs/qa/platform-checklist/areas/access-security.json:2649;
      • the surviving code comments in plugin-security.
    • For the seat's governed ADR card at landing: ADR-0094 (it names the projector throughout); ADR-0126 "Three real mechanisms"; ADR-0131 lines 59 and 993.
  • A deliberate correction of a pending release note, for confirmation: .changeset/23233-permission-echo-names-runtime-package.md (added by fix(plugin-security): the permission-set echo names its runtime package, so an isDefault set records its everyone suggestion #23258, not by this PR). Its "Unchanged" item said the set keeps a sys_permission_set row (managed_by: 'admin', no package_id). After this PR no row is written, so that sentence would publish a false statement. The seat's patch round (6160244399, item 3) asks for the correction. check-empty-changeset stays red on it by design, and this note is the human path that gate names.

Verification

Full battery, on accde78fe6 (main 18cd410dc4 merged in). Since then the code has changed only by main merges and this round's patch, which touches no runtime code path.

  • Full build: 75/75.
  • plugin-security typecheck: exit 0.
  • plugin-security suite: 185/185 files, 3,772 passed.
  • CLI integration tier: 125/125 files, 1,093 passed. This includes the zero-row test.
  • Dogfood: 257 files passed, 2,176 passed.
  • The derived sweep: 120 derived, 119 run, all exit 0. --ran reconciles with 0 UNRUN and 1 NOT-MEASURED: check:platform-checklist, exit 1 with the same 8 reds as main.
  • check:pm-dispatch-gates: exit 0.

The patch round, scoped, on ddcdb46bd4 (main 6eb6f7d65b merged in once, just before the push):

  • The spec liveness and proof-registry tests: 81/81.
  • check:liveness: exit 0.
  • The plugin-security lock suites (packaged-permission-set-lock*.test.ts): 2 files, 15 passed. The new non-list pin measured red with the branch ablated.
  • plugin-security and spec typecheck: exit 0.
  • The gates derived for the three changed paths, the three anchor sweeps among them: 72 derived and run. --ran reconciles with 0 NOT-MEASURED and 0 UNRUN. 71 exit 0. check-empty-changeset exits 1, deliberately: see the next point.

The ablation: see Acceptance notes (S8's parent 99e944daf2, red on both kernels).

objectui pinned sibling: NOT MEASURED. It is not checked out here, and the Console Pin Gate runs only in the full battery (#22998), so it is skipped on a PR. Nothing the console imports should move: the entry loses only the 11 projector names (permissionSetRowFields, permissionSetBodyFromRow, upsertEnvPermissionSet, projectPermissionMutation, registerPermissionSetProjection, createPermissionSetWriteThrough, reconcilePermissionSetProjection, and the types PermissionSeedOutcome, ProjectionDeps, ProjectionReconcileOutcome, WriteThroughDeps), and no package in this repository imports any of them.


Generated by Claude Code

…seed-name lookup and the refusal sink with the suites that pin only them (ADR-0131 D3, C3 stage 7a; held)

Carried over from the C3 build-ahead onto main after stage 8. Stage 8
stopped registering the sys_permission_set row object, the permission
mutation projector and the data-door write-through, so these modules
have no live caller left:

- permission-set-projection.ts, seed-name-lookup.ts and
  seed-refusal-sink.ts are deleted, with their index exports and the
  projection's ADR anchor;
- tryFind becomes a private helper of the overlay reading, its one live
  caller, and the reading's logger option asks only for warn;
- the security middleware loses the writesExecutedByDataDoor stand-down,
  which only the retired data door could arm;
- the suites that pin only the deleted modules go, and the packaged-set
  lock's pins move onto the metadata door's lock gate.

Held, not ready for review: permission-set-echo-package-owner.test.ts
still imports the projection. It is the only pin of the owner-carrying
echo, which lets a runtime package's default permission set keep its
owner and record its everyone suggestion on an environment kernel; stage
8 unwired that behaviour with the projector.
How that behaviour is carried is a decision owed before this deletion
lands. The census, ledgers, liveness registry and the epic's zero-row
acceptance are not done yet.

Claude-Session: https://claude.ai/code/session_01287UbjvrigpWwNfGDmdwpS
Co-authored-by: Claude <noreply@anthropic.com>
…ssue-22975-7a-recut

# Conflicts:
#	content/docs/permissions/system-context.mdx
#	packages/plugins/plugin-security/src/permission-set-projection.ts
#	scripts/tenant-audit-census.mjs
…rs and liveness row follow the projector's deletion

A CLI integration test pins the C3 epic's zero-row acceptance on both
kernel shapes: on a fresh database, a package-door publish, a writable
base's direct publish and a boot over the stored definitions leave no
sys_permission_set row, counted from the file through the test's own
connection; a control proves the counter sees a row a writer leaves.

The tenant-audit census and its counts are regenerated with the census
script, with the prose figures it holds to the census updated; the
engine-double ledger is regenerated, and the two baselines drop the
entries of the deleted suites. The liveness README's unbound
permission-set-projection proof row retires with the invariant, and the
uninstall dogfood header no longer describes a projector row.

Claude-Session: https://claude.ai/code/session_01287UbjvrigpWwNfGDmdwpS
Co-authored-by: Claude <noreply@anthropic.com>
…ssue-22975-7a-recut

# Conflicts:
#	content/docs/permissions/tenant-audit-census.mdx
#	docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Oct 14, 2026
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-security, @objectstack/spec, touching 72 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/plugins/plugin-security/src/index.ts, packages/spec/liveness/README.md, packages/spec/liveness/permission.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json e4ec6ec1a5eff818d69e5bdf1ae031f37fec64bd.

⛔ 12 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/plugins/plugin-security/src/index.ts, packages/spec/liveness/README.md, packages/spec/liveness/permission.json) — pages documenting those are invisible to this run
  • 12 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 145 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e4ec6ec1a5eff818d69e5bdf1ae031f37fec64bd → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c9223036787f188bf64d25bddffd880644d17942 — the merge of head 31262798277b1394e4be1401e6a6f5fad974f67c into base e4ec6ec1a5eff818d69e5bdf1ae031f37fec64bd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c9223036787f188bf64d25bddffd880644d17942 && git checkout c9223036787f188bf64d25bddffd880644d17942
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e4ec6ec1a5eff818d69e5bdf1ae031f37fec64bd 31262798277b1394e4be1401e6a6f5fad974f67c && git checkout -B drift-repro e4ec6ec1a5eff818d69e5bdf1ae031f37fec64bd && git merge --no-ff 31262798277b1394e4be1401e6a6f5fad974f67c

node scripts/docs-audit/affected-docs.mjs --json e4ec6ec1a5eff818d69e5bdf1ae031f37fec64bd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs e4ec6ec1a5eff818d69e5bdf1ae031f37fec64bd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: accde78fe683b6de16a301941ede9a5052f09c9c
Local-runs: none

PR #23368 (claude/issue-22975-7a-recut, draft, opened 2026-10-14T02:23Z), #22975 slice 7a. Read: the PR body with its ## 维护者速读(草稿), the 28-file list, the three-dot diff against origin/main (merge base 18cd410dc4; +336 / −6,516), the merge ref refs/review/pr-23368-merge = 007555b320 (parents dc393266fd and the head), the 41 check-runs on the head, the nine card comments the brief names, #23308 and PR #23334 (6d96721faa, an ancestor of the head), and ADR-0131, ADR-0087 and ADR-0094 on origin/main 713f231854. No 7a os-dev-report is on #22975: at 2026-10-14T03:24Z the newest comment after the resume dispatch 6157825993 is the dispatch itself. The body says the head's full re-run is "in progress" and its counts "go in the os-dev-report", so the head is weighed on the body and the check-runs, as the brief allows. Every local figure quoted from the body (suite counts, dogfood, the gate sweep, the ablation) is the dev's unposted claim on bb4760b9ba, not on this head; nothing here was re-run.

① Derived judgments

Dead before deletion — holds. On origin/main, outside the three modules themselves, the only code importers are: index.ts (the re-exports, removed), packaged-permission-set-overlay-detection.ts (tryFind, ProjectionLogger; re-homed), and the tests this PR deletes or re-pins (packaged-permission-set-lock.test.ts, packaged-permission-set-overlay-detection.test.ts for permissionSetRowFields, engine-find-bare-array.pin.test.ts for buildExistingByName). security-plugin.ts imports nothing from them since S8; security-catalog-echo.ts (#23308) imports only organization-scope.js and packaged-permission-set-lock.js. The 11 entry exports are used by nothing outside plugin-security in any package (only comments, pending changesets and docs name them). On the head tree no import of permission-set-projection.js, seed-name-lookup.js or seed-refusal-sink.js remains anywhere; Type Check · workspace is green. tryFind is re-homed as a private helper of the overlay detection (same catch → [] contract, reading as the system through seedCtx()), with a new pin in its test that a refused read answers nothing and that both type spellings are asked under the page cap. ProjectionLogger is not re-homed: OverlayDetectionOptions.logger now asks for { warn } only; that is a published type (index.ts exports OverlayDetectionOptions), and the change widens what a caller may pass (every old ProjectionLogger still satisfies it), so nothing compiled against it breaks. The writesExecutedByDataDoor stand-down removed from the security middleware had no .add caller left on main (only the declaration and the .has read), so its removal changes no behaviour. objectui pinned sibling: NOT MEASURED — see ③.

The package entry — disposition present and right; level right. The entry loses 7 runtime exports (permissionSetRowFields, permissionSetBodyFromRow, upsertEnvPermissionSet, projectPermissionMutation, registerPermissionSetProjection, createPermissionSetWriteThrough, reconcilePermissionSetProjection) and 4 types (PermissionSeedOutcome, ProjectionDeps, ProjectionReconcileOutcome, WriteThroughDeps): 11, as the body says. .changeset/15204-7a-permission-set-projection-retired.md carries the adr-0087 not-required (runtime-export-retired …) marker naming all 11 as @objectstack/plugin-security#Name, the ADR-0087 seventh category (addendum 2026-10-12), with a why that states no metadata moves. Each retired name has an arrowless - FROM … TO … line, the shape the addendum prescribes; the one non-trivial TO, permissionSetBodyFromRow → permissionSetDefinitionFromRow from @objectstack/core, resolves (packages/core/src/security/permission-set-row-definition.ts, re-exported through security/index.ts and the root). The level is minor with the BREAKING banner, the ! in the PR title and Clause-②: no (narrowing); under the WHICH LEVEL rule and the launch-window convention in pr-automation.yml (breaking-ness carried by the banner plus the ADR-0087 disposition, major refused by check-changeset-no-major) that is the right grade. Check Changeset is green on the head. @objectstack/spec is patch — right for a liveness-evidence edit — but see the liveness item below: the changeset's spec sentence is not yet true.

The deletion clause — holds for the five deleted suites; two sub-cases of still-live code are left without a direct pin (noted, not blocking).

The zero-row acceptance — pinned; the ablation is a measurement on another tree, reported only in the body. packages/cli/test/permission-set-zero-row.integration.test.ts boots the host shape and the environment shape (ObjectQLPlugin({ environmentId, registerProtocol: false }) + createMetadataProtocolPlugin, the real MetadataPlugin, AuthPlugin, SecurityPlugin) over a fresh SQLite file; does a package-door draft + publishPackageDrafts and a writable base's mode: 'publish' save; asserts non-vacuity (both names stored as active env-wide sys_metadata rows); counts sys_permission_set through the test's own connection (missing table = 0); reboots over the stored definitions and counts again; and a control creates the table, writes one row and reads one. That is the epic's three legs (6129253247, adopted 6129999436) on both kernels. The "writer put back" is S8's parent 99e944daf2, where the body reports both kernels red (expected 2 to be +0 after the publishes, and again after the boot). That is a legitimate red with the writer present, but it is a dev measurement in the PR body, not in an os-dev-report, and not re-runnable here. CI ran the file: with OS_TEST_TIERS: queue the packages/cli population is every file not named .e2e/.live, and the integration project collects it by its signals; the six Test Core shards are green on the head.

The liveness registry — NOT retired. This is the blocking finding. The entry Q3 → B retires at 7a (seat review 6124054616: "the liveness registry's permission-set-projection entry … its dangling proofRef is invisible to every gate") lives in packages/spec/scripts/liveness/proof-registry.mts (HIGH_RISK_CLASSES, lines 202–222 on main): id: 'permission-set-projection', proofRef: 'packages/qa/dogfood/test/showcase-permission-projection.dogfood.test.ts#permission-set-projection' — a file that does not exist on main — with a summary that still describes the data-door write-through and the awaited projection as present machinery. The diff does not touch that file. It removes only the README's mirror row (packages/spec/liveness/README.md line 531) and the tabPermissions evidence pointer in permission.json. The README states twenty lines above that row that "proof-registry.mts is the source of truth; the tables below mirror it", so after this PR the mirror contradicts its source. The dispatch's spelling "the entry in packages/spec/liveness/permission.json" was imprecise — permission.json has no such entry, only the evidence citation that 7a did fix — but the seat review and the body's own words ("the README's registered-but-unbound … proof row retires with the invariant") make the intent plain, and the "registered" half is still true: it is still registered. Nothing else holds the id (no @proof tag under packages/qa/dogfood/test/, no ledgerBindings, no fixture in packages/spec/scripts/liveness/), and proof-registry.test.ts validates proofRef only for bound classes, so deleting the entry is a one-hunk change under the @objectstack/spec patch already declared; the changeset's spec sentence and the body's liveness bullet then become true. Spec property liveness is green on the head exactly because the dangling ref is gate-invisible — which is why the seat put it on 7a's acceptance.

D's transitional text — removed; known-limitation item 3 is stated nowhere in the repo after this PR. content/docs/permissions/authorization.mdx on main carries no transitional managedBy:'admin' sentence: #23322 (4d4040ccb6) removed the "Until stage 7 retires the ADR-0094 projector …" Package-door text and the admin-door bullet's transitional row; the current Package-door bullet reads "no row is written for it", the admin door "No row mirrors it". The body's claim that nothing was left to change there is true. The dogfood header of package-uninstall-revokes-named-grants.dogfood.test.ts now says no seeder, materializer or projector writes a row. Item 3 (6139333945 on #15204; ruling 2 in 6145194655) is stated in that GitHub comment, in #23227's card text, and in the pending changeset .changeset/23233-permission-echo-names-runtime-package.md ("Unchanged. The sys_permission_set row (still managed_by: 'admin', with no package_id)"); all three become false at landing — see ③.

Regenerated files — consistent with the diff, and CI-verified on the merge tree. Census: the three permission-set-projection.ts sites (insert object, update object, delete sys_permission_set) leave; 226 → 223, decidable 153 → 152, undecidable 73 → 71, UNTYPED_RECEIVERS-placed 6 → 5, object: string-placed 7 → 5, and the prose figures in tenant-audit-census.mdx follow; the UNTYPED_RECEIVERS row in scripts/tenant-audit-census.mjs is removed. check-tenant-audit-census runs in Lint · repo & tooling gates — green. The stamp reads bb4760b9b, the pre-merge commit, and the gate passed on the merge with dc393266fd, so the counts hold on the tree CI judged. engine-double-contract.pinned.json loses the rows of the lock suite's gone engine double (3), the restore leg (2), the duplicate-name suite (2) and the projection suite (1); the baseline loses the projection suite's two DEBT rows; objectql-double-limit.baseline.json loses the restore leg and the projection suite — each a deleted suite or the re-pinned one. Those gates run in Lint · ESLint (check:engine-double-contract) and Lint · CI, release & engine gates (check:objectql-double-limit) — green. system-context.mdx row 11 (the projection middleware skip, anchored into the deleted file) is removed; the table already tolerated gaps (no row 14). scripts/measure-durability-swallow-family.mjs drops its POSITIVE_CONTROLS entry for the file (leaving one stray blank line). The ADR-anchor JSON for the file is deleted; the three symbol-anchor sweeps run in Lint · docs & code guards — green, so no ADR line anchors into the deleted file, as the body says.

Published text the change makes false. Corrected here: system-context.mdx row 11, the tabPermissions evidence, the dogfood header, the census pages. Carried by name in the body: ADR-0094 (its "Implementation:" line 398 and the suite at line 188 still name the files; its status line already records the stage-8 retirement via #23362), and "surviving comments" to 8-post's sweep. Not corrected and not named: ADR-0126 (the "Three real mechanisms" list cites permission-set-projection.ts for the #4669 row-state ruling), ADR-0131 (the Consumers line 59 and the dated code list at line 993); the spec docblocks permission.zod.ts:824, metadata-plugin.zod.ts:1166, error-code-ledger.zod.ts:1270; scripts/check-durability-degradation-log-level.mjs:156; and the platform-checklist item whose source string names the file (docs/qa/platform-checklist/areas/access-security.json:2649). #23362's diff touched none of these lines. All are prose pointers; none is gate-bearing. They belong with the seat's ADR-0094 card or 8-post's sweep — see ③.

The 维护者速读(草稿), statement by statement. True: the scope and size (28 files, +336 / −6,516; about 6,500 lines); the new integration test and its three legs on a fresh database; "stage 8 left the projector without a caller" (true of the code; the one live behaviour, the echo, is correctly attributed to #23308); "no behaviour change for a running deployment" (the stand-down removal is unreachable code, the three modules unwired); 11 exports removed, compile failure for a direct importer, a FROM → TO per name; whole-PR revert with no data migration; Tier H by line count; the seat-opinion part empty. Not true in the body outside the 速读: (1) "objectui pinned sibling … CI's Console Pin Gate covers it" — the console-pin job's if: excludes pull_request and merge_group events (full battery only, #22998), and on this head it is skipped; it covers the import after landing, not before, and nothing in this PR's checks measured objectui. (2) "the README's registered-but-unbound permission-set-projection proof row retires with the invariant" — the README row is gone; the registration is not. The 速读's own sentences are all true of the diff.

Check-runs on the head (the gate verdicts). 41, all completed by 2026-10-14T02:56Z; none running. 36 success: the six Test Core shards and the aggregate, Build Core, Build Docs, every Lint · and Type Check · job, Check Changeset, Check PR Size, Spec property liveness, Governed Surface Queue Guard, Part-of PR must not also close its card, the claim and single-writer checks, Check Documentation Links, Auto Label, Flag docs affected by code changes, filter. 5 skipped, all full-battery-only: Console Pin Gate, Dogfood Verify CLI, Temporal Conformance, the Dogfood Regression Gate shards (the aggregate reads success with its matrix skipped), Packed-tarball smoke. No commit statuses. The body carries no closing keyword (Refs #22975; the commits carry none either).

② Semver level

@objectstack/plugin-security: minor, with the BREAKING banner, the ! title and the adr-0087 not-required (runtime-export-retired …) marker over all 11 names — the launch-window grade for retiring published runtime exports (WHICH LEVEL in pr-automation.yml; major refused by check-changeset-no-major; breaking-ness carried by banner plus disposition, not level). @objectstack/spec: patch — the right level for the liveness edits, once the registry entry is actually removed. No level is wrong; the spec sentence in the changeset is currently a description of work not done.

③ Boundary flags

  • Blocking (acceptance Q3 → B): packages/spec/scripts/liveness/proof-registry.mts still carries the permission-set-projection class with its dangling proofRef; only the README mirror row was removed. One hunk, no other file names the id; re-review the new head.
  • Body correction required: the objectui pinned-sibling sentence names a check that does not run on pull requests. The honest line is: not measured on this head; main's full battery runs Console Pin Gate after landing; the retired names are server-side plugin functions and types. objectui itself: NOT MEASURED — no sibling checkout exists here, and the REST door to objectstack-ai/objectui is not enabled for this session.
  • No os-dev-report for the head. The body's verification is on bb4760b9ba (with 6d96721faa merged in); for accde78fe6 it promises the counts later. Dogfood (256 files) and the derived gate sweep (120 derived, 119 run) are local claims, since PR CI runs no dogfood (skills(ci): two-speed CI — fast set on PR and merge-queue runs, full battery hourly on main and before a release, main-red stops the lane, four required contexts plus the guard, timing gates report-only, test budgets not deletions #22998). The seat decides whether to wait for the report before the maintainer is asked.
  • Merge ref drift: refs/review/pr-23368-merge merges with dc393266fd; origin/main is six commits on at 713f231854. None of the six touches the PR's surface (overlap: content/docs/permissions/authentication.mdx only). GitHub reports mergeable: clean.
  • Known-limitation item 3 at landing: 6139333945 on refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write under single and refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 (the epic PM's), [Decision] 阶段 7a 之前,包发布的权限集在 sys_permission_set 里多出一行标成「管理员」的记录:现在另开小 PR 给投影器加判断挡掉(A),还是等 7a 整个删掉投影器(B) #23227's card text, and the pending .changeset/23233-permission-echo-names-runtime-package.md "Unchanged … still managed_by: 'admin'" line all become false the moment 7a lands in the same next prerelease. The changeset line is the one in the repo; the seat may want it corrected as a pending-note correction, or named in 7a's body.
  • Prose that names the deleted file, not carried by name: ADR-0126 "Three real mechanisms", ADR-0131 Consumers (line 59) and code list (line 993), the three spec docblocks, check-durability-degradation-log-level.mjs:156, access-security.json:2649 source. Suggest the seat's ADR-0094 card at landing takes the ADR lines, and 8-post's comment sweep the rest.
  • Pin gap, non-blocking: the classifier's non-list registry answer (listItems returning a non-array → unknown → refused) loses its only direct pin with old pin 5; the throw variant stays pinned in both the lock suite and the lock-gate suite.
  • Ablation shape: the "writer put back" is S8's parent tree, not an in-tree writer on the head; acceptable as a measured red, and the control case makes the counter non-vacuous, but it rests on the body's word.
  • Out of scope, pre-existing: packages/spec/liveness/permission.json line 20 (permission.description evidence) still cites the S8-retired sys-permission-set.object.ts; .changeset/23233-* as above; check:platform-checklist is maintainer-run by hand, not CI (lint.yml 2607–2612), so its "8 pre-existing reds" is the dev's reading.
  • NOT MEASURED here, by the read-only shape: the local suite counts, the dogfood run, the gate sweep, the ablation on 99e944daf2, the platform checklist, the objectui imports.

Adopted by domain:engine seat 2 at 2026-10-14T03:29Z, after a tool-use audit of the isolated reviewer's transcript. The reviewer made reads, git fetch into review refs, REST GETs, and record writes only. It made no GitHub write and ran no build, test or gate. The seat takes the verdict as FAIL and sends one patch round:

Implemented-by: claude/issue-22975-7a-recut
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: FAIL

…ry entry retires with its README row; the lock's non-list registry answer keeps a pin

The proof registry still carried the permission-set-projection
HIGH_RISK_CLASSES entry, pointing at a dogfood file that no longer exists;
only its README mirror row had been removed. The entry is removed, so the
README matches its source.

The classifier's refusal of a SchemaRegistry that answers with a non-list
lost its only direct pin with the projection suite. It is re-pinned on the
metadata door beside the throwing-registry case, and measured red with the
branch ablated.

The #23233 changeset no longer says the set keeps an admin
sys_permission_set row: no row is written once the projector is gone.

Claude-Session: https://claude.ai/code/session_01287UbjvrigpWwNfGDmdwpS
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ddcdb46bd44584fac99502c1b49c92d224696a7b
Local-runs: none

Delta review of PR #23368 (claude/issue-22975-7a-recut, draft, #22975 slice 7a) at the head the dev pushed after the FAIL record 6160232724. Read: the previous record and the seat's adoption note in it; the patch round 6160244399 and the amendment 6160721338 on #22975; the dev's os-dev-report 6161002759 (posted 2026-10-14T04:37Z, head: ddcdb46bd4); the PR body (9,428 bytes, updated 2026-10-14T04:36Z) with its ## 维护者速读(草稿); the delta accde78fe6..ddcdb46bd4 (three commits: the merge 5f852f064f of main 28e294f450, the round's own commit df605286ca, the merge ddcdb46bd4 of main 6eb6f7d65b); the three-dot net diff against origin/main (merge base 6eb6f7d65b; 30 files, +350 / −6,539); the 48 check-runs on the head through REST (polled until the last completed), the two failing jobs' step lists and annotations; scripts/check-empty-changeset.mjs, scripts/check-required-contexts.mjs, pr-automation.yml, ci.yml and lint.yml on the checkout. Nothing was built, run or re-run here; every local count below is the dev's, quoted as such.

The delta on the PR's own surface is the round commit alone. Restricted to the PR's 30 files, accde78fe6..ddcdb46bd4 is df605286ca: .changeset/23233-permission-echo-names-runtime-package.md (−1 / +1), packages/plugins/plugin-security/src/packaged-permission-set-lock.test.ts (+13) and packages/spec/scripts/liveness/proof-registry.mts (−22). The two main merges touched none of the PR's files: the set of files main changed between the old merge base 18cd410dc4 and the new one 6eb6f7d65b intersects the PR's file list in nothing, and neither merge commit differs from its branch parent on any PR path, so there was no conflict to resolve. Everything else the previous record judged is byte-identical at this head.

① Derived judgments

1. Item 1, the liveness registry — retired, with nothing dangling. packages/spec/scripts/liveness/proof-registry.mts on the head carries 41 HIGH_RISK_CLASSES ids and permission-set-projection is not among them; the 22-line entry (lines 202–222 on main, with its proofRef into the non-existent showcase-permission-projection.dogfood.test.ts) is gone whole, and the hunk leaves the file well-formed (check-liveness.mts imports it, and Spec property liveness is green on this head). A whole-tree grep of the head for permission-set-projection finds the string nowhere in packages/spec/liveness/README.md (the mirror row went in the previous round; the README's line 500 "proof-registry.mts is the source of truth; the tables below mirror it" is true again), nowhere in proof-registry.test.ts, orphans.mts, key-mention.baseline.json or undrilled-containers.baseline.json, in no @proof tag under packages/qa/dogfood/test/, in no ledgerBindings, and in no ledger proof field. The ledgers name the deleted file only in two prose strings of packages/spec/liveness/permission.json: line 158's tabPermissions history note (a record of a 2026-08 re-anchoring, not a citation) and line 20's description evidence, which is a separate finding in ③. proof-registry.test.ts checks proofRef only for bound classes and that every class proofId is in KNOWN_PROOF_IDS, so removing an unbound class is one hunk and nothing else moves; the dev reports the spec liveness and proof-registry tests 81/81 and check:liveness exit 0, and Spec property liveness is green on this head (2026-10-14T04:36Z). The changeset's spec sentence ("the README's registered-but-unbound … proof row is retired") now under-describes a true state rather than describing work not done.

2. Item 6, the non-list registry pin — re-homed, and it would go red with the branch gone. packaged-permission-set-lock.test.ts gains an eighth case beside the throwing-registry sibling: listItems answers undefined; the classifier is asserted { status: 'unknown' }; then through registerPackagedPermissionSetLockGate with the layered read throwing, the save rejects with PackagedPermissionSetProvenanceUnknownError, resolves to 403 NOT_OVERRIDABLE, and the diagnostic message contains schema registry returned no list of declared permission sets. Read against packaged-permission-set-lock.ts on the head: the Array.isArray(items) else-branch at line 227–232 pushes exactly that failure string and leaves anySourceAnswered false; the layered probe's status === 'failed' adds its own failure; the verdict is unknown with the reasons joined, and assertPermissionSetNotPackageDeclared throws the provenance-unknown error on unknown. Ablating the branch the natural way (treating a non-list as an answered empty list) makes anySourceAnswered true, so the classifier answers { status: 'org' } and the first assertion fails, and the gate resolves instead of throwing, so the second fails too; ablating only the failures.push keeps unknown but the reason becomes no artifact source available …, so the third assertion fails. The pin is red under both shapes, and it is on live code the throw-variant case does not reach (threw is a different path). The dev measured the first shape: 1 failed, 7 passed. The two lock suites now hold 8 + 7 = 15 cases, matching the reported "2 files, 15 passed". The engine-double gate that scans this file (check:engine-double-contract, in Lint · ESLint) is green on the head.

3. Item 4 in-repo, the pending-note correction — exact, true, red by design, advisory. The diff of .changeset/23233-permission-echo-names-runtime-package.md is one line: the "Unchanged" item's "The sys_permission_set row (still managed_by: 'admin', with no package_id), the packaged-permission-set lock and every edit door" becomes "The packaged-permission-set lock and every edit door. (No sys_permission_set row is written for the set: C3 stage 7a retires the last writer.)" Frontmatter, level (patch), title and every other sentence are byte-identical. It is true: the note was added by #23258 (09ef77c7d8, 2026-10-13T13:12Z) and was true when written; S8 (#23222, 66949b7729, 2026-10-13T14:14Z) stopped registering the projector an hour later, so no row has been written on main since then (the body's own acceptance note: the zero-row result already held after S8), and 7a deletes the writer. The parenthetical's "retires the last writer" is the deletion, not the moment the rows stopped; the sentence is still right. Check Changeset is red on it by design. Both runs of PR Automation on this head (38512210622 on synchronize, 38512258390 on edited, both pull_request events) fail at step 12 only, "Reject an empty-frontmatter changeset added by this PR", which runs scripts/check-empty-changeset.mjs --self-test then --base MERGE_BASE; the run's annotation on the file reads, verbatim, "exists on the merge base and was not added by this PR, so changing or deleting it silently replaces somebody else's release note (#17712)" followed by the two-class text whose second class is "DELIBERATE CORRECTION — your change may have made this PENDING release note false, and you rewrote it in the same stroke. Remedy: do NOT restore it — say so on the PR and get it confirmed". That is the foreign-changeset rule (option A' on #17712, ruled 2026-09-13; the two-class text is ruling D, #18160) and this PR is its second class. The gate script and the workflow are unchanged since the previous head, where the same step was green, so the self-test half is not what went red. Steps 11, 13, 14 and 15 of the same job ran and passed (the #20784 !cancelled() design): the ADR-0087 disposition was read on this head and the annotation records the runtime-export-retired exemption on the 7a changeset. It is not a required queue context. scripts/check-required-contexts.mjs REQUIRED_CONTEXTS names five: Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard (AGENTS.md says the same five block the queue, and that a check outside them is advisory and rides through). Check Changeset lives in pr-automation.yml, whose only trigger is pull_request, so it publishes no context on a merge-group run at all. What the human confirmation must cover, from the gate's own text ("say so on the PR, naming the note and what changed under it, and get it confirmed"): that .changeset/23233-permission-echo-names-runtime-package.md is a pending, unreleased note added by #23258 that this PR corrects deliberately, not a filename collision (the PR's own note is 15204-7a-permission-set-projection-retired.md); that the one sentence changed is the "Unchanged" item's claim of a surviving managed_by: 'admin' row, replaced by the statement that no row is written; that the claim is false because of S8 and 7a together and the replacement is what the zero-row test pins; that nothing else in the note moves; and that the Check Changeset red stands on this PR to landing, which the queue ignores. The body's "Acceptance notes" bullet says all of that in English; the 速读's 你要做的 does not (③).

4. Item 2 and the body — stated as asked; one stale figure. The body now reads "objectui pinned sibling: NOT MEASURED. It is not checked out here, and the Console Pin Gate runs only in the full battery (#22998), so it is skipped on a PR", and lists the 11 retired names and that no package here imports them; ci.yml's console-pin job carries github.event_name != 'pull_request' && github.event_name != 'merge_group' and the check is skipped on this head. The 8-post sweep items are named in "Acceptance notes": permission.zod.ts:824, metadata-plugin.zod.ts:1166, error-code-ledger.zod.ts:1270, scripts/check-durability-degradation-log-level.mjs:156, the source of access-security.json:2649, and "the surviving code comments in plugin-security". The ADR-card items are named: ADR-0094, ADR-0126 "Three real mechanisms", ADR-0131 lines 59 and 993. The "Liveness (Q3 → B)" bullet now says the registry entry is removed with its README row, which is true. The 速读 names the two additions in prose ("同时撤下 liveness 登记里指向已删文件的那一条,并为仍在使用的「权限集锁」补回一个被旧测试顺带覆盖的用例"), but its parenthetical "(28 个文件,+336 / −6,516)" is the previous head's figure: the net diff is 30 files, +350 / −6,539 (GitHub and git diff --shortstat origin/main...head agree). "共约 6,500 行" and Tier H by line count still hold.

5. What the previous record judged right still holds at this head.

  • Dead before deletion: no code file on the head imports permission-set-projection.js, seed-name-lookup.js or seed-refusal-sink.js; the 11 retired names appear outside plugin-security only in comments (packages/core/src/security/permission-set-row-definition.ts:16, packages/plugins/plugin-auth/src/last-admin-guard.test.ts:107, the zero-row test's header) and inside it only in comments. Type Check · workspace (2026-10-14T04:47Z), Type Check · consumer gates, Type Check · source gates, Type Check · debt ledger and Build Core (2026-10-14T04:43Z) are green on the head. The main window merged in touched no plugin-security, spec/liveness, packages/cli/test, census or double-contract file.
  • The exports and the ADR-0087 marker: .changeset/15204-7a-permission-set-projection-retired.md is unchanged since accde78fe6; its not-required (runtime-export-retired …) marker over the 11 names and its arrowless FROM/TO lines are as judged, and this head's Check Changeset step 13 read and accepted the disposition.
  • The deletion clause: the five deleted suites and the re-pinned ones are unchanged; the one gap the previous record named (the non-list answer) is closed by item 2 above; the dispatcher-door resolution of the lock's throw remains covered inside the userMessage cases rather than named, as before.
  • The zero-row acceptance: packages/cli/test/permission-set-zero-row.integration.test.ts is unchanged; the ablation on S8's parent 99e944daf2 (both kernels red, expected 2 to be +0 after the publishes and again after the boot) is now also recorded in the os-dev-report 6161002759, as the patch round asked, still as a dev measurement in a throwaway worktree. The file is in the packages/cli population the Test Core shards run.
  • The census: the generated page and counts are unchanged, the stamp still reads bb4760b9b, and check-tenant-audit-census runs in Lint · repo & tooling gates, green on this head's merge ref (2026-10-14T04:39Z), so the 223 holds on the tree CI judged after both merges. Lint · CI, release & engine gates (check:objectql-double-limit) and Lint · ESLint (check:engine-double-contract) are green, so the baseline removals hold.
  • The 速读's truth: every sentence is true of the diff except the file and line figures named in item 4. "没有行为变化" holds (the round's three files touch no runtime path); "去掉了 11 个导出", the per-name alternatives, the whole-PR revert with no migration, and "审阅后批准合并(Tier H)" hold.

6. The round's os-dev-report is on the head. 6161002759 names head: ddcdb46bd4, the four fixes, the zero-row pin and its measured red, the scoped verification on this head (spec liveness 81/81; check:liveness 0; the two lock suites 15 passed; plugin-security and spec typecheck 0; 72 gates derived and run, --ran reconciling 0 NOT-MEASURED and 0 UNRUN, 71 exit 0 and check-empty-changeset exit 1 by design), the previous full battery on accde78fe6 not re-run per the seat's scoping, mcp_calls: 0, two API writes (the body patch and the comment), and one deviation (a full re-run started and stopped per the amendment). The merge of main at ddcdb46bd4 was committed 2026-10-14T04:08Z, before the amendment 6160721338 (2026-10-14T04:12Z), so it does not run against it.

7. Check-runs on the head. 48, all completed by 2026-10-14T05:05Z (the last, the Test Core aggregate, at 2026-10-14T05:04Z); none running; no commit statuses. Red (2): Check Changeset twice, the synchronize and edited runs of PR Automation, both failing at step 12 on the foreign-changeset refusal for .changeset/23233-permission-echo-names-runtime-package.md, red by design as judged in item 3; not required; stays red to landing. Skipped (7): Console Pin Gate, Dogfood Verify CLI, Temporal Conformance (live PG + MySQL), Packed-tarball smoke (opt-in) and the Dogfood Regression Gate shard matrix (full-battery-only; the aggregate Dogfood Regression Gate reads success with the matrix skipped), plus Auto Label and Check PR Size on the edited run, where each skipped by its own if: after succeeding on the synchronize run. Green (39): Test Core and its six shards ((1/6) to (6/6), the packages/cli integration population included), Build Core, Build Docs, Lint & Repo Gates and every Lint · job, TypeScript Type Check and every Type Check · job, Spec property liveness, Governed Surface Queue Guard, Check PR Size, Check Documentation Links, Flag docs affected by code changes, Auto Label, filter, the claim and single-writer guards and Part-of PR must not also close its card (each on both runs), Dogfood Regression Gate. The five required contexts are all success on this head: Lint & Repo Gates (2026-10-14T04:40Z), TypeScript Type Check (2026-10-14T04:47Z), Build Core (2026-10-14T04:43Z), Governed Surface Queue Guard (2026-10-14T04:36Z) and Test Core (2026-10-14T05:04Z). The only red on the head is the advisory one judged above.

② Semver level

Unchanged and right. @objectstack/plugin-security: minor with the BREAKING banner, the ! title, Clause-②: no (narrowing) and the adr-0087 not-required (runtime-export-retired …) marker over all 11 names, the launch-window grade for retiring published runtime exports (major refused by check-changeset-no-major; this head's step 15 "Guard against accidental major bumps" passed). @objectstack/spec: patch, now describing a change that has happened (the tabPermissions evidence and the registry entry with its README row). The corrected 23233 note keeps its own patch; the one-line correction moves no level.

③ Boundary flags

  • Act before approval, body only (no new head): the 速读's "(28 个文件,+336 / −6,516)" reads "(30 个文件,+350 / −6,539)" at this head; and 你要做的 should say that approving also confirms the deliberate correction of the pending fix(plugin-security): the permission-set echo names its runtime package, so an isDefault set records its everyone suggestion #23258 release note (.changeset/23233-permission-echo-names-runtime-package.md), with Check Changeset red by design — today only the English "Acceptance notes" bullet says so, and the confirmation is the gate's named human path.
  • The human confirmation must name the note, the one sentence changed under it and why (S8 stopped the row, 7a deletes the writer, the zero-row test pins it), and accept the standing red; the seat's adoption or the maintainer's approval comment is where it lives. It is not a filename collision: the PR's own note is 15204-7a-….
  • Check Changeset to landing: it stays red on this PR by design and the queue does not read it (not among the five; pr-automation.yml runs on pull_request only). The seat should expect the red to persist on every later run of this head and not re-run it; a rerun cannot go green on this class.
  • The same pending note still says "The projection's copy …" in its "What changed" item. After fix(plugin-security): an environment kernel's security catalog serves permission sets and positions saved through the metadata door again (S8 regression) #23308 and 7a the copy that names the runtime package is the security-catalog echo's; the behaviour the note describes is unchanged, the module it names is gone. The patch round asked only for the "Unchanged" line and the correction is exact to that ask. The seat decides whether the confirmation covers that wording as the fix's historical description or whether a two-word edit is worth a new head and re-review; this record does not ask for one.
  • A ledger evidence row the deletion leaves citing two gone files, gate-invisible: packages/spec/liveness/permission.json:20, permission.description, status: live, evidence "persisted to sys_permission_set.description (plugin-security sys-permission-set.object.ts …); read from the authored set by permission-set-projection.ts (description: ps.description ?? null)". The object file retired at S8, the projector here. Both are bare basenames, and evidence.mts's PATH_RE scans only citations rooted at packages/…, so Spec property liveness cannot see it. Not the registry entry item 1 asks about, and not blocking; it belongs with the spec liveness worklist or 8-post, which should re-cite the live reader of description or re-measure the row.
  • Prose naming the deleted file that the body's sweep list does not name: packages/metadata-protocol/src/protocol.adr0005-org-override-rollback.test.ts:68 and :304; scripts/measure-durability-swallow-family.mjs:280; packages/plugins/plugin-security/tsconfig.test.json:35 (a comment citing the deleted suite as its TS2550 example); docs/qa/platform-checklist/areas/access-security.json:2602 and :2633, the clause and a note of the same item (access-security.packaged-permission-set-lifecycle) whose source at 2649 is named; and the retired names in the comments at packages/core/src/security/permission-set-row-definition.ts:16 and packages/plugins/plugin-auth/src/last-admin-guard.test.ts:107. All prose, none gate-bearing; for 8-post's sweep by name.
  • Deviations and the open question in the report: one deviation, the stopped full re-run, is the amendment's own instruction; the open question is the confirmation above. The report's "no result from it is reported" is the right shape. Nothing in the report claims a run it did not make.
  • Merge ref drift: origin/main is one commit past the merge base (0add8a49ce, docs(spec): the slot judges' door lists name the parse, and why it is not one of them (#23320) #23383, packages/spec/src/automation/* only, no overlap). GitHub reports mergeable: true, mergeable_state: blocked (a draft with required checks outstanding).
  • The seat's own items at landing, unchanged: the correction notes on 6139333945 (refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write under single and refused under a wall (ADR-0131 D2/D3/D5/D13) #15204) and [Decision] 阶段 7a 之前,包发布的权限集在 sys_permission_set 里多出一行标成「管理员」的记录:现在另开小 PR 给投影器加判断挡掉(A),还是等 7a 整个删掉投影器(B) #23227's text, and the governed ADR card (ADR-0094, ADR-0126 "Three real mechanisms", ADR-0131 lines 59 and 993).
  • NOT MEASURED here, by the read-only shape: objectui (no sibling checkout; Console Pin Gate skipped on a PR, as the body now says); the dev's local counts and the ablations on 99e944daf2 and on the lock branch; the platform checklist; and the Check Changeset job log text itself, whose download redirect the session's proxy refuses — the red's classification rests on the run's step list and annotations, which are REST data on the head.

Adopted by domain:engine seat 2 at 2026-10-14T05:06Z, after a tool-use audit of the isolated reviewer's transcript. The reviewer made reads, git fetch into review refs, REST GETs, and record writes only. It made no GitHub write and ran no build, test or gate. The seat takes the verdict as PASS.

  • The act-before-approval items are done by the seat on the body, with no new head: the 速读's figures (30 files, +350 / −6,539), and its 你要做的, which now names the pending-note confirmation.
  • The non-blocking flags:
    • the note's "The projection's copy" stays, as that fix's historical description, inside the same confirmation;
    • permission.json:20 and the extra prose pointers go to 8-post's sweep, by name.

Implemented-by: claude/issue-22975-7a-recut
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 · PR #23368(7a,C3 第 7 阶段的前半)· domain:engine 2 号席 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-14T05:08Z

改了什么
删掉权限集「投影器」和它的两个配套模块(种子名称查找、种子拒绝汇报),连同只测试它们的五组旧测试。共 30 个文件,+350 / −6,539 行,几乎全是删除。另外:

  • 新增一个集成测试,实测发布权限集不会再写任何 sys_permission_set 行。覆盖包发布、可写基础包直接发布和重启三种情况,主机内核和环境内核各测一遍。
  • 撤下 liveness 登记里指向已删文件的那一条(登记本身和 README 两处都撤了)。
  • 给仍在使用的「权限集锁」补回一个被旧测试顺带覆盖的用例(注册表返回非列表时拒绝保存)。
  • 更正 fix(plugin-security): the permission-set echo names its runtime package, so an isDefault set records its everyone suggestion #23258 一条还没发布的更新说明里,被本 PR 变成假话的一句(见「你要做的」)。

为什么改

风险与代价(含回滚)

  • 运行中的部署: 没有行为变化。被删的代码在 S8 之后已经不被调用,新测试实测零写入。
  • 外部代码: @objectstack/plugin-security 去掉 11 个导出(函数和类型)。直接导入它们的代码升级时会编译失败。changeset 按 ADR-0087 的退役条目逐个写明替代做法,版本级别是 minor 并带破坏性标记。仓库里没有任何包还在导入它们。
  • 没测到: objectui 那边没有实测。Console Pin Gate 只在完整检查里跑,PR 上不跑。但它能导入的这 11 个名字仓库内无人使用。
  • 回滚: 整体 revert 本 PR 即可,不涉及数据。

席位意见
建议批准。依据:

  • 契约审查第一轮 FAIL(6160232724),阻断项是 liveness 登记只撤了一半;修补后增量审查 PASS(6161351497),全部问题已关闭。ACCEPT 6161363247。
  • 合并队列必查的五项全绿。
  • 唯一的红灯 Check Changeset 是按设计红的:本 PR 改了别人还没发布的更新说明,这道门要求有人在 PR 上确认。它不在合并队列的必查项里,不挡入队。
  • 和 main 无冲突。

要你批准,只是因为改动超过 3,000 行。没有碰受管路径。

你要做的
用 os-zhuang 或 hotlong 批准本 PR。这次批准同时确认一件事:本 PR 改写了 #23258 尚未发布的更新说明 .changeset/23233-permission-echo-names-runtime-package.md 里的一句,把「仍是 managed_by: 'admin' 的 sys_permission_set 行」改成了「不再写任何 sys_permission_set 行」。所以 Check Changeset 会保持红。批准后,席位转 ready、送进合并队列;落地后立即推进 8-post(已在并行预建),#22975 随 8-post 关闭。

@os-zhuang
os-zhuang marked this pull request as ready for review October 14, 2026 05:10
@os-zhuang
os-zhuang enabled auto-merge October 14, 2026 05:10
…ssue-22975-7a-recut

# Conflicts:
#	content/docs/permissions/tenant-audit-census.mdx
#	docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
Merged via the queue into main with commit dac99c2 Oct 14, 2026
42 of 44 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-22975-7a-recut branch October 14, 2026 06:18
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 tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants