portal-mojo 0.2.19 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/docs/README.md +7 -1
  2. package/docs/admin-credentials.md +27 -0
  3. package/docs/admin-deployments.md +201 -0
  4. package/docs/admin-dns-certificates.md +160 -0
  5. package/docs/admin-dns-domains.md +92 -0
  6. package/docs/admin-dns.md +18 -0
  7. package/docs/admin-edge.md +200 -196
  8. package/docs/admin-fleet-configuration.md +98 -2
  9. package/docs/admin-geolocation.md +100 -0
  10. package/docs/admin-identity-directories.md +12 -0
  11. package/docs/admin-maintenance.md +133 -0
  12. package/docs/admin-modals.md +45 -2
  13. package/docs/admin-operational-fleet.md +206 -0
  14. package/docs/admin-presentation.md +51 -0
  15. package/docs/admin-signin.md +12 -2
  16. package/docs/auth-pages.md +18 -6
  17. package/docs/auth.md +16 -2
  18. package/docs/client.md +7 -0
  19. package/docs/collection-select.md +14 -0
  20. package/docs/combobox.md +18 -0
  21. package/docs/popover.md +5 -4
  22. package/package.json +1 -1
  23. package/src/admin/core/OperationsHelp.tsx +10 -0
  24. package/src/admin/core/index.ts +6 -4
  25. package/src/admin/core/route-context.ts +6 -0
  26. package/src/admin/credentials/AcmeConsumerKeyDialog.tsx +88 -0
  27. package/src/admin/credentials/acme-consumer-key.ts +34 -0
  28. package/src/admin/credentials/group-api-keys.tsx +5 -3
  29. package/src/admin/deployments/ApiDeploymentDetail.tsx +58 -0
  30. package/src/admin/deployments/DeploymentsPage.tsx +73 -0
  31. package/src/admin/deployments/WebAppDeploySetup.tsx +49 -0
  32. package/src/admin/deployments/WebAppDeploymentDetail.tsx +59 -0
  33. package/src/admin/deployments/WebAppDetail.tsx +104 -0
  34. package/src/admin/deployments/WebAppEditDialog.tsx +70 -0
  35. package/src/admin/deployments/WebAppKeyDialog.tsx +25 -0
  36. package/src/admin/deployments/WebAppOnboardingDialog.tsx +217 -0
  37. package/src/admin/deployments/WebAppRollbackDialog.tsx +82 -0
  38. package/src/admin/deployments/WebAppUploadPanel.tsx +132 -0
  39. package/src/admin/deployments/api.ts +158 -0
  40. package/src/admin/deployments/authority-state.ts +9 -0
  41. package/src/admin/deployments/deployment-state.ts +13 -0
  42. package/src/admin/deployments/index.ts +4 -0
  43. package/src/admin/deployments/onboarding-state.ts +11 -0
  44. package/src/admin/deployments/permissions.ts +37 -0
  45. package/src/admin/deployments/receipts.ts +78 -0
  46. package/src/admin/deployments/sanitize.ts +73 -0
  47. package/src/admin/deployments/shared.tsx +39 -0
  48. package/src/admin/deployments/types.ts +238 -0
  49. package/src/admin/deployments/upload.ts +53 -0
  50. package/src/admin/deployments/useDeploymentObservation.ts +104 -0
  51. package/src/admin/dns/CertificateDetail.tsx +8 -3
  52. package/src/admin/dns/CertificateRequestDialog.tsx +45 -7
  53. package/src/admin/dns/DomainDetail.tsx +14 -4
  54. package/src/admin/dns/DomainOnboardingContent.tsx +44 -0
  55. package/src/admin/dns/DomainOnboardingDialog.tsx +5 -24
  56. package/src/admin/dns/DomainPurchaseWizard.tsx +1 -1
  57. package/src/admin/dns/DomainRegisterExisting.tsx +63 -0
  58. package/src/admin/dns/DomainRegistrarSettings.tsx +52 -0
  59. package/src/admin/dns/DomainsPage.tsx +26 -13
  60. package/src/admin/dns/SharedValidationDialog.tsx +298 -0
  61. package/src/admin/dns/SharedValidationPanel.tsx +76 -0
  62. package/src/admin/dns/api.ts +4 -2
  63. package/src/admin/dns/domain-navigation.ts +16 -0
  64. package/src/admin/dns/registrar-api.ts +13 -0
  65. package/src/admin/dns/shared-validation-api.ts +90 -0
  66. package/src/admin/dns/shared-validation-data.ts +106 -0
  67. package/src/admin/domains/assistant.ts +5 -4
  68. package/src/admin/domains/communications.ts +10 -9
  69. package/src/admin/domains/identity.ts +8 -7
  70. package/src/admin/domains/infrastructure.ts +19 -13
  71. package/src/admin/domains/lazy-page.ts +6 -0
  72. package/src/admin/domains/observability.ts +6 -5
  73. package/src/admin/domains/operations.ts +27 -10
  74. package/src/admin/domains/security.ts +16 -15
  75. package/src/admin/edge/EdgeHelp.tsx +10 -0
  76. package/src/admin/edge/ServingReadiness.tsx +54 -0
  77. package/src/admin/edge/VhostDetail.tsx +145 -139
  78. package/src/admin/edge/VhostWizard.tsx +204 -115
  79. package/src/admin/edge/VhostsPage.tsx +27 -28
  80. package/src/admin/edge/api.ts +24 -10
  81. package/src/admin/edge/models.ts +4 -4
  82. package/src/admin/edge/serving-data.ts +78 -0
  83. package/src/admin/fleet/CapacityEditor.tsx +81 -0
  84. package/src/admin/fleet/CapacityInspector.tsx +19 -0
  85. package/src/admin/fleet/CapacityProgress.tsx +9 -0
  86. package/src/admin/fleet/FleetHelp.tsx +7 -0
  87. package/src/admin/fleet/FleetOverview.tsx +150 -0
  88. package/src/admin/fleet/FleetPage.tsx +11 -0
  89. package/src/admin/fleet/FleetWorkspace.tsx +82 -0
  90. package/src/admin/fleet/ScaleFleetDialog.tsx +169 -0
  91. package/src/admin/fleet/access.ts +22 -0
  92. package/src/admin/fleet/api.ts +77 -0
  93. package/src/admin/fleet/data.ts +148 -0
  94. package/src/admin/fleet/index.ts +8 -0
  95. package/src/admin/fleet/operations.ts +179 -0
  96. package/src/admin/fleet/standalone-data.ts +87 -0
  97. package/src/admin/fleet/types.ts +208 -0
  98. package/src/admin/fleet-configuration/FleetConfigurationPage.tsx +14 -8
  99. package/src/admin/fleet-configuration/SharedValidationConnectionPanel.tsx +166 -0
  100. package/src/admin/fleet-configuration/data.ts +4 -0
  101. package/src/admin/fleet-configuration/operator-destinations.ts +20 -0
  102. package/src/admin/fleet-configuration/shared-validation-data.ts +143 -0
  103. package/src/admin/geolocation/GeoLocationPage.tsx +171 -0
  104. package/src/admin/geolocation/data.ts +138 -0
  105. package/src/admin/geolocation/index.ts +3 -0
  106. package/src/admin/identity/groups/group-sections/SubGroupsSection.tsx +73 -2
  107. package/src/admin/identity/groups/models.ts +2 -0
  108. package/src/admin/identity/members/GroupMembersPanel.tsx +2 -8
  109. package/src/admin/index.ts +8 -0
  110. package/src/admin/maintenance/MaintenanceDialogs.tsx +161 -0
  111. package/src/admin/maintenance/MaintenancePage.tsx +442 -0
  112. package/src/admin/maintenance/data.ts +486 -0
  113. package/src/admin/maintenance/index.ts +3 -0
  114. package/src/admin/maintenance/operations.ts +208 -0
  115. package/src/admin/public/infrastructure.ts +1 -0
  116. package/src/admin/public/operations.ts +3 -0
  117. package/src/admin/registry.ts +6 -2
  118. package/src/admin/settings/SettingDetail.tsx +7 -2
  119. package/src/admin/settings/SettingEditor.tsx +2 -1
  120. package/src/admin/settings/SettingsPage.tsx +3 -1
  121. package/src/admin/settings/model.ts +1 -0
  122. package/src/admin/signin/SigninPage.tsx +17 -3
  123. package/src/admin/signin/api.ts +2 -0
  124. package/src/charts/MetricsChartView.tsx +4 -11
  125. package/src/charts/MetricsMiniWidget.tsx +3 -15
  126. package/src/client/auth.ts +77 -25
  127. package/src/client/client.ts +6 -0
  128. package/src/client/deployments-mock.ts +375 -0
  129. package/src/client/domain-operations-mock.ts +37 -0
  130. package/src/client/fleet-mock.ts +73 -1
  131. package/src/client/geo-provider-mock.ts +82 -0
  132. package/src/client/index.ts +2 -2
  133. package/src/client/maintenance-mock.ts +285 -0
  134. package/src/client/mock.ts +280 -11
  135. package/src/client/operational-fleet-mock.ts +279 -0
  136. package/src/client/operations-mock.ts +85 -0
  137. package/src/client/serving-mock.ts +22 -0
  138. package/src/styles/components/admin-deployments.css +35 -0
  139. package/src/styles/components/admin-dns-certificates.css +33 -0
  140. package/src/styles/components/admin-domains.css +12 -0
  141. package/src/styles/components/admin-edge.css +125 -1
  142. package/src/styles/components/admin-geolocation.css +40 -0
  143. package/src/styles/components/admin-maintenance.css +127 -0
  144. package/src/styles/components/admin-operational-fleet.css +38 -0
  145. package/src/styles/components/admin-operations.css +9 -0
  146. package/src/styles/components/admin-signin.css +2 -0
  147. package/src/styles/components/combobox.css +12 -2
  148. package/src/styles/components/fleet-page.css +9 -0
  149. package/src/styles/index.css +7 -0
  150. package/src/ui/CollectionSelect.tsx +30 -8
  151. package/src/ui/ComboBox.tsx +39 -8
  152. package/src/ui/FilterBar.tsx +3 -5
  153. package/src/ui/ModelTable.tsx +2 -8
  154. package/src/ui/Popover.tsx +15 -7
  155. package/src/ui/date/TimePicker.tsx +4 -14
  156. package/src/ui/image-editor/ImageEditor.tsx +4 -5
  157. package/src/ui/schema-form-core.tsx +3 -5
