Skip to content

SDF control-plane app — Tauri+Nuxt operator surface

State: open/accepted

  • solutions/ontological/desktop is the software-factory operator surface: the dashboard/observer component D-VSLI-distributed-work-runner-architecture reserves, built as a Tauri 2 + Nuxt 4 app with a first-class browser mode, scaffolded from template-tauri-nuxt by replaying the proven pumice recipe (D-0016-pumice-app-integration). See Context.
  • It supersedes the dashboard SPA and the thin wrapper. apps/dashboard (the D-0013-dashboard-app Vite+Vue SPA) and T-CKC1’s drafted apps/desktop are excised aggressively once SDF’s active-work view lands — good ideas are adopted, the code is removed. apps/pumice and apps/legacy-polish are peers, not part of SDF. See Decision.
  • Contract: ontogen-first, reconciled with the substrate schemas. SDF’s own Rust↔TS contract is generated by ontogen (HTTP + Tauri IPC + MCP transports, typed TS client, drift-gated) — the concrete convergence event D-0017-ontogen-shared-codegen-convergence deferred to. Substrate entities stay owned by the SDLC zod/markdown-contract schemas; a parity gate keeps SDF’s mirrored read-model aligned. See Decision.
  • Observe first, control later. Early phases are read-only over existing data planes (GET /api/state, lease refs, .sdlc/ logs). Bounded operator actions arrive in a later phase behind the determined-platform token pattern; the human merge gate is untouched. See Migration / rollout.

Three dashboard/desktop artifacts overlap today, and the target architecture already names the component they should become:

ArtifactStateDisposition
apps/dashboard (Vite+Vue SPA, D-0013-dashboard-app)shipped, v1 viewsuperseded by SDF; excised at view landing
apps/desktop thin Tauri wrapper (T-CKC1)draft, unbuiltabsorbed into SDF; task closes superseded
apps/pumice, apps/legacy-polish (Tauri+Nuxt apps)shippedpeers; recipe donors only
Dashboard slot in D-VSLI-distributed-work-runner-architecturereserved (“Observers: dashboard”)SDF fills it

The polish row is scoped to SDF and still holds: polish is no part of the operator surface. It does not speak to polish’s place in the determined suite, which D-0011 (2026-07-19, proposed) addresses separately — polish is ported there as a platform experience while remaining a peer here.

The requirement picture aggregates three sibling repos by the same author, reviewed 2026-07-16 (eight-agent research pass):

SourceContributes
sksizer/ontologicalontology-as-control-plane; live entity graph with containment drill-down; work-execution model (UnitOfWork → WorkflowTemplate → WorkExecution → StepResult); staleness/drift and write-collision signals; one-schema→HTTP/MCP/IPC/SSE codegen
sksizer/agentpantscanonical-Rust-API + ontogen-generated transports; agent-testable UI over plain HTTP; MCP tools mirroring the human UI; harness/terminal machine inventory; workflow graphs with human checkpoints; feature-flagged incremental panes
sksizer/zoosecure-agent cage patterns: deny-by-default egress policy, live event feed (bounded store, batched delivery, virtual scroll), streamed step-runner provisioning, discriminated-union state machines, reconcile-on-startup, blocked-event → one-click-amend

