📋 A short, honest backlog for what comes after the adoption push (installers + Docker + dashboard). Ordered by leverage, not by milestone number. Every item lists why it matters, its scope, and how we'll know it's done. M9–M11 stay demand-driven — captured, not committed.
v1 is complete + audited + hardened. M8 (v2 foundations) shipped & fleet-verified: schema migration runner,
per-(share,id) snapshots, daemon control socket + pause/resume, M8a teams (principals · roles · invites
with attenuation · members), and restore byte-safety. Plus an embedded live dashboard, cross-platform
installers with keep-alive services, a hub Docker image for NAS, and macOS Full Disk Access detection.
🚀 P1 shipped (2026-06-23): real published release (latest, 6 OS/arch incl. linux/arm64 for Pi),
a one-shot scripts/release.sh, CI on Forgejo + GitHub Actions (vet · build · -race), the hub image in the
Forgejo registry, and a public shoemoney/devbox-dist repo so curl|sh works with no token. The
GitHub repo is now public (AGPLv3, module path github.com/shoemoney/devbox → go install works) after a
clean two-scanner git-history secret sweep. Verified end-to-end on clean no-Go amd64 and arm64 boxes.
flowchart LR
NOW["✅ adoption tooling<br/>installers · docker · dashboard"] --> P1["✅ P1<br/>Releases + CI<br/>+ public repo"]
P1 --> P2["✅ P2<br/>M8a auth audit<br/>2 fixes + tests"]
P2 --> P3["✅ P3<br/>dashboard depth<br/>pull·gc·conflict + history"]
P3 -.demand-driven.-> LATER["🔮 M9–M11<br/>E2E · P2P · HA · TUI"]
style NOW fill:#1e5a2e,stroke:#51cf66,color:#fff
style P1 fill:#1e5a2e,stroke:#51cf66,color:#fff
style P2 fill:#1e5a2e,stroke:#51cf66,color:#fff
style P3 fill:#1e5a2e,stroke:#51cf66,color:#fff
style LATER fill:#0d1117,stroke:#4F9CF9,color:#fff
Why: the curl | sh installer and Docker image were built, but the installer's primary path —
download a prebuilt binary — had nothing to download (no published release), so it silently fell back to
go build / local dist/. A stranger couldn't adopt devbox without Go + the repo. P1 closed that gap and gave
the v2 codebase the regression safety it lacked.
Shipped
-
scripts/release.sh— one-shot: cross-build (now 6 targets incl.linux/arm64+linux/armfor Pi) → create/update the release → upload de-versioned assets (devbox_<os>_<arch>,+.exe) the installer downloads directly, plus a de-versionedSHA256SUMSsoshasum -cmatches. Idempotent. - CI on Forgejo Actions (
.forgejo/workflows/) + GitHub Actions mirror:go vet·go build ./...·go test ./... -raceon push/PR; release-on-tag workflow ready. GitHub CI is green on every push (Forgejo waits on a self-hosted runner — per the gotcha, the localrelease.shis the real path). - Hub image published to the Forgejo registry:
git.shoemoney.ai/shoemoney/devbox-hub:latest; compose nowimage:by default (--buildfor local source). - Deviation (approved): the source repo stays private, so a public
shoemoney/devbox-distrepo carries the installers + binaries as release assets →curl|shworks with no token. Separately, the GitHub mirror was made public (AGPLv3 — matching the open-core moat, module pathgithub.com/shoemoney/devboxsogo install …/cmd/devbox@latestworks) after a clean two-method git-history secret scan.
Acceptance — all met ✅
- ✅ Clean no-Go box,
curl -fsSL …/install.sh | shinstalls a workingdevboxfrom the real release — verified end-to-end on amd64 and fleet-verified on a real clean no-Go arm64 Raspberry Pi (192.168.1.13): anoncurl|sh→devbox 0346995,devbox doctorreportslinux/arm64. - ✅
docker compose up -dpulls the published image (no local build) — verified on the NAS (throwaway stack, production hub untouched). - ✅ CI green on
main(GitHub Actions) and gates PRs.
Effort: M · Risk: low — landed without product-code changes (module-path rename only).
Why: M8a added real privilege code — invite attenuation (meta.MayGrant), the push write-gate,
principal binding on join, token handling. It has unit + HTTP + fleet tests, but no dedicated adversarial pass.
This is the M7.5 treatment for v2's new attack surface, before anyone relies on it for multi-owner shares.
✅ SHIPPED 2026-06-23 — 13-agent adversarial pass (6 finders + skeptic verify), 2 real findings fixed, rest documented as single-owner residuals. Full report:
docs/M8a-audit.md.
Findings (hunted with regression tests for each real one)
- Invite replay / reuse — ✅ defended:
RedeemTokenis an atomic CAS; sequential + concurrent double-redeem both fail; PoP checked before redeem. - Privilege escalation — 🛠️ FIXED:
MayGrantnever attenuated the granted+sbit (req.Resharecopied verbatim) — nowMayGrant(…, grantReshare)rejects conferring+syou don't hold (owners unconstrained). Role bounding / no-demote-up / server-derived caller role were already solid. - TOCTOU legacy→explicit flip — ✅ defended by
publishMu; the push-gate micro-race is an accepted low-risk residual. - Revoked-device bearer reuse — ✅ defended:
revoked=0filtered in bothDeviceByBearerandEffectiveMember(denied on the next request). - Cross-share leakage — ✅ defended: redemption binds share/principal/role from the server-side binding, never the request.
- Join PoP — ✅ defended; surfaced that invites were unrevocable bearer capabilities → 🛠️ FIXED: added
meta.RevokeInvite+POST /v1/invite/revoke+devbox invite revoke <token>.
Acceptance — met ✅
- ✅ Both fixes have failing-then-passing regression tests (
TestInviteCannotGrantReshareCallerLacks,TestInviteRevoke, +5TestMayGrantcases);go test ./... -raceclean (18 pkgs). The hub binary carrying these changes ran clean on the real arm64 Pi (via the P3 fleet-verify); the auth logic is platform-independent, so HTTP+race is the load-bearing verification. - ✅
docs/M8a-audit.mdrecords findings, confirmed defenses, and the residual single-owner-threat-model deferrals.
Effort: M · Risk: medium (security-sensitive — done carefully, not rushed).
Why: the live dashboard wowed already, but it only animated join + push. P3 made it a genuinely
complete ops surface.
Shipped
- Hub emits
pull(head fetch / propagation,handleHead),conflict(stale-parent push,handlePush), andgc(in-process sweep) flow events. GC runs in-process via opt-inserve --gc-every <dur>(default off — auto-deleting blobs on a timer is opt-in) so the hub self-maintains and each sweep animates. - Server-side history window: per-minute activity buckets (push/pull/conflict/gc + bytes, last 60 min) in
/api/state→ the sparkline now survives a page reload (was live-stream-only). Recorded under theEmitlock; race-clean. - Frontend (
index.html, one file, no new deps):pull= teal inbound pulse,conflict= red collision burst,gc= amber hub sweep + toast; stacked per-minute history sparkline seeded from/api/stateand growing live. - [~] (Optional M11 TUI) — skipped, demand-driven (no consumer yet).
Acceptance — met ✅
- ✅
gc,pull,conflict(plusjoin/push) all visibly animate; all 5 fleet-verified on the live SSE stream on a real arm64 Pi (192.168.1.13) byscripts/dashboard-fleet-verify.sh(also aLOCAL=1mode). Frontend rendered headless with 0 console errors; history sparkline + gc toast + all event feeds present. Tests:TestHistoryRing;go test ./... -raceclean.
Effort: S–M · Risk: low.
Do not pre-build these — there's no user who needs them yet, and the spec explicitly defers them:
| Milestone | Trigger to build it |
|---|---|
| M9 — E2E (convergent encryption) | a real untrusted-hub user appears (you self-host on your own NAS → you trust it) |
| M9 — S3/R2 + Litestream HA | hub durability/DR becomes a felt need beyond NAS snapshots (slots behind the blobstore.Store seam) |
| M9 — read-side ACL gating | a genuinely untrusted multi-owner share exists |
| M10 — LAN P2P chunk exchange | the hub uplink actually hurts (today the hub is on the LAN) |
| M10 — conflict sidecar + diff3 resolver | conflicts get frequent enough to want interactive 3-way merge |
| M11 — full TUI + power sanity | on demand |
Recommendation: start at P1. It finishes the adoption story you just set and hardens the build — highest leverage, lowest risk. 🚀