Skip to content

fix(release): derive DSH bootstrap version from installer bytes - #7107

Merged
lefarcen merged 1 commit into
mainfrom
fix/dsh-bootstrap-auto-version
Aug 19, 2026
Merged

fix(release): derive DSH bootstrap version from installer bytes#7107
lefarcen merged 1 commit into
mainfrom
fix/dsh-bootstrap-auto-version

Conversation

@lefarcen

Copy link
Copy Markdown
Contributor

Why

The last production landing-page promotion (run 32213116590) failed and never reached Cloudflare Pages, so open-design.ai is still serving the 08-14 build.

The failing step was Publish immutable DeepSeek Harness bootstrap installers to R2:

Error: immutable bootstrap object already exists with different content:
       bootstrap/dsh/v1/install-dsh.ps1

landing-page-production.yml pinned DSH_BOOTSTRAP_VERSION: v1, while the publisher refuses to overwrite an already-published immutable version whose bytes differ. #6983 renamed the brand to OpenDesign inside install-dsh.ps1 and install-dsh.sh (5 copy lines total) without bumping that pin, so the very next production deploy failed closed — and because the step sits before the deploy, every later step was skipped, including the Pages publish and the D1 migration.

The fail-closed behavior is correct and worth keeping: these bytes get an immutable one-year cache header and act as the supply-chain archive for the open-design.ai/install-dsh.* entry points. The defect is that the version is a function of the installer bytes but was maintained as a hand-written constant. Anyone editing a line of installer copy has to remember to bump a version in a workflow they are not touching, and the failure surfaces only at production promotion time with an error that never names the fix.

What users will see

Nothing changes about the one-click install commands themselves — they still point at open-design.ai/install-dsh.*.

Operationally, editing an installer no longer breaks the production deploy. The promotion resolves the bootstrap version on its own: unchanged installers reuse the published version, changed installers roll forward to the next one. https://releases.open-design.ai/bootstrap/dsh/latest.json is new and names the current version plus each file's sha256 and the publishing run.

On the next production run this failure clears itself: the publisher sees that v1 holds different bytes, mints v2 with the OpenDesign copy, and leaves v1 intact as the historical archive. No brand rename gets reverted.

Surface area

  • None — release tooling, workflow, tests, docs

Bug fix verification

  • Test path: tools/serve/tests/dsh-bootstrap-publish.test.ts
  • Red on main, green on this branch: yes

The existing suite already ended with an assertion that changed bytes must throw. That assertion is inverted into the new expected behavior (auto-mint v2, v1 keeps its original bytes, pointer follows), plus a rollback case proving previously-published bytes reuse their original version instead of minting a third copy. Run against the pre-fix publisher it fails on the missing DSH_BOOTSTRAP_VERSION; against this branch both tests pass.

A second test pins DSH_BOOTSTRAP_VERSION explicitly and asserts the old fail-closed error still fires, so the escape hatch keeps its guarantee.

How it resolves the version

SHA256SUMS fingerprints all three installers, so one GET per version decides the whole set. The publisher probes v1, v2, … and settles on the first version that either does not exist (mint it) or already holds exactly these bytes (reuse it).

This needs no new R2 permissions — the storage layer only signs GET/PUT and has no ListObjectsV2, and the probe is GET-only. Every published version still goes up with if-none-match: * and the immutable cache header, so history stays permanent and unoverwritable; only the manual bump is gone. Concurrent promotions of different bytes still collide on the 412 path and fail loudly rather than clobbering.

Adjacent issues

Not addressed here: the R2 archive has no consumer that actually diffs it against what Pages serves, so its supply-chain value is currently latent. Worth deciding separately whether to publish these checksums to users or drop the archive.

Validation

  • pnpm guard — exit 0
  • pnpm typecheck — exit 0
  • pnpm --filter @open-design/tools-serve typecheck / @open-design/tools-release typecheck — exit 0
  • tools/servevitest run tests/dsh-bootstrap-publish.test.ts — 2 passed (red before the fix)
  • e2evitest run tests/packaged-smoke-workflow.test.ts — 70 passed, 1 skipped

Not run: the real R2 publish path. It needs production credentials, so it is exercised through the storage fixture server rather than end-to-end.

The production landing-page promotion pinned DSH_BOOTSTRAP_VERSION: v1 in
the workflow, while the publisher refuses to overwrite an already-published
immutable version with different bytes. PR #6983 renamed the brand inside
install-dsh.ps1 and install-dsh.sh without bumping that pin, so the next
production deploy failed closed on

  immutable bootstrap object already exists with different content:
  bootstrap/dsh/v1/install-dsh.ps1

and skipped every later step, including the Cloudflare Pages deploy. The
version is a function of the installer bytes, so maintaining it as a
hand-written constant guarantees this drift.

The publisher now resolves the version itself: it probes published versions
in order using SHA256SUMS as the fingerprint for the whole set, reuses the
version that already holds these exact bytes, and mints the next one when
the bytes changed. Every published version keeps if-none-match: * and the
immutable cache header, so history stays permanent and unoverwritable --
only the manual bump is gone. A mutable bootstrap/dsh/latest.json pointer
records the current version, per-file sha256, and the publishing run, and
the verify step reads the resolved version instead of naming one.

DSH_BOOTSTRAP_VERSION survives as an explicit escape hatch: setting it
forces one version and still fails closed on a content mismatch.
@lefarcen
lefarcen requested a review from a team as a code owner August 19, 2026 04:10
@lefarcen lefarcen added size/M PR changes 100-300 lines risk/high High risk: apps/desktop, daemon, auth, migration, workflows, package deps type/bugfix Bug fix skip-validation Maintainer override: bot will not auto-add needs-validation on this PR. labels Aug 19, 2026
@lefarcen
lefarcen requested a review from nettee August 19, 2026 04:15
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Landing page preview

This PR is deployed to a Cloudflare Pages preview — not staging or production:

@nettee nettee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lefarcen

I reviewed all five changed files and verified the byte-derived version resolution, immutable R2 writes, latest pointer, workflow output handoff, documentation, and regression coverage. I ran pnpm guard, pnpm typecheck, the focused DSH bootstrap publisher test (2 passed), and the packaged workflow smoke test (70 passed, 1 skipped); everything passed. This is a thoughtful fix for the production version-drift failure—nice work preserving the fail-closed path while making normal promotions self-healing.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@lefarcen
lefarcen added this pull request to the merge queue Aug 19, 2026
Merged via the queue into main with commit cdcd2af Aug 19, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk/high High risk: apps/desktop, daemon, auth, migration, workflows, package deps size/M PR changes 100-300 lines skip-validation Maintainer override: bot will not auto-add needs-validation on this PR. type/bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants