Add verified household TV updates and release tooling #60
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fm/tv-updater-core"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Safety review and UI contract delta
Two separate pushed commits:
383802611cda836a9cac017f65acf609c73bf169.51799669e7a47cdb9dc11247884a0ff32d2ffbcb(current head).The publisher now reads live latest/manifest anonymously before draft creation or resume and immediately before promotion, refusing equal/lower codes. A latest 404 permits a first release only after an empty release list succeeds. The builder fetches main and checks committed HEAD/main ledgers plus durable local consumed-code refs; discarding a ledger append cannot free a consumed code. Direct optimized diagnostics use code 1 and must never be installed on the household box. The PackageInstaller stream is hashed and length-checked against the verified asset before commit; mismatch abandons the session. A manifest-signer-differs regression is included.
UI-facing shape now includes
release: StateFlow<UpdateUiState.Available?>(including persisted notes/date/size after verified Ready recreation) and requiredFailed(kind, message, operation: UpdateOperation)withCheck,Download,Install. The fake matches the UI lane's additiveinitialReleaseshape. Old manifest-only Ready records remain readable. Permission and handoff cancellation restores Ready without discarding verified bytes; installer ownership is independent of visible Ready and still blocks duplicate handoffs, including late pending callbacks and success awaiting PackageManager refresh. Install from PermissionNeeded reopens settings.Current delta verification: release-tooling fixtures 24 passed; focused controller/session-stream cases 29 passed at the UI-seam head. Actual loopback publish CLI, compiled staged-byte integrity and compiled UI-contract smokes passed. The UI smoke proved notes/date/size after recreation, repeated permission settings opening, Ready dismissal with only one owned session, and structured Install failure.
Exact-head gate evidence accepted by firstmate (instruction 006): the single
make testinvocation at51799669e7a47cdb9dc11247884a0ff32d2ffbcbcompleted 976/977 passing, with onlySavedPeoplePickerTest.savedPeopleAreVisibleBeforeTheFirstNetworkResponsetiming out at line 72. Firstmate identifies this as a known pre-existing main timing flake, fixed by the saved-picker Android-main-queue synchronization change onfm/tv-round2; no repair or rebase belongs in this PR. Firstmate authorized exactly one retry of onlyorg.justinvisser.wolfskin.tv.SavedPeoplePickerTest: 1/1 passed, zero failures/errors/skips, at the exact same head. No test/source edit or full-suite rerun was made. This records the accepted composite evidence, not a claim that the original full invocation was green.The older full-suite/debug results below belong to
c78a8dfa. No new APK build, box action, rebase, real publication or repository creation occurred. Waiting for firstmate's Opus delta statuses and Justin's go for main; this worker does not merge.Outcome
AI-assisted (fork-internal). Add the verified household update core and release tooling for
org.justinvisser.wolfskin.tv.opt; no distribution repository or release is created by this change.Shared UI interface
The early interface commit (
152dac97) definesUpdateController,UpdateUiState,UpdateFailure,InstalledVersionandFakeUpdateControllerinorg.justinvisser.wolfskin.tv.update. The controller exposes state, installed identity, persisted auto-check preference, check/download/cancel/install/retry and resume handling. The separate Settings UI can bind to that interface. The classic Opt updater delegates to the same service rather than retaining an unsafe second download path.Applied decisions
defaultOptimizedassembly. The stable name is1.1.1; an explicit monotonic code is mandatory. The proposed first code100001exceeds the supplied installed inventory (Opt 65, production 28, Wholphin 62). The tracked ledger starts with used code 65; a dry-run does not reserve a new code.justinzeus/wolfskin-tv-updates. Migrate only the known old default for Opt, preserve custom URLs, and read installed-release notes from the same channel. The oldorg.justinvisser.wolfskin.tvpackage keeps its original repository, parser and legacy installer path; its behavior tests remain.make tv-release VERSION=1.1.1 VERSION_CODE=<n>requires a clean commit and the existing key, refuses used/lower codes, inspects final signed APKs, and writes four standalone APKs, actual hashes, manifest and notes.make tv-publishis separate: draft upload, byte verification, explicit--promote, anonymous post-promotion verification and draft withdrawal on verification failure. Credential-helper output stays in process memory with tracing disabled. Document signer authority, source offers, ledger reservation and keep-data policy indocker/README.md.OwnedSourceAuditTest; keep behavioral/rendered capture and token-drift coverage. Also replace the adapter's raw spacing with its existing design token.Verification
Final head:
c78a8dfa8dc8b9b6ce89673cee8f0116e3c78a78(pushed).make testtotalThe app total includes 49 updater-related cases: controller transitions and HTTP/error/credential/persistence boundaries, manifest/ABI checks, code ordering and legacy channel/parser behavior.
make apk-debugpassed. Four debug APKs exist for packageorg.justinvisser.wolfskin.tv.debug; these use the MPV stub and are not distribution builds.apksigner verify --print-certson the actual universal debug APK passed and matched the required household certificate SHA-256182bac72e292b9ea4d3d4e6383f92eb2161fa28ba5afcff0337cb7d5a9d5341d.make tv-release VERSION=1.1.1 VERSION_CODE=100001 ... --dry-runpassed at this head, without building, tagging, reserving a code or publishing.bash -npassed for the tooling. Temporary smoke scaffolds were removed; proof remains outside the checkout.Not in this change
justinzeus/wolfskin-tv-updates, any real draft/upload/publication, or a real optimized release build.No product decisions remain for Justin in this core change. Installer/signature and cross-family reviews are arranged by firstmate; this pull request does not merge itself.
Safety review (Tier C, Opus): BLOCKING, 1 finding
Reviewed head:
c78a8dfa8dc8b9b6ce89673cee8f0116e3c78a78. Statusreview/safety-opus= failure on this SHA. I found no path that installs an unverified, wrong-signer, wrong-package, equal-code or lower-code APK, deletes or resets data, forces an uninstall, leaks a credential, or publishes without--promote. The one blocker is in the release tooling: a used or lower versionCode can reach the channel.Blocking
B1. A used or lower versionCode can be released and published.
tv_release.pyvalidate_inputstakesmax()from the working-treedocker/tv-version-codes, andtv_publish.pyhas no monotonic check. Four ways through:git checkout -- docker/tv-version-codesafter a release resets the reservation.-PtvVersionCode=100001, the proposed first release code; a box bootstrapped with it would treat the real release as "Up to date".tv_publish.pypublishes any code, including one at or below the current latest.Probe with the real
validate_inputs:stale branch -> ACCEPTED,after discarding ledger append -> ACCEPTED, while main with a committed 100001 is refused. Devices refuse equal or lower codes (client and Android), so the impact is a stuck or split channel, not data loss.Fix: (a) before draft creation and again before
--promote,tv_publish.pyfetches the anonymous latestwolfskin-update.jsonand refuses unless bundle code > latest code (404 with an empty list = first release); (b)tv_release.pyalso takes the max fromorigin/main:docker/tv-version-codes; (c) change the README direct-build example away from a real release code.Non-blocking
install()verifies the file, thenAndroidUpdatePlatform.installre-reads it. The file is app-private, operations are serialized, and Android enforces signer, package and no-downgrade. Hardening: hash while streaming into the session and abandon on mismatch.UpdateChannel.SIGNERis unused in production. Trust is archive == installed == manifest signer, which is correct; a box with an older signer gets WrongSigner, not an install. Pin explicitly or delete the constant.browser_download_urlor redirect targets (any HTTPS). Acceptable given hash plus signer continuity.check();compatibleArm32IsPreferredAndUniversalRespectsMinimumSdkfails on ABI, not minSdk. The app allows cleartext app-wide, sovalidateUrlis the only HTTPS gate; it is correct.OwnedSourceAuditTestdeleted (both tests): style, token, font-floor and household-label wording rules. Nothing safety-relevant lost; unrelated to the updater.AppUpgradeHandler.kt:456-477clears current server and user whencurrent <= 1.0.8. Require VERSION ≥ 1.1.1 and ≥ the last ledger name.tv-releasecreates the local, unpushed tagpwolfskin-opt-v<VERSION>as part of the explicit command.tv_publishshould verify that the remote source tag exists and points atsourceCommitbefore--promote(otherwise the GPL source link can 404).--promote.docker/README.mdis shared, with #59).fm/tv-updater-uisits on152dac97only. It addsUpdateController.releaseand changescancel()semantics, which this head'sHouseholdUpdateControllerdoes not implement, so the rebase needs controller (core) changes and a delta safety check.adb uninstall -k"if unavoidable". Outside this diff; FYI.install()can still reach the user-confirmed session.Confirmed safe:
-d, uninstall orpm clearanywhere.--promote→ anonymous verify → withdraw on failure.Safety review delta (Opus): PASS. B1 closed
Reviewed head:
51799669e7a47cdb9dc11247884a0ff32d2ffbcb. Scope:git diff c78a8dfa..51799669only (the monotonic-release and staged-integrity fix38380261, plus the UI contract seams51799669). Statusreview/safety-opus= success on this SHA.B1 (reused or lower versionCode): closed on all four paths
validate_inputsfetchesorigin/main(and refuses if the fetch fails) and takes the max across the HEAD ledger, theorigin/mainledger and local reservation refs. Re-running my throwaway probe against the new code: a stale branch with a code reserved on main is refused.git update-ref refs/wolfskin-tv/version-codes/<code>reserves the code. Probe: discarding the ledger append is refused, also from a stale branch, and reserving the same code twice is refused.-PtvVersionCode=1, and direct optimized builds are documented as never installed or published.require_newer_codereads latest and its manifest anonymously, before any credential or draft and again immediately before--promote. The bundle code must be strictly greater. A 404 counts as a first release only when the release list is empty; anything else fails closed. Tooling tests run with plainpython3 -m unittest: 24/24 OK, including equal/lower refused, channel advance blocks promotion, and non-empty 404 fails closed.Earlier note 1 (verify-to-session TOCTOU): closed.
copyVerifiedUpdatehashes and counts the exact bytes streamed into the session and refuses before commit; the session is abandoned and Ready cleared.Seams keep the invariants
installerActiveis set beforeplatform.installand folded intosystemOwnsFlow.install(),check()anddownload()all refuse while the system installer owns the session.download.part, keepsverified.apkandready.json, restores Ready, and never installs.releaseNotesis an optional extra field; old manifest-only records still parse (ignoreUnknownKeys, tested).restorere-hashes and re-inspects before any Ready, so notes cannot make an unverified artifact Ready.Non-blocking
abandonSession, so the system confirmation can still appear; the user must still confirm. If no outcome ever arrives,installerActivestays latched for the process lifetime: Install, Check and Download silently do nothing while the screen shows Ready. Liveness only.origin/mainledger plus the live-channel gate.AppUpgradeHandlerresets whencurrent <= 1.0.8), 7 (remote source-tag check) and 9 (exact asset set on resumed drafts) remain open, non-blocking.