Acquisition coherence — 4 design questions for team sign-off
Status issue: #29 · Microsoft tracking: microsoft#15949 · Runbook: https://gist.github.com/radical/86bce917075fd87e684be5c3aecb0c37
These four decisions block implementation work in B3 (#40), B4 (#41), B5 (#42), and the --break-install path in B2 (#39). I'm batching them so the trade-offs can be weighed against each other in one pass instead of four separate threads.
Each question has my recommended lean. Sign-off needed on the lean, or pick a different option.
Q3 — Channel scope (#34)
Where does aspire.config.json's channel field live and who reads it?
| Option |
What it means |
Pro |
Con |
| A — Global, last-writer-wins |
Today's behavior |
Simplest impl |
Causes W34: S.u --channel daily poisons W's view of channel |
B — Per-route (own section under installs.<route>.channel) |
Each route reads its own |
Clean isolation; no cross-route bleed |
Need migration; project-level aspire update must pick a route to read from (active PATH binary) |
| C — Hybrid: global default + per-route override |
Project commands read route override if present, else global |
Backward compatible; isolates explicit overrides |
More config code paths; harder to reason about |
Lean: B. Cleanest semantics; the only reason for global-default (C) is migration, and migration cost is low (one-shot read-old-write-new on first run after upgrade). PATH-active binary is the right "active route" definition.
Q4 — Hive scope (#35)
Where do PR-script and dev-channel SDK hives live, and how are they isolated?
| Option |
Layout |
Pro |
Con |
A — Single shared ~/.aspire/hives/ keyed by hive ID |
Today's layout |
Simplest |
Routes don't own their hives; leakage on uninstall |
B — Route-scoped: ~/.aspire/hives/<install-id>/<hive-id>/ |
Each route owns its hives |
Uninstall is just rm -rf <install-id>/; clean |
Disk usage if multiple routes use the same NuGet content |
| C — Content-addressed dedup with route-owned references |
hives/by-content/<hash>/, route dir has symlinks/manifests |
No duplication; clean ownership |
Significant impl complexity for v1 |
Lean: B. Aligns with the install-id model from B0 (#33); makes uninstall trivial. Disk dup is acceptable for v1; revisit C in v2 if it actually hurts.
Q6 — Running CLI vs --self semantics (#36)
What does aspire update --self do when other aspire processes are running?
| Option |
Behavior |
Pro |
Con |
| A — Refuse hard |
Print "N aspire processes running, stop them first" and exit |
Safe; never corrupts a running process's bundle |
User friction; common case is "I forgot a long aspire run in another tab" |
| B — Warn and proceed (atomic flip) |
Warn, do versioned bundle install, atomic binary swap; running CLIs keep their old bundle dir |
UX-friendly; standard package-manager behavior |
Requires correct atomicity (today's reparse flip is non-atomic on Windows — gap in B5) |
C — Warn, proceed, with --force-while-running to suppress warning |
Same as B but explicit opt-in to silence |
Best of both |
Adds a flag |
Lean: C. Same as --break-install philosophy — warn loudly by default, give an opt-out for users who know what they're doing. Conditional on B5 (#42) closing the Windows atomicity gap — without that, we should pin to A on Windows.
Q7 — Self-update on package-manager-installed binaries (#37)
What happens when aspire update --self runs from a binary owned by winget/brew/dotnet-tool?
(dotnet-tool already redirects. This is about W and H.)
| Option |
Behavior |
Pro |
Con |
| 1 — Silent clobber (today) |
Replace the pkg-mgr-owned binary |
Works for end users who don't know/care |
Corrupts pkg-mgr state (W1 in bae142); next winget upgrade/brew upgrade is unpredictable |
| 2 — Refuse, redirect |
Print "this is a winget install; run winget upgrade Microsoft.Aspire" and exit |
Pkg-mgr stays consistent |
Some users genuinely want a non-stable channel and pkg-mgr only ships stable |
| 3 — Refuse + side-install |
Refuse to mutate W's binary; install S-style copy at ~/.aspire/bin/aspire so PATH precedence makes it active |
Both routes coherent; user gets daily |
Creates a "shadow install" that user didn't ask for; confusing |
4 — Refuse with explicit --break-install to opt into option 1 behavior |
Default refuse; flag opts into clobber + warning |
Honest; user owns the consequence |
Users may not discover the flag |
5 — Refuse + --break-install, plus suggest the script route in the refusal message |
Same as 4, but the refusal message includes "or use bash <(curl -fsSL https://aspire.dev/install.sh) --channel daily" |
Best UX: refuses by default, points users at the right tool, escape hatch exists |
None significant |
Lean: 5. Naming is --break-install (mirrors pip --break-system-packages honesty). See bae142 W1 for evidence the silent clobber is a real bug.
Cross-cutting impact summary
| Decision |
Blocks |
| Q3 |
B3 #40 (channel write semantics), B2 #39 (notification text per route), B4 #41 (uninstall channel sweep) |
| Q4 |
B4 #41 (uninstall sweep includes hives), B5 #42 (storage layout) |
| Q6 |
B2 #39 (--self flow), B5 #42 (atomicity requirement validates the lean) |
| Q7 |
B2 #39 (the whole --break-install code path) |
If you sign off on all 4 leans, B0 + B1 are already feasible and B2/B3/B4/B5 unblock immediately. If you push back on any, comment on the relevant question issue (#34, #35, #36, #37); I'll re-thread there.
Acquisition coherence — 4 design questions for team sign-off
Status issue: #29 · Microsoft tracking: microsoft#15949 · Runbook: https://gist.github.com/radical/86bce917075fd87e684be5c3aecb0c37
These four decisions block implementation work in B3 (#40), B4 (#41), B5 (#42), and the
--break-installpath in B2 (#39). I'm batching them so the trade-offs can be weighed against each other in one pass instead of four separate threads.Each question has my recommended lean. Sign-off needed on the lean, or pick a different option.
Q3 — Channel scope (#34)
Where does
aspire.config.json'schannelfield live and who reads it?S.u --channel dailypoisons W's view of channelinstalls.<route>.channel)aspire updatemust pick a route to read from (active PATH binary)Lean: B. Cleanest semantics; the only reason for global-default (C) is migration, and migration cost is low (one-shot read-old-write-new on first run after upgrade). PATH-active binary is the right "active route" definition.
Q4 — Hive scope (#35)
Where do PR-script and dev-channel SDK hives live, and how are they isolated?
~/.aspire/hives/keyed by hive ID~/.aspire/hives/<install-id>/<hive-id>/rm -rf <install-id>/; cleanhives/by-content/<hash>/, route dir has symlinks/manifestsLean: B. Aligns with the install-id model from B0 (#33); makes uninstall trivial. Disk dup is acceptable for v1; revisit C in v2 if it actually hurts.
Q6 — Running CLI vs
--selfsemantics (#36)What does
aspire update --selfdo when otheraspireprocesses are running?aspire runin another tab"--force-while-runningto suppress warningLean: C. Same as
--break-installphilosophy — warn loudly by default, give an opt-out for users who know what they're doing. Conditional on B5 (#42) closing the Windows atomicity gap — without that, we should pin to A on Windows.Q7 — Self-update on package-manager-installed binaries (#37)
What happens when
aspire update --selfruns from a binary owned by winget/brew/dotnet-tool?(dotnet-tool already redirects. This is about W and H.)
winget upgrade/brew upgradeis unpredictablewinget upgrade Microsoft.Aspire" and exit~/.aspire/bin/aspireso PATH precedence makes it active--break-installto opt into option 1 behavior--break-install, plus suggest the script route in the refusal messagebash <(curl -fsSL https://aspire.dev/install.sh) --channel daily"Lean: 5. Naming is
--break-install(mirrorspip --break-system-packageshonesty). See bae142 W1 for evidence the silent clobber is a real bug.Cross-cutting impact summary
--selfflow), B5 #42 (atomicity requirement validates the lean)--break-installcode path)If you sign off on all 4 leans, B0 + B1 are already feasible and B2/B3/B4/B5 unblock immediately. If you push back on any, comment on the relevant question issue (#34, #35, #36, #37); I'll re-thread there.