Public Access
Give the desktop a nightly channel, the way the phone has one
ADR 0014 gave the phone a nightly and ADR 0013 rule 3 gave the desktop none, so the two heads had different answers to the same question — how does somebody try what is on main? — for no reason except the order the work happened in. This is the desktop's answer: CI publishes a build from main on every push, and it installs beside the release one rather than over it. The phone gets its separation from the platform. Android refuses an update signed by a different key, so its two channels cannot replace one another whatever anybody does. Nothing refuses anything here: Velopack applies what its feed serves and verifies no signature. So all of it is construction, and there are four separations because each closes a different door. A pack id each, so the two install in different directories and neither feed's package can be applied to the other's install. A Velopack channel each — win and win-nightly — so neither build ever reads the other's release index; the name reaches the wire as releases.win-nightly.json, which is why the constant in VelopackUpdateChannel and the argument in ci.yml have to agree or the channel answers nothing forever with no error. A prerelease flag, so the release channel cannot see the nightly even by accident. And a profile directory each, which is the one that is easy to skip and would hurt most: the cache schema is migrated on every launch, before unlock, so a shared profile means a nightly quietly upgrading a database the release build then opens. Both are installed at once by design, so that is an ordinary Tuesday rather than a corner case. The prerelease flag turns out to be load-bearing across heads as well. The phone's release channel reads releases/latest, which skips prereleases — so a desktop nightly published as a stable release would become the newest release in this repository and every phone on the release channel would start failing its check against a release carrying no Android manifest. Which build this is arrives as assembly metadata, the same mechanism and the same reasoning as the Android head: the updater needs the string rather than a branch, and a value baked into the assembly is one a crash report can be asked for. Three things read it — the feed, the prerelease flag, and the profile — and one more shows it: the titlebar says DodoSSH Nightly. Everything else that distinguishes the two is somewhere nobody is looking while typing a passphrase into one of them. The version needed a floor and it is applied to the whole build rather than to the packaging. MinVer answers 0.0.0-alpha.0.N until the first v* tag and vpk refuses anything below 0.0.1, so the job lifts the patch digit and keeps the height — through MinVerVersionOverride, so the assemblies carry the same number the installer does. Packing a version the assembly disagreed with would put one string on the preferences screen and another in the feed, which is the screen somebody reads when asked which nightly they are on. Two things found by running it rather than reading it. -t:MinVer needs a restore first, because the target arrives with the package and MSB4057 on a clean checkout reads like a typo in the workflow rather than a missing restore; the release script had the same gap and now restores before it reads. And vpk rejects an empty --packVersion loudly, which is how a broken version handoff announces itself rather than shipping a package called 1.0.0. Rule 3 is untouched. The release channel still has no job, no token and no runner, and the two channels cannot see each other. What a nightly costs is written where somebody reads it before installing one: whoever can write a release here can put a build on every nightly machine, which is fine for a build being tried and is not fine for a build holding somebody's infrastructure credentials. Verified by running the job's own steps against a clone in a Linux container: DodoSSH.Desktop.Nightly-win-nightly-Setup.exe, and an index naming pack id DodoSSH.Desktop.Nightly at 0.0.1-alpha.0.144. The upload itself is the one step not exercised — it needs a real forge and a write token, and check 16.10 is what walks the half no runner can.
This commit is contained in:
@@ -228,6 +228,56 @@ changes.
|
||||
token, and it puts a compellable third party in the signing path — which is ADR 0011 rule 3's shape one
|
||||
layer down, declined there for reasons that do not stop applying because the vendor changed.
|
||||
|
||||
### 9. There is a second desktop channel, published by CI, and it is a second application
|
||||
|
||||
[ADR 0014](0014-android-updates.md) gave the phone a nightly channel and rule 3 above gives the desktop
|
||||
none, which left the two heads with different answers to the same question — *how does somebody try what
|
||||
is on main?* — for no reason other than the order the work happened in. This is the desktop's answer, and
|
||||
it is the phone's arrangement with one difference that changes how much of it has to be built.
|
||||
|
||||
**Android gets the separation from the platform. Windows does not.** Two Android channels cannot replace
|
||||
one another because the installer refuses a package signed by a different key; a mistake there is loud.
|
||||
Velopack applies what its feed serves and verifies no signature, so on this head the separation is entirely
|
||||
construction:
|
||||
|
||||
| | release | nightly |
|
||||
| --- | --- | --- |
|
||||
| pack id | `DodoSSH.Desktop` | `DodoSSH.Desktop.Nightly` |
|
||||
| Velopack channel | `win` | `win-nightly` |
|
||||
| feed | newest non-prerelease release | the rolling `nightly-desktop` prerelease |
|
||||
| profile | `%LOCALAPPDATA%\DodoSSH` | `%LOCALAPPDATA%\DodoSSH.Nightly` |
|
||||
| cut by | a person, from a `v*` tag | CI, on every push to main |
|
||||
|
||||
Four separations rather than one, because each closes a different door. The pack id decides the install
|
||||
directory and what an installed client matches an update against, so it is what makes a nightly install
|
||||
*beside* rather than *over*. The channel names the index file on the feed, so neither build ever reads the
|
||||
other's — and the release channel additionally refuses prereleases, which is belt and braces in the one
|
||||
direction that matters: an unsigned CI build must never reach a machine somebody is trusting with their
|
||||
credentials. The profile is the one that is easy to skip and would hurt most: the cache schema is migrated
|
||||
on every launch, before unlock, so a shared profile means a nightly quietly upgrading a database the
|
||||
release build then opens.
|
||||
|
||||
**The prerelease flag is also how the two heads stay out of each other's way.** The phone's release channel
|
||||
reads `releases/latest`, which skips prereleases. A desktop nightly published as a stable release would
|
||||
become the newest release in this repository, and every phone on the release channel would start failing
|
||||
its check against a release carrying no Android manifest. The nightly is published with `--pre` for its own
|
||||
sake and for that one.
|
||||
|
||||
**What this costs, stated where somebody will read it before installing:** whoever can write a release on
|
||||
this repository can put a build on every nightly desktop, because the client applies what the feed serves
|
||||
without verifying a signature. That set includes CI and therefore everyone who can change a workflow file.
|
||||
It is the same trade [ADR 0014](0014-android-updates.md) accepted for the phone's nightly, and it is
|
||||
acceptable for the same reason and only that reason: this is not the channel anybody's real credentials are
|
||||
on. Rule 3 is untouched — the release channel still has no job, no token and no runner.
|
||||
|
||||
**The version gets a floor, on this channel only.** MinVer answers `0.0.0-alpha.0.N` until the first `v*`
|
||||
tag and `vpk` refuses to pack anything below `0.0.1`, so the nightly job lifts the patch digit and keeps
|
||||
the prerelease height. It is applied through `MinVerVersionOverride`, which moves the assembly version too
|
||||
— packing a number the assemblies disagree with would put one version in the installer and another on the
|
||||
preferences screen, which is the screen somebody reads when asked which nightly they are running. Ordering
|
||||
across the floor holds: heights rise within a floored version, and the first real tag moves past all of
|
||||
them. The release script gets no floor and must not have one; there, `0.0.0` should be refused.
|
||||
|
||||
## Consequences
|
||||
|
||||
**The desktop gets what ADR 0011 had to refuse the phone.** Discovery is still manual — somebody has to be
|
||||
|
||||
@@ -104,6 +104,15 @@ is the entire reason there are two.
|
||||
other is an uninstall and a fresh enrolment. There is no migration and there will not be one — the two
|
||||
caches are encrypted under keys held by two package identities the platform keeps apart.
|
||||
|
||||
◆ **The desktop has since taken this arrangement, and it had to build what Android is given.**
|
||||
[ADR 0013](0013-desktop-distribution-and-updates.md) decision 9 is this ADR applied to Windows: a nightly
|
||||
CI publishes from main, installing beside the release build rather than over it. The difference worth
|
||||
carrying back here is that every separation this ADR gets from the platform — different package identity,
|
||||
signature-checked updates, a per-app data directory — is on Windows a thing somebody had to choose and can
|
||||
therefore undo. The paragraph above about the attack surface applies there word for word, with one line
|
||||
removed: on the desktop the packages are not signed at all, so the feed is the only thing standing between
|
||||
a nightly install and an arbitrary build.
|
||||
|
||||
**ADR 0011's "no automatic update" consequence is now wrong for the release channel and remains true for
|
||||
reach.** Discovery, MDM deployment and the sideloading permission are all unchanged. What changed is only
|
||||
that an installed copy can now learn a newer one exists.
|
||||
|
||||
@@ -2005,6 +2005,29 @@ is wrong. Reinstalling then asks for the passphrase rather than for a server.
|
||||
lost their offline unlock and any change that was still in the outbox. That is the failure ADR 0013
|
||||
decision 2 exists to prevent, and it is why 16.1 checks the same thing from the other end.
|
||||
|
||||
### 16.10 The nightly installs beside the release build and not over it · **the one CI cannot check**
|
||||
|
||||
Needs both: a release build installed by walking 16.1, and `DodoSSH.Desktop.Nightly-win-nightly-Setup.exe`
|
||||
from the `nightly-desktop` release. Install the nightly second, and then start **both**.
|
||||
|
||||
**Pass:** two entries in the Start menu, one *DodoSSH* and one *DodoSSH Nightly*; two directories,
|
||||
`%LOCALAPPDATA%\DodoSSH.Desktop` and `%LOCALAPPDATA%\DodoSSH.Desktop.Nightly`; two profiles,
|
||||
`%LOCALAPPDATA%\DodoSSH` and `%LOCALAPPDATA%\DodoSSH.Nightly`, each with its own `cache.db` and
|
||||
`device.key`. The nightly's titlebar says **DodoSSH Nightly** and the release build's says **DodoSSH** —
|
||||
which is the only difference visible while somebody is typing a passphrase into one of them. Signing in to
|
||||
the nightly leaves the release build signed in and untouched, and the account shows a second device.
|
||||
|
||||
**Failure means:** if there is one profile directory, the two builds are sharing a cache and a device key,
|
||||
and the next nightly carrying a schema migration will upgrade the database the release build opens. If
|
||||
there is one install directory, the pack ids collide and the nightly has replaced the release build
|
||||
outright — which is the thing ADR 0013 decision 9 is constructed to make impossible, so it means one of the
|
||||
four separations has been undone.
|
||||
|
||||
**And the direction that matters most:** on the release build, PREFERENCES → UPDATES → CHECK NOW must not
|
||||
offer a nightly, ever, however many have been published since. It reads a different index and refuses
|
||||
prereleases; if a nightly version is ever offered there, stop and treat it as a release-channel incident
|
||||
rather than as a bug in the nightly.
|
||||
|
||||
---
|
||||
|
||||
## Phase 17 — Installing and updating the phone
|
||||
|
||||
+17
-3
@@ -595,9 +595,23 @@ targeted, not which RID. Only signing needs Windows tooling, which is why `ci.ym
|
||||
without a target *evaluates* the project and runs nothing, while MinVer computes the version inside a
|
||||
target — so the read comes back as the SDK default on a full checkout with every tag present, which looks
|
||||
exactly like a version that was never configured. `-t:MinVer` makes `-getProperty` report the value after
|
||||
that target has run, and both readers of it — the tag check in `ci.yml` and `scripts/release-windows.ps1`
|
||||
— pass it. Neither had, and neither had ever run: the CI check is `if:` a tag ref and there are no tags
|
||||
yet, so the first release would have been refused by its own guard, which would have blamed `fetch-depth`.
|
||||
that target has run, and every reader of it — the tag check in `ci.yml`, the desktop nightly job, and
|
||||
`scripts/release-windows.ps1` — passes it. Neither of the first two had, and neither had ever run: the CI
|
||||
check is `if:` a tag ref and there are no tags yet, so the first release would have been refused by its own
|
||||
guard, which would have blamed `fetch-depth`.
|
||||
|
||||
**And naming that target requires a restore first, which is a second failure wearing a very different
|
||||
face.** MinVer arrives as a package, so its target is imported from `obj/*.nuget.g.targets` and does not
|
||||
exist at all on a clean checkout:
|
||||
|
||||
```
|
||||
error MSB4057: The target "MinVer" does not exist in the project.
|
||||
```
|
||||
|
||||
That reads like a typo in the workflow rather than like a missing restore, and it does not reproduce on any
|
||||
machine that has built the project before — which is every developer machine and no fresh runner. The
|
||||
build job's tag check is safe because it runs after that job's own restore; the desktop nightly job and the
|
||||
release script each restore before reading, deliberately and with a comment saying why.
|
||||
|
||||
**The Android head's lock file is outside the solution, so nothing checks it until the android job runs
|
||||
— and the android job was broken for an unrelated reason for the whole of the release that went stale.**
|
||||
|
||||
Reference in New Issue
Block a user