portal-mojo 0.2.18 → 0.2.20

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 (56) hide show
  1. package/README.md +12 -0
  2. package/docs/README.md +1 -1
  3. package/docs/admin-assistant.md +152 -1
  4. package/docs/admin-credentials.md +38 -0
  5. package/docs/admin-dns-certificates.md +160 -0
  6. package/docs/admin-dns.md +16 -0
  7. package/docs/admin-fleet-configuration.md +98 -2
  8. package/docs/admin-identity-directories.md +12 -0
  9. package/docs/admin-identity-users.md +10 -0
  10. package/docs/admin-modals.md +28 -0
  11. package/docs/auth-pages.md +18 -6
  12. package/docs/auth.md +16 -2
  13. package/docs/collection-select.md +14 -0
  14. package/docs/grouping-and-fmt.md +1 -1
  15. package/package.json +1 -1
  16. package/src/account/PasskeyList.tsx +1 -1
  17. package/src/account/credential-models.ts +2 -1
  18. package/src/admin/assistant/AssistantSetupPage.tsx +196 -0
  19. package/src/admin/assistant/index.ts +2 -0
  20. package/src/admin/assistant/setup.ts +174 -0
  21. package/src/admin/core/index.ts +14 -4
  22. package/src/admin/credentials/AcmeConsumerKeyDialog.tsx +88 -0
  23. package/src/admin/credentials/acme-consumer-key.ts +34 -0
  24. package/src/admin/credentials/group-api-keys.tsx +5 -3
  25. package/src/admin/dns/CertificateDetail.tsx +8 -3
  26. package/src/admin/dns/CertificateRequestDialog.tsx +45 -7
  27. package/src/admin/dns/DomainDetail.tsx +7 -1
  28. package/src/admin/dns/DomainsPage.tsx +6 -2
  29. package/src/admin/dns/SharedValidationDialog.tsx +298 -0
  30. package/src/admin/dns/SharedValidationPanel.tsx +76 -0
  31. package/src/admin/dns/shared-validation-api.ts +90 -0
  32. package/src/admin/dns/shared-validation-data.ts +106 -0
  33. package/src/admin/domains/assistant.ts +1 -0
  34. package/src/admin/domains/operations.ts +6 -4
  35. package/src/admin/fleet-configuration/FleetConfigurationPage.tsx +14 -8
  36. package/src/admin/fleet-configuration/SharedValidationConnectionPanel.tsx +166 -0
  37. package/src/admin/fleet-configuration/data.ts +4 -0
  38. package/src/admin/fleet-configuration/operator-destinations.ts +20 -0
  39. package/src/admin/fleet-configuration/shared-validation-data.ts +143 -0
  40. package/src/admin/identity/groups/group-sections/SubGroupsSection.tsx +73 -2
  41. package/src/admin/identity/groups/models.ts +2 -0
  42. package/src/admin/identity/users/UserDetail.tsx +1 -0
  43. package/src/admin/identity/users/sections/SecuritySection.tsx +4 -0
  44. package/src/admin/identity/users/sections/actions.tsx +75 -2
  45. package/src/admin/identity/users/sections/shared.tsx +18 -7
  46. package/src/client/auth.ts +77 -25
  47. package/src/client/fleet-mock.ts +73 -1
  48. package/src/client/index.ts +4 -2
  49. package/src/client/mock.ts +427 -14
  50. package/src/styles/components/admin-assistant.css +22 -0
  51. package/src/styles/components/admin-dns-certificates.css +33 -0
  52. package/src/ui/CollectionSelect.tsx +30 -8
  53. package/src/ui/date/fns.ts +32 -0
  54. package/src/ui/format.ts +26 -1
  55. package/src/ui/grouping.ts +31 -16
  56. package/src/ui/menu-registry.ts +3 -0
package/README.md CHANGED
@@ -145,3 +145,15 @@ The non-negotiables live in the repo root `PLAN.md` ("Architecture rules") and
145
145
  boundary; failure is unmissable; controlled inputs only; models are
146
146
  definitions + hooks (TanStack Query owns cache); `ReactNode` slots, never
147
147
  trusted-HTML strings; both themes from day one.
148
+
149
+ ### Owner-only AI Assistant setup
150
+
151
+ The Assistant domain includes a dedicated `/assistant/setup` page (also embedded
152
+ under the Admin mount prefix), protected by the additive literal-superuser
153
+ `AdminRoute.requireSuperuser` / `MenuItem.requireSuperuser` gate. It provides
154
+ explicit key check/save and enablement, with separate advanced Platform key,
155
+ deployed policy/safety, remote access and incident triage sections. Setup secrets
156
+ and actions stay page-local and cache-free. Fresh-auth requires key re-entry,
157
+ and deployment owns the runtime route. See [Admin Assistant](docs/admin-assistant.md)
158
+ for exact schema-2 actions, lifecycle guarantees, mock showcase and verification;
159
+ [Admin credentials](docs/admin-credentials.md) documents the shared core gate.
package/docs/README.md CHANGED
@@ -41,7 +41,7 @@ everything: run the showcase (`npm run dev:showcase`) → **Develop → Componen
41
41
  | [admin-messaging.md](admin-messaging.md) | SES domains/mailboxes/sent/templates plus public contact requests, with write-only credentials and offline HTML previews |
42
42
  | [admin-phonehub.md](admin-phonehub.md) | Global Phone Hub: evidence-backed lookup, sanitized SMS audits, and write-only provider configuration |
43
43
  | [admin-push.md](admin-push.md) | Global Push admin: caller stats, global metrics, strict audits, templates, and cache-free FCM configuration |
44
- | [admin-assistant.md](admin-assistant.md) | Global Assistant streaming selection/reconciliation, strict projectors/blocks, context reuse, skills, and explicit tiered memory |
44
+ | [admin-assistant.md](admin-assistant.md) | Owner-only AI Assistant Setup, credential/safety/remote controls and global Assistant streaming selection/reconciliation, strict projectors/blocks, context reuse, skills, and explicit tiered memory |
45
45
  | [ModelTable.md](ModelTable.md) | The server-driven table: columns, filters, selection/batch, chooser, persist, autoRefresh, expand, groupBy, export, skeleton |
46
46
  | [forms.md](forms.md) | The `Field` language, `SchemaForm`, `formModal`, `FormView` inline autosave + showWhen + tabsets |
47
47
  | [tabs-and-form-wizard.md](tabs-and-form-wizard.md) | Accessible Tabs variants and shared-state FormWizard, async finish, reset/roster semantics, modal helper |
@@ -2,7 +2,158 @@
2
2
 
3
3
  Import from `portal-mojo/admin`.
4
4
 
