From 3b058f9332d46adb2fd1612b51b2aaace7108980 Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Wed, 2 Sep 2026 22:10:04 -0400 Subject: [PATCH] Notes: one carry-across, and the built-in card taking its own update Co-Authored-By: Claude Opus 5 --- AGENTS.md | 66 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3adee99..7cff35a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 that until things actually can be updated." -- **What a build recorded is carried across the self row being - re-derived, like the settings beside it.** `reconcile_self` rebuilds - this server's own row from its declaration at every startup, and - `built_from`/`built_mode` were not among the things carried -- 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. Its own card then 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. `built_records` / - `restore_built` carry it, keyed by name *and* directory like every - other carry-across here. +- **Two places rewrite a project's components list, and one type says + what survives it.** `AppState::approve_declaration` and + `registry::reconcile_self` both replace `components` wholesale with + what the checkout declares -- right for everything the project asked + for, wrong for the three things this machine knows: the package read + out of an APK, the mode somebody chose here, and the commit and mode a + build recorded. `CarriedOver` / `carried_over` / `restore_carried_over` + hold all three, keyed by name *and* `cwd` like every other + carry-across here, and both callers take the whole thing. + It is one type because it was three collect-and-restore pairs + 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 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 before any route is reached: those stay one-way doors and `--download` 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: 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