Mutation Audit
Applies whenever the project has (or should have) an audit store — with or without multi-tenancy. Authz/scope → scoped-authz. Secrets in payloads → secrets-hygiene.
Prefer existing audit helpers; do not invent a third store or custom inserts.
When to emit
Any feature that writes data, changes auth/session/roles, or performs an operator/admin action. Reads, pure UI, and idempotent no-ops do not need new events.
If intentionally skipping audit → decision-records with why. Silent skips forbidden.
Baseline + semantic
- Go through the project's data layer so auto-emit runs when the project supports it (
collection.create|update|delete style).
- Still add a rich semantic event for meaningful domain actions (
user.invite, role.create, settings.update, …). Auto-emit alone is not enough for product/compliance readability.
- High-churn / excluded collections: emit semantic events from the domain service when the action matters.
Event shape (adapt to project helpers)
// Tenant / org-scoped example
await recordAuditEvent(ctx, {
action: "role.create", // dotted <domain>.<verb>
objectType: "role",
objectRef: doc._id, // entity id, not email/code
before: sanitizeAuditSummary(previous),
after: sanitizeAuditSummary(doc),
reason: input.reason ?? null,
})
- Actor / request id / impersonation from context — do not forge.
before / after / metadata via sanitize helpers only — never passwords, tokens, secrets, raw files, or huge free text.
- Operator/control plane: use the project's control audit helper with
action, target type/id, reason, small non-secret metadata.
Details → REFERENCE.md.
Non-negotiables
- Audit is append-only — never update/delete audit rows.
- Audit failure must not block the business write (follow existing swallow/report helpers).
- Emit in the same service function REST and Server Actions share — no fork.
- Ship audit in the same change set as the mutation when adding new write paths; cover the audit path in tests when practical.
Finish checklist
1---2name: mutation-audit3description: Requires append-only audit events for data writes, auth/session/RBAC changes, and operator actions — with sanitized payloads and no secrets. Use when adding mutations, Server Actions, write APIs, auth changes, role changes, or operator/admin actions; independent of multi-tenancy.4---56# Mutation Audit78Applies whenever the project has (or should have) an audit store — **with or without** multi-tenancy. Authz/scope → [scoped-authz](../scoped-authz/SKILL.md). Secrets in payloads → [secrets-hygiene](../secrets-hygiene/SKILL.md).910Prefer existing audit helpers; do not invent a third store or custom inserts.1112## When to emit1314Any feature that **writes data**, changes **auth/session/roles**, or performs an **operator/admin** action. Reads, pure UI, and idempotent no-ops do not need new events.1516If intentionally skipping audit → [decision-records](../decision-records/SKILL.md) with why. Silent skips forbidden.1718## Baseline + semantic19201. Go through the project's data layer so auto-emit runs when the project supports it (`collection.create|update|delete` style).212. **Still** add a rich semantic event for meaningful domain actions (`user.invite`, `role.create`, `settings.update`, …). Auto-emit alone is not enough for product/compliance readability.223. High-churn / excluded collections: emit semantic events from the domain service when the action matters.2324## Event shape (adapt to project helpers)2526```typescript27// Tenant / org-scoped example28await recordAuditEvent(ctx, {29 action: "role.create", // dotted <domain>.<verb>30 objectType: "role",31 objectRef: doc._id, // entity id, not email/code32 before: sanitizeAuditSummary(previous),33 after: sanitizeAuditSummary(doc),34 reason: input.reason ?? null,35})36```3738- Actor / request id / impersonation from context — do not forge.39- `before` / `after` / `metadata` via sanitize helpers only — **never** passwords, tokens, secrets, raw files, or huge free text.40- Operator/control plane: use the project's control audit helper with `action`, target type/id, reason, small non-secret metadata.4142Details → [REFERENCE.md](REFERENCE.md).4344## Non-negotiables4546- Audit is **append-only** — never update/delete audit rows.47- Audit failure must **not** block the business write (follow existing swallow/report helpers).48- Emit in the **same** service function REST and Server Actions share — no fork.49- Ship audit in the **same change set** as the mutation when adding new write paths; cover the audit path in tests when practical.5051## Finish checklist5253- [ ] Write via data layer / repository54- [ ] Semantic audit helper called (tenant, control, or auth helper)55- [ ] Stable dotted `action`; entity id refs56- [ ] Payloads sanitized — no secrets57- [ ] Skip (if any) logged via decision-records