Skip to content

feat(batch): add opt-in dedicated projection drain pump - #24712

Merged
Git-on-my-level merged 8 commits into
mainfrom
lane/pipeline-pump
Nov 5, 2026
Merged

Git-on-my-level merged 8 commits into
mainfrom
lane/pipeline-pump

Conversation

@Git-on-my-level

@Git-on-my-level Git-on-my-level commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Problem

Projection sweep supply is tied to serving instance count. Moving discovery onto dedicated instances needs an independent loop that cannot compete for maintenance/recovery leadership. This implements David's 2026-11-05 pump plan, sections 2 and 4; deployment shape remains pending.

Fix

  • Add serving_projection_sweep_enabled() using the shared kill-switch helper. BATCH_PIPELINE_SERVING_PROJECTION_SWEEP is on unless explicitly off; it gates only Serving.reconcile()'s sweep, preserving wakes, held drains, metrics and maintenance/recovery.
  • BATCH_PIPELINE_PROJECTION_PUMP_ENABLED=true selects one lean projection loop instead of the three serving loops, independently of BATCH_PIPELINE_SERVING_ENABLED. It never invokes reconcile, maintenance or recovery dispatch. Sweeps use run_blocking(sync_executor, ...); owed drains start on the event loop. After each sweep, it schedules discovered or owed projection follow-ups via start_followups(()) and the owed index flush on the event loop, before starting owner drains after all pages. Each completed tick, including a failed tick, updates omi_batch_projection_pump_last_tick_seconds (Unix timestamp, zero before first tick). It logs successful sweeps, owners requested and elapsed time; errors log and retry.
  • Pump metrics scrapes use refresh-only regardless of the serving/read-only metrics controls; they never invoke the reconcile coordinator on the configured pump role.
  • Bound pages/tick (default 1, range 1-4), sleep (default 20s, range 2-60s) and owner drains (default 8, range 1-16); malformed values retain defaults. Register all five env names, and regenerate the registry.

All unset defaults preserve today's behavior. projection_drain.py changes only its capacity constant and the config import. No deployment files or .github/** changes.

Validation

Code and hermetic tests only; no production access or GCP tier used.

Merged origin/main at 148bf4c1c57073dba4ec9a750415f2c72f5adb87 using a merge commit, without rebase. Six files had conflicts: batch config, flag budget, kill-switch helper, pipeline contract docs, policy tests and projection drain. Both #24710 claim-aware behavior and pump capacity accessor are retained. The drain implementation is byte-for-byte main after removing only the capacity accessor import and restoring the old constant. Main advanced during CI; a second merge includes f8edd96cdbe (#24716). Its four conflicts in flag budget, kill-switch helper, rendered registry and policy tests retain the new striping controls alongside the pump controls.

bin/omi-heavy run -- env BACKEND_UNIT_TEST_FILE_LIST=<file> bash backend/test.sh:

  • Pump: 92 passed.
  • Serving (including four pump metrics-route combinations): 61 passed.
  • Projection follow-ups: 36 passed.
  • Owner drain / claim-aware admission: 66 passed.
  • Metrics: 32 passed.
  • GMP sidecar: 117 passed.
  • Policy: 76 passed.
  • Projection simulation: 1 passed, 2 slow tests deselected by the standard runner filter.
  • Stage env ratchet: 7 passed.
  • Flag budget: 24 passed.

Additional post-merge suites: projection discovery 67, follow-up desync 36, follow-up reads 6, sweep owner chain 34, and timeliness 18 passed.

Total: 673 passed, fifteen isolated files (plus the same 2 slow simulations deselected). Registry validation passed; the env-ratchet baseline is unchanged. The enforced push gate passed 41 checks; pyright reported 0 errors; selected push-time unit tests passed.

The requested merge reconciliation and metrics-route P1 are included. The listed P2 follow-ups are outside this change; no new review round was started.

Risk

Enabling a pump adds discovery work and shared-executor load. Bounds are pages 1-4, tick 2-60 seconds and owner drains 1-16; defaults remain 1, 20 and 8. The pump stays disabled by default. Durable receipts, owner claims and fencing are unchanged.

Ramp plan

Owner: lead2, after David's deployment-shape decision. Studio safe start on a dedicated service:

  • 4 instances.
  • BATCH_PIPELINE_PUMP_SWEEP_PAGES=1.
  • BATCH_PIPELINE_PUMP_TICK_SECONDS=2.
  • BATCH_PIPELINE_MAX_OWNER_DRAINS=8 per instance.