5
- `ASSISTANT_ADMIN_SECTION` registers Conversations, Skills, and Memory beneath the Assistant navigation group. Its section clause is global-only ANY-of `sys.view_admin` or `sys.assistant`; Memory narrows its route to `sys.assistant`, matching the backend. Active-group grants never reveal it. `AssistantLauncher` uses the section gate. `AssistantContextLauncher` additionally requires `sys.view_security` or `sys.security` and only accepts `incident.Incident` or `incident.Ticket`.
5
+ `ASSISTANT_ADMIN_SECTION` registers Setup, Conversations, Skills, and Memory beneath the Assistant navigation group. Its section clause is global-only ANY-of `sys.view_admin` or `sys.assistant`; Memory narrows its route to `sys.assistant`, matching the backend. Active-group grants never reveal it. `AssistantLauncher` uses the section gate. `AssistantContextLauncher` additionally requires `sys.view_security` or `sys.security` and only accepts `incident.Incident` or `incident.Ticket`.
6
+
7
+ ## AI Assistant setup
8
+
9
+ `AssistantSetupPage` is a dedicated global owner page at `/assistant/setup`, or
10
+ `/system/assistant/setup` when the Admin helpers receive `mount: '/system'`.
11
+ The literal `me.is_superuser === true` requirement applies before lazy loading,
12
+ in grouped and ordinary menu entries, landing selection, direct component
13
+ mounting and every action. Permission-driven `admin`, `manage_settings`,
14
+ Assistant permissions and member/group grants cannot satisfy it. The server
15
+ also rejects confined ApiKey/GroupScopedToken sessions and enforces person
16
+ ownership; the client does not invent a broader per-user-key ban.
17
+
18
+ The basic section shows deployment readiness, exact provider/model/key
19
+ selection, Automatic — latest Sonnet or an explicit manual pin, credential source and optional server hint, blank replacement input,
20
+ Check and save key, Check current key and enable/disable. A configured key and
21
+ an enabled switch do not prove readiness. `route.ready` describes configuration,
22
+ not current provider health. Unknown routes remain unknown: a missing
23
+ credential can cause the backend to omit the entire route. Only an explicit
24
+ `handler` route points to the Platform credential section. Bedrock uses AWS
25
+ identity from deployment. Assistant, Platform and AWS credentials never
26
+ substitute for one another. Missing Assistant/realtime applications are also
27
+ reported. Runtime model identifiers preserve provider punctuation, including
28
+ Bedrock colons; only a manual pin uses the backend's restricted pin syntax.
29
+
30
+ Advanced native details separate Platform credentials, deployed policy/safety,
31
+ remote access/connections and incident triage. Policy activation acknowledges
32
+ the deployed policy on this node, does not edit it and never establishes
33
+ fleet-wide agreement. Static, database and effective emergency stops are
34
+ separate; only the database switch is writable. Activation and breaker reset
35
+ cannot release a stop.
36
+
37
+ Model selection controls the protected runtime selection only when the backend
38
+ returns a valid capability for the explicit Anthropic `auto:sonnet` policy.
39
+ Fixed Anthropic policies, Bedrock and unavailable policies remain read-only.
40
+ A deployment pin permits an owner manual override, but Automatic remains
41
+ unavailable until deployment removes that pin. Mode is authoritative even when
42
+ a fixed route retains an inert legacy `selected` value.
43
+
44
+ The exact resolved runtime identifier (`model.effective`) and source are
45
+ separate from the selection draft and `route.ready`. Advanced → Deployed policy
46
+ and safety contains the raw policy model and admission diagnostic. The additive
47
+ `route.can_invoke` reports admission to the guarded request path: a cold
48
+ automatic catalogue can admit a request while configuration readiness remains
49
+ false. It does not prove that a usable model or healthy provider is ready.
50
+
51
+ Refresh catalogue is an explicit backend request; only the backend contacts the
52
+ provider. The browser never decides which model is newest. A refresh announces
53
+ success only for `model.refresh.ok === true`, including when a manual pin
54
+ remains selected. Failed/throttled/in-flight attempts may retain a fresh or
55
+ stale cached resolution; the page reports its exact freshness, last successful
56
+ fetch and attempt without claiming a newer model. Passive reads neither refresh
57
+ the catalogue nor repeat the preceding explicit refresh outcome.
58
+
59
+ Check public discovery explicitly probes this installation's public address
60
+ (server cache: 60 seconds). Ordinary page reads trigger neither probe.
61
+
62
+ Both credential drafts and all setup operations stay imperative and page-local.
63
+ There is no autosave, model definition, Query/Mutation entry, browser
64
+ persistence, export, URL value or secret-bearing mock history. The backend
65
+ checks a replacement before encrypted storage; a candidate-only verification
66
+ never updates the stored-key check record. Successful credential saves and
67
+ clears empty the relevant draft. Clearing protected storage may reveal a
68
+ deployment fallback, so the returned source remains authoritative. Fixed
69
+ backend pre-write rejection/unreachable messages map to safe actionable copy;
70
+ unknown failures refresh authoritative state and report an uncertain outcome
71
+ without replaying the operation.
72
+
73
+ One operation/confirmation lock, AbortSignal and live owner/session guard cover
74
+ loads, confirmations, auth waits and late responses. Leaving the page,
75
+ revocation, logout or explicit session replacement discards drafts and pending
76
+ work. Ordinary same-tab token renewal preserves a draft. Cross-tab auth-storage
77
+ changes conservatively discard it: the shared auth API cannot distinguish
78
+ renewal from same-person replacement across tabs. A 440 uses the existing
79
+ fresh-auth UI after clearing credential drafts; even successful reauthentication
80
+ requires explicit re-entry/retry. No secret is silently replayed. Confirmation
81
+ is page-local, focuses Cancel, and ends with the page/session scope.
82
+
83
+ Protocol helpers are exported from `portal-mojo/admin/assistant`:
84
+ `getAssistantSetup(options, probe?)`, `mutateAssistantSetup(action, options)`,
85
+ `assistantSetupSave(state, changes)`, `projectAssistantSetup`,
86
+ `assistantSetupReadiness`, and `assistantSetupCredentialRefusal`. Options accept
87
+ `signal` and a `beforeSend` validity guard; mutation helpers enforce same-origin
88
+ before and after auth preflight and use the existing `mojoCall` envelope boundary.
89
+ `schema_version: 2` responses are positively projected with bounds of 200
90
+ model choices/connections/request aggregates and 100 breaker rows. Runtime IDs
91
+ preserve provider punctuation; only manual pins use the backend's restricted
92
+ 81-character lowercase alphanumeric/dot/hyphen syntax.
93
+
94
+ The additive wire fields live directly under `model`: `mode` (`auto`, `pinned`,
95
+ `fixed`), `editable`, `policy_model`, `restriction` (empty, `fixed_policy`,
96
+ `provider_unsupported`, `deployment_pin`, `policy_unavailable`), `discovery`
97
+ (`status`: `fresh`, `stale`, `unavailable`, `not_applicable`; nullable ISO
98
+ `fetched_at`, `attempted_at`, `expires_at`, `max_stale_at`; safe `error_code`),
99
+ and `refresh` (`attempted`, nullable boolean `ok`, safe `code`). The client
100
+ positively projects the complete valid set into optional `model.capability`.
101
+ Absent, malformed or unknown capabilities keep runtime facts visible, with
102
+ upgrade/read-only copy and no enabled selection action. No latest ID ships in
103
+ the production UI; returned bounded choices supply the datalist.
104
+
105
+ | Request | Exact body / query |
106
+ |---|---|
107
+ | Passive GET `/api/account/admin/assistant` | No probe parameters |
108
+ | Explicit catalogue / discovery GET | `refresh=models` / `check=discovery` |
109
+ | Save POST | `{action:'save',enabled:boolean,model?:string}` plus only intended optional `api_key`, `clear_api_key`, `handler_api_key`, `clear_handler_api_key`, `mcp_enabled`, `emergency_stop`, `autonomous_triage` |
110
+ | Model selection POST | Exactly `{action:'set_model_selection',mode:'auto'}` (no `model`) or `{action:'set_model_selection',mode:'pinned',model:ID}`; `saved:boolean` |
111
+ | Verify POST | `{action:'verify',target:'assistant'|'handler',api_key?}`; omitted candidate checks that exact stored credential |
112
+ | Policy POST | Exactly `{action:'activate_policy'}`; `activated:boolean` |
113
+ | Breaker POST | `{action:'reset_breaker',provider?:'anthropic'|'bedrock'}`; `reset:number` |
114
+ | Historical triage POST | Exactly `{action:'historical_triage',before:ISO,limit:1..100}`; `queued:number` |
115
+ | Disconnect POST | Exactly `{action:'revoke_grant',grant_id:positiveInteger}` or `{action:'revoke_all_grants'}`; `revoked:number` |
116
+
117
+ Every mutation returns fresh `state` plus the action result. Verification may
118
+ return `result.ok:false` in a successful envelope; that remains a failed check.
119
+ Save always sends authoritative `enabled`. With a valid capability it omits
120
+ `model` and cannot reset runtime selection; only the dedicated selection action
121
+ can change it. Capability-absent older schema-2 servers retain the required
122
+ `enabled`/legacy `model` baseline because they write both even when omitted.
123
+ On delegated `auto:sonnet` routes, new servers reject a differing legacy model
124
+ echo, preventing stale clients from reverting the active choice. Fixed policies,
125
+ Bedrock and unavailable-policy routes retain an inert legacy writer; these
126
+ values cannot change their deployed runtime model. Model actions carry no
127
+ enabled/key/stop/MCP/triage fields, and all selection drafts reset from each
128
+ authoritative response. Optional section switches are not
129
+ echoed during unrelated saves, and effective stop is never used as the database
130
+ stop. Replacement and clear are separate operations. The server retains its
131
+ 600-second fresh-auth, protected settings, auditing and same-origin checks;
132
+ generic Settings still cannot write the Assistant-owned protected keys.
133
+
134
+ Remote access off pauses existing grants without revoking them. The connection
135
+ count remains the full server count when rows are capped at 200. Tools, Full API
136
+ and combined grants are labeled separately; Full API carries the signed-in
137
+ user's API authority. Disconnect all spans both resource paths for every user.
138
+ Returned addresses are inert text/read-only select-to-copy inputs. Triage
139
+ activation establishes the no-history watermark; historical processing requires
140
+ an explicit cutoff, bounded limit and confirmation.
141
+
142
+ The mock-only Assistant showcase adds Setup with explicit fake owner/denied
143
+ permission-admin buttons, a one-shot fresh-auth challenge button, and ready, missing policy/key, handler, Bedrock, static
144
+ stop, rejected key, discovery failure, bounded-grants and deployment-fallback
145
+ scenarios. Model scenarios include manual pins, fixed policy, deployment pin
146
+ (including an owner override), fresh/stale refresh failure, unavailable
147
+ resolution, throttled/in-flight refresh, old-server capability absence and
148
+ malformed capability. `fixture-sonnet-2099-01` and `fixture-sonnet-2099-02` are
149
+ visibly fake future IDs; explicit catalogue refresh advances the fake catalogue,
150
+ preserves manual pins, and returning to Automatic adopts the successor. Seeded `assistant.owner@nativemojo.com` is a literal superuser;
151
+ `assistant.wildcard@nativemojo.com` has permission-driven admin only. Both use
152
+ fake password `mojo`. No real credentials or deployment changes belong in demos.
153
+ `verify:admin-assistant-setup` covers wire contracts, owner navigation/admission,
154
+ projections and mounted auth/draft/confirmation/fresh-auth/transport/cache
155
+ lifecycle; it is wired into `verify:release`. Browser checks separately establish
156
+ both-theme layout, native details, keyboard controls and console behavior.
6
157
 
