mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
stack-structure
18
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
91ade4b82a |
perf(relay): batch control lease renewals per cell instead of one write transaction per host (#21303)
* perf(relay): batch control lease renewals per cell instead of one write transaction per host Every connected desktop renewed its own control lease with its own single-row write transaction every 30s. At ~14,000 hosts that is ~470 write transactions per second fleet-wide, each with its own transaction id, all updating the same few heap pages of relay_assignments and relay_assignment_activity_leases. Sampling three onsets at 250ms showed no lock queue and no slow statement: 60-144 backends piled into LWLock:BufferContent and Timeout/SpinDelay inside that one statement, and Query Insights attributed 152 of 157 seconds of lightweight-lock wait in the onset minute to it. The heartbeat now enqueues a due renewal instead of issuing it. A cell flushes its queue once per second, or as soon as 500 rows are waiting, through one statement that unnests the parameter arrays and applies the same CTE row-wise. Concurrent writers drop from the host count to the cell count, and transaction ids with them. Measured against a 20,000-row table: 1 row 4.1ms, 12 rows 3.3ms, 100 rows 6.6ms, 500 rows 22.7ms. Per-session semantics are unchanged. Each enqueue still resolves on a renewal and rejects with the outcome as its message, so the completed-attempt counter, the staleness guard, and every close path route exactly as before, and one outcome per row is recorded against the flush latency. Lock order is (user_id, relay_host_id), the primary key of relay_assignments, applied in JavaScript and repeated as the statement's ORDER BY. EXPLAIN confirms LockRows sits above that Sort, so a batch acquires its assignment rows in one global order. Every writer in the store locks a host's assignment row before its migration or lease rows and only ever touches one host, so a batch can only wait on a row a single-host writer holds, never the reverse. One statement also means one contended row could fail the whole batch, so a failed batch degrades to the per-host statements it replaced rather than costing every other host on the cell its renewal. * perf(relay): batch control lease renewals per cell instead of one write transaction per host Every connected desktop renewed its own control lease with its own single-row write transaction every 30s. At ~14,000 hosts that is ~470 write transactions per second fleet-wide, each with its own transaction id, all updating the same few heap pages of relay_assignments and relay_assignment_activity_leases. Sampling three onsets at 250ms showed no lock queue and no slow statement: 60-144 backends piled into LWLock:BufferContent and Timeout/SpinDelay inside that one statement, and Query Insights attributed 152 of 157 seconds of lightweight-lock wait in the onset minute to it. The heartbeat now enqueues a due renewal instead of issuing it. A cell flushes its queue every second, or as soon as 200 rows are waiting, through one statement that unnests the parameter arrays and applies the same CTE row-wise. Concurrent writers drop from the host count to the cell count, and transaction ids with them. Per-host buffer traffic is unchanged: 30 hits for one row, 25.5 per host at 12 rows, 30.1 per host at 200, against the 28.7 the single-row statement reports in production. Per-session semantics are unchanged. Each enqueue still resolves on a renewal and rejects with the outcome as its message, so the completed-attempt counter, the staleness guard, and every close path route as before, and one outcome per row is recorded against the flush latency. Row locks live until the statement commits, so a batch that waited on a contended row would hold every other row's lock for that whole wait. The assignment pass therefore takes its locks with SKIP LOCKED and reports a contended host as assignment_lock_unavailable, which the registry retries on the next tick instead of closing the control. That keeps the hold to the statement's own execution: 11.5ms for 200 rows against a 20,000-row table, and 9.4ms with a host wedged in a per-host transaction, where a blocking FOR UPDATE spends the pool's whole 1s lock_timeout and then fails every row in the flush. An unlocked present_assignment probe separates a host with no assignment row from one the skip passed over, so a skipped row can never be mistaken for a missing assignment and close a live desktop. With no wait on the assignment pass the lock order is only needed for the two later passes, and it holds: every writer takes a host's assignment row before that host's lease rows, and a host whose assignment row is held was skipped, so the batch never reaches its lease. markMigrationTargetRegistered is the one writer that locks a migration row first, and it takes no further locks. * fix(relay): answer every row of a control-renewal batch from its own lease update Review findings on the batched renewal. Two control leases on one host in one batch made the second report control_activity_not_found although both were renewed: the assignment UPDATE is offered the same target row twice, applies one source row and returns one, so the other row_index never came back. The verdict now reads renewed_lease, which has a row per input row, and the assignment UPDATE groups per host so it also stops taking an arbitrary one of the two expiries instead of the later one. The queue now partitions per (userId, relayHostId) rather than per activity, so a second control activity for one host opens the next flush instead of sharing this one. Belt to the statement fix, not a substitute: the store API has to be right for the rows it accepts. renewControlActivities recorded no outcome for a one-row flush that threw, and none at all when every row failed validation, where a mixed batch recorded its invalid_* rows. Both now record in a finally, the way the single-row path's finally always did, and the error-to-outcome mapping both paths share is one function. The four flush fields the runtime metrics event emits had no log-based metric, so add them next to the existing controlRenewalLatencyMs* entries. Applying the Terraform is a separate manual step. controlRenewalLatencyMsP50/P95/Max now measure a batched row's flush duration rather than its own statement latency. Left named as they are for history, with a line at the emit site recording that the meaning changed here. |
||
|
|
5947d6b269 |
infra(relay): raise asia-east2 cell pools to 16 and record the measured connection ceiling (#21163)
* infra(relay): raise asia-east2 cell pools to 16 and retire four idle cells The three asia-east2 cells sit 176 ms from the Cloud SQL instance in us-central1. Server-side statement time there is 0.2 ms, so a pool slot is held by the round trip, not by the query. At a pool of 10 they measured 94-156 waiters and 2 s waits, and client accepts ran a ~4 s p95 against 222-646 ms in us-central1. Raising those three pools to 16 is the agreed first step; every other cell stays at 10. c4 and c5 join the committed fence set. Both are existing-only capacity the admission selector can never place on again, they carried ~1 connection each on 40-day-old images, and each still holds 10 Postgres connections. The fence set is the prerequisite the fence-source workflow confirms before it drains and attests a cell; it is not itself the resize. c17 and c18 are not fenced here. They are migration-only, and the runbook requires retire-migration-cell to move a migration-only cell to existing-only through a generation-bound selector CAS before it can be fenced. Terraform cannot express that step. The Cloud SQL consumer contract carried two stale numbers: auth at 2 instances when production has run a cap of 20 since 2026-09-04, and a 400-connection ceiling when the live instance reports 500. Both are corrected, and the budget now asserts its headroom in two named gates instead of one aggregate boolean. Those gates fail: auth alone accounts for 200 configured connections and a 215-connection rollout overlap, so the operating maximum is 713 against a usable ceiling of 490. Nothing here caused that, and no pool was lowered to hide it. * infra(relay): move the Cloud SQL contract correction out of this branch The contract correction (auth at its real 20-instance cap, the measured 500-connection ceiling) makes the budget gate fail for reasons that have nothing to do with asia pools or fenced cells, and it held this branch red. It moves to its own branch where the failure is the subject. production-cloud-sql-app-consumers.json returns to main unchanged. The budget test keeps main's single gate and only repins the cell figure that this branch genuinely moves: 230 -> 228, being +18 for three asia pools at 16 and -20 for fencing c4 and c5. Against main's 400-connection model that leaves an operating maximum of 383 under a usable ceiling of 390. * infra(relay): move the c4/c5 fence entries out of this branch Terraform now sets a cell's MIG target size directly from relay_gce_fenced_cells (relay-gce-cells.tf); the lifecycle ignore that used to protect operational target_size drift is gone. So a fence entry sitting on main ahead of its fence-source run is a standing instruction that any apply reaching that cell may execute without the documented drain and attestation. Keeping the entry in the same merge as an unrelated pool change widens that blast radius for no reason. The two entries move to their own branch, to be merged immediately before fence-source runs for c4 and then c5. This branch keeps the multi-line reflow of the list, which makes that later diff two added lines instead of a rewritten one. The cell figure in the budget test follows: 230 + 18 for the three asia-east2 pools at 16, with no fenced-cell subtraction. That is 403 operating against a usable ceiling of 390, so the headroom gate now fails by 13. It fails against a ceiling of 400 that is itself wrong; the instance reports 500. See the PR body. * infra(cloud-sql): record the measured 500-connection ceiling The budget's usable ceiling came from maxConnections: 400, described as the tier default. It is a tier default, since no max_connections flag is set, but the instance does not report 400. SHOW max_connections on it returns 500, measured 2026-09-16. On main the model sat at 385 against a usable ceiling of 390, five connections of margin, so raising the three asia-east2 pools by 18 failed the gate by 13 against a ceiling that was never checked. Against the measured one it is 403 against 490, clearing by 87. Only the ceiling and its source note change here. auth stays recorded at 2 instances, which is also wrong; PR #21165 corrects it, and with the true auth figure the budget is over by 225 for reasons that have nothing to do with these pools. * test(cloud): state the cell pool arithmetic literally in the budget pin comment |
||
|
|
71e308e574 |
feat(relay): count failed cell-inventory lock acquisitions (#21067)
* feat(relay): count failed cell-inventory lock acquisitions The cell inventory lock is taken NOWAIT, so contention errors with 55P03 and retries instead of waiting. CellInventoryHoldSamples.record only runs after a successful acquisition, so the hold metrics were structurally blind to the dominant failure mode: production showed ~65 failed fleet-wide acquisitions per minute while cellInventoryHoldMsMax read a benign 53ms mean. Count failures next to the holds and publish them as cellInventoryLockUnavailable in orca_relay_runtime_metrics. Drained on both the commit and the rollback path, since a 55P03 rolls its transaction back. * fix(relay): separate request-path lock timeouts from sweep deferrals Review caught that the first counter only incremented under failIfUnavailable, which is the sweep mode. Background sweeps take the inventory NOWAIT and re-derive a skipped candidate next tick, so those deferrals are by design and already reported as orca_relay_sweep_cell_inventory_busy. The request path uses a bounded lock_timeout instead, whose expiry raises the same 55P03 without NOWAIT and was not counted at all -- so the metric measured only the benign population and missed the user-visible one. Split them: cellInventoryLockUnavailable for NOWAIT deferrals, cellInventoryLockTimeouts for expired bounded waits. Production over 30 minutes shows why the distinction matters -- roughly 1,200 fleet-wide sweep deferrals against roughly 10/min request-path timeouts. Adds transaction-path coverage for both drains, which were previously unpinned. Timeouts count per attempt, not per request, since 55P03 is retryable. * fix(relay): publish the cell-inventory lock metrics to Cloud Monitoring google_logging_metric.relay_snapshot only creates metrics for fields listed in relay_runtime_metrics, and the cellInventoryHold* fields were never added when the hold telemetry landed. They have been log-only since, so nothing could alert on the lock and the contention stayed invisible in exactly the way the telemetry was meant to prevent. Maps the three hold fields and both new failure counters. Also corrects the field comment: the split is by wait policy, not by caller. assignOnce takes the inventory fail-fast on its first placement attempt, so request-reachable sites land in cellInventoryLockUnavailable too; that lane reads as contention pressure, and the expired bounded wait is the stall lane. |
||
|
|
cd9aa43a2c |
feat(relay): correct regional placement only when the source is idle (#20105)
* feat(relay): correct regional placement only at an idle source * test(relay): lock source activity capacity semantics |
||
|
|
eb2f2d52ae |
feat(cloud): native push gateway and dedicated infrastructure (1/3) (#19912)
* refactor(cloud): share PostgreSQL schema startup between services * feat(cloud): add durable native push notification gateway * infra(push): define dedicated gateway resources and operational checks * fix(push): bound cross-host admission and simplify gateway configuration * fix(push): validate deploy configuration and preserve topic-error registrations |
||
|
|
9c8f4c398c |
fix(relay): bound control RTT samples per ping and per flush window (#19268)
* fix(relay): bound control RTT samples per ping and per flush window An authenticated host chose how many round-trip samples a cell recorded: every pong carrying a recent plausible `t` was forwarded to the process-wide window, which grew unbounded until the 30s flush copied and sorted it for percentiles. Time a pong only when it echoes the `t` of the ping still outstanding on that session, so a flood yields at most one sample per ping the cell actually sent. A pong that lost the race to the next ping is dropped for timing but still counts as proof of life for the silence watchdog. Bound the process-wide window with a 1024-sample reservoir (Algorithm R) so the percentiles stay unbiased, keep `controlRttSamplesDelta` meaning round trips observed, and publish `controlRttSamplesDroppedDelta` for the ones the reservoir did not keep. Replace the leak guard's blanket `"credential":` string rewrite with an exact, path-scoped rename of the two schema keys that spell a policed word, and make the guard case-insensitive now that nothing legitimate trips it. Follow-up to #19232. * test(relay): prove the RTT reservoir samples the whole window |
||
|
|
db13cff832 |
relay: give the asia-east2 cells the regional rehome identity (#19239)
`relay_region_rehome_source_cell_ids` listed only the 16 US cells, and that list is the sole thing that stamps ORCA_RELAY_REHOME_DIRECTOR_SERVICE_ACCOUNT and ORCA_RELAY_REHOME_AUDIENCE into a cell's startup script. A cell reports regionalRehomeProtocol 1 only when both are present, so c27-c29 have always reported 0. That leaves them ineligible as rehome sources and, once the worker is bidirectional, as targets too, which strands the US desktops homed there. This is a prerequisite only. Merge and roll it ONLY AFTER the bidirectional rehome director change is deployed. Two live gates still hard-code the primary region and would reject an Asia source no matter what the template stamps: `cloud/apps/relay/src/app.ts` line 610 fails the trust probe with 409 when the source cell's region is not RELAY_DEFAULT_REGION, and `cloud/apps/relay/src/assignment-store.ts` line 5476 skips such a cell as source_ineligible during rehome source selection. The bidirectional lane removes both. The topology check asserted every source sits in the primary region. That mirrored those two gates rather than protecting anything Terraform owns, so it is now advisory: it requires only a configured, unfenced cell with an explicit connection limit, and the comment records that region eligibility belongs to the director's own source and target predicates. Every cell's region is already constrained by the assert above it. The same-cap census test cross-checked membership against us-central1. Every reviewed serving cell now carries the trust, so it asserts protocol 1 for all, plus one non-source cell to keep the validator's protocol-0 branch covered. Roll sequencing, because this apply is not self-contained: - After the apply the Asia templates carry the two rehome lines, and the `unexpectedRehome` rule at `cloud/dev/scripts/validate-relay-capacity-plan.mjs` lines 243-247 rejects a protocol-0 plan that contains them. So c27-c29 have no dispatchable protocol-0 same-cap roll until the director gate is gone or this is reverted. - The same-cap job runs the per-host trust probe after isolate, drain, and the targeted apply. A 409 there leaves the cell serving but isolated and migration-only, which is what happened to c13 on 2026-09-06. - The only safe path: deploy the bidirectional rehome director, then dispatch `Deploy Relay Production Same-Cap` canary-apply for one Asia cell with target-rehome-protocol 1 and rollback-rehome-protocol 0, then batch-apply the remaining two. That job runs its own targeted template and MIG apply. - Never reach these cells with an untargeted root apply. The current plan carries 60 changes and 50 destroys of unrelated standing drift. |
||
|
|
e068947d4c |
feat(relay): alert on far-cell placement and skewed region hints (#19253)
* feat(relay): alert on far-cell placement and skewed region hints US desktops were homed on asia-east2 cells for weeks in 2026-08 with every existing relay alert green. Roughly 226 of 332 hosts on those cells were non-APAC, and a phone connect took ~10 s there against ~0.6 s in region, but nothing in Cloud Monitoring could see distance: the connection, queue, heap, and SQL bars all measure a cell's own health, which was fine. Three policies close that gap. Two read distance per cell, from the accept and control-RTT timing added in the parent commit: phone-accept p95 above 2 s, and control ping p50 above 150 ms. The third reads the cause fleet-wide, as the asia-east2 share of the region hints desktops send the director, so a mis-picking client probe is visible before it lands anyone on a far cell. All three are MQL rather than the metric filters the other relay policies use. Every runtime metric is a DELTA DISTRIBUTION, and a filter condition can only align one with a percentile; each alert needs the sum of the extracted values as a volume floor so a sparse window cannot page. None of these metrics exists in the project yet, so what was checked against production is the query shape: the same MQL run over existing metrics of the same kind. The skew denominator needs one log-based metric per hint key, so `requestedRegionsDelta` now has one per relay region plus the unhinted bucket. Those ride the existing snapshot metric family, which adds map entries without touching the live metrics. A ratchet test pins the key list to relay-contract's RELAY_REGIONS: a region added there without a metric would shrink the denominator, so the test fails rather than letting the share quietly inflate. * fix(relay): compare hinted regions against placed ones, not a fixed share Review found the skew alert inverted at both ends. A fixed 40% bar on the asia-east2 share of region hints was silent through the exact broken state it was written for, and would page forever once the desktop probe is fixed and the genuine APAC share rises past it. An absolute share cannot separate those because it has no reference point. The hint share now has one: the share of assignments the director actually placed in that region during the same hour. Measured over twelve hours on 2026-09-07, while the probe was still mis-picking, asia-east2 was 33.8% of 33,800 hinted requests and 7.9% of 45,364 assignments. That is a 4.27x divergence and a 25.9-point gap, so the alert fires above 2x and 15 points, inside the broken state and outside a healthy one. Both bars must hold: the ratio alone blows up on tiny placement counts, the gap alone misses a proportionally large skew at low volume. The reviewer proposed either bar alone; requiring both keeps each one meaningful and still clears today's numbers with room. `unhinted` requests leave the denominator. They were 27% of all requests, so a client that always sends a hint would move the number from 21.9% to 35.0% with no behaviour change at all. The comparison needs per-region placement counters, so `selectedRegionsDelta` gets log-based metrics alongside the requested ones. Rather than extract four hyphenated map keys through quoted field paths, which nothing in the project does and which cannot be checked without applying, the relay now also publishes flat `requestedRegion<Region>Delta` and `selectedRegion<Region>Delta` fields next to the untouched maps. They are emitted as zeros in every interval, so no series can drop out of the alert's inner join in an hour with no asia placements, which is exactly the hour the skew is worst. Additive only: metricVersion is unchanged, the maps still carry anything outside the catalog, and the emitter's leak guard still passes. Two corrections to what the previous commit claimed. None of these metrics exist in the project yet, so the code, the doc and this message now say what was actually checked against production: the query shapes, run over existing metrics of the same kind. And the control-RTT policy records that EU desktops on us-central1 sit at 100-130 ms, so a European-heavy cell can approach the 150 ms bar while correctly homed. The skew alert will stay lit after a client fix until the backlog is rehomed. Sticky assignment never re-consults the hint, so a desktop already on an asia cell keeps landing there whatever it now asks for. The policy description and the doc both say so, so nobody reads a slow clear as a failed fix. * fix(relay): cross-multiply the skew bars so a zero placement share still fires `hint_share / placement_share` is undefined in the hour that matters most. When the director placed nobody in the region, MQL returns no rows for either 0/0 or x/0, so the series disappears before the gap and volume clauses run and the alert stays silent. That hour is not hypothetical: it is every desktop asking for a region while the director puts nobody there, which is what a drained, fenced, or full region looks like, and it is the most extreme skew the alert can see. The condition is now cross-multiplied, `hint_share > 2 * placement_share`, which is well defined at zero. Both forms were run read-only against production surrogates chosen so the placement denominator is exactly zero: the ratio form returned no rows, the cross-multiplied form returned the series with the condition true on every point. A second surrogate pass with a tiny hint share returned the series with the condition false, so the gap clause still suppresses the healthy shape rather than the query silently matching everything. The flat field names are no longer derived on either side. Terraform title cased each dash-separated part and the emitter upper cased each part's first character, so the ratchet had to pin two source expressions by regex, which a reformat would break and which never compared the actual rendered names. Both sides now declare a literal map, relay-contract's RELAY_REGION_METRIC_SEGMENTS and Terraform's relay_region_field_segments, and the test compares the two declarations against each other and against the expected names. `satisfies Record<RelayRegion, string>` makes a region added without a segment a compile error rather than a silent gap in the alert's denominators. Both ratchets were checked by mutation: a wrong Terraform segment, a contract region with no Terraform entry, and a revert to the ratio form each fail the node test, and the new region fails the contract build. |
||
|
|
ecfcc0d833 |
feat(relay): time successful client accepts and control round trips (#19232)
* feat(relay): time successful client accepts and control round trips A 6s accept on a cross-region cell was invisible: only the abandoned path was timed. Record per-stage durations across acceptClient and acceptHostData (assignment/credential/activity/attach), emit one completed log line per accept, and aggregate p50/p95/max into the runtime metrics event. Sample control ping round trips from the pong echo so a host sitting on a distant cell is visible fleet-wide and per host, rate-limited to one log line an hour per session. * fix(relay): review round 1 on accept and control-RTT timing Omit the accept and RTT percentiles from windows with no samples: accepts are sparse, so a zero point every 30s would pin the p50 at 0 and collapse the p95. The *Delta counts still publish, and say when the omission is expected. Control-renewal output is unchanged. Add a `basis` stage for the splice lease and connection-basis writes that run between the host data leg and relay-hello, and start `attach` where the activity stage ended, so the stages now tile the whole accept and their sum equals totalMs. Clamp every stage at zero against a backwards clock step. Carry role/cellId/region on both new log lines, flatten the stage p95 field names so the log-metric extractors stay top-level, and record that only the RTT median reads as distance: the desktop echoes the pong on its main thread, so the p95 and max track desktop stalls. |
||
|
|
d53cbed43f |
revert: hold mobile push feature for user testing (#19203)
Reverts
|
||
|
|
3160b54c69 |
feat: real background push notifications for the mobile app (#8129) (#18554)
* feat(cloud): add the mobile push gateway and its contract package (#8129) A small open-source service that holds the APNs key and FCM credentials and sends background push to paired phones on the desktop's behalf. Hosts authenticate with a box challenge and HMAC proof on their pairing key, the same shape the relay uses, so signed-in and accountless desktops share one path. Tokens are stored; alert text is held only for the coalescing window. The contract doc in docs/reference is the source of truth for every wire shape. The interop test runs the real desktop answerer against a real gateway-issued challenge so transcript drift fails in CI. * feat(push): register phones and send background push from the desktop (#8129) Adds the notifications.remote-push.v1 capability, the registerPush and unregisterPush RPCs on the mobile allowlist, a gateway client with a cached session and 401 re-auth, a durable unregister outbox, and a dispatcher that offers every mobile notification to the gateway after the socket fan-out. The dispatcher is fire-and-forget with one retry and drops registrations the gateway reports dead. Puts agentState on the mobile frame and fixes the #4375 wording so a working agent is never announced as finished. The relay host-proof code moves onto a shared envelope module with no behaviour change. * feat(mobile): background push registration, receive, and settings (#8129) Fetches the native APNs or FCM token, registers it with every paired host that advertises the capability, and re-registers on token change. Foreground pushes are suppressed inside handleNotification against the same seen set the socket path uses, so nothing shows twice. Taps route by host fingerprint. One Background notifications switch, off by default, with the disclaimer and needs-input / finished sub-switches; hidden until a paired desktop is new enough. Adds google-services.json and the expo-notifications plugin. * chore(cloud): Terraform and deploy workflow for the push gateway (#8129) Declares the Cloud Run service, runtime account, secrets, and orca_push database behind push_gateway_enabled, true only in production. The deploy workflow is gated like the relay's, deploys with no traffic, probes /ready and a validate-only FCM send, then shifts traffic. It runs as the shared production deploy account because the Cloud SQL rollout lease grant is foundation-owned; its extra authority is three bindings on the push service. docs/push-gateway.md carries the import commands for the resources created by hand and the APNs key rotation procedure. * docs: describe background notifications on the phone (#8129) * docs: check in the mobile push contract (#8129) Seven committed files cite it as the source of truth for every wire shape; docs/reference is allowlisted per file, so add the entry. * test(push): replay one checked-in host-proof vector on both sides (#8129) Cloud Verify installs only the cloud workspace, so the gateway suite cannot import the desktop answerer. Replace the cross-workspace import with a fixed challenge vector generated from the contract package; the gateway fixture and the desktop answerer each replay it and must produce the same HMAC. A transcript drift on either side now fails in that side's own suite. * fix(cloud): open the push gateway with invoker_iam_disabled, not an allUsers binding (#8129) The production domain-restricted-sharing policy rejects an allUsers run.invoker member, which the runbook anticipated. Opt the service out of invoker IAM the way the relay director already does; the host proof is the authentication either way. * docs(cloud): the push.onorca.dev record exists and is hand-managed (#8129) * fix(push): close review findings in the gateway (#8129) - Quota reservation takes a per-host advisory lock; READ COMMITTED admitted a whole burst past the cap (80/80 without, 60/80 with, against Postgres 16). - Challenge issuance no longer writes push_hosts; the row lands on proof verification. Stale hosts prune after 30 days. Per-IP token bucket on the two unauthenticated routes. - Streaming body limit via hono bodyLimit; a chunked body bypassed the Content-Length check. - registrationIds deduped in the schema; per-host device cap of 64; list bounded to its schema. - Gateway-side challenge TTL is the specified 10 s, not 40 s. - APNs stream settles on close as well as end/error. * fix(push): close review findings in the desktop client (#8129) - A gateway registration the registry cannot persist is enqueued for delete instead of leaking a live token. - Unregister outbox re-reads pending per pass, honours enqueues during a drain, and retries with backoff instead of waiting for the next launch. - Dispatcher batches registrations by 20 rather than starving the rest. - 401 compare-and-clear; a 401 after re-auth is unreachable; refused handshakes and 429s are cached briefly instead of re-handshaking per event. - Service is stopped on quit. * fix(mobile): close review findings in push registration and receive (#8129) - Consent generation guards a register that finishes after the switch went off; the host is re-queued for unregister instead of recorded live. - Foreground pushes seed the watermark before adopting the epoch, so a push on a never-connected session cannot wipe a valid watermark. - aps-environment follows the build via app.config.js; the iOS release workflow sets it to production. A bare plugin entry wrote development. - Pushes the OS showed while closed are marked seen before catch-up replay. - Token null result is not cached; failed capability probes are retried and never block an unregister; coalesced summaries are shown but not marked. - Unresolvable fingerprint routes nowhere and is suppressed in foreground. - Android channel ensured at boot; capability hook diffs clients by identity. * fix(cloud): harden the push deploy workflow and size the gateway to the budget (#8129) - Roll traffic back on a failed post-shift check; delete a candidate that never took traffic; retry the origin probe and the FCM probe. - Assert Terraform-owned scaling instead of mutating it from the workflow. - Build before taking the Cloud SQL rollout lease. - Declare the database pool in Terraform (2 per instance, max 2 instances) and add the gateway to the connection budget; the previous default put the shared instance 65 connections over its ceiling. - State plainly that the shared deploy identity's relay authority is inherited. * fix(push): read the runtime from shared state at push startup (#8129) Threading the runtime through launchDesktopMode put the launch module one line over the 300-line lint budget after the rebase. * fix(push): key the unauthenticated rate limit on the hop Cloud Run wrote (#8129) Cloud Run appends the connecting peer to x-forwarded-for; the limiter read the left-most value, which the caller controls, so a forged first hop earned a fresh bucket per request. * fix(push): close the final security review findings in the gateway and infra (#8129) - app.onError logs only the error name and answers a bare 500; hono's default handler printed the whole error, and a pg error carries the row in detail - a second per-IP bucket (240/min) runs ahead of the bearer lookup on every authenticated route, so forged bearers cannot spend the two-connection pool - one live session per host: minting deletes the host's earlier row - device-less hosts are pruned after 1 h, not 30 d; any keypair mints one free - notificationId is printable ASCII, since it becomes the APNs collapse header - the impersonated FCM probe token is masked in the workflow log - prevent_destroy on the Apple secrets and the orca_push database * fix(push): close the final security review findings in the desktop client (#8129) - fetch never follows a redirect: a 307 would replay the host proof and the phone's token to whatever origin the redirect named - registerPush params are strict and the paired identity is spread last - a per-device bucket (10/min) bounds a phone looping registerPush, which costs a gateway write and a synchronous registry write each time * fix(mobile): close the final security review findings in push receive (#8129) - a push with no epoch can no longer claim a seq-derived dedup key, in the foreground or from the tray; a forged seq:N could otherwise swallow the real bell at that seq - a provider-delivered push with no host catalog, or no fingerprint at all, stays unrouted instead of falling back to the hostId its raw data carries * docs(push): record the ip buckets, session and host retention, and the token-ownership limit (#8129) * fix(push): apply the schema on an untimed pool and retry statement-timeout aborts (#8129) Ports the relay's #18722 pattern to the gateway: DDL runs on a one-connection pool with statement_timeout 0 that is closed before the serving pool opens, and SQLSTATE 57014 joins the bounded transaction retry path. * fix: harden mobile push delivery and deployment recovery * feat: align mobile notification preferences with desktop delivery * fix: accept variable-length APNs device tokens * fix: deduplicate native APNs and background socket notifications |
||
|
|
6aba81f90a |
infra(relay): drop the unapplied region label from runtime log metrics (#18734)
The live metrics have role and cell_id only. A label change on a log metric is delete+create, so applying the region label would replace all 21 metrics, reset their history, and blank the relay alert policies during the swap. Matching Terraform to live state makes the targeted plan create-only (8 renewal metrics never applied, plus the incident dashboard from #18717). |
||
|
|
7cb05477a1 |
feat(relay): let cells dial Cloud SQL over private IP (#18720)
Cells run cloud-sql-proxy against the auth database's public IP, so every connection burns a Cloud NAT port on the relay gateway; that allocation filled on 2026-09-04 and every cell's proxy dial timed out at once. Add --private-ip behind relay_cloud_sql_private_ip so production can move the traffic onto the VPC peering once the foundation root has applied it. Default false, and the rendered startup script is byte-identical to main with that default, so merging rolls nothing. --unix-socket is untouched: it selects the listener, not the upstream address, so DATABASE_URL does not change. The director is Cloud Run and egresses outside this VPC's NAT, so it is not part of the problem; moving it would mean VPC egress plus a TCP DSN and its own secret, which is a separate change. |
||
|
|
2f4f4578c8 |
feat(relay-infra): cell crash-rate alert and incident dashboard (#18717)
201 relay cell process exits over 48h on 2026-09-04 paged nobody. Adds a log metric on the Docker `container die` event for the orca-relay container, an alert at >3 exits per instance per 15 min, and a four-chart incident dashboard covering the signals that had to be assembled by hand during the outage. |
||
|
|
9f10f415f5 |
feat(relay-infra): dynamic NAT ports and alerts for the 2026-09-04 stall signals (#18693)
* feat(relay-infra): dynamic NAT ports and alerts for the 2026-09-04 stall signals Relay cells reach Cloud SQL's public IP through Cloud NAT. The static default of 64 ports per VM filled on 2026-09-04 and every cell's proxy dial timed out at once, which read as a fleet-wide SQL stall against a healthy database. Switch both regional NATs to dynamic port allocation (64..4096 per VM). Add the three alerts that would have paged inside the first ten minutes: - Cloud SQL WAL-triggered checkpoint loop (log metric on "checkpoint starting: wal", > 3 in 5 min) - Cloud SQL disk utilization > 70% - Cloud NAT OUT_OF_RESOURCES packet drops on the relay gateways No workflow applies google_monitoring_* or the NAT resources today; every relay workflow is target-scoped to cells. Apply is a reviewed targeted plan (see PR body). * test(cloud): register the three new relay alert policies in the root partition fixture |
||
|
|
0746d82c01 |
chore(cloud): close the Workload Identity cutover onto stablyai/orca (#18509)
Mirrors stablyai/orca-cloud#470. The private relay workflows are retired, so the dual accept has one live arm left. Add `github_workflow_file_prefix` for the primary repository's workflow filenames, point `github_repo`/ `github_repo_id` at `stablyai/orca` (`1183888342`), and empty `github_accepted_repositories` in both environments. Every relay provider goes back to a single arm naming `cloud-` prefixed workflow refs. `cloud/infra/terraform` stays byte-identical to the private branch. The two identity tests diverge here as they already did, so they take the same change rather than the same bytes: both now render the trusted ref head from the Terraform variable instead of this checkout's own workflow filenames, which is what lets the length pin be the same 791 characters in either repository. |
||
|
|
fbea749d07 |
chore(cloud): pin staging relay c3 to the director's image (#18508)
* chore(cloud): pin staging relay c3 to the director's image Mirrors stablyai/orca-cloud#468. c3 stayed on sha-c91439af after the director and c4 moved to sha-e3e92d95, so the staging capacity proof's compatible-director-image check has failed since 2026-08-14. * test(cloud): scope the launch-image pin to staging C4 now that C3 shares the digest * test(cloud): keep the public workflow assertions; scope only the launch-image pin to C4 |
||
|
|
3eec77c11a |
chore(cloud): add the relay fence broker, ops console, Terraform root, scripts, and 24 cloud-* workflows (#18413)
Phase 6 of the relay split: the relay's deploy/operate surface moves under cloud/ with 24 cloud-* workflows gated on ORCA_CLOUD_OPERATIONS_ENABLED, the Cloud SQL rollout lease action, the relay Terraform root (dual-accept identities for both repositories), scripts, docs, CODEOWNERS, and a terraform validate job in Cloud Verify. |