Never set BATCH_PIPELINE_PROJECTION_PUMP_ENABLED=true on backend-integration itself: the toggle is service-wide. Keep BATCH_PIPELINE_SERVING_ENABLED false/unset on the dedicated pump service.

Cutover gate: owner_drain advanced >=20 rows/s for 30 minutes before disabling serving sweeps. Install and verify a missing/stale alert on the dedicated service's omi_batch_projection_pump_last_tick_seconds beforehand, including never-observed zero and missing instance series; a stale/missing alert must prevent cutover. Fallback: serving sweeps stay ON until both the throughput gate and heartbeat-alert prerequisite pass.

Alarm if Firestore read rate rises more than 50% over pre-cutover. Retain the owner_drain rows/s floor of 70% of pre-cutover; watch sweep elapsed time and executor saturation. Evaluate claim_busy share (no higher than pre-cutover) after serving sweeps are switched off, not during dual-running. This PR authorizes no deployment or cutover.

ROLLBACK

Env only, through new revisions, in this order:

  1. Restore serving sweeps ON on the existing service first (BATCH_PIPELINE_SERVING_PROJECTION_SWEEP=on).
  2. Set BATCH_PIPELINE_PROJECTION_PUMP_ENABLED=false on the dedicated service.
  3. Restore BATCH_PIPELINE_MAX_OWNER_DRAINS=8.

Failure-Class: none

These are pre-merge qualification corrections to the newly introduced pump role; no existing semantic failure-class definition matches the missing follow-up scheduling, pump-specific capacity bounds, and metrics-route role isolation.

View guided diff Turn on auto-fix

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-05T19:03:29.693946Z facdfd9 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 12 files

Confidence score: 5/5

  • test_batch_pipeline_projection_pump.py reloads production modules for every parametrized case, which makes the normal unit lane pay for repeated reloads. Mark test_owner_drain_module_capacity_from_env as slow.
  • feature-flag-registry.md still says the owner-drain cap is always 8, which is inaccurate when BATCH_PIPELINE_MAX_OWNER_DRAINS is set. Reword the row to say the cap comes from that setting.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/tests/unit/test_batch_pipeline_projection_pump.py">

<violation number="1" location="backend/tests/unit/test_batch_pipeline_projection_pump.py:87">
P3: Mark `test_owner_drain_module_capacity_from_env` as `slow`; it performs fresh production-module reloads for every parametrized case, violating the test-suite contract and making the normal unit lane pay the repeated reload cost.</violation>
</file>

<file name="backend/docs/feature-flag-registry.md">

<violation number="1" location="backend/docs/feature-flag-registry.md:531">
P3: The `BATCH_PIPELINE_OWNER_DRAIN_ENABLED` row still hardcodes "up to 8 owners at once", which is now wrong whenever `BATCH_PIPELINE_MAX_OWNER_DRAINS` is set. Reword that row to say the cap comes from `BATCH_PIPELINE_MAX_OWNER_DRAINS` (default 8).</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread backend/utils/batch_pipeline/serving.py Resolved
Comment thread config/feature-flags.yaml Resolved
patch.delenv('BATCH_PIPELINE_MAX_OWNER_DRAINS', raising=False)
else:
patch.setenv('BATCH_PIPELINE_MAX_OWNER_DRAINS', raw)
importlib.reload(projection_drain)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Mark test_owner_drain_module_capacity_from_env as slow; it performs fresh production-module reloads for every parametrized case, violating the test-suite contract and making the normal unit lane pay the repeated reload cost.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At backend/tests/unit/test_batch_pipeline_projection_pump.py, line 87:

<comment>Mark `test_owner_drain_module_capacity_from_env` as `slow`; it performs fresh production-module reloads for every parametrized case, violating the test-suite contract and making the normal unit lane pay the repeated reload cost.</comment>

<file context>
@@ -0,0 +1,235 @@
+                patch.delenv('BATCH_PIPELINE_MAX_OWNER_DRAINS', raising=False)
+            else:
+                patch.setenv('BATCH_PIPELINE_MAX_OWNER_DRAINS', raw)
+            importlib.reload(projection_drain)
+            assert projection_drain.MAX_OWNER_DRAINS == expected
+    finally:
</file context>

Comment thread backend/docs/feature-flag-registry.md Outdated
| `BATCH_PIPELINE_LANGUAGE_INHERITANCE_ENABLED` | Default on unless false; batch pieces inherit a known bound conversation language, else the user language, before per-piece LID | backend | env | open | — | — | — | — | keep | keep | — | dazheng |
| `BATCH_PIPELINE_LEADER_LEASE_ENABLED` | Default on unless false; one batch worker or serving replica per Firestore lease runs periodic recovery, drain, cleanup and quarantine discovery | backend | env | open | — | — | — | — | keep | keep | — | dazheng |
| `BATCH_PIPELINE_LONG_SPEECH_HANDOFF_ENABLED` | Default on unless false; a zero-word batch unit with >=5 s worker VAD speech whose intake STT language is unknown, multi or outside Parakeet TDT v3 skips the padded Parakeet retry and goes straight to quarantine recovery | backend | env | open | — | — | — | — | keep | keep | — | dazheng |
| `BATCH_PIPELINE_MAX_OWNER_DRAINS` | Default 8; concurrent owner drains per process, clamped to 1-32 at import; malformed keeps 8 | backend | env | closed | — | — | — | — | keep | keep | — | lead2 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The BATCH_PIPELINE_OWNER_DRAIN_ENABLED row still hardcodes "up to 8 owners at once", which is now wrong whenever BATCH_PIPELINE_MAX_OWNER_DRAINS is set. Reword that row to say the cap comes from BATCH_PIPELINE_MAX_OWNER_DRAINS (default 8).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At backend/docs/feature-flag-registry.md, line 531:

<comment>The `BATCH_PIPELINE_OWNER_DRAIN_ENABLED` row still hardcodes "up to 8 owners at once", which is now wrong whenever `BATCH_PIPELINE_MAX_OWNER_DRAINS` is set. Reword that row to say the cap comes from `BATCH_PIPELINE_MAX_OWNER_DRAINS` (default 8).</comment>

<file context>
@@ -528,6 +528,7 @@ and an explicit empty literal renders as `''`.
 | `BATCH_PIPELINE_LANGUAGE_INHERITANCE_ENABLED` | Default on unless false; batch pieces inherit a known bound conversation language, else the user language, before per-piece LID | backend | env | open | — | — | — | — | keep | keep | — | dazheng |
 | `BATCH_PIPELINE_LEADER_LEASE_ENABLED` | Default on unless false; one batch worker or serving replica per Firestore lease runs periodic recovery, drain, cleanup and quarantine discovery | backend | env | open | — | — | — | — | keep | keep | — | dazheng |
 | `BATCH_PIPELINE_LONG_SPEECH_HANDOFF_ENABLED` | Default on unless false; a zero-word batch unit with >=5 s worker VAD speech whose intake STT language is unknown, multi or outside Parakeet TDT v3 skips the padded Parakeet retry and goes straight to quarantine recovery | backend | env | open | — | — | — | — | keep | keep | — | dazheng |
+| `BATCH_PIPELINE_MAX_OWNER_DRAINS` | Default 8; concurrent owner drains per process, clamped to 1-32 at import; malformed keeps 8 | backend | env | closed | — | — | — | — | keep | keep | — | lead2 |
 | `BATCH_PIPELINE_MERGE_CATCH_UP_DEPTH` | Default 16; fresh batch uploads (capture ended at most 10 min before admission) commit ahead of the owner's pending catch-up rows, and catch-up commits wait while the owner already has this many committed rows awaiting projection | backend | env | closed | — | — | — | — | keep | keep | — | dazheng |
 | `BATCH_PIPELINE_MERGE_HEAD_WAIT_SECONDS` | Default 60; once a batch result has waited this long behind an unready earlier-captured merge head, the owner's ready results commit in capture order and the head appends later as late_capture | backend | env | closed | — | — | — | — | keep | keep | — | dazheng |
</file context>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 85b564880f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +195 to 196
if self.projector and serving_projection_sweep_enabled():
self.projector.sweep()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve async follow-up discovery when serving sweeps are disabled

With async projection enabled, cutting over to the documented pump/serving split can strand receipts after assignment. Fresh serving processes with BATCH_PIPELINE_SERVING_PROJECTION_SWEEP=off never run the sweep that sets has_async_followups, so reconcile_async() does not call start_followups() unless an unrelated owner wake supplies work. Meanwhile, the pump calls only sweep() and start_drains(), neither of which launches async follow-ups. Consequently, ordinary pump-persisted receipts can remain unfinished without client completion. Keep durable follow-up discovery running independently of the serving assignment-sweep gate, or explicitly hand this responsibility to the pump.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36db876ba6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +390 to +393
if projector.owes_drains:
with projector.handoff:
owners_requested = len(projector.drain_requests)
projector.start_drains()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Flush deferred indexing after pump sweeps

When BATCH_PIPELINE_OWNER_DRAIN_ENABLED=false and deferred indexing remains enabled, Projector.sweep() walks owner chains itself and queues search/people-stats updates in the projector's in-memory index. The pump only starts owner drains; it never calls schedule_index_flush(), unlike Serving.reconcile_async(). Consequently, conversations assigned by these walks can remain absent or stale in search indefinitely, and serving instances cannot flush the pump's process-local queue. Schedule owed index work after each pump tick independently of whether owner drains are requested.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e1cc45d27b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 474 to +476
async def run():
await asyncio.gather(supervise(), drain(), dispatch_recovery())
if pump_enabled:
await projection_pump()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep pump metrics scrapes out of serving reconciliation

When the dedicated pump is monitored through /v2/batch-pipeline/metrics, selecting only this background loop does not keep the process projection-only. In backend/routers/batch_pipeline.py:265-270, the metrics handler still calls service.reconcile_async(periodic=True) unless read_only_metrics_enabled() succeeds; that helper requires BATCH_PIPELINE_SERVING_ENABLED=true, which the documented pump configuration explicitly leaves false/unset. Thus even enabling read-only metrics cannot prevent authenticated pump scrapes from competing for the serving maintenance lease and running cleanup, enrichment sweeps and queue recovery. Make the metrics handler recognize the pump role and refresh metrics without reconciliation.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 222610743d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


def serving_projection_sweep_enabled() -> bool:
"""Only ``off`` disables serving-instance discovery; owner wakes still run."""
from config.policy import kill_switch

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move the new policy imports to module scope

Both serving_projection_sweep_enabled() and projection_pump_enabled() introduce function-local imports, violating the explicit requirement in backend/AGENTS.md: “All imports at module top level — never inside functions.” Import kill_switch once at module scope and reuse it in both accessors. Existing local imports elsewhere in this file do not exempt these newly added functions from the scoped convention.

AGENTS.md reference: backend/AGENTS.md:L168-L168

Useful? React with 👍 / 👎.

# Conflicts:
#	backend/config/flag_budget.json
@Git-on-my-level
Git-on-my-level merged commit 9f2ddcc into main Nov 5, 2026
58 checks passed
@Git-on-my-level
Git-on-my-level deleted the lane/pipeline-pump branch November 5, 2026 19:14
Git-on-my-level added a commit that referenced this pull request Nov 5, 2026
## Problem

Pusher drops its in-memory playback projection close obligation when the
bounded close rebuild retries are exhausted or the socket is evicted.
The retained audio remains safe, but no durable owner retries the stale
playback metadata after the socket disappears.

## What changes

- Persist coalesced `record_projection_rebuild_due` markers in
`record_projection_rebuild_jobs`, keyed by user/conversation, on
exhausted retries and socket cleanup. No conversation marker and no
additional ordinary-flush reads or writes.
- Queue terminal handoffs in one bounded metadata-only background task.
Preserve a separate bounded overflow retry inventory, batch writes with
one-second RPC deadlines, and cap all socket-cleanup marker work at
three seconds. Marker failures never change recording completion.
- Retire confirmed empty-inventory markers after seven days from
Firestore creation, or under the 10,000-marker maintenance ceiling.
Limit collection scans to 10,001 and rebuilds to 128; expose bounded
count, observed oldest age, capacity state and `retired_no_objects`.
Retirement rereads the marker version and the conversation producer map
in one Firestore transaction. Only an explicit empty map permits
deletion; live, unknown or malformed producer fences retain and rotate
debt, including under collection pressure. The pressure-blocked gauge
and aggregate fenced count surface passes that cannot evict empty
markers. No audio is deleted.
- Extract shared full metadata discovery and a socket-independent
rebuild. A bounded pass in the existing record-seal-repair CronJob
consumes at most 128 markers in 60 seconds, rotates work before
discovery, and clears only the version it attempted. Failed,
interrupted, invalid, or concurrently replaced work remains due;
confirmed empty inventory follows the age/capacity policy above. A
rebuild may update metadata while existing producers remain pending, but
keeps its marker until those fences clear through their existing owner.
- Refuse deleted owners before discovery and recheck deletion intent,
privacy, and the prior audio-files revision transactionally before
updating only `audio_files`. Keep preview read-only and retain debt on a
broken raw-pack index.

The runtime kill-switch parser lives in
`config/batch_pipeline_projection_rebuild.py`; metadata rebuild stages
do not read process policy from the environment.

