feat(traffic): reconcile one-box hostnames back onto the router every tick #458

Merged
gmackie merged 1 commit from feat/onebox-hostname-reconcile into main 2026-08-26 06:07:50 +00:00
Owner

Closes the domain-steal path the agent-side strip (#428 / agent 0.1.57) deliberately leaves open: a production deploy of an app whose wrangler config declares its own domains re-attaches them to the app worker — the config name matches the target, so the override strip does not fire — and one-box silently stops routing while still reporting weights. This is live-relevant today: habitplay's wrangler.jsonc declares habitplay.io+www, so its next production deploy would steal both back from habit-app-router.

Mechanism: tickTrafficLifecycles now ends each sweep (at most every 5 min) by re-asserting hostnames → router for every enabled split in idle/baking/failed whose state settled ≥2 min ago (draining/shifting are excluded — the shift machinery owns the binding mid-flight). A foreign attachment is moved back via delete-then-ensure with restore-on-failure (the rebindHostnames contract, so a CF hiccup can't leave a hostname dark), and a hostname attached to nothing — how habitplay.io went down — is re-created. Moves land as traffic.hostnames_reconciled events; failures as traffic.reconcile_failed, retried next sweep. One CF listing per workspace per sweep.

6 new tests (3 router-level, 3 tick-level); 16/16 lifecycle + 41/41 router suites green; tsc clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CMpzX1b6swjezEptw3T71f

Closes the domain-steal path the agent-side strip (#428 / agent 0.1.57) deliberately leaves open: a **production** deploy of an app whose wrangler config declares its own domains re-attaches them to the app worker — the config `name` matches the target, so the override strip does not fire — and one-box silently stops routing while still reporting weights. This is live-relevant today: habitplay's wrangler.jsonc declares habitplay.io+www, so its next production deploy would steal both back from `habit-app-router`. **Mechanism**: `tickTrafficLifecycles` now ends each sweep (at most every 5 min) by re-asserting `hostnames → router` for every enabled split in `idle`/`baking`/`failed` whose state settled ≥2 min ago (draining/shifting are excluded — the shift machinery owns the binding mid-flight). A foreign attachment is moved back via delete-then-ensure with restore-on-failure (the `rebindHostnames` contract, so a CF hiccup can't leave a hostname dark), and a hostname attached to nothing — how habitplay.io went down — is re-created. Moves land as `traffic.hostnames_reconciled` events; failures as `traffic.reconcile_failed`, retried next sweep. One CF listing per workspace per sweep. 6 new tests (3 router-level, 3 tick-level); 16/16 lifecycle + 41/41 router suites green; tsc clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01CMpzX1b6swjezEptw3T71f
feat(traffic): reconcile one-box hostnames back onto the router every tick
All checks were successful
CI / gitleaks (pull_request) Successful in 6s
CI / storybook (pull_request) Successful in 1m26s
forgegraph/ci CI passed
CI / ci (pull_request) Successful in 9m47s
e10d723c5d
Closes the steal path the agent-side route strip cannot cover: a
PRODUCTION deploy of an app whose wrangler config declares its own
domains re-attaches them to the app worker (the config name matches the
target, so the override strip deliberately does not fire) — one-box
keeps reporting weights that no longer route anything. The lifecycle
tick now sweeps every enabled, settled split (idle/baking/failed, state
older than 2 min) at most every 5 minutes and re-asserts hostnames →
router: a foreign attachment is moved back (delete-then-ensure with
restore-on-failure, the rebindHostnames contract), and a hostname
attached to nothing — the habitplay deletion case — is re-created.
Moves are recorded as traffic.hostnames_reconciled events; per-split
failures become traffic.reconcile_failed and the next sweep retries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMpzX1b6swjezEptw3T71f
Author
Owner

Preview environment is live: https://pr-458-forgegraph.forgegraf.com

Deployed e10d723c with the beta stage's environment. It redeploys on every push and is destroyed when this PR closes.

Preview environment is live: https://pr-458-forgegraph.forgegraf.com Deployed `e10d723c` with the beta stage's environment. It redeploys on every push and is destroyed when this PR closes.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
gmackie/ForgeGraph!458
No description provided.