-
Notifications
You must be signed in to change notification settings - Fork 1.3k
docs(devlog): plan the dev to preview to main release train #2755
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| # 260827 release train — dev 7c333f30e to published | ||
|
|
||
| Research and decisions for promoting `dev` to both published channels. Written before any | ||
| branch moves, because a promotion that drops a real commit is not recoverable by rerunning it. | ||
|
|
||
| ## Starting state, measured | ||
|
|
||
| | Thing | Value | | ||
| | --- | --- | | ||
| | `dev` | `7c333f30e`, 293 commits ahead of `main` | | ||
| | `main` | `ec51e42d7`, `release: v2.33.0` | | ||
| | `preview` | `678517f56`, `release: v2.33.0-preview.20260825` | | ||
| | `package.json` on dev | `2.34.0` | | ||
| | npm `latest` | `2.33.0` | | ||
| | npm `preview` | `2.33.0-preview.20260825` | | ||
| | package name | `@bitkyc08/opencodex` | | ||
|
|
||
| Both channels are a full release behind `dev`. | ||
|
|
||
| ## The preview reconciliation question | ||
|
|
||
| `preview` is 2 commits *ahead* of `dev`, which on its face means promoting `dev` onto | ||
| `preview` would discard work. It does not, and the reason is worth writing down rather than | ||
| asserting. | ||
|
|
||
| The two commits are `cf762b1f5` (a `dev` merge) and `678517f56` (the version bump). | ||
| `cf762b1f5` shows a 24-line change to `tests/api-usage.test.ts`, which looks like real | ||
| content — and `git merge-base --is-ancestor cf762b1f5 origin/dev` answers **NO**, so it is | ||
| genuinely not in `dev`'s history. | ||
|
|
||
| The resolution is that ancestry and content are different questions. Measuring content | ||
| directly: | ||
|
|
||
| ``` | ||
| git diff --stat origin/dev origin/preview -- tests/api-usage.test.ts # empty | ||
| git diff $(git merge-base origin/dev origin/preview) origin/preview --name-only | ||
| # -> package.json | ||
| ``` | ||
|
|
||
| So since the merge-base (`e1fb67559`), the *only* file `preview` changes is `package.json`, | ||
| and the only change is the version string. `cf762b1f5` is a merge whose second parent IS the | ||
| merge-base, so the test change it displays arrived on `dev` through a different commit and is | ||
| already present there. Nothing real is lost. | ||
|
|
||
| **Decision:** promote `dev` onto `preview` and let the release bump re-establish the version. | ||
| The stale `2.33.0-preview.20260825` string is exactly what the bump is going to overwrite. | ||
|
|
||
| ## Version numbers | ||
|
|
||
| The published scheme, read from the registry rather than assumed: | ||
| `2.29.0-preview.20260821`, `2.30.0-preview.20260821`, `2.31.0-preview.20260822`, | ||
| `2.32.0-preview.20260824`, `2.32.1-preview.20260825`, `2.33.0-preview.20260825`. So preview is | ||
| `<version>-preview.<YYYYMMDD>` and stable is plain semver. | ||
|
|
||
| - preview target: **`2.34.0-preview.20260827`** | ||
| - stable target: **`2.34.0`** | ||
|
|
||
| `2.34.0` is what `dev`'s `package.json` already reads (wp2 of the hardening unit moved it | ||
| there), it is unused on npm (404) and unused as a git tag, and both targets move their | ||
| channel forward past `2.33.0`, which is what `assertChannelVersionMovesForward` requires. | ||
|
|
||
| ## Mechanics that constrain the plan | ||
|
|
||
| `main` and `preview` rulesets are `[deletion, non_fast_forward, pull_request]` with bypass | ||
| actors `DeployKey = always` and `RepositoryRole 5 = pull_request`. Two consequences: | ||
|
|
||
| - Content promotion cannot be a push. It travels as a PR merged with `--admin`. | ||
| - The release bump push *can* work, but only through the deploy key. `scripts/release.ts` | ||
| reads `OCX_RELEASE_SSH_KEY`; the key at `~/.ssh/opencodex_release_ed25519` was confirmed to | ||
| authenticate and to see both branches. | ||
|
|
||
| `scripts/release.ts` is the release authority and does the whole sequence: preflight | ||
| (clean tree, `audit:high`, `tsc`, the CI-matching test grouping, `privacy:scan`), bump, | ||
| commit, push, wait for **both** Cross-platform CI and Service lifecycle at the release sha, | ||
| then dispatch `release.yml` with `version`, `tag`, `expected-sha`, `dry-run`. | ||
|
|
||
| `release.yml` requires `expected-sha` and defaults `dry-run=true`, so a dry run exercises the | ||
| real release commit. Re-running with `--publish` is the documented second step and the script | ||
| is written to be idempotent across it. | ||
|
|
||
| ## Constraint that shapes execution | ||
|
|
||
| The preflight runs the full test suite locally, which this session is forbidden to do. That | ||
| is not a reason to bypass the preflight — it is a reason to run the suite where it belongs | ||
| (`ssh lidge` via `ocx-run` at the exact release sha) and to let the workflow gates be the | ||
| binding check. The plan records how each phase satisfies the preflight's intent without | ||
| running `bun run test` on this machine. | ||
| ## Amendment — two things the first pass got wrong or left open | ||
|
|
||
| ### The preflight is not the binding gate (resolves the `020` open question) | ||
|
|
||
| `020` framed the local test suite as a problem to work around. Reading `release.yml` | ||
| end to end shows the gates that actually bind are all server-side, and the script's local | ||
| preflight duplicates them as a convenience: | ||
|
|
||
| | Gate | Where it lives | | ||
| | --- | --- | | ||
| | `expected-sha` matches the checked-out commit | `release.yml` "Verify dispatched SHA" | | ||
| | version equals `package.json` | "Verify version matches package.json" | | ||
| | branch/version/dist-tag agree (main=stable+latest, preview=prerelease+preview) | "Require successful Cross-platform CI" | | ||
| | a **push-event** `ci.yml` run succeeded for this sha on this branch | same step — a PR run explicitly does not qualify | | ||
| | Service lifecycle succeeded when service paths changed since the previous merged tag | same step | | ||
| | `audit:high` | "Dependency audit" job step | | ||
| | typecheck + build | `prepublishOnly`, run by `npm publish` | | ||
| | tag/release do not already exist at another sha | "Preflight release metadata" | | ||
|
|
||
| The workflow also creates the git tag and the GitHub Release itself (`git tag` / | ||
| `gh release create`), so nothing about tagging depends on the local script. | ||
|
|
||
| So the route is: bump and push the release commit, let the branch's own push-event CI run, | ||
| then dispatch `release.yml` directly with `gh workflow run`. That is exactly what | ||
| `scripts/release.ts` does at its end, minus the local suite. Recording it this way is not a | ||
| shortcut around a gate; it is declining to run a *duplicate* of gates the workflow enforces | ||
| anyway, on a machine that is not allowed to run them. | ||
|
Comment on lines
+110
to
+114
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Document a supported way to skip the local suite. Lines 72-75 state that 🧰 Tools🪛 LanguageTool[style] ~111-~111: Consider an alternative for the overused word “exactly”. (EXACTLY_PRECISELY) 🤖 Prompt for AI Agents |
||
|
|
||
| The suite still runs — on `ssh lidge` at the exact release sha, as every phase of the | ||
| hardening unit did. | ||
|
|
||
| ### `[WRONG BRANCH]` on promotion PRs is expected, and is how this repo has always released | ||
|
|
||
| `010` guessed that `enforce-target` would complain. It does more than complain: | ||
| `ALLOWED_BASES = ["dev"]` (`enforce-pr-target.yml:254`), so a promotion PR gets a | ||
| `wrong_base` failure AND has `[WRONG BRANCH] ` prepended to its title. | ||
|
|
||
| This is not a new problem to solve. Every promotion in this repository's history carries it: | ||
|
|
||
| ``` | ||
| #2553 codex/promote-main-2330 -> main [WRONG BRANCH] merge dev into main for the v2.33.0 release | ||
| #2507 dev -> main [WRONG BRANCH] release: promote dev into main for v2.32.1 | ||
| #2551 dev -> preview [WRONG BRANCH] merge dev into preview for the v2.33.0-preview... | ||
| ``` | ||
|
|
||
| All merged. The gate has no promotion exemption and the maintainers evidently merge through | ||
| it with admin rather than teaching it about release branches. Follow that precedent rather | ||
| than inventing an exemption: the check failing on a promotion PR is a known false positive, | ||
| and the merge is `--admin` regardless. | ||
|
|
||
| Worth stating plainly since it looks alarming in the checks list: on a promotion PR, | ||
| `enforce-target` failing is the expected outcome, not a signal to stop. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,34 @@ | ||||||||||||||
| # 010 — promote dev content onto preview | ||||||||||||||
|
|
||||||||||||||
| ## What changes | ||||||||||||||
|
|
||||||||||||||
| `preview` moves from `678517f56` to a merge carrying `dev`'s tree at `7c333f30e`. | ||||||||||||||
| No file in the repository is edited by this phase; it is a branch move only. | ||||||||||||||
|
|
||||||||||||||
| ## How | ||||||||||||||
|
|
||||||||||||||
| `preview` requires a pull request, so: | ||||||||||||||
|
|
||||||||||||||
| ``` | ||||||||||||||
| gh pr create --base preview --head dev --title 'release: promote dev to preview' ... | ||||||||||||||
| gh pr merge <n> --merge --admin | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| A PR from `dev` directly rather than a `codex/` branch, because the content being promoted | ||||||||||||||
| IS `dev` — an intermediate branch would add a commit that says nothing. | ||||||||||||||
|
|
||||||||||||||
| Note `enforce-target` rejects PRs that do not target `dev`. A promotion PR targets | ||||||||||||||
| `preview` by definition, so expect that check to complain and confirm it is the | ||||||||||||||
| promotion exemption rather than a real finding before overriding it. | ||||||||||||||
|
Comment on lines
+20
to
+22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Describe the
Proposed wording-Note `enforce-target` rejects PRs that do not target `dev`. A promotion PR targets
-`preview` by definition, so expect that check to complain and confirm it is the
-promotion exemption rather than a real finding before overriding it.
+Note `enforce-target` rejects PRs that do not target `dev`. A promotion PR targets
+`preview` by definition, so expect the known `wrong_base` failure documented in
+`devlog/_plan/260827_release_train/000_state_and_decisions.md` before merging with admin.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| ## Acceptance | ||||||||||||||
|
|
||||||||||||||
| - `git diff --stat origin/dev origin/preview` shows only `package.json` (the stale version | ||||||||||||||
| string) or nothing at all | ||||||||||||||
| - the merge sha is recorded | ||||||||||||||
| - no `src/` or `tests/` file differs between the two branches | ||||||||||||||
|
|
||||||||||||||
| ## What would make this wrong | ||||||||||||||
|
|
||||||||||||||
| Promoting before confirming the reconciliation in `000`. That check is done: since the | ||||||||||||||
| merge-base, `preview`'s only exclusive change is the version string. | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # 020 — publish the preview prerelease | ||
|
|
||
| ## Target | ||
|
|
||
| `2.34.0-preview.20260827`, dist-tag `preview`. | ||
|
|
||
| ## How | ||
|
|
||
| From a checkout on `preview`, with the deploy key exported: | ||
|
|
||
| ``` | ||
| OCX_RELEASE_SSH_KEY=~/.ssh/opencodex_release_ed25519 \ | ||
| bun scripts/release.ts 2.34.0-preview.20260827 --tag preview | ||
| ``` | ||
|
|
||
| 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 preflight problem, and how this phase satisfies it honestly | ||
|
|
||
| The preflight runs the suite locally, which is forbidden here. Do not edit the script to | ||
| skip it. Instead: | ||
|
|
||
| 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. | ||
|
Comment on lines
+28
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
gh workflow run --help | grep -F -- '--ref'
rg -n -C 4 'workflow_dispatch|expected-sha|actions/checkout|ref:' .github/workflows/release.ymlRepository: lidge-jun/opencodex Length of output: 2786 🏁 Script executed: set -euo pipefail
printf '%s\n' '--- applicable repository knowledge files ---'
find /tmp/coderabbit-repo-knowledge/lidge-jun-opencodex-7afea732 -type f -name '*.md' -print
printf '%s\n' '--- knowledge headers ---'
for f in /tmp/coderabbit-repo-knowledge/lidge-jun-opencodex-7afea732/*/*.md; do
printf '\n### %s\n' "$f"
head -40 "$f"
done
printf '%s\n' '--- target files ---'
for f in \
devlog/_plan/260827_release_train/020_preview_release.md \
.github/workflows/release.yml \
.github/scripts/release-dispatch-guard.cjs; do
if [ -f "$f" ]; then
printf '\n### %s\n' "$f"
wc -l "$f"
cat -n "$f"
fi
done
printf '%s\n' '--- release script candidates ---'
fd -i -t f 'release' . | head -80
rg -n -C 8 'workflow run|workflow_dispatch|expected-sha|release\.yml|gh ' . --glob '*release*' --glob '*.ts' --glob '*.js' --glob '*.cjs'Repository: lidge-jun/opencodex Length of output: 50378 🏁 Script executed: set -euo pipefail
printf '%s\n' '--- knowledge file list ---'
find /tmp/coderabbit-repo-knowledge/lidge-jun-opencodex-7afea732 -maxdepth 2 -type f -name '*.md' -print
printf '%s\n' '--- target plan ---'
cat -n devlog/_plan/260827_release_train/020_preview_release.md
printf '%s\n' '--- workflow dispatch and checkout contract ---'
sed -n '1,125p' .github/workflows/release.yml
printf '%s\n' '--- guard implementation ---'
cat -n .github/scripts/release-dispatch-guard.cjs
printf '%s\n' '--- release script path and dispatch references ---'
fd -i -t f 'release' scripts .github
rg -n -C 12 --glob 'scripts/release.ts' --glob '.github/scripts/**' \
'workflow run|workflow_dispatch|expected-sha|release\.yml|GITHUB_SHA|rev-parse|git push|--ref' .Repository: lidge-jun/opencodex Length of output: 19641 🏁 Script executed: set -euo pipefail
printf '%s\n' '--- GitHub conventions ---'
cat -n /tmp/coderabbit-repo-knowledge/lidge-jun-opencodex-7afea732/conventions/github.md
printf '%s\n' '--- script conventions ---'
cat -n /tmp/coderabbit-repo-knowledge/lidge-jun-opencodex-7afea732/conventions/scripts.mdRepository: lidge-jun/opencodex Length of output: 1115 🌐 Web query:
💡 Result: When using the GitHub CLI command gh workflow run, the --ref flag specifies the branch or tag that contains the version of the workflow file you wish to execute [1][2]. Key technical constraints and behaviors include: 1. Requirement of Default Branch Existence: A fundamental constraint of the workflow_dispatch trigger is that the workflow file must exist on the repository's default branch for the event to be registered and executable [3][4][5]. If you attempt to run a workflow that only exists on a feature branch (and has not been merged to the default branch), the command will typically fail with a 404 error, as the GitHub Actions platform cannot discover or initiate the workflow [4][6]. 2. Testing Workflow Changes: To test changes to a workflow file before merging, the file must exist on both the default branch and your target branch (with the same filename) [6]. When you run gh workflow run --ref , GitHub will use the version of the workflow file located at that specific ref [1][6]. 3. GITHUB_SHA Context: When a workflow is triggered via workflow_dispatch, the GITHUB_SHA environment variable is set to the commit SHA of the ref you specified with --ref [7]. This SHA is considered stable for the duration of that specific workflow run [7]. If the --ref flag is omitted, the command defaults to the repository's default branch, and GITHUB_SHA will correspond to the latest commit on that default branch [1][3]. Citations:
Bind the fallback dispatch to 🤖 Prompt for AI Agents |
||
| 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`. | ||
|
|
||
| ## 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # 030 — promote dev content onto main | ||
|
|
||
| ## What changes | ||
|
|
||
| `main` moves from `ec51e42d7` (293 commits behind) to a merge carrying `dev`'s tree. | ||
| This is the promotion the readiness statement in `260827_dev_hardening/070` was written for. | ||
|
|
||
| ## How | ||
|
|
||
| Same shape as `010`: a PR from `dev` to `main`, merged with `--admin`, because `main` | ||
| requires a pull request and the `RepositoryRole` bypass is `pull_request` only. | ||
|
|
||
| ## Ordering | ||
|
|
||
| After `020`, not before. Publishing the preview channel first is what makes the stable | ||
| release a re-publication of already-exercised content rather than a first contact. | ||
|
|
||
| ## Acceptance | ||
|
|
||
| - `git diff --stat origin/dev origin/main` shows only `package.json` or nothing | ||
| - the merge sha is recorded | ||
| - `main` contains `origin/dev` (`git merge-base --is-ancestor origin/dev origin/main`) | ||
|
|
||
| ## Carried forward from the hardening unit | ||
|
|
||
| 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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # 040 — publish the stable release | ||
|
|
||
| ## Target | ||
|
|
||
| `2.34.0`, dist-tag `latest`. | ||
|
|
||
| ## How | ||
|
|
||
| From a checkout on `main`: | ||
|
|
||
| ``` | ||
| 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 | ||
| ``` | ||
|
|
||
| `release.ts` refuses a prerelease version on `main`, so the plain `2.34.0` is required | ||
| here rather than a matter of taste. | ||
|
|
||
| ## Acceptance | ||
|
|
||
| - `npm view @bitkyc08/opencodex dist-tags` shows `latest = 2.34.0` | ||
| - `npm view @bitkyc08/opencodex@2.34.0 gitHead` equals the `main` release commit | ||
| - the `v2.34.0` git tag exists and points at that commit | ||
| - the published tarball's `package.json` version reads `2.34.0` — checked by unpacking, | ||
| not by trusting registry metadata | ||
|
|
||
| ## Risk | ||
|
|
||
| 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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # 050 — docs deploy and installed-runtime proof | ||
|
|
||
| ## Docs deploy | ||
|
|
||
| `deploy-docs.yml` triggers on push to `main` under `docs-site/**`. The `030` promotion | ||
| carries 293 commits of `docs-site` changes, so the deploy should fire on that merge without | ||
| a manual dispatch. Confirm rather than assume: if the path filter did not match, dispatch it. | ||
|
|
||
| ## Installed-runtime proof | ||
|
|
||
| Registry metadata is not the same claim as a working install. Install the published | ||
| version into a scratch prefix and run it: | ||
|
|
||
| ``` | ||
| cd "$(mktemp -d)" && npm i @bitkyc08/opencodex@2.34.0 && npx ocx --version | ||
| ``` | ||
|
|
||
| The version the runtime reports is the evidence, not what the registry says it stored. | ||
|
|
||
| ## Release record | ||
|
|
||
| Write the outcome to this unit: both channel versions, both release commits, both workflow | ||
| run ids, the docs deploy run id, and what was deliberately NOT included (#2745). Then the | ||
| unit is closable to `_fin`. | ||
|
|
||
| ## Acceptance | ||
|
|
||
| - 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If another release advances
latestorpreviewafter this plan's registry check but before execution, the proposed directgh workflow runroute bypassesscripts/release.ts'sassertChannelVersionMovesForwardcheck (scripts/release.ts:339-368).release.ymlverifies that the requested version is unused, but it never compares it with the current channel tip before runningnpm publish --tag;npm publish --helpconfirms that--tag <tag>assigns the distribution tag. An unused but older version could therefore publish successfully and move the channel backward. Keep the release authority in the path, or reproduce its forward-version check immediately before publishing.AGENTS.md reference: AGENTS.md:L21-L24
Useful? React with 👍 / 👎.