Skip to content

S4: CI cutover — release.yml builds from materialized tree, retire fork workflows (fork-absorption Phase 3) #1096

Closed

Description

@jeonghun-jj-lee

Sub-issue of #1091 (Phase 3). S3 (#1095) is closed — cross-compilation is proven. This is the riskiest slice — the actual cutover.

Important

Problem — release.yml, ci.yml, prepare-release-candidate.yml, and overlay-promotion-bot.yml all depend on the fork repo for binary provisioning and provenance verification. After S1-S3 land, the binary can be built from the overlay, but CI still downloads from the fork.

Approach — Replace the fork-download steps with a single build-binary CI job that compiles the binary once and shares the artifact to all consumers via actions/upload-artifact / actions/download-artifact. Rewrite provenance checks against the overlay's version and base SHA (not the fork's repo/tag/ref). Retire fork-only workflows. Remove fork-specific CI secrets last, as a separate manual step after the alpha validates. The ordering is explicit: rewrite provenance gates first, retire fork workflows second, remove secrets last.

Build strategy — S3 proved cross-compilation works. S4 uses a single runner with OPENCODE_BUILD_TARGETS to produce all platform binaries in one job, with a downstream boot-smoke matrix for native execution verification (the fork's existing pattern).

Scope — in: release.yml rewrite (build from overlay, not download from fork); dedicated build-binary CI job with artifact sharing; ci.yml updates (all 5+ jobs that call fetch:opencode gain a needs: build-binary dependency instead); app-bundle-gate provenance check rewrite (verify lock.version against baked OPENCODE_VERSION + upstream_base_sha against materialized base); .buildinfo sidecar for channel assertion; package script update; prepare-release-candidate.yml retirement; overlay-promotion-bot.yml retirement; OPENCODE_FETCH_TOKEN removal; REPO_ACCESS_TOKEN removal (after verifying no remaining consumers); alpha release validation. · out: infrastructure retirement (S6); fork archival (S7).

Design decisions

Build artifact sharing (avoids 7x CI time multiplier)

The binary is built once in a dedicated build-binary CI job. All binary-consuming jobs (fast, vsix-gate, app-shelf-boot-proof, boot-smoke, release packaging) declare needs: build-binary and retrieve the artifact via actions/download-artifact. This avoids 7 jobs each compiling independently (7 × 2-5 min = 14-35 min wasted CI time).

Pin verification replacement

The fork-era lock.repo && lock.tag && lock.ref provenance check is replaced by two checks:

  1. lock.version matches the OPENCODE_VERSION string baked into the compiled binary (the binary already embeds this at build time).
  2. The manifest's upstream_base_sha matches the base commit that was actually materialized into the overlay tree.

These are strictly local checks — no fork repo access required.

Channel assertion mechanism

The build:binary script records OPENCODE_CHANNEL into a .buildinfo sidecar file (plain text, key=value) alongside the compiled binary. The post-build assert_ui_gate.sh assertion reads this sidecar — simple, deterministic, no grepping minified code.

Acceptance Criteria

  • A dedicated build-binary CI job compiles the binary once from the materialized overlay tree for all 3 platforms
  • All binary-consuming CI jobs (fast, vsix-gate, app-shelf-boot-proof, boot-smoke, release packaging) use needs: build-binary + actions/download-artifact — no job compiles independently
  • release.yml packages binaries from the shared artifact — no download from harmoniqs/opencode
  • The package script invokes build:binary instead of fetch_opencode.mjs for binary provisioning
  • A .buildinfo sidecar is written alongside the binary recording OPENCODE_CHANNEL; assert_ui_gate.sh reads it to verify dev channel
  • The app-bundle-gate CI job verifies provenance: lock.version matches baked OPENCODE_VERSION, upstream_base_sha matches the materialized base commit
  • Every CI job that called fetch:opencode instead declares needs: build-binary and downloads the shared artifact (with bun installed)
  • The boot-smoke matrix (native execution on 3 platforms) passes with the new binaries
  • prepare-release-candidate.yml and overlay-promotion-bot.yml are deleted
  • promote.yml still dispatches release.yml correctly
  • An alpha release through the full pipeline succeeds on all platforms
  • After the alpha validates: OPENCODE_FETCH_TOKEN is removed from CI secrets (separate manual step)
  • After the alpha validates: REPO_ACCESS_TOKEN is removed after verifying no remaining consumers (separate manual step)

Rollback path

Code revert and secret removal are separate, sequential steps — secret deletion is non-revertable.

  • Code revert: One PR reverts the CI changes back to the fork-download path. The fork is still alive (not archived until S7) and the old release.yml is one git revert away. This is safe and repeatable.
  • Secret removal: Happens only after the alpha release validates successfully on all platforms. This is a separate manual step precisely because it cannot be undone — re-creating secrets requires regenerating tokens with the correct scopes.

The ordering is: land code changes → validate alpha → remove secrets. Never the reverse.

Obligations (from #1091 review)

  • O1: Verify promote.yml compatibility with the new release.yml
  • O2: Keep boot-smoke matrix as native execution verification
  • O4: Update the package script's internal fetch_opencode.mjs call
  • O7: Specify how the lock-file pin verification step transitions between S4 and S6 — S4 replaces the fork-era check with the lock.version + upstream_base_sha verification described above; S6 restructures the lock file format itself

Risks

  • Cross-compiled binaries may behave differently than natively-built ones — the boot-smoke matrix (native execution, not just compilation) is the safety net.
  • CI time: compiling once is slower than downloading a pre-built binary, but the single-job + artifact-sharing design keeps the increase to one compile (~2-5 min) rather than seven.
  • The package script chains multiple steps; updating one link may break downstream steps.
  • Secret removal is irreversible — the explicit ordering (code first, secrets last, after alpha validation) is the mitigation.

Part of #1091. Answers #843 (retire fork provisioning, replace with overlay build).

Activity

  1. jeonghun-jj-lee commented on Sep 13, 2026

    @jeonghun-jj-lee
    ContributorAuthor

    Adversarial review (round 1) — blocking

    4 blocking, 5 advisory.

    Blocking

    ID Finding Fix
    B1 Rollback path claims "one git revert" but CI secret deletion (OPENCODE_FETCH_TOKEN, REPO_ACCESS_TOKEN) is non-revertable. Reverting code after deleting secrets leaves restored workflows with empty secret values. Sequence secret removal as the last step, AFTER alpha validation. State explicitly that code is revertable but secrets are not.
    B2 No design for build artifact sharing vs. build-per-job. 7 independent CI jobs currently call fetch:opencode (~20-30s each). Replacing each with build:binary (2-5 min) gives 14-35 min additional CI time. bun install caching does not cache the compile step. Add explicit design: build once in a dedicated job, share via actions/upload-artifact, add needs: build-binary to consumers. Or accept the 7x multiplier with documented rationale.
    B3 release.yml pin verification (lock.repo && lock.tag && lock.ref) becomes vestigial after S4 but is not removed or replaced until S6. O7 is acknowledged but unanswered. Specify the replacement verification in S4 (e.g., verify lock.version matches OPENCODE_VERSION and overlay manifest base SHA matches materialized tree).
    B4 "Post-build assertion verifies OPENCODE_CHANNEL=dev" has no specified mechanism. The current assert_ui_gate.sh checks version + auth_token string, not channel. Channel is a build-time Vite define, minified in the binary. Pick a mechanism: .channel sidecar, opencode --channel flag, or .buildinfo file. Make it an explicit design note.

    Advisory

    ID Finding
    A1 app-bundle-gate rewrite lacks a specified replacement provenance mechanism.
    A2 boot-smoke strategy branches on S3 finding — but S3 is already answered (cross-compile works).
    A3 OPENCODE_FETCH_TOKEN removal must follow gate rewrites. Ordering constraint should be explicit.
    A4 package script change makes bun a local dev dependency — contributor-facing regression.
    A5 S4 ACs are written before S3 findings exist — mark as conditional or close S3 first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

hitlNeeds human review before merge

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions