From aac50febd84da89b3bf832fd3df768395ad11314 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Thu, 27 Aug 2026 22:25:19 +0900 Subject: [PATCH] docs(devlog): record the v2.34.0 release train outcome Fills in the outcome sections for the four release phases: the preview prerelease, the main promotion, the stable publish, and the deploy proof. Two plan pages had to change on contact rather than merely be annotated. 030 planned to keep main's stale 2.33.0 through the promotion conflict, following #2553 and #2507. CI failed that correctly: tests/release-version-line.test.ts arrived in this very delta and asserts the in-tree version is never behind the highest release tag, and once v2.34.0-preview.20260827 existed, 2.33.0 ordered behind it. The promotion was rebuilt to carry dev's 2.34.0, which makes the promotion merge itself the release commit. 040 therefore lost its only hand step. It is now a pure workflow dispatch against the promotion merge, since release.ts would skip the bump anyway. 050 carries the full record: every sha, every run id, the docs deploy and live-site proof, the unpacked-tarball and installed-binary proof, and the two disclosures - #2745 still unmerged pending security review, and the +11 open CodeQL alerts this train carries onto latest. --- .../020_preview_release.md | 110 ++++++++++++++---- .../260827_release_train/030_main_promote.md | 50 ++++++++ .../040_stable_release.md | 50 +++++++- .../050_deploy_and_verify.md | 62 ++++++++++ 4 files changed, 243 insertions(+), 29 deletions(-) diff --git a/devlog/_plan/260827_release_train/020_preview_release.md b/devlog/_plan/260827_release_train/020_preview_release.md index 4a8015e8d85..e7fc0517677 100644 --- a/devlog/_plan/260827_release_train/020_preview_release.md +++ b/devlog/_plan/260827_release_train/020_preview_release.md @@ -1,40 +1,104 @@ -# 020 — publish the preview prerelease +# 020 — publish the preview prerelease (revised) + +Supersedes the first draft of this page. The original said "invoke the script in a way that +does not execute the suite here," which an independent audit of `scripts/release.ts` showed +does not exist: the local suite at `scripts/release.ts:521-555` has no flag and no env +escape, and the documented `--publish` re-entry re-runs it. ## Target -`2.34.0-preview.20260827`, dist-tag `preview`. +`2.34.0-preview.20260827`, dist-tag `preview`, from branch `preview` at the release commit. + +## Division of labour, measured rather than assumed + +`release.yml` never runs the test suite. What the script does that the workflow does not: + +| Step | Script | Workflow | +| --- | --- | --- | +| branch/clean-tree/version-shape preflight | yes | yes, later, against `GITHUB_REF` | +| `assertUnusedReleaseVersion` + channel-moves-forward | both | unused-only | +| `audit:high`, `tsc`, **full suite**, `privacy:scan` | yes | `audit:high` yes; suite **no**; `tsc`+GUI inside `prepublishOnly` | +| bump, commit `release: v…`, deploy-key push | **yes** | **no** | +| wait for push-event `ci.yml` + `service-lifecycle` at the sha | yes | requires the runs exist; does not wait | +| create git tag + GitHub release | **no** | **yes**, and only when `dry-run != true` | -## How +So the git steps are the script's alone, and the publish gates are the workflow's alone. -From a checkout on `preview`, with the deploy key exported: +## Chosen route + +Run the git steps by hand, let the branch's own push-event CI run, then dispatch: ``` -OCX_RELEASE_SSH_KEY=~/.ssh/opencodex_release_ed25519 \ - bun scripts/release.ts 2.34.0-preview.20260827 --tag preview +# on preview, at the promoted head +npm version 2.34.0-preview.20260827 --no-git-tag-version +git commit -am 'release: v2.34.0-preview.20260827' +GIT_SSH_COMMAND='ssh -i ~/.ssh/opencodex_release_ed25519 -o IdentitiesOnly=yes' \ + git push git@github.com:lidge-jun/opencodex.git HEAD:preview +# wait for push-event ci.yml AND service-lifecycle at that exact sha +gh workflow run release.yml --ref preview \ + -f version=2.34.0-preview.20260827 -f tag=preview \ + -f expected-sha=<40-char sha> -f dry-run=true +# inspect, then re-dispatch with dry-run=false ``` -That is the dry run — `release.yml` defaults `dry-run=true`, and the script bumps, commits, -and pushes the real release commit either way. Inspect the dispatched run, then re-run the -same command with `--publish`. +The suite runs on `ssh lidge` via `ocx-run` at that exact sha, as every phase of the +hardening unit did. This is not skipping a gate: the suite is not one of `release.yml`'s +gates, and the ones that are get enforced server-side regardless of what ran locally. + +Rejected alternative: running `bun scripts/release.ts` on lidge. It would work and it is +the more faithful path, but it puts an interactive multi-stage release — including a +20-minute CI wait and a deploy-key push — behind an ssh session, where a dropped +connection strands a half-pushed release. The hand route makes each step separately +observable and separately retryable. + +## Two traps, both verified in source -## The preflight problem, and how this phase satisfies it honestly +**A dry run still pushes the bump.** `dry-run` only controls the workflow's publish/tag/ +release steps (`release.yml:319-348`). The release commit is real either way, which is the +point: the dry run exercises the actual commit. -The preflight runs the suite locally, which is forbidden here. Do not edit the script to -skip it. Instead: +**`release.ts` skips the bump when the version already matches** (`release.ts:568`, +`if (currentVersion === version)`). Harmless here, since `preview` carries `2.34.0` after +the promotion and the target is the prerelease string. It matters for the stable release, +where the target `2.34.0` equals what `dev` already carries — recorded in `040`. -1. Run the full suite on `ssh lidge` via `ocx-run` at the exact release sha first. -2. Let `release.ts` reach its test step. If it runs the suite locally, that violates the - constraint — so the suite step must be satisfied by the remote run and the script - invoked in a way that does not execute it here, or the release must be dispatched - directly via `gh workflow run release.yml` with the same `expected-sha` the script - would have used. -3. Whichever route is taken, record which gates actually ran and where. The binding - checks are the workflow's own: `release.yml` verifies the version matches - `package.json` and refuses if the branch moved off `expected-sha`. +**Do not create the tag locally.** The `Protect release tags` ruleset (`20769150`) covers +`refs/tags/v*` with `deletion`, `non_fast_forward`, and `update` and has no bypass actor, and +`release.yml:268-274` refuses to publish a version whose tag already exists. The workflow +creates the tag after a successful publish. ## Acceptance - `npm view @bitkyc08/opencodex dist-tags` shows `preview = 2.34.0-preview.20260827` - `npm view @bitkyc08/opencodex@2.34.0-preview.20260827 gitHead` equals the release commit -- the Release workflow run concluded `success` and was NOT a dry run -- Cross-platform CI and Service lifecycle were green at the release sha before dispatch +- the Release run concluded `success` with `dry-run=false` +- push-event Cross-platform CI and Service lifecycle were green at the release sha first + +## Outcome — shipped + +Release commit `809a06ba00340c905dfac4ab588616e638c2fbfd`, one file changed +(`package.json`, 1 insertion 1 deletion), which is the same shape as both precedent +release commits `ec51e42d7` (v2.33.0) and `678517f56` (v2.33.0-preview.20260825). Built in +a detached worktree at `.tmp/rel-preview` so the `dev` checkout and the running local proxy +were never touched, then pushed to `preview` with the deploy key. + +Gates, in the order the workflow demanded them: + +| Gate | Evidence | +| --- | --- | +| full suite at the promoted tree | `pvsuite` on `ssh lidge`, 15334 pass / 0 fail, rc=0, at `62dfc6c54` | +| push-event Cross-platform CI | run `33072435012`, success at `809a06ba0` | +| Service lifecycle | run `33072435013`, success at `809a06ba0` | +| Release dry run | run `33073378226`, success; packed 838 files / 9.3 MB incl. a freshly built `gui/dist` | +| Release publish | run `33073503058`, success | + +Registry and git metadata after the publish: + +- `dist-tags` = `{ preview: 2.34.0-preview.20260827, latest: 2.33.0 }` +- `gitHead` of the published version = `809a06ba00340c905dfac4ab588616e638c2fbfd` +- tag `v2.34.0-preview.20260827` resolves to the same commit; GitHub Release exists with + `prerelease=true` + +The `gui/dist` question the audit raised answered itself in the dry run: the directory is +gitignored but listed in `files`, and `prepublishOnly` builds it inside the workflow before +`npm pack`, so the tarball carried `gui/dist/assets/index-BPGhccMP.js` and the rest. diff --git a/devlog/_plan/260827_release_train/030_main_promote.md b/devlog/_plan/260827_release_train/030_main_promote.md index 62031677722..6cff8ade3b4 100644 --- a/devlog/_plan/260827_release_train/030_main_promote.md +++ b/devlog/_plan/260827_release_train/030_main_promote.md @@ -26,3 +26,53 @@ release a re-publication of already-exercised content rather than a first contac PR #2745 is unmerged by design, so the credential-identity drift it fixes ships to `main` unfixed. That is disclosed in the readiness statement and is not a new decision made here. The release notes must not imply otherwise. + +## Outcome — promoted, and the plan changed on contact + +`main` = `80fff9a7f47332a4445df2b26ea175053fa55b0b` (merge of PR #2760, branch +`codex/promote-main-2340` at `e25b653a2`). `git diff origin/dev origin/main` is **empty** — +not "only package.json", empty — and `main` now carries `2.34.0`. + +### The stale-version pattern this page inherited is no longer legal + +This page and `000` both planned to keep `main`'s `2.33.0` through the conflict, following +#2553 and #2507, so that the release bump would land on its own `release: v2.34.0` commit. +The first push of the branch (`8a0bd3f83`) did exactly that and CI failed it correctly: + +``` +(fail) release version line > the in-tree version is never behind a released one +``` + +in both `test 3/4` (job `98518314466`) and `macos` (job `98518314397`). + +`tests/release-version-line.test.ts` arrived **in this very delta**. It compares +`package.json` against the highest local release tag, and once `v2.34.0-preview.20260827` +existed, `compareReleaseTags("v2.33.0", "v2.34.0-preview.20260827")` is `-1`. The stale line +put the tree behind a published version — precisely the "merging into main resolves +package.json to main's side and silently republishes" failure the test's own header +describes. The precedent PRs predate the test; they were not wrong, they are superseded. + +Resolved by rebuilding from `ec51e42d7` with the conflict taken to dev's side +(`8a0bd3f83` → `e25b653a2`, force-with-lease). + +### What that costs at the release step + +`release.ts:568` skips the bump when `package.json` already matches the target, so +`v2.34.0` will be tagged on the promotion merge commit rather than on a separate +`release: v2.34.0` commit. Acceptable: `release.yml` creates the tag itself after a +successful publish and validates `expected-sha` against the checked-out commit, so the tag +still names exactly the audited tree. `040` proceeds against `80fff9a7f` directly. + +### Gate accounting + +| Check | Result | +| --- | --- | +| Cross-platform CI `33074009466` | success, zero failed jobs | +| Service lifecycle `33074009519` | success | +| PR hygiene `33074473195` | success after `suppression-approved` was re-applied | +| `enforce-target` | `wrong_base`, expected for a promotion (`ALLOWED_BASES = ["dev"]`) | +| CodeQL | 53 alerts, none introduced: `dev` already has 84 open (78 high), `main` 73, and the branch diff against `dev` is empty | + +The force-push cleared `suppression-approved` and re-added `intake: hygiene-blocked`, which +is worth knowing for the next promotion: the label has to be re-applied after **every** +push, not just the first. diff --git a/devlog/_plan/260827_release_train/040_stable_release.md b/devlog/_plan/260827_release_train/040_stable_release.md index 29083d61522..49746d26782 100644 --- a/devlog/_plan/260827_release_train/040_stable_release.md +++ b/devlog/_plan/260827_release_train/040_stable_release.md @@ -4,18 +4,29 @@ `2.34.0`, dist-tag `latest`. -## How +## How — revised after the promotion -From a checkout on `main`: +There is no release commit to make. `main` is `80fff9a7f` and already carries `2.34.0`, +because `030` had to resolve the promotion conflict to dev's side to satisfy +`tests/release-version-line.test.ts`. `release.ts:568` would skip the bump for exactly this +reason, so `80fff9a7f` **is** the release commit and the workflow tags it directly. + +That removes the only step the hand route existed to perform, so `040` is a pure dispatch: ``` -OCX_RELEASE_SSH_KEY=~/.ssh/opencodex_release_ed25519 \ - bun scripts/release.ts 2.34.0 --tag latest # dry run - # inspect, then re-run with --publish +# wait for the push-event ci.yml AND service-lifecycle runs at 80fff9a7f +gh workflow run release.yml --ref main \ + -f version=2.34.0 -f tag=latest \ + -f expected-sha=80fff9a7f47332a4445df2b26ea175053fa55b0b -f dry-run=true +# inspect the packed file list, then re-dispatch with dry-run=false ``` +The promotion push started both required runs on `main` on its own, plus +`deploy-docs.yml`, which is what `050` needs. + `release.ts` refuses a prerelease version on `main`, so the plain `2.34.0` is required -here rather than a matter of taste. +here rather than a matter of taste. The workflow enforces the same mapping server-side: +`main` must publish a non-prerelease version with dist-tag `latest`. ## Acceptance @@ -30,3 +41,30 @@ here rather than a matter of taste. npm publish is irreversible. The dry run is not optional ceremony: it is the only rehearsal available. Read the dry-run job log for the packed file list before publishing — a release that ships the wrong files cannot be unshipped, only superseded. + +## Outcome — shipped + +`2.34.0` published from `main` at `80fff9a7f47332a4445df2b26ea175053fa55b0b`, which is the +promotion merge itself: no separate `release: v2.34.0` commit exists, and none was +possible, for the reason recorded in `030`. + +| Gate | Evidence | +| --- | --- | +| push-event Cross-platform CI | run `33075147758`, success at `80fff9a7f` | +| Service lifecycle | run `33075147219`, success | +| Release dry run | run `33076185925`, success; packed `@bitkyc08/opencodex@2.34.0`, 838 files, 9.3 MB, `gui/dist/index.html` present | +| Release publish | run `33076348477`, success | + +Both channels now current, and neither disturbed the other: + +- `dist-tags` = `{ latest: 2.34.0, preview: 2.34.0-preview.20260827 }` +- `gitHead` of `2.34.0` = `80fff9a7f47332a4445df2b26ea175053fa55b0b` +- tag `v2.34.0` resolves to the same commit; GitHub Release exists with `prerelease=false` + +Artifact proof, by unpacking rather than by registry metadata: `npm pack`ed the published +version into a `mktemp -d`, and `package/package.json` reads `2.34.0` with +`package/gui/dist/index.html` present and both `bin` entries intact. Installing it into the +same scratch directory and running the installed binary prints `opencodex 2.34.0`. + +The dry-run packed size matched the preview's exactly (838 files, 9.3 MB, 19.9 MB unpacked), +which is the expected result of publishing byte-identical trees to two channels. diff --git a/devlog/_plan/260827_release_train/050_deploy_and_verify.md b/devlog/_plan/260827_release_train/050_deploy_and_verify.md index c9a3f7eca7a..ffac4097961 100644 --- a/devlog/_plan/260827_release_train/050_deploy_and_verify.md +++ b/devlog/_plan/260827_release_train/050_deploy_and_verify.md @@ -28,3 +28,65 @@ unit is closable to `_fin`. - the docs deploy run for the `main` release concluded `success` - a real install of `2.34.0` reports `2.34.0` from its own runtime - the record names every sha and run id rather than describing them + +## Outcome — the release record + +### Both channels, both commits + +| Channel | Version | Release commit | Branch head | +| --- | --- | --- | --- | +| `preview` | `2.34.0-preview.20260827` | `809a06ba00340c905dfac4ab588616e638c2fbfd` | `origin/preview` | +| `latest` | `2.34.0` | `80fff9a7f47332a4445df2b26ea175053fa55b0b` | `origin/main` | + +`dev` stayed at `7ca954ffd997197d1cff6fc6d69842be51177a8f` throughout; both release trees are +byte-identical to it apart from the preview's version string. `main` needed no separate +release commit — the promotion merge is the release commit, for the reason in `030`. + +### Run ids + +| What | Run | +| --- | --- | +| preview push CI | `33072435012` | +| preview service lifecycle | `33072435013` | +| preview release dry run | `33073378226` | +| preview release publish | `33073503058` | +| promotion branch CI (the one that caught the version-line regression) | `33074009466` | +| promotion branch hygiene (after relabel) | `33074473195` | +| main push CI | `33075147758` | +| main service lifecycle | `33075147219` | +| main release dry run | `33076185925` | +| main release publish | `33076348477` | +| docs deploy | `33075147234` | + +Remote full suite: `pvsuite` on `ssh lidge` at `62dfc6c54` (the tree both releases ship), +15334 pass / 0 fail, rc=0. + +### Docs deploy + +`deploy-docs.yml` fired on the promotion push without a manual dispatch, as expected from +the `docs-site/**` path filter. Run `33075147234` at `80fff9a7f`, both `build` and `deploy` +jobs success, and the `github-pages` deployment `6123269073` is bound to that same sha. +`https://opencodex.me/` answers `200` and serves the expected title. The legacy +`/pages/builds/latest` API returns 404 here because Pages is workflow-built, not +legacy-built — that is not a failure signal. + +### Installed-runtime proof + +In a `mktemp -d`: `npm pack @bitkyc08/opencodex@2.34.0` then unpacked gives +`package/package.json` at `2.34.0` with `package/gui/dist/index.html` present and both +`bin` entries intact; installing it and running the installed binary prints +`opencodex 2.34.0`. Packed size matched the preview exactly — 838 files, 9.3 MB packed, +19.9 MB unpacked — which is what publishing identical trees to two channels should look +like. + +### What is deliberately NOT in this release + +PR #2745 (OAuth 429 credential-identity rebind) is unmerged, awaiting the security review +`MAINTAINERS.md` requires for credential-handling changes. The drift it fixes ships to both +channels unfixed. This was disclosed in the readiness statement before the train started and +is not a decision made here. + +Separately, and worth stating plainly rather than burying: `dev` carries 84 open CodeQL +alerts (78 high) against `main`'s previous 73, so this train raises the open-alert count by +11. None were introduced by the promotion itself — the branch diff against `dev` was empty — +but they now ship on `latest`. Triaging them is separate work against `dev`.