7
158
  ## Conversation transport
8
159
 
@@ -26,6 +26,17 @@ use `basePath: ''`; its labeled routes become visible child links rather than
26
26
  claiming `/`. Prefixed sections receive an index redirect to the first route
27
27
  the current operator can see.
28
28
 
29
+ ## Literal-owner routes
30
+
31
+ `AdminRoute.requireSuperuser?: boolean` is an additive opt-in gate. It requires
32
+ literal `me.is_superuser === true` before a lazy page can load and during
33
+ section/mount landing selection. `adminSectionsMenu` carries it into grouped
34
+ and ordinary `MenuItem.requireSuperuser` entries. Permission-driven admin and
35
+ group grants never satisfy it; existing permission semantics remain unchanged.
36
+ Built-in Assistant Setup uses this gate and additionally checks live ownership
37
+ and session lifetime within the component/actions. Toolkit consumers can opt
38
+ into the same route/menu guard without changing other routes.
39
+
29
40
  ## Group API keys
30
41
 
31
42
  ```tsx
@@ -195,3 +206,30 @@ See [Admin modal policy](admin-modals.md) for header actions, content-only
195
206
  sections, supported lifecycle fields and retained irreversible exceptions.
196
207
  Group API keys and webhook subscriptions deactivate/reactivate; their built-in
197
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.
@@ -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 → Shared validation setup**, or **Set up shared validation** in a
127
+ group-managed Domain or Certificate header menu. Domain **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 → 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.
package/docs/admin-dns.md CHANGED
@@ -84,3 +84,19 @@ See [Admin modal policy](admin-modals.md) for header actions, content-only
84
84
  sections, supported lifecycle fields and retained irreversible exceptions.
85
85
  Provider credentials retire/reactivate through `is_active`, keeping history;
86
86
  rotation remains write-only and imperative. Built-in credential Delete is absent.
87
+
88
+ ## Shared wildcard validation
89
+
90
+ The existing Domains header offers **Shared validation setup** to global DNS
91
+ managers even when server setup needs an operator. Domain detail includes
92
+ read-only **Validation**; its header action and Certificate Delegation compose
93
+ the same focused flow. See [the human walkthrough and exact
94
+ contracts](admin-dns-certificates.md#shared-validation-setup).
95
+
96
+ Shared setup uses independent Readiness v1/hub v2 adapters. It never changes the
97
+ strict legacy `/config` response contract. A controller grant is explicit
98
+ apex/wildcard certificate issuance authority; local DNS management alone cannot
99
+ supply it. Only public five-field connection requests are copied. No private
100
+ key, PEM, DNS credential or protected hub key is requested, read, persisted or
101
+ logged. Deployment operators deliver static hub settings server-side and restart
102
+ API/workers. Validation CNAME routing remains separate from traffic routing.
@@ -7,8 +7,9 @@ page checks `is_superuser === true` before fetching. Backend authority remains
7
7
  mandatory, including rejection of diagnostic/key-backed sessions and fresh auth.
8
8
 
9
9
  Applications register their schema in django-mojo. The page renders the returned
10
- section, label, description, default, type and restart requirement; no application
11
- setting names are hardcoded. Each field defaults to Keep current value. Set value
10
+ section, label, description, default, type and restart requirement; application schemas remain server-owned. The three managed shared-validation
11
+ connection fields route to the dedicated topic below, rather than appearing as
12
+ independently editable generic fields. Each field defaults to Keep current value. Set value
12
13
  and Remove published override are explicit actions. Clearing falls back to base
13
14
  configuration/default; it does not promise erasure of a bootstrap secret.
14
15
 
@@ -48,3 +49,98 @@ example. Mock Portal uses representative registered settings and an offline node
48
49
  Django receives this page only after a canonical Portal artifact is built and
49
50
  vendored; follow `docs/admin-artifact.md` and the Django vendor tool. Ordinary
50
51
  Vite build output is not the canonical artifact.
52
+
53
+ ## Shared validation connection
54
+
55
+ Operations → Fleet Configuration → Shared validation connection is a private, lazy
56
+ route. It requires a current literal superuser; the server additionally requires
57
+ an active interactive session, same-origin writes and fresh authentication.
58
+ Key-backed sessions are rejected by the server even when their user is an owner.
59
+
60
+ On the validation hub, update every expected node using the existing Deployments
61
+ controls, then use **Enable shared validation** once. The compact hub section is
62
+ independent of consumer connection configuration and uses the same Portal login,
63
+ fresh authentication and native confirmation as the existing page. No separate
64
+ operator service, login or wizard is required. Missing or incompatible deployment
65
+ proof shows the ordinary prerequisite instead of an enabled action. Each issuer
66
+ keeps its own ACME account, certificates and private keys; allocation trust still
67
+ requires explicit approval in the DNS setup screen. Renewal remains automatic.
68
+
69
+ `GET /api/account/admin/fleet/shared-validation/hub` returns
70
+ `{version:1, enabled, configured, deployment_uuid, can_enable, reason}`.
71
+ The deployment UUID is canonical lowercase with hyphens, unlike consumer project
72
+ UUIDs. `POST` takes only `{deployment_uuid}` after confirmation. The backend
73
+ rechecks the complete deployed roster and framework version atomically before
74
+ enabling. A stale deployment, failed read or fresh-auth refusal requires Refresh
75
+ and another deliberate review; mutations are never replayed. Keep the compatible
76
+ framework on every node after enabling sharing.
77
+
78
+ The panel consumes the private `/api/account/admin/fleet/shared-validation`
79
+ contract. Choose a named approved hub profile, inspect its exact origin and
80
+ original project UUID, then Keep, Replace or Disable the connection. Replacement
81
+ is a normal write-only password field; blank preserves the existing credential.
82
+ The approved catalog fixes the origin. Arbitrary URLs and raw JSON are not inputs.
83
+
84
+ Project identity fields also accept existing lowercase16hex Group IDs, preserving
85
+ the exact value through profiles, observations and protected key issuance. Fleet
86
+ revisions/operation IDs remain32hex and deployment IDs remain canonical UUIDs.
87
+ The legacy identity showcase fixture changes wire data only; no Group identity
88
+ is migrated or rewritten.
89
+
90
+ Preview probes the candidate without saving it. Publication has a separate
91
+ confirmation, frozen against the preview and auth session. Publishing revalidates
92
+ the catalog and expected revision; it does not Apply. Apply has another
93
+ confirmation and targets the exact current revision. Every expected node must
94
+ report installed, restarted and combined request-service/job-engine/scheduler
95
+ health at that revision and the expected topic profile/project identity. Offline,
96
+ draining, stale, partial and missing-node results remain unconfirmed. The UI
97
+ polls at most120 observations and offers deliberate Refresh afterward. Verified
98
+ connection identity with `sharing_ready=false` still needs sharing enabled on the
99
+ approved hub. Refresh the connection after enabling it there.
100
+
101
+ Create the replacement on the approved hub with the named **ACME validation
102
+ consumer** key form. Preview/Publish, Apply, verify every expected node, then
103
+ explicitly review and deactivate the old key in the hub inventory. Issuance,
104
+ publication and successful dispatch never retire keys. Disable masks the whole
105
+ connection tuple and base credential without deleting base settings or revoking
106
+ remote trust; retained/shared identities can make disabling ineligible.
107
+
108
+ Secrets remain in an uncontrolled mounted password input and a short-lived
109
+ imperative request draft. They never enter Query/Mutation caches, URL/history,
110
+ storage, receipts or logs. These new writes call `requestFreshAuth` without
111
+ replaying the attempted action; the existing generic Fleet helpers keep their
112
+ prior behavior. Auth/session change or unmount invalidates drafts and closes
113
+ owned confirmations. Double activation is single-flight. A409 revision/catalog
114
+ conflict or `configuration_busy` requires Refresh and a deliberate retry. An
115
+ uncertain publication or issuance may have persisted; read authoritative status
116
+ or exact Group inventory before another attempt.
117
+
118
+ Unavailable APIs, missing DNSMan/publisher/delegation/schema/enrollment or an empty
119
+ approved catalog render a concrete blocked state. Bootstrap supplies `admin_path`
120
+ and permission facts for existing legacy System Setup, Deployments, topology and
121
+ Fleet Scaling links. Links are same-origin and capability-gated. The backend's
122
+ semantic `capacity` hint maps to the actual registered `#/fleet` route. These
123
+ legacy destinations retain their protected confirmations, policy, release holds
124
+ and IaC restrictions. This React topic reuses those controls without a new
125
+ deployment or root authority service.
126
+
127
+ The Runtime settings showcase mounts the actual connection panel with central
128
+ mock scenarios and operator/ordinary-manager identities. The named
129
+ `verify:admin-shared-validation-config` gate includes private DTO/API and mounted
130
+ StrictMode contracts, and is included once by `verify:admin-contracts`. Browser
131
+ proof remains necessary for real native dialogs, keyboard/focus and390px in both
132
+ themes. Mock receipts are fixtures, never live configuration evidence.
133
+
134
+ The private form uses an inline-size container: fact labels stack above UUIDs and
135
+ revision values below420px of available content, including a narrow embedded
136
+ Admin host. The existing Admin sidebar's Collapse control supplies its normal
137
+ compact host at390px; the topic does not change global navigation behavior.
138
+
139
+ Every successful authoritative refresh invalidates a preview when revision,
140
+ catalog, profile, identity or credential facts change. Late preview/operation
141
+ responses cannot restore spent consent or overwrite a newer observation. After
142
+ the automatic observation limit, explicit Refresh re-observes the tracked exact
143
+ operation and resets the read budget; it never dispatches another Apply. Only
144
+ known healthy report/node statuses with no top-level or node error can turn the
145
+ combined proof green. Apply/read replies remain bound to their exact operation
146
+ ID and requested/tracked revision.
@@ -35,6 +35,18 @@ existing system permission gate and conditional queries. Parent, child, member
35
35
  and user navigation opens native detail modals while keeping the directory
36
36
  underneath.
37
37
 
38
+ The Sub-Groups section carries the "Administrators can create sub-groups"
39
+ switch for `sys.groups | sys.manage_groups` holders only; nobody else sees it.
40
+ It reads `metadata.protected.allow_member_subgroups === true` (anything else
41
+ is off) and changes it only through the django-mojo Group action
42
+ `POST /api/group/<id> {"allow_member_subgroups": true|false}`, which answers
43
+ `{"status": true, "allow_member_subgroups": <bool>}`. Never write
44
+ `metadata.protected` directly: that path refuses `manage_groups` admins. The
45
+ action needs django-mojo with #9126; the server refuses a caller without the
46
+ global grant with 403 and a non-boolean with 400. While on, people who manage
47
+ the group as members can create groups directly under it; the setting is not
48
+ inherited by the new groups, and turning it off leaves existing groups alone.
49
+
38
50
  The group model and section code are adapted from the reference portal at
39
51
  `v0.2.5` commit `93c7fdfeb3096d33010e72bd4483cf9ca6e3d6f5`. The source hashes
40
52
  and corresponding package hashes are recorded in
@@ -62,6 +62,16 @@ per endpoint, so `UserDetail` and the fleet-wide tables share one cache.
62
62
 
63
63
  ## Action contracts