Flag: `PUSHER_PROJECTION_DEBT_DURABLE_ENABLED`, **default ON**. `0`,
`false`, or `off` disables marker handoff and consumption. Rollback: set
`recordSealRepair.projectionDebtDurableEnabled=false` in the repair
runtime first, then disable pusher's flag; existing markers remain inert
and can resume when reenabled. This PR does not unsuspend or activate a
CronJob.

## Custody and privacy

Playback metadata only. No seal authority, producer-token mutation,
heartbeat, entry clock, dead-letter, conversation status, PCM read, or
audio-object creation/rewriting/deletion. Tombstones, hard/soft
deletion, account deletion, privacy changes, sync opt-out, peer
projection updates, and marker replacement are fenced. Scope is redesign
phase 5C only. #24712/#24716 and #23850 were checked before editing;
#24712 and #24716 merged during verification and their flag metadata is
preserved in the feature branch; shared overlap is flag metadata, not
projection-drain behavior or deployment configuration. No `.github`
changes.

A persistent Firestore outage or process death before a marker is
committed can still prevent handoff; this marker is not a WAL. Failed
marker writes retain metadata-only retry inventory and emit sanitized
`marker_write_failed` outcomes; bounded inventory saturation emits
`marker_retry_overflow`. Cached close debt remains retryable even when
overflow inventory is full.

## Verification

- All tests/build checks ran through `~/workspace/omi/bin/omi-heavy`
with the requested GCP credential/project variables unset.
- Focused per-file backend runner: **314 passed** (projection
producer/consumer, flag budget, rendered independent rollback, CLI
output isolation, batch-stage environment ratchet and outbox fault
injection).
- State table/invariants/writer inventory plus other deployment
contracts: **1,917 passed, 75 baseline xfailed**.
- Lifecycle simulator plus lifecycle properties under the existing
isolated stack environment and a disposable loopback Firestore emulator:
**23 passed, 3 existing strict xfailed**. The shared-collection fixture
includes a clear peer and verifies per-owner transactional outcomes. The
exhaustion/eviction/deletion/marker-race cases and six empty-retirement
cases passed: aged live/clear fences, pressure with fenced markers,
post-discovery producer registration, marker replacement and late-tail
repair.
- One local composed full-stack attempt stopped at an HTTP read timeout
in the existing REST-restart scenario before reaching lifecycle tests;
CI passed that scenario on the same production source. The final
exact-head CI gauntlet is linked below.
- Runtime image dependency closure: **19 registered images passed**; no
image/dependency changes needed.
- Required Grok review via `agentctl`: Luna R1 and R2 findings fixed.
The same required Grok reviewer rechecked transactional empty
retirement, producer and marker races, pressure telemetry and regression
coverage: **no blockers**. No additional reviewer agents.
- `make preflight`: **44 checks passed**, including the registered
Firestore guard. `scripts/pr-preflight` metadata contracts passed with
the documented six-line router exception; the final frozen run passed
all 44 checks; the normal hook repeated them successfully on the
published head.
- **Normal pre-push passed** on
`95a9ea47ae98eb03843909459a6604523534a98e`: full backend type checking,
selected backend units, registered Firestore guards, OpenAPI and pinned
formatting. No bypass, cap override or force push; Firestore guard
passed in 172.30 seconds without timeout or retry.

