|
| 1 | +// SPDX-License-Identifier: MPL-2.0 |
| 2 | +// SPDX-FileCopyrightText: 2026 hyperpolymath (Jonathan D.A. Jewell) <j.d.a.jewell@open.ac.uk> |
| 3 | += ADR-019: Compiler distribution — GitHub Releases binaries + thin Deno/JSR shim |
| 4 | +Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
| 5 | +:toc: |
| 6 | +:toclevels: 2 |
| 7 | +:icons: font |
| 8 | + |
| 9 | +Status:: Accepted |
| 10 | +Date:: 2026-05-19 (accepted); implementation S1–S4 landed 2026-05-20 |
| 11 | +Issue:: https://github.com/hyperpolymath/affinescript/issues/260[INT-04 #260] (split from https://github.com/hyperpolymath/affinescript/issues/181[#181]) |
| 12 | +Series:: Settled-decisions ledger; companion entry in `.machine_readable/6a2/META.a2ml [[adr]] id="ADR-019"`; narrative in `docs/specs/SETTLED-DECISIONS.adoc` §"Compiler Distribution". |
| 13 | + |
| 14 | +== Context |
| 15 | + |
| 16 | +INT-04 (#181) is "publish compiler + runtime". The two halves have very |
| 17 | +different shapes: |
| 18 | + |
| 19 | +* The *runtime* JS packages (`@hyperpolymath/affine-js`, |
| 20 | + `@hyperpolymath/affinescript-tea`) are ordinary JSR/npm packages — their |
| 21 | + publish path is bounded packaging-prep (`docs/PACKAGING.adoc`) and was |
| 22 | + already ready/independent of this decision. |
| 23 | +* The *compiler* is a native OCaml binary. It is **not** a JSR/npm package, |
| 24 | + so "publish the compiler" has no off-the-shelf answer. How consumers — and |
| 25 | + in particular `affinescript-lsp` (INT-10) — install it is a one-way-door |
| 26 | + contract: it sets the install surface, the checksum/trust story, and the |
| 27 | + LSP's compiler-resolution path. |
| 28 | + |
| 29 | +This was escalated as a design fork (issue #260, AskUserQuestion 2026-05-19) |
| 30 | +rather than guessed, because it is irreversible-ish and outside the |
| 31 | +packaging-prep scope. |
| 32 | + |
| 33 | +== Decision |
| 34 | + |
| 35 | +=== Option survey |
| 36 | + |
| 37 | +The fork presented four options: |
| 38 | + |
| 39 | +(1) **GitHub Releases binaries.** Per-platform artifacts via the existing |
| 40 | + `release.yml` (`v*` tags) + an install script / fetch-pin. |
| 41 | + |
| 42 | +(2) **Guix/Nix channel.** Aligns with the repo's primary packaging |
| 43 | + (`guix.scm` / `flake.nix`); reproducible, but a narrower audience and no |
| 44 | + `deno`/`npm` ergonomics for the LSP-installer consumers. |
| 45 | + |
| 46 | +(3) **Thin JSR/npm shim.** A small package that, on first run, downloads a |
| 47 | + pinned, checksummed prebuilt binary and execs it — `deno`/`npm` |
| 48 | + ergonomics without shipping OCaml source. |
| 49 | + |
| 50 | +(4) **Combination** of the above. |
| 51 | + |
| 52 | +=== Chosen: (4) — *Releases-canonical, dual-channel* |
| 53 | + |
| 54 | +The owner chose the combination, with GitHub Releases as the **single |
| 55 | +canonical artifact source** and a thin Deno/JSR shim as the ergonomic front |
| 56 | +door: |
| 57 | + |
| 58 | +* *Canonical artifact.* `.github/workflows/release.yml`, on a `v*` tag, |
| 59 | + builds per-platform compiler binaries and a `SHA256SUMS` manifest, both |
| 60 | + attached to the GitHub Release. The Release is the one source of truth; |
| 61 | + Guix/Nix and any later npm tail are **additive fetch-derivations over it**, |
| 62 | + not separate producers. |
| 63 | + |
| 64 | +* *Ergonomic front door.* A thin Deno/JSR package |
| 65 | + `@hyperpolymath/affinescript` downloads the host-platform binary from the |
| 66 | + pinned Release, verifies it against the `SHA256SUMS` checksum embedded in |
| 67 | + that shim version, then caches and execs it. HTTPS-only, no secrets, one |
| 68 | + version+checksum pinned per shim release (no floating fetch). |
| 69 | + |
| 70 | +* *LSP path.* `affinescript-lsp` (INT-10) resolves the compiler via |
| 71 | + `AFFINESCRIPT_COMPILER` → `affinescript` on `PATH` → the |
| 72 | + `@hyperpolymath/affinescript` shim. Consuming the shim is what unblocks |
| 73 | + INT-10; the LSP does no bespoke bundling. |
| 74 | + |
| 75 | +* *npm tail deferred.* An npm publish is added only if an npm-native consumer |
| 76 | + needs it, mirroring the `affine-vscode` runtime exception. It is **not** |
| 77 | + wired by this ADR. |
| 78 | + |
| 79 | +== Staging |
| 80 | + |
| 81 | +Tracked in `docs/TECH-DEBT.adoc` (INT-04 / INT-10); all four stages have |
| 82 | +landed: |
| 83 | + |
| 84 | +[cols="1,4,1"] |
| 85 | +|=== |
| 86 | +|Stage |Content |Status |
| 87 | + |
| 88 | +|S1 |This ADR + plan; file INT-10. No code. |DONE |
| 89 | +|S2 |`release.yml` per-platform binary build + `SHA256SUMS` matrix |DONE (#283/#284/#285) |
| 90 | +|S3 |The shim package — download + checksum-verify + cache + exec + tests; publish owner-gated via the existing manual JSR workflow |DONE |
| 91 | +|S4 |Wire INT-10 `affinescript-lsp` onto the shim |DONE (#282) |
| 92 | +|=== |
| 93 | + |
| 94 | +First JSR publish landed 2026-05-20: `@hyperpolymath/affinescript@0.1.2` |
| 95 | +LIVE (cross-runtime Deno + Bun + Node, MPL-2.0, sibling `.d.ts` for |
| 96 | +JSR fast-check). The shim pins all three v0.1.1 binaries by SHA256, so the |
| 97 | +resolution path executes end-to-end. Compiler version-string drift was fixed |
| 98 | +in lock-step via `lib/version.ml` (single source of truth) + a release.yml |
| 99 | +tag-time bake step (#297/#300). |
| 100 | + |
| 101 | +== Consequences |
| 102 | + |
| 103 | +* The install contract is *Releases-first*. Renaming the `SHA256SUMS` |
| 104 | + manifest or the per-platform asset names is an ABI change for every shim |
| 105 | + version and for Guix/Nix derivations downstream — do not rename without |
| 106 | + amending this ADR and the shim's pinned spec. |
| 107 | +* Browsers and Cloudflare Workers are out of scope for the shim by |
| 108 | + construction (fetch → save to disk → exec a native binary cannot run in a |
| 109 | + sandboxed JS runtime). The shim's runtime-compatibility matrix is therefore |
| 110 | + Deno ✅ / Bun ✅ / Node ✅ / Workers ❌ / Browsers ❌ (see `.claude/CLAUDE.md` |
| 111 | + Runtime Exemptions). |
| 112 | +* Reproducible-build consumers are served by the additive Guix/Nix |
| 113 | + derivations layered over the same Release artifact, not by a competing |
| 114 | + artifact source. |
| 115 | + |
| 116 | +== References |
| 117 | + |
| 118 | +* Issue: https://github.com/hyperpolymath/affinescript/issues/260 (INT-04 |
| 119 | + compiler distribution), split from |
| 120 | + https://github.com/hyperpolymath/affinescript/issues/181[#181]. |
| 121 | +* Unblocks: https://github.com/hyperpolymath/affinescript/issues/282[INT-10 |
| 122 | + #282] (`affinescript-lsp` distribution). |
| 123 | +* `docs/specs/SETTLED-DECISIONS.adoc` §"Compiler Distribution: GitHub |
| 124 | + Releases Binaries + Thin Deno/JSR Shim (ADR-019)" — the narrative twin of |
| 125 | + this record. |
| 126 | +* `docs/PACKAGING.adoc` §"The compiler itself — decided (ADR-019 / #260)". |
| 127 | +* `docs/TECH-DEBT.adoc` INT-04 / INT-10 rows. |
| 128 | +* `.machine_readable/6a2/META.a2ml [[adr]] id="ADR-019"` — the machine record. |
| 129 | +* `.github/workflows/release.yml` (S2 producer), `.github/workflows/publish-jsr.yml` |
| 130 | + (owner-gated shim publish), `packages/affinescript-cli/` (the shim). |
| 131 | + |
| 132 | +== Standing rule |
| 133 | + |
| 134 | +This decision is *settled and implemented*. Do not reopen without amending |
| 135 | +this ADR. The companion `SETTLED-DECISIONS.adoc` section and the `META.a2ml` |
| 136 | +ADR-019 record must move in lock-step with any amendment. |
0 commit comments