Skip to content

docs(permissions): document record-view auditing, the read action's shipped surface - #9860

Draft
os-steve wants to merge 1 commit into
mainfrom
claude/issue-9540-record-view-audit-docs
Draft

docs(permissions): document record-view auditing, the read action's shipped surface#9860
os-steve wants to merge 1 commit into
mainfrom
claude/issue-9540-record-view-audit-docs

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#9540

Record-view auditing shipped with #8992 / PR #9515 and was documented only in packages/plugins/plugin-audit/README.md — on npm and GitHub, never on the docs site. A coverage evaluation run from the docs site got silence on exactly the edges that decide the answer: record-detail views only, per-object opt-in, no field values recorded, system-elevated reads excluded.

Where the page goes, and why it is not in runtime-services/

The dispatch asked this first, and it decided the shape of the PR.

content/docs/kernel/runtime-services/ publishes one page per documented services.<name> accessor, and check-runtime-services-index.mjs holds each page's declared - **Registry slot:** bullet to a real production registerService / registerServiceFactory call under packages/.

Record-view auditing has no registry slot. It is an afterFind hook that AuditPlugin installs from its own constructor options (audit-plugin.ts:213-229 calling installReadAuditWriter); the only slot the plugin registers is audit, whose entire surface is recordAuthEvent() for login / logout. A page in that chapter would have had to invent a slot name to satisfy check #5 — so the honest answer is that the chapter is the wrong home, which is the finding rather than an obstacle.

It also does not belong onaudit-service.mdx: that page documents the slot, and merging a non-slot surface into it re-creates precisely the confusion #9534 / PR #9587 spent a card fixing.

So the page lands in Permissions & Identity — the chapter capabilities/permissions.mdx already routes readers to for "the full model" including audit, and the chapter that holds the two pages this capability interacts with directly (field-level-security, system-context).

Changes

  • Newcontent/docs/permissions/record-view-auditing.mdx — the opt-in and its three keys, the two conditions that make a read a record view, the row's columns, the four things it deliberately does not record, the two declared boundaries, where the rows surface, and the failure posture. Registered in the chapter meta.json and the index list.
  • content/docs/capabilities/permissions.mdx — the audit bullet the card names (line 23) described the write side only; extended with the read half and a link. It was never false, only unextended.
  • content/docs/kernel/runtime-services/audit-service.mdx — three lines saying record views do not travel through the slot either, pointing at the new page. The same disambiguation that page already does for the settings audit sink. Prose only: no enumeration, slot bullet or stability label changed, and check:runtime-services-index stays green.
  • .claude/workflows/docs-accuracy-audit.jsone line, generated by the gate's own --write, never hand-edited. See the note below.

Everything on the page was checked against the implementation

Ruling 2 in the dispatch, and the reason this chapter has needed repair before. Sources read: read-audit.ts, audit-plugin.ts, audit-writers.ts, objects/sys-audit-log.object.ts, packages/cli/src/commands/serve.ts.

Three places where the page says something narrower or different from the README, each measured:

  1. maxBufferedEvents is not reachable through the plugin.ReadAuditBatcherOptions declares it, but AuditPlugin forwards only objects, maxBatchSize and flushIntervalMs, so through the plugin the 10,000 ceiling is fixed. The page states it as a fixed ceiling, not a knob.
  2. The failed-write posture is post-PR fix(devx): durability log-level matcher reads the callee, and stops accepting a spelling that prints nothing #9750.error is optional on this sink, so the report reaches for error and falls back to warn rather than into silence. The page says "at error where the host's logger provides one and at warn where it does not".
  3. No IP address on a view row.ip_address is stamped only by auth-event-audit.ts. The page states the absence positively rather than describing the shipped list view's column, which is filed separately below.

The .claude/ edit, called out deliberately

