What is left to build, ordered by the bar in CLAUDE.md §12: security correctness first, the lean foundation next, visibility last. Status reflects what is actually verified per §11, not what merely exists.
Legend: ✅ done and verified · 🔶 in progress or needs end-user-faithful re-verification · ⬜ not started.
Detailed specs: docs/specs/2026-06-09-lean-verified-perimeter-roadmap.md (WS0 to WS5), docs/specs/2026-06-28-command-surfaces-implementation-plan.md (CLI / registry / MCP — the next frontier), docs/road-to-recommendable.md, the decisions in docs/adr/.
The north star: OpenTrApp becomes the perimeter people trust to run open-source agents, earning thousands of stars and forks because it is the best, not through promotion (CLAUDE.md §12.4: substance first, visibility follows). Four rungs, each a measurable benchmark that gates the next. We do not climb a rung before the one below it is verified through the product's own entrypoints (§11). Sections 1 to 7 below are the tactical detail for each rung.
Rung 1: Provably contained (security correctness).
Benchmark: the perimeter provably contains the agent, verified through the product CLI (opentrapp-daemon vault up / vault verify), cold and after every resume.
- T0 boundary self-test exit 0 cold AND resumed, end-user-faithful (not dev scaffolding).
- T1 idle auto-pause fires; T2 wake exactly-once with a security-correct resume.
- Red-team breakout and proxy soak green. Gate: all green via the product path. (Section 1.) T0 ✅ (2026-06-26); T1 mechanism ✅ + T2 security-correct resume ✅ (#195/#197), with the live wake-on-real-message the named maintainer tail (#35, #40); #76 (first-run image staging) ✅ (#200). The Rung-1 substance is met; what remains is the owner-gated release cut (Section 5).
Rung 2: Lean and CLI-first (the de-Tauri product). Benchmark: the three concerns are CLI-operable, the GUI is optional, and the full perimeter runs on a 7.2 GB laptop with idle auto-pause collapsing resting RAM toward zero.
- The de-Tauri cutover (the daemon is the product; the GUI an optional projection). ✅ shipped (clears Vulnerabilities #46 at the code level, pending Scorecard re-scan).
- Each of Vault / Skill / Social independently CLI-operable and distributable.
- Leanness verified on the 7.2 GB box (resting RSS; idle auto-pause firing). The 7.2 GB floor is non-negotiable. Gate: de-Tauri done and lean verified on the laptop. (Section 2.) Cutover + lean-down (WS-A…WS-D) DONE and box-verified; the enablement defaults (idle-pause-on, daemon-default-on) and the goproxy live-boundary gate remain — they sit under Rung 1. Release-critical under the hard-gate.
Rung 3: Best-in-class posture (the trust signals). Benchmark: the posture is verifiably best-in-class: clean Scorecard on the trusted-tier items, CII Gold, signed reproducible releases, a second maintainer.
- Scorecard Tier-1/2 alerts cleared (#46 via Rung 2; #43 and #1 via a co-maintainer).
- CII Best Practices Gold; signed releases (SignPath); reproducible builds.
- Co-maintainer recruited (also unlocks the people-gated checks). Gate: the trust signals are real and verifiable; the release hard-gate (owner 2026-06-22) binds here. (Sections 4 and 5.)
Rung 4: Adopted (the stars follow). Benchmark: OpenTrApp is the referenced perimeter for open-source agents; stars and forks in the thousands, earned.
- The opencode institutional reference (highest-leverage lever; gated on Rung 1).
- Public launch: the CDR / supply-chain story (Show HN, r/netsec), the evolution article, the Skill Firewall on the Marketplace.
- Awesome-lists, community contributions, the standalone-module adoption path. Gate: every visibility push is scoped to the substance behind it (§12.4); never promote ahead of the verified foundation. (Section 6.)
The perimeter must provably contain the agent, cold and after every resume. This is the load-bearing gate for the opencode pitch and the proof the security promise is real.
| Item | Status | Notes and gate |
|---|---|---|
| T0 boundary self-test, cold == resumed | ✅ | Verified via the product CLI 2026-06-26 on the live goproxy perimeter (not dev scaffolding): opentrapp-daemon vault up → vault verify pass=7 fail=0 cold, then vault pause→resume→vault verify pass=7 fail=0 with B5 CA-fingerprint UNCHANGED. End-user-faithful (the product daemon's native orchestrator, not podman-compose). Scope: DevVerifier/from-source mode + a placeholder key (B1–B6 need no valid key — P1-1); the BundleVerifier digest-staging path is the post-release T0, exercised once the owner tags v0.9.0 and the signed assets exist (the lane itself is now built — #199/#200). |
| WS0-0a: idle auto-pause actually fires (T1) | 🔶 | Always-on, token-gated — the IDLE_AUTO_PAUSE_ENABLED const was removed (2026-06-09); the supervisor auto-pauses whenever a Telegram token is present (should_auto_pause; fail-safe). Mechanism verified on the box 2026-06-27 via the OPENTRAPP_IDLE_TIMEOUT_MS knob: idle past the threshold → [supervisor] pausing to dormant → DORMANT marker + all containers stopped; control-resume → auto-self-test PASS → awake. Maintainer tail: the full RAM-collapse magnitude needs a real agent loaded (a placeholder OpenClaw never loads its ~356 MB working set), and the live wake-on-real-message needs a real token. |
| WS0-0c: wake exactly-once + security-correct resume (T2) | 🔶 | Security-correct resume CLOSED (#195): both resume paths — control-channel (resume_now) AND wake-on-message (idle::resume_from_dormant) — now run the boundary self-test fail-closed (the waker path previously skipped it). The waker arms while dormant; exactly-once is by construction (peek-only, never advances the offset). Verified on the box: dormant→resume→auto-self-test PASS. Maintainer tail: the live wake on a real Telegram message (real token + capable hardware) + upgrade the e2e wake test to assert exactly-once (#40). |
Credential hardening: --env-file / podman secrets (#75) |
✅ | Done. Secrets pass as -e NAME (name only on the argv) with the value injected into podman's process env via Command::env() — never on the argv / host process table. Pinned by secrets_never_leak_onto_the_argv + apply_secret_env_injects_into_child_process_environment. |
| WS1: bound proxy memory over long sessions | ✅ | Resolved by replacement (WS-C). The mitmproxy RSS leak (54→550+ MB) is gone — vault-proxy is now the Go goproxy chokepoint (<50 MB RSS measured, leak-free; ADR-0026, Section 2). The 256 MB mem_limit is the blast-radius cap, not a working set. |
Enforce command boundary_impact at the run_command chokepoint (ADR-0021) |
⬜ | Phase 0 of the command-surfaces plan. Command.boundary_impact is classified + fail-closed but not runtime-enforced today — execute::run_command runs commands ungated; the GUI relies on a documented assumption (routes.rs:75). Audit + classify every workload command, gate at the single chokepoint (fail-closed refuse), so the new CLI/MCP and the existing GUI all inherit one gate. Prerequisite of the agent-reachable CLI/MCP surfaces (owner decision 2026-06-28). |
The north star (ADR-0019 / ADR-0020 / ADR-0022): a lean headless daemon + CLI as the product, the GUI an optional projection. The lean-down campaign (WS-A…WS-D) shipped on main (2026-06): the Tauri GUI is deleted, the proxy is a 15 MB Go chokepoint, and every base image is alpine. The cutover/lean parts are done and verified on the 7.2 GB box; the enablement parts (idle-auto-pause-on, daemon-default-on) stay gated on Rung 1 product-daemon T0.
| Item | Status | Notes and gate |
|---|---|---|
Headless daemon operation (make daemon, docs/headless.md) |
✅ | Lean GUI-free binary + operating docs shipped. |
| Delete the Tauri crate + GTK3 deps (the cutover) | ✅ | Shipped on main 2026-06-24 (WS-D). Workspace re-rooted at crates/{core,daemon,viewer-server}; Cargo.lock has 0 tauri/wry/webkit entries; deny.toml ignore list empty. Clears Vulnerabilities #46 at the code level (Scorecard re-scan pending). |
| Loopback-viewer de-risking spike + threat model | ✅ | ADR-0022 §0 passed; viewer-server built, session-bootstrap + events WS wired, Linux-proven this campaign. |
WS-A: tune compose.yml/perimeter.yml mem_limits to measured + SIGTERM teardown |
✅ | Resting-cap sum 6.3 GB → ~3 GB; clean idle shutdown (orchestrator-check §31). |
| WS-B: lean every workload base to alpine | ✅ | Shipped 2026-06-26 (PR #191). vault-skills 233→72 MB, vault-social 153→74 MB; vault-egress + goproxy already alpine; vault-agent node:22-alpine (distroless non-viable, #87). Verified on musl (self-test/test/scan green; PyYAML musllinux wheel). |
| WS-C / WS5: replace mitmproxy with a lean Go L7 proxy | ✅ | Built + switched in on main (PR #189/#190; ADR-0026). elazarl/goproxy, 15.6 MB, leak-free, drops the Python interpreter from the keys-holding container. Live-boundary gate GREEN (2026-06-26): the live boundary-selftest.sh ran via opentrapp-daemon vault verify against the goproxy perimeter — B1 isolation, B2 allowlist (deny 403 / allow), B3 credential-separation (goproxy injects; no key in agent), B4 L3 egress, B5 CA-stable cold==resumed, B6 read-only — pass=7 cold and resumed. A placeholder key suffices for the boundary (P1-1). |
| Enable idle auto-pause by default | ⬜ | After WS0-0a/0c verified (Rung 1). |
Flip the daemon default on (OPENTRAPP_DAEMON_DEFER) |
⬜ | After resting-RSS + viewer-survival verified on the product path. |
| Status-streaming API (Unix socket or loopback) | ⬜ | Today the CLI reads markers + stderr. |
Unify the CLI as opentrapp <concern> <verb> (retire opentrapp-daemon) |
⬜ | CLI spec + plan Phases P1–P2. A thin projection over core's manifest command API (execute::run_command/execute_workflow) — no concern rewritten. Inherits the Phase-0 chokepoint gate. The bare-opentrapp rename is owner-gated (P2). |
| Optional MCP adapter (external-operator-only) | ⬜ | MCP spec + plan Phase P4. A thin stdio JSON-RPC wrapper over the same command API; host-operator-only (never the contained agent — ADR-0020 tenet 5); inherits the chokepoint gate. Gated on the dep-vs-hand-roll decision (rec: hand-roll minimal). |
| OS autostart launches the daemon, not the GUI | ⬜ | systemd user unit / launchd / Windows task. |
| Item | Status | Notes |
|---|---|---|
| ESLint 9 to 10 migration (unblocks unicorn 66, #107) | ⬜ | Blocked upstream on eslint-plugin-react ESLint-10 support; @dependabot ignore posted. Revisit when upstream ships it. |
| Dependabot majors (react-router v7, form-data) | ✅ | Merged. New ones handled per the bar: verify, then merge. |
| Item | Status | Notes |
|---|---|---|
| Token-Permissions least privilege | ✅ | PR #139. |
| Dependabot #19 (js-yaml DoS) | ✅ | Overridden to js-yaml ≥4.2.0; npm install reports 0 vulnerabilities (PR #152). |
| Pinned-Dependencies #80/#81/#82 | 🔶 | npm install -g pkg@VERSION in the agent/skills build scripts: version-pinned, but Scorecard wants integrity pins. Hash-pin or document the rationale (the built image is digest-pinned downstream by the signed bundle). |
| Vulnerabilities #46 | 🔶 | De-Tauri cutover shipped (Section 2): the GUI crate is deleted, Cargo.lock is GTK-free. The 19 GTK3/WebKit advisories are gone at the code level; the Scorecard badge clears on its next re-scan cycle. |
| Code-Review #43 | ⬜ | Solo-maintainer ceiling: needs a second human reviewer. |
| Branch-Protection #1 | 🔶 | Partly settable now via gh api (require status checks, no force-push, require PR); a required-review count ≥1 is meaningless without a co-maintainer. |
| CII Best Practices + Scorecard climb | 🔶 | Several checks unlock with a co-maintainer. |
| Item | Status | Notes |
|---|---|---|
| Skill Firewall on the Marketplace + one-way sync | ✅ | Action published; projection sync verified end to end. |
| Publish the de-Tauri release (next: v0.9.0) | 🔶 | Release LANE built (#199, #200, 2026-06-27); the cut is the owner's go/no-go (P5-1). cargo-dist builds the daemon + viewer-server for Linux/macOS/Windows (×2 arch) with shell+PowerShell installers + SLSA attestations; the five perimeter images publish as durable signed release assets the daemon digest-pins (#76 closed). Version is 0.9.0 lockstep; dist plan announces it cleanly on the box. Code-scan posture for the gate: #80-82 done, #46 de-Tauri shipped, the goproxy live-boundary self-test is GREEN (Section 2) and its Go advisories are cleared (#198); the only open alerts are #43 + #1 (co-maintainer) — honestly open, never dismissed. Last tagged release (v0.8.0) is still the pre-cutover Tauri app until the owner tags v0.9.0. |
| CLI/registry distribution (crates.io / Homebrew / curl-sh; GHCR) | 🔶 | Built, owner-trigger-gated. curl-sh + PowerShell installers via cargo-dist (#199); opentrapp-core crates.io publish wired (publish-crate.yml, needs CARGO_REGISTRY_TOKEN); GHCR images signed + published as release assets (#200). Homebrew tap is a later optional convenience (ADR-0023). OS-agnostic, no lock-in. See the registry readiness spec + plan Phase P3 (a pre-tag readiness harness — cargo publish --dry-run, metadata pins — + the owner runbook). |
| Code signing (SignPath reapply) | ⬜ | After visibility signals land (rejected 2026-06-15). |
Substance first, visibility follows. The opencode reference is the highest-leverage lever and is gated on the security foundation in Section 1.
| Item | Status | Notes and gate |
|---|---|---|
| opencode pitch (institutional reference) | ⬜ | Gated on T0 + T1/T2 verified end-user-faithfully. |
| Awesome-list submissions (4 lists) | 🔶 | Drafted and turnkey in drafts/; owner submits. |
| Co-maintainer recruitment | ⬜ | Also unlocks the Scorecard/CII ceiling. |
| Evolution article (albertdobmeyer.dev) | 🔶 | Drafted and deployed; diagram SVGs in-repo. |
| Show HN / r/netsec / r/selfhosted | 🔶 | Drafted in drafts/. |
| Item | Status |
|---|---|
| Rotate the two dev keys (echoed into a session transcript 2026-06-22 by podman-compose) | ⬜ |
Marketplace publish + SKILL_FIREWALL_SYNC_TOKEN |
✅ |
The single most important next move is to finish Section 1 through the product's own entrypoints. Everything in Section 6 waits on it.
Release hard-gate (owner, 2026-06-22): no new version ships until all code-scan alerts are closed. Because #46 needs the de-Tauri cutover (Section 2) and #43/#1 need a co-maintainer (Section 6), those are now on the release critical path, not deferrable if a release is wanted. The fixable alerts (#80-82, #1 settings) are being closed now; #19 is done.