Skip to content

fix(audit): warn when storage parser falls back to Custom for framework namespace#24

Merged
rrrodzilla merged 1 commit into
mainfrom
fix/audit-parser-fallthrough-warn
May 28, 2026
Merged

fix(audit): warn when storage parser falls back to Custom for framework namespace#24
rrrodzilla merged 1 commit into
mainfrom
fix/audit-parser-fallthrough-warn

Conversation

@rrrodzilla

Copy link
Copy Markdown
Contributor

Summary

All four audit storage backends ended AuditEventKind reconstruction with a Custom catch-all that silently wrapped any unknown event-kind string. For genuinely user-defined kinds this is correct, but for framework-owned namespaces (auth.*, http.*, account.*, config.*) it swallows version-skew bugs without signal: a downstream emitter on a newer acton-service writes a new kind, an older reader returns Custom("auth.token.missing"), and any Rust consumer pattern-matching on the typed variant misses the event.

This PR factors the catch-all logic into two pub(crate) helpers in audit/storage/mod.rs:

  • looks_like_framework_kind(s) — true if the stored string begins with one of the four framework-owned prefixes.
  • parse_custom_kind(s) — strips the custom. prefix (preserving prior round-trip behavior for user-defined kinds) and emits a tracing::warn! naming the exact stored string when looks_like_framework_kind is true.

All four parser catch-alls route through parse_custom_kind. The warn fires once per parsed event, names the offending string, and is structured (stored_kind = %s), so operators can pipe it into their existing log-aggregation alerts.

Closes #20.

Behavior

  • User-defined custom kinds (custom.user.exported_data, billing.invoice.paid): unchanged. Strip custom. prefix when present, no warn.
  • Framework-owned kinds the parser knows (auth.login.success, etc.): unchanged. Match the typed arm before the catch-all.
  • Framework-owned kinds the parser doesn't know (auth.token.missing from an older reader, or any future addition): now emit tracing::warn! and return Custom(name) as before — the value-shape is unchanged, only observability improves.

Test plan

Five new unit tests in audit::storage::helper_tests cover:

  • framework_prefixes_detected — all four prefixes detected.
  • non_framework_prefixes_ignoredcustom., bare user strings, empty string.
  • parse_custom_strips_custom_prefix — preserves user-defined custom round-trip.
  • parse_custom_preserves_unprefixed_user_strings — unrecognized non-framework strings pass through.
  • parse_custom_passes_through_framework_strings_for_visibility — framework-prefixed unknowns preserve their string (warn is verified via manual tracing-subscriber inspection; a tracing-test dep is overkill here).

All five pass under --features "full,audit". The full --features full CI run also passes clean (audit module isn't compiled there, but the helpers compile fine and clippy is green under both feature sets).

  • cargo clippy -p acton-service --all-targets --features full -- -D warnings — clean
  • cargo clippy -p acton-service --all-targets --no-default-features --features "full,crypto-ring" -- -D warnings — clean
  • cargo nextest run -p acton-service --features full — 520/520 pass
  • cargo nextest run -p acton-service --features "full,audit" — 5 new helper tests pass (one pre-existing extensions test fails on origin/main under this feature combo; unrelated to this PR)

Note for follow-up

The --features full CI matrix does NOT include audit, so any test that needs the audit feature (mine, and the existing ClickHouse roundtrip test) doesn't run in CI. Worth a separate CI matrix entry to cover audit code paths — would have caught the missing parser arms in #15 earlier.

…rk namespace

All four storage backends' AuditEventKind parsers ended with a Custom
catch-all that quietly wrapped any unknown event-kind string. For
genuinely user-defined custom events this is correct, but for the
framework-owned namespaces (auth.*, http.*, account.*, config.*) it
silently swallows version-skew bugs — a downstream emitter on a newer
acton-service writes a new kind, an older reader returns
Custom("auth.token.missing"), and any consumer matching on the typed
variant misses the event with no signal.

Factor the catch-all logic into pub(crate) helpers in
audit/storage/mod.rs:

- looks_like_framework_kind(s) — true for strings beginning with one of
  the four framework-owned prefixes.
- parse_custom_kind(s) — strips the "custom." prefix (preserving the
  prior round-trip behavior for user-defined kinds) and emits a
  tracing::warn! when looks_like_framework_kind is true, naming the
  exact stored string so operators can grep for it.

All four parser catch-alls route through parse_custom_kind. Add unit
tests covering both helpers across the framework-namespace, custom-
namespace, and bare-user-string cases.

Closes #20
@rrrodzilla rrrodzilla merged commit 93300be into main May 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

audit: storage parser Custom fallthrough silently swallows framework-owned namespaces on version skew

1 participant