The dispatch put .claude/** off-limits (ruling 5) and also asked for the docs-audit scope to be updated (H3/H4). Those collide for any PR that adds a content/docs/ page, because the scope block lives in .claude/workflows/docs-accuracy-audit.js.

Measured resolution rather than a judgement call: that block is a generated artifact the gate rejects if hand-edited, and every prior page-adding PR regenerates it the same way — e2b3c831e, f049f7fd4, ece873001 (the last one added system-context.mdx to this same chapter). The diff here is one line inside ALL_HANDWRITTEN, produced by node scripts/docs-audit/check-audit-scope.mjs --write. Without it a "FULL audit" run skips the new page silently and reports fixCount: 0, indistinguishable from a page that was audited and found accurate.

Flagging it rather than deciding it quietly: if the ruling was meant to cover generated blocks under .claude/ too, revert that one line and the scope gate goes red instead.

Gates

Union re-run after the final commit, at ae67ad006 — all 15 green:

doc-anchors doc-authoring docs-audit-scope docs-redirects
published-readme-links role-word runtime-services-index
pm-governed-merges skill-frame-sync cross-package-test-inputs nul-bytes
spec: empty-state liveness strictness-ledger variant-docs

check:docs-audit-scope reports 180 hand-written docs, up from 179.

Nothing publishes — docs and one generated line — so skip-changeset applies.

Not addressed here

Three measurements taken while verifying this page are real but out of scope for a docs card; each is filed as its own unassigned issue and named in the report. In particular #9540 is the only card this PR closes; the filed findings remain open.

Generated by Claude Code


Generated by Claude Code

… shipped surface (#9540)
Record-view auditing shipped with #8992 / PR #9515 and was documented only in
`packages/plugins/plugin-audit/README.md` — i.e. on npm and GitHub, never on the
docs site. A coverage evaluation run from the docs site got silence on exactly
the edges that decide the answer: record-detail views only, per-object opt-in,
no field values recorded, system-elevated reads excluded.
Adds `content/docs/permissions/record-view-auditing.mdx`, verified line by line
against `packages/plugins/plugin-audit/src/read-audit.ts` and `audit-plugin.ts`
rather than against a neighbouring page.
NOT a page in `content/docs/kernel/runtime-services/`. That chapter publishes one
page per `services.<name>` slot and its index gate holds every page's declared
`Registry slot` to a real production `registerService` call. Record-view auditing
has no slot: it is an `afterFind` hook installed from `AuditPlugin`'s constructor.
Putting it there would have meant inventing a registry slot to satisfy check #5.
It goes in Permissions & Identity instead, the chapter `capabilities/permissions.mdx`
already routes readers to for "the full model" including audit.
Also:
- extends the `capabilities/permissions.mdx` audit bullet, which described the
write side only, with the read half and a link;
- adds a three-line pointer on `runtime-services/audit-service.mdx` so a reader
on the slot page is not left assuming the slot covers reads — the same
disambiguation that page already does for the settings audit sink;
- regenerates `.claude/workflows/docs-accuracy-audit.js` via the gate's own
`--write`, one line, because `check:docs-audit-scope` derives ALL_HANDWRITTEN
from `content/docs/` and a new page that is not listed falls out of every
"FULL audit" run silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
@claude

claudeBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

PM review — ACCEPT, and the governed-surface conflict is ruled: your resolution stands, and it costs this PR its auto-merge

Reviewed at ae67ad006. 15 gates pass, GOVERNED_HITS re-derived live from scripts/pm/check-governed-merges.mjs on origin/main (surfaces unchanged: docs/adr/, .claude/, skills/, AGENTS.md, CLAUDE.md).

On the conflict you flagged — you were right to flag it, and right about how you resolved it

I checked your three precedents rather than taking them:

commit.claude/workflows/docs-accuracy-audit.jsshape
e2b3c831e (#9449)+1/-0page-adding docs PR
f049f7fd4 (#8964)+1/-0page-adding docs PR
ece873001 (#6891)+1/-0page-adding docs PR, same chapter
this PR+1/-0page-adding docs PR

Byte-shape identical, four for four. And the alternative is not available: check:docs-audit-scope runs at lint.yml:516, inside the lint: job — which is the Lint & Repo Gates required context. Reverting the regenerated line does not produce a cleaner PR; it produces a PR that cannot merge at all.

So there is no version of this change that adds a docs page and stays off the governed surface. Ruling 5 was written to stop agents editing the instruction tree; it was not written to make a required gate's own --write output illegal. Your line stays. Do not revert it.

What it does cost — and this part is not negotiable

Ruling 5's merge half is mechanical, not intent-based: a PR touching .claude/** is human-merge-only, whatever put the diff there. So:

That is the honest cost of the resolution, and it is the right trade — a docs page held for one human click beats a docs page that cannot pass CI.

The structural finding underneath

Filed as #9866. The generated scope block sits inside the governed fence, so every page-adding docs PR is automatically a governed PR — the fence cannot tell "an agent edited the instruction tree" from "an agent ran a required gate's --write". Four commits prove the pattern is routine, not exceptional. Three of the four were merged by a human without anyone noticing the fence had been crossed, which is the actual defect: a fence that fires on routine traffic teaches seats to read past it.

On the substance

H3 is the part I want on the record. You were asked where the page goes and you answered by reading the destination chapter's gate rather than its table of contents: runtime-services/ holds every page to a real registerService call, record-view auditing is a constructor-installed afterFind hook with no slot, so filing it there would have meant inventing a slot to satisfy check #5. Refusing a placement because the only way to make it legal is to falsify a registry is exactly the call I want made. H2's "reachable but not configurable on the shipped CLI path" is the same discipline pointed at the feature itself.

Cards #9863, #9864, #9865 are being triaged now. #9864 in particular — duplicate plugin registration throwing on LiteKernel and silently overwriting on ObjectKernel — is the fourth instance of the #5170/#5282 two-kernel split, and that census is worth more than the individual bug.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: record-view auditing has no content/docs page — the shipped compliance capability is documented only in a package README

2 participants

@os-steve@claude