Commit Graph
83 Commits
Author SHA1 Message Date
Thomas SharedInboxandClaude Sonnet 4.6 839a3c63f9 feat: keep secrets in sync via age-encrypted master key (#208)
Store all production secrets encrypted in secrets.age (committed to the
repo) using age. Only one secret needs to be in CI: SECRETS_AGE_KEY.

When a secret changes locally, update secrets.env and re-run
scripts/secrets-encrypt.sh to commit a new secrets.age. CI picks up the
updated secrets automatically on the next push — no manual CI variable
updates required.

Changes:
- scripts/secrets-encrypt.sh: encrypt secrets.env → secrets.age
- scripts/secrets-decrypt.sh: decrypt secrets.age → GITHUB_ENV (CI) or
  eval-safe export block (local)
- scripts/test_secrets.sh: encrypt/decrypt round-trip tests
- secrets.env.example: template documenting all production secret keys
- ci/main.go: add CheckSecrets function (runs test_secrets.sh via Dagger),
  wire into Check(), update Graph(); add age to toolchain apt packages
- .forgejo/Dockerfile: add age package
- .forgejo/workflows/deploy.yml: replace per-secret CI references with a
  single "Decrypt production secrets" step using SECRETS_AGE_KEY
- flake.nix: add age to dev shell
- Taskfile.yml: add check-secrets task, include in check-fast
- .gitignore: ignore plaintext secrets.env
- DAGGER.md: document Option 5 (encrypted secrets file) as active approach

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:32:27 +02:00
Bot of Thomas Güttler 7dd5800064 perf: cache Linux engine artifacts via flutter precache --linux (#129) (#218) 2026-05-24 14:30:07 +02:00
Bot of Thomas Güttler 37eca207c6 fix: pin SSH host key via known_hosts instead of StrictHostKeyChecking=no (#161) (#181) 2026-05-24 13:00:04 +02:00
Bot of Thomas Güttler 30bcc8a314 fix: skip CI jobs when unrelated files change (#144) (#207) 2026-05-24 08:30:10 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 7d393ec818 fix: switch Play Store upload from httplib2 to requests
httplib2 treats 308 Resume Incomplete responses (used by Google's
resumable upload API) as redirects and raises RedirectMissingLocation
when the response lacks a Location header. Switch to
google.auth.transport.requests.AuthorizedSession + direct HTTP calls
so the upload uses the requests library, which handles 308 correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 07:32:22 +02:00
Bot of Thomas Güttler 83060bc1bf fix: add timeout and retries to Play Store upload (#185) (#195) 2026-05-24 04:45:07 +02:00
Bot of Thomas Güttler 14342f6472 fix: use exact grep patterns for build_runner and flutter pub get (#136) (#159) 2026-05-23 17:25:08 +02:00
Bot of Thomas Güttler b86c1a5c69 fix: verify Hugo binary SHA-256 checksum after download (#162) (#182) 2026-05-23 17:10:11 +02:00
Bot of Thomas Güttler e37d8066cb fix: prevent Gradle daemon hang in Android test build (#155) (#178) 2026-05-23 15:45:08 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 7e234b4835 fix(ci): chmod 700 /root/.ssh in GenerateBuildHistory container
Dagger mounts the secret file with 0600 but the parent directory may
get created with world-readable permissions, causing SSH to refuse
the key with exit 255.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 12:09:35 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 b6a2f91820 security: fix log/state file permissions, Firebase key on disk, TLS cleanup
- agent_loop.py: create log dir with mode 0700 and enforce it on
  existing dirs; open log files with mode 0600; chmod state file
  to 0600 after every write. Prevents other local processes from
  reading agent output (which may contain credential paths) or
  tampering with the state file's pid field.

- ci/main.go (TestAndroidFirebase): replace
    echo "$FIREBASE_SA_KEY" > /tmp/key.json
  with bash process substitution
    --key-file=<(echo "$FIREBASE_SA_KEY")
  The key is now passed via a file descriptor — it never touches
  disk, so it cannot be stranded by a failed gcloud auth call or
  snapshotted into the Dagger layer cache.

- ci.yml / deploy.yml: add "Cleanup TLS credentials" step
  (if: always()) at the end of every job that calls
  setup_dagger_remote.sh. Removes /tmp/dagger-tls,
  /tmp/stunnel-dagger.conf, /tmp/stunnel.pid from the self-hosted
  runner after each job, so client certs do not accumulate between
  job runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 10:54:53 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 509a0bc954 fix(ci): remove Gradle cache mount from pubGetLayer()
flutter pub get is pure Dart — it never invokes Gradle. The mutable
gradle-cache volume mount caused the same execution-cache instability
we just fixed for the pub cache: Dagger sees a changed volume and
cache-misses pubGetLayer() on every run.

The Gradle cache stays in Base(), which is only used for steps that
actually build Android code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 10:15:39 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 6cfc3dfda4 fix(ci): remove pub cache volume from Base() and pubGetLayer()
The mutable flutter-pub-cache volume made the execution cache key unstable —
pub get cache-missed every run because the volume's mutable layer changed the
snapshot hash.  Removing the volume lets Dagger snapshot packages inside the
execution-cache layer, which is stable and reclaimable via dagger prune.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 10:11:08 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 1a7b585dd4 fix(agent-loop): filter issues by author; comment when setting State/Question (#158)
- Only pick up issues created by guettli, guettlibot, or guettlibot2
  to prevent the loop from acting on external/bot issues.
- Post an explanatory comment on the issue whenever the loop sets
  State/Question (agent killed, no CI run, no push detected), so the
  reason is visible without digging through cron logs. Closes #158.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 10:04:44 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 959ce92a69 fix(ci): drop false-positive 'error' grep in Firebase test check
Firebase CLI emits "A non-retryable error occurred." even for passing runs.
The grep -qwi 'error' triggered on this message despite gcloud exiting 0
and the result table showing Passed. The gcloud exit code, device-count,
and Passed checks are sufficient to detect real failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:22:25 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 7e3a63f507 ci: validate gcloud auth stderr, fail on 'error' in output, check test count (#145)
- Capture gcloud auth stderr separately and fail on unexpected output;
  ignore the two known informational lines ("Activated service account
  credentials for: [...]" and "Updated property [core/project].") while
  keeping a strict "fail if unknown stderr" check for anything else.
- Replace the narrow pattern grep (non-retryable error|infrastructure_failure|
  test execution failed) with a broad whole-word case-insensitive grep for
  'error', so any infrastructure or Firebase error in the output causes CI
  failure.
- Verify that the number of device result rows in the result table matches
  the expected device count (1), so a silent test-run failure cannot slip
  through.
- Add scripts/test_firebase_check.sh with 18 unit tests for the three new
  bash patterns (auth stderr filter, error-word detection, device count).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 16:31:14 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 e057e1f483 fix: set Owner: "ci" on gradle and pub cache mounts
The gradle-cache volume was mounted without an owner, so the root-owned
volume caused "Permission denied" when the ci user tried to create
gradle-8.14-all.zip.lck during bundleRelease. Add Owner: "ci" to all
three WithMountedCache calls so the ci user can write to the caches.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:55:30 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 cc51abd1fa fix: reduce CI noise from apt-get, sdkmanager, stunnel, and Gradle (#140)
- Add -qq to apt-get update/install in Dagger toolchain to suppress
  verbose package-list output (hundreds of lines on cold cache)
- Wrap sdkmanager in silent-on-success pattern — only shows output
  on failure, like the build_runner and flutter pub get steps
- Set debug = warning in stunnel config to suppress LOG5 (info/notice)
  startup lines while keeping LOG4 (warning) and above
- Add org.gradle.welcome=never to android/gradle.properties to
  suppress the "Welcome to Gradle N.NN!" banner
- Filter SKIPPED Gradle tasks, Gradle Daemon startup messages, and
  gcloud support-page promo lines in run_firebase_test.sh

Errors and warnings are preserved in all cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:37:12 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 9e4a36b330 fix: drop -u 1000 from useradd in Dagger toolchain — UID already taken in flutter image
The cirruslabs/flutter:3.41.6 image already has UID 1000 assigned to
another user, so `useradd -u 1000` exits with code 4 ("UID not unique")
and the ci user is never created. Dagger then fails to resolve `owner:
"ci"` on subsequent WithDirectory calls. Removing the explicit UID lets
useradd pick the next available one.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:19:05 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 f9a5aa0372 fix: do not run Flutter as root in CI (#138)
Create a non-root user 'ci' (UID 1000) in the Dagger toolchain container,
transfer ownership of the Flutter SDK and Android SDK to that user, and
switch to it with WithUser("ci"). Update all cache mount paths from /root/
to /home/ci/ and set Owner: "ci" on every WithDirectory call so Flutter
can write build output. Flutter emits a strong warning when run as root;
this change eliminates that warning by running the tool as a regular user.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:09:42 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 ea52e89934 fix: run build_runner once via shared codegenBase, fix CheckMocks staleness detection (#137)
Previously build_runner compiled separately for each setup() variant
(checkSrc, backendSrc, integrationSrc, etc.) since their differing
source inputs produced distinct Dagger cache keys. CheckMocks also ran
build_runner twice: once inside setup() and again explicitly — and the
second run always compared two freshly-generated outputs, so stale mocks
in the repo were never detected.

Introduce codegenBase() that runs build_runner on the minimal common
source (lib/, test/, assets/, pubspec.*) excluding committed generated
files. All setup() calls now share this single Dagger cache entry, so
build_runner compiles only once per pipeline run instead of once per
source variant.

Fix CheckMocks to start from pubGetLayer() + committed source (including
any stale *.mocks.dart), commit that state as the git baseline, then run
build_runner once. The subsequent git diff now correctly detects stale
mocks in the repository, matching the behaviour of check_mocks_fresh.sh.

Also update Graph() to reflect the new codegenBase node.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 12:23:52 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 d36d9a679d fix: fail Android CI when gcloud reports non-retryable error (#143)
Previously, `gcloud firebase test android run` could exit 0 while printing
"A non-retryable error occurred." in its output. The old check
`&& echo "$out" || { exit 1; }` only caught non-zero exit codes, and the
success grep `'Passed|passed|test cases'` was too broad — "test cases" can
appear in Firebase output before the error, giving a false positive.

The fix captures gcloud's exit code explicitly via `rc=$?`, adds an explicit
error-string check for known Firebase failure phrases (non-retryable error,
infrastructure_failure, test execution failed), and tightens the success
pattern to `'Passed|passed'` only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:30:56 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 92f3e30e00 ci: fail if Firebase Test Lab reports no test case results
gcloud exits 0 even when no tests ran. Add a post-check that greps
the output for 'Passed/passed/test cases' and fails explicitly if
none are found, so 'no test case results' turns the CI red.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 08:58:09 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 cd7455d3a5 ci: remove unnecessary CACHE_BUSTER from Firebase step
The results-bucket change already busts the cache; Dagger doesn't
cache failed execs anyway.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 07:43:13 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 f047dd34ea ci: use project-owned bucket for Firebase Test Lab results
The default Firebase Test Lab bucket is in a Google-managed project so
project-level IAM grants have no effect on it. Use sharedinbox-ftl-results
which is in sharedinbox-496103 where the service account has storage.admin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 07:32:09 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 357f6e194c ci: bust Dagger cache for Firebase Test Lab step
WithEnvVariable(CACHE_BUSTER, time.Now()) ensures gcloud firebase test
always runs fresh rather than returning a cached result from a prior run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 06:08:36 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 1991508a8b Fix Firebase Test Lab device model ID: Pixel6 -> oriole
'Pixel6' is not a valid Firebase Test Lab model ID.
'oriole' is the correct internal codename for Pixel 6.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 18:58:56 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 689ce8721d Fix androidTest APK search path — Flutter redirects Gradle output to /src/build
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:40:17 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 6bb191ee99 Fix androidTest APK path using find instead of hardcoded path
The exact output path varies by AGP version. Use find to locate the
test APK and copy it to a known location.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:34:41 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 01cbf5b805 Add Firebase Test Lab integration for Android instrumented tests
Implements issue #132. Builds debug app APK + androidTest APK via Dagger,
then runs them on Firebase Test Lab using the FIREBASE_TEST_LAB_SERVICE_ACCOUNT_KEY
secret and FIREBASE_PROJECT_ID variable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:20:26 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 f2d24a8514 fix(ci): reduce noise in CI output (#128)
- Filter flutter pub get package-listing lines (^[+~><] ) in pubGetLayer
- Filter build_runner compilation-progress lines (^\[) in setup() and CheckMocks()
- Add -q to git commit in CheckMocks to suppress "460 files changed" stats
- Wrap flutter test in Coverage, TestBackend, TestIntegration, TestSyncReliability
  to show only the summary line on success and full output on failure
- Apply same build_runner filter to scripts/check_mocks_fresh.sh for local runs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:51:56 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 9dc34cefe5 ci: add 30-minute Dagger-side timeout to Check pipeline
If any step hangs (stuck service, deadlocked test, network stall), the
pipeline will now cancel itself after 30 min rather than blocking the
runner indefinitely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:53:49 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 34fb51d85d feat(ci): add Graph() to visualize CI pipeline as Mermaid diagram (#126)
Adds a Ci.Graph() Dagger function that emits a Mermaid flowchart showing
both the Dagger Check pipeline (toolchain → pubGetLayer → parallel steps)
and the Codeberg CI job dependencies (check → build-linux / deploy-playstore
→ publish-website).

Usage: dagger call -m ci --source=. graph
       task ci-graph

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 10:28:28 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 07823373c1 fix(ci): add withGoCache helper and pip cache for UploadToPlayStore
Adds withGoCache() that mounts GOCACHE and GOMODCACHE as Dagger cache
volumes — the standard pattern for any Go container added to the pipeline.
Also adds pip cache to UploadToPlayStore so pip wheel downloads are reused
between Play Store deploys.

Closes #123

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 06:41:04 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 1af2a36af7 fix(ci): remove pub cache volume from pubGetLayer for stable execution cache
flutter pub get was re-running on every CI run because Base() attached a
mutable WithMountedCache volume to /root/.pub-cache, making the execution
cache key unstable. Extract toolchain() without cache mounts; pubGetLayer()
now uses toolchain() so Dagger execution-caches pub get between runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 06:35:14 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 2748517d1c feat(ci): run TestBackend and TestIntegration in parallel
Saves ~1 minute on every CI run by starting the integration test build
concurrently with the backend Stalwart tests instead of waiting for them
to finish first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 08:58:55 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 e0a99d7d63 fix(ci): remove --no-pub from integration tests; use dart analyze instead of flutter analyze
Integration tests build native Linux app via CMake which requires pub get side effects
(plugin registrant file generation) — --no-pub broke the CMake step.

Switch flutter analyze to dart analyze --fatal-infos to eliminate the flutter wrapper's
non-deterministic state writes to /root/.dartServer/, which were preventing action cache
hits on the analyze step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 23:57:25 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 7fce683375 fix(ci): add --no-pub to flutter analyze and flutter test execs
Without --no-pub, flutter re-runs pub get internally before each
analyze/test call, writing a fresh package_config.json with new
timestamps. This makes the exec output snapshot non-deterministic
and prevents BuildKit from caching the result across CI runs.

With --no-pub, flutter uses the package_config.json already produced
by pubGetLayer(), and the exec output is stable → persistent cache hits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 23:30:47 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 39204fabcd fix(ci): remove dart-tool-build cache mount from setup()
Shared mutable cache mounts prevent BuildKit from persistently caching
the exec result across sessions. Without the mount, build_runner output
is stored in the content-addressed snapshot and survives GC cycles,
allowing downstream analyze/test steps to also be stably cached.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:46:39 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 b090342637 fix(ci): revert bad /root/.flutter cache mount — it is a file, not a directory
WithMountedCache requires a directory. /root/.flutter in the cirruslabs/flutter
image is a plain text file (Flutter SDK marker), causing "not a directory" at
container startup. Reverts to the pre-365 Base() so run-364 exec cache entries
are still valid.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 19:11:40 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 6dcefa856e fix(ci): mount /root/.flutter as cache volume to keep pub-get snapshot small
Flutter writes tool state to /root/.flutter on every invocation. Without a
cache mount this ends up in the pub-get snapshot, making it large and prone
to GC eviction. Moving it to a cache volume keeps the snapshot tiny so
BuildKit's exec cache for pub get survives between CI runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 19:00:45 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 59dfb0cfb3 fix(ci): also strip date_created from .flutter-plugins-dependencies
flutter pub get writes a date_created timestamp into .flutter-plugins-
dependencies in addition to the generated field in package_config.json.
Both files are part of the pub-get execution snapshot, so both timestamps
must be removed to make the layer deterministic and cacheable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 18:33:20 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 4daf47f7a3 fix(ci): make pub get layer deterministic to enable test caching
Remove non-deterministic "generated" and "generatorVersion" fields from
.dart_tool/package_config.json after flutter pub get, so the snapshot
hash is stable across runs and all downstream test steps can be cached.
Mount only .dart_tool/build as a mutable cache volume so the incremental
build graph persists without polluting the deterministic snapshot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 17:39:20 +02:00
Thomas SharedInbox dd9bd24f09 perf(ci): cache pub get separately from source to fix downstream cache misses
flutter pub get embeds a timestamp in .dart_tool/package_config.json, making
its output snapshot non-deterministic and busting the cache for dart format,
flutter analyze, unit tests, mocks, and integration tests on every run.

Fix: isolate pub get into its own layer using only pubspec.yaml + pubspec.lock
as inputs, then normalise the generated timestamp. setup() now overlays the
full source on top of this stable layer before running build_runner.

Result: on an empty commit, all steps downstream of pub get should be cached.
2026-05-19 16:59:19 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 354f7959f6 fix(ci): pre-install Android SDK components in container layer
Cache volumes for NDK/CMake proved unreliable on the remote Dagger
engine: the android-ndk-cache volume was empty on each run, causing
Gradle to re-download NDK + CMake + build-tools + platform during every
`flutter build appbundle` (~3-4 min of extra downloads).

Pre-install all four SDK components via sdkmanager in Base() so Dagger's
execution cache captures them. Base() is CACHED on subsequent runs with
identical inputs, eliminating the per-run SDK downloads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 11:35:44 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 9e709873b9 refactor(ci): scope source inputs per pipeline — android/linux builds no longer bust on unrelated changes
Base() no longer mounts m.Source. Each function gets only the files it
needs via a narrow filter, so Dagger's content-addressed cache is scoped
correctly: changing website/, scripts/, or stalwart-dev/ no longer
invalidates the Android or Linux build cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 10:52:57 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 f60beaa199 fix: XmlNode.element is at proto field 1, not 2 — versionCode patch was silently skipping all elements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 17:49:10 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 d9cde7cacf debug: dump manifest proto structure when versionCode not found
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 17:23:44 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 bb163542bb fix: add versionCode read-back verification and handle fixed-width wire types
- _parse now handles wire types 1 (fixed64) and 5 (fixed32) so it doesn't
  crash on unknown fields in the manifest proto
- _patch_prim patches both int_decimal_value (field 6) and int_hexadecimal_value
  (field 7) — AAPT2 may use either
- patch() reads versionCode before and after patching and exits with a clear
  error if the patch didn't take effect

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 17:11:20 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 02e8c2200a fix: fail fast with clear error when keystore secrets are empty
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 14:17:41 +02:00