Manage apps and the app-settings lane — what landed
The admin half of phase 3 of Apps live on the instance (§6, §7). It builds on
the plan in Settings extensions. Everything here is a framework control
(DataGrid + PropertyColumnControl, Stack, Title, Markdown, Button,
MeshNodePickerControl); nothing writes a grant, creates a Space or copies a package.
Every page is a template (Doc/GUI/DataBinding, "Templates first, data later"): the grids are
BindGrid and the lines BindMarkdown, fed by ManageAppsFeeds / InstanceMapsFeeds, which read
the mesh and build no control; row buttons act on the clicked row (ctx.RowAs<TRow>()). The admin
verdict and the page switch (grid / one app / capabilities) are structure, decided before any read.
The lane: UiContribution context AppSettings
A content module cannot reach another hub's configuration, so it could not add a tab to the Admin app or to another app's settings. Core gained one context for it (MeshWeaver#6445):
| Field | Meaning |
|---|---|
context: AppSettings |
a tab on ONE app's settings page |
host |
the app's path — Admin for an Administration section, AI/AiThreads for Threads |
address + area |
the area embedded as the tab body; the address must lie in the contribution's own partition (the PersonApp rule) |
gates.adminOnly |
every Administration section carries it; the Store areas refuse a non-admin as well |
The tab joins the host's settings page (/{host}/Settings/{node id}) and no other. Seed validation
reports a hostless tab and a foreign address; scripts/check-menu-contexts.py knows the context.
Administration (host Admin)
| Tab | Contribution | Body |
|---|---|---|
| Manage apps | Store/AdminTabs/ManageApps |
Store area ManageApps |
| Maps | Store/AdminTabs/Maps |
Store area AdminMaps |
| Operations packages | Store/AdminTabs/Operations |
Store area AdminOperations |
| Fleet console | Hosting/AdminTabs/FleetConsole |
Hosting/Console › Content |
| Build queue | Hosting/AdminTabs/BuildQueue |
Hosting/Console › Builds |
| Governed activities | Governance/AdminTabs/Activities |
Governance/Activities › Overview |
The existing compiled Admin tabs (Registration from HostingInstanceModuleAttribute, Fleet from
InstancesAdminLayoutArea) are unchanged.
Manage apps
- The grid lists exactly the app roots:
nodeType:Store/Plugin content.app:true, read through the synced query so the content is typed (ManageApps.AppRows). Columns: icon, name, tier, Who sees it (the audience, or "everyone on or above"; "…" while the policy is read, "unknown" when it cannot be), status, and Manage. - Add lists the package catalog (
StorePackagefeed) minus the ids already on the instance (AddableRows). Add to this instance files a governed proposal — aGovernance/Proposalat{admin}/_Proposals/{id}under thepackage.provisionstandard with inputpackage. Once two signers who are not the proposer sign, the governance executor writes theStore/Provisionrequest as System and the control plane runsSystemInstall.RunResolved. A person-writtenStore/Provisionrequest is refused whenAccess:BroadGrantGuard:Modeenforces, so the page never writes one (Governance/Catalogue). - Remove proposes
package.removethe same way (→SystemRemoval, which refuses a package another one requires and a pre-installed one). - The management page shows what the app declares it needs (
PluginContent.Configuration: key, kind, secret or value, why) and itsrequires. - Audience. The picker offers users and groups through the platform's
AccessSubjectQueries.ForScope(null). Adding or removing a subject edits{app}/_Policyfieldaudience(ManageApps.AudienceField, the one spelling on this side; corePartitionAccessPolicy.Audience, MeshWeaver#6442). The content object is edited, never replaced (WithAudience), and the write runs as System afterSystemAuthorizationconfirms the clicking admin, because a global admin holds no Update on a system-synced space. What the audience does (plan coverage AND audience → Read) is the Licensing change'sPlanAccess(MeshWeaver.Plugins#3308); this page only writes the field. - 🚧 Blocked under an enforcing guard: a
PartitionAccessPolicywrite must run under a governedaccess.policy-changeactivity (Governance/Catalogue), and that standard's executor creates policies only — an UPDATE executor is the follow-up. Until it exists, the audience write (System, after the admin check) passes only whileAccess:BroadGrantGuard:Modeis log-only; underEnforceit is refused and the status line says why. The audience then belongs on a governed proposal like Add/Remove. - Show capabilities lists the roots that are neither apps nor hosted in one.
Maps
- Keys:
GoogleMaps:ApiKeyandAppleMaps:Token(the renderers), plus the Apple Maps Server API trioAppleMaps:TeamId/KeyId/PrivateKeythat place search and routes run on (AmapsConnect.InstanceKey; all three needed). The action names only the configuration key; the deployment record resolves its vault object and synced Secret. The page lists the renderer packages present and which are ready (OpenStreetMap needs no key); which one DRAWS is the deployment's module choice (Modules:Assemblies), which the page does not claim. - Plaintext never reaches content. The value is encrypted with the platform key
protector (refused when it does not come back
enc:-tagged), then aHosting/InstanceActionwithrequestedAction: SetSecretsis filed in the operational space for this instance'sHosting:Deployment— the path the record page's Set Key Vault secrets dialog uses. The page shows a mask, and "set on …" ("…" while the history is read, "unknown" when it cannot be) from the newest COMPLETED action naming that key (state: Done,lastAction: SetSecrets), in the viewer's time zone (pinned across both DST changes and the date line). - An instance that does not know its deployment id (
Hosting:Deploymentunset) refuses the save and says to set the key on its record page on the control instance. - The user-facing key entry is gone: the Apple Maps page of a viewer's copy no longer renders the credentials form. Keys already stored there keep working; moving them to the instance is the migration phase, and nothing here deletes them.
App settings (other hosts)
| App | Tabs | Notes |
|---|---|---|
Threads (AI/AiThreads) |
Models, Harnesses, Web search (AI/AppSettings/*) |
Models lists the ModelProvider nodes in the viewer's personal provider namespace (ModelProviderNodeType.UserNamespacePath, the one the picker and the credential resolver read; masked key dialog on each); the Setup page's model-provider sell-shelf is removed. Harnesses reuses the harness cards (Get = enable, settings link = /login). Web search states the instance default by the plugin's own rule (Google only with both key and cx; retired Bing never). No plan gates any tab. |
Signature (Signature/MySignatures) |
Providers (Signature/AppSettings/Providers) |
embeds the existing SigningAuthority area; the signing authority stays in the person's own settings |
Tests
- core
AppSettingsContributionTest— host match, other pages, hostless, AdminOnly, foreign address, seed validation. Store/Catalog/Test/ManageAppsTests— the grid lists exactly app roots; Add and Remove propose the governedpackage.provision/package.removeactivity; the audience lands on_Policywith every other field kept; the Maps save filesSetSecretsand never puts the secret in content; "set on" and the renderer.AppleMaps/Maps/Test/MapsTests— no personal key entry.src/MeshWeaver.AI.Test/AiThreadsApplicationTest— the Settings tabs render the same for a pro and a free viewer; Models lists own providers, never a shelf; the web-search default.
Not executed by these suites (follow-up): the Maps Save chain (encrypt → create the
Hosting/InstanceAction in the operational space → control-plane target) and the audience
WriteAudience chain (admin check → System → owning _Policy hub) need a mesh rig with a layout
host, an operational space and a real admin grant; the pure suites pin their shapes, and the live
write is the deploy check below.
Deploy
Recycle after the merge and the core seal: Store (the catalog type), AI/AiThreads, Admin.
The tabs appear only once the running platform carries MeshWeaver#6445; before that the
contributions are inert (no consumer renders the context).