0138 — Give the staff "All companies" conversation list its own recency index without test traffic

Context

Platform staff with no company picked read /conversations as one stream: the recency index (GSI5, ADR-0049), which holds every conversation. The page asks for no test traffic, and the route drops test rows only after reading them (route.ts, the canonical isTestTrafficConversation filter). When bench and harness rows dominate the head of the index, the walk spends its five-read budget and returns a short page with a cursor. The table then fires a second request to fill it. The first page takes about 4.9 s against the 2.5 s goal (Phase 11 C2; lane CV audit).

Three options were weighed:

Decision

Add conv-nontest-recency-index:

HASH GSI8PK = the constant CONV_RECENCY_NONTEST (one partition, for ADR-0049's reason)
RANGE the existing GSI5SK. It is reused, not copied.
Projection ALL, like GSI5

Membership. A conversation meta row carries GSI8PK exactly when both hold:

isIntrinsicTestTrafficConversation is the config-free part of the canonical predicate, and it lives in the same module. It covers:

The canonical predicate is now defined as that, OR the caller's Property.isTest set, OR the configured internal-tester senders. So there is still one predicate, and the index rule is literally part of it.

Invariant: the index is a superset of what the staff read shows. A row the write-time rule calls test is test for every reader. A row it calls real may still be dropped by the read's full predicate, which runs after the read, unchanged. The two configuration markers are left out of the write-time rule on purpose:

Stamping either would leave real conversations missing from the index.

Unassigned conversations are members. Membership never consults the org, and it consults the building only for the historical bench ids.

Why reuse GSI5SK as the sort key:

Writers:

Rollout follows the prospect-list precedent (b89979462, b90011572). Each step ends dark, and the read switches only on coverage:

  1. Writer stamping ships.
  2. The create/backfill/revert scripts ship.
  3. Stage rehearsal.
  4. The index is created on propflow-prod (Gera's go).
  5. Prod dry-run and a read-only coverage harness. The harness checks:
    • G1: no non-test GSI5 row is missing GSI8PK;
    • G2: no test row carries it;
    • G3: the index's id set equals G1's non-test set;
    • G4: the newest 500 rows match row for row under the full predicate;
    • G5: the share still dropped at read.
  6. The prod backfill --apply (Gera's go).
  7. Only then does staff All with includeTest=false read the new index. includeTest=true stays on GSI5.

Consequences

Alternatives considered

Amendment 2026-09-29 — membership also excludes Property.isTest buildings

Decision: Option A of the lane-IX design note. The decider is Astra (receipt fb983eb85), and the coordinator verified its claims. This supersedes the Stamp from Property.isTest now rejection above and resolves the Residual under Consequences.

Why. Prod was measured read-only on 2026-09-29 under the original rule:

The alternatives, measured on the same data:

The rule, as amended: a row carries GSI8PK iff all three hold:

Unassigned rows have no building, so they are members as before.

The write.

The invariant still holds: the index is a superset of what the staff read shows. The read's full predicate uses the same Property.isTest set.

Flips. When a building's flag flips, its conversations keep the old answer until they are re-stamped. The flip re-stamper is a separate part: a durable workflow triggered from the one isTest writer, which re-stamps the building after the cache TTL and reports done only when coverage is clean for that building. Until it ships, a flip is corrected by the backfill's repair mode.

Rollout numbers (prod, 2026-09-29):