Skip to content

Role/user/schema mutations are eventually-consistent across worker threads (async ITC not awaited) — spurious 403s + flaky tests #1497

Description

@kriszyp

Summary

Mutating operations that change auth or schema state — add_role / alter_role / drop_role, user create/alter/drop, and schema DDL — return HTTP 200 after the durable write + a local cache update + an ITC broadcast send, but before other worker threads have rebuilt their in-memory caches. The cross-worker propagation is fire-and-forget, so a request routed to a lagging worker observes the old state.

This is an internal consistency-invariant gap, not just a test artifact. The invariant we'd expect: once a role/user/schema mutation returns success, the change is in effect for subsequent requests. Today it's eventually-consistent across the worker pool.

Where it lives

  • security/role.tsalterRole (~L122), dropRole (~L189), add-role path: await signalling.signalUserChange(new UserEventMsg(process.pid)).
  • utility/signalling.tssignalUserChange / signalSchemaChange: call serverItcHandlers.user(...) (updates the local worker only, synchronously) then return sendItcEvent(...) (broadcasts to other workers). The awaited value is the send, not remote acknowledgment.
  • security/user.ts — each worker's usersWithRolesMap is rebuilt by setUsersWithRolesCache() only when it processes the inbound ITC event.
  • security/permissionsTranslator.jstranslateRolePermissions expands per-attribute permissions from the (possibly stale) cached role; a stale role with no attribute_permissions for a table yields an empty attr-perm map.

Observable symptoms

  1. Spurious 403 on grant. Admin grants attribute access via alter_role, gets 200, then a query for the newly-granted column hits a lagging worker → 403 … "Attribute 'X' does not exist on 'schema.table'" (the empty attr-perm map path in checkAttributePerms, utility/operation_authorization.ts:765-773).
  2. Brief continued access on revoke. A revoke is similarly delayed on lagging workers — a short window where the old (broader) permissions still apply. (Live subscriptions have separate continuous re-auth; this is about request-path auth.)
  3. Intermediate/partial-looking states. Because different workers can be at different role versions, a 403 can appear with a different body than the final state (e.g. empty unauthorized_access, a subset of invalid_schema_items).
  4. Schema variant. The same pattern affects schema propagation — e.g. describe_all on a worker that hasn't yet seen a just-created schema returns it as missing/empty (integrationTests/apiTests/blob.test.mjs "all three device-type schemas are visible in describe_all").

CI impact

This is the root cause behind the recurring Integration Tests shard 5/6 flakes (and others) in northwind.test.mjs (cross-schema-join role tests) and the schema-visibility flake in blob.test.mjs. Test-side mitigations are in flight (#1474, #1475 — poll for propagation with waitFor), but those are per-call-site whack-a-mole and easy to get subtly wrong (the first waitFor predicate in #1475 was too loose and still flaked). Related closed test-side issue: #1222.

Proposed direction (needs design buy-in)

Make mutating role/user/schema ops await cross-worker propagation before returning, OR expose an internal propagation barrier:

  • Add an ack to the ITC round-trip: sendItcEvent resolves once all live workers have processed the user/schema event (each replies after setUsersWithRolesCache() / schema reload). Mutating ops await that instead of the bare send.
  • Bound it with a timeout + worker-death handling so a stuck/dying worker can't hang an admin op (degrade to best-effort + log, since the durable write already succeeded).

Trade-offs

  • Adds latency to admin/DDL ops (rare, so likely acceptable) and some ITC complexity.
  • Eliminates the entire mutate-then-read flake class (auth and schema) at the source, instead of per-test polling.
  • Alternative (lighter) option: keep ops async but provide a documented/internal "propagation settled" signal the test harness (and careful clients) can await.

Filing for discussion before any product change — the test-side PRs (#1474/#1475) unblock CI in the meantime.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions