These materially change the design. Each has a recommendation; the Decision line records the locked answer.
Status (2026-06-28): all questions are now resolved. The owner delegated the remaining calls ("follow what is designed"), so every still-open question below is locked to its stated recommendation. Decisions can be revisited at the milestone that consumes them (each is tagged); revisit only on concrete evidence (a support ticket, a benchmark, a real workflow), not speculatively.
v1 is designed as a stateless CLI (no daemon): ref-count drift is reconciled lazily on the next command, and there is no automatic autostop of shared services when their last consumer goes down. A background agent (systemd --user / launchd) would enable autostop-on-zero-ref and a live dashboard, but adds a whole new install/IPC/cross-platform surface.
- Recommendation: CLI-only for v1; accept lazy reconcile;
shared gc/doctor --fixfor manual cleanup. Revisit a daemon in v2 alongside the dashboard. - Decision (RESOLVED): CLI-only, no daemon for v1 — already embodied in ARCHITECTURE §1 and the shipped M0 spine. Lazy reconcile;
shared gc/doctor --fixfor cleanup. Revisit a daemon in v2 alongside the dashboard.
Brief says macOS + Linux + WSL2, all "undefined" priority. WSL2 needs the most special-casing; macOS needs its own CI runner and has the worst resolver/trust quirks.
- Recommendation: Linux + WSL2 first, macOS fast-follow. Native Windows (non-WSL2) explicitly out of scope (alias symlinks →
.cmdshims at best). - Decision (RESOLVED): Linux + WSL2 first, macOS fast-follow; native Windows out of scope. CI cross-compiles
linux/{amd64,arm64}+darwin/{amd64,arm64}; WSL2 branch logic lives ininternal/xdg(WSL2 detect,/mnt/*refusal, 9p detection). The macOS trust/resolver runner is the M7 fast-follow.
Design requires Docker Engine + docker compose plugin v2.20+. Many OSS devs avoid Docker Desktop for licensing reasons and use Colima/Rancher/Podman/Lima.
- Recommendation: Docker + compose v2.20+ only for v1; Podman/rootless/Colima/Lima out of scope (documented, with a clear
doctorerror). Revisit if your audience needs it. - Decision (RESOLVED): Docker Engine +
docker composev2.20+ only for v1. Podman/rootless/Colima/Lima are out of scope and must surface a cleardoctorremediation. Theinternal/dockerpreflight already enforces the compose≥2.20 floor.
Q-T · which text-template engine — blocks M1 (see DECISIONS D2)
Compose generation is programmatic (compose-go) either way. The fork is the engine for text artifacts + user-authored templates:
- Option A — stdlib
text/template+ sprig (recommended for clean-slate: lean, zero engine-maintenance risk; weaker inheritance). - Option B —
gonja/v2(pure-Go Jinja2: richer authoring, inheritance, mirrors devdock heritage; single-maintainer dep). - Recommendation: A, unless you want Jinja-style authoring for third-party templates.
- Decision (M1, RESOLVED): Option A — stdlib
text/templatewith custom[[ ]]delimiters, behind the swappableinternal/templateengine. Sprig is not pulled in: its non-deterministic helpers (now/uuid/randAlphaNum) would break the byte-identical-output guarantee, so a small deterministic, in-tree FuncMap (default/upper/lower/trim/replace/atoi/indent/…) is reimplemented with golden tests instead. Inheritance is done at the structured layer (extends-chain render-then-deep-merge), not via the text engine.
Research recommends shipping exactly two behind the pluggable interface; Vault/1Password/Doppler defer.
- You originally named Infisical + AWS. Research recommends SOPS+age (offline, no-account default) + AWS for the best OSS first-run.
- Recommendation: ship SOPS+age + AWS + Infisical if effort allows (Infisical was an explicit ask); otherwise SOPS+age + AWS and Infisical as the first 1.x plugin.
- Decision (RESOLVED, M4): target SOPS+age (offline default) + AWS + Infisical; if the M4 budget is tight, ship SOPS+age + AWS and land Infisical as the first 1.x plugin behind the same
ProviderFactory. SOPS pulls all cloud-KMS SDKs transitively — gate with build tags or shell out to thesopsbinary (DECISIONS D10).
- Recommendation: Caddy (label-driven, one-label local HTTPS via
caddy-docker-proxy). Traefik/nginx remain pluggable behind theProxyinterface. - Decision (RESOLVED, M5): Caddy via
lucaslorentz/caddy-docker-proxy(label-driven,tls=internalfor one-label local HTTPS). Traefik/nginx stay pluggable behind theProxyinterface (DECISIONS D11).
- Recommendation: shell out to the maintained
mkcertbinary (auto-install or bundle) rather than importing the unmaintainedsmallstep/truststore. Means a runtime dependency on mkcert. - Decision (RESOLVED, M5): shell out to
mkcert(auto-install or bundle); accept the runtime dependency. Local HTTPS stays opt-in so a broken trust path never blocksup; WSL2 trust drivescertutil.exeinterop (DECISIONS D11).
docker-compose-<env>.yaml + Dockerfiles: commit them (stable, reviewable diffs — but you must fight compose-go normalization churn with golden output) or gitignore + always regenerate (freer, but no review trail)?
- Recommendation: gitignore + regenerate by default; offer a
--commit-artifactsmode for teams that want reviewable diffs. - Decision (M1, RESOLVED): gitignore + regenerate. Artifacts are written under each stack's
.devstack/(already in.gitignore);devstack generateregenerates idempotently andgenerate --checkis the CI drift gate. Determinism is enforced (golden suite +make determinism) so the churn objection is moot, leaving the door open for a future--commit-artifactsmode.
Your friend's existing devdock users are the most likely early adopters. A devstack import reading an old project.yaml → new workspace.yaml + devstack.yaml split is low-cost, high-leverage. (Note: not byte-compatible — clean-slate — so it's a converter + migration guide, not a drop-in.)
- Recommendation: include
devstack import+ a migration guide in v1. - Decision (RESOLVED): include
devstack import+ a migration guide in v1. It isinternal/migrate(spec 14) — a pure YAML→YAML converter that takes no lock and ships independently. It remains an optional, milestone-tagged stub today (it does not block M1's generation pipeline) and lands as a fast-follow.
Canonical name devstack (matches devstack.yaml / apiVersion: devstack/v1), built from ./cmd/devstack. The Go module + GitHub repo are github.com/open-source-cloud/devstack (the local checkout folder is devstack). Aliases rq and uranus dispatch via argv[0] symlinks.
- Decision: binary/package name
devstack; aliasesrq,uranus. Load-bearing for the install-method detection + upgrade-remediation strings in spec 14 (Homebrew formula /.deb/.rpmpackage name =devstack) and the alias-relink step in spec 13/spec 14. The OpenStack DevStack name collision is accepted for now; revisit only if a public distribution-name conflict arises.
Q-PROFILE · profile definition plane + default-profile policy — blocks M6 (feature #4) (surfaced by spec 12)
Should service slices be defined per-repo (each devstack.yaml carries its own Compose profiles: membership tags), at the workspace level (workspace.yaml groups: naming cross-repo slices like frontend), or both? And what does up start with no --profile? Per-repo maximizes repo portability but no single repo can express a cross-repo slice; workspace groups express cross-repo slices but couple the workspace to service names inside repos it doesn't own. Sub-question: should down --profile/stop --profile exist for pause-without-teardown, or is keeping down whole-project sufficient for v1?
- Recommendation: both, unioned by name;
defaultProfileopt-in inworkspace.yamlwithallas the no-config default; keepdownwhole-project (enumerate by tool-owned label, never--profile) and deferstop --profile. - Decision (RESOLVED, M6): both planes, unioned by name.
defaultProfileis opt-in inworkspace.yamlwithallas the no-config default.downstays whole-project (enumerate by the tool-ownedcom.devstack.managedlabel, never--profile);stop --profileis deferred. (M1 generation deliberately does not emit composeprofiles:yet — seeinternal/generate/compose.go— because compose would disable un-selected services; the profile-aware emit lands with theup --profilegraph walk in M6.)
Q-SAGA-PARALLEL · parallel vs sequential project compose-up — affects M6 (feature #1) (surfaced by spec 09)
Should the saga's compose-up phase bring multiple project stacks up in parallel (faster on multi-project workspaces, but interleaves image-pull progress and complicates the nested checklist + per-project compensation) or sequentially for clarity?
- Recommendation: sequential in v1; add parallel behind a
--parallelflag once the checklist model proves stable. - Decision (RESOLVED, M6): sequential in v1. Add
--parallelonly once the bubbletea checklist + per-project compensation model has proven stable.
Q-HEALTHWAIT · who owns the readiness poll — affects M6 (feature #3) (surfaced by spec 10)
Should the intra-project wave rely on docker compose up --wait (reserving the read-only SDK poll strictly for the cross-project shared-stack gate), or should internal/health always own the poll for uniform --json records and a single timeout/backoff policy?
- Recommendation: own the poll uniformly; use
--waitonly as belt-and-suspenders within a project. - Decision (RESOLVED, M6):
internal/healthowns the poll uniformly for one--jsonrecord shape and one timeout/backoff policy;docker compose up --waitis used only as belt-and-suspenders within a single project.
Q-HOOK-SCOPE · firstRun data-volume identity — affects M6 (feature #5) (surfaced by spec 11)
Should firstRun's scope key be the provisioned (db, role) tuple (survives a Postgres image upgrade that keeps the volume) or the Docker named-volume id (re-arms whenever the volume object is recreated)? They diverge on the "upgraded the Postgres major but kept the data" path.
- Recommendation: the
(db, role)tuple, keyed alongside theprovisionedledger; revisit if image-upgrade re-seeding becomes a real workflow. - Decision (RESOLVED, M6): key
firstRunon the provisioned(db, role)tuple, stored alongside theprovisionedledger (survives a Postgres major upgrade that keeps the volume). Revisit only if image-upgrade re-seeding becomes a real workflow.
Q-DOCTOR-FIX · interactive tier for borderline repairs — affects M6 (feature #2) (surfaced by spec 13)
Should doctor --fix ever offer an interactive (non---yes) tier for borderline-destructive repairs (reassigning a port a foreign process holds, recreating a drifted stateless shared service), or is the safe/destroy line absolute?
- Recommendation: keep
--fixstrictly non-destructive; route everything else through the named teardown /sharedverbs. - Decision (RESOLVED, M6):
doctor --fixstays strictly non-destructive. Anything that could lose data or reassign a foreign-held resource routes through an explicit named teardown /sharedverb. No interactive borderline tier.
Q-SNAP-RETENTION · snapshot retention / prune policy — affects v2 (feature #7) (surfaced by spec 15)
The content-addressed snapshot store grows unbounded without a policy. What is the default retention — keep-last-N per (project,kind), a max-age, or a total-store-size budget — and should db snapshot auto-prune, or should pruning be an explicit db gc --snapshots only?
- Recommendation: explicit
db gc --snapshotsonly by default (never silently delete a developer's data); offer an opt-in keep-last-N per(project,kind)once the workflow is in real use. - Decision (RESOLVED, v2): explicit
db gc --snapshotsonly — never silently delete a developer's data. Offer an opt-in keep-last-N per(project,kind)once the workflow is in real use.
Q-SANITIZE-ENGINE · db pull sanitization mechanism — affects v2 (feature #7) (surfaced by spec 15)
Production-derived db pull dumps must be scrubbed before they land on disk. Is the sanitization transform a built-in declarative sanitize: profile (NULL/mask/hash/drop), a shell-out to an external anonymizer, or both behind one interface — given the single-static-binary constraint?
- Recommendation: built-in declarative
sanitize:profile for the common PII/token/payment cases (keeps the static-binary promise); an external-anonymizer seam behind the same interface as a later escape hatch. - Decision (RESOLVED, v2): built-in declarative
sanitize:profile (NULL/mask/hash/drop) for the common PII/token/payment cases (preserves the static-binary promise); an external-anonymizer seam behind the same interface as a later escape hatch.
Q-DASH-STATS · dashboard CPU/mem default on vs opt-in — affects v2 (feature #8) (surfaced by spec 16)
A ContainerStats stream per container is heavy and VM-skewed on Docker Desktop/WSL2. Should CPU/mem default on in the dashboard (richer cockpit) or be opt-in behind --stats (cheaper, accurate-by-omission)?
- Recommendation: on for
dashboardwith--no-statsto disable; off forlogs(it never needs stats). - Decision (RESOLVED, v2): stats on for
dashboardwith--no-statsto disable; off forlogs(it never needs stats).
Q-IDE-DEBUGPORTS · debug-port allocation lifetime — affects v2 (feature #9) (surfaced by spec 17)
Should debug: true allocate a persistent published host port (stable across up, so a committed launch.json stays valid — but consumes the registry + a host port even when no debugger attaches) or an ephemeral port requested only by ide gen/an explicit --debug up (frees ports but the attach value can drift between runs)?
- Recommendation: persistent per-
(project,service)debug port in the ledger, gated behinddebug: true; confirm against the port-budget concern on low-port hosts. - Decision (RESOLVED, v2): persistent per-
(project,service)debug port in the ledger, gated behinddebug: true, so a committedlaunch.jsonstays valid. Watch the port budget on low-port hosts; only reconsider an ephemeral mode if exhaustion becomes real.
Q-RESOURCES · memory-budget hard-fail tier — affects v2 (feature #10) (surfaced by spec 18)
The budget check warns when the declared sum exceeds the budget. Should up ever hard-fail above a second, higher threshold (e.g. declared sum > VM ceiling, guaranteeing OOM), or stay warn-only forever?
- Recommendation: warn-only in v2 (a dev may know their services won't all peak at once); revisit a
--strict-budgetopt-in only if OOM-during-upbecomes a common support ticket. - Decision (RESOLVED, v2): warn-only. A dev may know their services won't all peak at once. Add a
--strict-budgetopt-in only if OOM-during-upbecomes a common support ticket.
Q-REGISTRY · single- vs multi-template bundles — affects v2 (feature #11) (surfaced by spec 19)
Does template push support multi-template bundles (one artifact carrying a whole php.*/node.* family addressed by sub-path) or strictly one template per tag? Multi-template eases atomic team releases and shared extends: bases but complicates per-template digest pinning and partial cache GC.
- Recommendation: one artifact per template family with sub-path refs (
oci://…/templates:1.4.0//php.laravel), pinned by the family digest. Record before wiringpush. - Decision (RESOLVED, v2): one artifact per template family with sub-path refs (
oci://…/templates:1.4.0//php.laravel), pinned by the family digest. Locked beforepushis wired.
Q-SIGN-TRUST · cosign keyless vs keyed default — affects v2 (feature #11) (surfaced by spec 19)
Is cosign keyless (Fulcio/Rekor; requires reaching the public transparency log to verify) acceptable as the default for OSS teams, or must the default be keyed (a team-distributed public key, fully offline-verifiable)? Trades zero-key-management for an online verification dependency (relates to the offline-first posture in Q-DAEMON).
- Recommendation: keyed by default for offline determinism; keyless opt-in.
- Decision (RESOLVED, v2): keyed by default (offline, fully verifiable — consistent with the offline-first CLI posture); keyless opt-in for teams that want Fulcio/Rekor.
Q-TELEMETRY · default OTLP endpoint owner + privacy policy — blocks shipping telemetry (feature #12) (surfaced by spec 20)
Where does the default OTLP endpoint live, who operates it, and what is its data-retention + source-IP-dropping policy? This must be answered and published before telemetry ships — the default endpoint is the project's standing privacy promise.
- Recommendation: if no project-operated collector exists at ship time, default the endpoint to empty (telemetry can be enabled but goes nowhere until a self-hoster sets
telemetry.endpoint). - Decision (RESOLVED, feature #12): telemetry is opt-in and OFF by default, and the default OTLP endpoint is empty until a self-hoster sets
telemetry.endpoint. No project-operated collector ships until an owner + a published retention / source-IP-dropping policy exists. This keeps the privacy promise trivially true at ship time.
Q-REMOTE-LOCK · distributed lock + ledger authority — affects later (feature #13) (surfaced by spec 21)
A local gofrs/flock cannot serialize two machines against one remote backend. What is the DistLock + ledger-authority strategy: a Postgres advisory lock with the cluster DB as the source of truth, a dedicated coordinator daemon (reintroduces a daemon — Q-DAEMON), or an external lease store? Local SQLite degrades to a cache regardless.
- Recommendation: start with
pg_advisory_lockon the shared cluster Postgres (zero new infra, crash-safe, reusespgx/v5); confirm the ledger becomes remote-authoritative with local SQLite as a cache. - Decision (RESOLVED, later): start with
pg_advisory_lockon the shared cluster Postgres (zero new infra, crash-safe, reusespgx/v5); the cluster DB becomes the remote-authoritative ledger with local SQLite as a cache. Avoids reintroducing a coordinator daemon (consistent with Q-DAEMON).
Q-REMOTE-TENANT · tenant identity for per-user isolation — affects later (feature #13) (surfaced by spec 21)
On a shared cluster, what identifies a tenant for role/db/bucket naming and db gc ownership: the OS username, an explicit --as/config-declared team identity, or the cluster-auth identity? Naming the role after the project is not isolation when two developers both have a project app.
- Recommendation: an explicit config-declared/
--asteam identity (stable, collision-free, decoupled from local OS accounts); fall back to OS username only as a default seed. - Decision (RESOLVED, later): an explicit config-declared /
--asteam identity (stable, collision-free, decoupled from local OS accounts) keys role/db/bucket naming anddb gcownership; OS username is only a default seed.
devstack ai install writes into the repository, so the guidance is committed and
the whole team's tooling picks it up. A --global variant writing into
~/.claude/skills/ would make every project on the machine devstack-aware,
including ones that have no workspace yet.
- Recommendation: repo-scoped for v1. Global is a second install location with
its own uninstall story and a
devstack uninstallinteraction, and the repo-scoped files already cover the case that matters (a teammate cloning the repo). Revisit in v1.1 on concrete demand. - Decision (RESOLVED, M11): repo-scoped only;
--globaldeferred.
A plugin/ tree plus .claude-plugin/marketplace.json would let a user run
/plugin marketplace add open-source-cloud/devstack and get the skills and the
MCP server with zero files in their own repository.
- Recommendation: defer. It is a second distribution channel with its own
versioning and release surface, and its audience — people who want devstack's
agent integration — by definition already have the binary installed, which is
all
ai installneeds. Revisit once the emitted skills have stabilized against real usage. - Decision (OPEN, revisit v1.1): deferred; the emitter would generate the plugin tree from the same pack, so nothing here forecloses it.
- Ambition: open-source product.
- v1 scope: all four pillars (with the M0–M3 "core 1.0" phasing recommended in ROADMAP).
- Config: clean-slate schema (not a devdock drop-in).
- Capacity: solo, full-time (~40h/week).
- Compose generation: programmatic via compose-go, not string-templated YAML.
- Name: canonical binary
devstack; aliasesrq,uranus; module + GitHub repogithub.com/open-source-cloud/devstack(see Q-NAME). - All Q-* questions above are locked to their recommendations (2026-06-28) by owner delegation; revisit at the consuming milestone on concrete evidence only.