Apps live on the instance, not in your home
Maintainer, 2026-10-10:
- "Apps should be globally installed, not under user account. User should see apps depending on his roles and functions (access control). For each user, let's store apps in cache for like 5 min sliding scale."
- "remove stuff from home. only actual user data will be stored in home, but not app"
- "remove install in this sense, it is not robust"
- "plan well and migrate across the fleet. see we don't have a gazillion apps appearing. what is settings tabs should be in settings apps, what is examples should be in examples app. make nice app design."
- "user data can still be installed. courses, when we start them."
- On the design (mockups linked from the pull request): "app screen, like it, make search (filter) bigger. personal: like design. we should offer the connect to mail / calendar, home entertainment, ... in setup phase. not just google, but any email, linkedin, ..." and "maps => this must go to instance admin app. in user examples, we should see examples how to use them."
Status: phases 1–4 are in progress — most of each is merged, parts are still in review or blocked. The launcher flip (core #6448) is live on both instances: the launcher reads the app directory. The per-phase state is §10. This page is the design of record for the migration. It builds on Settings extensions (plans only, Manage apps, three settings homes). It retires the per-user half of Installing and of the home surface. Plan → access, the access-check half, is carried by a parallel change (see §3).
1. What is wrong today — measured
An app is already stored once, as a partition on the instance (Signature/, Crm/, Edu/).
"Install" then copies part of it into every user's home and writes bookkeeping beside the copy.
Measured with get @{user}/* for one long-standing account on the control instance, 2026-10-10:
| In the home | Count | Written by |
|---|---|---|
{user}/{Package} "X — my workspace" roots |
~50 (WebSearch, Mail, Grok, Cursor, Stripe, Hosting, Radzen, …) | Localizer.BuildHomeRoot |
{user}/_App/* tiles (InstalledApp) |
one per app | Localizer.RegisterAppRecord, core SeedDefaultAppsLogonAction + SeedInboxAppLogonAction, InstanceAppTileLogonAction ({user}/_App/Instance), Edu CourseAppTile, SocialMedia ProfileAppPin / ProfileTilesEnsure |
{user}/_Install/* provenance records |
one per package | Localizer.WriteManifest |
{user}/Agent, /Skill, /Harness |
copies of every package's agents, skills, harnesses | installPaths fixed segments |
{user}/{Course}/… |
whole course trees, e.g. 211 nodes of AgenticEngineering | Localizer.Localize via Store/InstallRequest |
{user}/_InstallRequests/* |
one per first-open | Edu EnsureCopy, StartExercise, GoToMyCopy |
every user's AiSettings |
package /Agent + /Skill queries appended |
AiSourcesInstallHook (on every partition install) |
The copy machinery. It has four copy engines: Localizer.Localize, ItemRestore,
StandardPacks.EnsureFor* and the Reinsurance demo importer. About 20 trigger sites call them.
Two of those triggers are page renders: StandardPacksOnboarding.EnsureForViewer on the
catalog/home render, and Edu EnsureCopy on the lesson render. Keeping the copies converging takes
Store/Installer, about 8,900 lines, plus about 320 node-test methods.
Every way it has failed is a property of copying. One per-user state machine per app per home gives one chance per viewer per app to go wrong:
- snapshots that never refresh (AgenticOffice);
- a Reset that deleted nothing because an empty read meant "Anonymous" (211 nodes kept);
- a verify that failed 42 times on
Agent/Voicevsvoice(MeshWeaver#4817); - tile names, icons and groups that drift and need a mesh-wide heal sweep;
AdvancedBusinessRules · 1/17 · 1/14 · 1/14 …;- a free-pack sweep that minted grants for 60+ users;
- unrecorded learner copies nothing can update (#1606).
That is what "not robust" means here, and the fix is to stop copying apps, not to copy more carefully. Only material the user explicitly starts (a course) is still copied, because that copy is the user's own work (§5).
2. The model in one table
| Question | Answer |
|---|---|
| Where does an app live? | Once, on the instance, in its own partition. Add and Remove are the instance admin's acts (SystemInstall, Admin › Manage apps). There is no per-user step. |
| Who sees it? | Whoever the access check lets read it: plan coverage AND the app's optional audience (roles and groups), plus explicit grants (§3) |
| What does the launcher show? | AppDirectory.ForViewer(viewer): the visible apps, computed live and kept warm per viewer for 5 min sliding (§4) |
| What is in my home? | Only what I made, what I started, or what is about me. A started course is mine. No app content, tiles, workspace roots, or agent/skill copies (§5) |
| How many apps? | About a dozen, from 111 packages. Settings go to the settings apps, examples to the Examples app, modules to their suite. A roster gate keeps it that way (§6) |
3. Who sees an app — access, not install
visible(app, viewer) = app.content.app == true ← one of the roster (§6)
AND Read(viewer, app entry point) ← the ONE access check
Read on gated package content =
PlanAccess.Covers(viewer, package) ← effective plan covers package.tier
AND (app._Policy.audience is empty OR viewer ∈ audience) ← roles / functions, transitively via GroupMembership
OR an explicit AccessAssignment / publicRead _Policy ← as today, BUT capped by the audience (below)
- Roles and functions are groups. "Only underwriters see Underwriting" means the admin sets
Underwriting/_Policy.audience: [Underwriters]in Manage apps. The plan still has to cover the tier. With no audience set, everybody the plan covers sees the app. - The access check is the only gate. It answers the launcher, opening by URL, search and agents identically, so a restricted app cannot leak through any of them.
- The plan → access seam is carried by a parallel change on branch
feat/covered-plan-opens-without-get. It providesPlanAccess.Covers/PlanAccess.GrantsinStore/Licensing/Source/PlanAccess.csand folds them into core'sPermissionEvaluator. This plan consumes it and does not touchStore/Licensing,PluginGateor the cover areas. The audience field was agreed with it on 2026-10-10. - The audience is a cap, not one more grant. The gating pass writes a package's audience as
deny rows over everyone outside it, so a legacy package-wide grant cannot reopen an app its
audience excludes. One example is the
PluginGate.Enrollrow that an old Get orSubscriptions.Unlockminted. The parallel change retracts plan-derived legacy grants and keeps deliberate one-time purchases (the eternal entitlement records). - The probe is the app's entry point, not its root, because a gated root is readable by everyone
as the storefront ("root +
_-satellites").
4. The launcher list: live, kept warm 5 minutes
Core gets AppDirectory (in MeshWeaver.Graph, beside UserActivityLayoutAreas.BuildAppsBand,
which reads it instead of path:{owner}/_App):
The app roots. One process-wide query in the established package-root shape:
nodeType:(Space OR Store/Plugin) is:main partitions:all(DataModelExplorerLogic.AllPluginRootsQuery, because a root can still be indexed asSpace), filtered tocontent.app == trueand bounded by an explicitlimit. It goes throughIMeshNodeStreamCache. It is the same for every viewer.Per root,
PermissionEvaluator.HasPermission(viewer, entryPoint, Read). Its inputs are the process-wide$security-*streams, so the result is live: a grant, a membership, a plan change or a revoke re-emits.Plus the platform entries every viewer has.
- Settings (
/{user}/Settings) for everyone. - Administration (
Admin) whenIsGlobalAdmin. - Inbox.
These are not records, so
SeedDefaultAppsLogonAction,SeedInboxAppLogonAction,InstanceAppTileLogonActionand the icon-adoption logon actions are retired.- Settings (
Display (name, icon, description, category) comes straight from the package root, so there is nothing to stamp or heal.
The 5-minute cache. AppDirectoryCache is a singleton keyed by viewer, holding
Replay(1) of the live stream.
- Each subscription resets the clock.
- After the last subscriber leaves, the stream is kept warm for 5 minutes, then disposed.
- A user who comes back within 5 minutes paints instantly. One who is idle longer recomputes once.
| Property | Value |
|---|---|
| Freshness while held | live: revokes and grants re-emit, so there is no stale-but-cached window |
| Lifetime | sliding 5 min after the last subscriber |
| Scope | per process (each replica its own); its own MemoryCache instance, because the distributed host registers no IMemoryCache |
| Not authorization | opening an app re-evaluates access. The list never grants anything |
5. The home holds only the user's own data
Rule for every app author: an app writes into a user's home only in response to that user creating something. Signing in, opening an app, being covered by a plan and rendering a page write nothing.
What remains of "install" is Start: copying user-data templates into the home on an explicit
user act. A package declares them in startPaths (today's installPaths, narrowed). They are
material the user will work in, such as a course's exercises or a template workspace. They are
never the app's own code, agents, skills, guides or examples, which stay in the package partition
and reach the user through access. Start writes the copy and its fingerprint record
({user}/_Install/{package}, kept for refresh and restore). It writes no tile, no workspace
root of its own and no agent/skill copies. The copy engine stays for exactly this:
Localizer.Localize, narrowed to startPaths, with RefreshPlan and ItemRestore.
| Was in the home | Becomes |
|---|---|
{user}/_App/* tiles |
nothing; the launcher reads AppDirectory. The arrangement (group, order) moves into one user-data node, {user}/_Settings/Launcher, written only when the user rearranges. Favorites stay User.PinnedPaths |
{user}/{Package} workspace roots |
nothing; the app's front page is its entry point |
{user}/_Install/*, {user}/_InstallRequests/* |
only for what the user started (a course); nothing for apps |
{user}/Agent, /Skill, /Harness copies |
nothing. The AI pickers (AgentPickerProjection.BuildAgentQuery, BuildSkillQueries, BuildHarnessQuery) query the package partitions of the viewer's visible apps. {user}/Skill and {user}/Agent hold only what the user wrote. AiSourcesInstallHook stops appending to every user's AiSettings |
{user}/{Course}/… course copy |
stays, but only on Start. Starting a course is the learner creating their own work, so the course's working material is copied when they press Start, never on a page render or a first open. Lessons a learner only reads render from {Course}/…. Answers stay in {user}/_Answers/… and progress in {user}/_Progress/…. Refresh-what-you-did-not-edit (RefreshPlan) and per-exercise Restore keep working on this copy |
{user}/AppleMaps/MyMaps key, {user}/AppleWeather connector data |
Maps: nothing (keys are instance-only, Administration › Maps). Weather and the other personal connectors: {user}/_Connections/{provider} (the existing Connections layer), written when the user connects. Existing connector records are moved there by an idempotent migration in phase 4: vault references are carried over, never re-encrypted in the clear; a conflict keeps the newer record and reports the other; it is verified per user before the old readers are retired |
| ReinsuranceDemo submissions | stays: the user pressed Create demo data, so this is user data |
6. The fleet consolidation — 111 packages → about a dozen apps
Scanned 2026-10-10 at origin/main of Plugins (76 roots), SocialMedia (5), Reinsurance (17),
Manufacturing (1), Education (11), Crm (1) and FundReporting (1). Every root lands in exactly one
place:
| Place | Declared by | Surfaces as |
|---|---|---|
| App | app: true, and on the roster |
a launcher tile, subject to §3 |
| Settings section | hostedIn: {user}/Settings · Admin · {app}#settings |
a tab of the user settings app, of Administration, or of the app's own Settings |
| Example | hostedIn: Developer/Home, slot examples · showcases · tools |
a section of the Examples app (already built: Developer/DeveloperApp) |
| Section of an app | hostedIn: {app}, slot |
inside that app (a suite module, a course, a game, a channel) |
| Capability | none of the above | no surface; listed in Manage apps only |
Apps — the whole launcher
| App | Package | Absorbs | Seen by |
|---|---|---|---|
| Threads | AI |
Chat; providers and harnesses in its Settings |
everyone |
| Learning | Edu |
11 Education courses, RiskTransfer, SwissSolvencyTest, ReinsurancePractice, LearningRoadmap, Training tours |
everyone (courses by tier) |
| Feedback | Feedback |
everyone | |
| Examples | Developer (renamed Examples) |
9 galleries, 4 showcases, 3 builder tools (already hosted), plus the Maps galleries | everyone |
| Settings | core person app | §7 | everyone |
| Approvals | Approvals |
pro | |
| Expenses | Expenses |
pro | |
| Signature | Signature |
MySignatures; providers in its Settings; signing authority stays in user Settings |
pro |
| CRM | Crm |
pro | |
| Social Media | SocialMedia |
LinkedIn, X, YouTube, Marketing as channels; LinkedIn profile pins become sections, not _App tiles |
pro |
| Personal (new host) | Personal |
Google, ICloud, HomeAssistant, AppleMusic, AppleWeather as sections, each with its connection in the app's Settings |
free |
| Games (new host) | Games |
Chess, RolePlay, QualityTime / QualityTimeDe (the language twin is picked by the viewer's language) |
personal (maintainer, 2026-10-10: "games are included in personal tier") |
| Administration | core Admin app | Manage apps (+ Add), Operations (Hosting, Governance, BuildServer, AzureCostManagement, Observability), instance settings |
global admins |
| Reinsurance | Reinsurance |
Claims, Underwriting, Pricing, LossModelling, Planning, Ifrs17, SST, ILS, EventMonitoring, Portfolio, PortfolioOptimization, TaskManagement, ReinsuranceDemo, Cornerstone |
enterprise + audience |
| Manufacturing | Manufacturing |
enterprise + audience | |
| Fund reporting | the FundReporting root | enterprise + audience |
A pro user sees 12 tiles, an admin 13, and an enterprise user adds one per vertical. Today the home shows ~50 roots plus a tile per install.
Settings sections — no tile
| Package | User settings | Administration | App's own Settings |
|---|---|---|---|
GoogleMaps, AppleMaps, OpenStreetMap, Maps |
— (no user map keys) | Maps: the instance's keys and default renderer, the only place they are set | — · users learn maps in Examples › Maps |
MyAi |
AI: my agent/skill/model sources | — | — |
Google, ICloud, Mail (personal half), LinkedIn, X, YouTube, HomeAssistant, AppleMusic + new generic IMAP/SMTP mail and CalDAV / ICS calendar connectors |
Connections › Accounts: every account the assistant may use, any provider, several per kind | the OAuth app registrations | the apps read the viewer's connections; Personal and Social Media show Connect in Settings when one is missing |
Providers, Anthropic, OpenAI, AzureFoundry, AppleIntelligence, WebSearch |
— | AI providers: shared keys, tiers, search key | Threads › Models: my own keys; web search default |
ClaudeCode, Codex, Copilot, Cursor, Grok, OpenCode, Antigravity, Acp |
— | CLI availability | Threads › Harnesses: enable + /login |
Teams, WhatsApp, iMessage, Voice |
Channels: link my account / Mac bridge / satellite | Channels: bot / Business API registrations | — |
Mail |
Connections › Accounts (Microsoft 365 consent) | Mail: system mail + intake | — |
Mcp |
Connections › MCP clients | — | — |
Notifications |
Notifications (exists) | — | — |
Stripe |
— | Payments | — |
Store (plans, subscription) |
Plan & billing | Plans & seats, coupons | — |
Hosting.Instance |
— | Registration (exists) | — |
Capabilities — no surface
Essentials, Publish, Import, Indexing, Collaboration, Export (the menu action; its
gallery is in Examples), OgCard (embed; gallery in Examples), RemoteControl, Video,
AzureBlob, Store (the engine). They are listed in Manage apps so an admin can see they are
present.
The roster gate — no gazillion apps
validate-repos.py gets check_app_roster, which holds a literal set of the package ids
allowed to declare app: true. It follows the pattern of check_one_agent_master, and each
satellite carries its own set. A new launcher tile is therefore a deliberate, reviewed one-line
diff. Everything else must declare hostedIn or nothing.
- A Plugins package that sets
app: truewithout being on the roster is red. - A
hostedInnaming a host that does not exist is red.
7. App design — one look for every app
Every surface is built from the platform's controls (Stack, LayoutGrid, Title, Markdown,
Button, DataGrid), never from markup of its own.
The launcher. A large filter field at the top of the home, 64 px tall, full width, with
/ to focus it. It matches app names and what an app does ("sign a contract" finds Signature), and
it filters the one grid in place. Below it is one grid of rounded-square icons, grouped by category. It has the existing ⋯ menu,
Recent / Favorites / All, and Icons / Cards / Rows. With a dozen apps the grouping shrinks to four
headings:
| Daily work | AI & learning | Business | Personal |
|---|---|---|---|
| Approvals · Expenses · Signature · Feedback | Threads · Learning · Examples | CRM · Social Media · Reinsurance · … | Personal · Games · Settings |
Administration sits last, admins only. Icons follow one system: a 48-unit rounded square
(rx=10), one brand colour or a two-stop gradient, and a white glyph, as Developer, Store and
Crm already do. Packages whose icon is an emoji or a currentColor outline (Agent, Skill,
Training, Voice, …) get redrawn in that system.
The app front page (AppFrontPage, one shared builder in Store/Core, used by every app's
entry point):
- Hero: the icon at 64 px, the name, a one-line promise, and one primary button. That button is Open or, for a suite, the first section. A secondary button reads Settings when the app has any.
- What you can do: three cards, each a sentence and a deep link.
- Ask the agent: two or three example prompts, which start a thread through
hub.StartThread. - Sections: for suite apps (Personal, Games, Social Media, Reinsurance, Examples, Learning), a
card grid of the hosted packages, from the existing
ExtensionShelfquery. - Not covered: one line, Included in Pro → see plans, never a buy button.
Setup: connect what you already use. The first sign-in walks through Profile → Connect → Your apps. Connect offers every kind of account at once, by any provider:
- Mail & calendar: Microsoft 365, Google, iCloud, other email (IMAP/SMTP), other calendar (CalDAV or a subscribed link).
- Professional & social: LinkedIn, X, YouTube.
- Messaging: Teams, WhatsApp, iMessage.
- Home & entertainment: Home Assistant, Apple Music, Weather.
Each one is optional, and Skip for now is always there. Every connection is the user's own data
({user}/_Connections/{provider}, secrets encrypted through ConnectionSecrets), so this is exactly the kind of write
§5 allows. The same list is Settings › Connections › Accounts afterwards, and an app with a
missing connection links there.
The Settings app. A left rail grouped into Account (Profile, Account, Preferences, Sharing,
Plan & billing), Personal (AI, Notifications, Signing authority) and Connections (Accounts,
Messaging, MCP clients). There is no Maps tab: map keys are instance configuration. Each tab is a UiContribution context: PersonApp from the
owning package, shown only when the access check passes.
Administration. Manage apps first: a DataGrid of every app on the instance (icon, name,
tier, Who sees it, status), with Add (the registry catalog) and Remove. Each app's row
opens its management page, which holds its instance settings inline and the audience picker. Then
Users & access, Plans & seats, AI providers, Maps, Channels, Mail, Payments, Operations.
App settings. A Settings tab on the host app, fed by a new context: AppSettings
contribution. Threads, Signature, Personal and Social Media use it.
8. Migration — phases, repos, gates
Each phase is its own pull request or set of pull requests. Phases 1–5 are additive or retire code paths. Nothing in them deletes user data. Phase 6 deletes, and runs only on the maintainer's go, per instance, after a dry run.
| # | Repo | Change | Proof |
|---|---|---|---|
| 0 | Plugins | This page | — |
| 1a | core | AppDirectory + AppDirectoryCache (live, 5 min warm). The Apps band reads it behind Home:AppSource (Records | Directory; the default was Records until MeshWeaver#6448 made it Directory — see How the switch flips below). {user}/_Settings/Launcher arrangement node, seeded once from the viewer's _App records on first directory render |
AppDirectoryTest: roster × plan × audience × admin matrix; revoke re-emits; warm reuse within 5 min and disposal after; the arrangement carries over |
| 1b | core + Plugins | (parallel change) plan → Read seam with audience | its own tests |
| 2 | Plugins + satellites | Apply §6 to every index.json. app: true only on the roster, hostedIn everywhere else. Developer → Examples. New host packages Personal and Games (this directive is the owner's go). check_app_roster gate |
validate-repos.py; each host renders its sections (render_area) |
| 3 | core + Plugins | Setup › Connect + Connections › Accounts (new generic IMAP/SMTP and CalDAV/ICS connectors beside the existing Microsoft 365, Google, iCloud, LinkedIn, X, YouTube, Home Assistant, Apple Music ones); the launcher's large filter. Settings homes: the Settings app rail + PersonApp tabs (Maps, AI, Channels, Connections, Plan & billing). Administration › Manage apps (+ Add / Remove / audience) and the instance tabs. context: AppSettings + Threads › Models / Harnesses. AppFrontPage |
per-tab Tests areas; the Manage apps audience write; render as free, pro, admin |
| 4 | Plugins + Education + Reinsurance | Stop writing implicitly: StandardPacks per-user sweep + render-path onboarding, Edu EnsureCopy on lesson render, RegisterAppRecord, AppTileRefresh, HostedTilePurgeWatcher, InstanceAppTileLogonAction, AiSourcesInstallHook. Narrow install to Start: installPaths → startPaths (course exercise material only; every Skill/Agent/Guide/Examples/My* entry dropped across the fleet); only the course's Start button and StartExercise copy. Readers switch: AI pickers to visible-app partitions; unstarted lessons render the master; cover CTA → Open. Home:AppSource becomes Directory with the image (How the switch flips below) |
a fresh user signs in, opens every app, reads a lesson: get @{user}/* lists only _Settings, _Answers, _Progress, _UserActivity and what they created; pressing Start on a course adds that course's material and its _Install record, nothing else |
| 5 | core + Plugins + satellites | Delete what Start does not need: tile and home-root code in Store/Installer (BuildHomeRoot, RegisterAppRecord, AppTileRefresh, the fixed Skill/Agent/Harness segments, StandardPacks per-user), the logon seeders, SocialMedia tile pins, installPaths (now a validate error naming startPaths), core InstalledApp from the launcher. Keep Localize for startPaths, RefreshPlan, ItemRestore, InstallRequest (the Start front door) |
all repos green; the tile/sweep/home-root test methods are retired with their code |
| 6 | ops | Clean the homes (destructive): a Store/Maintenance action, dry run first, report per instance. Scope: legacy APP copies only: workspace roots, copied Agent/Skill/Harness, _App records, and the _Install/_InstallRequests records of packages that are not started-course material. Pristine app copies (fingerprint = record) are deleted; edited ones are kept as the user's content with the app chrome stripped; unrecorded ones are kept and listed. Started courses and their _Install provenance are never touched, pristine or not, because RefreshPlan and per-exercise Restore depend on that record |
report reviewed by the maintainer, then run per instance; afterwards get @{user}/* matches the phase-4 proof |
How the switch flips (phases 1a and 4). The plan above had an operator set Home:AppSource = Directory per instance. That cannot work on the record-driven fleet: its deployments render no
Home__AppSource, so a values overlay could not flip it, and a launcher flipped without its writers
(or the reverse) leaves an instance half-switched. The transition is therefore driven by the IMAGE. Core MeshWeaver#6448 makes Directory the launcher's default, and
every reader of the switch in this repo applies one rule: an explicit Records or Directory
decides; an absent, blank or unknown value follows the running image's launcher default
(HomeAppSource.IsDirectory(null), probed by reflection because the code also runs on images that
predate the type). That rule is applied in a STAGED way. On the roll that brings #6448 the launcher and the TILE
writers switch together: HomeWrites.TilesEnabled / EduHomeWrites.TilesEnabled (the standard-packs
sweep, AppTileRefresh, HostedTilePurgeWatcher, the course-tile stamp), the AI engine's
HomeWritesSwitch and InstanceAppTileLogonAction follow the image default. Three readers are
deliberately EXPLICIT OPT-IN for now and keep their legacy behaviour on an absent value on every
image: the Store/Edu COPY writers (HomeWrites.Enabled / EduHomeWrites.Enabled: fulfilment,
DependencyInstall, Localizer, the lesson-render install request), because they still carry
acquisitions until phase 5; and the credential readers AmapsConnect.DirectoryFor (instance-only
map key) and AwConnections.DirectoryFor (Weather record under _Connections), until the per-user
credentials are migrated. They turn only on an explicit Home:AppSource = Directory, and an
instance that must stay on the records states Home:AppSource = Records explicitly.
Phase 3 status (2026-10-11). The admin half landed: Administration › Manage apps (grid of
app roots, Add/Remove as governed package.provision / package.remove proposals, audience on
_Policy, capabilities), Maps (instance keys to Key Vault via SetSecrets, OpenStreetMap
default; the per-user Apple Maps key form is gone), Operations (Fleet console, Build queue,
Governed activities, overview), and core's context: AppSettings (MeshWeaver#6445) with Threads ›
Models / Harnesses / Web search and Signature › Providers. Administration is a host (Plugins
#3325): the operations shelf carries Hosting, Governance, Build Server, Observability and Azure
Cost Management, the AI providers shelf carries Providers, and Hosting and Providers left the
launcher roster. Details: Manage apps — what landed. Setup › Connect and
Connections › Accounts / Messaging landed too (Plugins #3312). Still open in phase 3: the Settings
app rail, the launcher filter and AppFrontPage. §10 carries the per-pull-request state.
Order of landing: 1a → 1b → 2 → 3 → 4 → 5 → 6. Phase 2 can run in parallel with phase 3.
Deploy before recycle. A core change (phase 1a's AppDirectory, the AppSettings context) is
process-loaded. It reaches an instance only through the CD roll of a core image that contains it:
confirm the instance's /api/version against the merge first. A module or package change reaches
an instance through the registry publish (after main's settle-locks PR) and the instance's
update. Only once the instance runs the new build does a recycle make sense. Then recycle the
per-node hubs that serve the changed types:
- the person app on a test user, and
Admin; AI/AiThreads;Developer/Home;Store/Catalog;- every new host.
Then verify on the public instance as anonymous, free, pro and admin.
9. Decisions taken here, and the ones left
| Live + warm instead of a snapshot with TTL | The ask was "5 min sliding". A live stream kept warm 5 minutes gives the same cost profile and cannot show a revoked app |
Developer keeps its id and is displayed as "Examples" |
renaming the partition would move every hosted path for no user benefit |
| Map keys are instance-only (maintainer) | this reverses Settings extensions' "every user may enter their own map key". Users learn maps from Examples › Maps. The existing per-user AppleMaps MyMaps key nodes are the user's private material (a .p8), and a global admin has no read on user data. They are never shown to an admin or reused as the instance key. They stay private, unused; their owners are told once that maps now use the instance key and how to delete their old key. The admin configures a separate instance credential |
| Personal and Games become new host apps | they bring the personal-data and game tiles from 9 down to 2 |
| Open: default audience of a newly Added app | Proposed: none, so the plan alone decides; the admin narrows when needed. Today's install-defaults pass keeps adding the platform core set |
| Open: phase 6 | runs only after the dry-run report is reviewed, instance by instance |
10. Status — 2026-10-11 (live readings)
| Phase | Pull requests | State |
|---|---|---|
| 0 plan | Plugins #3306 | merged |
| 1a app directory + 5-min warm cache | core #6446 | merged, live on both instances (core 0b9cfa9 and later). path:{user}/_Apps answers 15 apps (control instance) / 17 (public instance) for one account. Before, that account had ~50 workspace roots. |
| 1a flip: directory is the DEFAULT | core #6448 | merged (core 5467df1668), live on both instances: the home's Apps band now queries path:{user}/_Apps scope:children nodeType:InstalledApp, i.e. the directory. Its chart guard is HomeAppSourceReachesTheConfigMapGuard |
| 1b plan → access + audience | core #6442 (merged), Plugins #3308 (parallel change) | audience field merged; Store half in progress |
| 2 roster, Personal, Games, Examples | Plugins #3310, SocialMedia #240, Reinsurance #254, Crm #169, Manufacturing #129, FundReporting #13, Education #393 | merged, live |
| 2 follow-up: suite shelves | SocialMedia #241, Reinsurance #256, Plugins #3318 (Cornerstone → Reinsurance) | merged. Live on the public instance; SocialMedia lags on the control instance (filed to triage) |
| 3 Connections + Setup (any provider) | Plugins #3312, core #6444 + #6449 (MailKit) | merged: Setup › Connect, plus two Connections tabs. Accounts lists mail and calendar (Microsoft 365, Google, iCloud, other email over IMAP/SMTP, other calendar over CalDAV or .ics), professional (LinkedIn) and home and entertainment (Home Assistant, Apple Music, Weather). Messaging is a separate tab for Teams, WhatsApp and iMessage (ConnectionGroup.Messaging, which Accounts excludes). X and YouTube are not offered yet: neither package has a connect flow to wrap (AI/GettingStartedAndConnections). Live on the control instance since its pods rolled (2026-10-11): ConnectionAccounts renders the three groups and their tiles. The rest of phase 3 (the Settings app rail, the launcher filter, AppFrontPage) is still open, see §8 |
| 3 Manage apps, instance Maps, Operations, Threads Settings | Plugins #3313, core #6445 (AppSettings context) |
merged; Manage apps renders on the control instance |
| 3 Administration is a host (Operations, AI providers) | Plugins #3325 | merged (2026-10-11) |
| 4 stop home writes, Start-only | Plugins #3316, Reinsurance #255 | merged and deployed. Since Plugins #3321 (merged and live 2026-10-11) the writers are split, as §8 describes. The TILE writers (HomeWrites.TilesEnabled / EduHomeWrites.TilesEnabled: tiles, pack sweep, tile heals, the course-tile stamp) follow the image default, and with core #6448 live they are off on both instances. The COPY writers (Enabled: acquisition copies, install requests, the lesson-render copy) stay on until an instance sets Home:AppSource = Directory explicitly, or phase 5 removes them |
| 4 writers follow the image default; Threads / Learning names | Plugins #3321 | merged and live on the control instance (Store/Core and Store/Installer compiled Ok 2026-10-11 03:15Z) |
4 phase-5 gate: installPaths is a validate error |
Plugins #3332 | merged |
4 courses declare startPaths |
Education #394 | waiting on a Plugins seal. The access denial on Edu/HowExercisesWork cleared on its own. What remains red is #394's own assertion: DataModeling records 22 roots against 21 startPaths. 22 is the legacy installPaths count, so the disposable mesh still runs a Store from before #3316: its upstream is the newest SEALED Plugins publication, and none sealed in the 40 platform sets before 3.1.10495. A re-run cannot change that; it goes green once a publication containing #3316 seals. Until #394 lands the courses are legacy packages (no startPaths), and what that means follows Home:AppSource: under Records their legacy plan is unchanged, so installPaths are still copied; under Directory a legacy plan narrows to the empty start plan and nothing is copied, Start included (InstallPlan.ForHomeWrites) — so #394 has to land for a course to be startable after the flip |
| 5 delete what Start does not need | — | not started |
| 6 clean existing homes (destructive) | Plugins #3331 | in review: the CleanHomes maintenance task (Store 1.29), with the scope of §8's phase-6 row. It deletes tiles, pristine app copies, app install requests, all-app install records and legacy workspace roots. Every delete is non-recursive, so the mesh refuses atomically while anything lies under a node; a root goes only when everything under it goes, which gives the phase-4 postcondition apart from nodes the plan lists as kept. It runs as System, because a global admin cannot read other users' homes. A dry run by default writes {request}/Plan. A real run needs Execute, the confirm token and no blocker: the writers off, every read complete, every record readable and no Start in flight. The plan goes to the maintainer before anything is deleted |
Leftovers found live: Parties and DeepSign are installed on the instances with app: true but are in no repo, so the directory lists them. Removing them is a destructive uninstall, done through Manage apps › Remove on the maintainer's word.
Gaps against the directive:
- The audience accepts users and groups only, because the access check has no role subject; "functions" are modelled as groups.
Google,ICloud,HomeAssistantandAppleMusicstill copy their connection node into the home throughstartPaths, until their readers move to{user}/_Connections.Signatureis a different migration:startPathscopies onlySignature/MySignatures, while its provider credentials live under{user}/_Signature/{provider}(SignatureSecrets). Those move to{user}/_Connectionsseparately.- X and YouTube have no connect flow, so Setup › Connect cannot offer them (§7 lists them).
- "Other email" and "Other calendar" can be saved and tested, but no assistant tool reads them yet.