package/docs/README.md CHANGED
@@ -14,11 +14,17 @@ everything: run the showcase (`npm run dev:showcase`) → **Develop → Componen
14
14
  | [realtime.md](realtime.md) | Auth-challenged WebSocket lifecycle, heartbeat/backoff, refcounted topics, typed event projection, deterministic mock |
15
15
  | [auth-pages.md](auth-pages.md) | The in-app auth pages: routes + guard, `VITE_MOJO_AUTH` switch, fresh-auth (440 step-up) modal, MFA panel, password tools |
16
16
  | [admin-credentials.md](admin-credentials.md) | Group API keys and webhook subscriptions — dual-mount pages, lossless per-endpoint rate limits, permission registry, secret-safe create/reveal/rotation |
17
+ | [admin-presentation.md](admin-presentation.md) | KISS presentation rules, shared styles and required rendered visual QA |
17
18
  | [admin-dashboard.md](admin-dashboard.md) | Package-owned global Admin landing, authoritative signals, independent gates, caller-isolated caches, and root fallback |
18
19
  | [admin-cloudwatch.md](admin-cloudwatch.md) | Global EC2/RDS/Redis CloudWatch charts, exact resource/fetch wires, constrained controls, refresh ownership, and modal details |
19
20
  | [admin-monitoring.md](admin-monitoring.md) | Monitoring Admin registration, logs, and exact Redis-backed metrics-permission mutations |
20
21
  | [admin-metrics-explorer.md](admin-metrics-explorer.md) | Read-only recorded-metrics discovery, full-slug adapters, KPI/fan-out/scalar contracts, and cache identity |
21
22
  | [admin-settings.md](admin-settings.md) | Runtime settings admin — direction-aware atomic writes, write-only secrets, and global/group scope |
