gowalk-cicd 1.0.167 → 1.0.169

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +125 -220
  2. package/action/action.yml +59 -362
  3. package/action/scripts/auto_detect.py +39 -60
  4. package/action/scripts/commit_bot_changes.sh +3 -26
  5. package/action/scripts/creds_store.py +10 -91
  6. package/action/scripts/mobile_artifact_files.py +14 -3
  7. package/action/scripts/mobile_artifact_inventory.cjs +32 -19
  8. package/action/scripts/mobile_inputs.cjs +6 -4
  9. package/action/scripts/native_cache.py +5 -1
  10. package/action/scripts/native_pods.py +44 -15
  11. package/action/scripts/native_pods_diagnostics.py +18 -3
  12. package/action/scripts/native_pods_process.py +5 -0
  13. package/action/scripts/prepare_crashlytics_build.py +22 -5
  14. package/action/scripts/prepare_signing.py +4 -6
  15. package/action/scripts/profile_manager.py +74 -165
  16. package/action/scripts/resolve_info_plist.py +7 -2
  17. package/action/scripts/resolve_marketing_version.py +9 -1
  18. package/action/scripts/set_app_store_whats_new.py +3 -4
  19. package/action/scripts/version_utils.py +17 -7
  20. package/android-action/action.yml +6 -8
  21. package/android-action/flutter-setup/action.yml +46 -7
  22. package/android-action/scripts/android_build_diagnostics.py +53 -27
  23. package/android-action/scripts/crashlytics_symbols.py +4 -0
  24. package/android-action/scripts/sign_bundle.py +10 -7
  25. package/backend-action/action.yml +41 -46
  26. package/backend-action/configure_vhost.py +39 -21
  27. package/backend-action/edge_vhost.py +7 -7
  28. package/backend-action/ensure_certificate.sh +66 -186
  29. package/backend-action/remote_deploy.sh +55 -23
  30. package/bin/cli.mjs +3 -3
  31. package/docs/CANDIDATE_ARTIFACTS.md +20 -6
  32. package/docs/CONFIGURATION.md +3 -1
  33. package/docs/GRADLE_CACHE.md +8 -3
  34. package/package.json +16 -2
  35. package/src/config/build-steps.mjs +2 -1
  36. package/src/config/merge.mjs +29 -1
  37. package/src/config/transaction.mjs +8 -4
  38. package/src/config/validation.mjs +10 -1
  39. package/src/config/workflows.mjs +17 -1
  40. package/src/install.mjs +65 -37
  41. package/templates/deploy-backend.yml +8 -9
  42. package/templates/deploy.yml +90 -82
  43. package/templates/history.json.gz +0 -0
  44. package/templates/mobile-candidate.yml +1 -2
  45. package/action/prompts/generate_dependent_fields.prompt.yml +0 -78
  46. package/action/prompts/generate_descriptions.prompt.yml +0 -62
  47. package/action/scripts/app_context_scanner.py +0 -239
  48. package/action/scripts/asc_metadata_applier.py +0 -432
  49. package/action/scripts/asc_metadata_detector.py +0 -250
  50. package/action/scripts/autoupdate_check.sh +0 -61
  51. package/action/scripts/autoupdate_stage.py +0 -62
  52. package/action/scripts/cert_factory.py +0 -102
  53. package/action/scripts/certificate_failure.py +0 -47
  54. package/action/scripts/fixtures/single-size-ios-icon.json +0 -38
  55. package/action/scripts/metadata_constants.py +0 -102
  56. package/action/scripts/profile_store.py +0 -87
  57. package/action/scripts/source_maintenance_fixture.py +0 -58
  58. package/action/scripts/test_action_step_order.py +0 -83
  59. package/action/scripts/test_app_context_scanner.py +0 -167
  60. package/action/scripts/test_app_group_requirements.py +0 -83
  61. package/action/scripts/test_asc_build_history.py +0 -221
  62. package/action/scripts/test_asc_builds_prerelease.py +0 -219
  63. package/action/scripts/test_asc_common_timeouts.py +0 -121
  64. package/action/scripts/test_asc_metadata_applier.py +0 -990
  65. package/action/scripts/test_asc_metadata_detector.py +0 -445
  66. package/action/scripts/test_asc_network_diagnostics.py +0 -92
  67. package/action/scripts/test_auto_detect.py +0 -492
  68. package/action/scripts/test_autoupdate_stage.py +0 -107
  69. package/action/scripts/test_capabilities.py +0 -261
  70. package/action/scripts/test_certificate_cap_policy.py +0 -72
  71. package/action/scripts/test_certificate_failure.py +0 -64
  72. package/action/scripts/test_certificate_request.py +0 -62
  73. package/action/scripts/test_cfg_resolve_credentials.py +0 -48
  74. package/action/scripts/test_compute_next_version.py +0 -95
  75. package/action/scripts/test_compute_next_version_patch_backcompat.py +0 -41
  76. package/action/scripts/test_compute_next_version_strictness.py +0 -67
  77. package/action/scripts/test_crashlytics_dsyms.py +0 -211
  78. package/action/scripts/test_crashlytics_phase_effects.py +0 -30
  79. package/action/scripts/test_delivery_icons.py +0 -76
  80. package/action/scripts/test_delivery_preflight.py +0 -354
  81. package/action/scripts/test_fetch_versions.py +0 -163
  82. package/action/scripts/test_flutter_native_preparation.py +0 -103
  83. package/action/scripts/test_ground_truth_floor.py +0 -104
  84. package/action/scripts/test_keychain.py +0 -145
  85. package/action/scripts/test_manage_marketing_version.py +0 -134
  86. package/action/scripts/test_manage_marketing_version_autoroll.py +0 -158
  87. package/action/scripts/test_manage_marketing_version_floor.py +0 -170
  88. package/action/scripts/test_manage_marketing_version_match.py +0 -193
  89. package/action/scripts/test_mmv_409_classifier.py +0 -231
  90. package/action/scripts/test_mmv_autobump_persist_gate.py +0 -131
  91. package/action/scripts/test_mmv_decide_create.py +0 -158
  92. package/action/scripts/test_mmv_floor_check.py +0 -158
  93. package/action/scripts/test_mmv_floor_crosscheck.py +0 -132
  94. package/action/scripts/test_mmv_helpers.py +0 -57
  95. package/action/scripts/test_mmv_testflight_only.py +0 -147
  96. package/action/scripts/test_mobile_artifact_restore.py +0 -93
  97. package/action/scripts/test_mobile_artifacts.py +0 -92
  98. package/action/scripts/test_native_bundle.py +0 -97
  99. package/action/scripts/test_native_cache.py +0 -37
  100. package/action/scripts/test_native_dependency_guard.py +0 -116
  101. package/action/scripts/test_native_dependency_wiring.py +0 -70
  102. package/action/scripts/test_native_pods.py +0 -151
  103. package/action/scripts/test_native_pods_diagnostics.py +0 -86
  104. package/action/scripts/test_native_pods_process.py +0 -41
  105. package/action/scripts/test_native_pods_retry.py +0 -182
  106. package/action/scripts/test_native_pods_timeout.py +0 -34
  107. package/action/scripts/test_native_prepare_evidence.py +0 -76
  108. package/action/scripts/test_next_build_number.py +0 -207
  109. package/action/scripts/test_pbxproj_editor.py +0 -153
  110. package/action/scripts/test_prepare_crashlytics.py +0 -281
  111. package/action/scripts/test_profile_app_groups.py +0 -103
  112. package/action/scripts/test_profile_reuse.py +0 -113
  113. package/action/scripts/test_read_config_auto_detect.py +0 -257
  114. package/action/scripts/test_registry_managed_signing.py +0 -116
  115. package/action/scripts/test_resolve_info_plist.py +0 -120
  116. package/action/scripts/test_resolve_marketing_version.py +0 -298
  117. package/action/scripts/test_retained_signing_identity.py +0 -110
  118. package/action/scripts/test_reuse_stale_editable.py +0 -182
  119. package/action/scripts/test_set_app_store_whats_new.py +0 -71
  120. package/action/scripts/test_signing_source_boundary.py +0 -82
  121. package/action/scripts/test_source_maintenance.py +0 -122
  122. package/action/scripts/test_source_preflight.py +0 -226
  123. package/action/scripts/test_team_fallback_wiring.py +0 -157
  124. package/action/scripts/test_team_resolver.py +0 -249
  125. package/action/scripts/test_testflight_version_slot_wiring.py +0 -75
  126. package/action/scripts/test_upload_build.py +0 -267
  127. package/action/scripts/test_upload_checksums.py +0 -74
  128. package/action/scripts/test_write_marketing_version.py +0 -101
  129. package/action/scripts/tests_common.py +0 -167
  130. package/android-action/scripts/bitrise_deploy.py +0 -160
  131. package/android-action/scripts/crashlytics_bootstrap_test_support.py +0 -60
  132. package/android-action/scripts/fixtures/flutter_test_loopback.dart +0 -58
  133. package/android-action/scripts/flutter_manifest_fixture.py +0 -164
  134. package/android-action/scripts/test_android_build_diagnostics.py +0 -145
  135. package/android-action/scripts/test_android_config.py +0 -281
  136. package/android-action/scripts/test_bitrise_deploy.py +0 -124
  137. package/android-action/scripts/test_crashlytics_bootstrap.py +0 -97
  138. package/android-action/scripts/test_crashlytics_prebuild.py +0 -40
  139. package/android-action/scripts/test_crashlytics_symbols.py +0 -212
  140. package/android-action/scripts/test_early_play_workflow.py +0 -82
  141. package/android-action/scripts/test_flutter_archive.py +0 -109
  142. package/android-action/scripts/test_flutter_archive_cache.py +0 -84
  143. package/android-action/scripts/test_flutter_archive_setup.py +0 -67
  144. package/android-action/scripts/test_flutter_archive_size.py +0 -28
  145. package/android-action/scripts/test_flutter_cache_setup.py +0 -108
  146. package/android-action/scripts/test_flutter_download_budget.py +0 -145
  147. package/android-action/scripts/test_flutter_manifest.py +0 -108
  148. package/android-action/scripts/test_flutter_manifest_scope.py +0 -44
  149. package/android-action/scripts/test_flutter_obfuscation.py +0 -115
  150. package/android-action/scripts/test_flutter_origin_retry.py +0 -66
  151. package/android-action/scripts/test_flutter_release_cache.py +0 -94
  152. package/android-action/scripts/test_flutter_release_cache_scope.py +0 -49
  153. package/android-action/scripts/test_flutter_transfer_progress.py +0 -81
  154. package/android-action/scripts/test_gradle_wrapper.py +0 -268
  155. package/android-action/scripts/test_play_connect_retry.py +0 -115
  156. package/android-action/scripts/test_play_inventory.py +0 -98
  157. package/android-action/scripts/test_play_preflight.py +0 -100
  158. package/android-action/scripts/test_play_preflight_transport.py +0 -96
  159. package/android-action/scripts/test_resolve_play_readiness.py +0 -109
  160. package/android-action/scripts/test_select_jdk.py +0 -213
  161. package/android-action/scripts/test_sign_bundle.py +0 -82
  162. package/android-action/scripts/test_version_override.py +0 -153
  163. package/backend-action/certificate_vhost.py +0 -259
  164. package/backend-action/network_contract.py +0 -119
  165. package/backend-action/network_diagnostics.py +0 -182
  166. package/backend-action/network_inventory.py +0 -235
  167. package/backend-action/network_owned_inventory.py +0 -118
  168. package/backend-action/network_pool_facts.json +0 -214
  169. package/backend-action/network_pools.py +0 -275
  170. package/backend-action/network_prepare.py +0 -152
  171. package/backend-action/network_provider.py +0 -213
  172. package/backend-action/network_provider_test_fixture.py +0 -30
  173. package/backend-action/network_readback.py +0 -31
  174. package/backend-action/network_runtime.py +0 -62
  175. package/backend-action/network_selected.py +0 -96
  176. package/backend-action/network_selected_receipt.py +0 -60
  177. package/backend-action/network_test_fixture.py +0 -57
  178. package/backend-action/nginx_test_fixture.py +0 -98
  179. package/backend-action/test_certificate_vhost.py +0 -453
  180. package/backend-action/test_edge_health.py +0 -75
  181. package/backend-action/test_edge_inputs.py +0 -79
  182. package/backend-action/test_edge_nginx.py +0 -125
  183. package/backend-action/test_edge_vhost.py +0 -112
  184. package/backend-action/test_identity_artifact.py +0 -77
  185. package/backend-action/test_identity_diagnostics.py +0 -101
  186. package/backend-action/test_identity_failures.py +0 -56
  187. package/backend-action/test_network_builtins.py +0 -28
  188. package/backend-action/test_network_diagnostics.py +0 -77
  189. package/backend-action/test_network_docker.py +0 -129
  190. package/backend-action/test_network_fixture_evidence.py +0 -23
  191. package/backend-action/test_network_inventory.py +0 -241
  192. package/backend-action/test_network_ipam_gateway.py +0 -46
  193. package/backend-action/test_network_noop.py +0 -64
  194. package/backend-action/test_network_owned_inventory.py +0 -128
  195. package/backend-action/test_network_prepare.py +0 -120
  196. package/backend-action/test_network_provider.py +0 -162
  197. package/backend-action/test_network_races.py +0 -73
  198. package/backend-action/test_network_rule_diagnostics.py +0 -69
  199. package/backend-action/test_network_runtime.py +0 -49
  200. package/backend-action/test_network_selected.py +0 -153
  201. package/backend-action/test_runtime_env.py +0 -229
  202. package/docs/BACKEND_NETWORKS.md +0 -159
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # gowalk-cicd
2
2
 