64
64
 
65
+ - **Set temporary password** is available in the user header menu and Security
66
+ section with `sys.users | sys.manage_users`. Confirmation identifies the user
67
+ and explains that their password is replaced and all sessions are revoked.
68
+ `POST /api/account/admin/user/password/temporary` sends only `{user: id}`
69
+ through `withFreshAuth` (440 prompts and retries once). The generated password
70
+ appears once in a copyable dialog; closing or losing permission removes it.
71
+ It never enters Query/Mutation cache, storage, or a toast. The server requires
72
+ a person session, restricts superuser recovery to superusers, and forces the
73
+ recipient to replace the password before completing sign-in. This uses the
74
+ existing django-mojo endpoint; no backend upgrade or new permission is added.
65
75
  - Username changes use the `change_username` POST_SAVE_ACTION, not a direct
66
76
  field save. Model saves/actions retry one fresh-auth 440 automatically;
67
77
  combined `disable_totp` writes explicitly use `withFreshAuth`.
@@ -135,3 +135,31 @@ browser interactions for all family bodies, both themes, desktop/narrow,
135
135
  keyboard navigation and console checks. The lazy Showcase **Admin modal audit**
136
136
  demo supplies long-title, rich/zero/dot badges, nested editor, lifecycle-cancel and
137
137
  embedded-host cases. Existing ToastHost top-layer visibility work remains separate.
138
+
139
+ ## Shared validation confirmations
140
+
141
+ The DNS shared-validation editor freezes the target group/domain, existing
142
+ allocation and session while an operation is pending. Grant additionally freezes
143
+ the imported request, active exact recipient UUID and client reference. Participant
144
+ revoke/cleanup freezes the selected current-page participant independently of any
145
+ request or recipient lookup; cleanup retry leaves the reference revoked.
146
+ Its owner AbortSignal closes only its confirmation on unmount, scope loss or
147
+ auth/session replacement; submitted writes can still persist. Escape/backdrop
148
+ and Close are unavailable while the outer operation is pending.
149
+
150
+ Prepare explains the pause of direct validation/renewal while valid material
151
+ remains available. Grant names the exact project and apex/wildcard issuance
152
+ authority and independent private-key custody. Revoke explains that future
153
+ validation stops, already-issued certificates remain valid and other consumers
154
+ keep working. Revoked references require a fresh, explicitly prepared request.
155
+ No confirmation is inferred from a copied descriptor. Fresh authentication
156
+ prompts without replay; every submitted result reconciles before another write.
157
+
158
+
159
+ The protected connection topic separately confirms Publish and Apply, naming
160
+ profile/project and exact revision consequences. The ACME consumer key form
161
+ confirms the current original Group name/UUID and fixed protected purpose before
162
+ issuance; its token display is transient and the existing audited reveal is
163
+ available for recovery. Every owned nested dialog receives the auth-owner abort
164
+ signal. Session replacement, scope loss and unmount close those dialogs; submitted
165
+ writes may still persist and are never replayed after fresh authentication.