feat(control-plane): persist provisioning and failed tenant lifecycle states for the customer status surface - #8154
Conversation
… states for the customer status surface (JSONbored#7677)
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8154 +/- ##
==========================================
- Coverage 92.04% 91.51% -0.54%
==========================================
Files 765 765
Lines 77668 77701 +33
Branches 23472 23474 +2
==========================================
- Hits 71493 71111 -382
- Misses 5062 5524 +462
+ Partials 1113 1066 -47
Flags with carried forward coverage won't be shown. Click here to find out more.
|
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-23 06:35:22 UTC
Review summary Nits — 4 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionPartially addressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
Summary
Implements #7677's ratified decision (2026-07-21) — the provisioning-status state machine behind the customer-facing polling surface:
"failed"added toTenantLifecycleState(control-plane/src/tenant-provisioning-driver.ts) — the spec's bolded gap: today a provision-step failure pages and rethrows but persists nothing, so a polling customer would watch a record stuck at"provisioning"forever with no signal.provisionTenanttransitions the record to"failed"before rethrowing (control-plane/src/provisioning.ts): a new optionalonFailureseam runs first in the existing catch — best-effort by design (its own rejection is swallowed), so a KV write outage can never mask the provisioning error, which still pages (Wire real PagerDuty alerting into control-plane provisioning failures #7667) and rethrows exactly as before."provisioning"BEFORE the standup starts (POST /v1/tenants,control-plane/src/http-app.ts): without this pre-write, the transitional state the decision's polling loop watches ("Customer dashboard polls every 3-5s while inprovisioning") was never observable at all — the old flow only ever wrote the record after success. Per the ratified decision, the polling surface is the already-decidedGET /v1/tenantstransport (Stand up control-plane's real HTTP transport (POST/GET/DELETE /v1/tenants) matching the already-merged client #7654) — no new endpoint is added."failed"is re-creatable, like"torn down"(isRecreatableState, both conflict sites — name+product and the Route incoming GitHub webhooks to the correct tenant's hosted ORB container #7181 installation-ID claim): before this PR a failure left NO record so a retry always worked; persisting the failure must not turn "Setup failed" into a permanent squat on the tenant name. An active tenant still 409s both checks, pinned by test.DELETEof a failed tenant needs no change —deprovisionTenantis idempotent by driver contract, so cleanup works as-is.Deliberate scope line, from the spec's own text: the customer dashboard polling UI and its three-state copy ("Setting up your instance" / "Ready" / "Setup failed") live "alongside #4926/#4927" — both of which are open and owner-assigned; that surface is the maintainer's in-flight redesign, not contributor territory. This PR delivers the state machine and its observability through the exact read path that dashboard will poll — the acceptance test below is worded directly from the spec's own test deliverable: "a provisioning failure transitions the record to
failedand is observable via the same read path a customer's dashboard uses." The state→copy mapping is preserved verbatim in the lifecycle type's doc comment for that follow-up to consume.Closes #7677
Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally;codecov/patchrequires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateIf any required check was skipped, explain why:
control-plane/package (own npm package, own build, ownnode:testsuite, own Codecov flag), so the checks that actually exercise it were run in full, not the root chain whose suites never import it:npm --prefix control-plane test→ build + 187/187 tests pass (182 existing + the 5 added below).npm --prefix control-plane run cf:typecheck(the Worker tsconfig) → clean.npm run control-plane:coverage→ package 99.94% lines / 99.05% functions; empirically intersectinglcov.infowith this diff's changed lines: 48/48 changed instrumented lines covered, zero uncovered branch arms — patch = 100% lines and branches (measured per line/arm with max-merged DA/BRDA records, not inferred from file totals).npm audit --audit-level=moderate→found 0 vulnerabilities;git diff --checkclean.typecheck,test:coverage, workers/mcp/ui/openapi) cover surfaces this PR does not touch — nosrc/**,packages/**, orapps/**file changes; roottsconfigdoes not includecontrol-plane/(it typechecks under its own two tsconfigs, both run above). Also swept every repo-wide consumer ofTenantLifecycleStatebefore widening the union: no exhaustive switch/mapping exists (the miner'stenant-clientpasses states through verbatim with deliberately non-exhaustive docs), so nothing outside the package can be broken by the new member.Safety
UI Evidencesection below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. SVG screenshots are not used as review evidence. Review-only screenshots or recordings are not committed to the repository.Unchecked boxes above, and why — all N/A rather than skipped:
Notes
control-plane/test/http-app.test.ts, +5, mirroring the existing driver-failure/onError idioms):"failed", andGET /v1/tenants— the dashboard's read path — returns the terminal failed state, not a record stuck at"provisioning"."provisioning"while provisioning is in flight, then"active"after release — the transitional state the 3-5s poll watches now actually exists on the wire."failed"tenant (including one holding an installation-ID claim) is re-creatable — 201 on retry with a healthy driver; before Spec: customer-facing repo-provisioning status surface (provisioning/ready/failed) #7677 a failure left no record, so this pins that persisting failures doesn't regress retryability."active"tenant still 409s both the name+product check and the installation-claim check."failed"write still surfaces the ORIGINAL provisioning error (500internal_error), with the record left at its pre-written"provisioning"state — the swallowed-rejection arm of the new catch path, covered explicitly."provisioning"→ "Setting up your instance" (poll every 3-5s) ·"active"→ "Ready" (terminal, stop polling) ·"failed"→ "Setup failed" (terminal, stop polling) ·"suspended"/"torn down"→ post-provisioning operator states, out of the provisioning flow's scope.