Exact published head: `95a9ea47ae98eb03843909459a6604523534a98e`. CI
results: [checks for this exact
commit](https://github.com/BasedHardware/omi/commit/95a9ea47ae98eb03843909459a6604523534a98e/checks).

## Product invariants affected

none

## Not measured

No GCP/prod access or tier used; no live IAM/permission test,
deployment, CronJob activation, production backlog/cost/latency
measurement, device playback acceptance, or A/B worker-death/dead-letter
simulator changes. Existing strict xfails remain existing
simulator/state-table debt.

Line-Count-Exception: backend/routers/pusher.py | 1851 -> 1857 | Phase
5C adds exact-owner terminal projection-debt and socket-cleanup handoffs
at the existing boundaries; full discovery is extracted into a shared
module, and splitting adjacent custody code would exceed this lane's
named scope.
Git-on-my-level added a commit that referenced this pull request Nov 6, 2026
GPU batch holds had no bounded terminal disposition or automatic
re-attempt. This adds a seven-day `held_deadline_at`, retained
`dead(held_expired)` rows, a private-OIDC CAS requeue, a nightly UTC
re-drive on the existing serving maintenance leader, and an agent-tier
volume alert. No CronJob is added.

Initial quarantine and failed-held recovery writers stamp a full window.
Legacy rows receive seven days at their first eligible sweep.
Expiry/requeue recheck content ownership, privacy generation, dormant
held state, no result or result receipt, no worker token and an expired
finite lease. Active paid/operator intents block the transition. Durable
document-key cursors bound each pass to 32 rows per five-minute tick;
crashes replay idempotently and malformed rows cannot pin the page.
Nightly re-drive rechecks its previous-day cutoff inside the transaction
before normal GPU admission.

`BATCH_HELD_DEAD_LETTER_ENABLED` / `batch_held_dead_letter_enabled()`
defaults **ON**. It is registered in kill switches, flag budget, feature
registry, deployment classification, common runtime declarations and
worker overlays. **Rollback:** false on serving/workers stops new stamps
and automatic maintenance; the private requeue endpoint remains usable.
Expiry atomically sets `merge_pending=true`, so after reverting the new
code an operator can still use the unchanged `POST
/v2/batch-pipeline/redrive-dead` route. No pre-revert drain or data
rewrite is required for rows written by this implementation. The
rolled-back service requires an operator re-drive rather than the new
nightly helper. See `backend/docs/runbooks/batch-held-dead-letter.md`.

**Custody:** nothing is deleted. Every new dead letter has reason
`held_expired`, retains its deadline, encrypted audio references and
attempt/intent history, and has a sanctioned requeue path. No TTL,
conversation-status writer or custody/privacy fence is changed. Status
and partial-accounting readers retain the held answer, so age does not
acknowledge phone custody or hide healthy siblings. Projection expiry
(4b), BYOK/finalization tools (4c), and `privacy_blocked` remain outside
this change. Provider-STT holds remain fenced from the GPU queue; the
state-table deadline gap remains explicitly strict-xfailed.

The volume alert is **agent tier**, with `omi-tier-silent` routing so it
never pages humans. It checks fresh retained stock >100 or >10
expirations in 30 minutes for five minutes. Each stock sample must be
younger than 15 minutes before aggregation; unrelated legacy dead
reasons do not count. Notification timing follows the warning contract.
Split/combined rules, metric inventories, the sync GMP scrape allowlist
and all four dev/prod main/sync exporter prefix lists include the three
emitted metric families. Thresholds are initial operator defaults, not
production-calibrated. Backend and metrics-egress/alert rollout must use
the checked-in declarations; this PR performs none of those rollouts.

Validation (offline/synthetic; suites overlap):
- Final held-recovery, alert, notification, inventory and transport
contracts: 319 passed.
- Real loopback Firestore integration: 3 passed, including expiry → flag
off → unchanged legacy dead re-drive → normal completion/merge,
contention and duplicate delivery.
- Main-sync serving/flag/held/worker contracts: 135 passed.
- State-table/writer/policy validation retained 75 strict expected gaps;
no unrelated xfails were removed. The shared emulator regressions are
also registered in the lifecycle harness.
- `make preflight` and `scripts/pr-preflight`: 52 checks passed. Both
published fix and merge heads passed the normal pre-push hook, including
backend tests, type checks, OpenAPI and Firestore guard; no bypass, cap
override, force or timeout retry.
- Required Grok review completed with NO BLOCKERS (receipts
`exec-heavy-system-squeeze-major-install-point`,
`exec-breeze-portion-boy-castle-gauge-marine`,
`exec-blur-source-live-meadow-inject-damp`). The last Luna P1 rollback
finding is fixed inline with the atomic legacy marker and explicit
rollback regressions; no further review round was requested.
- [Exact-head GitHub
checks](https://github.com/BasedHardware/omi/pull/24736/checks?sha=30b59b4c95631168a4a5fac9d7807cadd747327c)
for `30b59b4c95631168a4a5fac9d7807cadd747327c`.

Main is merged through `d6112658e5fa`, retaining #24735's cadence
controls and #24733's 16-worker seal-repair rollback. The lane base
already includes #24712/#24716. No `.github` edits; unrelated Flutter
work is preserved.

Not measured: production stock/dwell, vendor spend, live nightly
execution, live exporter/alert delivery, deployed revision or
user-visible recovery. No GCP tier, production access, merge to main or
deployment was used.

Product invariant: INV-MEM-4; canonical memory intake/promotion
authority is unchanged.
Failure-Class: FC-batch-fault-domain-head-of-line-poison
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.

1 participant