3
- One installer for iOS TestFlight, Android Google Play, and Flutter web release
4
- artifacts. It supports native Swift/SwiftUI iOS projects, native Kotlin/Gradle
3
+ One installer for iOS TestFlight and Android Google Play delivery, plus the
4
+ optional backend deploy. It supports native Swift/SwiftUI iOS projects, native Kotlin/Gradle
5
5
  Android projects, and Flutter apps. Provision encrypted repository secrets, validate a
6
6
  task candidate, and promote its release intent. Store builds and uploads run only in GitHub Actions.
7
7
 
8
8
  App-specific workflow settings live in `.gowalk-cicd.yml`. The installer migrates supported
9
9
  existing settings once and regenerates managed workflows without replacing custom behavior.
10
- See [configuration and parallel organization upgrades](docs/CONFIGURATION.md).
10
+ See [configuration](docs/CONFIGURATION.md).
11
11
 
12
12
  ## Install
13
13
 
@@ -23,12 +23,18 @@ Writes three core things into your repo:
23
23
  - `.github/actions/android-app/` — Android build/sign action (Flutter or Gradle)
24
24
  - `.github/workflows/deploy.yml` — workflow that builds and deploys both platforms
25
25
 
26
- Flutter projects that contain `web/index.html` also get
27
- `.github/workflows/deploy-web.yml`. It creates a version-stamped release build
28
- and retains `build/web` as a 30-day workflow artifact. Native-only repositories
29
- are unaffected.
26
+ The installer no longer adds `.github/workflows/deploy-web.yml`. On re-adoption it
27
+ removes a `deploy-web.yml` that is still byte-for-byte the generated file and has no
28
+ overrides in `.gowalk-cicd.yml`; a customized one keeps upgrading (see
29
+ [Flutter web release](#flutter-web-release)).
30
30
 
31
- Re-run the same command anytime to pull the latest version.
31
+ Re-run the same command anytime to pull the latest version. The vendored action
32
+ directories then mirror the package: a file the package no longer ships is deleted,
33
+ except the `.daemux-version` markers and any file a workflow under
34
+ `.github/workflows/` still names by path (the installer prints what it kept and
35
+ removed; `--dry-run` deletes nothing). Tests and fixtures are not shipped. A
36
+ workflow the package does not manage that fails to parse is skipped with
37
+ `[warn] skipped .github/workflows/<file>: <code>` instead of aborting the install.
32
38
 
33
39
  Ready task PRs build shipping binaries alongside validation; the protected merge promotes matching
34
40
  binaries through the existing store upload paths. See [candidate artifact reuse](docs/CANDIDATE_ARTIFACTS.md)
@@ -92,6 +98,11 @@ job. This bounds individual cache segments, not the total cache or SDK download,
92
98
  and keeps existing cache keys. A successfully installed SDK is saved before app
93
99
  checks run, so a later test/build failure does not force another cold SDK download.
94
100
  Failed setup never saves an SDK cache; pub dependencies retain their job-end save.
101
+ Pull-request runs (`refs/pull/*`, which includes every candidate build) restore these caches
102
+ but never save them; task branches and the default branch still save. When the caller names
103
+ no Flutter version, a committed `.flutter-version` (`x.y.z`, a regular file of at most 64 bytes)
104
+ pins the SDK; a symlink or malformed pin fails with `Invalid project Flutter pin`, and no file
105
+ keeps the channel's latest release.
95
106
 
96
107
  Native dependency preparation uses a committed `Gemfile` and `Gemfile.lock` beside
97
108
  the selected iOS project, falling back to the repository root. It installs missing
@@ -109,9 +120,16 @@ Pods and verifies the selected workspace before release configuration. Preserve
109
120
  order in customized validation and deployment workflows; updating action directories
110
121
  alone cannot repair a configuration step that runs before the action.
111
122
 
123
+ The iOS action then skips `bundle install` and `pod install` when the Pods are already in
124
+ sync: `Pods/Manifest.lock` equals `Podfile.lock`, the lock's `PODFILE CHECKSUM` matches the
125
+ Podfile, every local `:path:` pod exists, a workspace exists and xcodegen did not just
126
+ generate the project. Its receipt then reports `pods: in_sync`, and its own native Pods
127
+ cache is not restored or saved for that run.
128
+
112
129
  ## Flutter web release
113
130
 
114
- The conditional `deploy-web.yml` workflow runs on changes to Flutter source,
131
+ A repository whose `.gowalk-cicd.yml` customizes `deploy-web.yml` keeps it.
132
+ That workflow runs on changes to Flutter source,
115
133
  web assets, package metadata, localization config, or the workflow itself. It
116
134
  also supports manual dispatch. The build uses `GITHUB_RUN_NUMBER` as its build
117
135
  number and uploads the complete `build/web` directory as a release artifact.
@@ -165,10 +183,6 @@ in App Store Connect; see [First-time app setup](#first-time-app-setup).
165
183
 
166
184
  ## Backend deploy (Python + Postgres, Docker)
167
185
 
168
- New ordinary Compose projects receive small isolated networks inside Docker's verified existing pools.
169
- Existing/custom/external networks and authored IPAM retain their contracts. Read-only
170
- `network-diagnostics` uses the same deployment identity; see [capacity and diagnosis](docs/BACKEND_NETWORKS.md).
171
-
172
186
  When a repo carries a Dockerized backend, the installer also vendors a backend deploy path:
173
187
  `.github/actions/backend-app/` and `.github/workflows/deploy-backend.yml`. A
174
188
  mobile-only repo is unaffected (the backend path is not installed).
@@ -211,13 +225,31 @@ wires an nginx vhost + Let's Encrypt cert for the API domain, and health-checks.
211
225
  Hosts with several existing Let's Encrypt accounts select one deterministically,
212
226
  and a domain deploy fails unless its public HTTPS certificate and health route validate.
213
227
  Changing a container's published port preserves its existing HTTPS configuration; nginx must accept
214
- the change, or the previous vhost is restored and the deploy fails. An existing certificate is
215
- installed for the requested domain on redeploy, repairing older HTTP-only vhosts too.
216
- A redeploy whose vhost already holds that certificate behind Certbot's redirect, whose certificate and
217
- key form a pair, whose running nginx presents it for the domain, and whose name no other server block in
218
- nginx's loaded configuration declares, skips the install, which would otherwise save a checkpoint copy of
219
- every nginx file on the host. It still tests and reloads nginx as the install did, and fails the deploy
220
- when nginx refuses either.
228
+ the change, or the previous vhost is restored and the deploy fails.
229
+ When the domain's vhost has exactly one `ssl_certificate`, and that file exists, stays valid for more
230
+ than a day and lists `DNS:<domain>` in its subjectAltName, the deploy runs no Certbot, only follows the
231
+ published port, and logs `certificate state=current`. Otherwise the name is issued by webroot: the vhost
232
+ serves `/.well-known/acme-challenge/` from `/var/lib/gowalk-acme` on port 80 (and proxies the API there
233
+ until HTTPS exists); the deploy waits up to 20 s for this host's nginx to serve a probe file under the
234
+ domain's name; `certbot certonly --webroot --cert-name <domain> --keep-until-expiring` issues it (a lineage
235
+ of that name that is not due is kept); then the vhost is rendered with HTTPS: port 80 answers challenges
236
+ and redirects, port 443 proxies with `/etc/letsencrypt/live/<domain>/`. Certbot saves the webroot and a
237
+ deploy hook that reloads nginx under the host nginx lock, so the host's renew timer renews the lineage.
238
+ Vhosts that older releases configured with `certbot --nginx` keep their TLS directives; only their
239
+ upstream port changes. Every Certbot call runs under `flock -w 900 /run/gowalk-certbot.lock`, so deploys
240
+ on one host take turns; when the host's renew timer holds Certbot's own lock, the call retries every
241
+ 10 s for up to 10 minutes. Vhost writes with their nginx test and reload, and the host port reservation,
242
+ run under the host nginx lock `/run/app-robot-nginx.lock` (`flock -w 120`), which the panel's domain
243
+ applier also takes. New Compose networks come from Docker's own address pools; the deploy hosts'
244
+ `default-address-pools` hand out `/24`s. After a healthy deploy the replaced build's images are pruned
245
+ (`docker image prune -f --filter label=com.docker.compose.project=<app>`); a refusal because another
246
+ prune is running defers it to the next deploy.
247
+
248
+ Each app gets one stable loopback port, reserved once in the host's `.env` as
249
+ `BACKEND_HTTP_PORT` (starting at `20000 + cksum(app) % 10000`, skipping ports other apps reserved or
250
+ already bound). A Compose file opts in by publishing `127.0.0.1:${BACKEND_HTTP_PORT:-}:<container-port>`;
251
+ a redeploy then leaves the nginx upstream unchanged. A literal `127.0.0.1:<port>:<container-port>`
252
+ keeps working as before.
221
253
 
222
254
  Requirements on the consumer repo:
223
255
 
@@ -228,6 +260,11 @@ Requirements on the consumer repo:
228
260
  never bake secrets in. A generated `.env` (with `POSTGRES_PASSWORD`) is created
229
261
  on the host on first deploy and preserved across deploys (rsync excludes it).
230
262
  - Repo **secret** `BACKEND_DEPLOY_SSH_KEY` (a private key with access to the host).
263
+ - Repo **variable** `BACKEND_DEPLOY_KNOWN_HOSTS` (recommended): `known_hosts` lines for the exact
264
+ host string the workflow connects to (`BACKEND_DEPLOY_HOST`, for example
265
+ `138.197.36.107 ssh-ed25519 AAAA...`). Deploys then require that key
266
+ (`StrictHostKeyChecking=yes`). While it is empty the job trusts `ssh-keyscan` and prints one
267
+ `backend_host_key_unpinned` warning.
231
268
  - Repo **secret** `BACKEND_RUNTIME_ENV` (optional): newline-delimited `KEY=value`
232
269
  runtime secrets. The action writes it as mode 0600 on the host and passes it
233
270
  to Compose without copying values into the repository or action log.
@@ -238,10 +275,15 @@ Requirements on the consumer repo:
238
275
  Malformed or ambiguous multiline structures refuse before deployment. Transport is bounded
239
276
  to 1 MiB of UTF-8 input and 512 KiB per value, uses private SSH stdin, and atomically replaces
240
277
  `.runtime.env`; application sync preserves the last installed file if validation fails.
241
- - Repo **variables** (optional): `BACKEND_API_DOMAIN` (its DNS A record must point
242
- at the host for the cert; empty = deploy the container only, skip nginx/cert),
243
- `BACKEND_APP_NAME` (defaults to the repo name), `BACKEND_HEALTH_PATH`
244
- (default `/health`), `BACKEND_DEPLOY_HOST` (default `138.197.36.107`).
278
+ - Repo **variable** `BACKEND_API_DOMAIN`: the bound API host name (for example
279
+ `api.<app-domain>`); its DNS A record must point at the host for the cert.
280
+ **Backend Deploy runs only when it is set**: a repository with no bound backend
281
+ origin holds no deploy key, so `deploy-backend.yml` is skipped and the feature-branch
282
+ `backend-preview` deploy ends with a `backend_origin_unbound` notice.
283
+ - Repo **variables** (optional): `BACKEND_APP_NAME` (defaults to the repo name),
284
+ `BACKEND_HEALTH_PATH` (default `/health`), `BACKEND_DEPLOY_HOST` (default
285
+ `138.197.36.107`). The action refuses an `api-domain` that is not a lowercase DNS
286
+ name, a `health-path` outside `/[A-Za-z0-9_./-]*` and a malformed `cert-email`.
245
287
 
246
288
  The action directory is canonical here (like the iOS/Android actions): edit it in
247
289
  `backend-action/`, never in a consumer's vendored copy.
@@ -354,7 +396,17 @@ rather than a failure, so it reads as a flake rather than an out-of-memory.
354
396
 
355
397
  Before building, the Android job caps `-Xmx` at 4 GB and `MaxMetaspaceSize` at
356
398
  2 GB in the checkout, with a `::warning::` naming the original value. Projects
357
- already at or below those numbers are left untouched. Linux only.
399
+ already at or below those numbers are left untouched. A runner with at least
400
+ 16 GiB of `MemTotal` (for example an 8-core `ANDROID_RUNNER`) keeps the app's own
401
+ settings. Linux only.
402
+
403
+ ### Android runner size
404
+
405
+ The Android job runs on `${{ vars.ANDROID_RUNNER || 'ubuntu-24.04' }}`. Set the
406
+ repository or organization variable `ANDROID_RUNNER` to a larger hosted runner
407
+ label to shorten the release build; unset it before deleting that runner. The
408
+ variable is not a build input: setting, changing or removing it never discards a
409
+ candidate binary.
358
410
 
359
411
  ### When Play will not accept a release for review
360
412
 
@@ -428,12 +480,11 @@ the situation and whose **message** is one line of JSON carrying a versioned
428
480
  | `source_maintenance_pending` | `gowalk-cicd/source-maintenance-pending.v1` | `repo`, `base_sha`, `head_sha`, `branch`, `run_id` |
429
481
  | `apple_build_upload_failed` (error) | `gowalk-cicd/apple-build-upload-failed.v2` | `category`, `stage`, `receipt_available`, receipt identifiers (`cfBundleVersion`, `upload_id`, `file_id`, `state`, `provider_status`, `provider_code`, `provider_pointer`), `action` |
430
482
  | `flutter_download_failed` (error) | `gowalk-cicd/flutter-download-failed.v1` | `phase`, `curl_exit`, `http_status`, `proxy_status`, `attempts`, `retained_bytes` |
431
- | `android_build_failed` (error) | `gowalk-cicd/android-build-failed.v1` | `phase`, `exit_code`, `signals` (fixed observed patterns; empty means unclassified) |
483
+ | `android_build_failed` (error) | `gowalk-cicd/android-build-failed.v1` | `phase`, `exit_code`, `signals` (fixed observed patterns; empty means unclassified), optional `hosts`, `artifacts` and `excerpt` (error lines plus the next line, URL userinfo removed, at most 4 KB) |
432
484
 
433
- Android release compilers retain only a bounded private output window. Their failure signals
434
- describe observed error patterns, not retry permission or a unique root cause. Silent builds
435
- emit `android_build_progress` notices (`gowalk-cicd/android-build-progress.v1`) every minute
436
- with `phase` and `elapsed_seconds`; successful compiler output is not republished.
485
+ Android release compilers stream their output to the job log inside `::stop-commands::` markers,
486
+ so no build line can act as a workflow command. Their failure signals describe observed error
487
+ patterns, not retry permission or a unique root cause.
437
488
 
438
489
  Native bundle and Pods failures emit `native_prepare_failed` with schema
439
490
  `gowalk-cicd/native-prepare-failed.v1`, `phase`, a fixed refusal `code` and fixed observed `signals`.
@@ -565,10 +616,11 @@ reliably fit. The failure surfaces far from its cause:
565
616
  zip I/O error: No space left on device
566
617
  ```
567
618
 
568
- The Android job removes the preinstalled toolchains a Flutter build never uses
569
- (.NET, GHC, PowerShell, Swift, Chromium) and prunes Docker
570
- images, reclaiming roughly 25 GB in a few seconds. It prints `df -h /` before
571
- and after. Linux only; skipped in Bitrise mode. npm survives on purpose: the
619
+ The Android job prints `df -h /` and does nothing more when `/` already has
620
+ 40 GB or more free (a larger runner). Otherwise it removes the preinstalled
621
+ toolchains a Flutter build never uses (.NET, GHC, PowerShell, Swift, Chromium)
622
+ and prunes Docker images in the background, reclaiming roughly 25 GB while the
623
+ JDK, Gradle restore and Flutter setup run. Linux only. npm survives on purpose: the
572
624
  [Crashlytics symbol upload](#crashlytics-symbol-delivery-firebase_app_id) runs
573
625
  the Firebase CLI through `npx`.
574
626
 
@@ -621,56 +673,12 @@ Both stores pick up per-locale release notes from committed files:
621
673
  the deploy workflow passes it to the Play upload and the notes ship with
622
674
  every release; when absent, releases upload without notes as before.
623
675
 
624
- ## Android delivery via Bitrise (alternative mode)
625
-
626
- Use this when the Android **upload keystore password lives in Bitrise** (not in
627
- the repo) — the GitHub runner then cannot sign the AAB locally. In this mode the
628
- Android job triggers a Bitrise workflow (which holds the keystore + Play
629
- service-account secrets) over the Bitrise API and waits for it, mirroring the
630
- build result back into the GitHub Actions run. iOS still ships via GitHub
631
- Actions as normal.
632
-
633
- Enable it by committing `creds/bitrise.json`:
634
-
635
- ```json
636
- {
637
- "enabled": true,
638
- "app_slug": "8b2a7a24-....",
639
- "workflow": "android-deploy",
640
- "branch": "main",
641
- "api_token": "bitpat_...."
642
- }
643
- ```
644
-
645
- - `app_slug` — the Bitrise app slug (from the app's Bitrise URL or
646
- `GET https://api.bitrise.io/v0.1/apps`).
647
- - `workflow` — the Bitrise workflow that builds, signs, and runs
648
- `google-play-deploy` (default `android-deploy`).
649
- - `branch` — optional; defaults to the pushed ref.
650
- - `api_token` — a Bitrise personal access token. Committing it to a **private**
651
- repo's `creds/` follows the same trust model as the ASC `.p8`. For non-private
652
- repos, omit it here and set the `BITRISE_API_TOKEN` Actions secret instead.
653
-
654
- When `creds/bitrise.json` is present with `enabled: true`, the local
655
- build/sign/upload steps are skipped and `bitrise_deploy.py` drives the Bitrise
656
- build; otherwise the local path above runs unchanged.
657
-
658
- The Bitrise `android-deploy` workflow must produce a versionCode higher than
659
- what is already on Play. The convention used by this fleet is a `+200` offset
660
- over `$BITRISE_BUILD_NUMBER` via a `change-android-versioncode-and-versionname`
661
- step, with the versionName read from `pubspec.yaml`.
662
-
663
676
  ## Sharing one distribution cert across a team's apps
664
677
 
665
- The iOS action caches the Apple Distribution cert under `creds/` and reuses it
666
- across runs (cert alive + NotAfter > 30 days). But Apple caps a team at **2**
667
- distribution certs, and the cache is per-repo. If **three or more apps share one
668
- Apple Developer team**, they cannot each hold their own cert — every run finds
669
- its cached cert already revoked by a sibling, mints a new one, and revokes
670
- another. The symptom is a "Your Certificate Has Been Revoked" email on nearly
671
- every deploy.
672
-
673
- Fix: apps in the same Apple team should **share one cert**. A fleet registry can
678
+ The iOS action signs with the retained Apple Distribution identity in `creds/`
679
+ (cert alive + NotAfter > 30 days) and never creates or revokes a certificate.
680
+ Apple caps a team at **2** distribution certs, so apps in the same Apple team
681
+ should **share one cert**. A fleet registry can
674
682
  provision the same `creds/cert.p12` + `creds/cert.meta.json` into every repo of
675
683
  that team (an Apple Distribution cert is team-scoped, not app-scoped, so one
676
684
  cert signs them all). Registry provisioning must also write
@@ -683,9 +691,10 @@ The marker is a fail-closed ownership boundary: the action may reuse the
683
691
  identity and create per-app provisioning profiles, but it never creates,
684
692
  replaces, or revokes a registry-managed distribution certificate. Missing,
685
693
  partially written, corrupt, expired, Apple-revoked, or resource-ID/P12-mismatched
686
- managed material aborts with a reconciliation error. Legacy repos without the
687
- marker may create a certificate in an available slot, but never revoke another
688
- identity. A full certificate cap stops for account-registry reconciliation.
694
+ managed material aborts with a reconciliation error. A legacy repo without the
695
+ marker whose retained identity is missing or unusable fails with
696
+ `apple_signing_identity_required` (`gowalk-cicd/apple-signing-identity-required.v1`)
697
+ and keeps its existing signing files.
689
698
 
690
699
  ## How it works
691
700
 
@@ -770,9 +779,7 @@ package produces — the Android App Bundle and the iOS archive — is compiled
770
779
  config key that turns it off. Native Swift and Kotlin/Gradle apps are
771
780
  unaffected: the flag belongs to the Dart compiler and they have no Dart code.
772
781
  Flutter web builds are also untouched — `flutter build web` has no such flag;
773
- dart2js minifies release output instead. An Android app delivered through
774
- [Bitrise](#android-delivery-via-bitrise-alternative-mode) is built by Bitrise's
775
- workflow, not by this action; put the same two flags in that workflow.
782
+ dart2js minifies release output instead.
776
783
 
777
784
  What it does: the Dart AOT compiler replaces class, function and library names
778
785
  with short random identifiers, so the app's Dart symbols cannot be read out of
@@ -847,7 +854,8 @@ is required. A completed CI run does not mean this deferred upload is complete.
847
854
 
848
855
  ### iOS delivery
849
856
 
850
- The iOS composite action runs on `macos-15` and:
857
+ The iOS composite action runs on `macos-15`; promoting a verified candidate IPA runs its first
858
+ attempt on `ubuntu-24.04` (see [Candidate artifacts](docs/CANDIDATE_ARTIFACTS.md)). It:
851
859
 
852
860
  GitHub Actions reaches Apple and Google **directly**. No store proxy is configured, read
853
861
  or inherited here: the account-pinned residential exit belongs to the panel and the runner
@@ -877,19 +885,16 @@ preserving each app's custom checks and artifact retention settings. An isolated
877
885
  4. **Computes the next build number** by querying ASC for the latest uploaded
878
886
  build and incrementing.
879
887
  5. **Provisions signing** at runtime: reconciles the App ID's capabilities with
880
- the target's entitlements (see below), then generates a throwaway Apple
881
- Distribution cert + a per-target App Store provisioning profile named
882
- `CI-<bundle_id>`. Patches the `.pbxproj` to use Manual signing against those
883
- profiles.
888
+ the target's entitlements (see below), then signs with the retained Apple
889
+ Distribution identity and the per-target App Store provisioning profile named
890
+ `CI-<bundle_id>` that App Store Connect already holds, creating the profile
891
+ when it is missing or stale. Patches the `.pbxproj` to use Manual signing
892
+ against those profiles.
884
893
  6. **Archives** with `xcodebuild archive`, exports the IPA, and uploads via
885
894
  Apple's build-upload REST API.
886
895
  7. **Sets "What's New"** on every declared localization (reads
887
896
  `fastlane/metadata/ios/<locale>/release_notes.txt` if present, or from the
888
897
  `app-store-whats-new` input).
889
- 8. **Auto-fills empty App Store metadata** (name, subtitle, keywords,
890
- description, promotional text, what's new) via GitHub Models AI, on every
891
- locale that has gaps. See [AI metadata auto-fill](#ai-metadata-auto-fill)
892
- below.
893
898
 
894
899
  The iOS action requires only the p8. Everything else is derived.
895
900
 
@@ -919,17 +924,16 @@ Before creating a profile, the action reads the target's
919
924
  ID — Push Notifications, Associated Domains, HealthKit, SiriKit, HomeKit,
920
925
  network extensions and the other plain on/off toggles.
921
926
 
922
- The cached profile under `creds/profiles/` is checked against those same
923
- entitlements and regenerated when it does not carry them. The cache manifest
924
- records only a UUID and an expiry, so without that check a profile issued
925
- before a capability was turned on — whether by this action or by you in the
926
- developer portal — would be reused indefinitely, and the archive would keep
927
- failing on a capability the App ID already has.
927
+ An existing `CI-<bundle_id>` profile is found by name and reused only when it
928
+ is ACTIVE, issued for the current certificate, more than 30 days from expiry,
929
+ and carries those same entitlements. Otherwise it is deleted and regenerated, so
930
+ a profile issued before a capability was turned on — whether by this action or
931
+ by you in the developer portal — is never reused.
928
932
 
929
933
  App Groups require registration and assignment to each participating App ID through
930
934
  the account's Apple Developer console. The action reads the literal identifiers from
931
- each archive target's entitlements and verifies their exact membership in both cached
932
- and fresh profiles. A stale cache regenerates; a fresh profile missing a required group
935
+ each archive target's entitlements and verifies their exact membership in both reused
936
+ and fresh profiles. A stale profile regenerates; a fresh profile missing a required group
933
937
  fails before installation with `apple_app_groups_required`, schema
934
938
  `gowalk-cicd/apple-app-groups-required.v1`, containing the bundle ID and missing groups.
935
939
  Complete those assignments through the scoped console and rerun CI. This check creates
@@ -948,63 +952,6 @@ Two other classes of capability are **not** enabled automatically, and each gets
948
952
  → &lt;your App ID&gt;**. Until then the archive fails with `Provisioning profile
949
953
  "CI-<bundle>" doesn't include the App Attest capability`.
950
954
 
951
- ## AI metadata auto-fill
952
-
953
- On every run, after the TestFlight upload succeeds, the action:
954
-
955
- 1. Queries App Store Connect for every `appInfoLocalization` and
956
- `appStoreVersionLocalization` on the editable version.
957
- 2. Computes the set of empty fields per locale (URL fields are always
958
- skipped — you must set those manually in ASC).
959
- 3. Scans your repo for context (README, Info.plist, dependency files, top
960
- Swift files) and feeds it to `openai/gpt-4o` via GitHub Models with a
961
- strict JSON schema.
962
- 4. PATCHes only the fields that were empty — never overwrites existing
963
- content.
964
-
965
- Fully idempotent: a second run with no empty fields skips the AI step
966
- entirely (zero requests, zero PATCHes).
967
-
968
- ### Requirement: `permissions: models: read`
969
-
970
- `actions/ai-inference` needs the `models: read` permission. The template
971
- workflow written by `npx gowalk-cicd` already includes it:
972
-
973
- ```yaml
974
- permissions:
975
- contents: read
976
- models: read
977
- ```
978
-
979
- **Existing consumers must add this block to their `deploy.yml`** at the
980
- workflow or job level. If it's missing, the AI step fails open with a
981
- `::warning::` and the rest of the workflow continues unaffected.
982
-
983
- ### Rate limits
984
-
985
- GitHub Models free tier allows **50 `gpt-4o` requests per day** (10 per
986
- minute). One workflow run = one request. If you run many apps from the
987
- same GitHub account, or trigger several builds per day, switch to the
988
- cheaper mini model:
989
-
990
- ```yaml
991
- - uses: ./.github/actions/swift-app
992
- with:
993
- ai-metadata-model: openai/gpt-4o-mini
994
- ```
995
-
996
- `gpt-4o-mini` has a much higher free-tier quota.
997
-
998
- ### Disabling AI metadata
999
-
1000
- Pass `ai-metadata: 'false'` to skip the AI steps entirely:
1001
-
1002
- ```yaml
1003
- - uses: ./.github/actions/swift-app
1004
- with:
1005
- ai-metadata: 'false'
1006
- ```
1007
-
1008
955
  ## Update
1009
956
 
1010
957
  ```bash
@@ -1012,7 +959,8 @@ npx --yes gowalk-cicd
1012
959
  ```
1013
960
 
1014
961
  Overwrites both vendored actions and `.github/workflows/deploy.yml` with the
1015
- latest versions. Because the actions are vendored locally, builds use the
962
+ latest versions, and deletes vendored files the package no longer ships (see
963
+ [Install](#install)). Because the actions are vendored locally, builds use the
1016
964
  reviewed copy in your app repository; updates arrive through this npm package.
1017
965
 
1018
966
  ## Override config (rare)
@@ -1040,22 +988,14 @@ common ones:
1040
988
  | `bundle-id` | Override the auto-detected bundle identifier |
1041
989
  | `team-id` | Override the auto-detected team ID |
1042
990
  | `app-store-apple-id` | Numeric ASC app ID (override auto-lookup) |
1043
- | `certificate-cap-policy` | Only `fail` is accepted (the default); existing identities are always preserved. |
991
+ | `certificate-cap-policy` | Deprecated and ignored: CI never creates or revokes certificates. |
1044
992
  | `run-tests` | `false` to skip the simulator test stage |
1045
993
  | `uses-non-exempt-encryption` | Value for `ITSAppUsesNonExemptEncryption` |
1046
994
  | `archive` | `false` to build-only (PR runs without secrets) |
1047
995
  | `upload` | `false` to archive but not upload to TestFlight |
1048
996
  | `manage-app-store-version` | `false` to upload without creating/editing the App Store release slot |
1049
997
  | `app-store-whats-new` | Inline "What's New" text (overrides files) |
1050
- | `ai-metadata` | `false` to disable AI auto-fill of empty ASC metadata |
1051
- | `ai-metadata-model` | GitHub Models model id (default `openai/gpt-4o`) |
1052
-
1053
- Certificate creation sends the complete PEM CSR in Apple's `csrContent` field, including its framing and newlines.
1054
- Certificate creation refusals emit `apple_certificate_rejected` with schema
1055
- `gowalk-cicd/apple-certificate-rejected.v1`, `operation`, `http_status`, `reason` and `error_codes`.
1056
- HTTP 409 is classified as `certificate_limit` only when Apple's returned detail identifies the limit;
1057
- otherwise it remains `unclassified_conflict`. Codes come from a fixed allowlist. Raw provider text and
1058
- CSR data are excluded. Neither outcome permits automatic certificate deletion or another creation attempt.
998
+ | `ai-metadata`, `ai-metadata-model` | Deprecated and ignored: the action no longer writes App Store metadata with AI. |
1059
999
 
1060
1000
  ## First-time app setup
1061
1001
 
@@ -1067,10 +1007,9 @@ inside GitHub Actions.
1067
1007
  Once the app exists, all subsequent builds and uploads are fully automated via
1068
1008
  the ASC API key.
1069
1009
 
1070
- Automatic certificate revocation is disabled for every account. The action may add a certificate when Apple has a free
1071
- slot, but a full-cap response aborts without listing or revoking existing identities. If
1072
- signing preparation fails after creating a certificate, the default-branch workflow first
1073
- commits any completed cache files, then re-raises the failure so the private key is not lost.
1010
+ CI never creates, lists for deletion or revokes an Apple Distribution certificate; it
1011
+ signs only with the retained identity (see
1012
+ [Sharing one distribution cert](#sharing-one-distribution-cert-across-a-teams-apps)).
1074
1013
 
1075
1014
  ## Troubleshooting
1076
1015
 
@@ -1162,29 +1101,13 @@ generated pbxproj). Either move `MARKETING_VERSION` under
1162
1101
  `settings.base` in `project.yml`, or pin
1163
1102
  `marketing-version-auto-bump: 'none'` and bump manually.
1164
1103
 
1165
- ## Auto-updates
1166
-
1167
- The vendored action ships with a per-run autoupdate check. On every
1168
- push to your default branch, the action queries npm for the latest
1169
- `gowalk-cicd`, compares against the local marker at
1170
- `.github/actions/swift-app/.daemux-version`, and if newer, re-vendors
1171
- the package via `npx --yes` in a temporary detached worktree and preserves its action files
1172
- on a verified task branch (under both `.github/actions/swift-app/` and
1173
- `.github/actions/android-app/`).
1174
- `.github/workflows/deploy.yml` is NEVER auto-committed — see
1175
- "deploy.yml is not auto-updated" below.
1176
-
1177
- | Aspect | Behaviour |
1178
- |--------|-----------|
1179
- | Trigger | Push to default branch only (PR / branch runs do nothing) |
1180
- | Landing | The app session adopts the exact receipt on its current task branch and uses the protected PR |
1181
- | Isolation | CI leaves HEAD and worktree bytes intact; no default-branch push or rebase |
1182
- | Candidates | Action updates and later version changes have separate verified receipts |
1183
- | Failure mode | npm discovery remains optional; unconfirmed source preservation fails the job |
1184
-
1185
- The shipped `deploy.yml` watches only the release intent. Adopting an action-directory
1186
- update on its own therefore does not launch another mobile upload. Review and adopt the template
1187
- as well as the action directories to enable this behavior in an existing consumer.
1104
+ ## Updates
1105
+
1106
+ CI never updates the vendored actions: the `auto-update` input is deprecated and
1107
+ ignored. A repository adopts a new release only when its task runs
1108
+ `npx --yes gowalk-cicd` and lands the result through the protected PR. The
1109
+ `.daemux-version` markers the installer writes name the release the vendored
1110
+ actions came from.
1188
1111
 
1189
1112
  ### deploy.yml is not auto-updated
1190
1113
 
@@ -1194,9 +1117,9 @@ of `contents: write` — this is a built-in safeguard against CI
1194
1117
  self-modification. When a new version of `gowalk-cicd`
1195
1118
  requires `deploy.yml` schema changes (e.g., new permissions, new
1196
1119
  paths-ignore entries), the action's release notes will call this out
1197
- and the app session runs the pinned `npx --yes gowalk-cicd` adoption in its task
1120
+ and the app session runs the `npx --yes gowalk-cicd` adoption in its task
1198
1121
  workspace, preserving app-specific workflow inputs and landing the full change through
1199
- its checked PR. The CI-side update does not commit the workflow.
1122
+ its checked PR.
1200
1123
 
1201
1124
  Changes that need that manual run:
1202
1125
 
@@ -1207,24 +1130,6 @@ Changes that need that manual run:
1207
1130
  passed to both actions. Until it is, setting the variable does nothing.
1208
1131
  See [Crashlytics symbol upload](#crashlytics-symbol-delivery-firebase_app_id).
1209
1132
 
1210
- ### Opt out
1211
-
1212
- Pin the vendored copy by passing `auto-update: 'false'` to the action:
1213
-
1214
- ```yaml
1215
- - uses: ./.github/actions/swift-app
1216
- with:
1217
- auto-update: 'false'
1218
- ```
1219
-
1220
- ### First-run bootstrap
1221
-
1222
- The marker is written by `npx gowalk-cicd` itself. A repo
1223
- without a marker (e.g. an old hand-vendored copy) will be treated as
1224
- out-of-date on its first run, after which updates land incrementally.
1225
- Run `npx --yes gowalk-cicd` once locally if you want to skip
1226
- even that first auto-bootstrap.
1227
-
1228
1133
  ## License
1229
1134
 
1230
1135
  MIT