Repository navigation
finding: record-view auditing has no content/docs page — the shipped compliance capability is documented only in a package README #9540
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentationand removed
on Aug 18, 2026 os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsFirst-touch grading: promoted to
pm:queue,domain:devx, type Task. Ruling between the card's two dispositions: add the docs page. Rationale on the axes — the capability was built for a regulated-industry buyer and a coverage evaluation from the docs site currently gets silence on exactly the edges that matter (record-detail scope, per-object opt-in, no field values, system-elevated exclusion); the content already exists source-verified in the plugin README, so the cost is a mirror, not new research. Scope: one page (or a section on an existing capability page) undercontent/docs/, mirroring the README's "Record-view auditing" section; no code, no contract change.Size/model suggestion: S,
mode:subagent.
Generated by Claude Code
Claiming this card.
- Session:
session_01XqDQYVU5smx29ts9pAErja - Branch:
claude/issue-9540-record-view-audit-docs - Container & model: M, mode:subagent, model: opus
Plan: first measure the shipped record-view auditing surface against the actual
plugin-auditimplementation (H1), then decide whether this is a new page incontent/docs/kernel/runtime-services/, a section on an existing page, or somewhere else entirely (H3 — a page in that chapter needs a realregisterServiceslot to satisfy the index gate's fifth check). Reporting back on this issue as a comment before the final report.Generated by Claude Code
Generated by Claude Code
- Session:
- added a commit that references this issue
on Aug 19, 2026 { "issue": 9540, "status": "done", "branch": "claude/issue-9540-record-view-audit-docs", "pr": "https://github.com/objectstack-ai/objectstack/pull/9860", "premise_still_valid": true, "summary": "H1 (shipped behaviour, from read-audit.ts / audit-plugin.ts / sys-audit-log.object.ts): a record-view row is written by ONE afterFind hook that AuditPlugin registers on exactly the objects named in readAudit.objects, filtered against the derived AUDIT_EXCLUDED_OBJECTS list (refusals warn, naming the object). A read qualifies only when it materialized exactly one record AND its predicate pinned the primary key through an AND-closure walked to depth 8; $or/$not anywhere disqualifies; list/search never qualify. The hook enqueues and returns, awaiting nothing; a batcher persists via engine.insert('sys_audit_log', rows, {context:{isSystem:true}}), flushing at maxBatchSize (50) or flushIntervalMs (2000ms), with destroy() draining the tail. The row carries action:'read', created_at = the VIEW instant (not the flush instant), user_id, object_name, record_id, tenant_id (the viewed record's organization_id, falling back to the session's), plus organization_id and actor where the columns exist; old_value/new_value stay null deliberately because afterFind runs ahead of SecurityPlugin's masking and ctx.result is pre-mask plaintext. Two declared boundaries write no row: session.isSystem === true, and a read with neither userId nor actor. Failure posture is post-PR #9750 and I documented it as it is now: buffer overflow past a fixed 10000 ceiling drops the OLDEST and warns once; a failed write loses the batch and reports once, reaching for logger.error and falling back to logger.warn (never into silence), never retried. maxBufferedEvents is NOT forwarded by AuditPlugin, so through the plugin that ceiling is fixed, not a knob -- the README's 'three keys' is right and I stated the ceiling as fixed. H3 VERDICT: this does NOT belong in content/docs/kernel/runtime-services/. That chapter's gate holds every page's declared Registry slot to a real production registerService call, and record-view auditing has no slot -- it is a constructor-installed hook. The plugin's only slot is 'audit', whose whole surface is recordAuthEvent() for login/logout. A page there would have required inventing a slot to satisfy check #5, so I did not put one there; check:runtime-services-index stays green and untouched (ruling 3 honoured). It also does not belong ON audit-service.mdx, which documents the slot -- merging a non-slot surface in re-creates the exact confusion #9534/PR #9587 fixed. The page went to content/docs/permissions/ (Permissions & Identity), the chapter capabilities/permissions.mdx already routes readers to for 'the full model' including audit, and the chapter holding field-level-security and system-context, the two pages this capability interacts with directly. I added a 3-line pointer on audit-service.mdx instead, matching the disambiguation that page already does for the settings audit sink. H2 (user-facing surface): reachable but NOT configurable on the shipped CLI path -- serve.ts:2440 auto-registers `new AuditPlugin()` with no options and there is no appAuditPluginOptions(config) helper, unlike SecurityPlugin's; zero call sites supply readAudit anywhere in packages/, examples/ or apps/. An app config's plugins array CAN supersede it, but only via an undeclared name-keyed last-wins overwrite in ObjectKernel.use (LiteKernel.use throws on the same input), so the page deliberately does not teach that; it says the opt-in is given where you compose the kernel and that os serve passes no options. Filed as #9863 and #9864. H4 (docs-audit scope): check:docs-audit-scope went red on the new page exactly as predicted (179 -> 180) and I regenerated the block with the gate's own --write; the diff is one line. NOTE A CONFLICT for the PM: that block lives in .claude/workflows/docs-accuracy-audit.js, which ruling 5 puts off-limits, while H4 asks for the scope to be updated. I resolved it by precedent rather than preference -- it is a generated artifact the gate rejects if hand-edited, and every prior page-adding PR regenerates it identically (e2b3c831e, f049f7fd4, ece873001, the last adding system-context.mdx to this same chapter). Called out in the PR body so it can be reverted in one line if the ruling was meant to cover generated blocks too. One unrelated observation: a bot appended a second signature footer to the PR body after creation; I left it alone per basic rule 2.", "tests": "Docs-only card: no package build/test applies, so no heavy-verify lock was taken. Gate union re-run AFTER the final commit and quoted from that run -- HEAD ae67ad006, all 15 PASS: 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, and spec: empty-state, liveness, strictness-ledger, variant-docs (UNION_FAIL=0 at ae67ad006). Gate list was DERIVED from the actual changed paths via `node scripts/pm/dispatch-gates.mjs`, not recalled -- that derivation added pm-governed-merges, skill-frame-sync, doc-formula-expressions and the four spec-liveness gates off the .claude/ and content/docs/ triggers, and I ran everything it named that my diff touches. Baselines before editing were green too, so the greens are not pre-existing-red masking: check-doc-anchors 251 fragments across 398 files; check-runtime-services-index '8 chapter pages ... all enumerations agree'; check-audit-scope '179 hand-written doc(s)' -> after the change '180 hand-written doc(s) (+1 added, -0 removed)'. Ruling 3 verified specifically: check:runtime-services-index PASS both before and after the audit-service.mdx pointer, which changes prose only and touches no enumeration, Registry slot bullet or Stability label. Control-byte discipline: targeted `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over all six changed files returned no matches, and check:nul-bytes scanned 6282 files clean. No ablation applies to a docs card, so none is claimed.", "open_questions": [], "out_of_scope_findings": [ "filed as #9863: record-view auditing has no configuration path on the `os serve` boot path -- the CLI registers AuditPlugin with no options and there is no appAuditPluginOptions(config) counterpart to the SecurityPlugin helper", "filed as #9864: duplicate plugin registration throws on LiteKernel and silently overwrites on ObjectKernel -- a fourth instance of the documented two-kernel semantic split (#5170 / #5282 family), and the undeclared behaviour #9863's half-path depends on", "filed as #9865: sys_audit_log's shipped `record_views` list view declares an `ip_address` column that a `read` row can never populate -- ip_address is stamped only by auth-event-audit.ts, so the column is empty for every row that view can show" ] }Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026
Observation-class finding, recorded while completing #9517's residual half (documenting the read-audit surface in
packages/plugins/plugin-audit/README.md). ⛔ Not a defect claim — nothing states anything false. Filing because the measurement is cheap to lose and expensive to re-take.Measurement
Record-view auditing landed with #8992 / PR #9515 (
5126e795d). Onorigin/mainat53fc09922, the docs corpus has no page for it:The seven
content/docspages that mentionsys_audit_logat all areui/setup-app.mdx,protocol/kernel/config-resolution.mdx,kernel/runtime-services/audit-service.mdx,deployment/production-readiness.mdx,plugins/packages.mdxand two release pages. None describes thereadaction, the per-object opt-in, or the record-detail scope.content/docs/capabilities/permissions.mdx:23describes audit as "records who changed what, when, with old and new values". That is not false — it is a write-side description that has simply not been extended. I am deliberately not calling it drift.Why it might matter, stated narrowly
The capability was built for a regulated-industry buyer, and the only place its scope is written down is a package README on npm. The scope has real edges someone evaluating coverage needs — record-detail views only, per-object opt-in, no field values recorded, system-elevated reads excluded — and a coverage question answered from the docs site alone currently gets silence rather than those edges.
⇒ Two honest dispositions, both fine, which is why this is a
findingand not queued:check:affected-docsis green on the PR that documented it, so no gate believes a docs page is owed.Refs: #9517 (the README card) · #8992 / PR #9515 (the capability) · #9534 (a separate, real mismatch on
audit-service.mdx).