Together they describe one product: a control plane and metaharness for secure agent-based development. The substrate (D-VSLI-distributed-work-runner-architecture) and its adapters already expose the data SDF needs without violating S-0008-apps-consume-substrate-through-published-surfaces.

  1. Create solutions/ontological/desktop (moon id sdf), scaffolded from template-tauri-nuxt replaying the pumice recipe: peer src-tauri/src-nuxt layout, Bun-ified (no pnpm lockfiles), moon.yml with tags: ['tauri'] + language: 'rust', src-tauri joins the root cargo workspace (pumice pattern — it shares the pinned ontogen rev deliberately).
  2. Supersede and excise. SDF replaces apps/dashboard as the operator surface. Once SDF ships the active-work view (Phase 2), apps/dashboard and its moon/justfile wiring are removed; T-CKC1 and T-JDEV close superseded; T-Z5MK’s two-Tauri-apps question resolves as “SDF is the operator app; pumice/polish are peers”. The Bun/Hono server, the zod contract, and packages/ts/dashboard-client survive — they are the Phase-2 data plane, not part of the excision.
  3. Nuxt supersedes D-0013’s frontend pick for the operator app. D-0013-dashboard-app chose Vite+Vue for a server-served SPA; SDF is a desktop-first app from a maintained template fleet (Storybook, per-worktree ports, drift sync, release pipeline) with two in-repo precedents. D-0013’s serving-model reasoning stays valid for what it decided; its framework choice does not bind SDF.
  4. ontogen-first contract with a reconciliation gate. SDF’s app-local entities are Rust schema structs; one cargo build generates the Axum HTTP and Tauri IPC transports, the MCP tool registry, and the typed TS client — committed and drift-gated (pumice’s codegen_drift.sh pattern). Substrate entities (task, milestone, lease…) remain owned by the SDLC zod/markdown-contract schemas; SDF mirrors them as a read-model and a parity gate asserts the mirror matches the upstream contract (JSON-schema comparison), failing CI on drift. Full single-source convergence is a follow-up decision under D-0017-ontogen-shared-codegen-convergence.
  5. Transport switching, not transport forking. UI code targets the generated Transport interface; desktop builds bind IPC, browser/dev builds bind HTTP — the agentpants/pumice dual-transport pattern. Every view is therefore exercisable in a plain browser, which is how the app’s own UI gets tested by agents building it.
  6. Observe first; controls are token-guarded and bounded. Read-only phases consume sanctioned seams only. Later operator actions (refresh, open worktree/terminal, dispatch tick, reset stuck-loop guard) ride the determined-platform x-platform-token pattern (since widened to every HTTP /api/* request); merge stays human. Scaffolding writes INSIDE a registered checkout are within that envelope — project setup and harness install claude are idempotent sdlc ops the operator triggered, and what they write is configuration the project was going to need anyway; merge and lease ownership stay outside it.

Every project-parameterised service fn takes a registered project id and resolves it through the registry’s membership guard (crate::projects::root_of). There is no fallback to a launch checkout, an environment variable, or an sdlc.yaml walk.

Two exemptions take no project id, because they genuinely have none: the platform token, which is per install, and the suspenders skill store, which is user-level and indexes skills across every registered project.

A refusal is recorded. An id the registry never minted, arriving on a token-guarded route, is the most security-interesting event this design has, so it lands on the install-level denial trail beside the 401s — the same trail, for the same reason: neither ever named a project. A request with no token never reaches the service fn: the guard 401s it first, and that 401 is the record.

Both trails have read verbs (get_install_log, get_denial_log) and appear in the Action Log panel, so nothing SDF writes is unreadable by the operator it is written for.

The three trails are keyed by what they are about, not by one rule with exceptions: a project’s actions by its registered id (so a checkout that moves keeps its history), the install’s own actions and every refusal by nothing at all.

The token now lives at <durable>/token, keyed by nothing. It used to live at <durable>/projects/<path-digest>/token, keyed by the checkout it was minted from — which gave the guard and the reveal button two chances to disagree about which token this process is actually checking. One SDF process serves one HTTP port with one token. The audit trail was re-keyed in the same change, from that path digest to the registered project’s id, so a checkout that moves keeps its history.

There is no migration, and deliberately no deletion. An install upgrading across this change mints a new token at the new path, so every copy the operator had already pasted into a browser or a script stops working with a bare 401 — and its pre-upgrade action trails sit under a key nothing addresses any more, which makes them unreachable from an app where every log read is addressed by a registered project id. Startup therefore names both sets of leftovers on stderr and says why they stopped mattering. It removes neither: the tokens are live credentials under the user’s app-data directory and the trails are the operator’s own evidence, and an upgrade that silently deleted files there would be a worse habit than the one it fixed.

  • One operator surface. Three overlapping artifacts violate the repo’s decision-first, one-owner convention; the target architecture has exactly one observer slot, and every month of coexistence doubles view maintenance.
  • The recipe is paid for. Pumice already proved template → monorepo conversion (Bun-ify, moon tags, cargo membership, CI shape) — SDF’s scaffold is a replay, not a research project.
  • Generated contracts are the testability story. TS types emitted from Rust structs cannot drift silently; HTTP/IPC/MCP from one definition means the human UI, the web UI, and agent tools cannot diverge; drift gates make the contract a CI property. This is the strongest available answer to “make it as testable as possible”.
  • Reconcile, don’t fork, the entity model. The substrate’s markdown-contract schemas are load-bearing for the whole plugin; SDF re-declaring them in Rust without a gate would create the project’s second competing ontology (the failure mode ontological itself exhibits).
  • Aggressive excision beats parity-someday. The SPA’s view is one screen; carrying it while SDF grows invites divergence. Adopt its good ideas (cross-linked panels, status color language, typed client, one-command dev) and delete it.
OptionVerdict
SDF supersedes dashboard + thin wrapper; pumice/polish peers (chosen)One owner for the operator surface; resolves T-Z5MK
CoexistTwo dashboards maintained indefinitely; rejected
Build into pumiceCouples unrelated domains; rejected
OptionVerdict
ontogen-first + parity gate against substrate schemas (chosen)Generated multi-transport + TS; reconciliation explicit
zod-first, ontogen laterDefers D-0017 again; hand-kept Rust types for the desktop half
Dual pipelines unreconciledTwo ontologies, no gate; the ontological repo’s known failure mode
  • D-0013-dashboard-app is partially superseded (frontend choice and the SPA it governs); its API/serving reasoning survives in the Bun/Hono server SDF consumes. T-CKC1, T-JDEV close superseded at Phase 3; just dev repoints to moon run sdf:dev-desktop.
  • The root cargo workspace gains solutions/ontological/desktop/src-tauri; SDF inherits the pinned ontogen rev and its upgrade cadence (a coordinated-bump cost accepted for the shared model).
  • SDF’s own quality surface lives in moon (moon run sdf:check) + a per-app CI workflow; root sdlc.yaml quality_checks stay substrate-scoped.
  • .sdlc/-adjacent runtime state (SDF’s own pidfiles/registries) follows the existing dashboard-service conventions.
  • The engine event stream (D-VSLI-distributed-work-runner-architecture step 4) is not a dependency: early phases read files/HTTP; the event-stream consumer is added when Engine v0 exists.

Stacked-PR arc; each phase is independently adoptable and lands in dependency order (M-5EEX, member tasks below):

#PhaseTaskShips
0This ADR + milestone + tasks—the decision
1ScaffoldT-WDXJsolutions/ontological/desktop builds desktop + browser; moon run sdf:check green; CI workflow
2Active-work viewT-6S37SDF renders GET /api/state (tasks/leases/worktrees/PRs) with polling + filter; desktop supervises the backend
3ExcisionT-QT7Xapps/dashboard removed; T-CKC1/T-JDEV closed superseded; D-0013 annotated
4ontogen backendT-UBPGgenerated HTTP+IPC+MCP + TS client; transport switch; drift + parity gates
5Read panelsT-0DLCleases (authoritative refs), orchestrator log tail, PR verdict board
6Safe controlsT-DBALtoken-guarded refresh / open-worktree / dispatch-tick / guard-reset
  • Full contract convergence — whether substrate schemas eventually generate from (or into) ontogen structs; owned by D-0017-ontogen-shared-codegen-convergence, triggered after Phase 4 runs.
  • Event-stream contract — SDF’s consumer for Engine v0’s status socket/JSONL logs; decided when the D-VSLI engine lands.
  • Embedded consumer dashboard — whether the plugin ships any web UI for symlink-install consumers after the excision (the INDEX_HTML string’s fate beyond GET /api/state).
  • Filename follows D-0002-entity-identifier-shape: D-0018- prefix, slug sdf-control-plane-app. Hand-authored against the decision body template.
  • Research provenance: eight-subagent review (2026-07-16) of ontological, agentpants (+PR corpus), zoo, template-tauri-nuxt, rust-ontogen (origin/main v0.4.0; local clones of ontogen were 148 commits stale), the existing dashboard stack, the Dark Factory corpus, and monorepo conventions.

← Back to Decisions