Skip to content

refactor(plugin-audit)!: retire export / permission_change from the sys_audit_log action enum (#8147) - #8200

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-8147-audit-action-enum-retire
Aug 12, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-8147-audit-action-enum-retire

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Part of #8147

⚠️ Part of, not Fixes — deliberately. This lands 2 of the 3 values the
2026-08-12 ruling named. The third (import) is referred back to the maintainer
because its stated premise is measurably false; see "The escalation" below.
Merging this must not close #8147.

What landed

export and permission_change are retired from the sys_audit_log.action
enum. Neither has ever been written. The repo has exactly two
sys_audit_log writers:

writer vocabulary
packages/plugins/plugin-audit/src/audit-writers.ts (generic hook writer) actionFor() maps afterInsert/afterUpdate/afterDelete to create/update/delete and nothing else
packages/plugins/plugin-auth/src/admin-import-users.ts action: 'import', run-level, record_id: null

Neither emits export or permission_change, so the shipped list views
filtering on them and the dashboard tiles counting them were permanently empty —
the visible product defect the ruling names (空 widget + 永远查不到东西的过滤器).

  • action enum: export + permission_change removed
  • auth_events list view filter: narrowed to ['login', 'logout']
  • config_changes list view filter: export dropped, config_change and import kept
  • plugin-audit translation bundles regenerated (4 locales, via check-i18n-bundles --write — not hand-edited)
  • ADR-0087 registration: semantic migration audit-log-action-enum-retired on D3 step 17, with spec-changes.json and the upgrade guide regenerated
  • new pin test sys-audit-log-retired-actions.test.ts
  • service-analytics routing fixture reseeded off permission_change

Why a semantic entry and not a D2 conversion

sys_audit_log is a platform-owned append-only object whose every field is
readonly: true. Nobody authors an audit row and nobody authors this enum, so
there is no source for the chain to rewrite — the same disposition
BatchOptions.validateOnly and the notification cursor already take in this
major. As an enum-VALUE retirement nothing lands in RETIRED_KEYS_BY_MAJOR and
the four surface ratchets are byte-identical by construction (no def and no
authorable key changed) — verified, they did not move.

The escalation: import is NOT retired

The ruling named import on the premise 无此 feature. That premise is false:

  • plugin-auth/src/admin-import-users.ts writes a real run-level audit row on
    every admin user-import run, and its docblock records the design decision
    (corrected rationale, superseding an earlier stale claim) that this row is a
    deliberate complement to the per-row rows, not a duplicate.
  • Case W4 of packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts
    asserts that row exists, with record_id: null and metadata.event === 'user.import_run'.

Retiring it would make the enum deny a value the platform writes — and do it
silently: validateRecord skips readonly fields on insert, every field
here is readonly, so nothing rejects the write and nothing goes red. The result
would be written-but-not-declared, which for an audit surface is strictly worse
than the declared-but-never-written defect this card exists to fix, because the
row exists and the contract denies it. config_changes is also the only shipped
view that lists those rows.

Ablation (predicted before measured)

Restoring both values to the enum, everything else held:

surface predicted measured
new pin test RED, exactly 2 failures (the "not declared by the enum" cases); filter/dangling/import cases stay green RED, exactly 2, exactly those. 205 others passed
check:i18n (plugin-audit) RED, bundle drift RED — DRIFTED (4)
check:spec-changes / check:upgrade-guide / check:authorable-surface GREEN — nothing cross-checks the ADR-0087 entry against the enum PASS / PASS / PASS
service-analytics routing test GREEN — fixture uses delete, never validates against the object schema 1655 passed

Zero divergence. The negative arms matter as much as the red one: the ADR-0087
registration is not verified against reality by any gate
, and the
service-analytics fixture is not a detector of this retirement. The only
mechanical detectors are the new pin test and the i18n bundle ratchet — which is
precisely why the pin test is in this PR, with its expectations written as
literals so that expectation and reality do not derive from the same source.

Gates

check:nul-bytes, check-adr-0087-registration, check-changeset-no-major,
check-empty-changeset, check:migration-registry, check:spec-changes,
check:upgrade-guide, check:authorable-surface, check:api-surface,
check:liveness, check:i18n, check:adr-anchors, check:merge-driver,
check:cross-package-test-inputs, check:test-source-alias,
check:docs-audit-scope, check:engine-double-contract,
check:query-options-erasure — all PASS. check:type-check-debt PASS from the
repo root after a full build closure (no entry above its ceiling; the surplus
notes are pre-existing and in packages this PR does not touch — ledger not
lowered). plugin-audit 207 tests pass, service-analytics 1655 pass,
plugin-audit + spec typecheck pass.

Downstream, deliberately untouched

⚠️ Both are scoped as "the three retired values". If the maintainer upholds this
PR's finding, both need correcting to two — dropping import from the
console badge map would leave real, existing rows rendering without a label.

Generated by Claude Code


Generated by Claude Code

…_audit_log action enum (#8147)

Retires the two action values with no writer anywhere in the repo, per the
maintainer ruling of 2026-08-12 on #7675. Narrows the auth_events and
config_changes list-view filters, regenerates the translation bundles, and
registers the retirement under ADR-0087 as `audit-log-action-enum-retired`.

`import` is deliberately NOT retired: plugin-auth's admin user-import writes a
real run-level row with that action, pinned by dogfood case W4. Escalated on
the issue for a maintainer ruling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEVB6w7D7uCszR9Mw1BL73
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 12, 2026 8:00pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-audit, @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/plugin-audit, @objectstack/spec)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/plugin-audit)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-audit, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

