Apps live on the instance, not in your home

Maintainer, 2026-10-10:

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:

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)

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):

  1. 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 as Space), filtered to content.app == true and bounded by an explicit limit. It goes through IMeshNodeStreamCache. It is the same for every viewer.

  2. 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.

  3. Plus the platform entries every viewer has.

    • Settings (/{user}/Settings) for everyone.
    • Administration (Admin) when IsGlobalAdmin.
    • Inbox.

    These are not records, so SeedDefaultAppsLogonAction, SeedInboxAppLogonAction, InstanceAppTileLogonAction and the icon-adoption logon actions are retired.

  4. 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.

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.

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):

  1. 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.
  2. What you can do: three cards, each a sentence and a deep link.
  3. Ask the agent: two or three example prompts, which start a thread through hub.StartThread.
  4. Sections: for suite apps (Personal, Games, Social Media, Reinsurance, Examples, Learning), a card grid of the hosted packages, from the existing ExtensionShelf query.
  5. 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:

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:

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: