Repository navigation
fix(plugin-audit)!: serve the ledger’s deployment-level rows to the platform administrator alone, in every posture - #28047
Conversation
…atform administrator alone, in every posture Ruling A: a ledger row about no organization (tenant_id NULL, ADR-0131 D7) is served only to the carried PLATFORM_ADMIN rung; every other non-system reader, the view_all_audit_log holder included, reads through a tenant_id-present clause ANDed after the parent-record gate. Under a wall the shipped row policy already excluded them; this holds the line under single and for a set that authors its own ledger policy or the superuser read bypass on the ledger. Claude-Session: https://claude.ai/code/session_016WV4cwC9jjzyBACUnk1V9V Co-authored-by: Claude <noreply@anthropic.com>
…nd the ledger read scope sentence in the record-view auditing page Claude-Session: https://claude.ai/code/session_016WV4cwC9jjzyBACUnk1V9V Co-authored-by: Claude <noreply@anthropic.com>
…vel rows as non-admins The audit capability fixture's bulk rows now carry the holder's organization, as the CRUD mirror stamps a row about a record of it; unstamped they are deployment-level rows, served to the platform administrator alone. The admin identity decision-metadata fixture's rows about the subject are all deployment-level, so no class reader is served one in any posture. Its decisions-only guarantee is asserted at the producer, on the rows at rest, and its read side now pins the ruling: every reader is served only a control row about the same subject that carries an organization, and the platform administrator is served every row. The armed checks keep their shape. The changeset's upgrade note and the docs examples name the admin create-user and password-reset rows among the deployment-level rows. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cwC9jjzyBACUnk1V9V
… served, and read the leaf gate's WHERE past the ruling's clause The field-redaction fixture's rows from other writers, and the leaf fixture's rows about no record, were system inserts with no tenant_id: deployment-level rows, which no reader in either file is served now. They carry the Default Organization, as a tenant-scope writer stamps them. The leaf fixture's observer records each ledger read's WHERE as the gate left it, with the deployment-level clause ANDed after the gate taken off, and the holder's case pins that the clause is there. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cwC9jjzyBACUnk1V9V
📓 Docs Drift CheckThis PR changes 2 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 171 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2aefbcde88274fdb6de117cd9d2c7920b36d391e && git checkout 2aefbcde88274fdb6de117cd9d2c7920b36d391e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 847e4248e9b7059e5b069056bf636c75c1f53b14 e94fcd240ecc95c3fecb352f7010958877dade4a && git checkout -B drift-repro 847e4248e9b7059e5b069056bf636c75c1f53b14 && git merge --no-ff e94fcd240ecc95c3fecb352f7010958877dade4a
node scripts/docs-audit/affected-docs.mjs --json 847e4248e9b7059e5b069056bf636c75c1f53b14
|
|
domain:server#1 · os-elon-musk · session_016WV4czVnUdQQCVKSW5J2uk Contract reviewServed-tier: Check-runs on the head at review time: every completed run is ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Must-fix: none. Implemented-by: VERDICT: PASS |
…nt-level rows on a request that carries its rung The changeset's Unchanged bullet now says the platform administrator's read holds on a request whose authorization context carries the PLATFORM_ADMIN rung, and that a context built outside that resolution is served the rows about an organization only, fail-closed. The comment above view_all_audit_log names the rung beside the superuser read bypass. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cwC9jjzyBACUnk1V9V
|
domain:server#1 · os-elon-musk · session_016WV4czVnUdQQCVKSW5J2uk Contract reviewServed-tier: This head follows Check-runs on the head at review time: completed and ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Must-fix: none. Implemented-by: VERDICT: PASS |
…e deployment-level pins The test layer compiles under tsconfig.test.json: a reader's posture is the rung union, so each fixture reader spells its rung `as const`, and the rung-only case reads the admin grant's capability list through a non-empty precondition rather than spreading an optional array. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cwC9jjzyBACUnk1V9V
|
domain:server#1 · os-elon-musk · session_016WV4czVnUdQQCVKSW5J2uk Contract reviewServed-tier: This head follows Check-runs: on ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Must-fix: none. Implemented-by: VERDICT: PASS |
|
domain:server#1 · os-elon-musk · session_016WV4czVnUdQQCVKSW5J2uk Seat ACCEPT · PR #28047 · head
|
Requested by Elon · project thread
Fixes #26750
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: updated here
维护者速读
tenant_id为空:全局设置变更、用户导入、平台管理员身份变更、管理员建号与重置密码),在所有租户姿态下(含single)只给平台管理员看;其他读者,包括view_all_audit_log的持有者,只看属于某个组织的记录。singleposture, does an organization-scoped audit-ledger reader also read deployment-level ledger rows, or are those for the platform administrator only? #26750 的裁决 A。single姿态下组织行策略被剥离,任何拿到账本读权的人都能读到部署级记录。single部署里自建的审计角色和组织管理员将看不到部署配置历史;记录仍在库,系统上下文的导出不受影响。回滚即 revert 本 PR,无数据迁移。packages/plugins/plugin-audit/src/audit-log-read-visibility.ts(中间件末尾新增的一行规则与readsDeploymentLevelLedgerRows)、packages/plugins/plugin-audit/src/audit-log-deployment-level-rows.integration.test.ts、packages/qa/dogfood/test/admin-ledger-decision-metadata.dogfood.test.ts(按裁决翻转的钉子)。pnpm --filter @objectstack/plugin-audit exec vitest run src/audit-log-deployment-level-rows.integration.test.ts,预期single/group/isolated三个姿态共 18 条全绿。What changes
packages/plugins/plugin-audit/src/audit-log-read-visibility.ts). On every non-system engine read ofsys_audit_log(find, findOne, count, aggregate), a reader that is not the platform administrator gets one more clause ANDed into itswhere:tenant_idis neither null nor empty. It is added after the parent-record gate, which plans its parents off the read's ownwhere, and it reaches the holder ofview_all_audit_logtoo, whom that gate exempts. The platform administrator is the carriedPLATFORM_ADMINposture rung (PLATFORM_OPERATOR_POSTURE, ADR-0095 D2/D3), which the unscopedadmin_full_accessgrant derives. Positions, set names and capabilities are not read, and a context with no rung gets the clause. Writes, system-context reads and context-less programmatic reads are untouched. Which organization's rows an organization reader is served stays the row policy's answer (ADR-0131 D8). The module is not re-exported, so the package's public surface is unchanged.packages/spec/src/security/capabilities.ts). Theview_all_audit_logdescription gains one sentence forsingle. Nothing else inpackages/specchanges.content/docs/permissions/record-view-auditing.mdx..changeset/26750-ledger-deployment-level-rows.md: patch for both packages, the two declaration lines, a BREAKING banner, and an upgrade note forsingleoperators who granted the ledger to someone who is not the platform administrator.Why
Ruling A on #26750 (comment 6457448424): deployment-level ledger rows (no
tenant_id, ADR-0131 D7) are served only to the platform administrator in every tenancy posture,singleincluded, and an organization-scoped holder ofview_all_audit_logreads only the rows about its organization(s). Undersinglethe shipped row policysys_audit_log_orgis stripped (a platform tenant policy by provenance, ADR-0105 D3), so every reader with a ledger grant was served those rows.On the ruling's "its superuser read bypass": the bypass (
viewAllRecords) skips plugin-security's Layer 1 only and never reaches this plugin's read middleware. So the rule reads the carried rung that names the platform administrator, and no new capability is introduced.Tests
audit-log-read-visibility.test.ts(unit):TENANT_ADMIN,MEMBER,EXTERNAL, a mis-cased rung, no rung,platform_adminas a position, andadmin_full_accessas a permission or a capability all answer false;audit-log-read-visibility.integration.test.ts: the fixture rows now carry the tenant each real writer stamps. A new case serves the deployment-level rows to the platform administrator alone; the member, the other member, the organization admin and the holder are served none.audit-log-deployment-level-rows.integration.test.ts(new): one boot per posture (single,group,isolated) with the real CRUD mirror. The organization holder and the organization admin are served their organization's rows and no deployment-level row, through find, count, atenant_id: nullfilter, findOne by id and a grouped aggregate. The platform administrator is served both.The dogfood pin sweep. Both files were named to the seat and added to the claim's file surface before they were edited.
audit-log-audit-capability.dogfood.test.ts: the broad read's bulk rows were system inserts with notenant_id, which made them deployment-level rows. They now carry the holder's organization, as the CRUD mirror stamps a row about a record of it. The test's subject is unchanged.admin-ledger-decision-metadata.dogfood.test.ts: a pin flip caused by ruling 6457448424, not a fix-to-green. Every row about the subject (plugin-auth's explicit rows and the mirror's rows, written by the platform administrator's endpoints with no organization) is deployment-level, so no class reader can be served one in any posture. The decisions-only guarantee is now asserted at the producer, on the rows at rest. The read side pins the ruling: every reader is served only a control row about the same subject that carries the readers' organization, and the platform administrator is served every row. The armed checks keep their shape: the rows exist and the mirror carries every class; every reader opens the subject; no row about the subject carries an organization, and every reader is served the control row.Two plugin-audit fixtures the full package suite found (inside the claim's surface, "its tests under
packages/plugins/plugin-audit/src/"):audit-log-field-redaction.test.ts: the rows from other writers were system inserts with notenant_id, so the organization readers there were no longer served them. They carry the Default Organization, as a tenant-scope writer stamps them. The redaction cases are unchanged.parent-record-read-leaf.integration.test.ts: the rows about no record carry the Default Organization the same way. The observer that records each ledger read's WHERE "as the gates left it" takes the deployment-level clause, ANDed after the gate, off what it records, so the gate's own WHERE is still what is measured. The holder's case now also pins that the clause is there.Acceptance notes
groupandisolated, a custom set that grants the ledger read andview_all_audit_logand omitssys_audit_log_orgwas measured on a real boot before the change. Its holder was served only its own organization's rows, so that set has no NULL-tenant leak to fix.Verification
Final head
af925f22. It differs from173abc30only in the two plugin-audit fixtures above, which no dogfood file runs.Ablation 1: the rule line replaced by a marker, three plugin-audit files: 20 red, restored
Ablation 2: the predicate also reads a
platform_adminposition: the rung-only pin red, restoredAblation 3: the rule removed, against the re-armed leaf fixture: the holder's clause pin red, restored
Package suites (green), each under the verify lock
The neighbouring suites are the ones outside plugin-audit that boot the audit plugin and read the ledger.
Full dogfood battery: red on the first head (the two fixtures, named above), green after
dispatch-gates: 106 derived families, run and reconciled
Three families needed a prerequisite in this shallow cloud clone before they measured:
check-plugin-teardown-shape --self-test(the pinned fixture commit fetched),spec check:skill-examples(client SDK declarations built),check:dual-build-cjs-loads(six package builds).Lint and generated artifacts
Human Minimum
packages/specholds no runtime logic. (The only spec change is one sentence in a capability description.)pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (check:generated: all 14 up to date; no artifact carries the description.)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.维护者速读block is the fleet's one sanctioned Chinese section.)Contract review is owed: the seat runs it on the final head.
🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4cwC9jjzyBACUnk1V9V
Generated by Claude Code