23
+ | [admin-deployments.md](admin-deployments.md) | API serving/attempt evidence, WebApp release recovery, durable onboarding, transient CI keys and real uploads |
24
+ | [admin-dns-domains.md](admin-dns-domains.md) | Simple domain inventory, exact-name registration/adoption/purchase, focused registrar settings and DNS links |
25
+ | [admin-maintenance.md](admin-maintenance.md) | Independent AWS upgrade scans and framework policy, exact confirmation, observation and uncertain operation guards |
26
+ | [admin-geolocation.md](admin-geolocation.md) | Owner-only GeoIP provider setup, ordered fallbacks, transient credential verification and publishing |
27
+ | [admin-operational-fleet.md](admin-operational-fleet.md) | Operational health matrix, exact backend grants, reviewed stored capacity plans and bounded batch observation |
22
28
  | [admin-fleet-configuration.md](admin-fleet-configuration.md) | Superuser structured configuration publishing, version restore and per-node application evidence |
23
29
  | [admin-bouncer.md](admin-bouncer.md) | Bouncer decisions, device investigations, and bot-signature administration with token-safe caching |
24
30
  | [admin-security-tickets.md](admin-security-tickets.md) | Ticket queue, KISS detail modal, notes, approvals, and Maestro sync contract |
@@ -36,7 +42,7 @@ everything: run the showcase (`npm run dev:showcase`) → **Develop → Componen
36
42
  | [admin-dns-registrar.md](admin-dns-registrar.md) | Domain search/quote/purchase, one-use token custody, durable-ledger reconciliation, scoped registrant PII, and manual House adoption |
37
43
  | [admin-storage.md](admin-storage.md) | Global S3 buckets, masked storage backends, explicit policy-backed File uploads, capability-safe FileView/shares, and finite rendition convergence |
38
44
  | [admin-media-rendering.md](admin-media-rendering.md) | Storage → Media rendering: the three FILEMAN_RENDITIONS_* settings as a descriptor-driven form — delta-from-defaults writes, inline 400 routing, reset by writing `{}` |
39
- | [admin-edge.md](admin-edge.md) | Edge vhosts by template kind with a shape-first wizard, `site_api` routes and their create order, declared upstreams, and the fleet blocklist with log-first modes. |
45
+ | [admin-edge.md](admin-edge.md) | Serving hostname configuration with Address → Routing → Certificate → Review creation, independently admitted node posture and bounded hosting readiness, `site_api` routes and their create order, declared upstreams, and the fleet blocklist with log-first modes. |
40
46
  | [admin-shortlinks.md](admin-shortlinks.md) | Global shortlink operations, destination-free caches, bounded click history, and reconciled one-shot mutations |
41
47
  | [admin-messaging.md](admin-messaging.md) | SES domains/mailboxes/sent/templates plus public contact requests, with write-only credentials and offline HTML previews |
42
48
  | [admin-phonehub.md](admin-phonehub.md) | Global Phone Hub: evidence-backed lookup, sanitized SMS audits, and write-only provider configuration |
@@ -206,3 +206,30 @@ See [Admin modal policy](admin-modals.md) for header actions, content-only
206
206
  sections, supported lifecycle fields and retained irreversible exceptions.
207
207
  Group API keys and webhook subscriptions deactivate/reactivate; their built-in
208
208
  Delete controls are removed. Reveal, secret rotation and cache boundaries remain.
209
+
210
+ ## Protected ACME consumer purpose
211
+
212
+ Literal deployment operators can open **Create ACME consumer key** from the
213
+ existing fixed-Group or cross-Group API-key inventory. This lazy form resolves
214
+ the selected original hub Group through the private shared-validation recipient
215
+ endpoint. It displays the actual active Group name/UUID, the named ACME validation
216
+ consumer purpose and the fixed `dnsman_acme_federation` consequence. A tenant or
217
+ acting user cannot substitute for the original recipient. It has no freeform
218
+ permission or JSON input. The server rechecks Group activity, expected label and
219
+ protected grant at issuance and rejects key-backed sessions.
220
+
221
+ Issue is separately confirmed, single-flight and session-bound. Its response is
222
+ split before presentation: safe key metadata may be displayed; the transient
223
+ token stays outside Query/Mutation caches, URL/history and storage. Closing the
224
+ native token dialog drops that display. The existing explicit, audited
225
+ `/api/group/apikey/<id>?graph=token` reveal remains the recovery route. A timeout
226
+ may follow successful issuance: inspect the exact Group's recent inventory and
227
+ use audited reveal before creating another successor. The form blocks automatic
228
+ reissue and never retries after fresh authentication.
229
+
230
+ Create a replacement, enter it normally in the receiving deployment's protected
231
+ connection form, Preview/Publish, Apply and verify every expected node. Only then
232
+ explicitly review/deactivate the old hub key using existing key controls. This
233
+ form neither rotates nor retires keys, grants allocation trust, activates hub
234
+ sharing or asserts cross-deployment verification. Existing in-place key rotation
235
+ semantics remain unchanged and can invalidate the previous token immediately.
@@ -0,0 +1,201 @@
1
+ # Deployments
2
+
3
+ `DeploymentsPage` contributes one global Operations workspace. The Admin section
4
+ singleton belongs to `admin/domains/operations.ts`; its route lazily imports the
5
+ page. `WebAppDetail({id,onClose})` is a focused `modal.open` task dialog.
6
+ Import these through `portal-mojo/admin/operations` after central registration.
7
+ The domain's owned stylesheet is `styles/components/admin-deployments.css`.
8
+
9
+ API and WebApp lanes are independently admitted. API sections require exact global
10
+ `view_platform|manage_platform|admin`; `view_platform_security` alone never fetches
11
+ deployments. WebApps require exact global `view_dns|manage_dns|security` in this
12
+ Admin workspace. The finite Fleet `backendGlobalCan` helper reproduces backend
13
+ category expansion; the frontend's broader admin wildcard does not admit reads.
14
+ Inactive lanes and denied identities mount no domain query. No active group is
15
+ inherited. Explicit owning-group action scope is separate from page scope.
16
+
17
+ API uses `GET /api/account/admin/platform?sections=deployments,api`. The last
18
+ converged serving SHA and its observation date remain separate from the latest
19
+ attempt. Server node summary distinguishes restart proof, reported success,
20
+ dispatched and failed nodes. Durable diagnosis is separate from replaceable node
21
+ observations. Unknown and stale states remain visible. Recovery supports retry
22
+ same SHA, verify and converge according to the exact attempt status matrix; each
23
+ confirmation refreshes the attempt first. Retry sends a stable bounded
24
+ `Idempotency-Key`, then watches its returned attempt ID. There is no arbitrary SHA
25
+ input, API rollback, framework update or pin editor.
26
+
27
+ WebApp summary lists are bounded to 50 with honest truncation disclosure. Full
28
+ summary, default graph ownership, registered release history and deployment
29
+ attempt history are separate reads. The default graph is positively projected:
30
+ no bucket, prefix, key, unrelated group data or raw metadata reaches Query.
31
+ Full summary's latest attempt has no release identity; the detail resolves its
32
+ exact app/attempt-filtered generic history record before naming the version.
33
+ Generic attempt graphs omit the parent, so the request binds both `webapp` and
34
+ `id`, asks for one row, and validates the returned attempt and release IDs.
35
+ Ordinary viewers are never sent to the stronger rollout-status endpoint: it
36
+ requires WebApp SAVE authority. Admitted operators can inspect node evidence
37
+ through that endpoint. Selected active attempts use bounded, owner-fenced
38
+ observation; terminal outcomes invalidate the owning summary and history.
39
+ The current release pointer is desired content, never proof of fleet convergence.
40
+ HTTPS health and active certificates are independent observations.
41
+
42
+ Rollback requires `manage_webapp` plus the owning object's SAVE authority (`manage_dns|security`).
43
+ Current-user membership may be resolved only for that known app's owning group;
44
+ global grants bypass unnecessary membership reads. Bootstrap eligible groups do
45
+ not grant or narrow existing-app custom actions. Manual release registration has
46
+ its own exact object SAVE predicate (`manage_dns|security`); read-only `view_dns` does not grant release registration; workflow generation requires security or
47
+ both WebApp and DNS management. A 403 removes app mutation controls for that
48
+ session. The backend remains authoritative for all object, actor, origin,
49
+ interactive and freshness rules.
50
+
51
+ Release and deployment history have separate paged views. Eligible release rows
52
+ open a preselected rollback; the focused chooser also pages beyond the first 25.
53
+ Rollback selects a concrete noncurrent release in `uploaded|live|superseded`,
54
+ requires typed `app/version`, refreshes ownership/current pointer and the exact app/target-filtered release, then
55
+ sends `{webapp,release,group}`. It watches the exact returned deployment through
56
+ `/api/edge/release/deployment/ID`, validating deployment/app/release identities.
57
+ Only `live` is success. `rolled_back` means the attempted version was unsuccessful;
58
+ a failed rollout can leave mixed nodes. An uncertain rollback is not replayed on
59
+ remount. Its safe receipt blocks conflicting app mutations while it is unsettled.
60
+
61
+ Before creation, App and Build options share one retained draft. After creation,
62
+ only the current server task appears beside noninteractive App → Address →
63
+ Repository → HTTPS progress; operation/revision/activity live in Setup details.
64
+ Closing preserves setup; cancelling server setup requires explicit confirmation.
65
+ New WebApp uses separately admitted bootstrap eligibility and server onboarding
66
+ options/precheck. Missing destination or allowed bucket blocks creation with a concrete prerequisite. Existing workspaces precheck their concrete address before create. New-workspace intent deliberately has no domain before create and does not require an invented ready verdict. Defaults are production, main,
67
+ dist and the first allowed bucket. The durable server operation owns cursor,
68
+ revision, status, activity and resources. Choose/cancel use its latest observation;
69
+ a revision/lease refusal requires another detail read, never repeated POST.
70
+ Unknown cursors and parked waiting states stop automatic reads. Optional GitHub
71
+ verification preserves `unavailable` versus explicit attestation. Closing does not
72
+ cancel server work. A matching actor/origin safe receipt resumes by operation UUID;
73
+ an app without such a receipt shows unfinished diagnostics without creating a
74
+ replacement resource. An uncertain create can be deliberately retried only with
75
+ its immutable profile and UUID after an exact 404 observation.
76
+
77
+ Deploy opens its GitHub, Upload and Other CI methods directly. Workflow and
78
+ Other CI instructions use focused dialogs. Linked-key controls follow observed
79
+ state: create when unlinked; rotate when linked; revoke only an active key. Both
80
+ CI methods expose these controls. Deploy setup lazy-loads key/upload surfaces.
81
+ Generated YAML stays outside
82
+ Query and references one repository secret named `MOJO_DEPLOY_KEY`. Copy/download
83
+ instructions leave installation to the operator. Keys are revealed in one focused
84
+ transient dialog, cleared on close, authority/session replacement or unmount.
85
+ Receipt replay with `token:null` says the secret is unavailable and requires an
86
+ explicit new rotation. Rotate/revoke explain CI impact; existing content remains
87
+ independent. Revoke's backend freshness window is 300 seconds; other sensitive
88
+ custom actions use 600. A 440 prompts through `requestFreshAuth` but never replays
89
+ the POST; refresh and renewed explicit consent are required.
90
+
91
+ Built artifact upload is register → anonymous checksum-bound PUT → complete →
92
+ exact deployment observation. Files are normalized relative paths, reject
93
+ traversal/collisions, and are hashed with SHA-256 before registration. Defaults
94
+ are 5000 files and 1 GiB; backend limits may be lower. Signed destinations and File
95
+ objects stay transient. Each PUT uses only the returned exact checksum header,
96
+ raw bytes, `credentials:omit`, `redirect:error` and no API bearer. A failed/CORS
97
+ PUT stops completion. Resume requires reselecting and hashing an identical
98
+ manifest before registration remints URLs; a verified release with no uploads is
99
+ handled. Saved receipts contain only actor/origin, finite IDs, immutable safe
100
+ profile/address or normalized manifest, never key material or signed URLs.
101
+
102
+ `useDeploymentObservation` reads only known identities, stops on terminal,
103
+ parked/unknown cursor, failure, owner disposal or a bounded deadline, and skips
104
+ hidden or blurred documents. Refresh/Resume resets the read budget without
105
+ creating a new server operation. Native Escape/backdrop dismissal and repeated
106
+ submission are locked during HTTP writes. Observations can stop after close;
107
+ submitted server work persists and is reconciled from safe receipts.
108
+
109
+ Mock integration uses `deploymentsMock(path,opts,{user,keyBacked,authTime,group?,
110
+ interactive?})`, returning `undefined` for unrelated paths. The central bootstrap
111
+ composer consumes `readDeploymentBootstrapFacts(caller)` and merges only its
112
+ explicit WebApp fields. `deploymentMockStorageFetch` is an explicit mock-only
113
+ secondary PUT simulator; real deployments use browser fetch. Scenario controls
114
+ and `AdminDeploymentsDemo` exercise authority splits, current-A/failed-B,
115
+ reported/partial/unknown/stale evidence, same-SHA retry, rolled-back/mixed WebApps,
116
+ setup prerequisites, cursor/lease refusal, uncertain create, key replay, upload
117
+ failure and bounded observation. Fixtures establish UI contracts, not live rollout.
118
+
119
+ The supported profile editor writes only `display_name`, `environment`,
120
+ `github_repository`, `deployment_ref` and `build_output` through the existing
121
+ WebApp model save. It requires exact SAVE authority independently of
122
+ `manage_webapp`; owner, bucket, release pointer, slug and credential fields are
123
+ not editable here. The API wire field is `github_repository`, while the display
124
+ DTO calls it `repository`. Save re-reads ownership and all baseline values,
125
+ refuses observed drift, then reads back the saved profile. This is a client
126
+ stale-value check, not an atomic server revision condition. Profile changes do
127
+ not deploy content or update a workflow already installed in GitHub.
128
+
129
+ A lost profile-save response retains its submitted receipt and blocks another
130
+ save. Reload shows current values but does not prove the earlier writer finished.
131
+ Only after that read may an operator explicitly acknowledge the unknown prior
132
+ writer with the app-specific phrase. Acknowledgment sends no POST; a new change
133
+ and fresh preflight are still required. A late observed commit refuses the next
134
+ save. Known profile 440 refusal settles its unsent receipt and never replays the
135
+ POST. No profile patch or secret is retained in the receipt.
136
+
137
+ Run `node scripts/verify-admin-deployments.mjs` and
138
+ `node scripts/verify-admin-deployments-mounted.mjs`, then repository typecheck.
139
+ The first covers positive projection, authority/status/receipt/wire contracts and
140
+ actual client idempotency headers in both transports. Mounted StrictMode tests
141
+ hold responses to exercise single-flight, unsettled rollback remounts, owner
142
+ replacement, fresh auth without replay, one-time key replay, denied no-request
143
+ lanes, safe operation resume, terminal/parked/error/timeout and observer disposal.
144
+ The additional `verify:admin-deployments-setup-mounted` and
145
+ `verify:admin-webapp-inspection-mounted` commands cover state-aware setup,
146
+ later-page target preflight, ordinary CI viewers/observation, scoped refresh and
147
+ profile stale/unknown/fresh-auth journeys. The integration owner runs the
148
+ reviewed modal inventories and actual
149
+ light/dark, narrow and native-dialog browser verification.
150
+
151
+
152
+ New workspace continuation uses the exact `operation.group.id` returned by durable
153
+ create. That group identity is saved separately from the immutable create profile;
154
+ resuming never rewrites `group_intent:new` into another create body. The address
155
+ cursor reads options for that concrete group and explicitly links to the host's
156
+ registered Domains destination when configured and independently admitted. A new
157
+ workspace starts without an own/ancestor domain; configure or connect one for the
158
+ named group, refresh options, then precheck the address and submit the returned
159
+ `domain`/normalized `label` with the latest operation revision. Continue stays
160
+ unavailable with a concrete prerequisite until an actual address choice exists.
161
+ The frontend never borrows an installation-wide or unrelated workspace domain.
162
+
163
+ A WebApp deployment marked `superseded` with `terminal:true` stops observation and
164
+ settles its receipt, with `success:false`. It is not a successful rollout.
165
+
166
+ Upload receipts durably record `completion_submitted`, the completion timestamp,
167
+ and bounded prior deployment IDs before sending completion. Completion is a new
168
+ promotion request: after an earlier failed, rolled-back or superseded attempt,
169
+ repeating it can create another deployment. A lost response therefore blocks
170
+ implicit registration/completion even after remount or same-manifest reselection.
171
+ Reconcile reads the owning app summary/history and validates exact app, release,
172
+ version and newly observed deployment identity before observing terminal outcome.
173
+ Ambiguity remains unknown. Only a separately typed, freshly rechecked new deployment
174
+ consent permits resetting the local upload phase; that consent sends no POST and
175
+ requires a subsequent deliberate reselect/upload/deploy action. It neither erases
176
+ an earlier attempt nor silently replays it. The mock follows the backend's narrow
177
+ reuse rule: current release plus an active/live attempt, not permanent reuse for
178
+ every release ID.
179
+
180
+
181
+ Owner lifetimes survive React development StrictMode's setup/cleanup/setup replay.
182
+ Query closures keep a stable identity/scope handle whose live signal is renewed
183
+ before read effects resume; they do not retain an aborted first-render controller.
184
+ Actual unmount, actor/session replacement and authority or active-state changes
185
+ abort the old lifetime and discard late results. The mounted harness places
186
+ StrictMode above the providers/router so React 19 actually replays initial effects,
187
+ and verifies cold authorized bootstrap/platform reads through an asynchronous
188
+ transport gate as well as real owner cancellation.
189
+
190
+ ### KISS presentation
191
+
192
+ API and WebApps are distinct tasks; hosts admitting only one lane show no tab
193
+ strip. Creation appears only in WebApps. The API lane shows last convergence and
194
+ attempts, keeps unavailable/stale evidence visible, and puts secondary collector
195
+ facts behind an info icon. API inspection separates Overview, Nodes and Activity
196
+ and offers recovery only on the latest applicable attempt. WebApp inspection
197
+ separates Overview, Deployments, Releases, Deploy and Settings; wholly read-only
198
+ viewers omit Deploy. Useful node evidence adds tabs to an attempt dialog only
199
+ when there is more than one content area. No single-item side rail or long
200
+ collapsible section is introduced. Admin Dashboard stays unchanged. See
201
+ [visual QA](admin-presentation.md).
@@ -120,3 +120,163 @@ npm run verify:admin-dns-records
120
120
 
121
121
  Live verification is read-only. Do not request or revoke a real certificate
122
122
  without separate operator authorization.
123
+
124
+ ## Shared validation setup
125
+
126
+ Use **Domains → Advanced → Shared validation setup**, or **Set up shared validation** in a
127
+ group-managed Domain or Certificate header menu. Domain **Advanced validation** and
128
+ Certificate **Delegation** show the same read-only facts. No new route or sidebar
129
+ resource is added. The flow never reads certificate material.
130
+
131
+ 1. Select an authorized existing local Domain, or an eligible group and exact
132
+ apex name. Group selection uses `/api/dnsman/delegation/group-choice`, bounded
133
+ `search/start/size` and explicit `?id=` hydration. Product active-group context
134
+ and credential group choices do not authorize shared setup.
135
+ 2. Enter the existing public allocation target supplied by its controller and
136
+ **Inspect plan**. Inspection creates no connection or reference.
137
+ 3. **Prepare connection** and confirm the displayed domain/profile/target.
138
+ Preparation pauses direct local validation and renewal until authorization
139
+ and checking finish. Existing valid certificate material remains available.
140
+ 4. **Download request** saves `shared-validation-request.json`. Send it to the
141
+ allocation controller using your normal coordination channel. **Share request**
142
+ uses native file sharing when supported; **Copy request** is optional. Download
143
+ stays available after copy/share denial or cancellation. The canonical file is
144
+ at most 2048 UTF-8 bytes and has exactly `version:1`, `consumer_project_uuid`,
145
+ `client_ref`, `domain`, `allocation_ref`, with no wrapper, key, token or scope.
146
+ 5. The controller opens their deployment’s **Domains → Advanced → Shared validation setup →
147
+ Controller review**, checks access and uses **Import request**. There is no JSON
148
+ editor or controller URL. Empty, oversized, malformed, noncanonical or extra-field
149
+ files are refused before installation. Import alone grants nothing. The named
150
+ public fields fill the exact domain query; **Find allocations**, page if needed
151
+ and select the exact existing target. Only a matching request and resolved active
152
+ exact recipient enable **Review grant**, then **Allow issuance** after consent
153
+ names both apex and wildcard authority. No allocation is created.
154
+ 6. Back in the project, **Refresh**, **Connect approved validation**, then
155
+ **Check validation**. These are separate deliberate actions. Check requires
156
+ the approved existing one-hop `_acme-challenge.<apex>` CNAME.
157
+ 7. **Request wildcard certificate** issues this deployment's own apex-plus-
158
+ wildcard certificate. Existing valid exact-profile certificates remain in
159
+ place; queued work stays queued and due renewal reuses its lineage. Watch
160
+ the local certificate and separate renewal status, or Refresh after timeout.
161
+
162
+ Only global DNS managers (`sys.manage_dns | sys.security`) see local mutation
163
+ controls, and every action also requires the backend's current per-target flag.
164
+ Controller operations additionally require v2 `shared_validation` and
165
+ `can_manage_trust`, then exact-allocation authority. A human controller can have
166
+ `can_consume:false`; local project setup uses the server-held project identity
167
+ independently. Ordinary DNS managers cannot grant themselves trust. Groupless
168
+ house domains are read-only and ineligible in this rollout; direct operation
169
+ remains available and Portal never assigns a group automatically.
170
+
171
+ ### Local API and safe state
172
+
173
+ | Method | Local route under `/api/dnsman` | Input |
174
+ |---|---|---|
175
+ | GET | `delegation/plan` | `{domain}` or `{group,name}`, optional `allocation_ref` |
176
+ | POST | `delegation/connect` | Same target plus `allocation_ref`, optional explicit `restart:true` |
177
+ | POST | `delegation/check` | `{delegation:<local id>}` |
178
+ | POST | `delegation/request` | `{delegation:<local id>}`; no custom SANs, force or keys |
179
+ | GET | `delegation/group-choice` | `id` or bounded `search/start/size` |
180
+
181
+ All setup actions return Readiness v1. Portal strictly projects only public
182
+ metadata and validates version, selected identity, source/target, fixed profile,
183
+ booleans and enums. Expected 409s can include the same safe readiness DTO.
184
+ Unknown containers and provider error text are discarded before state. Plans,
185
+ descriptors and controller identities are modal-local; no draft is persisted or
186
+ written to URLs, diagnostics or logs. Writes are imperative and non-retrying,
187
+ freeze the scope, recheck auth/session/permission before send, and reject late
188
+ responses after scope loss. After every attempted write, safe Domain,
189
+ Certificate and legacy delegation caches are invalidated and plan/trust is
190
+ refreshed. Ambiguous results require successful Refresh before another write.
191
+ A 440 prompts authentication but never automatically repeats the operation.
192
+ Every explicit session replacement, including the same user signing in again,
193
+ clears controller capabilities, recipients and participant facts. Check controller
194
+ access and select an authorized allocation again. A new grant also requires a matching imported request and freshly resolved active recipient.
195
+ Closing a view cannot cancel a write already submitted.
196
+
197
+ Controller POSTs use only the local `/api/dnsman/acme` endpoints:
198
+ `capabilities {}`, `allocation/list {domain,cursor?}`,
199
+ `trust/list {allocation_ref,cursor?}`,
200
+ `trust/recipient {allocation_ref,consumer_project_uuid}`,
201
+ `trust/grant {connection_request}` and
202
+ `trust/revoke {allocation_ref,participant_ref}`. Each page contains at most 20
203
+ rows. Only a capability-approved controller probes the allocation/trust routes;
204
+ there is no arbitrary project directory, remote hub call, key provisioning or
205
+ consumer/status call from a human JWT. Recipient and allocation authorization
206
+ are repeated server-side on every operation.
207
+
208
+ ### Operator setup and mixed versions
209
+
210
+ If the local plan says the hub is unconfigured or misconfigured, a deployment
211
+ operator uses the existing server configuration delivery to set
212
+ `DNSMAN_ACME_HUB_URL` to the already-used Maestro hub and
213
+ `DNSMAN_ACME_HUB_API_KEY` to the already-provisioned protected project key in
214
+ Django settings, normally `VAR_ROOT/django.conf`. Upgrade the hub first, then
215
+ project API and every certificate worker; drain/restart old issuance workers
216
+ before shared connections are accepted. Restart application and worker
217
+ processes and **Refresh Portal**. A responsive API does not prove that workers
218
+ were restarted; worker version is unknown unless authoritative telemetry exists.
219
+ These settings are static deployment configuration, never DB Setting overrides,
220
+ browser fields, arbitrary per-site URLs or values copied into descriptors.
221
+
222
+ 404/405 and unsupported new versions disable only shared setup; existing strict
223
+ DNS config, direct-provider and legacy single-consumer certificate paths retain
224
+ their contracts. 401/403 report access refusal instead of suggesting an upgrade.
225
+ Disabled sharing, unconfigured/auth-rejected/unavailable hubs and unknown proof
226
+ never display setup as complete. Portal does not bypass backend activation.
227
+
228
+ ### Repair and revocation
229
+
230
+ ACME validation and traffic routing are separate. The validation CNAME does not
231
+ route apex/www/API/app traffic. Follow the deployment's separate A/AAAA/CNAME
232
+ traffic instructions. Missing, changed or chained validation proof shows the
233
+ expected and bounded observed target and its check time. Ask the domain owner
234
+ to restore the approved one-hop route after checking other consumers. Portal
235
+ does not replace/remove that CNAME or rotate the allocation as a default repair.
236
+
237
+ Controller maintenance starts cold with **Check controller access**, an **Exact
238
+ domain** and **Find allocations**. Native choices show target and owner UUID.
239
+ **Next allocation page** / **First allocation page** and **Next project page** /
240
+ **First project page** navigate bounded 20-row pages. Select an allocation to
241
+ inspect participants, role, state, exact scope and revoked time. No request file
242
+ or recipient lookup is needed for this path. An imported inactive consumer can
243
+ fail recipient lookup while participant inspection, revoke and cleanup remain
244
+ available. Owner participants cannot be revoked. **Refresh trust** checks current
245
+ capabilities; an unsuccessful reconciliation blocks mutation until refresh succeeds.
246
+
247
+ Controller revocation stops future DNS-01 validation for that project. Already-
248
+ issued certificates remain valid until expiry or separate certificate revocation,
249
+ and other trusted projects keep working. A 503 may occur after revocation was
250
+ persisted; refreshed trust can say revoked while DNS cleanup remains unconfirmed.
251
+ Retrying cleanup never regrants the reference. A revoked reference cannot be
252
+ revived: the project must explicitly confirm **Prepare fresh request**, keep
253
+ the existing target, and obtain a new exact controller grant.
254
+
255
+ A valid local certificate with blocked/failed/retrying renewal is shown as
256
+ “valid until …; renewal needs attention.” It is not labeled expired or revoked
257
+ as a repair. Material custody is API-node evidence, not proof that any serving
258
+ host installed that certificate. Shared pending/broken/revoked routing never
259
+ permits fallback TXT writes through the direct provider. Private keys stay in
260
+ each deployment.
261
+
262
+ The mock showcase includes deterministic controller-maintenance pages and an inactive consumer, plus the complete project/controller handshake, viewer,
263
+ revoked, pending/changed/chained DNS, disconnected/unconfigured/disabled hub,
264
+ older API, failed renewal and 390px embedded-content scenarios. Scenario changes
265
+ reset mock shared state; identity changes preserve it. Leaving the demo restores
266
+ its default mock identity and scenario. `verify:admin-dns-shared-validation`
267
+ runs API/projection and React StrictMode mounted contracts. Real browser proof
268
+ remains required for focus, native-dialog stacking, clipboard and both themes.
269
+
270
+ Owner and consumer project identities preserve existing lowercase16hex Group
271
+ IDs as well as the previously supported32hex and canonical UUID forms. They
272
+ are compared exactly and never padded, regenerated or rewritten. Client and
273
+ participant references retain their strict UUID validation; a short project ID
274
+ cannot stand in for either reference.
275
+
276
+
277
+ Shared setup prerequisites now link literal deployment operators to the actual
278
+ mount-aware Fleet shared-validation connection topic. It owns named approved
279
+ profiles, write-only protected keys, expected/observed identity and revision-bound
280
+ Preview/Publish/Apply. Ordinary DNS managers receive a concrete domain, next
281
+ action and checked-time handoff with no private inputs or private API background
282
+ calls. This route does not perform the protected hub activation ceremony.
@@ -0,0 +1,92 @@
1
+ # Domains
2
+
3
+ `DomainsPage` is the everyday global DNS inventory at `/dns/domains`, under the
4
+ host Admin mount. Import it from `portal-mojo/admin/infrastructure`. It keeps
5
+ `DomainModel`, the `admin:dns:domains` table persistence key and server search,
6
+ provider/status filters, sorting and paging. Scope comes from each row's group
7
+ or Platform; the active product group is never used.
8
+
9
+ The header exposes **Register existing**, **Adopt**, and **Buy domain**. Register
10
+ and Buy need global `sys.manage_dns | sys.security`. Adopt also needs literal
11
+ `me.is_superuser === true`; the backend independently refuses noninteractive
12
+ and key-backed authority. Viewer sessions can inspect domains, DNS records and
13
+ certificates without mutation controls.
14
+
15
+ ## Explicit onboarding
16
+
17
+ `showDomainOnboarding()` preserves its original menu. An optional
18
+ `{ initialMode: 'menu' | 'register' | 'adopt' | 'purchase' }` opens a workflow
19
+ directly. The page imports the dialog only after a button click.
20
+
21
+ Register existing selects one authorized group from bounded
22
+ `/api/dnsman/credential/group-choice`, then an active verified GoDaddy
23
+ credential queried through `DnsCredentialModel` with exact group/provider and
24
+ verification filters. No credential selector mounts before an explicit group.
25
+ The selector remounts by group, clears its selection on group change and rejects
26
+ resolved credentials from another group or unsupported/unusable credentials.
27
+ There is no `/api/group` read, free-form group id, provider-secret input or
28
+ provider-account enumeration. The exact normalized name is probed for ownership
29
+ without spending money. The only body is `{ group, domain, credential }`.
30
+
31
+ Adopt takes one normalized existing public zone name, then explicitly reviews
32
+ that name and Platform scope before posting `{ domain }`. It never supplies
33
+ `group` or `create_zone`, discovers inventory or assigns a tenant. Review and
34
+ mutation guard native dialog dismissal; Cancel review performs no mutation.
35
+ Registration/adoption use an immediate single-flight ref. An unknown outcome
36
+ invalidates inventory and requires operator review before starting another flow.
37
+
38
+ Buy preserves the bounded typed-TLD, prerequisite, tri-state search and durable
39
+ ledger flow documented in [the registrar reference](admin-dns-registrar.md).
40
+ The purchase body is exactly `{ group, purchase, confirm_token, confirm_domain,
41
+ confirm_price }`. Name and decimal price come from the immutable validated quote,
42
+ not editable confirmation inputs. The quote token is removed from its mounted
43
+ ref before the operation and kept transient through `withFreshAuth`; only a 440
44
+ may resume the identical body once. Cancelled step-up and other failures never
45
+ replay. An attempted purchase is terminal in that mounted wizard and reconciles
46
+ against the durable ledger, including ambiguous provider outcomes.
47
+
48
+ ## Inspection and registrar settings
49
+
50
+ A row opens the standard `modal.detail` overview. Server-owned name, provider,
51
+ status, scope, registration/expiry dates and redacted failure remain read-only.
52
+ DNS Records is a direct shortcut for Route 53 and GoDaddy; Mojo explains its
53
+ certificate-only limitation. Shortcuts derive their mount from the observed DNS
54
+ route, so root, `/admin` and embedded prefixes work. Serving links explicitly
55
+ navigate to the mount's sibling `/edge/vhosts`; they are outside the registry
56
+ whose routes are relative to `/dns`.
57
+
58
+ Only Route 53 managers receive **Registrar settings**. Its focused guarded native
59
+ modal sends only changed boolean `auto_renew`/`privacy` fields to the existing
60
+ generic domain save endpoint. It never edits group, credentials, raw metadata,
61
+ protected fields or registrar contacts. Every attempted save authoritatively
62
+ refetches and invalidates DomainModel, including rejection. HTTP success alone
63
+ is not registrar success: a nonempty `last_error` stays visible with a historical
64
+ warning and instructions to inspect the registrar. Uncertain writes keep the
65
+ modal open, disable additional writes and require review. There is no automatic
66
+ retry. GoDaddy registrar/privacy/WHOIS management remains in GoDaddy; Mojo is
67
+ certificate-only. Unknown providers remain labeled and read-only.
68
+
69
+ ## Advanced validation
70
+
71
+ **Domains → Advanced → Shared validation setup** opens without an existing
72
+ domain, preserving external-domain consumer setup and controller review.
73
+ Domain detail retains Advanced validation, DNS Records and Certificates sections,
74
+ existing registered related links and the permission/readiness-gated setup action.
75
+ Descriptor import/export, TLS paths and shared-validation protocol are unchanged.
76
+
77
+ ## Verification and showcase
78
+
79
+ `verify-admin-domains.mjs` exercises the independent strict purchase peer,
80
+ fresh-auth continuation/cancellation and ambiguous no-replay. Its central mock
81
+ contract leg requires missing/mismatched name and decimal-price tuples to leave
82
+ the quote unspent. `verify-admin-domains-mounted.mjs` mounts real wizard, picker,
83
+ page and editor components to prove scope isolation, action authority, deliberate
84
+ adoption, registrar reconciliation and mount-aware navigation. The closing browser
85
+ pass supplies native focus, responsive layout and light/dark evidence.
86
+
87
+ The focused **Admin → Domains** showcase uses mock manager, platform and viewer
88
+ identities and preserves every earlier DNS showcase.
89
+
90
+ ### KISS presentation
91
+
92
+ Domain/provider share one table cell. The page header owns registration/adoption/purchase; technical registrar facts are in Registration details. Provider warnings remain visible. See [visual QA](admin-presentation.md).
package/docs/admin-dns.md CHANGED
@@ -37,6 +37,8 @@ domain includes registration/hosted-zone presence, safe zone metadata,
37
37
  tracking/adoption state, and a reason. `untracked=true` returns only untracked
38
38
  rows; discovery and assignment remain interactive-superuser operations.
39
39
 
40
+ The everyday inventory and three onboarding actions are documented in [admin-dns-domains.md](admin-dns-domains.md).
41
+
40
42
  The operator registrar workflow is documented in [admin-dns-registrar.md](admin-dns-registrar.md). It does not use discovery: House adoption is manual exact-name only. Purchase tokens remain transient, all post-confirmation outcomes reconcile through the read-only ledger, and contact PII remains imperative/local.
41
43
 
42
44
  These responses require local call-flow handling and must never enter Query or
@@ -84,3 +86,19 @@ See [Admin modal policy](admin-modals.md) for header actions, content-only
84
86
  sections, supported lifecycle fields and retained irreversible exceptions.
85
87
  Provider credentials retire/reactivate through `is_active`, keeping history;
86
88
  rotation remains write-only and imperative. Built-in credential Delete is absent.
89
+
90
+ ## Shared wildcard validation
91
+
92
+ Domains offers **Advanced → Shared validation setup** to global DNS
93
+ managers even when server setup needs an operator. Domain detail includes
94
+ read-only **Advanced validation**; its context action and Certificate Delegation compose
95
+ the same focused flow. See [the human walkthrough and exact
96
+ contracts](admin-dns-certificates.md#shared-validation-setup).
97
+
98
+ Shared setup uses independent Readiness v1/hub v2 adapters. It never changes the
99
+ strict legacy `/config` response contract. A controller grant is explicit
100
+ apex/wildcard certificate issuance authority; local DNS management alone cannot
101
+ supply it. Only public five-field connection requests are copied. No private
102
+ key, PEM, DNS credential or protected hub key is requested, read, persisted or
103
+ logged. Deployment operators deliver static hub settings server-side and restart
104
+ API/workers. Validation CNAME routing remains separate from traffic routing.