Skip to content

auth_events has principal columns the read surface never fills, so version-gated-out reports carry no identity #42

Description

@iceteaSA

auth_events carries principal_kind and principal_id (added by the migration at crates/credentials-core/src/store.rs:199-200). The read surface never writes them.

crates/credentials-module/src/read_surface.rs:1181 constructs the observation with four fields:

let observation = credentials_core::store::AuthObservation {
    kind: if refreshable { ... },
    provider_status: Some(params.provider_status),
    detail: None,
    reporter_source: params.reporter_source.as_deref().map(ReporterSource::from_wire),
};

No principal field exists on the struct, so the columns are structurally NULL on consumer reports rather than merely unpopulated. On this deployment: 36 of 36 consumer_report_stale rows NULL, 133 of 133 rows NULL across every kind.

Why it matters is specific, not cosmetic. Identity is available on the audit row — actor holds conn-N — but audit rows are written only when the version gate passes (mark_stale_if_version_reported, store.rs:1656-1698: append_auth_event_tx runs unconditionally, append_audit_tx only if n > 0). So the reports that DO get attributed are the ones that changed state, and the reports that carry no identity are exactly the version-gated-out ones.

That is the wrong way round for diagnosis. Two distinct consumer behaviours produce an identical applied=0 row:

behaviour row
a consumer in flight with material the vault rotated past — reports v(n-1), honest applied=0, no audit row
a consumer repeatedly re-serving a version it already reported — stale_pending already 1 applied=0, no audit row

The first is benign and expects roughly one row per consumer per rotation. The second is a consumer-side defect and can produce many rows from one connection. Distinguishing them requires knowing how many distinct connections produced the cluster, and that is precisely what the gated-out rows do not record.

Concretely: an 8-row applied=0 cluster from 8 connections means eight caches went stale together and the fence did its job. The same 8 rows from one connection means a consumer is looping. Today those read identically.

Suggested shape: carry the connection actor onto AuthObservation and write it into the existing columns for report_auth_failure. The audit row already computes an actor at read_surface.rs:1175, so the value is in scope at the call site — this is a plumbing change, not new identity machinery.

Noting two constraints rather than assuming them away:

  • auth_events is trimmed to 64 rows per credential per insert (trim_auth_events_tx, store.rs:3710), so attribution is only ever available on the recent tail.
  • conn-N identifies a connection, not an OS process. Whether one consumer process holds one connection is a consumer property, so the column would support "how many distinct connections" and not, by itself, "how many processes".

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions