MGM GUI as a plugin: contract v0 and Gridtop (proposal, Nov 2026)
Status: proposal, 6 November 2026. Owner: Aaron Tushabe. Delivery order decided 6 Nov 2026: P0 first, then P1. The other decisions below are still open.
Interactive version with phone, tablet and laptop mockups: https://claude.ai/artifact/BJDj9UTEykuEMTtYPG1yoJ (private; ask Aaron to share it). The full written proposal (
proposal-final.md) and the type-checked contract (contract.ts) are attached to this page.
In one paragraph
MGM's interface came from MBE, which was only ever a metering and billing engine. MGM is becoming an agentic Microgrid OS built on Cordis, where everything is a plugin. This proposal makes the interface a plugin too. Capability plugins (community management, metering, billing, payments, partners' plugins) describe what exists and what a person may do, as typed views and actions behind MGM's existing gated routes. A GUI plugin decides only how that looks. MGM ships one default GUI, Gridtop, and a deliberately plain second GUI, MGM Lite, which proves that the interface can be swapped. The assistant is one more optional plugin: off by default, it reads what views allow and proposes actions that a person submits.
Why redesign
A survey of the code and the monthly work turned up five things:
- The only OS-like screen is unreachable. The microgrid overview (edge health, open period, top households, consumption calendar, activity) exists, but
/microgrids/[id]redirects to the households list. - No live telemetry is released. The OpenEMS client can read current values, but the routes for them are not in the release list.
- Mobile does not work. The shell is a fixed sidebar with no collapsible navigation, no app manifest and very few responsive styles.
- The monthly close is long. Getting to Generate takes about 8 clicks; sending bills then costs 3 to 5 clicks per household, pasted into WhatsApp by hand.
- Machine actors already exist. The Pesapal IPN, the reconcile worker and customerapp are authenticated, gated and audited, which is the template the assistant follows.
Who uses MGM, on what
This is the reasoning behind Gridtop that has no home in the public repository. Update it as phases ship and as pilot feedback comes in.
| Who | Device and setting | What the GUI must do |
|---|---|---|
| Organization manager (month-end) | A 1366 × 768 budget laptop; Excel, the URA portal and CSV/PDF downloads | Dense tables, about 13 rows visible; columns that copy as bare figures for the URA paste; the seven-step close in one place |
| Organization manager (daily) | Laptop or phone | A "Needs you" queue: offline edges, periods to close, payments to reconcile |
| Sending bills | A phone, because bills go out over WhatsApp | One share per household with the PDF and pay link; progress kept per phone |
| Field technician or meter reader | A 360 px Android over weak 3G, outdoors | A reading form with 16 px inputs and 44 px targets; sunlight-readable contrast |
| Households | A phone, no login | The pay link and payment receipt pages: small, static, readable |
| NFE platform staff | Laptop | Organizations, users and plugins; rare jobs |
Connectivity budget. Gridtop is measured on a throttled 3G profile (1.6 Mbps, 300 ms round trip): about 5 s cold and 1.5 s per navigation on a low-end Android. Shell JavaScript is capped at 40 KB gzipped above the framework, with one variable font of at most 50 KB, which is skipped under Save-Data. The payment receipt page is at most 15 KB with no JavaScript. MGM Lite targets 2G with system fonts and no icons.
Principles
- Describe, don't draw. Capability plugins contribute data, never pixels, so each plugin and each GUI is written once (N+M, not N×M).
- The server and the host hold every rule. Role, gate, release, RLS and the close digest are enforced by routes and the database; confirmation steps are host-owned, code-owned and accept only real input, so a GUI frames them and has no way to submit without them.
- URLs belong to capabilities and the host. GUIs own no paths and no current URL moves, so SMS and invoice links, bookmarks, guides and assistant hand-offs survive any GUI switch.
- Deterministic first, AI optional. Needs you, ⌘K, signals and suggestions work with the assistant off; the assistant adds explanation and drafting, never authority.
- Proposed is dashed, recorded is solid. Anything a watcher or the assistant prepared looks like a draft until a person submits it, which keeps accountability visible.
- A quiet board. Colour means abnormal or flowing, every live value shows its age, derived values are marked, so the screen reads in sunlight and can be trusted at night.
- Design for a 1366×768 laptop and a $40 Android on 3G. At most two bands above content, 44px touch targets, and byte budgets the Build job enforces.
- Small PRs that ship and delete. Each phase lands in reviewable slices and removes what it replaces, and no page loses navigation before it migrates.
Architecture
| Layer | What it holds | Examples |
|---|---|---|
| GUI plugins | A reviewed, compiled-in React package plus metadata: Shell, a synchronous generic View, optional bespoke renderers and forms with declared versions, an Overlay frame for host steps, a Loading skeleton, a server-only PublicFrame, a stylesheet declared in metadata. No fetch, env, Supabase, composers, Server Actions, next/* or URLs | Gridtop (default, MGM_GUI=gridtop), MGM Lite (MGM_GUI=lite, 2G phones, P3), later a thread-style GUI |
| Host (core; contract, host, conformance code-owned) | Session checks, the manifest builder, the view resolver and /api/gui dispatcher (released, owned, gated, no-store), strict parse of every boundary output, targeted-action merge, the action host that runs forms and all four confirmation levels and the only invoke, auth pages, the client kit | src/app/(gui)/layout.tsx, src/app/(gui)/[...path]/page.tsx, GET /api/gui/view|search|signals, POST /api/gui/prepare (read), src/lib/gui/host/ConfirmStep, kit.act, kit.Link, KitView, RawView |
| Cordis root | One long-lived context per server process: mgmSurface (contribute, zod-parsed at boot; install, range-checked), mgmCatalog, mgmGate, provider registries | SurfacePlugin providing mgmSurface, billing-surface and sms-reminders-surface platform plugins, gui-gridtop and gui-lite install plugins, meteringProviders, paymentProviderRegistry, llmProviders |
| Capability plugins | Routes, one body schema per action in schemas.ts, composers, capabilities, and one server-only surface contribution: nav, views with declared keys and segment kinds, actions with bind/risk/humanOnly/confirm, signals, status, search, own entity kinds. No React, no CSS | billing.periods, billing.period-review (ext billing.period-review@1), billing.close-checklist (flow), billing.close-period (ceremony), metering.site-day, metering.live, payments.queue, sms-reminders.send |
| Assistant plugin (optional, off by default) | Threads, events and pseudonym tables written only by a SECURITY DEFINER function, a gated step route, a feed view with compose and stop, a status item, read tools from views marked agent:'read' and propose tools from ProposableActions | assistant.thread (feed), assistant.proposal (record), view:billing.period-review, propose:billing.create-period, llmProviders openai-compatible and anthropic |
| Provider plugins | Vendor adapters behind registries; nothing in a GUI or the assistant reaches them directly | OpenEMS metering, fixture metering (only with MGM_METERING_FIXTURES, values labelled synthetic), Pesapal, OpenAI-compatible (OpenAI, DeepSeek, OpenRouter, vLLM, Ollama, llama.cpp), Anthropic |
The GUI contract, v0
Two TypeScript files: src/lib/gui/contract.ts is client-safe and is all a GUI author reads; src/lib/gui/surface.ts is server-only and is what a capability contributes. It type-checks with 0 errors in strict mode against the repository's own types, together with three worked examples and twelve deliberately wrong cases that the compiler rejects. Its status is "v0, experimental" until Gridtop and MGM Lite both pass conformance; then it becomes 1.0.
Five view kinds: collection, record, metrics, feed and flow. Values are formatted on the server (money, energy, power, dates, status), so any GUI can simply print them, and bare columns paste into URA's portal as 4,216,800.
Four confirmation levels: none, confirm, typed and ceremony. The platform owns the confirmation steps; a GUI only frames them. Routes check again: the close route refuses without the reviewed-totals confirmation.
What a capability author does
- Add catalog entries in bundled.ts (code-owned): routes, pages with the entity kind of each segment, and a short id alias (org for organization-directory, community for community-management, otherwise the plugin name); add released-routes entries for new paths and a method-aware entry for each DELETE, recorded as a release decision in the same PR.
- Write src/lib/
/schemas.ts with one zod body schema per action, and make each route parse with it in the same PR; an action without a schema cannot be contributed. - Write surface.ts exporting a Contribution: views with path, params (segment → kind), entity segment, declares (every emitted key), cost, optional agent:'read', live hints, fixtures and a loader using the caller's RLS client and read-only compositions; actions with route, method, bind (path and body fields filled from the entity), schema and inputVersion, risk, required humanOnly, confirm level and prepare where needed (typed, ceremony, external); deterministic signals; optional status, search and own entity kinds.
- Register a five-line platform plugin that calls ctx.effect(() => ctx.mgmSurface.contribute(SURFACE)), shaped like metering/providers-plugin.ts; unloading it removes everything it added.
- Optionally publish a versioned ext schema (e.g. billing.period-review@1), strict objects all the way down, for bespoke renderers; a changed shape becomes @2.
- Run gui-surface.test.ts, the credential canary test and the GUI × view matrix; they name anything missing. No React, no CSS and no change to any GUI.
What a GUI author must do
- Export a GuiPlugin (metadata plus components) from a module with no 'use client' and no CSS import, plus a GuiMeta in meta.ts; one identifier is the map key, GuiMeta.name, the Cordis plugin name and the MGM_GUI value.
- Declare a contract range (e.g. ^0.1) that includes GUI_CONTRACT, a stylesheet source in GuiMeta.styles, and every npm dependency, each on the reviewed list in gui-boundary.test.ts.
- Shell: an organization switcher when there is more than one organization, nav grouped Operate/Money/Configure/System with a context picker when a needed entity is missing (ViewState.href and choices), the trail, the Needs-you count from useSignals, every status item, kit.SignOut and a main landmark.
- View: render all five kinds synchronously: print every Value.text, honour copy:'bare' and column priority, render every ActionRef as a control or show its reason, mark provenance as Proposed (dashed, accessible), render links, let the empty state own the create action, put totals below rows, show notices. Re-exporting kit.KitView satisfies this.
- Write only through kit.act(ref) and open links only through kit.open(link); use kit.Link, navigate, refresh and usePathname instead of next/*.
- Import only @/lib/gui/contract, @/lib/gui/kit, react, react-dom, its own files and reviewed npm packages; no fetch/XHR/EventSource/WebSocket/sendBeacon, process.env, Supabase, runtime, composers, capability modules, 'use server', next/headers, server-only,
- Pass describeGuiConformance(gui) in a file the gui Vitest project collects, and the GUI × view matrix with the injected acme.poke targeted action.
What a GUI author may do
- Bespoke renderers keyed by view id, declaring the ext versions they read (fallback to View on a mismatch), e.g. a CopyTable for billing.period-review or the energy-beside-money Live board.
- Bespoke forms keyed by action id for none and confirm actions, declaring the input versions they read (fallback to the kit's ActionForm), e.g. a household wizard or a tier editor.
- An Overlay that frames the host's forms and confirmation steps as a dialog, a snapping sheet or a page, without altering their content.
- A Loading skeleton; otherwise the host's neutral one.
- A PublicFrame around the host's auth pages, as a server component with no client imports.
- Any layout grammar: docks, tiles, an inspector, a command palette, keyboard maps, PWA chrome, pop-out windows.
- GUI-owned interface state in ui.* query parameters through kit.useUiParam (never seen by loaders).
- Its own look entirely: tokens, type, density and icons mapped from the contract's Icon names.
What every installation can rely on
- Every GUI write reaches a route that the surface test proves exists, is owned, released and gated (or exempt with a PaymentExemption); the host adds only three GET routes and a prepare POST classified as a read.
- Confirmation steps are host-owned and code-owned, accept only trusted input, and are enforced again by the route (428 without a confirmation, 409 when stale) and, for close, by the database.
- The close function becomes the single path to a closed period: it recomputes the digest of the reviewed totals and the required acknowledgements (including households not billed) inside the write, with one signature and service_role covered by the same rule.
- Only declared keys leave the server: the host strict-parses views, embeds, ext, signals, status, search, ceremonies and prefill, and a database-backed canary test checks every contributed output.
- Every entity in a path resolves to the same organization, loaders get their own RLS client and never run without an organization unless installation-scoped, and the manifest carries one organization.
- Deletes are contributed human-only actions with typed confirmation, served by gated DELETE routes that compare the typed name on the server, each new method recorded as a release decision.
- Releases stay explicit: the dispatcher refuses unreleased or unowned hrefs and any /api/* path, and the manifest drops unreleased entries.
- Session checks run on every new path (layout, catch-all, /api/gui/*, assistant steps), and each new module is listed in the import-graph test.
- Human-only, typed and ceremony actions never become agent tools (by type, by boot-time parse and by run-time re-filter), and a view is agent-readable only when marked agent:'read'.
- No URL moves, so SMS and invoice links, bookmarks and guides survive any GUI switch.
- Conformance and the GUI × view matrix make every contributed view render in every installed GUI, or CI fails; byte budgets fail Build on regression.
Gridtop, the default GUI
| Window | Layout |
|---|---|
| Compact, 360–599px (phones) | Two bands: a 48px title bar (context ▾, ⌕ search/ask, ▲ Needs-you count, ◇ assistant) and a single 44px window bar (view title as a menu listing attached tabs, plus actions) that collapses on scroll; a 56px bottom dock with three pinned apps and More. Collections become cards by priority-1 columns; records and the inspector push full screens with canonical URLs (Android Back works); forms, confirmation steps and the assistant are bottom sheets snapping to peek/half/full by drag or buttons, decision bar above the safe area; typed confirmations open full screen. At 360×640 about 436px of content (about four bill cards), 480px with the window bar collapsed. |
| Medium, 600–1279px (tablets, small laptops) | A 72px rail dock with labels under icons, groups separated by rules; the title bar; the window bar as one band of two 36px rows (title and actions, then tabs); one main window; the inspector is a right sheet, docked at 360px from 1024px. Tables scroll sideways with priority-3 columns hidden. |
| Expanded, 1280–1599px (the 1366×768 laptop) | A 64px dock, title bar, two-row window bar, a 24px status bar and fixed tiles: the main window plus one 360px column where Inspector and Assistant are tabs (main window 942px at 1366px); ⌘. collapses the column for the full CopyTable. About 485px of content in a browser tab (about 13 rows of 32px); the status bar hides below 800px of viewport height (14 rows). |
| Expanded wide, 1600px and up | Three fixed, non-overlapping tiles: main window, inspector pinned at 360px (the selected record's attached views) and assistant pinned at 400px (the status item's docked feed); dock, title bar, window bar and status bar as at 1280px. |
| Installed PWA (desktop and Android) | display_override window-controls-overlay then standalone: the title bar moves into the OS title area (about 18 table rows at 1366×768); ⧉ Pop out opens the canonical URL with its ui.* state in a real OS window via window.open with noopener; no in-app window manager; a BroadcastChannel nudges sibling windows to refresh tray counts; app badge shows the your-move count; shortcuts use /go/ |
Dock groups: Operate (Today, Sites, Live, Households, Readings, Devices); Money (Billing, Payments); Configure (Tariff, Data source, Invoice branding, Payment accounts); System (Plugins, Users, Profile, Organizations).
⌘K palette: / for commands, @ for places and households, # for periods and invoices, plain text to search, and Ask only when the assistant is on. Commands never run directly: an action still goes through its own confirmation.
Key screens
- Today · Needs you. Home: a ranked queue of what needs a person, with live and money tiles attached by the metering and billing plugins; no LLM on load.
- Microgrid Live: energy beside money. The quiet-board live view of one microgrid: energy flow, money for the open period, edge health, with every value aged and derived values marked.
- Billing · October 2026 review with the close rail. Aaron's month-end table: review bills, copy bare columns into URA's portal, and walk the seven-step close.
- Close October 2026 (the ceremony). The host-owned confirmation step that is the only way a period becomes closed.
- Send October bills. Hand each household its bill and pay link from a phone, one person-initiated share at a time.
- Readings: record a reading in the field. The field visit's main write on a 360px Android over weak 3G.
- Devices: edges and discovery. See each edge's devices and register what discovery found, with suggestions that work without the assistant.
- Payments: reconciliation queue. Organization-wide queue of payments that need a person, at /payments?org=.
- System › Plugins with the pause preview and Interface. Turn capability plugins on or off per organization, seeing exactly what pauses, and see which GUI the installation runs.
- Assistant tile with a proposal. Optional, default-off assistant hosted by the GUI: reads marked views, proposes actions a person submits.
Visual language
- One variable Inter with tabular figures, plus the system monospace. Type scale: 12 caption, 13 dense table, 15 body, 16 inputs, 18 title, 22 headline, 32 KPI.
- Colour tokens in OKLCH for light and dark. Status hues stay fixed (success 152, warning 78, danger 27) so a rebrand never touches safety colours. The brand hue is safe from 200 to 340.
- Energy colours (solar, battery, grid, load) always come with a glyph. Derived values are marked ≈ with a dashed line; stale values are hatched with their age.
- Proposed is dashed, recorded is solid.
The assistant
- Optional and off by default: a non-core plugin with defaultEnabled false plus a code-owned migration; while off, no status item, Ask, tile, tools or JavaScript reach any GUI.
- Provider registry llmProviders with two server-only fetch adapters (openai-compatible for OpenAI, DeepSeek, OpenRouter, vLLM, Ollama, llama.cpp; anthropic); operator-supplied key as installation settings; the base URL is never an organization setting; per-organization keys under the DEK later, then llmProviders joins PRIVILEGED_REGISTRIES.
- Runs as the signed-in person: each POST /api/assistant/threads/[id]/steps is gated on 'assistant' against the thread's organization, checks the caller is the thread's author, composes afresh so getUser() runs every step, makes one model call and at most three tool calls.
- Hosted by any GUI through the generic feed: compose posts text and the current view as context, ActionResult.next continues, stop ends; kit.useFeed pumps the loop; Rich text carries grounded figures and unverified marks.
- Organization binding: every tool call answers 403 unless it resolves to the thread's organization; proposal params must resolve there too; reuse re-serves only the same user's stored event after authorization.
- Deny-by-default read tools: only views marked agent:'read' become view:
; refused for Configure and System views and credential entities; vendor reads need a person's Run event recorded on the server. - Propose tools only from ProposableActions without writeOnly fields, re-filtered at run time; external inputs carry no free text or recipient; human-only, typed and ceremony actions are never tools, at most an intent-URL hand-off.
- Proposals are dashed ActionRefs a person reviews through kit.act and the normal confirmation; a shared server helper records a 'submitted' event only after checking the proposal's organization, action, entity and state; audit tables stay human.
- Audit: authenticated has SELECT only; every event, pseudonym and budget row is written by a SECURITY DEFINER function that derives org_id, actor and seq; append-only except the retention function; database tests prove direct INSERT is refused.
- Privacy: threads visible to their author and super admins; names, account numbers, meter ids, addresses, emails and phone numbers pseudonymised per thread on the server and de-pseudonymised before the feed leaves it; 180-day retention and a human-only erasure path.
- Cost and latency for Uganda: small or self-hosted models, priority-1 columns and 20 rows, stable prompts for prefix caching, monthly and per-turn budgets, no token streaming in v0, a ≤ 25 KB chunk loaded on open, functions in bom1.
- MCP later only with MGM-issued, organization-bound, read-and-propose, revocable tokens, never a Supabase access token; explicitly released, session-checked and rate-limited.
Delivery plan
At about half time for one person, P0 to P4 is 26 to 33 weeks. Each phase deletes what it replaces, and no page loses navigation before it migrates.
| Phase | When | Deliverables | Deletes |
|---|---|---|---|
| P0 Visible fixes | 0.1 under both options; ~2–3 half-time weeks | Tokens into @theme, one variable Inter, type scale, dark and energy tokens; Token and type guards; raw colours fixed on login, merchant settings and payment history; Sideways-scrolling tables, dialog max-height, sheets on compact, 44px coarse targets; App manifest, icons, metadata and a styled 'not in this release' page; /payment/callback as a static route.ts receipt (≤ 15 KB, no JavaScript); plain HTML states on /pay; /microgrids/[id] redirects to the Operations day view once the operations PRs merge; A PR template with a 360px check | tailwind.config.ts, Inter Tight and JetBrains Mono, payment-shell.tsx, review-bills.tsx (after csvCell moves), the tenants page, the payment callback page |
| P1 Contract and shell | 0.1 only under option B, otherwise first in 0.2; ~4–5 half-time weeks | contract.ts, surface.ts, fmt.ts, mgmSurface with its boot-time parse, GUI install plugins; GateTarget gains household and paymentAttempt; Host: manifest, resolver, dispatcher, strict parse, merge, action host with ConfirmStep and invoke, (gui) layout and pages, /api/gui/, kit, KitView, RawView, ActionForm; (dashboard) renamed (gui); legacy nav entries and legacyPage() for every unmigrated page; Gridtop Shell v0 (dock, context path, tray, bottom dock and sheets, ⌘K, pop-out, /go/); GUI stylesheet build; MGM_GUI check; CSP; Catalog pages and aliases for billing, metering, payments; schemas.ts for the pilot's actions; Pilot: the operations day view as metering.site-day with billing.savings attached; fixture provider getSiteSamples behind MGM_METERING_FIXTURES; gui Vitest project, conformance kit, matrix, host tests, gui-boundary, gui-surface, installed-guis, canary test, budgets in Build, 360px Playwright smoke | sidebar-nav*, home quick-links, (dashboard)/layout.tsx, the operations legacy page; per-page sub-navs, TabNav and HierarchyNav stay until each page migrates |
| P2 The money path | 0.2; ~5–7 half-time weeks | The close confirmation in one commit: migration (digest function, single close function, status trigger, grants), route 428/409, the legacy close button switched to the host ceremony, database and component tests, regenerated types; src/lib/billing/schemas.ts with the billing routes parsing it; Billing views: periods, period review (CopyTable renderer), history, close checklist, send queue; every row of the BillingTable parity table contributed; Today with signals from billing, payments and metering and plugin-attached tiles; app badge; Deletes for the screens it migrates under the general rule, with method-aware release entries | BillingTable.tsx (1,256 lines) and BillingPeriodList.tsx once the parity table is complete; legacy billing pages and their sub-navs; the org-card home |
| P3 Live, payments, the rest, gui-lite | 0.3; ~10–12 half-time weeks | getLiveSnapshot on the OpenEMS and fixture providers; metering.live; /payments?org= reconcile queue; Households (wizard form), household record, readings, devices and discovery, data source, tariff (tier and grid-tariff editors); Settings, users, plugins, organizations, communities, invoice branding, payment accounts, each capability on its schemas.ts; gui-lite; contract 1.0 once Gridtop and gui-lite both pass conformance and the matrix | All remaining legacy pages, sub-navs and HierarchyNav; the wizard modal shell; openems-backend-shell and invoice-shell as page shells; raw payments components; globals.css beyond host pages |
| P4 The assistant | 0.3 or later; ~5–6 half-time weeks | Plugin with default-off migration, llmProviders and two adapters; Threads, events and pseudonym tables with SECURITY DEFINER writer and purge functions; Read tools from agent:'read' views, the feed and status item, Explain; Propose tools, proposal view and lifecycle, the submitted helper, grounding, budgets; Tests: no tool from human-only or unmarked views, organization binding, direct INSERT refused, step route gated | Nothing (a new plugin) |
| P5 Field and desktop | Later (after 0.3) | Service worker caching only the static shell and an offline page, never RSC payloads, /api/gui/* or household payment paths; caches cleared on sign-out; Offline reading drafts a person submits online through the normal route; money never queued; Window Controls Overlay polish, Realtime payments on desktop, per-person GUI choice; mcp-server with MGM-issued tokens; a thread-style GUI explored | The desktop 30-second payment polling hint once Realtime replaces it |
Decisions
| # | Question | Recommendation | Status |
|---|---|---|---|
| 1 | What enters 0.1? | Option A: P0 only in 0.1 while #35 is open; Option B (P0 and P1, with Hillary's agreement that #56 documents the contract as an experimental appendix) only if M2 closes early. | Order decided 6 Nov: P0 first, then P1. Whether P1 counts toward 0.1 is open. |
| 2 | When does the close confirmation ship? | As the first commit of P2 in 0.2, with the legacy close button switched to the host's ceremony in the same commit. | Open |
| 3 | What is the contract's status? | 'MGM GUI contract v0, experimental', recorded as ADR-005 worded by what it adds; 1.0 when Gridtop and gui-lite both pass. | Open |
| 4 | Do any URLs move? | No; only add …/close, …/send, …/readings, /payments?org=, /assistant/* and /go/*. | Open |
| 5 | Which GUI is the default? | Gridtop; an unset MGM_GUI means gridtop; a per-person choice later. | Open |
| 6 | What joins CODEOWNERS? | /src/lib/gui/contract.ts, /src/lib/gui/surface.ts, /src/lib/gui/host/, /src/lib/gui/conformance/, /src/gui/meta.ts, /src/gui/installed.ts, /src/lib/assistant/ and the guard tests' files, each in the PR that creates it; GUI packages and kit components stay unlisted. | Open |
| 7 | Who owns confirmation steps? | The host: ConfirmStep in code-owned src/lib/gui/host, trusted input only; GUIs frame it through Overlay; bespoke forms only for none and confirm actions. | Open |
| 8 | Which actions are human-only? | Proposable: create period, generate bills, open-period regeneration, tariff drafts, refresh payment status, register devices, create payment link, send reminder. Human-only: close, closed-period regeneration, manual usage, reconcile and adjustments, manual payment status, deletes, credentials, roles, plugin toggles, tokens, assistant erasure. | Open |
| 9 | Where is send progress kept? | Per phone in P2; a stored send record (table, gated route, code-owned migration) only if the laptop must see it. | Open |
| 10 | How is the assistant configured and what data does it see? | Installation-level provider settings; names pseudonymised by default, phone numbers never sent; threads visible to their author and super admins; 180-day retention. | Open |
| 11 | Is proof of presence at close needed now? | Digest, acknowledgements and trusted input now; defer an optional AAL2 requirement via Supabase MFA. | Open |
| 12 | Which type and brand range? | One variable Inter plus system mono; brand-hue safe range 200–340. | Open |
| 13 | Where does the reasoning live? | The contract in ADR-005 (and #56's appendix under option B); the operator reasoning behind Gridtop on an NFE Library page written with the NFE Library skill and updated as phases ship. | Open |
Risks
| Risk | Mitigation |
|---|---|
| The redesign takes hours from 0.1 | Option A keeps 0.1 to P0 with no contract and no migrations; P2 and its migration are 0.2, after #48's baseline and #58's acceptance. |
| Code-owner review becomes a bottleneck | Batch catalog, root and released-route changes per phase; code-own only the contract, host, conformance and assistant paths. |
| A page loses navigation mid-migration | Legacy nav entries and legacyPage(); each per-page nav is deleted only when its page migrates. |
| Month-end tools are lost when BillingTable goes | A parity table (legacy control → action, route, humanOnly, confirm, form); the legacy page stays mounted until every row is contributed. |
| Generic rendering drifts back to an admin template | Today, Live, the period review and the close are bespoke renderers from the start; generic kinds are the floor. |
| Bespoke renderers and forms couple to plugin internals | Versioned ext and input schemas with declared reads; a mismatch falls back to the generic renderer or ActionForm; the matrix catches the rest. |
| Action schemas drift from route parsing | One schemas.ts per capability imported by the route and the surface; gui-surface checks the import. |
| A GUI package carries something it should not | Import allowlist with a reviewed npm list, the boundary scan, host-owned confirmation steps with trusted-input checks, and a CSP. |
| The catch-all adds latency on 3G | Signals out of the manifest, one organization per manifest, loaders on their own clients, enforced budgets, functions in bom1. |
| A mistyped MGM_GUI stops the whole process, payments included | Release verification and #55's guides check it before promotion; CI already fails a contract-range mismatch. |
| One GUI's CSS leaks into another | Stylesheets declared in metadata and linked only for the active GUI; CSS imports banned in GUI packages. |
| Live data is slow or costly (OpenEMS in Europe, Hobby limits) | 10 s cache after authorization, polling only while visible, back-off on constrained connections, fixture provider for demos. |
| Synthetic readings reach a real installation | The fixture provider registers only under MGM_METERING_FIXTURES, and every Value and bill from it is labelled synthetic. |
| Web Share behaves differently per phone and target | Prefetch with canShare, the wa.me alternative, and a real-device test before P2 closes. |
| jsdom conformance cannot see a 360px overflow | Source guards for overflow and target size, the PR template, and a 360px Playwright smoke test. |
| Assistant hazards: invented numbers, personal data, injected text, provider churn, cost | Grounded Values and server-marked unverified figures, server-side pseudonyms, deny-by-default read tools, no write path, the llmProviders seam, budgets and small models. |
| Public artifacts describe weaknesses | Issues, PRs, commits and the ADR describe this work only by what it adds, e.g. 'the close function becomes the single path to a closed period and checks the reviewed totals'. |
How the proposal was made
Five interface concepts (conversation-first, live operations console, desktop environment, inbox and approvals, pragmatic evolution) and three competing contract designs were scored by three judges: GUI-author experience, architecture and safety, and operator vision. The strongest parts were merged into one proposal, which three critics then attacked: security and invariants, an outside GUI author, and operators and codebase feasibility. All 38 must-fix findings were applied before the contract was type-checked.
Related
- Target Architecture (this chapter)
- Towards a Framework for Autonomous Microgrids
- Launch tracker: https://github.com/Nearly-Free-Energy/mgm/issues/30
Last reviewed 6 November 2026.
No comments to display
No comments to display