Skip to content

feat(host): hold UI adoption on backend changes (BOS_HOLD_ON_BACKEND_CHANGE) + the publish-to-user deploy flow - #897

Open
elliotBraem wants to merge 3 commits into
mainfrom
feat/deploy-flow-coherence-guard
Open

elliotBraem wants to merge 3 commits into
mainfrom
feat/deploy-flow-coherence-guard

Conversation

@elliotBraem

Copy link
Copy Markdown
Contributor

Ticket

fly-hosting 14 — update prompts: what a publish changes, how it reaches users, proven end to end.

The two hazards, and how this PR addresses each

1. UI/API incoherence during a backend publish

Today the old instance adopts the new UI generation within ~30s of a publish even when the API pin moved and it still runs the old API — the window where new UI calls routes the old API lacks.

Guard: opt-in host mode BOS_HOLD_ON_BACKEND_CHANGE=1 (set later by the hosting adapter's descriptor, ticket 07; default off, so single-box/self-hosted behavior is unchanged). Under the flag, adoptTransaction refuses to swap when bootFrozenRestartRemotes(...) is non-empty and records pendingRestart: [...] alongside the existing restartRequired surface on /health and /.well-known/version. A generation then moves as a unit: the old instance keeps serving a coherent old UI+API pair until the reconciler (ticket 08) replaces it, and the replacement boots both. Details:

  • held adopts exit before compose pre-warm and before chain verification — a held candidate is never composed, never persisted as an approved boot snapshot
  • the watch records the held pointer as seen (lastSeen), so it does not re-derive a refused generation every 30s; the tick outcome reports held
  • the coordinator exposes pendingRestart; it clears on any later adopt that swaps or converges
  • decision proven in host/tests/unit/snapshot-coordinator-hold.test.ts: UI+API publish → held, serving digest unchanged; UI-only publish → swapped; flag off → old adopt-and-flag behavior (restartRequired)

2. Old page, new API

After the instance is replaced, open tabs run the previous UI against the new API until Refresh.

Rule (documented, no code): API and plugin changes must stay backward compatible with the previous UI generation for at least one release cycle; a breaking change ships in two deploys. Recorded in docs/agents/deploy-flow.md (and the plugin-development surface inherits it via the flow link).

Banner scope decision (recorded): the banner stays signed-in-only — anonymous navigation picks up new versions naturally; no new complexity. Locked in by the new anonymous-tab spec.

What's here

  • feat(host) — the coherence guard: env flag read at adopt time, held outcome, pendingRestart ledger on SnapshotCoordinator, surfaced on /health + /.well-known/version, watch tick maps held + backoff reset, runtime-snapshot omits empty restartRequired
  • test(regression) — tests/regression/browser/specs/update-banner.spec.ts: UI-only publish → banner within the poll window → Refresh reloads → dismiss lasts the session (reload-proof); backend publish variant (restartRequired/pendingRestart payload); poll survives a failing endpoint (abort × 2 → recover, no page errors); anonymous tab never polls
  • docs(agents) — docs/agents/deploy-flow.md: the flow, hot vs boot-frozen table in plain words, the guard, the backward-compat rule, banner audience + timing (30s watch + 60s poll, ~90s worst case, auto-stop note), what the reconciler restarts, child secrets (NEAR_PRIVATE_KEY, BOS_STORAGE_API_KEY; Railway tokens transitional until ticket 13), framework upgrade flow (bos upgrade --dry-run → upgrade → lock → merge), rollback pointers (tickets 06/10), failing-deploy table (preflight, composition gate/bos mf check, smoke gate, drift, silent-banner diagnosis). Linked from AGENTS.md, .github/workflows/README.md, and the everything-dev-app skill. The fleet console's read-only "behind release X — run bos upgrade" indicator is recorded as a ticket 10 requirement; no button edits a repo.

Test evidence

  • pnpm --filter host test — 46 files, 292 tests green (includes the new hold-decision suite + the watch held-mapping test)
  • pnpm run typecheck green; pnpm run lint green; pnpm run docs:check green
  • Browser spec: 4/4 passed against the start:ssr regression stack (Docker, production-like), local run in this branch's worktree. Note on fidelity: the harness boots one instance from a staged config — it cannot publish a real pointer (no FastKV/NEAR credentials) and cannot replace the instance (reconciler, ticket 08), so the spec simulates the newer generation at the /.well-known/version boundary the banner actually polls. The reconciler-driven variants — a real second instance, Refresh landing on new bytes — are NOT exercised anywhere yet; they need ticket 08's adapter. Tracked as the remaining checkbox in the ticket.
  • Owner/CI step: green in the tier: release regression plane (this spec runs in the start:ssr + backcompat projects per playwright.config.mjs).

Doc locations

  • New: docs/agents/deploy-flow.md
  • Links added: AGENTS.md (Sync and Publish), .github/workflows/README.md (overview), .agents/skills/everything-dev-app/SKILL.md (Ship step)
  • No contradicting passages rewritten: docs/self-deploy.md and the workflows README's child Railway-leg detail are current transitional truth owned by tickets 13/07 — the flow doc marks Railway tokens as transitional instead.

This branch has not been deployed

No deployments
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