Phase 4-Foundation's admin UI is the first instance of the plugin architecture: a content-addressed shareable subgraph the user installs via signed manifest envelope, that lets them create + edit + share workflows + composed views through a browser-tab or Tauri 2.x embedded-webview surface. This document is the user-facing walkthrough + the engineer-facing spec for the v0 surface.
Cross-references: D-4F-1 (FULL plugin manifest scope) + D-4F-4 (browser-wasm32 + Tauri 2.x as swappable Renderer backends) + CLAUDE.md #18 (app-level plugins are subgraphs) + CLAUDE.md #19 (engine extensions are Rust crates — benten-renderer-tauri).
Admin UI v0 is content-addressed, importable, shareable across Atriums. It ships AS the first app-level plugin per CLAUDE.md baked-in #18 — not as a separate "platform admin" surface with its own trust posture. Same plugin-DID + UCAN envelope + manifest-requires/shares machinery any other plugin uses.
This is intentional: by making admin UI v0 a plugin, the manifest schema gets dogfooded by the platform's most-trusted-but-still-app-level surface. If admin UI works through the plugin envelope, arbitrary third-party plugins will too.
What this rules out:
- Admin UI as engine extension per CLAUDE.md #19. Plugin-arch-r1-12 + plugin-arch-r1-17 surfaced this explicitly. Engine extensions are Rust crates compile-time linked; admin UI is JS+wasm in a browser tab or webview — it's app-level.
- A "system" trust posture distinct from regular plugins. Admin UI's privileges come from caps the user grants at install (per Layer 2 manifest envelope); not from being baked into the engine.
The renderer-backend impls (BrowserRender in benten-platform-foundation + TauriRenderer in benten-renderer-tauri) ARE engine extensions per CLAUDE.md #19. They sit at the engine boundary, are compile-time linked, are trusted because the user compiled the engine. Admin UI consumes them via the Renderer trait surface.
Per post-R1-triage ratification #4: 4-category navigation over the unified subgraph substrate (workflow ↔ plugin ↔ schema = same subgraph shape per D-4F-14):
| Category | Surface |
|---|---|
| Plugins | Plugin library subgraph; installed-versions timeline; active-version highlight; cap-grant management; multi-device sync visibility |
| Workflows | User-authored workflow handlers (composes from 12 primitives + 8 typed-field-Node labels via drag-drop primitive-picker) |
| Content Types | Schemas (the Phase 4-Foundation typed-field-Node subgraph shape per SCHEMA-DRIVEN-RENDERING.md vocabulary) |
| Views | Composed views (user picks anchor pattern + projection; emits via generalized IVM Algorithm B kernel per D-4F-2) |
The substrate is unified: all four categories are subgraphs at the engine layer. The user-facing IA distinguishes them by usage intent + lifecycle affordances; the underlying machinery is identical.
The "Content Types" category exposes a small, plain-English label set to users. Each label maps 1-to-1 to the engine-facing typed-field-Node vocabulary (8 labels per SCHEMA-DRIVEN-RENDERING.md §2.1) — there are no hidden modes or compound expansions at the user surface. Closes ux-r1-12 R1-triage disposition (a) FIX-NOW-INLINE; companion to GLOSSARY.md "FieldEnum / … / SchemaRoot" entry.
| User-facing label | Engine-facing vocabulary | Notes |
|---|---|---|
| Text | FieldScalar(text) |
Free-form string |
| Number | FieldScalar(int) or FieldScalar(float) |
Picker disambiguates at field-create time |
| Yes/No | FieldScalar(bool) |
Boolean toggle |
| Date | FieldScalar(timestamp-hlc) |
HLC-keyed for sync ordering |
| Link | FieldRef |
Cross-Node reference (typed by target schema) |
| Choice | FieldEnum |
Closed variant set |
| Variant | FieldUnion |
Open variant set (each carries its own shape) |
| List | FieldList |
Ordered collection of one type |
| Map | FieldMap |
Keyed collection (key-type + value-type) |
| Object | FieldObject |
Nested record |
| Schema | SchemaRoot |
Top-level container (the schema itself) |
See SCHEMA-DRIVEN-RENDERING.md §2 for the engine-facing vocabulary's full surface (5 labeled edges connecting these labels; object-to-field is implicit-via-recursion per D-4F-NEW-TYPED-FIELD-NODE-VOCAB). Cross-link: GLOSSARY.md "FieldEnum / FieldUnion / FieldList / FieldMap / FieldObject / FieldRef / FieldScalar / SchemaRoot" entry.
When the user receives a plugin (out-of-band content-addressed-share over Atriums in v0; decentralized registry → Phase 4-Meta), admin UI surfaces a manifest review dialog:
- Manifest display. Plain English: "<plugin_name> wants to ..."
- For each
requiresentry: plain-English description (e.g., "read your notes labeledtag/work", "use the time host-fn", "execute SANDBOX modules"). Private-namespace caps (shapeprivate:<plugin_did>:*) are visually distinguished from cross-plugin-shared caps: render them in a separate "Private to this plugin" section with the disclosure "This plugin uses sovereign storage scoped to its own DID — no other plugin can read or write here, regardless of anysharespolicy below." - For each
sharesentry: plain-English delegation policy (e.g., "may share read-access fornotes:workwith any AI assistant plugin you install later" / "will NOT delegate any caps to other plugins"). Private-namespace caps NEVER appear in this section by construction (cross-plugin delegation refused at the chain validator).
- For each
- Per-cap-grant decline. User can decline a single cap without aborting install (per ux-r1-2 BLOCKER closure). The plugin still installs; the declined cap is not granted; the plugin handles cap-absence gracefully (or fails at first use of the missing cap — handled by E_CAP_DENIED).
- Identity disclosure. Manifest review shows the four identity concepts (per D-4F-12):
- Content-CID + canonical bytes hash
- Peer-DID of original author + verification status against
requires_plugin_authorstrust list - Plugin-DID that the admin-UI caller mints + inserts into
PluginDidStorebefore invokinginstall_plugin(caller-mint-first contract per R6-FP-A; seedocs/PLUGIN-MANIFEST.md §3"Plugin-DID minting protocol" for the 4-step caller sequence +crates/benten-platform-foundation/src/plugin_lifecycle.rs::InstallParams::expected_plugin_didrustdoc for the API contract). The engine never mints; the caller-mint-first contract makes Ed25519-derives-DID-from-key the structural defense against plugin-DID substitution at install. - Confirmation that user-DID will sign the install record (binding
manifest_cid+consenting_user_did+plugin_did_bytes+nonce).
- Consent. User clicks "Install" → user-DID signs InstallRecord + grants UCANs for accepted caps → plugin enters library + active reference updates.
install_pluginStep 8 verifiesinstall_record.plugin_did == ctx.expected_plugin_didANDplugin_did_store.get(expected_plugin_did).is_some(); either mismatch surfaces a typedE_PLUGIN_INSTALL_RECORD_PLUGIN_DID_MISMATCHorE_PLUGIN_DID_HANDLE_NOT_PRE_INSERTEDto the admin-UI for plain-English display.
UX-acceptance (per ux-r1-2 BLOCKER closure): user can install a plugin in ≤3 clicks; user can decline a single cap without aborting install.
Per post-R1-triage ratification #8 (cap-change-triggered fresh consent):
- Silent within-lineage upgrade. If
requiresof the new version is a strict subset of installed manifest, upgrade applies silently. User sees a non-blocking "Plugin updated: <plugin_name> v1.2 → v1.3" toast. - Re-consent on cap growth. If
requiresGREW (any cap added or scope widened), full manifest review dialog fires for the new version. User can: (a) accept (re-install); (b) reject (stay on previous version); (c) decline specific new caps (per-cap-grant decline analogous to install flow). - Cross-fork merge. If user is on a fork (their version is NOT a descendant of mainline-v2), cross-fork = user-initiated merge through same consent flow. Admin UI surfaces a 3-way merge view: "Mainline-v2 has changed in ways your fork doesn't have; would you like to merge?" with "show what changed" diff per node + "keep my changes" vs "accept mainline" toggle.
- Pull-not-push notification. Atrium-mesh announce-event fires
E_PLUGIN_NEW_VERSION_AVAILABLEnotification (no auto-pull); admin UI shows in-app notification. User must explicitly trigger the update check.
When the user edits a Node in an installed plugin that the plugin doesn't expose as configurable, admin UI prompts:
"This creates a fork. Your edits will diverge from the plugin author's version."
Options:
- Commit fork. User's edits land on a new version-chain branch (per D-4F-14 DAG-shape versioning). Active reference updates to the fork tip.
- Revert. Discard the user's edits; remain on the unmodified plugin.
- Propose upstream. (Phase 4-Meta scope; v0 shows "coming in Phase 4-Meta" placeholder.)
"Plugins" tab houses the plugin library subgraph view:
- List of installed plugins (one row per plugin anchor).
- Per-plugin: name + active version + installed-versions timeline rendered as DAG-branching tree (per D-4F-14).
- "Switch to this version" button on any non-active version (updates active reference per ratification #2 — Loro Map per-device-keyed CURRENT).
- "Remove this version" button on any non-active version (keeps the version-chain history but drops the durable copy if no longer referenced).
- Active-version highlight + last-used timestamp.
"Devices" sub-panel under "Plugins":
- List of user's full peers (laptop / phone-OS-app / desktop per CLAUDE.md #17).
- Per-device last-sync-time + per-plugin sync status.
- Conflict indicator when concurrent writes occur (resolved by Loro CRDT + DAG-shape versioning + cap-change-triggered consent on cross-fork).
- "This version active on " annotations per ratification #2 (per-device-local CURRENT).
"Plugins → → Capabilities" panel:
- Every UCAN cap held by this plugin, listed.
- Per-cap revoke button.
- "What stopped working" preview before revoking (best-effort UX — the plugin may handle the revocation gracefully or fail at next use).
- Uninstall plugin button (couples to G24-D-FP-1 cascade-revoke): cascades revoke ALL caps held by this plugin + cascades plugin-DID's own downstream UCAN delegations + deletes private namespace + removes library entry + terminates live subscriptions. Confirmation dialog shows the cascade scope before commit.
Drag-drop primitive-picker:
- Picker palette: 12 primitive Node types + 8 typed-field-Node labels (per SCHEMA-DRIVEN-RENDERING.md vocabulary) as drag-targets.
- Canvas: drag primitives onto canvas; connect ports with wire-drag (port-to-port).
- Invalid-graph feedback: red-edge + inline-text on cycle / type-mismatch / cap-missing.
- Schema-driven form generation: forms generated from schema CIDs (via G23-A schema-compiler); user fills in field values; submit emits SubgraphSpec for
Engine::register_subgraph.
Composed-view creator:
- User picks anchor pattern (e.g.,
tag/work/*/version_current:<anchor_cid>) + projection (subset of fields to materialize). - View emits via G23-0a + G23-0b generalized IVM Algorithm B kernel (subgraph-shaped view definition per D-4F-2).
- Live preview via G23-B materializer with ≤200ms latency for primitive-vocab schemas; <1s for typical schema; degrade-to-on-blur if budget exceeded (ux-r1-16).
"Plugins → New → Meta-plugin":
- Pick sub-plugins from library (composition recursive per D-4F-14).
- Admin UI runs cycle-check at packaging time (per post-R1-triage Q2 ratification — meta-plugin cycle detection AS REJECTION; surfaces
E_PLUGIN_META_COMPOSITION_CYCLE_REJECTEDif cycle). - Merge requires/shares from sub-plugins into the meta-plugin manifest.
- User signs (user-DID) + commits.
When a plugin's peer-DID has a rotated key (per benten-id RotationLog + ratification #6 RotationLog MVP), admin UI surfaces a yellow-banner on the plugin's detail page:
"This plugin was signed by a key the publisher has rotated. Your installation is unaffected; you may want to check for an updated version signed by their new key."
Action button: "Check for new versions" (triggers Atrium pull for newer versions of this plugin).
"Plugins → → Share with Atrium friend":
- Generates a copy-paste handshake-token (CID + peer-DID + optional metadata).
- Recipient pastes into their admin UI → install flow fires.
- No registry / no DHT involved (per ratification #3 — decentralized registry → Phase 4-Meta).
"Welcome — install your first plugin":
- 2 starter-plugin pointers (
benten-starter-notes+benten-starter-tasks— TBD). - Skip-and-explore link.
Per Ben D-4F-4 + CLAUDE.md #17:
| Shape | Renderer backend | Trust posture | Use case |
|---|---|---|---|
| Browser tab (thin client) | BrowserRender (browser-wasm32) |
Full peer is user's own machine; browser tab is a view INTO it | Default v0; works everywhere with a browser |
| Tauri 2.x embedded-webview | TauriRenderer (in benten-renderer-tauri) |
Same as above + native shell can hold engine-level caps | Desktop dogfood; v0 ships minimal scope per post-triage Q4 (full T3 defenses → Phase 4-Meta) |
The 3-rung baked-in #17 defense extends to both renderer crates (br-r1-4 + br-r1-13): wasm32-objdump forbidden-prefix list updated; feature-graph-closure test extended; tauri-runtime-verso swap-readiness validated.
Per br-r1-3: admin UI v0 bundle ≤600KB gzipped with code-splitting strategy:
- Workflow editor (sub-feature; dynamic-import boundary)
- View creator (sub-feature; dynamic-import boundary)
- Plugin browser (sub-feature; dynamic-import boundary)
CI workflow admin-ui-v0-bundle-size.yml at G26-B enforces.
Per br-r1-11 (both browser-tab and embedded-webview deployment shapes):
script-src 'self' 'wasm-unsafe-eval'connect-src 'self' tauri://*style-src 'self'font-src 'self'default-src 'none'
Per br-r1-14: same DidKeyedSession + SessionToken contract as thin-client HTTP/fetch (G24-F), transport-swapped to in-process channel. Native Tauri shell holds engine-level capabilities; admin UI runs as plugin per CLAUDE.md #18; IPC method allowlist enforced in the native shell (IpcAllowlist::method_permitted).
Per br-r1-8: admin UI v0 wasm bundle must not reference DOM-only APIs / FormData / fetch-relative URLs. CI guard at G26-B WinterTC-compat check.
Per br-r1-7 grep-assert pin: admin UI v0 IndexedDB writes ONLY snapshot cache and manifest store. Blocks UCAN bytes / plugin secrets / direct sync state from landing in browser storage. CAPS stay at full peer; admin UI delegates via UCAN.
Per R6-FP-E (closes br-r6-r1-3 MAJOR full path-a — both halves shipped): the production caller for TauriRenderer + InProcessSessionBridge lives at tools/benten-admin-shell/ as the benten-admin-shell bin crate. The crate splits cleanly along four lines:
src/lib.rs—AdminShellStatecomposesTauriRenderer+InProcessSessionBridge+ the canonical admin-UI-v0 manifest envelope (admin_ui_v0_canonical_manifest).dispatch(IpcRequest) -> Result<IpcResponse, IpcError>is the exact code path a real Tauri 2.x command handler invokes one-to-one.src/main.rs— default-mode boot path prints a launch summary (IPC method-cap-binding map + locked CSP header). With thetauricargo feature enabled,tauri_boot::runwires the REAL Tauri 2.xBuilder::default().invoke_handler(...).run(generate_context!())pipeline; the three Tauri commands dispatch toAdminShellState::dispatchone-to-one. Feature is OFF by default to keep the workspaceCargo.locklight when only the IPC pipeline is exercised; the CI workflow.github/workflows/admin-shell-e2e.ymlruns the feature ON.tests/e2e_admin_shell_ipc.rs— substantive default-mode end-to-end pin (pim-2 §3.6b production-arm + observable-consequence + would-FAIL-if-no-op'd) exercises 9 paths through the integrator: happy-path dispatch for every allowlisted method, T3 rung 1 unknown-method reject, T3 rung 2 missing-cap reject, missing-session reject, origin-mismatch reject, session-expired reject, handshake replay reject, CSP header canonical pin, single-DidKeyedSession-instance reuse across handshake + dispatch.tests/e2e_webview_smoke.rs(undertaurifeature) — webview-driventauri-driverE2E via fantoccini-rustls WebDriver client: spawnstauri-driversubprocess, launches the admin-shell binary, drives a real Tauri command-invoke roundtrip through the embedded WebView2 / WKWebView / WebKit2GTK runtime, asserts CSP blocks aneval()execution attempt + the IPC channel returns the expected response. Linux substantive (WebKit2GTK under Xvfb); macOS build-only smoke per upstream Tauri WKWebView WebDriver limitation; Windows deferred perdocs/future/phase-4-backlog.md §3.6upstream-migration carry.webview-assets/—index.html+style.css+bootstrap.jsloaded by the embedded webview whentauri_boot::runfires. The<meta http-equiv="Content-Security-Policy">directive inindex.htmlis asserted byte-equivalent toWEBVIEW_CSP_HEADERbytests/webview_assets_csp_meta_matches_rust_constant.rs(defense-in-depth duplicate; T3 rung 3 stays armed even if the embedding context strips the server-set header).
Both halves of br-r6-r1-3 are CLOSED at HEAD per HARD RULE rule-12 (path-a-FULL). Only the Tauri upstream migration carries (gtk-rs → GTK4; proc-macro-error → proc-macro-error2; etc.) remain as docs/future/phase-4-backlog.md §3.6 advisory-window items.
Per arch-r1-16 + br-r1-9: Renderer trait at crates/benten-platform-foundation/src/materializer.rs::Renderer:
pub trait Renderer: Send + Sync {
fn render(&self, output: &MaterializerOutput) -> Result<(), RenderError>;
fn backend_name(&self) -> &'static str;
// ... transport-agnostic methods only;
// transport concerns (DOM mutation, IPC method invocation) live in concrete impls.
}
The unit-return Result<(), RenderError> reflects that the trait's render method drives side-effects into the renderer's owned transport (DOM writes for BrowserRender, Tauri IPC pushes for TauriRenderer); the materialized bytes belong to the concrete impl, not the trait return.
tauri-runtime-verso swap-readiness committed: methods are minimal + transport-agnostic; Verso is post-v1 swap target.
Per ux-r1-16: composed-view creator live-preview latency ≤200ms for primitive-vocab schemas; <1s for typical schema; degrade-to-on-blur if budget exceeded.
Per ux-r1-15: typed-ErrorCode → user-facing copy table at docs/ERROR-CATALOG.md; admin UI surfaces typed-error code in dev-mode + user-friendly copy in production. Companion at ERROR-CATALOG.md retense.
Per ux-r1-17 + ux-r1-18 (Phase-4-Meta carries; docs/future/phase-4-backlog.md §3.4):
- a11y baseline at v0: keyboard-nav + alt-text + sufficient-contrast. Full WCAG 2.1 AA certification at Phase 4-Meta.
- i18n at v0: hardcoded en-US strings via i18n-ready helper (e.g.,
t("install_plugin")returning hardcoded string in v0) so later extraction is mechanical. Full i18n scope deferred to Phase 4-Meta.
Per ux-r1-1 BLOCKER closure; each path carries observable user-moment + success state + failure state + acceptance pin at crates/benten-engine/tests/dogfood_path_<a..f>_ux_acceptance.rs:
(a) Create a workflow ─ build in ≤5 clicks via primitive-picker drag-drop. (b) Create a composed view ─ pick anchor + projection in ≤4 clicks; live preview ≤200ms. (c) Multi-device sync leg ─ change propagates ≤3s on loopback; "Devices" panel shows last-sync. (d) Revoke-cap mid-session ─ "Capability revoked" toast; affected view re-renders to redacted state (Node-granularity per ratification #7). (e) Install admin UI on 2nd device ─ install consent screen ≤3 clicks; per-cap decline supported. (f) Install a 2nd plugin ─ same flow as (e); install record signed by user-DID per D-4F-12.
Phase 4-Foundation G24-A wave-6 canary ships the engine-substrate arm of the admin UI v0 surface. The 4-category nav substrate + materializer-pipeline consumer wiring + dogfood-path engine-side arms are LIVE; the browser-side admin UI components (workflow editor drag-drop, view creator UI, install consent dialog) land at G24-B + G24-C wave-6b + G24-D wave-7. Dogfood UX arms — click-budgets + live-preview latency + multi-device-sync wall-clock + revoke toast surfacing — carry to wave-9 dogfood gate (named at docs/future/phase-4-backlog.md §2).
| Component | Location | Status |
|---|---|---|
| Admin UI v0 module | crates/benten-platform-foundation/src/admin_ui_v0/mod.rs |
LIVE (524 LOC) |
4-category nav constants (NAV_CATEGORIES, Category) |
same module | LIVE |
build_admin_ui_v0_subgraph + build_category_route_subgraph |
same module | LIVE — composes from 12 primitives only |
render_category_content_allow_all + render_category_content |
same module | LIVE — Materializer trait consumer |
Subscriber::for_category seam |
same module | LIVE — patterns route to on_change_as_with_cursor |
| Engine→Materializer adapter | crates/benten-platform-foundation/tests/common/admin_ui_v0_engine_adapter.rs |
LIVE — test-side adapter shape |
| Defense-in-depth SANDBOX banned-host-fn pin (mr-4) | crates/benten-platform-foundation/tests/materializer_defense_in_depth_rejects_banned_sandbox_host_fn_for_handcoded_spec.rs |
4 sub-tests pass |
| End-to-end dual-gate pin (mr-3) + propagation pin (mr-5) + invocation-count pin (mr-8) | crates/benten-platform-foundation/tests/admin_ui_v0_materializer_reactive_update_propagates_through_engine_on_change_as_with_cursor.rs |
4 sub-tests pass |
| Engine-side shell pins (7) | crates/benten-engine/tests/admin_ui_v0_*.rs |
Un-ignored + substantively LIVE |
| Dogfood-path engine-substrate arms (6) | crates/benten-engine/tests/dogfood_path_<a..f>_ux_acceptance.rs |
Un-ignored + substantively LIVE |
- Click-counter test harness + live-DOM workflow editor → G24-B wave-6b
- Composed-view creator live-preview latency p50/p99 measurement → G24-C wave-6b
- Multi-device sync ≤3s loopback round-trip + Devices sub-panel → wave-9 dogfood gate
- "Capability revoked" user-visible toast UX → G24-C wave-6b + wave-9 dogfood gate
- ≤3-click install-consent flow + plain-English manifest display → G24-D wave-7 + wave-9 dogfood gate
- User-DID install-record signing UX → G24-D wave-7 + wave-9 dogfood gate
- CLAUDE.md baked-in #18 — three-layer consent (canonical)
- CLAUDE.md baked-in #19 — engine extensions;
benten-renderer-tauriis a 12th crate engine extension - CLAUDE.md baked-in #17 — full peer vs thin compute surface vs embedded-webview deployment shapes
PLUGIN-MANIFEST.md— manifest schema admin UI reviews + signsSCHEMA-DRIVEN-RENDERING.md— workflow editor's form generation consumes thisARCHITECTURE.md§"Plugins and engine extensions" — workspace shape + crate boundariesSECURITY-POSTURE.md"Plugin trust model" — security narrativeERROR-CATALOG.md— admin UI surfaces typed errors with user-facing copy
(Phase-4-Foundation companion doc lands at G24-A canary per feedback_post_fix_doc_coupling_preflight.md §3.5b HARDENED + meth-r1-7 companion-with-canary discipline.)