⛔ 7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-audit, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 12, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 12, 2026 20:32
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit 344a22a Aug 12, 2026
27 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8147-audit-action-enum-retire branch August 12, 2026 20:49
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…he last_login_at update (objectstack-ai#8244)

* feat(audit): write login/logout rows to sys_audit_log and attribute the last_login_at update (objectstack-ai#8144)

sys_audit_log.action declares `login` and `logout`, the shipped `auth_events`
list view filters on them, and two System Overview widgets chart them — but
nothing ever wrote either row: the audit writers subscribe to the ObjectQL CRUD
lifecycle, so create/update/delete/restore were the only actions that could
materialize. The whole trace a sign-in left behind was an unattributed
`update sys_user` row (user_id null) diffing last_login_at.

- plugin-audit registers the `audit` service — the ledger's ingress for events
  that are not CRUD. The row shape stays owned by plugin-audit; the caller hands
  over an EVENT with a closed `login | logout` union, which is the only
  structural protection available on an object whose action enum nothing
  validates in either direction (objectstack-ai#8203).
- plugin-auth emits from better-auth's session lifecycle hooks:
  session.create.after => login (covers every sign-in method, not just
  /sign-in/email), session.delete.after under /sign-out => logout. Revokes, bans,
  erasure and expired-row collection are deliberately NOT logout — they already
  carry their cause on the ADR-0069 D4 tombstone, and naming them logout would be
  a wrong record rather than a vague one.
- stampLastLogin now carries attributedUserId (objectstack-ai#4586), so the last_login_at diff
  row names the person who signed in. Attributed rather than excluded: the write
  still authorizes as the system, and suppressing it would delete the
  last_login_ip trail repo-wide.

Neither package depends on the other; a stack without plugin-audit writes no auth
rows, exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEVB6w7D7uCszR9Mw1BL73

* test(dogfood): read the auth_events filter from the running registry (objectstack-ai#8144)

objectstack-ai#8200 retired `permission_change` / `export` from the action enum and narrowed
the `auth_events` view in the same PR. A hard-coded copy of the old filter kept
querying a value nothing can hold while still reporting success — the view has
exactly the shape that hides it, since the login rows alone satisfy the
assertion. Read the shipped filter instead, so the test tracks the view.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEVB6w7D7uCszR9Mw1BL73

* test(plugin-auth): type the audit sink spy so its call log is a real tuple (objectstack-ai#8144)

`vi.fn(async () => undefined)` types `mock.calls` as `[][]` — a zero-length
tuple — so every `calls[0][0]` in this file reached past the end of a tuple the
type system believed was empty (TS2493 x3), and the dereference that followed
was `possibly undefined` (TS18048 x3). Reading the argument back is the entire
point of these cases, so the fix is to declare what the spy receives rather than
to soften the read: the spy's implementation now names its parameter, and the
call log is pulled through `recordedEvents` / `firstEvent`, which name the
"never called" case instead of letting it surface as a TypeError.

Pinning the element type to `AuthSessionAuditEventInput` also makes these
assertions type-check against the real event surface instead of `any`: a
renamed field now fails at compile time rather than quietly comparing
`undefined` to `undefined`. One `(c: any[])` map goes away with it.

Behaviour unchanged — 1113/1113 plugin-auth tests pass, same 20 cases.

Why now: objectstack-ai#8225 lowered this package's TEST_DEBT ceiling 131 -> 111 after this
branch was cut, so these six errors stopped being slack and became a violation
in the merge queue. Measured at 117 against the merged tree, 111 after this
commit — exactly the ceiling, ledger untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEVB6w7D7uCszR9Mw1BL73

---------

Co-authored-by: Claude <noreply@anthropic.com>
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

audit-log (C): retire export / import / permission_change from the sys_audit_log action enum and its in-repo consumer surfaces (ADR-0087 registration)

2 participants