8842a80a44
## What Adds a pre-merge CI check that proves a database created and seeded by the **oldest supported release** (`twentycrm/twenty:v1.22` from Docker Hub) can be upgraded by the **current version built from source**, and that the upgraded instance comes up healthy with its data still queryable. Runs only on PRs touching the upgrade path (`upgrade-version-command/**` + `core-modules/upgrade/**`), and blocks the PR via `ci-server-status-check`. ## How New reusable workflow `ci-cross-version-upgrade.yaml` (`workflow_call` + `workflow_dispatch`), called from `ci-server.yaml` after `server-build` so the build cache is populated in-run: 1. **Services** — `postgres:16` (prod parity) + `redis:7` on a docker network. 2. **Old version** — pull `twentycrm/twenty:v1.22`, boot it against the DB, `workspace:seed:dev`, sanity-check the seed via `psql`. 3. **New version (from source)** — restore the `server-build` nx cache (best-effort: a miss just cold-builds), `nx build`, run the `upgrade` command against the same DB, `start:ci`, poll `/healthz`. 4. **Smoke** — assert `upgrade:status` shows `Instance: Up to date` / `0 behind, 0 failed`, then run companies/people/metadata GraphQL queries. The job is always invoked but gated by a `skip` input (computed from the upgrade-paths `changed-files` check), with a `no-op` job reporting success when skipped — so the status check always resolves instead of leaving a dangling skipped job, mirroring the twenty-infra pattern. Unlike the equivalent post-merge gate in infra-twenty, this is **pre-merge**, uses **native PR path filtering** (no compare API), and **reuses the from-source build cache** instead of pulling an ECR image — no cross-repo plumbing, no skipped-commit gap. ## Security note No credentials are committed. `APP_SECRET` is generated fresh per run (`openssl rand`, `::add-mask::`'d) and shared between the old container and the from-source server within the job; the smoke-test API token is minted at runtime via `workspace:generate-api-key` against the upgraded server and masked in logs. ## Verified with a real run Validated end-to-end by temporarily touching the upgrade path to trigger the job (trigger commit since dropped), in [CI Server run `28097035272`](https://github.com/twentyhq/twenty/actions/runs/28097035272) → [`cross-version-upgrade` job](https://github.com/twentyhq/twenty/actions/runs/28097035272/job/83189453451) ✅ **all steps green**: - v1.22 container boot → `workspace:seed:dev` → `psql` seed sanity check ✅ - from-source build (nx cache restored) → `upgrade` → **56 workspace(s) succeeded, 0 failed** ✅ - server healthy → API token minted at runtime via `workspace:generate-api-key` ✅ - `upgrade:status` → `Instance: Up to date`, `0 behind, 0 failed` ✅ - companies / people / metadata GraphQL smoke queries ✅ - `no-op` job correctly skipped (real job ran because the gate matched) ✅ The three assumptions originally flagged for first-run all held; one bug was found and fixed in the process — `upgrade:status` colorizes via `chalk` even with `NO_COLOR`, so the assertion now strips ANSI escapes before grepping. > Note: the overall `ci-server` run shows a failure from an **unrelated flaky integration test** (`if-else-workflow.integration-spec.ts`, `column workspaceMember.region does not exist` in shard 11). The same trigger commit passed all 16 integration shards in the prior run — it's a pre-existing flake, not caused by this PR. ## Note Still keeping the equivalent one inside infra-twenty as an final bottleneck just in case <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22065?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a> <!-- End of auto-generated description by cubic. -->