# Configuration-preserving upgrades

Run `npx --yes gowalk-cicd@<version>` from the app repository. The first upgrade
extracts supported settings from existing workflows into `.gowalk-cicd.yml` and
renders the managed workflows in the same change. Later upgrades read that file.
The installer does this locally; app CI gains no configuration job or preflight.

```yaml
schema: 1
mobile:
  platforms: ios
  candidate-artifacts: true
  ios:
    project: Forms.xcodeproj
    workspace: Forms.xcworkspace
    scheme: Forms
    configuration: Release
    firebase-app-id: ${{ vars.FIREBASE_APP_ID }}
backend:
  host: ${{ vars.BACKEND_DEPLOY_HOST }}
  runtime-env: ${{ secrets.BACKEND_RUNTIME_ENV }}
  public-base-url: ${{ vars.BACKEND_PUBLIC_BASE_URL }}
  public-health-url: ${{ vars.BACKEND_PUBLIC_HEALTH_URL }}
  expected-build-sha: ${{ vars.BACKEND_PUBLIC_HEALTH_URL && github.sha || '' }}
  ingress-required: ${{ vars.BACKEND_INGRESS_REQUIRED || 'false' }}
  ingress-token: ${{ secrets.BACKEND_INGRESS_TOKEN }}
workflows:
  deploy.yml:
    overrides: []
```

Omitted settings retain the template defaults. The installer initially extracts
existing variable expressions, so current GitHub variables remain authoritative
until the app explicitly replaces their references in this file. Keep passwords,
signing material, tokens and private keys in GitHub Secrets. Reference their names;
never put their values in this configuration.

Backend discovery accepts `compose.yaml`, `compose.yml`, `docker-compose.yaml` and
`docker-compose.yml` inside `backend/` or `server/`, or at the repository root with a Dockerfile in
exactly one of those source directories. Multiple matches require an explicit selection; no file is
renamed and no arbitrary precedence chooses the deployment. Existing named settings disambiguate it:

```yaml
backend:
  backend-dir: service
  compose-file: service/compose.yml
```

Use an existing source subdirectory and either its standard Compose file or a root standard Compose
file. Root Compose layouts also require that source directory's Dockerfile. `backend-dir: .`, parent
paths and symlinks are refused. Both generated backend entrypoints receive the selected settings.
For a custom source directory, retain its app-owned backend workflow path filter alongside the standard
`backend/**` and `server/**` defaults; layout selection does not change custom deployment triggers.
Run `npx --yes gowalk-cicd@<version> --dry-run`, then the same installer without `--dry-run`; a
`backend_layout_ambiguous` or `backend_layout_missing` receipt identifies selection repair before any
files are written. Keep runtime and workflow customizations in this configuration when rerunning it.

Backend edge settings are optional and preserve legacy deployments when absent. When enabled,
the infrastructure `api-domain` must be a fresh alias distinct from the retained legacy hostname.
Provision its Cloudflare route/token first. Public health proves the exact `build_sha`; the app's
Compose service must pass `BACKEND_PUBLIC_BASE_URL` into its container and use it in generated URLs.
The same named repository inputs reach the feature-branch backend preview in `deploy.yml`.

The supported managed files are `deploy.yml`, `mobile-candidate.yml`,
`deploy-web.yml` and `deploy-backend.yml`. App-specific values outside the named
mobile/backend inputs, and custom jobs or steps embedded in those workflows,
are retained as sparse `workflows.<file>.overrides` entries. An override identifies
a YAML path, with `value` or `remove: true`; step paths use stable IDs/names rather
than numeric positions. These extensions are app-owned configuration, including
their execution conditions, dependencies and secret references.

Candidate artifacts are enabled automatically for recognized execution. Custom deployment
commands that the installer cannot establish as candidate-safe retain the normal main
release build, with `mobile.candidate-artifacts: false`; no candidate store mutation is
inferred safe from a custom step name. Candidate misses never wait or reserve a machine.

Separate custom workflows remain app-owned. Recognized native dependency jobs
receive optional lock/toolchain-keyed cache restore/save inside their existing job.
An app-owned workflow whose push trigger is a plain `branches` list admitting `task/**`
validates each candidate on that push, which runs on the exact head: the installer deletes its
duplicate `pull_request` trigger, and a trailing `!task/**` written by earlier releases, in one
write. Job names, commands and custom checks are unchanged. A push with `branches-ignore`,
`paths` or `paths-ignore`, or one that does not admit `task/**`, is left as it is; such
workflows retain separate push/PR concurrency groups, so neither check cancels the other.
An upgrade also repairs the shared group emitted by 1.0.142–143. Separate platforms and
repositories keep their existing parallelism.

`.gowalk-cicd.lock.json` records the template and generated-file digests. Commit it
with the configuration and rendered workflows. It is migration state, not a CI job.
Edit configuration, then rerun the installer; editing a managed generated workflow
directly yields `managed_workflow_modified_use_config` without overwriting the edit.

The package carries released template history for versioned three-way migrations.
An unknown layout or overlapping custom code edit returns a JSON
`gowalk-cicd/migration-repair.v1` receipt with its code, file and YAML path. The
receipt never includes the conflicting value. Resolve that specific customization
in the same repository; unrelated repositories can continue upgrading.

# Organization rollout

Use a released organization script and the reviewed installer package:

```sh
node /path/to/package/scripts/upgrade-org.mjs \
  --org gowalk-public --state /durable/cicd-rollout/state.json \
  --workers 8 --max-active 8
```

It paginates the organization, discovers actual installed actions, and uses one
`task/gowalk-cicd-<version>` branch/PR per affected repository. Workers prepare
independent repositories concurrently. Existing CI remains authoritative; each
successful candidate merges independently, and any resulting push workflows are
followed before branch/checkout cleanup. No factory task or Mac reservation is
created. API throttling and `--max-active` bound publication. One repository’s queued CI
never stops unrelated PRs; GitHub schedules each platform against its available runners.
Existing checks must finish successfully, and GitHub enforces its required checks and reviews
for the exact candidate. A repository without candidate checks gains no new CI requirement.

The state file records repository heads, PR/run URLs, failures and cleanup.
Restart with the same file and package to resume. `--retry-unresolved` retries
recorded exceptions after their cause is repaired; `--dry-run` prepares local
changes without pushing, opening PRs or merging. Use a separate state file for a
new target version. The final status counts distinguish updated, already-current,
not-installed and unresolved repositories; an unresolved result exits nonzero.

`--installer-dir /path/to/reviewed/package` selects a retained installer independently of
the organization script release. Its package version owns the existing state, branches and
installed files. This lets an orchestration-only repair resume a rollout without reinstalling
another package version or replacing its PRs. Omitting it installs the script’s own package.
