Notes: one carry-across, and the built-in card taking its own update

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-02 22:10:04 -04:00
1 parent a8f75be190
commit 3b058f9332
1 file changed
+55 -11
+55 -11
View File
@@ -605,17 +605,32 @@ mutable at runtime from the phone.
Update is pressable. Iris's rule for it, 2026-09-02: "it should not do Update is pressable. Iris's rule for it, 2026-09-02: "it should not do
that until things actually can be updated." that until things actually can be updated."
- **What a build recorded is carried across the self row being - **Two places rewrite a project's components list, and one type says
re-derived, like the settings beside it.** `reconcile_self` rebuilds what survives it.** `AppState::approve_declaration` and
this server's own row from its declaration at every startup, and `registry::reconcile_self` both replace `components` wholesale with
`built_from`/`built_mode` were not among the things carried -- so the what the checkout declares -- right for everything the project asked
commit written down by the build that produced the new binary was for, wrong for the three things this machine knows: the package read
forgotten by the process that build started, and restarting is how this out of an APK, the mode somebody chose here, and the commit and mode a
server takes an update. Its own card then had no freshness ever again: build recorded. `CarriedOver` / `carried_over` / `restore_carried_over`
never behind, never current, and its Update with nothing to do, since hold all three, keyed by name *and* `cwd` like every other
both readings need a commit to compare against. `built_records` / carry-across here, and both callers take the whole thing.
`restore_built` carry it, keyed by name *and* directory like every It is one type because it was three collect-and-restore pairs
other carry-across here. remembered separately, and each caller forgot a different one.
`reconcile_self` dropped `builtFrom`, so the commit written down by the
build that produced the new binary was forgotten by the process that
build started -- and restarting is how this server takes an update, so
its own card had no freshness ever again: never behind, never current,
and its Update with nothing to do, since both readings need a commit to
compare against. Then `approve_declaration` turned out to drop the same
field, which is worse because it hits *every* project: accepting a
declaration told every one of that project's cards it had never been
built here. Silent both times, because "not built here" is a true
sentence about plenty of projects and so reads as an answer rather than
as a loss -- it was found by making freshness say which kind of unknown
it was and then looking at a card that had no business saying that one.
Adding a field to a component now means adding it here once.
`reconcile_self` sets the built-in row's package *after* the restore,
so what is derived wins over an older row carrying something else.
- **One predicate decides both the "Up to date" heading and whether - **One predicate decides both the "Up to date" heading and whether
Update can be pressed** (`hasWorkWaiting` in `UpdaterScreen.kt`): they Update can be pressed** (`hasWorkWaiting` in `UpdaterScreen.kt`): they
@@ -1220,6 +1235,35 @@ mutable at runtime from the phone.
survive a changed CA, port or token, since those break the connection survive a changed CA, port or token, since those break the connection
before any route is reached: those stay one-way doors and `--download` before any route is reached: those stay one-way doors and `--download`
stays their answer. stays their answer.
**It is also how the built-in card downloads its own APK**, not just
how the rescue dialog does. `downloadApk` sends the built-in project to
`/self/apk` rather than to the manifest's route, because this is the
one download whose server changed underneath it between the build and
the request -- so it takes the route nothing can rename. It carries no
`component` or `variant` and does not need to: that project has one
APK, and which build of it to serve is the mode set on the build
machine.
**And the card's Update installs this app itself**, rather than
building it and leaving a dialog to offer it. Which means it waits for
the server *twice* (`awaitServerBack`): once before fetching, and again
before handing anything to the installer. The restart is deferred a
couple of seconds past the build and then held off for as long as an
APK is going down the wire, so it lands either just before the download
or in the gap between the last byte and the install -- a download that
dies partway reads as the update having failed at the moment it worked,
and an install offered mid-restart replaces this app while the server
it must talk to is down. Bounded, and it goes ahead anyway when the
wait runs out: by the second one the APK is already here, and never
installing it would be the worse failure.
**The dialog is now offered only when the built-in card cannot offer
it** -- that card missing from the list, or no list at all because this
app is too old to read what the server now sends. `AppListScreen`
derives that from `manifestState` and reports it up; `Loading` counts
as present, since not having found out yet is not an answer and a
dialog flashing up during every load is exactly the noise it exists to
avoid. Iris's call, 2026-09-02: the card's own Update should do the
work, "though keep the modal if the dev updater card itself is missing
or fails to load."
- **There is deliberately no general migration mechanism.** Iris's call: - **There is deliberately no general migration mechanism.** Iris's call:
each app decides its own, and the updater's job is only to be able to each app decides its own, and the updater's job is only to be able to
get both halves of *itself* to the next version. A project that renames get both halves of *itself* to the next version. A project that renames