Skip to content

Persisted main quota pairs an account-bound snapshot with an unbound observation timestamp #177

Description

@iceteaSA

getPersistedMainQuota returns mainQuota — which carries an embedded accountIdentity — alongside the top-level mainQuotaCheckedAt, which carries no account binding of its own. QuotaManager.seedMainFromStorage consumes that pair and uses the timestamp to build QuotaEntry.checkedAt and refreshAfter, where it acts as the persisted-vs-current ordering fence (quota-manager.ts:565 and :584).

So a caller can validate the identity on one field and then trust a second field that a concurrent cross-account write may have already moved.

Consequence

A mixed state — quota belonging to account A, timestamp belonging to account B — lets the persisted A entry win the cache ordering fence against a newer in-memory A entry, and keeps stale A poll-owned fields resident until an observation later than B's timestamp arrives.

What it does not do, so the severity is not overstated: strict identity checking still prevents cross-account quota from being served through getMain, and each window's own checkedAt caps refreshAfter, so the mixed timestamp alone cannot suppress a refresh that is otherwise due.

Reachability

The ordinary request path reaches it, with no additional race needed once the mixed state exists:

harvestQuotaHeaders -> persistPushedQuota -> refreshSidebarQuota -> updateStorage -> seedMainFromStorage

Relationship to the publication defect

This is the second instance of one shape. The first was in persistPushedQuota, where the published feed entry validated mainQuota.accountIdentity and then adopted the unbound mainQuotaCheckedAt, so a feed entry for account A could carry account B's observation time. That one is fixed — publication now always uses the request's own entry.checkedAt — and it was reachable in practice: it surfaced as an intermittent CI failure that took several runs to catch, because removing ~300ms of network latency from the test suite made the interleaving reachable where it previously was not.

Fixing the publication site does not fix this one; they consume the same unbound pair independently.

Two directions

  1. Bind the timestamp to the account in persisted state, so an unbound read becomes impossible. Cleanest, but it is a schema change and needs a compatibility decision for existing state files.
  2. Stop trusting the top-level timestamp independently and derive the entry's observation time from the account-bound quota itself, keeping only account-bound timestamps in the fence.

The second is smaller and needs no migration; the first removes the shape rather than the instance. I have not implemented either — filing rather than bundling it into an open PR, since both touch persisted-state semantics and deserve their own review.

Found during a five-way independent review of the network-guard branch.

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