Phase 8 — Integration prep (cockpit_url + /admin/pool-refresh + cleanup hook + forge_id slot) #9

Open
opened 2026-05-21 14:48:06 +00:00 by mik-tf · 0 comments
Owner

Tracking issue for s2-010 (Phase 8 of the hero_onboarding arc per hero_onboarding#1).

Goal

Make hero_onboarding deployer-pluggable AHEAD of Track D shipping. When Track D's deployer API lands (under home#235), the integration is ~1-2 sessions (s2-015) instead of 5-6 because Phase 8 has already reserved the schema slots + plumbing.

Scope

  1. Schema additions (all ?: str additive — no codegen-breaking changes):

    • VmAllocation.cockpit_url — URL users follow from the dashboard's Active services table to reach the cockpit on their assigned VM. Populated from PoolVm.cockpit_url at allocate() time.
    • User.forge_id — slot reservation for s2-011 Phase 9 (Forge OAuth login + dual-auth model). Not populated by s2-010 code; just present in the schema so Phase 9 doesn't need a separate migration.
    • PoolVm.cockpit_url — operator's cockpit URL for each pool entry; flows into VmAllocation.cockpit_url at allocate time.
  2. New admin route POST /admin/pool-refresh (admin-secret-gated):

    • Accepts the same JSON shape as the VM_POOL_JSON env var (bare array OR {pool: [...]} wrapper).
    • Atomically swaps the in-memory pool via Arc<RwLock<Vec<PoolVm>>> (refactor from owned Vec in PoolAssignmentProvisioner).
    • Returns JSON {prev_pool_size, pool_size}.
    • 503 if pool provisioner not registered (no PROVISIONER_DEMO + no VM_POOL_JSON).
  3. Release cleanup hook stub:

    • Impl-local async fn on_release(&self, allocation: &VmAllocation) on PoolAssignmentProvisioner (NOT on the Provisioner trait per D-17 stability).
    • For v0 logs "deployer-release stub: would release pool_vm_id={} here".
    • Called from inside the trait's release() impl AFTER the OSIS write succeeds.
    • When Track D ships, this becomes a reqwest.post(deployer_url + "/admin/release-vm") call — single-call-site change.
  4. Dashboard surface: 5th column "Cockpit" in the Active services table — renders <a href="{cockpit_url}">Open →</a> when set, otherwise. Honest fallback for v0 pool entries without a URL.

  5. Admin surface: 13th column "cockpit" in the admin /allocations table.

Non-goals (deferred)

  • No Forge OAuth wiring — that's hero_onboarding#9 (Phase 9 / s2-011) territory. Phase 8 just reserves the forge_id? schema slot.
  • No actual deployer call — Track D doesn't ship deployer API until later in home#235. Phase 8 ships the hook stub so the wire-up site is single-shot when the API arrives.
  • No PoolVm.status mid-flight mutation — refresh_pool does whole-pool swap. Per-VM maintenance toggling stays in VM_POOL_JSON regen + refresh.
  • No mid-flight allocation safety — operator is responsible for the obvious pattern (don't refresh while allocates are in-flight at high rate; v0 expected pool sizes ≤ 100, low-frequency operator action).

Trait stability (per D-17)

The Provisioner async-trait shape (locked at s2-009 / D-17) is unchanged at Phase 8:

  • name() + is_demo() + allocate() + release() + status() — same 5 methods, same signatures.
  • refresh_pool and on_release are impl-local methods on PoolAssignmentProvisioner, NOT trait methods. v1 TfchainAutoDeployProvisioner will not have these (its "pool" is the on-chain VM registry).
  • AppState gains provisioner_pool: Option<Arc<PoolAssignmentProvisioner>> as a second reference to the same Arc already stored in provisioner: Option<Arc<dyn Provisioner>> — the standard Arc<T>Arc<dyn Trait> coercion pattern.

Acceptance

  • cargo check --workspace clean.
  • cargo test --workspace — 47/47 from s2-009 + 4 new provisioner unit tests = 51/51.
  • lab build --release --install --workspace VICTORY 3/3.
  • lab infocheck 3/3 clean.
  • cargo fmt --check + cargo clippy --workspace --all-targets -- -D warnings clean.
  • scripts/smoke_pool_refresh.sh (new, ~10 checks) green.
  • scripts/smoke_vm_allocate.sh 35/35 (33 from s2-009 + 2 new cockpit_url checks).

Cross-track

Zero file overlap with Track A's home#235 demo-deployer arc (currently on s137 per-user manifest writes in hero_cockpit). The new /admin/pool-refresh API surface is ready for Track D to call whenever they ship the deployer-side pool feed. No Track A coordination required for Phase 8 itself.

ID slots

  • No D-NN minted at s2-010 (all additions are mechanical; no design lock).
  • No L-NN expected — there are no coping fixes; the PoolVm.status: Maintenance mechanism already exists.

Reference

  • D-17 trait shape: decisions/D-17-provisioner-trait-shape.md in workspace.
  • Phase 7 outcome: hero_onboarding#8.
  • 5-session plan locked at s2-009 /stop2: Phase 8 (this) → Phase 9 (Forge OAuth + dual-auth, D-18) → Phase 10 (prod keys + runbook) → Phase 11 (refund + multi-currency) → Phase 12 (cron rehearsal + auto-release).
Tracking issue for s2-010 (Phase 8 of the hero_onboarding arc per [hero_onboarding#1](https://forge.ourworld.tf/lhumina_code/hero_onboarding/issues/1)). ## Goal Make `hero_onboarding` **deployer-pluggable AHEAD of Track D shipping**. When Track D's deployer API lands (under [home#235](https://forge.ourworld.tf/lhumina_code/home/issues/235)), the integration is ~1-2 sessions (s2-015) instead of 5-6 because Phase 8 has already reserved the schema slots + plumbing. ## Scope 1. **Schema additions** (all `?: str` additive — no codegen-breaking changes): - `VmAllocation.cockpit_url` — URL users follow from the dashboard's Active services table to reach the cockpit on their assigned VM. Populated from `PoolVm.cockpit_url` at `allocate()` time. - `User.forge_id` — slot reservation for s2-011 Phase 9 (Forge OAuth login + dual-auth model). Not populated by s2-010 code; just present in the schema so Phase 9 doesn't need a separate migration. - `PoolVm.cockpit_url` — operator's cockpit URL for each pool entry; flows into `VmAllocation.cockpit_url` at allocate time. 2. **New admin route** `POST /admin/pool-refresh` (admin-secret-gated): - Accepts the same JSON shape as the `VM_POOL_JSON` env var (bare array OR `{pool: [...]}` wrapper). - Atomically swaps the in-memory pool via `Arc<RwLock<Vec<PoolVm>>>` (refactor from owned `Vec` in `PoolAssignmentProvisioner`). - Returns JSON `{prev_pool_size, pool_size}`. - 503 if pool provisioner not registered (no `PROVISIONER_DEMO` + no `VM_POOL_JSON`). 3. **Release cleanup hook stub:** - Impl-local `async fn on_release(&self, allocation: &VmAllocation)` on `PoolAssignmentProvisioner` (NOT on the `Provisioner` trait per D-17 stability). - For v0 logs `"deployer-release stub: would release pool_vm_id={} here"`. - Called from inside the trait's `release()` impl AFTER the OSIS write succeeds. - When Track D ships, this becomes a `reqwest.post(deployer_url + "/admin/release-vm")` call — single-call-site change. 4. **Dashboard surface**: 5th column "Cockpit" in the Active services table — renders `<a href="{cockpit_url}">Open →</a>` when set, `—` otherwise. Honest fallback for v0 pool entries without a URL. 5. **Admin surface**: 13th column "cockpit" in the admin `/allocations` table. ## Non-goals (deferred) - No Forge OAuth wiring — that's [hero_onboarding#9](https://forge.ourworld.tf/lhumina_code/hero_onboarding/issues/9) (Phase 9 / s2-011) territory. Phase 8 just reserves the `forge_id?` schema slot. - No actual deployer call — Track D doesn't ship deployer API until later in [home#235](https://forge.ourworld.tf/lhumina_code/home/issues/235). Phase 8 ships the hook stub so the wire-up site is single-shot when the API arrives. - No `PoolVm.status` mid-flight mutation — `refresh_pool` does whole-pool swap. Per-VM maintenance toggling stays in `VM_POOL_JSON` regen + refresh. - No mid-flight allocation safety — operator is responsible for the obvious pattern (don't refresh while allocates are in-flight at high rate; v0 expected pool sizes ≤ 100, low-frequency operator action). ## Trait stability (per D-17) The `Provisioner` async-trait shape (locked at s2-009 / D-17) is **unchanged** at Phase 8: - `name()` + `is_demo()` + `allocate()` + `release()` + `status()` — same 5 methods, same signatures. - `refresh_pool` and `on_release` are impl-local methods on `PoolAssignmentProvisioner`, NOT trait methods. v1 `TfchainAutoDeployProvisioner` will not have these (its "pool" is the on-chain VM registry). - AppState gains `provisioner_pool: Option<Arc<PoolAssignmentProvisioner>>` as a second reference to the same `Arc` already stored in `provisioner: Option<Arc<dyn Provisioner>>` — the standard `Arc<T>` ↔ `Arc<dyn Trait>` coercion pattern. ## Acceptance - `cargo check --workspace` clean. - `cargo test --workspace` — 47/47 from s2-009 + 4 new provisioner unit tests = 51/51. - `lab build --release --install --workspace` VICTORY 3/3. - `lab infocheck` 3/3 clean. - `cargo fmt --check` + `cargo clippy --workspace --all-targets -- -D warnings` clean. - `scripts/smoke_pool_refresh.sh` (new, ~10 checks) green. - `scripts/smoke_vm_allocate.sh` 35/35 (33 from s2-009 + 2 new cockpit_url checks). ## Cross-track Zero file overlap with Track A's [home#235](https://forge.ourworld.tf/lhumina_code/home/issues/235) demo-deployer arc (currently on s137 per-user manifest writes in hero_cockpit). The new `/admin/pool-refresh` API surface is **ready for Track D** to call whenever they ship the deployer-side pool feed. No Track A coordination required for Phase 8 itself. ## ID slots - No D-NN minted at s2-010 (all additions are mechanical; no design lock). - No L-NN expected — there are no coping fixes; the `PoolVm.status: Maintenance` mechanism already exists. ## Reference - D-17 trait shape: [`decisions/D-17-provisioner-trait-shape.md`](https://forge.ourworld.tf/lhumina_code/hero_onboarding/issues/1) in workspace. - Phase 7 outcome: [hero_onboarding#8](https://forge.ourworld.tf/lhumina_code/hero_onboarding/issues/8). - 5-session plan locked at s2-009 /stop2: Phase 8 (this) → Phase 9 (Forge OAuth + dual-auth, **D-18**) → Phase 10 (prod keys + runbook) → Phase 11 (refund + multi-currency) → Phase 12 (cron rehearsal + auto-release).
Sign in to join this conversation.
No labels
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
lhumina_code/hero_onboarding#9
No description provided.