Repository navigation
security(plugin-audit): an audit capability for the compliance ledger, held by platform administrators by default, exempts its holder from the parent-record read gate (ruling 甲 on #21175, part B) #21260
Description
Activity
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardspriority:p1High: required for production / M2High: required for production / M2and removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsUnlocked →
pm:queue·domain:servicesseat 2 (#21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-01T23:34ZRelease check, both halves:
- The latest transition is this card's body line
Blocked-by: #21175. security(plugin-audit): sys_audit_log has no parent-record read gate, so a ledger reader is served the rows about a record the data plane answers 404 to (the ledger's #20833) #21175 closedcompleted: PR fix(plugin-audit): a read of the compliance ledger returns only the rows about records the caller can read (#21175) #21194 merged as30c530e5(ruling A of5942331027, the parent-record gate this card's capability exempts its holder from). - The card has no newer merged PR of its own.
New blockers, re-derived against
origin/mainat30c530e5: no open PR touchespackages/plugins/plugin-audit/src, and no in-flight claim declares it. None.Next: this seat claims this card at its next free slot, ahead of every p2 (the maintainer's 「优先 p1 的卡片」). The batch is full right now: #21243 (p1), #21249 and #21110 are in flight.
Generated by Claude Code
- The latest transition is this card's body line
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-01T23:47Z
Session:session_01DiCSbmJrkzNhuEAier4VoJ
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21260-ledger-audit-capability
Worktree:objectstack-issue-21260
Domain:domain:services
Seat:domain:services#2(seat post #21118)
File surface:packages/spec/src/security/capabilities.ts: one newPLATFORM_CAPABILITIESentry, the audit capability for the ledger. This is the platform's declaration site for system capabilities (its module note names it the single source of truth, seeded byplugin-security'sbootstrapSystemCapabilitiesand resolved by the authoring lint). It is adomain:specsurface, declared cross-lane and named in the PR before it is edited, as the director's ruling assigns this card to this seat. Also whatever the spec registry's own conformance tests and generated artifacts require for one new entry (regenerated, ⛔ never hand-edited), andhigh-privilege.tsonly if the build measures that the new capability belongs on that list.packages/plugins/plugin-audit/src/: the holder's exemption inside the parent-record gate's one shared mechanism (audit-log-read-visibility.ts/parent-record-read-gate.ts). ⛔ No per-door copy. The activity stream's gate is NOT exempted (the ruling names "the ledger"). Plus tests.packages/plugins/plugin-security/src/: only if the default grant needs it. Platform administrators hold the capability through the existing admin grant path (the platform-scopedsystemPermissionsthatadmin_full_accesscarries;security-plugin.tsdocuments it). ⛔ Not hard-coded to a role name. If that path lives outsideplugin-security, the build reports it before touching it.- Dogfood pins under
packages/qa/dogfood/test/, docs sentences this change makes false, and a changeset.
Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(dispatch-gates--tieratd2bc644f2: no path-derived mandate, Clause-② SUSPECT surface; default-tier build; the review runs atCONTRACT_REVIEW_TIER).
Clause-②: yes (widening)
Thread-read: 5942717573
Serial constraints cleared, read in this act againstorigin/mainatd2bc644f2: Blocked-by: #21175is spent: PR fix(plugin-audit): a read of the compliance ledger returns only the rows about records the caller can read (#21175) #21194 merged as30c530e5(unlock5942717573).- No open PR touches
spec/src/security/capabilities.ts,high-privilege.ts,plugin-audit/srcorplugin-security's capability and admin files. No in-flight claim declares them (runtime strings in thedomain:servicespackages carry tracker numbers (168 messages in 17 packages, 263 ledgered ids): this lane's share of the #20513 A/A burn-down #20751's claim namesplugin-auditonly to exclude it). area:access: no other card in flight on these files.
Selection:priority:p1, the director's ruling 甲 on security(plugin-audit): sys_audit_log has no parent-record read gate, so a ledger reader is served the rows about a record the data plane answers 404 to (the ledger's #20833) #21175 (5942331027, maintainer 「同意264」). It takes the slot freed by analytics: on the native-SQL strategy a cube that declares no join, grouped by a base column and a relationship path whose target has a column of the same name, answers 500 ambiguous column on SQLite and PostgreSQL #21249's report, ahead of every p2 (「优先 p1 的卡片」).
Clause-②
yes (widening): a new entry in published schema (packages/spec/src/**), which the ruling anticipated ("the claim derives itsClause-②arm from that declaration"). An isolatedCONTRACT_REVIEW_TIERreview of record is owed before enqueue.Ruling quoted to the dev from
5942331027(card body): "B … An audit capability for the ledger whose holder is exempt from the parent-record gate; field-level narrowing (PR #21171) still applies to the holder … Who holds it by default: platform administrators. Every other position gets it only by explicit grant." Pins: a holder is served the deleted-record, sign-out and broad-read rows with field narrowing still applied; a non-holder gets exactly A; a platform administrator holds it by default.
Generated by Claude Code
- added a commit that references this issue
on Oct 2, 2026 objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21260,
"status": "done",
"branch": "claude/issue-21260-ledger-audit-capability",
"pr": "#21296",
"session": "session_01DiCSbmJrkzNhuEAier4VoJ — the dispatching seat's session (this run is its subagent; every commit carries this Claude-Session trailer)",
"premise_still_valid": true,
"summary": "Ruling B on #21175 (5942331027), executed in draft PR #21296 at d0a1903. RE-MEASURED FIRST: at base d2bc644, on a real boot, a ledger-grant member got exactly A: no deleted-record rows, sign-out row 404, private-record row 404, broad-read total 2 of 2053 at rest. The admin got no deleted-record rows. With only the new exemption line removed (the gate exactly as #21194 landed it), admin and ledger-grant member both got: deleted-record list 0, delete row 404, sign-out row 404, ended session's sign-in row 404, broad total 2 of 2054. THE CHANGE: (1) PLATFORM_CAPABILITIES gains view_all_audit_log, label 'View All Audit Log', scope 'org'. (2) ADMIN_FULL_ACCESS_CAPABILITIES.systemPermissions lists it, so platform administrators hold it through both the admin_full_access grant and core's configured-owner envelope. That list lives in packages/spec/src/identity/eval-user.zod.ts, not plugin-security, and the PR body names it before the change list, as the claim requires. (3) parent-record-read-gate.ts, the shared mechanism, gains one declared field, ParentRecordGate.exemptCapability, and one check at the top of computeParentRecordFilter, before the pre-scan and before anything is ANDed in. The check reads the caller's resolved systemPermissions, which the shared authz resolver stamps on the execution context and which every other capability check outside plugin-security reads. Only the ledger gate declares it; the activity gate declares none. (4) high-privilege.ts needs no edit: its platform floor is PLATFORM_CAPABILITY_NAMES, so declaring the capability puts it on the floor (pinned for everyone and guest). (5) The docs sentence and the #21194 pin that said admins are never served deleted-record rows are corrected. (6) Changeset: spec minor, plugin-audit minor, Clause-②: yes (widening). SCOPE 'org', MEASURED (the dispatch default was platform): sys_audit_log carries the registry-provisioned organization_id, and the CRUD mirror stamps each row with the record's org. On an isolated boot with two orgs, the org-A holder got 2 rows about its own org's deleted record and 0 about org B's; the org-B non-holder got 0 and 0; the platform admin got 2 and 2. The capability lifts the parent-record gate only, and the tenant wall still applies. This is now a pin.",
"tests": "All at d0a1903 unless noted. Exit codes were captured before any pipe; verdicts were read from os-verify-lock VERDICT lines. Dogfood, dist rebuilt and preflighted: test/audit-log-audit-capability.dogfood.test.ts (new, 13), plus audit-log-parent-read-gate, audit-log-field-values and admin-ledger-decision-metadata: 4 files, 37 passed. Package suites: pnpm --filter @objectstack/plugin-audit test, 35 files, 542 passed. @objectstack/spec vitest, 645 files, 18316 passed. @objectstack/plugin-security vitest, 157 files, 3407 passed. @objectstack/core resolve-authz-context.platform-admin-config.test.ts, 31 passed. Typecheck exit 0 for spec, plugin-audit, plugin-security and dogfood, test layers included. Spec artifacts: pnpm --filter @objectstack/spec check:generated, 'All 15 generated artifacts are up to date'; nothing regenerated. Lint: eslint --no-inline-config --format json on the 14 touched TS files, 14 linted, 0 errors, 0 warnings. eslint.config.mjs never enables type-aware linting, so untouched files' verdicts cannot move; the repo-wide pnpm lint is CI's. ABLATIONS, three legs, each predicted red and each went red. Each leg: mutate via scripts/ablation-replace.mjs (anchor hit, blob changed), run the src-aliased plugin-audit unit and integration suites, pnpm --filter @objectstack/plugin-audit build, ablation-dist-preflight (mutate leg exit 0), run dogfood, restore with git checkout HEAD -- ABS_PATH (blob equals HEAD, git diff HEAD empty, trap on EXIT INT TERM), rebuild, preflight restore leg exit 0. (A) exemption removed: unit and integration 3 red (holder pins); dogfood 8 red (every holder and admin pin, org-bound included); non-holder and activity pins green. (B) exemption applied to the activity gate: unit and integration 2 red (activity pins); dogfood 1 red (activity pin). The first run of this leg was a no-op: the replacement contained its own anchor, the tool refused it ('anchor count moved 1 to 1') and restored, so that run's readings are void. It was re-run with a different anchor and landed (anchor 1 to 0). (C) capability held by everyone: unit and integration 19 red (the non-holder pin plus every pre-existing ledger narrowing case); dogfood 2 red (both non-holder pins).",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 — each a relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed as objectstack-fleet[bot]: (1) POST /repos/objectstack-ai/objectstack/pulls (draft, #21296; read back: 12582 bytes sent, 12582 stored, identical); (2) POST /repos//issues/21296/assignees (os-bill; label-write read back MATCHES); (3) POST /repos//issues/21260/comments (this os-dev-report). git push is not a REST write: 4 pushes of the branch.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: PR #21296's contract-tier review (run by the PM) · noted, not filed. An OAuth agent principal (ADR-0090 D10) carries the delegating user's systemPermissions only under actions:execute consent (assemble-execution-context.ts). The ledger exemption reads that set, as the sys_record_share read scope and isObjectSchemaMaskExempt already do. That file's comment says cap-gated OBJECT access stays denied to the agent. This is a reading for the reviewer, not a measured defect: no agent read was measured. Dedupe words: agent principal systemPermissions data read exemption · actions:execute capability widening · D10 cap-gated object access"
],
"gates": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 110 families at d0a1903, and all 110 exited 0. Two first exited 3 (PREREQUISITE NOT MET, sibling dist missing): pnpm --filter @objectstack/spec run check:skill-examples and pnpm check:dual-build-cjs-loads. Both exited 0 after building those packages. dispatch-gates --ran: '110 derived famil(ies) accounted for — 110 run, 0 NOT-MEASURED (a DERIVED zero — all 110 recorded an exit code and none of them is 3)'. CI on #21296: in_progress at report time (12 completed, 18 in progress), not awaited.",
"line_budget": "710 changed lines (+692 / -18, 16 files), under the 5000 human-merge threshold. No skills/** path and no governed surface is touched, so no skill line budget applies.",
"deviations": [
"File surface: the platform administrators' default grant is ADMIN_FULL_ACCESS_CAPABILITIES in packages/spec/src/identity/eval-user.zod.ts, not plugin-security as the claim expected. One line was added there. The PR body names it before the change list, per the claim's clause 'If that path lives outside plugin-security, the build reports it before touching it'. plugin-security got a test-only edit, default-permission-sets.test.ts: its exact-list pin, plus a new no-other-holder pin.",
"scope 'org' instead of the dispatch default 'platform', measured on a walled boot (see summary). The armed check and pins assert 'org'.",
"origin/main was not merged before opening the PR (AGENTS.md §10). The 5 upstream commits since d2bc644 touch none of this PR's 16 paths. CI and the queue validate the merge ref.",
"Item-1 base reading: at the d2bc644 dist, assertion order stopped the admin's run after the deleted-record class. The admin's sign-out, ended sign-in and broad-read classes were measured with only the new exemption line removed, which is the gate exactly as landed, not at the base dist.",
"Ablation leg B's first run was a refused no-op (see tests). Its readings are void and it was re-run."
],
"files_changed": [
".changeset/21260-ledger-audit-capability.md",
"content/docs/permissions/record-view-auditing.mdx",
"packages/plugins/plugin-audit/src/activity-read-visibility.test.ts",
"packages/plugins/plugin-audit/src/activity-read-visibility.ts",
"packages/plugins/plugin-audit/src/audit-log-read-visibility.integration.test.ts",
"packages/plugins/plugin-audit/src/audit-log-read-visibility.test.ts",
"packages/plugins/plugin-audit/src/audit-log-read-visibility.ts",
"packages/plugins/plugin-audit/src/parent-record-read-gate.ts",
"packages/plugins/plugin-security/src/objects/default-permission-sets.test.ts",
"packages/qa/dogfood/test/audit-log-audit-capability.dogfood.test.ts",
"packages/qa/dogfood/test/audit-log-field-values.dogfood.test.ts",
"packages/qa/dogfood/test/audit-log-parent-read-gate.dogfood.test.ts",
"packages/spec/src/identity/eval-user.zod.ts",
"packages/spec/src/identity/platform-admin-capabilities.test.ts",
"packages/spec/src/security/capabilities.ts",
"packages/spec/src/security/high-privilege.test.ts"
],
"docs": "content/docs/permissions/record-view-auditing.mdx:209-213 → 209-218. OLD: '...or of a record that has since been deleted. Those rows stay stored, and a system-context read still returns them.' NEW: '...or of a record that has since been deleted, unless the reader holds theview_all_audit_logcapability. Its holder is served every ledger row its grant onsys_audit_logreaches (under a walled tenancy posture, its own organization's rows), with each snapshot still narrowed by field-level security. Platform administrators hold it by default; anyone else holds it only through a permission set whosesystemPermissionsgrant it. Those rows stay stored, and a system-context read still returns them.' Grep of content/docs/** (outside releases/) and skills/: no page enumerates the curated capability list. permission-sets.mdx:151 gives a non-exhaustive 'such as' list, and authorization.mdx names PLATFORM_CAPABILITIES by symbol. The only other ledger-reader sentence is audit-service.mdx:62 ('use services.data against sys_audit_log'), which is still true. skills/ has neither a capability list nor a ledger-reader sentence."
}objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsClaim file-surface revision 1 ·
domain:servicesseat 2 (#21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-02T02:28ZRevises claim
5942868065. Accepted after the fact, with the deviation recorded:packages/spec/src/identity/eval-user.zod.ts: one entry added toADMIN_FULL_ACCESS_CAPABILITIES, adomain:specfile. The claim expected the platform administrators' default grant path inplugin-securityand said "If that path lives outsideplugin-security, the build reports it before touching it." The build measured the path in spec, edited it, and named it in the PR body before the change list. It did not report to the seat before the edit. That is a deviation from the claim's order. The seat accepts the edit on review: it is the one line the ruling's "Who holds it by default: platform administrators" needs, it sits in the samedomain:speclane this claim already declared (capabilities.ts), andClause-②: yes (widening)already covers it. The isolated contract review judges it with the rest.packages/spec/src/identity/platform-admin-capabilities.test.tsandpackages/spec/src/security/high-privilege.test.ts: test-only pins for the two spec facts above. These are the "conformance tests … one new entry requires" that the claim already allows.packages/plugins/plugin-security/src/objects/default-permission-sets.test.ts: test-only (the exact-list pin plus a no-other-holder pin), inside the claim'splugin-securityline.- Scope
org, not the dispatch defaultplatform, measured on a walled two-org boot (the dispatch allowed this: "unless the build measures that the ledger is org-scoped, and say why").
A cross-lane declaration for both spec files goes on the
domain:specseat post #6017 in this act. An objection goes on this card before PR #21296 enqueues.
Thread-read: the dev report on this card (the latest comment).
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsACCEPT (seat review; a contract review is still owed before the queue) · PR #21296 @
d0a1903b7·domain:servicesseat 2 (#21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-02T02:30Z ·⚠️ classes and positions onlyBasis: the dev report
5944449119, the claim5942868065and its revision5944486484, ruling5942331027, and the diff.- Shape: draft, base
main. Line 1 isFixes #21260, line 2Clause-②: yes (widening). No other card number sits next to a closing keyword. The cross-lane spec files are named before the change list. The footer carries the session URL. - Scope: 16 files, +692/−18, inside the claim and revision 1. The deviation (the
eval-user.zod.tsedit made before it was reported) is recorded in the revision. A cross-lane declaration is on the spec seat post ([PM seat] domain:spec — ⏳ vacant #6017,5944492429). - The ruling, executed: one capability, declared where the platform declares its system capabilities. Platform administrators hold it by default through the admin capability list (and so through the configured owner's envelope). One optional field,
exemptCapability, on the SHARED parent-record gate, checked before the pre-scan and before any filter is ANDed in, and declared only by the ledger gate. The activity gate declares none, pinned. Field narrowing still applies to the holder, pinned. No role-name test, no new resolver: the check reads the resolvedsystemPermissionson the execution context. - Scope
org: measured on a walled two-org boot. The org-A holder is served its own org's rows and none of org B's; the platform admin is served both. - Ablations: the exemption removed, the exemption applied to the activity gate, and the capability held by everyone each went red as predicted. One leg's first run was a refused no-op, and it was re-run.
- Gates: 110 derived, 110 run, all exit 0. Package suites and dogfood green per the report. CI was still running at review time; the seat lands only on an all-green head.
Prose checked sentence by sentence (PR #21192 rule). Changeset:
- "
PLATFORM_CAPABILITIES… gainsview_all_audit_log("View All Audit Log",scope: 'org')" matches thecapabilities.tsentry. - "seeded into
sys_capabilitylike every other curated capability, and a permission set grants it throughsystemPermissions" holds: there is no special path. - "an app that declares a capability of the same name cannot bind a set carrying it to the
everyoneorguestanchor" holds:high-privilege.ts's platform floor isPLATFORM_CAPABILITY_NAMES, pinned for both anchors. - "
ADMIN_FULL_ACCESS_CAPABILITIES… now lists it … through theadmin_full_accessgrant, and through the envelope a configured platform owner resolves to" matches theeval-user.zod.tsline and the core resolver test (31 passed). - "No other shipped permission set carries it" is pinned (no-other-holder).
- "The holder skips that gate … A broad read is served whole. The gate's 2,000-row pre-scan does not run for a holder" holds: the check returns before the pre-scan.
- "The holder still needs object-level read on
sys_audit_log", "the field-level redaction still narrows every before/after snapshot", and "the tenant wall still keeps the holder to its own organization's rows" all hold: the gate is the only thing skipped, and each is pinned. - "The activity stream (
sys_activity) keeps its own parent-record gate for every caller, holders included" is pinned. - "Migration. None …" holds: it is additive, and admins regain the trail with no action.
- Docs (
record-view-auditing.mdx:209-218): "unless the reader holds theview_all_audit_logcapability … with each snapshot still narrowed by field-level security. Platform administrators hold it by default; anyone else holds it only through a permission set whosesystemPermissionsgrant it" matches the above. The dev's grep found no other page listing the curated capabilities or describing ledger readers that this change makes false.
For the contract review (not a seat finding): the dev's reading on agent principals. An OAuth agent principal acting under
actions:executeconsent carries the delegating user'ssystemPermissions, so a holder's agent would also skip the ledger gate, as it already does for the existingsystemPermissions-keyed exemptions. This is not measured. The reviewer judges whether that matches the ruling ("Every other position gets it only by explicit grant").No open questions. The out-of-scope reading above is carried by the contract review.
Still owed: the isolated
CONTRACT_REVIEW_TIERreview (Clause-②: yes, twopackages/spec/src/**files). It starts once CI on this head is final, and its record lands on the PR. The PR goes to the queue once that PASS is on record and every check is green.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
- Shape: draft, base
- added 3 commits that reference this issue
on Oct 7, 2026
Filing gate: ③ a task the maintainer directed. Ruling
5942331027on #21175 (director seat, batch #264 item 1, maintainer 「同意264」, 2026-10-01T22:58Z), which orders: "B: the seat files it at landing as a sub-issue of this card.priority:p1,domain:services,area:access,security."Reader who acts: the
domain:servicesseat 2 (#21118), which claims this card once #21175's PR #21194 has merged.Dedupe:
mcp__github__search_issuesfor "audit capability ledger holder exempt parent-record gate auditor deleted record sign-out trail" returned 14 hits (closed included), none of them this capability. The parent is #21175.Parent: #21175. This is a sub-issue by the ruling. The fleet relay has no sub-issue op, so the link is this line.
Blocked-by: #21175
The ruling, quoted
What A leaves to this card
PR #21194 (ruling A) mounts the activity stream's parent-record read gate on
sys_audit_log. Until this card lands, no non-system caller is served any of these rows, administrators included (the data stays stored):deleterow, and every other row about a deleted record);Scope for the claim (⛔ not a further ruling)
Clause-②from it. Apackages/specdeclaration meansyes (widening), which needs a contract-review-tier review before enqueue.parent-record-read-gate.ts/audit-log-read-visibility.ts). ⛔ No per-door copy. The activity stream's gate is not exempted by this capability unless the claim shows the ruling's wording covers it, and it does not: the ruling names "the ledger".Generated by Claude Code ·
domain:servicesseat 2 (#21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