Commit Graph
16 Commits
Author SHA1 Message Date
Neil d634063ef2 refactor: organize mobile cloud relay by lifecycle owner (#41681) 2026-11-12 14:36:07 -08:00
Neil d3b6609ac9 refactor(rpc): group root modules by defining owner (#41475)
* refactor(rpc): group root modules by defining owner

* test(orcad): include JSON CLI failures in launcher diagnostics
2026-11-12 02:18:19 -08:00
Neil a82468b023 chore(deps): safely update vetted dependencies across workspaces (#41291)
* chore(deps): safely update vetted dependencies across workspaces

* fix(deps): deduplicate rolldown to 1.2.16 for electron-vite tsc parity
2026-11-11 17:58:44 -08:00
OrcaWinandOrcaWin 9576e55773 test: remove 1,144 low-value declarations (#41177)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-11 09:11:06 -08:00
Neil ba3f9918f1 relay-edge: reconnect_gap metric (time from a host's last control close to its next activation) (#41034)
Diagnostics only. Separates sleeping hosts (long gap) from awake hosts whose
connection was dropped (short gap) after silence closes. Stores one small
per-host key, cleared on the next activation and by erase.
2026-11-10 21:57:40 -08:00
Neil 7fa124de5e relay edge: silent host at authorization expiry closes 4408; dead-control diagnostics (#40929)
* relay edge: a keepalive host already silent when its authorization expires closes 4408

At token exp + 60 s, a keepalive control that went silent (asleep, offline) had its silence probe
pending, so the expiry check closed it 4401. Production showed these as 4401 'never refreshed'
closes with the host silent for ~5-6 minutes, inflating the auth-failure count and hiding drops
from the 4408 comparison with the cells. Such a host now closes 4408 (orphan time back-dated as
for any silence close). Local e2e K4 (fails without the change: 4401). 48/48.

* relay edge: handshake time on control_active, control age and silence on dead-control closes

At 1%, many new controls went silent at or right after activation and were closed minutes later,
with no rebound from a retry. To tell a desktop that gave up the connect (15 s deadline, e.g. a
slow first hello on a new object) from one that died later: control_active double2 = ms from the
upgrade to activation; new silence_close (seconds silent, where found, keepalive/legacy, age);
auth_expired_close double3 = control age.
2026-11-10 16:19:45 -08:00
Neil 557d588e69 relay edge: tag auth-expiry closes and control opens with keepalive use and host silence (#40882)
Desktop closes for expired relay authorization rose after the keepalive release (3/day to ~35/day
on the edge cohort). The close metric now carries whether the control took the keepalive offer and
how long the host had been silent, and control_active carries keepalive use, so the cause can be
told apart (stalled desktop timers vs a control whose refresh stopped while it stayed live).
2026-11-10 14:08:25 -08:00
OrcaWinandOrcaWin 618c7f5b3f Remove low-value voice, speech, and relay tests (#40617)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-09 19:49:36 -08:00
Neil cc7d19e932 relay edge: host-sent keepalive answered without waking the object (#40010)
Desktops advertise client-keepalive-v1; the edge offers it after the hello-ack and
auto-responds to a fixed keepalive frame, so idle hosts need no 25 s ping alarm
(each setAlarm is a billed row write). Silent keepalive hosts are probed with one
relay ping before a phone is refused, closed at once where the host is leaving.
Shipped desktops and cells see nothing new. Kill switch CLIENT_KEEPALIVE=0.
2026-11-08 15:03:20 -08:00
OrcaWinandOrcaWin 88aa9fa82a test: remove 154 low-value tests and redundant relay checkpoints (#39814)
* test: remove 155 low-value tests and two relay checkpoints

* test: retain schema drift protection and remove dead imports

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-08 03:06:41 -08:00
Neil c362cb183e relay-edge: keep valid desktops through JWKS outages, deep object probe, cohort-scaled alerts (#39772)
* relay-edge: keep valid desktops on JWKS outages, deep object probe, cohort-scaled alerts

- auth: tell 'token invalid' from 'keys unavailable'. An auth-refresh that cannot get keys keeps
  the control and its session unchanged (metric auth_refresh:keys_unavailable); the exp+grace
  close still applies. A control upgrade without keys gets 503 instead of 401 (shipped desktops
  treat both as a retried connect failure). The last good JWKS is kept in the colo's Cache API
  (24 h max age) as a fallback for cold isolates; a stored set lacking the token's key never
  reads as invalid.
- admin export ignores closing controls, like drain, so a just-drained host is not busy.
- GET /__health/object: canary HostRelay reads its SQLite (SELECT 1), no writes; per-source
  limited; 503 on failure or after 3 s. The alerts Worker probes it (object-probe, > 2 s alerts).
- alerts: MIN_VOLUME stays configurable, reconnect-surge rule (control hellos >= max(3x previous,
  RECONNECT_SURGE_MIN)), optional cloudflare-errors rule from the GraphQL Analytics API
  (CF_GRAPHQL_TOKEN). Rules moved to alerts-rules.ts.
- metrics: heartbeat events carry their 1-in-10 sampling factor as their last double.

* relay-edge: persisted JWKS trusted for 30 min, as the cells' stale window

* relay-edge alerts: opt-in reuse of the Analytics Engine read token for the GraphQL error rule
2026-11-07 23:38:28 -08:00
Neil 261bc0f2be relay-edge: single-use admin tokens (jti), signing key rotation, auth-refresh metrics (#39738)
- Admin tokens with a jti (16-128 chars) are single use, enforced inside the target
  HostRelay (one storage key, expired ids pruned on each call, capped); a repeat is 409
  'replay'. Tokens without a jti are still accepted (metric admin_no_jti) unless
  ADMIN_REQUIRE_JTI=1.
- ADMIN_SIGNING_KEY_NEXT / ERASE_SIGNING_KEY_NEXT: second accepted key for rotation;
  validated >= 32 chars when set.
- Metrics auth_refresh (extended/unchanged/invalid/identity_changed) and
  auth_expired_close (seconds since the last extending refresh).
2026-11-07 21:14:12 -08:00
OrcaWinandOrcaWin 6d95515306 test: remove 297 low-value tests (#39720)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-07 21:10:57 -08:00
Neil 8ea8406c64 relay-edge: control liveness on any frame, signed drain op, small-volume alerts (#39706)
* relay-edge: any inbound control frame refreshes control liveness

A control that keeps sending commands or auth refreshes is alive even when
its pongs are late. Every inbound frame from an active control now refreshes
lastPongAt (field name kept so hibernated attachments read the same field);
pong_rtt_ms is still recorded for pongs only.

* relay-edge: signed admin op drain

POST /__admin/drain { relayHostId }, signed like assign/export/erase with
op 'drain'. Every control of the host gets the lease-expiry drain frame
(graceMs 0, resolve-director), closes 4503 'host leaving edge' and goes
through the normal socket-gone path. Returns 200 { outcome: 'idle' } or
{ outcome: 'drained', controls }. Assignment, devices and splices untouched.

* relay-edge: alerts that fire at small volume, and an edge probe

Absolute-count rules regardless of MIN_VOLUME: admin-failures (>= 3 admin
calls per 15 min whose label is not an expected outcome) and
auth-refusals-low-volume (>= 5 4401s below MIN_VOLUME). Optional
EDGE_PROBE_URL: each run sends an unauthenticated control upgrade and
alerts unless it gets 401 within 5 s (stateless: every failing run alerts).

* relay-edge: MVP checks for control liveness and the drain op
2026-11-07 19:51:09 -08:00
Neil 6757dd6758 relay-edge: alias relay-contract subpaths to source in the local wrangler config (#39680)
Wrangler's alias does not rewrite subpaths and the package exports point them at dist/, so a
clean checkout could not run wrangler dev (or the object MVP) without a prior relay-contract
build, and a checkout with a stale build bundled it.
2026-11-07 18:58:03 -08:00
Neil 3f0c565994 relay-edge: Cloudflare Worker + Durable Objects relay (#39363)
* spike(relay-edge): local Durable Object relay prototype and de-risk harness

Stateless Worker routes control/phone/data sockets to one object per host.
Harness drives the shipped desktop control client and data transport against
wrangler dev. Not wired into builds or deploys.

* spike(relay-edge): one Durable Object per splice isolates slow phones

The host object keeps control, session and connection bookkeeping. Each
phone connection and its host data socket live in their own object and talk
to the host over DO RPC. Harness adds flood isolation, splice reset, budget
reclaim and RPC latency checks; the silent-bytes guard is measured and left
disabled.

* spike(relay-edge): Cloudflare de-risk kit (spike env, load, deploy, canary, rtt tools, runbook)

* spike(relay-edge): rebind after an object reset keeps the generation

* spike(relay-edge): pool splice objects per host to avoid object creation per phone connect

* spike(relay-edge): slow-phone flood test for Cloudflare

* relay-edge: MVP backend (credential store over Hyperdrive, JWKS, epoch check, host commands)

* relay-edge: MVP checks on local Postgres (credentials, commands, epoch, DB-down)

* relay-edge: refusal and database-latency metrics; Analytics Engine on the spike

* relay-edge: alerts cron Worker over Analytics Engine; edge.relay.orca.dev on the spike

* relay-edge: slow assignment check closes 4429, never 4401; staging env over Hyperdrive; db latency debug route

* relay-edge alerts: silence only after traffic

* relay-edge: Durable Object SQLite adapter; relay credential-store tests pass on it (P1)

* relay-edge phase 2: credential store on HostRelay SQLite, signed director push/export/erase; 17/17 in workerd

* relay-edge phase 2: Cloudflare env, push-assignments load prep, remote mode for phase-2 checks

* relay-edge: export freezes the host and is refused while connected; move-on replaces host data; stale/conflict carry the edge epoch; push location hint; admin content-length check

* relay-edge: daily store sweep on the object alarm (armed on every wake and admin call), wipe of objects whose host never arrived or left 30 days ago, erase guard ignores expired bases, admin and sweep metrics

* relay-edge: admin key renamed ADMIN_SIGNING_KEY; optional erase-only signer for the erasure job; a move onto the edge also voids audit rows from an earlier stay

* relay-edge: phase2 test env names the erasure issuer

* relay-edge: director ⇄ cell ⇄ edge round trip with the shipped desktop and phone clients

* relay-edge: round trip resolves the live phone first (a failed resolve rate-limits the host)

* relay-edge: per-source pre-auth limits; phones for unpushed hosts answered without storage

Phone and host data upgrades are counted per source (CF-Connecting-IP, IPv6 by /64)
through a Rate Limiting binding, failing open. A phone for a host the director never
pushed (or one since wiped) gets relay-hello 4409 from the Worker, so neither the host
nor a splice object writes storage. Local 25/25, phase2 21/21.

* relay-edge: production env on edge.relay.orca.dev; object schema version; frame caps; place on metrics

- production env (orca-relay-edge): custom domain edge.relay.orca.dev moved off the
  spike env; object backend, production auth JWKS, no debug/reset routes.
- Object storage carries a schema version; code older than an object's schema refuses
  it; a test pins the version to a hash of the tables so changes need a migration.
- Frames over the cells' limits close 1009 (control 1 MiB, splices 8 MiB).
- Metrics carry the client's ingress '<continent>/<colo>'.
Local 26/26, phase2 22/22, unit 24/24.

* relay-edge: production alerts env (reads the production dataset)

* relay-edge: keep only local dev config in this repo

Deployed environments (Worker names, hostname, account id, dataset names,
rate limits, Hyperdrive id) move to the private deploy config. The local
config and tests are unchanged.

* cloud: keep relay-edge out of the pnpm workspace (own npm lockfile)

* Revert "cloud: keep relay-edge out of the pnpm workspace (own npm lockfile)"

This reverts commit a55955d3c023964ccd2dfb644e2d09ca67322722.

* cloud: relay-edge as a pnpm workspace member (lockfile; workerd binary-only)

* relay-edge: follow main's relay module moves; alias workspace packages by directory so subpath imports resolve

* relay-edge: splice byte counters on the socket attachment (survive hibernation)

* relay-edge: keep deployed-target load tools out of the public tree; abort routes only on the spike backend

The local harness (spike/run-all.ts, lib.ts, keys.ts) stays. The tools that drive a deployed
Worker and the Cloudflare runbook live with the private deploy config.

* relay-edge: satisfy the changed-lines lint gate

Braces on every if, type aliases over interfaces, template strings, no explicit any,
!== -1 index checks, and a justified max-lines header on the per-host object, the splice
object and the end-to-end harnesses. No behaviour change: unit 19/19, object MVP 26/26.

* relay-edge: split the host object and harnesses under the 300-line limit

HostRelay is layered base → handshake → control → phones → HostRelay (one object, one state;
method bodies moved verbatim, private → protected). SpliceRelay's state types move to
splice-state.ts. The harnesses' helpers move to sibling modules. No max-lines disables.
Unit 19/19, object MVP 26/26, local spike harness 19/19.

* relay-edge: run its vitest suite from cloud's pnpm -r test and register the runner
2026-11-07 18:47:21 -08:00