SDF control-plane app — Tauri+Nuxt operator surface
State: open/accepted
Summary
Section titled “Summary”solutions/ontological/desktopis 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 fromtemplate-tauri-nuxtby 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) andT-CKC1’s draftedapps/desktopare excised aggressively once SDF’s active-work view lands — good ideas are adopted, the code is removed.apps/pumiceandapps/legacy-polishare 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.
Context
Section titled “Context”Three dashboard/desktop artifacts overlap today, and the target architecture already names the component they should become:
| Artifact | State | Disposition |
|---|---|---|
apps/dashboard (Vite+Vue SPA, D-0013-dashboard-app) | shipped, v1 view | superseded by SDF; excised at view landing |
apps/desktop thin Tauri wrapper (T-CKC1) | draft, unbuilt | absorbed into SDF; task closes superseded |
apps/pumice, apps/legacy-polish (Tauri+Nuxt apps) | shipped | peers; recipe donors only |
| Dashboard slot in D-VSLI-distributed-work-runner-architecture | reserved (“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):
| Source | Contributes |
|---|---|
sksizer/ontological | ontology-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/agentpants | canonical-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/zoo | secure-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.
Decision
Section titled “Decision”- Create
solutions/ontological/desktop(moon idsdf), scaffolded fromtemplate-tauri-nuxtreplaying the pumice recipe: peersrc-tauri/src-nuxtlayout, Bun-ified (no pnpm lockfiles),moon.ymlwithtags: ['tauri']+language: 'rust',src-taurijoins the root cargo workspace (pumice pattern — it shares the pinnedontogenrev deliberately). - Supersede and excise. SDF replaces
apps/dashboardas the operator surface. Once SDF ships the active-work view (Phase 2),apps/dashboardand its moon/justfile wiring are removed;T-CKC1andT-JDEVclose 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, andpackages/ts/dashboard-clientsurvive — they are the Phase-2 data plane, not part of the excision. - 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.
- ontogen-first contract with a reconciliation gate. SDF’s app-local
entities are Rust schema structs; one
cargo buildgenerates the Axum HTTP and Tauri IPC transports, the MCP tool registry, and the typed TS client — committed and drift-gated (pumice’scodegen_drift.shpattern). 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. - Transport switching, not transport forking. UI code targets the
generated
Transportinterface; 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. - 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-tokenpattern (since widened to every HTTP/api/*request); merge stays human. Scaffolding writes INSIDE a registered checkout are within that envelope —project setupandharness install claudeare 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.
No ambient project
Section titled “No ambient project”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 per-project keying went away, once
Section titled “The per-project keying went away, once”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
ontologicalitself 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.
Options considered
Section titled “Options considered”Lineage
Section titled “Lineage”| Option | Verdict |
|---|---|
| SDF supersedes dashboard + thin wrapper; pumice/polish peers (chosen) | One owner for the operator surface; resolves T-Z5MK |
| Coexist | Two dashboards maintained indefinitely; rejected |
| Build into pumice | Couples unrelated domains; rejected |
Contract source of truth
Section titled “Contract source of truth”| Option | Verdict |
|---|---|
| ontogen-first + parity gate against substrate schemas (chosen) | Generated multi-transport + TS; reconciliation explicit |
| zod-first, ontogen later | Defers D-0017 again; hand-kept Rust types for the desktop half |
| Dual pipelines unreconciled | Two ontologies, no gate; the ontological repo’s known failure mode |
Consequences
Section titled “Consequences”- 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-JDEVclose superseded at Phase 3;just devrepoints tomoon run sdf:dev-desktop. - The root cargo workspace gains
solutions/ontological/desktop/src-tauri; SDF inherits the pinnedontogenrev 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; rootsdlc.yamlquality_checksstay 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.
Migration / rollout
Section titled “Migration / rollout”Stacked-PR arc; each phase is independently adoptable and lands in dependency order (M-5EEX, member tasks below):
| # | Phase | Task | Ships |
|---|---|---|---|
| 0 | This ADR + milestone + tasks | — | the decision |
| 1 | Scaffold | T-WDXJ | solutions/ontological/desktop builds desktop + browser; moon run sdf:check green; CI workflow |
| 2 | Active-work view | T-6S37 | SDF renders GET /api/state (tasks/leases/worktrees/PRs) with polling + filter; desktop supervises the backend |
| 3 | Excision | T-QT7X | apps/dashboard removed; T-CKC1/T-JDEV closed superseded; D-0013 annotated |
| 4 | ontogen backend | T-UBPG | generated HTTP+IPC+MCP + TS client; transport switch; drift + parity gates |
| 5 | Read panels | T-0DLC | leases (authoritative refs), orchestrator log tail, PR verdict board |
| 6 | Safe controls | T-DBAL | token-guarded refresh / open-worktree / dispatch-tick / guard-reset |
Open questions
Section titled “Open questions”- 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, slugsdf-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.