Merge branch 'main' into website
Bring v0.2.0 + install/packaging surface (crates.io, curl|sh installer, Windows install.ps1, Scoop/Homebrew/winget, --version/version command) onto the website branch for documentation reconciliation.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# ADR-0054: Release versioning policy + version surfaces (`--version` / `version`)
|
||||
|
||||
## Status
|
||||
|
||||
Accepted — **implemented 2026-06-16** (plan:
|
||||
`docs/plans/20260616-public-availability.md`, step 1). First step on the
|
||||
road to public availability. Adds a `--version` / `-V` CLI flag and an
|
||||
in-app `version` command, both reporting `CARGO_PKG_VERSION`, plus a
|
||||
release-CI guard that the `v*` tag equals that version. No prior issue or
|
||||
`requirements.md` item existed for this — it was an untracked gap.
|
||||
|
||||
## Context
|
||||
|
||||
Before this, `Cargo.toml` carried `version = "0.1.0"`, the binary exposed
|
||||
**no** way to report its version, and `release.yaml` named assets from the
|
||||
**git tag** (`rdbms-playground-<tag>-<target>`) while building from
|
||||
`Cargo.toml`. Tag and crate version were **decoupled**: tagging `v0.2.0`
|
||||
would publish an asset named `…-v0.2.0-…` containing a binary that (had it
|
||||
been able to say) reported `0.1.0`. On the way to public availability —
|
||||
where users download a versioned artifact and file bug reports against "the
|
||||
version I'm running" — that drift is a correctness problem.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **`Cargo.toml` `version` is the single source of truth.** This is the
|
||||
idiomatic Rust position and avoids making `Cargo.toml` lie. The version
|
||||
is read at compile time via `env!("CARGO_PKG_VERSION")`; no build-time
|
||||
injection of the tag into the crate.
|
||||
|
||||
2. **Two user-facing surfaces, one source:**
|
||||
- **`--version` / `-V`** — CLI flag (hand-rolled parser, mirrors
|
||||
`--help`): prints and exits before any other work (`main.rs`).
|
||||
- **`version`** — an in-app app command (REGISTRY node `app::VERSION`,
|
||||
`AppCommand::Version`), emitting the same line into the output panel.
|
||||
Both go through `cli::version_text()` → the catalog key
|
||||
`cli.version_line` (`"rdbms-playground {version}"`), so there is exactly
|
||||
one rendered string and one version source.
|
||||
|
||||
3. **Release-CI discipline.** `release.yaml`'s pre-build `test` job gains a
|
||||
**version guard**: it reads the `[package]` version directly from
|
||||
`Cargo.toml` (`grep -m1 '^version = '` — toolchain-free; an earlier
|
||||
`cargo metadata | node` form broke on the flake devShell's stdout banner)
|
||||
and **fails the release** unless the pushed tag equals `v<that version>`.
|
||||
So `--version`, the release name, and the downloaded asset are always in
|
||||
lockstep — enforced by the machine, not by memory.
|
||||
|
||||
4. **The release ritual:** bump `Cargo.toml` → commit → tag `v<that
|
||||
number>` → push the tag. The guard rejects any deviation.
|
||||
|
||||
### Rejected / deferred
|
||||
- **Inject the tag into the build** (tag as source of truth): fiddly with
|
||||
cargo and makes `Cargo.toml` a placeholder/lie. Rejected.
|
||||
- **Git-hash + build-date enrichment** (a `build.rs` so dev builds read
|
||||
`0.2.0 (a1b2c3d)`): useful for bug reports, but not needed for the
|
||||
tag↔release↔`--version` consistency this ADR is about. Deferred; can be
|
||||
added behind the same `version_text()` seam without changing the policy.
|
||||
- **UI placement beyond the `version` command** (status-bar string, etc.):
|
||||
the command + `help` listing is enough for now (user decision).
|
||||
|
||||
## Consequences
|
||||
|
||||
- A release can no longer ship a binary whose self-reported version
|
||||
disagrees with its tag/filename.
|
||||
- Cutting a release now *requires* a `Cargo.toml` bump commit — a small,
|
||||
deliberate step (and a natural place to update a changelog later).
|
||||
- New keys: `cli.version_line` (+ `help.app.version`, `parse.usage.version`,
|
||||
`hint.cmd.version.what`/`.example`); a new REGISTRY command means the
|
||||
comprehensiveness coverage test now also requires `hint.cmd.version`,
|
||||
which is supplied. Tested: CLI flag parse (`--version`/`-V`/default-off),
|
||||
`version_text()` carries `CARGO_PKG_VERSION`, the in-app command parses to
|
||||
`AppCommand::Version` and emits the version.
|
||||
- This is step 1 of `docs/plans/20260616-public-availability.md`; the
|
||||
installer (`install.sh`) and package-manager work (D3) build on top.
|
||||
@@ -0,0 +1,86 @@
|
||||
# ADR-0055: `curl | sh` install script (`scripts/install.sh`)
|
||||
|
||||
## Status
|
||||
|
||||
Accepted — **implemented 2026-06-17** (plan:
|
||||
`docs/plans/20260616-public-availability.md`, step 2). Step 2 on the road
|
||||
to public availability, building on ADR-0054 (versioned releases) and
|
||||
ADR-ci-001/003 (the Gitea releases it downloads from). Tracked by the
|
||||
plan + this ADR (no Gitea issue — user decision, 2026-06-17).
|
||||
|
||||
## Context
|
||||
|
||||
Until now, installing meant: find the releases page, work out which of
|
||||
the six assets matches your machine, download it, `chmod +x`, move it
|
||||
onto `PATH`, and (on macOS) wonder about Gatekeeper. That is too much
|
||||
friction for a teaching tool aimed at beginners. The Gitea releases are
|
||||
**publicly downloadable** (confirmed), with deterministic asset names
|
||||
(`rdbms-playground-<tag>-<target>[.exe]`) and `.sha256` sidecars
|
||||
(ADR-ci-003), and a `releases/latest` API — enough to script a one-liner
|
||||
install.
|
||||
|
||||
## Decision
|
||||
|
||||
Ship **`scripts/install.sh`**, run as
|
||||
`curl -fsSL <gitea-raw>/scripts/install.sh | sh`:
|
||||
|
||||
- **POSIX `sh`** (no bashisms) — it runs under the `sh` of `curl | sh`;
|
||||
kept **shellcheck-clean** (`-s sh`).
|
||||
- **Platform detection** from `uname` → target triple: Linux →
|
||||
`<arch>-unknown-linux-musl` (the fully-static build — one universal
|
||||
Linux artifact, no glibc/version coupling), macOS → `<arch>-apple-darwin`;
|
||||
`x86_64`/`amd64` and `aarch64`/`arm64` both map. **Windows is rejected**
|
||||
with a pointer to Scoop/winget/the releases page (the binary is a `.exe`,
|
||||
not a `curl|sh` target).
|
||||
- **Version:** the `releases/latest` API tag by default; `RDBMS_VERSION`
|
||||
pins a specific tag.
|
||||
- **Integrity:** always download the `.sha256` sidecar and **verify**
|
||||
(`sha256sum`/`shasum -a 256`); a mismatch aborts the install. HTTPS only.
|
||||
- **Install location:** `~/.local/bin` by default (user-writable, no
|
||||
sudo), overridable via `RDBMS_INSTALL_DIR`; prints a PATH hint if the
|
||||
dir isn't on `PATH`.
|
||||
- **macOS note:** a `curl` download is **not** Gatekeeper-quarantined, so
|
||||
the binary runs as-is even while it is only ad-hoc-signed; proper
|
||||
Developer-ID signing + notarization (for *browser* downloads) is a
|
||||
separate, postponed task (see the plan's signing item).
|
||||
- **Testing seams:** `RDBMS_OS`/`RDBMS_ARCH` force detection and
|
||||
`--print-target` prints the resolved triple and exits — so the mapping
|
||||
is checkable without a download.
|
||||
|
||||
### Rejected / deferred
|
||||
- **Hosting the script on the website domain** (Cloudflare): nicer URL,
|
||||
but adds a moving part; the **Gitea repo raw URL** is simplest and the
|
||||
binaries live there anyway (user decision). The website may later
|
||||
*reference* the same command.
|
||||
- **Uploading `install.sh` as a release asset** for a stable link:
|
||||
optional; the branch raw URL is fine for now.
|
||||
|
||||
## Amendment 1 — `install.ps1` (Windows) added (2026-06-17)
|
||||
|
||||
Windows was originally deferred to Scoop/winget; the user opted for **both**
|
||||
a PowerShell one-liner now *and* package managers later. Added
|
||||
**`scripts/install.ps1`** (`irm <url> | iex`): maps the host CPU to our
|
||||
`*-windows-gnu`/`-gnullvm` `.exe`, resolves the latest release (or
|
||||
`-Version`/`RDBMS_VERSION`), downloads + **SHA-256-verifies**, installs to
|
||||
`%LOCALAPPDATA%\Programs\rdbms-playground` (`-InstallDir`/`RDBMS_INSTALL_DIR`
|
||||
override), and adds that dir to the **user PATH**. **Caveat:** unlike
|
||||
`install.sh` (verified end-to-end), this was **written but not tested from
|
||||
this environment** (no PowerShell available) — validate on a real Windows
|
||||
host. Scoop/winget (D3) remain the idiomatic package-manager routes.
|
||||
|
||||
## Consequences
|
||||
|
||||
- A first-time user runs one line and gets a checksum-verified binary on
|
||||
`PATH`. The website's install copy (website branch, separate agent) can
|
||||
point at this command.
|
||||
- **Verified end-to-end** (2026-06-17) against the live public `v0.1.0`:
|
||||
all four Linux/macOS platform mappings + Windows/unknown-arch rejection;
|
||||
pinned and latest paths; checksum verification incl. a tamper-rejection
|
||||
check; install + run on Linux x86_64. (The installed `v0.1.0` predates
|
||||
`--version`, ADR-0054 — a non-issue, and the reason to cut a new
|
||||
release.)
|
||||
- **No automated regression guard in CI yet:** shellcheck isn't in the
|
||||
flake, and there's no shell-test harness here (no bats). Recommended
|
||||
follow-up: add a `shellcheck scripts/*.sh` gate (touches ADR-ci-002 —
|
||||
needs shellcheck in the devShell). For now the guard is local
|
||||
shellcheck + the documented end-to-end verification.
|
||||
@@ -0,0 +1,319 @@
|
||||
# ADR-0056: crates.io publish-readiness + `cargo binstall` metadata (D3)
|
||||
|
||||
## Status
|
||||
|
||||
Accepted — **prepared 2026-06-17** (plan:
|
||||
`docs/plans/20260616-public-availability.md`, step 3a). The crate is made
|
||||
**ready to publish** and carries `cargo-binstall` metadata. The actual
|
||||
`cargo publish` is a gated maintainer step (see Ordering). First D3
|
||||
package-manager mechanism; builds on ADR-0054 (versioned releases),
|
||||
ADR-0055 (installer), ADR-ci-003 (release assets). Tracked by plan + ADR
|
||||
(no Gitea issue — user decision).
|
||||
|
||||
## Context
|
||||
|
||||
`cargo binstall rdbms-playground` (and `cargo install`) need the crate on
|
||||
**crates.io** (user decision, 2026-06-17). The manifest had
|
||||
`publish = false`, a `readme = "README.md"` pointing at a **missing**
|
||||
file, and no `keywords`/`categories`. Our release assets are **bare
|
||||
binaries** (not archives) named `rdbms-playground-v<version>-<target>`
|
||||
(`.exe` on Windows) with `.sha256` sidecars (ADR-ci-003); critically the
|
||||
**release target triples differ from users' host triples** — we ship the
|
||||
static `*-linux-musl` build (hosts are `*-linux-gnu`) and
|
||||
`*-windows-gnu`/`-gnullvm` (hosts are `*-msvc`); only macOS matches.
|
||||
|
||||
## Decision
|
||||
|
||||
**Publish-readiness (this change):**
|
||||
- Drop `publish = false`; add `homepage = "https://relplay.org"`,
|
||||
`keywords`, `categories = ["command-line-utilities", "database"]`, and
|
||||
an `exclude` (`/website`, `/docs`, `/.gitea`, `/.codegraph`) so the
|
||||
published crate is code-only (585 files/8.3 MiB → 353/913 KiB
|
||||
compressed).
|
||||
- Author **`README.md`** (the `readme` target + crates.io front page;
|
||||
engine-neutral and "simple/advanced mode" wording per ADR-0002 / the
|
||||
website copy rules), with install instructions (curl|sh, binstall,
|
||||
source, prebuilt).
|
||||
- Add **`LICENSE-MIT`** and **`LICENSE-APACHE`** (the latter the verbatim
|
||||
canonical text, added by the maintainer; both © Lazy Evaluation Ltd —
|
||||
the publication entity), and a **`CONTRIBUTING.md`** stating the
|
||||
"inbound = outbound" dual-license arrangement (so Apache-2.0 §5 makes
|
||||
the §3 patent grant explicit on the self-hosted forge). Dual license
|
||||
kept (not MIT-only) — user decision after reviewing the patent-grant
|
||||
rationale.
|
||||
|
||||
**`cargo binstall` metadata** (`[package.metadata.binstall]`, syntax
|
||||
verified against cargo-binstall SUPPORT.md):
|
||||
- `pkg-fmt = "bin"` (bare binary), `bin-dir = "{ bin }{ binary-ext }"`,
|
||||
and a base `pkg-url` using `v{ version }` (the `{ version }` placeholder
|
||||
excludes the leading `v`).
|
||||
- **Per-target `overrides`** mapping the common host triples to the asset
|
||||
we actually publish: `x86_64`/`aarch64-unknown-linux-gnu` → the `-musl`
|
||||
asset; `x86_64`/`aarch64-pc-windows-msvc` → the `-gnu`/`-gnullvm`
|
||||
`.exe`. macOS needs no override (host triple == asset triple). The docs
|
||||
do **not** promise automatic musl/gnu or msvc/gnu fallback, hence
|
||||
explicit overrides.
|
||||
|
||||
**Ordering / gating (important):**
|
||||
- `cargo publish` is **irreversible** (needs the crates.io token; a
|
||||
version can't be un-published, only yanked) — a deliberate **maintainer
|
||||
step**, not done here.
|
||||
- binstall's `pkg-url` resolves to a **tagged release's** assets, so
|
||||
publish **at a new tagged version whose release already exists**, and
|
||||
publish **after** that release is built. **Do not publish `0.1.0`** — it
|
||||
would diverge from the already-released `0.1.0` binaries (which predate
|
||||
`--version`, ADR-0054). The clean path: bump → tag → release builds →
|
||||
`cargo publish`.
|
||||
|
||||
## Verification
|
||||
|
||||
- `cargo publish --dry-run --allow-dirty` packages + verify-builds cleanly
|
||||
(353 files, 913 KiB compressed; no metadata errors).
|
||||
- `cargo metadata` confirms the `binstall` block + all four `overrides`
|
||||
parse.
|
||||
- **Unverified:** an actual `cargo binstall` run — cargo-binstall isn't a
|
||||
dependency and nothing is on crates.io yet. **Validate at the first
|
||||
publish + matching release** (especially the windows-msvc→gnu and
|
||||
linux-gnu→musl overrides).
|
||||
|
||||
## Consequences
|
||||
|
||||
- The crate can be published at the next tagged release with `cargo
|
||||
publish` (+ the token); `cargo install rdbms-playground` and `cargo
|
||||
binstall rdbms-playground` then work.
|
||||
- Remaining D3: Scoop, Homebrew (`lazyeval` tap), winget (komac/manual) —
|
||||
each a manifest + a per-release bump, tracked in the plan.
|
||||
- Remaining follow-up: run the real `cargo binstall` validation at the
|
||||
first publish + matching release (the license files, © holder, and
|
||||
CONTRIBUTING are now in place).
|
||||
|
||||
## Amendment 1 — 2026-06-18: published live + a manual `publish` workflow
|
||||
|
||||
**`rdbms-playground 0.2.0` is published to crates.io** (`cargo install` and
|
||||
`cargo binstall rdbms-playground` both verified working by the user). The
|
||||
"unverified binstall" caveat is resolved — the per-target overrides
|
||||
resolve correctly against the `v0.2.0` release assets.
|
||||
|
||||
**How publishing is wired:** a new **manual `workflow_dispatch` workflow**
|
||||
(`.gitea/workflows/publish.yaml`), mirroring `release-macos.yaml`, takes a
|
||||
`tag` input and runs `cargo publish` (token via the
|
||||
`CARGO_REGISTRY_TOKEN` Gitea Actions secret — a crate-scoped,
|
||||
publish-update token). **Not** automated on the tag, by decision: the
|
||||
publish is irreversible (yank-only), keeping the registry token off every
|
||||
tag push; the release is split (Linux/Windows on the tag, macOS
|
||||
dispatched), so a human is the natural "all assets are up — go" gate; and
|
||||
crates.io has no Gitea-Actions trusted-publishing path today, so a stored
|
||||
token on the self-hosted runner would be the only automated option.
|
||||
Each registry is its **own idempotent job** (no inter-job `needs`) — the
|
||||
crates.io job skips cleanly if the version is already published (crates.io
|
||||
API pre-check + `cargo publish` as the backstop) — so future
|
||||
Scoop/Homebrew/winget jobs can be added alongside without breaking one
|
||||
another or re-runs. The first such job's `tag`-vs-`Cargo.toml` guard
|
||||
mirrors `release.yaml`.
|
||||
|
||||
## Amendment 2 — 2026-06-19: Scoop bucket + Homebrew tap (D3 §3b/§3c)
|
||||
|
||||
Two more package managers wired as **sibling `publish.yaml` jobs**
|
||||
(`scoop-bucket`, `homebrew-tap`), following Amendment 1's independent +
|
||||
idempotent pattern. Each fetches the release's `.sha256` sidecars, renders
|
||||
a manifest, and commits it into a per-manager repo.
|
||||
|
||||
**Repos — org-level and multi-package.** Both live under a new **`lazyeval`
|
||||
Gitea organisation** (created with the `oli` account, which gives the
|
||||
`git.lazyeval.net/lazyeval/...` paths): `lazyeval/scoop-bucket` and
|
||||
`lazyeval/homebrew-tap`. A Scoop *bucket* and a Homebrew *tap* are by
|
||||
definition **collections of manifests**, so these are reusable for future
|
||||
tools, not single-package repos. Homebrew's `homebrew-` repo-name prefix is
|
||||
mandatory (→ referenced as `lazyeval/tap`); Scoop's bucket name is free.
|
||||
Users: `scoop bucket add lazyeval <url>` (the label is local/arbitrary;
|
||||
only the URL owner is real) then `scoop install rdbms-playground`; and
|
||||
`brew tap lazyeval/tap https://git.lazyeval.net/lazyeval/homebrew-tap`
|
||||
(the explicit-URL form — the `user/repo` shorthand assumes GitHub) then
|
||||
`brew install lazyeval/tap/rdbms-playground`.
|
||||
|
||||
**Credential — a scoped bot user, not an `oli` PAT.** Gitea PATs scope by
|
||||
**permission category, not per-repository** (`write:repository` grants
|
||||
write to *every* repo the account can reach — there is no repo picker like
|
||||
GitHub fine-grained PATs). So an `oli` token would also be able to push to
|
||||
`oli/rdbms-playground` itself. Instead a dedicated bot user **`lazyeval-ci`**
|
||||
is a member of a `lazyeval` org team with **Write** to the package repos
|
||||
only; its `write:repository` PAT is therefore effectively scoped to those
|
||||
repos and **cannot touch the main project repo**. Stored as the
|
||||
`LAZYEVAL_PKG_TOKEN` Actions secret on `oli/rdbms-playground` (where the
|
||||
workflow runs — *not* an org secret, which wouldn't reach a user-repo
|
||||
workflow; *not* on the target repos, which only receive pushes). Passed via
|
||||
`env:` (never inlined), so it stays masked and only materialises in the
|
||||
clone URL at runtime; pushes go to `HEAD:main` (assumes the repos default
|
||||
to `main`).
|
||||
|
||||
**Render scripts are dependency-free bash.** The CI job container is
|
||||
`node:22-bookworm-slim` — **no jq, no ruby** — so
|
||||
`scripts/render-{scoop-manifest,homebrew-formula}.sh` are pure bash
|
||||
(heredocs, no external deps) taking a version + the relevant hashes and
|
||||
emitting the manifest on stdout. `scripts/test-package-renders.sh` is their
|
||||
test (JSON validated with `node` — present in the image — plus `jq`/`ruby`
|
||||
when available; field-level assertions). The job validates the rendered
|
||||
Scoop JSON with `node -e JSON.parse` before committing.
|
||||
|
||||
**Manifest specifics.**
|
||||
- *Scoop* (`rdbms-playground.json` at bucket root): `64bit` =
|
||||
`x86_64-pc-windows-gnu.exe`, `arm64` =
|
||||
`aarch64-pc-windows-gnullvm.exe`; each URL carries a
|
||||
`#/rdbms-playground.exe` rename fragment so the `bin` shim resolves
|
||||
regardless of version. Carries `checkver` (lets `scoop status` / the
|
||||
community excavator see lag) but **no `autoupdate`** — our pipeline is the
|
||||
updater.
|
||||
- *Homebrew* (`Formula/rdbms-playground.rb`): `on_macos`/`on_linux` ×
|
||||
`on_arm`/`on_intel` selecting the four bare-binary assets (macOS direct;
|
||||
Linux = the static `-musl` build). **Windows absent** — Homebrew has no
|
||||
Windows port. `install` drops the single staged binary under a stable
|
||||
name; the `test` block runs `--version`.
|
||||
|
||||
**Unverified (validate on first real use):** an actual `scoop install` and
|
||||
`brew install`/`brew test`; the `HEAD:main` default-branch assumption; and
|
||||
whether macOS Gatekeeper accepts the **ad-hoc-signed** mac binary via
|
||||
`brew` (execution should be fine — ad-hoc satisfies arm64's signing
|
||||
requirement and `brew`'s curl download sets no quarantine xattr, unlike a
|
||||
browser download — but this rides on the still-parked Developer-ID signing
|
||||
decision). **Remaining D3:** winget (komac on Linux CI, or a manual PR).
|
||||
|
||||
## Amendment 3 — 2026-06-19: validated end-to-end; a presigned-URL/HEAD gotcha + the Caddy fix it needs
|
||||
|
||||
**Scoop and Homebrew now install and run end-to-end** (`v0.2.0`,
|
||||
user-verified on real Windows + macOS): `scoop install rdbms-playground`
|
||||
and `brew install lazyeval/tap/rdbms-playground` both fetch, checksum,
|
||||
install, and the installed binary launches. Amendment 2's "unverified"
|
||||
caveats are resolved (the `HEAD:main` push populated both repos cleanly).
|
||||
|
||||
**Distribution depends on a server-side Caddy rule — RECORD THIS: it lives
|
||||
in the Gitea edge config, NOT this repo, and if it is lost in a server
|
||||
rebuild Homebrew silently 403s again.** Symptom: `brew install` failed with
|
||||
`curl (22) … 403` on the asset URL. Root cause: Gitea (≥1.25; here 1.26.2)
|
||||
serves release-asset downloads by **303-redirecting to a method-bound
|
||||
AWS-SigV4 presigned S3 URL** (OVH), signed for the *incoming* request's HTTP
|
||||
verb. Homebrew **resolves the URL with a HEAD, captures the returned
|
||||
(HEAD-signed) presigned URL, then runs the download GET against that
|
||||
captured URL** → GET-on-a-HEAD-signed-URL → 403. Reproduced precisely:
|
||||
`GET on HEAD-resolved = 403`, `HEAD on HEAD-resolved = 200`, `GET on
|
||||
GET-resolved = 200`, `HEAD on GET-resolved = 403`. GET-only tools
|
||||
(`install.sh`, `install.ps1`, `cargo binstall`, curl) are unaffected — they
|
||||
GET the original URL and let it redirect fresh to a GET-signed URL. This is
|
||||
a **Homebrew defect** (it reuses an ephemeral, method-scoped credential as
|
||||
if it were a durable resource locator); Gitea's per-request method-bound
|
||||
signing is correct, and SigV4 *cannot* sign one URL for two verbs.
|
||||
`SERVE_DIRECT=false` (Gitea proxies, no presign) would also fix it but was
|
||||
declined — not reshaping storage for one client.
|
||||
|
||||
**Fix (deployed, "Tier A"): a Caddy rule that answers HEAD on
|
||||
release-download paths directly** — 200, no redirect, no body — so brew's
|
||||
resolve records the *original* URL (no presigned credential captured) and
|
||||
its download GET runs through the redirect fresh → GET-signed → 200. Scoped
|
||||
to `method HEAD` + the release-download path; **GET is untouched**, so every
|
||||
working channel is unaffected, and a HEAD carries no payload so no client
|
||||
can *break* (at most a HEAD-probing download manager loses a progress-bar
|
||||
size — cosmetic). Reference Caddyfile (place before the Gitea
|
||||
reverse_proxy):
|
||||
|
||||
```
|
||||
@release_head {
|
||||
method HEAD
|
||||
path_regexp ^/[^/]+/[^/]+/releases/download/.+
|
||||
}
|
||||
handle @release_head {
|
||||
header Accept-Ranges bytes
|
||||
header Content-Type application/octet-stream
|
||||
respond 200
|
||||
}
|
||||
```
|
||||
|
||||
A "Tier B" variant (a sidecar that fetches and returns the *real*
|
||||
Content-Length so even HEAD-probing clients stay fully faithful) was specced
|
||||
but proved unnecessary — brew is happy with the bare 200.
|
||||
|
||||
**macOS signing:** the brew-installed binary **runs** under the current
|
||||
**ad-hoc** signature (`codesign --sign -`) — confirmed on Apple Silicon.
|
||||
**Developer-ID signing + notarization remains parked** pending the user's
|
||||
Apple account conversion to an Organization (GA plan / ADR-ci-003); it is
|
||||
needed for *browser-download* trust, not for the package-manager paths,
|
||||
which are all working now.
|
||||
|
||||
**Remaining D3:** winget only.
|
||||
|
||||
## Amendment 4 — 2026-06-20: winget (D3 §3d) — the last package manager
|
||||
|
||||
winget wired as the fourth `publish.yaml` sibling job, **completing the D3
|
||||
package-manager set** (crates.io/binstall, Scoop, Homebrew, winget; plus the
|
||||
install scripts + direct binaries).
|
||||
|
||||
**Model — fundamentally unlike Scoop/Homebrew.** winget has no
|
||||
self-hosted-source equivalent: its default catalog is the central,
|
||||
**human-gated** GitHub repo `microsoft/winget-pkgs`, and you get in by
|
||||
**opening a PR** of manifests into it. So the job submits a PR (via
|
||||
**komac**, pinned `2.16.0`, prebuilt glibc binary — the CI image has no
|
||||
cargo) and **Microsoft's pipeline validates async** (schema, SHA256,
|
||||
AV/SmartScreen scan, sandbox install) + moderator review. It is *not* live
|
||||
on dispatch like the bucket/tap.
|
||||
|
||||
**PackageIdentifier `LazyEvaluation.RdbmsPlayground`** (publisher segment =
|
||||
the publishing entity / `lazyeval` org / license holder — consistent with
|
||||
everything else). Bare-exe → winget **`portable`** installer type; x64
|
||||
(`-pc-windows-gnu`) + arm64 (`-pc-windows-gnullvm`), komac infers arch +
|
||||
type from the binaries.
|
||||
|
||||
**Auth — a dedicated GitHub bot, classic token.** komac needs a **classic
|
||||
`public_repo` PAT**; **fine-grained tokens cannot open the cross-fork PR**
|
||||
(komac #310 — the PR is created on the *target* repo you don't administer,
|
||||
which the fine-grained model can't express). A classic `public_repo` token
|
||||
can't be scoped to one repo, so it lives on a **dedicated GitHub bot
|
||||
account** (a leak can't reach other repos — the lazyeval-ci reasoning), as
|
||||
the `WINGET_GITHUB_TOKEN` secret, referenced **only** in the `winget` job
|
||||
(job-level scoping keeps it away from the crates.io / lazyeval tokens) and
|
||||
passed to the API guards via a 0600 curl config file (never argv).
|
||||
|
||||
**Idempotency — stronger than the others need.** A re-submitted version
|
||||
would open a *duplicate* PR, so before submitting the job guards on **both**
|
||||
(1) already-merged versions (`contents` API on
|
||||
`manifests/l/LazyEvaluation/RdbmsPlayground/<ver>`) and (2) an already-open
|
||||
PR for the id+version (`search/issues`). Either → clean skip, so a repeated
|
||||
`publish` dispatch is safe even mid-review.
|
||||
|
||||
**Signing:** none required to *submit* (only MSIX needs it; ours is
|
||||
portable). The unsigned binary may earn an AV/SmartScreen **manual-review
|
||||
label** on the first PR — usually clears; a nudge toward Trusted Signing
|
||||
(the UK Ltd clears the 3-year-history bar) but not a blocker. Continues the
|
||||
parked Developer-ID/notarization posture.
|
||||
|
||||
**One-time bootstrap (manual — NOT in CI, because `komac new` is
|
||||
interactive).** Run once to create the package in winget-pkgs, then CI
|
||||
`komac update` handles every release after:
|
||||
|
||||
```
|
||||
komac token update # paste the bot's classic public_repo PAT at the prompt (not in argv/history)
|
||||
komac new LazyEvaluation.RdbmsPlayground \
|
||||
-v 0.2.0 \
|
||||
-u https://git.lazyeval.net/oli/rdbms-playground/releases/download/v0.2.0/rdbms-playground-v0.2.0-x86_64-pc-windows-gnu.exe \
|
||||
https://git.lazyeval.net/oli/rdbms-playground/releases/download/v0.2.0/rdbms-playground-v0.2.0-aarch64-pc-windows-gnullvm.exe \
|
||||
--publisher "Lazy Evaluation Ltd" --package-name "RDBMS Playground" \
|
||||
--moniker rdbms-playground --license "MIT OR Apache-2.0" \
|
||||
--package-url https://relplay.org -s
|
||||
```
|
||||
komac downloads the two exes, detects the bare binary as **`portable`**, and
|
||||
**prompts for the command alias — enter `rdbms-playground`** so users get
|
||||
that on PATH (not the long versioned filename). `-s` opens the PR; fill any
|
||||
remaining prompts. (Flags verified against komac 2.16.0: `-v/--version`,
|
||||
`-u/--urls`, `-s/--submit`; the alias has no flag and is prompted.) Once
|
||||
that first PR merges, the CI job's `komac update` takes over.
|
||||
|
||||
**Bootstrap learnings (2026-06-21, first real run).** GitHub bot account =
|
||||
`lazyeval-ci`. Its fork **`lazyeval-ci/winget-pkgs` must exist *before*
|
||||
`komac new`** — komac's auto-fork races on winget-pkgs (one of GitHub's
|
||||
largest repos) and fails with *"Could not resolve to a Repository
|
||||
'lazyeval-ci/winget-pkgs'"*; fork it manually (web **Fork**, or `gh repo
|
||||
fork microsoft/winget-pkgs --clone=false`), wait for it to populate, then
|
||||
re-run. **Alias:** for a **direct single-exe portable**, winget derives the
|
||||
on-PATH command from **`Commands[0]`** (the interactive "Commands" prompt →
|
||||
`rdbms-playground`) — **not** `PortableCommandAlias`, which is
|
||||
**archive/nested-portable-only** and must NOT be added to our manifest
|
||||
(verified against winget-cli's portable-install logic + the 1.6.0 installer
|
||||
schema; komac correctly omits it). So komac's generated manifest needs no
|
||||
hand-editing. First PR: **#391335**.
|
||||
File diff suppressed because one or more lines are too long
@@ -133,3 +133,13 @@ declaration of the dev *and* build environment.
|
||||
flake for `requirements.md` **TT5** (CI runs the tiers) and the
|
||||
**D1/D2/D3** distribution items (the release uses a static musl target
|
||||
built through this flake).
|
||||
|
||||
## Amendment 1 — 2026-06-17: `fmt` gate enabled (issue #35)
|
||||
|
||||
The deferred "revisit on `main`" is done. With the CI + website branches
|
||||
merged and before the first public release, the tree was reformatted once
|
||||
with **stock `cargo fmt`** (no `rustfmt.toml` — stable rustfmt supports no
|
||||
meaningful customisation, and the pinned 1.95.0 toolchain makes
|
||||
`fmt --check` deterministic) in a single mechanical commit (`41b7e9a`,
|
||||
102 files, behaviour-preserving; recorded in `.git-blame-ignore-revs`).
|
||||
`ci.yaml`'s gate is now **`fmt --check` + clippy + test**. Closes **#35**.
|
||||
|
||||
@@ -20,6 +20,17 @@ This ADR records the **cross-platform build strategy**; it sits on top of
|
||||
**ADR-ci-002** (the nix flake, which now carries the cross toolchain) and
|
||||
**ADR-ci-001** (the pipeline, whose release job this fills in).
|
||||
|
||||
## Amendment 2 — 2026-06-16: CI on `main`; `release-macos` dispatched + verified
|
||||
|
||||
The CI branch is **merged to `main`**, so `release-macos.yaml`
|
||||
(`workflow_dispatch`, Gitea-default-branch-only) is now triggerable. It
|
||||
has been **dispatched and verified end-to-end**: both `*-apple-darwin`
|
||||
targets build on the Tart runner, the de-nix/re-sign step runs, the
|
||||
assets upload to the tagged release, and the binaries launch. macOS is
|
||||
therefore runtime-verified (alongside the original Linux x86_64 +
|
||||
Windows aarch64); only **Linux aarch64** and **Windows x86_64** remain
|
||||
link-clean / valid-format without a runtime smoke-test.
|
||||
|
||||
## Amendment — 2026-06-14: macOS implemented (closes D1)
|
||||
|
||||
macOS is no longer deferred. The two `*-apple-darwin` targets now build on a
|
||||
|
||||
@@ -19,5 +19,5 @@ here too).
|
||||
## Index
|
||||
|
||||
- [ADR-ci-001 — CI + release pipeline on Gitea Actions](20260612-adr-ci-001.md) — **Accepted 2026-06-12** (implemented the same day on the `ci` branch). Establishes the CI/release pipeline on the self-hosted Gitea instance's Actions runner (`ci-public`). **Runner model** (established by a throwaway probe): jobs execute *inside* a container (`catthehacker/ubuntu:act-22.04` by default), as root, so the runner host's nix is **not** reachable from steps. **Toolchain delivery:** a **baked CI image** — `node:22-bookworm-slim` (satisfies the act_runner job-container contract: `/bin/sleep` keep-alive, `bash`, `node` for JS actions; a bare `nixos/nix` image lacks these and won't start) **+ single-user nix + the flake's devShell pre-warmed** — built by `build-ci-image.yaml` via DinD and pushed to the Gitea container registry as a **public** package, so CI runs `nix develop -c …` against the **same pinned toolchain as dev** (the flake, ADR-ci-002) with a warm store (~1.4 s to a ready toolchain). **Gate** (`ci.yaml`): `clippy -D warnings` + `cargo test` inside that image on branch pushes + PRs; **fmt deliberately not gated** (the tree isn't stock-rustfmt-clean — user decision, revisit on `main`; see ADR-ci-002). **Release** (`release.yaml`): on a `v*` tag, runs the tests, builds the **static `x86_64-unknown-linux-musl` binary** (D2: single static binary, no runtime deps — the glibc/nix build is non-portable), strips it, and publishes it + a `.sha256` to a Gitea release via the API and the auto-provided `GITEA_TOKEN`. **Triggers:** gate + image-build are scoped to **branch** pushes (`branches: ['**']`) so a release tag doesn't spuriously re-run them; the image-build additionally path-filters to its inputs (Dockerfile/flake/toolchain); the release owns tags. **Auth:** a dedicated PAT (`REGISTRY_USERNAME`/`REGISTRY_TOKEN` secrets) pushes the image; the auto `GITEA_TOKEN` publishes releases. **Scope:** the original release job was Linux x86_64 only; it's now the **four non-macOS D1 targets** (Linux + Windows × x86_64/aarch64) cross-built via cargo-zigbuild — see **ADR-ci-003**. macOS, D3 package-manager manifests, CI-speed dependency caching, and the website's static→Cloudflare deploy remain deferred, added step by step. Verified live: probe → runner facts; image built + checked locally; gate green (**2424 tests**); release exercised end-to-end (`v0.0.0-citest2` published with binary + checksum). Builds on **ADR-ci-002** (the nix flake, relocated here from main's ADR-0049 to avoid exactly this cross-branch collision).
|
||||
- [ADR-ci-002 — Nix flake for a reproducible dev + build environment](20260612-adr-ci-002.md) — **Accepted 2026-06-12** (relocated from main's **ADR-0049** on the same day — content unchanged — to keep CI/dev-env decisions out of `main`'s integer sequence). The single, version-pinned declaration of the **dev *and* build toolchain** so CI never relies on whatever Rust is on the build machine — mirroring **datamage ADR 0046**, but far simpler (pure-Rust TUI). Root **Nix flake** with two outputs: **`devShells.default`** (pinned **Rust 1.95.0** via `rust-toolchain.toml` + `rust-overlay`, `cargo-sweep`, and the musl cc for the static release build) and **`packages.default`** (`rustPlatform.buildRustPackage` from the committed `Cargo.lock`; `doCheck = false`). Exact-pin (not floating `stable`) so `nix flake update` can't surprise-bump clippy past the `-D warnings` gate. System inputs near-empty by design (`libsqlite3-sys bundled` → stdenv cc only; `arboard`→`x11rb` pure-Rust). `.envrc` (`use flake`) for direnv parity. Verified through the flake: `nix build` yields a working binary, clippy clean, **2424 tests pass / 0 fail / 1 intentional ignored doctest**. Consumed by **ADR-ci-001** (the pipeline). Alternatives rejected: dev-shell-only; a standard `rust:1.95` CI image (a second toolchain definition = drift); `rustup` on the build host (non-reproducible).
|
||||
- [ADR-ci-003 — Cross-platform release builds (the D1 matrix)](20260613-adr-ci-003.md) — **Accepted 2026-06-13** (implemented + a real matrix release verified the same day — tag `v.0.0.0-citest3` published 8 assets). Cross-compiles the **four non-macOS D1 targets** from the Linux x86_64 runner with **`cargo-zigbuild`** (Zig's bundled clang + libc as one universal cross cc/linker, incl. rusqlite's bundled SQLite C; added to the flake devShell, replacing the single-target musl cc): **`x86_64`/`aarch64-unknown-linux-musl`** (musl + crt-static → fully static, **D2**) and **`x86_64-pc-windows-gnu`** / **`aarch64-pc-windows-gnullvm`** (Zig statically links libc → standalone `.exe`). **Windows `synchronization` stub:** Rust std links `-lsynchronization` (WaitOnAddress thread-parking), an import lib rust-overlay's toolchain doesn't ship and Zig's mingw lacks; the symbols are forwarded by `kernel32`, so an **empty 8-byte stub** `libsynchronization.a` (`ci/winstub/`, wired via `.cargo/config.toml` for the Windows targets only) satisfies the linker. **Workflow:** `release.yaml` = **`test` once (host) → `build` matrix** over the four targets (`needs: test`, `fail-fast: false`); each job packages binary (`.exe` for Windows) + `.sha256` and uploads to the **shared release** via idempotent create-or-get. **macOS** (2026-06-14 amendment) — built natively on a **Tart (Apple-Silicon) runner** (`runs-on: macos`), which makes the SDK fully licensed and dissolves the grey-area/public-image problem; `release-macos.yaml` is **dispatch-only** (intermittent runner; becomes triggerable once CI is on `main`), de-nixes the binary's libiconv load path (`install_name_tool` → `/usr/lib`) + re-signs ad-hoc, and uploads to the tagged release. **D1 complete (all six targets).** Alternatives rejected: `cross` (no macOS, needs DinD), per-target nix cross (Windows-aarch64 unpackaged, macOS-from-Linux unsupported), a real `libsynchronization.a` (more machinery, doesn't cover Windows-aarch64). Runtime-verified by the user (2026-06-13): Linux x86_64 + Windows aarch64 run correctly; Linux aarch64 + Windows x86_64 are the outstanding runtime checks. Builds on ADR-ci-002 (flake) and fills in ADR-ci-001 §3 (Release).
|
||||
- [ADR-ci-002 — Nix flake for a reproducible dev + build environment](20260612-adr-ci-002.md) — **Accepted 2026-06-12** (relocated from main's **ADR-0049** on the same day — content unchanged — to keep CI/dev-env decisions out of `main`'s integer sequence). The single, version-pinned declaration of the **dev *and* build toolchain** so CI never relies on whatever Rust is on the build machine — mirroring **datamage ADR 0046**, but far simpler (pure-Rust TUI). Root **Nix flake** with two outputs: **`devShells.default`** (pinned **Rust 1.95.0** via `rust-toolchain.toml` + `rust-overlay`, `cargo-sweep`, and the musl cc for the static release build) and **`packages.default`** (`rustPlatform.buildRustPackage` from the committed `Cargo.lock`; `doCheck = false`). Exact-pin (not floating `stable`) so `nix flake update` can't surprise-bump clippy past the `-D warnings` gate. System inputs near-empty by design (`libsqlite3-sys bundled` → stdenv cc only; `arboard`→`x11rb` pure-Rust). `.envrc` (`use flake`) for direnv parity. Verified through the flake: `nix build` yields a working binary, clippy clean, **2424 tests pass / 0 fail / 1 intentional ignored doctest**. Consumed by **ADR-ci-001** (the pipeline). Alternatives rejected: dev-shell-only; a standard `rust:1.95` CI image (a second toolchain definition = drift); `rustup` on the build host (non-reproducible). **Amendment 1 (2026-06-17, issue #35):** the deferred `fmt` gate is enabled — the tree was reformatted once with **stock `cargo fmt`** (no `rustfmt.toml`; pinned toolchain makes `fmt --check` deterministic) in a single mechanical commit (`41b7e9a`, 102 files, behaviour-preserving, in `.git-blame-ignore-revs`), and `ci.yaml`'s gate is now **`fmt --check` + clippy + test**.
|
||||
- [ADR-ci-003 — Cross-platform release builds (the D1 matrix)](20260613-adr-ci-003.md) — **Accepted 2026-06-13** (implemented + a real matrix release verified the same day — tag `v.0.0.0-citest3` published 8 assets). Cross-compiles the **four non-macOS D1 targets** from the Linux x86_64 runner with **`cargo-zigbuild`** (Zig's bundled clang + libc as one universal cross cc/linker, incl. rusqlite's bundled SQLite C; added to the flake devShell, replacing the single-target musl cc): **`x86_64`/`aarch64-unknown-linux-musl`** (musl + crt-static → fully static, **D2**) and **`x86_64-pc-windows-gnu`** / **`aarch64-pc-windows-gnullvm`** (Zig statically links libc → standalone `.exe`). **Windows `synchronization` stub:** Rust std links `-lsynchronization` (WaitOnAddress thread-parking), an import lib rust-overlay's toolchain doesn't ship and Zig's mingw lacks; the symbols are forwarded by `kernel32`, so an **empty 8-byte stub** `libsynchronization.a` (`ci/winstub/`, wired via `.cargo/config.toml` for the Windows targets only) satisfies the linker. **Workflow:** `release.yaml` = **`test` once (host) → `build` matrix** over the four targets (`needs: test`, `fail-fast: false`); each job packages binary (`.exe` for Windows) + `.sha256` and uploads to the **shared release** via idempotent create-or-get. **macOS** (2026-06-14 amendment) — built natively on a **Tart (Apple-Silicon) runner** (`runs-on: macos`), which makes the SDK fully licensed and dissolves the grey-area/public-image problem; `release-macos.yaml` is **dispatch-only** (intermittent runner), de-nixes the binary's libiconv load path (`install_name_tool` → `/usr/lib`) + re-signs ad-hoc, and uploads to the tagged release. **D1 complete (all six targets).** Alternatives rejected: `cross` (no macOS, needs DinD), per-target nix cross (Windows-aarch64 unpackaged, macOS-from-Linux unsupported), a real `libsynchronization.a` (more machinery, doesn't cover Windows-aarch64). **Amendment 2 (2026-06-16):** CI is **merged to `main`**, so `release-macos` is now triggerable (`workflow_dispatch` is default-branch-only) and has been **dispatched + verified end-to-end** (build → de-nix/re-sign → upload, binaries launch). Runtime-verified by the user: Linux x86_64, Windows aarch64, **and both macOS targets**; Linux aarch64 + Windows x86_64 are the outstanding runtime checks. Builds on ADR-ci-002 (flake) and fills in ADR-ci-001 §3 (Release).
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# Session handoff — 2026-06-16 (73)
|
||||
|
||||
Short, focused handover. Continues from handoff-72 (which completed the
|
||||
H2 hint-corpus verification pass). This session shipped one small
|
||||
feature — a **`Ctrl-G` demo-mode alias for F1** — plus follow-on doc
|
||||
hygiene. Commit `4016c3e`.
|
||||
|
||||
## §1. State
|
||||
|
||||
**Branch:** `main`, clean, all committed (local; **push pending** — your
|
||||
step; the backlog now spans the CI merge, H2, the hint-corpus fixes,
|
||||
handoffs 71/72/73, and this Ctrl-G commit). **2503 pass / 0 fail / 1
|
||||
ignored** (the long-standing `friendly` doctest), **clippy clean**
|
||||
(nursery, all targets). Open Gitea issues unchanged: **#35–#38**.
|
||||
|
||||
## §2. Why Ctrl-G (the problem)
|
||||
|
||||
Casts are recorded with **`autocast`** (ADR-0047 demo mode: `--demo` /
|
||||
`RDBMS_PLAYGROUND_DEMO`). The contextual hint overlay (ADR-0053 / H2)
|
||||
opens on **F1** — but F1 reaches the app only as a terminal **escape
|
||||
sequence** (`\eOP` / `\e[11~`), and **autocast cannot emit escape
|
||||
sequences**. So the single most teaching-relevant overlay was
|
||||
unreachable in recordings (and in presenter/teacher sessions, which also
|
||||
run `--demo`). Same wall that pushed step-captions onto `Ctrl+]` (a
|
||||
single control byte) rather than `Ctrl+!`.
|
||||
|
||||
### Chord choice — why Ctrl-G, why not Ctrl-1
|
||||
|
||||
The user's first instinct was `Ctrl-1` (mnemonic, near F1). **Not
|
||||
possible:** in a legacy terminal `Ctrl`+digit has no control byte —
|
||||
`Ctrl-1` arrives as a bare `1` (would type "1" into the buffer). The
|
||||
kitty keyboard protocol *would* encode it, but only as an escape
|
||||
sequence (the very thing autocast can't send), and this app deliberately
|
||||
does **not** push `KeyboardEnhancementFlags` (`runtime.rs` does only
|
||||
`enable_raw_mode` + `EnterAlternateScreen`). So the usable space is
|
||||
exactly **`Ctrl`+letter** (single legacy control bytes). After excluding
|
||||
taken chords (`Ctrl-C` quit, `Ctrl-O` nav, `Ctrl+]` caption,
|
||||
`Ctrl-A/E/W/K/U` readline per ADR-0049), byte-collisions
|
||||
(`Ctrl-H/I/J/M/[`), flow-control (`Ctrl-S/Q`), and likely-future
|
||||
(`Ctrl-R/P/N/Y/L/V`), **`Ctrl-G`** is the clean survivor (BEL/"abort" in
|
||||
line editors — nothing destructive to shadow).
|
||||
|
||||
## §3. What shipped (commit `4016c3e`)
|
||||
|
||||
ADR-0047 **Amendment 1**. **In demo mode only**, `Ctrl-G` aliases F1:
|
||||
|
||||
- Runs the *exact* F1 hint logic (`hint_key` guard in
|
||||
`App::handle_key`, `src/app.rs` ~line 1226 — `key.code == F(1) ||
|
||||
(self.demo_mode && Ctrl-G)`), so live-input → form hint, empty input →
|
||||
recent-error / getting-started.
|
||||
- **Badges as `[F1]`** (not `[CTRL-G]`): `demo_badge_label` maps
|
||||
`Ctrl-G → Some("[F1]")` (consulted only in demo mode — the caller
|
||||
gates). So a recorded cast is **visually identical to a real F1
|
||||
press**.
|
||||
- **Demo-gated:** the shipped keymap stays F1-only. Outside demo mode
|
||||
`Ctrl-G` falls through to the inert catch-all (the `Char(c)` insert arm
|
||||
excludes CONTROL, so no `g` is typed).
|
||||
- The keybinding strip (ADR-0051) is **not** changed — F1 stays the
|
||||
advertised key; `Ctrl-G` is a recorder aid and the badge already reads
|
||||
`[F1]`.
|
||||
|
||||
**Tests (test-first, `src/app.rs` Tier-1):** `ctrl_g_in_demo_mode_-
|
||||
aliases_f1_on_input`, `…_on_empty_input`, `ctrl_g_outside_demo_mode_-
|
||||
is_inert`, plus a `Ctrl-G → [F1]` assertion added to
|
||||
`demo_badge_label_maps_the_invisible_keys`. All confirmed red→green; the
|
||||
"inert" test passed on the pre-change code, proving the demo-gate.
|
||||
|
||||
### Using it in a cast
|
||||
With `--demo` active, send **`Ctrl-G`** in the autocast script wherever
|
||||
you want the hint overlay to appear; the viewer sees the `[F1]` badge.
|
||||
|
||||
## §4. Doc hygiene done alongside (same commit)
|
||||
|
||||
- **ADR-0047 file:** removed two stray `</content>` / `</invoke>` lines
|
||||
(tool-call residue accidentally committed when the ADR was authored).
|
||||
- **CLAUDE.md "Things deliberately deferred":** dropped three **stale**
|
||||
entries — **I1b** readline shortcuts (done, ADR-0049), **I3** tab
|
||||
completion, and **I4** syntax highlighting (both done; requirements.md
|
||||
even carries a 2026-06-07 reconciliation note that I3/I4 were
|
||||
"shipped but marked not yet"). Only **I1** (multi-line input) remains —
|
||||
it is genuinely still open (`[ ]` in requirements.md). *(This follows
|
||||
the handoff-72 cleanup that removed the equally-stale m:n/C4 +
|
||||
project-storage entries.)*
|
||||
|
||||
## §5. Open / next (unchanged from handoff-72 §5)
|
||||
|
||||
The hint corpus is trustworthy and the cast-F1 gap is closed. Roadmap:
|
||||
|
||||
1. **Push** (your step).
|
||||
2. **#35 (cargo fmt gate)** — precondition (CI merged) is met; the user
|
||||
wants it done once, before first publication. Needs a `rustfmt.toml`-
|
||||
vs-defaults decision first; tree is ~1800 hunks dirty.
|
||||
3. Other open `requirements.md` items: **I1** multi-line input, **I5/B3**
|
||||
in-flight cancellation, **TT4** PTY tier-4 (unwired), **DOC1**/**E2**
|
||||
user docs (partial), **TT5** Windows-execution + Tier-4-in-CI, **D3**
|
||||
packaging manifests. Design-first (`[~]`): **V4** session journal,
|
||||
**TU1** tutorial, **C3a**, **V3**.
|
||||
4. Hint follow-ups if wanted: **#36** `help` advanced-SQL, **#37** hint
|
||||
clause-concepts, **#38** hint diagnostic route.
|
||||
|
||||
## §6. How to take over
|
||||
|
||||
1. Read handoffs 71 → 72 → 73, `CLAUDE.md`, `docs/requirements.md`.
|
||||
2. Confirm green: `cargo test` (**2503 / 1 ignored**) + `cargo clippy
|
||||
--all-targets` (clean).
|
||||
3. For demo-mode / casting, read **ADR-0047** (+ its Amendment 1); for
|
||||
the hint overlay it aliases, **ADR-0053**.
|
||||
4. Workflow unchanged: phased, test-first, `/runda` + DA before commits,
|
||||
ADR amendment + README index-upkeep for decided-area changes, confirm
|
||||
commit messages with the user.
|
||||
5. Consider a `cargo sweep` at this milestone (`target/` grows; see
|
||||
CLAUDE.md "Build hygiene").
|
||||
@@ -0,0 +1,137 @@
|
||||
# Session handoff — 2026-06-18 (74)
|
||||
|
||||
Large session. Continues from handoff-73 (Ctrl-G demo alias). This one ran
|
||||
the **road to public availability** end to end: a post-merge doc
|
||||
reconciliation, then versioning, installers, crates.io/binstall, the
|
||||
`cargo fmt` gate, and an actual **`v0.2.0` release that is now live on
|
||||
crates.io**. Three new main-sequence ADRs (0054/0055/0056), one CI-ADR
|
||||
amendment, issue **#35 closed**.
|
||||
|
||||
## §1. State
|
||||
|
||||
**Branch `main`.** **2509 pass / 0 fail / 1 ignored** (the long-standing
|
||||
`friendly` doctest); **clippy clean**; **`cargo fmt --check` clean** (the
|
||||
tree is now stock-rustfmt formatted, and CI gates it). `rdbms-playground
|
||||
--version` → `rdbms-playground 0.2.0`.
|
||||
|
||||
**Released:** **`v0.2.0`** — Gitea release with all six D1 targets
|
||||
(Linux/Windows via `release.yaml` on the tag; macOS via the dispatched
|
||||
`release-macos.yaml`, **ad-hoc-signed**). **Published to crates.io**
|
||||
(`cargo install rdbms-playground` and `cargo binstall rdbms-playground`
|
||||
both user-verified).
|
||||
|
||||
**Push state:** earlier commits were pushed (they triggered CI/releases).
|
||||
The **most recent commits are unpushed** and matter:
|
||||
- `3c87dbb` macOS nix-prune fix (takes effect next macOS dispatch).
|
||||
- `d3af1c4` + `8ebe213` the manual `publish.yaml` workflow — ADR-0056 in
|
||||
the first, the **workflow file itself in the second** (`git commit -am`
|
||||
had skipped the new untracked file).
|
||||
- this handoff.
|
||||
Push `main` to land them. (Push is the user's step.)
|
||||
|
||||
## §2. What shipped (commits `628b250`→`d3af1c4`)
|
||||
|
||||
- **`628b250` doc reconciliation** after the CI + website branch merges:
|
||||
rewrote CLAUDE.md's stale repo-layout tree; added a Website subproject
|
||||
note; fixed the CI note; **gitignored `.wrangler/` + `.vscode/`** and
|
||||
removed a tracked `website/.vscode/`; updated requirements (D1 macOS
|
||||
runtime-verified, DOC1 canonical-docs-on-website) + ADR-ci-003.
|
||||
- **`c30a611` ADR-0054 — version surfaces.** `--version`/`-V` + an in-app
|
||||
**`version`** command, both reading `CARGO_PKG_VERSION` via one
|
||||
`cli::version_text()`. A **release-CI guard** fails the release unless
|
||||
the `v*` tag equals `v<Cargo.toml version>`.
|
||||
- **`ef99e6c` ADR-0055 — `scripts/install.sh`** (curl|sh, POSIX,
|
||||
shellcheck-clean, checksum-verified, `~/.local/bin`). Verified
|
||||
end-to-end against the live release. **`install.ps1`** (Windows
|
||||
`irm|iex`) added later (`e9606b5`) — **written but untested here** (no
|
||||
PowerShell on this box; validate on Windows).
|
||||
- **`e9606b5` ADR-0056 — crates.io + binstall prep.** Publish-ready
|
||||
Cargo.toml (dropped `publish=false`; homepage/keywords/categories/
|
||||
`exclude`); `README.md`; `LICENSE-MIT`/`LICENSE-APACHE` (dual, © Lazy
|
||||
Evaluation Ltd) + `CONTRIBUTING.md` (inbound=outbound); the
|
||||
`[package.metadata.binstall]` block with per-target overrides
|
||||
(linux-gnu→musl, windows-msvc→gnu/gnullvm; macOS direct).
|
||||
- **`41b7e9a` + `ec3c7c3` — #35 fmt gate.** One mechanical `cargo fmt`
|
||||
(stock defaults, 102 files, behaviour-preserving) recorded in
|
||||
`.git-blame-ignore-revs`; `ci.yaml` now gates `fmt --check` (ADR-ci-002
|
||||
Amendment 1). **Closes #35.**
|
||||
- **`88830ed`+`bd5be5e` — v0.2.0 bump + the guard bug.** The first
|
||||
`release.yaml` run **failed** at the version guard: it piped `nix
|
||||
develop -c cargo metadata` to node, but the **flake devShell prints a
|
||||
banner to stdout**, corrupting the JSON. Fixed to a toolchain-free
|
||||
`grep -m1 '^version = ' Cargo.toml`. The `v0.2.0` tag was re-pointed
|
||||
(Option A) to the fix commit; re-run went green.
|
||||
- **`3c87dbb` — macOS nix-prune fix.** The prune step's profile dir
|
||||
(`~/.cache/rdbms-ci`) didn't exist, so `nix develop --profile` errored
|
||||
(swallowed by `|| true`) → the gc-root was never created → the whole
|
||||
toolchain (~3.8 GiB) was deleted **and re-downloaded every run**. Added
|
||||
`mkdir -p` + dropped the `|| true`. Diagnosed from the run-74 log via
|
||||
`tea actions runs logs 74`.
|
||||
- **`d3af1c4` — manual `publish.yaml`.** `workflow_dispatch` + `tag`
|
||||
input (mirrors `release-macos.yaml`). Idempotent `crates-io` job
|
||||
(crates.io API pre-check + `cargo publish` backstop), independent jobs
|
||||
so Scoop/Homebrew/winget slot in later. ADR-0056 Amendment 1.
|
||||
|
||||
## §3. Live vs manual vs parked
|
||||
|
||||
- **Automated on a `v*` tag:** `release.yaml` builds + publishes the four
|
||||
Linux/Windows targets (+ fmt/clippy/test gate).
|
||||
- **Manual `workflow_dispatch`:** `release-macos.yaml` (mac binaries —
|
||||
intermittent runner) and `publish.yaml` (crates.io now; more registries
|
||||
later). Run them once the tag's build is up.
|
||||
- **Parked (user decisions):**
|
||||
- **macOS Developer-ID signing.** The pipeline **ad-hoc-signs**
|
||||
(`codesign --sign -`). The user's `Apple Development` cert is the
|
||||
**wrong type** — distribution needs **`Developer ID Application`** +
|
||||
**notarization** (App Store Connect API key recommended). Fine for
|
||||
`curl|sh` (no quarantine); matters for browser downloads. Details in
|
||||
`docs/plans/20260616-public-availability.md`.
|
||||
- **Remaining D3:** Scoop (`lazyeval` bucket), Homebrew (`lazyeval`
|
||||
tap), winget (komac on Linux CI, or manual PR) — each a sibling job
|
||||
in `publish.yaml` + a manifest repo.
|
||||
|
||||
## §4. Immediate next steps
|
||||
|
||||
1. **Push `main`** (lands `3c87dbb` + `d3af1c4`).
|
||||
2. **Add the `CARGO_REGISTRY_TOKEN` secret** (crate-scoped,
|
||||
`publish-update`) so `publish.yaml` works: `tea actions secrets create
|
||||
CARGO_REGISTRY_TOKEN` (paste at prompt) or the Gitea UI.
|
||||
3. **Smoke-test `publish.yaml`:** dispatch it for `v0.2.0` — it should
|
||||
**idempotently skip** ("already on crates.io"), exercising the path
|
||||
risk-free.
|
||||
4. The release ritual going forward (ADR-0054): bump `Cargo.toml` →
|
||||
commit → tag `v<x.y.z>` → push tag (Linux/Windows release builds) →
|
||||
dispatch `release-macos` → dispatch `publish`.
|
||||
|
||||
## §5. Gotchas learned (don't relearn the hard way)
|
||||
|
||||
- **The flake devShell prints a banner to stdout** — never pipe `nix
|
||||
develop -c <cmd>` into a parser. Read Cargo.toml directly, etc.
|
||||
- **Workflow-file source differs by trigger:** a **tag**-triggered run
|
||||
(`release.yaml`) uses the workflow **at the tagged commit**; a
|
||||
**`workflow_dispatch`** run (`release-macos`/`publish`) uses the
|
||||
**default branch** (`main`). So fixing a dispatched workflow only needs
|
||||
a `main` push; fixing a tag-triggered one needs the tag re-pointed.
|
||||
- **Version vs tag:** `Cargo.toml` is bare `0.2.0`; the git tag is
|
||||
`v0.2.0`; the guard checks `tag == "v" + version`; binstall `pkg-url`
|
||||
spells `v{ version }`.
|
||||
- **CI logs are reachable** via `tea actions runs logs <id>` (and `tea
|
||||
actions runs list --output tsv`). Use it instead of guessing from a
|
||||
step name.
|
||||
- **crates.io API needs a descriptive User-Agent** (403 without one).
|
||||
|
||||
## §6. How to take over
|
||||
|
||||
1. Read handoffs 72 → 73 → 74, `CLAUDE.md`, `docs/requirements.md`, and
|
||||
**`docs/plans/20260616-public-availability.md`** (the GA roadmap with
|
||||
all decisions + parked items).
|
||||
2. Confirm green: `cargo test` (**2509 / 1 ignored**), `cargo clippy
|
||||
--all-targets`, `cargo fmt --check`.
|
||||
3. ADRs for this arc: **0054** (versioning), **0055** (installer),
|
||||
**0056** (crates.io/binstall + the publish workflow); CI side
|
||||
**ADR-ci-002 Amendment 1** (fmt gate), **ADR-ci-003** (release matrix
|
||||
+ macOS).
|
||||
4. Workflow unchanged: phased, test-first, `/runda` + DA before commits,
|
||||
ADR amendment + README index-upkeep for decided-area changes, confirm
|
||||
commit messages, never push.
|
||||
5. Consider a `cargo sweep` at this milestone (`target/` grows).
|
||||
@@ -0,0 +1,145 @@
|
||||
# Session handoff — 2026-06-21 (75)
|
||||
|
||||
Continues from handoff-74 (v0.2.0 live on crates.io). This session **finished
|
||||
the D3 package-manager rollout** — Scoop, Homebrew, and winget — plus the
|
||||
Windows `install.ps1` fixes, and included a deep **presigned-URL / Homebrew**
|
||||
debugging saga that's fixed by a server-side Caddy rule. Five commits; one
|
||||
new ADR amendment arc (ADR-0056 Amendments 2–4).
|
||||
|
||||
## §1. State
|
||||
|
||||
**Branch `main`.** **No crate (Rust) code changed this session** — only
|
||||
`.gitea/workflows/`, `scripts/`, and `docs/`. So the `cargo test` baseline
|
||||
from handoff-74 (**2509 pass / 0 fail / 1 ignored**, clippy + `fmt --check`
|
||||
clean) stands unchanged by construction; not re-run (nothing in the build
|
||||
graph moved). New coverage this session is **`scripts/test-package-renders.sh`**
|
||||
(shellcheck-clean, green), now gated on every push by a new **`ci.yaml`
|
||||
`manifests` job** (bash + node; ruby-absent in CI degrades gracefully).
|
||||
|
||||
**Commits this session** (`cabc813`→`0208c67`; the workflow-bearing ones were
|
||||
pushed, since the live `publish` dispatches + raw-URL installs the user tested
|
||||
needed them on `main`):
|
||||
- `42b40bc` install.ps1 → Windows PowerShell 5.1 compat
|
||||
- `c0531aa` install.ps1 → immediate-use PATH + honest messaging
|
||||
- `6d54c1e` Scoop + Homebrew jobs (D3 §3b/§3c)
|
||||
- `6bb2288` Scoop/Homebrew validated + Caddy fix recorded (Amendment 3)
|
||||
- `0208c67` winget job via komac (D3 §3d, Amendment 4)
|
||||
|
||||
**External / infra state created this session (NOT all in the repo):**
|
||||
- **Caddy Tier-A HEAD rule on the Gitea server** — *load-bearing for Homebrew*,
|
||||
lives in the Gitea edge config, **not** this repo (see §4). If lost on a
|
||||
server rebuild, brew 403s again.
|
||||
- **Gitea:** `lazyeval` org with repos **`scoop-bucket`** + **`homebrew-tap`**;
|
||||
**`lazyeval-ci`** Gitea bot user (org team, Write to those repos);
|
||||
**`LAZYEVAL_PKG_TOKEN`** secret on `oli/rdbms-playground`.
|
||||
- **GitHub:** **`lazyeval-ci`** bot account; classic `public_repo` PAT →
|
||||
**`WINGET_GITHUB_TOKEN`** secret on `oli/rdbms-playground`; fork
|
||||
**`lazyeval-ci/winget-pkgs`**.
|
||||
- **winget bootstrap PR [#391335](https://github.com/microsoft/winget-pkgs/pull/391335)**
|
||||
submitted, in Microsoft review.
|
||||
- **komac 2.16.0** installed locally at `~/.local/bin/komac`.
|
||||
|
||||
## §2. D3 — the package-manager set (the session's throughline)
|
||||
|
||||
| Channel | State |
|
||||
| --- | --- |
|
||||
| crates.io / `cargo binstall` | **live** (handoff-74) |
|
||||
| `install.sh` / `install.ps1` | **live** — PS1 validated on ARM Windows 11 (5.1 + 7.6) |
|
||||
| Scoop (`lazyeval/scoop-bucket`) | **live + validated** (install + run) |
|
||||
| Homebrew (`lazyeval/homebrew-tap`) | **live + validated** — needs the Caddy rule (§4) |
|
||||
| winget (`LazyEvaluation.RdbmsPlayground`) | **wired**; PR #391335 awaiting Microsoft merge |
|
||||
|
||||
How Scoop/Homebrew/winget are wired: sibling jobs in
|
||||
`.gitea/workflows/publish.yaml` (manual `workflow_dispatch`, `tag` input),
|
||||
each idempotent + independent. Scoop/Homebrew render **dependency-free bash**
|
||||
manifests (`scripts/render-{scoop-manifest,homebrew-formula}.sh`) from the
|
||||
release `.sha256` sidecars and push to the org repos via `LAZYEVAL_PKG_TOKEN`.
|
||||
winget runs `komac update --submit` to PR `microsoft/winget-pkgs`, guarded
|
||||
against duplicate PRs. ADR-0056 **Amendments 2–4** carry the full design.
|
||||
|
||||
## §3. The Homebrew 403 saga + Caddy fix (don't lose this)
|
||||
|
||||
Homebrew `brew install` 403'd while every other tool worked. Root cause,
|
||||
reproduced: Gitea (≥1.25; here 1.26.2) **303-redirects release downloads to a
|
||||
method-bound AWS-SigV4 presigned S3 URL** (OVH), signed for the *incoming*
|
||||
request's HTTP verb. Homebrew **resolves with a HEAD, captures the returned
|
||||
HEAD-signed URL, then runs the download GET against it** → GET-on-a-HEAD-signed
|
||||
URL → 403. (`GET on HEAD-resolved = 403`, `HEAD on HEAD-resolved = 200`,
|
||||
`GET on GET-resolved = 200`.) GET-only tools (install.sh/ps1, binstall, curl)
|
||||
are unaffected. This is a **Homebrew defect** (reusing an ephemeral
|
||||
method-scoped credential as a durable locator); Gitea's per-request signing is
|
||||
correct, and SigV4 *cannot* sign one URL for two verbs. `SERVE_DIRECT=false`
|
||||
would fix it but was **declined** (don't reshape storage for one client).
|
||||
|
||||
**Fix (deployed, "Tier A"):** a Caddy rule answering **HEAD on
|
||||
`…/releases/download/…` directly** (200, no redirect, no body) so brew's
|
||||
resolve records the *original* URL and its download GET redirects fresh →
|
||||
GET-signed → 200. GET untouched; a HEAD carries no payload so nothing can
|
||||
break (a HEAD-probing download manager at most loses a progress-bar size).
|
||||
Reference Caddyfile is in **ADR-0056 Amendment 3**.
|
||||
|
||||
## §4. Immediate next steps
|
||||
|
||||
1. **winget PR #391335:** watch validation — the one likely speed bump is an
|
||||
**AV/SmartScreen manual-review label** (unsigned binary); usually clears.
|
||||
Once a moderator merges, winget is live.
|
||||
2. **Post-merge check (Windows):** `winget install LazyEvaluation.RdbmsPlayground`,
|
||||
confirm the command is **`rdbms-playground`** (it is — a direct single-exe
|
||||
portable takes its PATH alias from `Commands[0]`, which komac set; see §5).
|
||||
3. After merge, future releases are hands-off: the CI `winget` job's
|
||||
`komac update` carries each version (alias inherited from the merged
|
||||
manifest). The release ritual is unchanged from ADR-0054, with the
|
||||
`publish` dispatch now also doing Scoop/Homebrew/winget.
|
||||
|
||||
## §5. Gotchas learned (don't relearn the hard way)
|
||||
|
||||
- **The Caddy HEAD rule is server-side, not in this repo.** Homebrew (and any
|
||||
HEAD-then-GET client) depends on it. Record/back it up.
|
||||
- **GitHub tokens:** komac needs a **classic `public_repo`** PAT — **fine-grained
|
||||
tokens can't open the cross-fork PR** (komac #310: the PR is created on the
|
||||
*target* repo you don't administer, which the fine-grained model can't
|
||||
express). A classic token can't be repo-scoped, so it lives on a **dedicated
|
||||
bot** (`lazyeval-ci`) to bound the blast radius.
|
||||
- **komac fork must pre-exist:** `komac new` auto-fork *races* on winget-pkgs
|
||||
(huge repo) → *"Could not resolve to a Repository 'lazyeval-ci/winget-pkgs'"*.
|
||||
Fork manually first (`gh repo fork microsoft/winget-pkgs --clone=false`),
|
||||
wait, then re-run. Pre-fill `komac new` flags to skip the long interactive
|
||||
prompt parade.
|
||||
- **Direct portable alias = `Commands[0]`, NOT `PortableCommandAlias`.** The
|
||||
latter is **archive/nested-portable-only**; adding it to a bare-exe portable
|
||||
is wrong (komac correctly omits it). The "Commands" prompt → `rdbms-playground`
|
||||
is the on-PATH command. Verified vs winget-cli logic + the 1.6.0 schema.
|
||||
- **install.ps1 / Windows PowerShell 5.1** (the in-box shell — PS7 is opt-in):
|
||||
arch via `PROCESSOR_ARCHITECTURE` env (not `RuntimeInformation::OSArchitecture`,
|
||||
which is absent under 5.1's .NET-Framework facade + StrictMode); force TLS 1.2;
|
||||
`-UseBasicParsing`. Also update `$env:Path` in-session (the persisted User PATH
|
||||
only reaches *new* processes; "restart your shell" was wrong — sign-out/in or
|
||||
the in-session update).
|
||||
- **Gitea release downloads go through OVH S3 presigned URLs** — method-bound,
|
||||
300 s expiry. Never assume HEAD-resolve-then-GET works against them.
|
||||
|
||||
## §6. Parked / deferred (user decisions)
|
||||
|
||||
- **macOS Developer-ID signing + notarization** — pending the Apple account →
|
||||
Organization conversion. Ad-hoc signing covers all package-manager paths
|
||||
(brew-installed binary *runs* on Apple Silicon); Developer-ID only matters for
|
||||
browser-download Gatekeeper trust.
|
||||
- **Windows code signing (Trusted Signing)** — not required to *ship* winget
|
||||
(portable; only MSIX needs signing). Unsigned can earn an AV/SmartScreen
|
||||
manual-review label + a user-run warning. **Lazy Evaluation Ltd (since 2012)
|
||||
clears Trusted Signing's 3-year-org bar** when the user wants to remove the
|
||||
warning (individual onboarding is currently paused; org path is open).
|
||||
- **Release notes / CHANGELOG** — raised this session, not done. If wanted:
|
||||
a `CHANGELOG.md` and/or populated Gitea release bodies, then the CI `winget`
|
||||
job can pass komac `--release-notes-url …/releases/tag/$TAG`.
|
||||
|
||||
## §7. How to take over
|
||||
|
||||
1. Read handoffs 73 → 74 → 75, `CLAUDE.md`, `docs/requirements.md` (D1/D3),
|
||||
and **ADR-0056 (esp. Amendments 1–4)** + the GA plan
|
||||
`docs/plans/20260616-public-availability.md`.
|
||||
2. Workflow unchanged: phased, test-first, `/runda` + DA before commits, ADR
|
||||
amendment + README index-upkeep for decided-area changes, confirm commit
|
||||
messages, never push.
|
||||
3. If `cargo test` is needed, the baseline is **2509 / 1 ignored** (handoff-74).
|
||||
4. Consider a `cargo sweep` at this milestone (`target/` grows).
|
||||
@@ -0,0 +1,193 @@
|
||||
# Plan — road to public availability (versioning + install + packaging)
|
||||
|
||||
Status: **planning** (2026-06-16). Captures the decisions taken in
|
||||
discussion plus the open questions, so the work can proceed in steps.
|
||||
The versioning piece is being implemented first and is recorded as a
|
||||
decision in **ADR-0054**; the install-script and package-manager pieces
|
||||
are roadmap items here until each is built.
|
||||
|
||||
Scope note: this repo lives on the self-hosted Gitea at
|
||||
`git.lazyeval.net` (`oli/rdbms-playground`); releases publish there
|
||||
(ADR-ci-001/003). Publication branding uses the company name **Lazy
|
||||
Evaluation Ltd → `lazyeval`**, not the personal `oli` username, for
|
||||
anything user-facing (taps, buckets).
|
||||
|
||||
---
|
||||
|
||||
## 1. Versioning + version surfaces — DECIDED (→ ADR-0054, building now)
|
||||
|
||||
- **`Cargo.toml` `version` is the single source of truth.** `--version`
|
||||
reads `env!("CARGO_PKG_VERSION")` (compile-time, no deps).
|
||||
- **Two surfaces:** a `--version` / `-V` CLI flag (prints + exits, like
|
||||
`--help`), and an in-app **`version`** app command.
|
||||
- **CI discipline:** `release.yaml` gains a guard that asserts the pushed
|
||||
tag `vX.Y.Z` equals `CARGO_PKG_VERSION`, and **fails the release on
|
||||
mismatch** — so the binary's self-reported version, the release name,
|
||||
and the downloaded asset always agree.
|
||||
- **Release ritual:** bump `Cargo.toml` → commit → tag `v<that number>`
|
||||
→ push tag. (The guard enforces it.)
|
||||
- Optional enrichment (not decided): a `build.rs` baking a git short-hash
|
||||
+ build date so non-tagged dev builds read `0.2.0 (a1b2c3d)`. Good for
|
||||
bug reports; can be added later.
|
||||
|
||||
---
|
||||
|
||||
## 2. `install.sh` (curl | sh) — DONE 2026-06-17 (ADR-0055)
|
||||
|
||||
**Shipped** `scripts/install.sh` (POSIX sh, shellcheck-clean). Verified
|
||||
end-to-end against the live public `v0.1.0` release: platform mappings
|
||||
(Linux/macOS × x86_64/aarch64; Windows + unknown arch error cleanly),
|
||||
pinned (`RDBMS_VERSION`) and latest (`releases/latest`) paths, SHA-256
|
||||
verification (incl. a tamper-rejection check), install to
|
||||
`~/.local/bin`, PATH hint. **`install.ps1` (Windows) added 2026-06-17**
|
||||
(user chose both a one-liner *and* Scoop/winget; ADR-0055 Amendment 1):
|
||||
`irm | iex`, maps host CPU → our `*-windows-gnu`/`-gnullvm` `.exe`,
|
||||
SHA-256-verifies, installs to `%LOCALAPPDATA%\Programs\…` + user PATH —
|
||||
**written but untested from this env** (no PowerShell; validate on
|
||||
Windows). The website copy that references these commands is the
|
||||
**website branch's** job (separate agent), later.
|
||||
A **shellcheck CI gate** for `scripts/` is a recommended follow-up (not
|
||||
added — shellcheck isn't in the flake yet; touches ADR-ci-002).
|
||||
|
||||
Original decided shape (for reference):
|
||||
|
||||
- **Hosted from the Gitea repo URL** on `git.lazyeval.net` (simplest):
|
||||
`curl -fsSL https://git.lazyeval.net/oli/rdbms-playground/raw/branch/main/scripts/install.sh | sh`
|
||||
(exact raw-URL form to confirm against the Gitea version).
|
||||
- **Behaviour:** POSIX `sh`; detect `uname` OS+arch → target triple
|
||||
(Linux → the **musl static** build, our universal Linux artifact);
|
||||
query the latest release via the Gitea API
|
||||
(`/repos/oli/rdbms-playground/releases/latest`) → tag → deterministic
|
||||
asset name `rdbms-playground-<tag>-<target>`; download + **verify the
|
||||
`.sha256`**; install to `~/.local/bin` (fallback `/usr/local/bin` with
|
||||
a sudo prompt); `chmod +x`; print a PATH hint if needed.
|
||||
- **macOS:** binaries are signed (see §4 signing note); a `curl`
|
||||
download does **not** apply the Gatekeeper quarantine xattr, so the
|
||||
installed binary runs without `xattr` faff.
|
||||
- **Windows:** not `curl | sh` — provide a PowerShell `install.ps1`
|
||||
(`irm … | iex`) and/or steer to Scoop/winget (§3).
|
||||
- **Security posture:** HTTPS only; in-script checksum verification;
|
||||
document the download-inspect-run alternative (`curl|sh` is a trust
|
||||
tradeoff).
|
||||
- **Deliverables we own now:** `scripts/install.sh` (+ later
|
||||
`install.ps1`); confirm releases are **publicly downloadable**; decide
|
||||
whether to also upload `install.sh` as a release asset for a stable
|
||||
link. Website copy referencing the command is the **website branch's**
|
||||
job (separate agent), later.
|
||||
|
||||
---
|
||||
|
||||
## 3. D3 — package managers (roadmap; each layers on the release assets)
|
||||
|
||||
Common thread: a manifest pointing at our checksummed assets + a
|
||||
per-release step to bump it. Ordered cheapest → most gatekept.
|
||||
|
||||
### 3a. `cargo binstall` + crates.io — PREPARED 2026-06-17 (ADR-0056)
|
||||
|
||||
**Done (prep):** crate made publish-ready (dropped `publish = false`;
|
||||
added `homepage`/`keywords`/`categories`/`exclude`; authored `README.md`
|
||||
+ `LICENSE-MIT`); `[package.metadata.binstall]` added with per-target
|
||||
overrides (linux-gnu→musl, windows-msvc→gnu/gnullvm; macOS direct);
|
||||
`cargo publish --dry-run` clean (913 KiB compressed). Dual license kept;
|
||||
`LICENSE-MIT` + `LICENSE-APACHE` (© Lazy Evaluation Ltd) + `CONTRIBUTING.md`
|
||||
(inbound=outbound) all in place. **Gated / remaining:** the actual `cargo
|
||||
publish` (token, irreversible) at a **new tagged release** (not 0.1.0); a
|
||||
real `cargo binstall` validation.
|
||||
|
||||
- **Bootstrapping matters (user-flagged):** `binstall` is **not** a
|
||||
built-in cargo subcommand — users must install **`cargo-binstall`**
|
||||
first (its own `curl|sh`/PowerShell installer, or
|
||||
`cargo install cargo-binstall`). **Our instructions must say this.**
|
||||
- Add `[package.metadata.binstall]` to `Cargo.toml` (pkg-url template →
|
||||
our Gitea release assets; our naming already fits).
|
||||
- **DECIDED (2026-06-17): publish to crates.io** — so the frictionless
|
||||
`cargo binstall rdbms-playground` resolves the crate, and the project
|
||||
is discoverable there. (A crates.io publish is its own small task:
|
||||
metadata completeness — description/license/repository/keywords/readme
|
||||
— and `cargo publish`; the `[package.metadata.binstall]` URL template
|
||||
points binstall at our Gitea release assets.) *(Verify current
|
||||
cargo-binstall behaviour when wiring.)*
|
||||
|
||||
### 3b. Scoop (Windows)
|
||||
- A **bucket** repo under `lazyeval` on Gitea with a JSON manifest
|
||||
(`.exe` URL + hash + `autoupdate`). Release job commits the bump.
|
||||
- Users: `scoop bucket add lazyeval <gitea-url>; scoop install rdbms-playground`.
|
||||
|
||||
### 3c. Homebrew (macOS/Linux)
|
||||
- A **company-branded tap** — `lazyeval/homebrew-tap` (on Gitea) — with a
|
||||
Ruby formula (per-arch `url` + `sha256`). Release job commits the bump.
|
||||
- Users: `brew tap lazyeval/tap https://git.lazyeval.net/lazyeval/homebrew-tap`
|
||||
then `brew install lazyeval/tap/rdbms-playground` (the explicit-URL tap
|
||||
form, since the `user/repo` shorthand assumes GitHub).
|
||||
- *"Notability bars"* = the acceptance criteria for the default
|
||||
**homebrew-core** tap (must be sufficiently popular/maintained). Our
|
||||
own `lazyeval` tap sidesteps that entirely — no review gate.
|
||||
|
||||
### 3d. winget (Windows)
|
||||
- Manifests are YAML PR'd to `microsoft/winget-pkgs` (reviewed by MS).
|
||||
- **`wingetcreate` is Windows-only** (.NET) — no good without a Windows
|
||||
runner. **Automatic path to evaluate first: `komac`** — a
|
||||
cross-platform (Rust) winget manifest creator/submitter that runs on
|
||||
our **Linux** CI. *(Verify komac's current capabilities/auth model.)*
|
||||
- **Fallback:** a manual YAML PR per release — acceptable given releases
|
||||
are infrequent (user-confirmed).
|
||||
|
||||
### Cross-cutting (3a–3d)
|
||||
- Two extra repos (tap + bucket) under `lazyeval`, with CI push
|
||||
credentials — setup TBD (user: "we'll figure that out").
|
||||
- **`cargo-dist`/"dist"** automates installers + Homebrew + CI, but is
|
||||
**GitHub-Actions/Releases-centric**; on self-hosted Gitea it won't drop
|
||||
in cleanly (installer-script generation might be reusable). Likely
|
||||
hand-roll the manifests + a small "update on release" job instead.
|
||||
*(Verify cargo-dist's current Gitea support before fully ruling out.)*
|
||||
|
||||
---
|
||||
|
||||
## Open decisions
|
||||
|
||||
1. **crates.io:** **RESOLVED 2026-06-17 — yes, publish.** (See §3a.)
|
||||
2. **Tracking:** **RESOLVED 2026-06-17 — doc + ADR only, no Gitea
|
||||
issues.**
|
||||
3. **Release downloads public:** **CONFIRMED 2026-06-17** — the Gitea
|
||||
releases are publicly downloadable (no auth); `install.sh` relies on
|
||||
it and was verified against the live `v0.1.0`.
|
||||
|
||||
### Still open / postponed
|
||||
|
||||
- **macOS signing — CONFIRMED BUG (2026-06-16), POSTPONED by the user
|
||||
(2026-06-17)** pending the correct signing ID. Details:
|
||||
- `release-macos.yaml` does `codesign --force --sign -` (ad-hoc) and has
|
||||
**no signing scaffolding at all** (no keychain import, no secrets) —
|
||||
so a downloaded binary is *not* properly signed (user-verified).
|
||||
- **The credential the user has is the wrong type:** `Apple Development:
|
||||
Oliver Sturm (W687M898E4)` is a *development* cert (Gatekeeper won't
|
||||
trust it for distribution). Distribution needs a **`Developer ID
|
||||
Application`** cert (same format, different type). Signing under the
|
||||
company name *"Lazy Evaluation Ltd"* would need an **Organization**
|
||||
Apple Developer account; a personal account signs as "Oliver Sturm".
|
||||
- **Notarization** (required with Developer ID for non-quarantined trust
|
||||
on browser downloads): after signing, `xcrun notarytool submit`. Creds
|
||||
= an **App Store Connect API key** (Issuer ID + Key ID + `.p8`,
|
||||
recommended for CI) *or* Apple ID + app-specific password + Team ID.
|
||||
A bare CLI binary can't be *stapled* (only bundles/dmg/pkg) — Gatekeeper
|
||||
does an online check instead.
|
||||
- **Urgency caveat:** the `curl|sh` path doesn't need any of this (curl
|
||||
downloads aren't quarantined); signing matters for browser downloads
|
||||
from the releases page. Fix when the right cert + creds exist; corrects
|
||||
the ad-hoc docs once landed.
|
||||
|
||||
## Sequencing
|
||||
|
||||
1. ✅ **Version discipline** (ADR-0054) — `--version`/`-V` + `version`
|
||||
command + CI tag-match guard + tests.
|
||||
2. ✅ **`scripts/install.sh`** (ADR-0055) — built + verified against the
|
||||
live public release.
|
||||
3. Package managers, cheapest first:
|
||||
- ✅ **`cargo binstall` + crates.io** — *prepared* (ADR-0056);
|
||||
publish gated on a new tagged release + the token.
|
||||
- **← next:** Scoop (`lazyeval` bucket) → Homebrew (`lazyeval` tap) →
|
||||
winget (komac / manual). Two `lazyeval` repos (tap + bucket) + CI
|
||||
push creds to set up.
|
||||
4. **Cut a release at a new version** — bump `Cargo.toml` (0.1.0 →
|
||||
0.1.1/0.2.0; the ADR-0054 guard checks the tag), tag, push; the four
|
||||
Linux/Windows targets build immediately. (macOS leg awaits signing.)
|
||||
+27
-8
@@ -70,8 +70,11 @@ since ADR-0027.)
|
||||
natively on a Tart Apple-Silicon runner via the dispatched
|
||||
`release-macos.yaml`. All uploaded to the Gitea release with a
|
||||
`.sha256` each. Decisions in `docs/ci/adr/` (ADR-ci-001/002/003).
|
||||
Runtime-verified by the user: Linux x86_64 + Windows aarch64; the
|
||||
others are link-clean / valid format.)*
|
||||
Runtime-verified by the user: Linux x86_64, Windows aarch64, and both
|
||||
macOS targets (the `release-macos.yaml` dispatch — now triggerable
|
||||
since CI is on `main` — was run end-to-end and the binaries launch);
|
||||
Linux aarch64 + Windows x86_64 remain link-clean / valid-format
|
||||
only.)*
|
||||
- [x] **D2** Single static binary, no runtime dependencies.
|
||||
*(Done 2026-06-15, per platform: **Linux** is fully static (musl +
|
||||
`crt-static`); **Windows** is a standalone `.exe` (Zig statically
|
||||
@@ -82,11 +85,24 @@ since ADR-0027.)
|
||||
No target requires anything the user must install. ADR-ci-003.)*
|
||||
- [ ] **D3** Released via prebuilt binaries plus Homebrew, Scoop,
|
||||
`winget`, and `cargo binstall`.
|
||||
*(Prebuilt binaries + checksums now published to Gitea releases
|
||||
(D1); the package-manager manifests (Homebrew / Scoop / winget /
|
||||
`cargo binstall`) remain to do. The asset naming
|
||||
`rdbms-playground-<tag>-<target>` is already binstall-friendly.
|
||||
Tracked under ADR-ci-003 "Deferred".)*
|
||||
*(Prebuilt binaries + checksums on Gitea releases (D1); **`cargo
|
||||
binstall` + crates.io live** (ADR-0056); **Scoop + Homebrew wired and
|
||||
validated end-to-end** on real Windows + macOS (ADR-0056 Amendments
|
||||
2 & 3) — `publish.yaml` `scoop-bucket` / `homebrew-tap` jobs render
|
||||
dependency-free manifests from the release `.sha256` sidecars and push
|
||||
them, via the scoped `lazyeval-ci` bot token, to
|
||||
`lazyeval/scoop-bucket` and `lazyeval/homebrew-tap`; rendering covered
|
||||
by `scripts/test-package-renders.sh`. NB: the Homebrew path depends on
|
||||
a **server-side Caddy rule** answering HEAD on release-download paths
|
||||
directly (Gitea's presigned-S3 redirect is method-bound and brew
|
||||
reuses a HEAD-signed URL for its GET) — see ADR-0056 Amendment 3;
|
||||
that rule lives in the Gitea edge config, not this repo. **winget
|
||||
wired** (ADR-0056 Amendment 4) — a `publish.yaml` `winget` job submits
|
||||
a PR to `microsoft/winget-pkgs` via komac (`LazyEvaluation.RdbmsPlayground`,
|
||||
portable, x64+arm64), guarded against duplicate PRs; pending the
|
||||
**one-time manual `komac new` bootstrap** + Microsoft review before
|
||||
it goes live. Asset naming `rdbms-playground-<tag>-<target>` is
|
||||
binstall-friendly.)*
|
||||
|
||||
## TUI shell
|
||||
|
||||
@@ -888,7 +904,10 @@ since ADR-0027.)
|
||||
exists (~55 lines, covers the WHERE-expression and
|
||||
table-creation boundaries). **Missing:** a DSL
|
||||
command-surface reference and a standalone type-system
|
||||
reference under `docs/`.)*
|
||||
reference under `docs/`. **Note (2026-06-16):** the **canonical**
|
||||
user docs now live on the **website** (ADR-website-001, deployed) —
|
||||
it covers the full feature set; the in-repo `docs/` reference pieces
|
||||
named here remain the outstanding part of DOC1.)*
|
||||
|
||||
## Testing (per ADR-0008)
|
||||
|
||||
|
||||
@@ -16,4 +16,4 @@ applies here too).
|
||||
|
||||
## Index
|
||||
|
||||
- [ADR-website-001 — Public website and documentation site](20260604-adr-website-001.md) — **Accepted 2026-06-04** (formerly ADR-0044 in the main index; moved here 2026-06-10 to end recurring cross-branch number collisions). The first public website: a marketing landing page plus the **canonical** user docs. Stack **Astro 6 + Starlight + Tailwind v4** (chosen over SvelteKit + Tailwind for a docs-heavy + marketing site; interactive bits as Astro islands). Showcase demos are **asciinema** `.cast` recordings (scripted-input driver for paced, re-recordable sessions — *not* `history.log` replay), reused inline in docs. The **in-page WASM playground is deferred** (OOS: deferred) behind a stable `Demo` seam, with the portable-core vs native-edge boundary recorded for a future ADR + iteration plan. Portable **static build** (**Cloudflare** target via **Gitea Actions**, host-agnostic); **no CI yet**; **monorepo** (`website/`). Docs cover the **full supported feature set** with "planned" callouts for the unshipped minority; two wording rules bind user-facing copy — **no engine name** (continues ADR-0002) and **no "DSL"** ("simple mode" / "advanced mode"). Install docs cover **prebuilt binaries + package managers** (D1–D3 track the release tooling). Plan: [`docs/website/plans/20260604-website-implementation-plan.md`](../plans/20260604-website-implementation-plan.md)
|
||||
- [ADR-website-001 — Public website and documentation site](20260604-adr-website-001.md) — **Accepted 2026-06-04** (formerly ADR-0044 in the main index; moved here 2026-06-10 to end recurring cross-branch number collisions). The first public website: a marketing landing page plus the **canonical** user docs. Stack **Astro 6 + Starlight + Tailwind v4** (chosen over SvelteKit + Tailwind for a docs-heavy + marketing site; interactive bits as Astro islands). Showcase demos are **asciinema** `.cast` recordings (scripted-input driver for paced, re-recordable sessions — *not* `history.log` replay), reused inline in docs. The **in-page WASM playground is deferred** (OOS: deferred) behind a stable `Demo` seam, with the portable-core vs native-edge boundary recorded for a future ADR + iteration plan. Portable **static build** (**Cloudflare** target via **Gitea Actions**, host-agnostic); **monorepo** (`website/`). **§4 update (CI implemented):** the static→Cloudflare Pages deploy now runs via Gitea Actions (`.gitea/workflows/website.yaml`; the crate gate is skipped for website-only changes); both `website` and `main` are merged and the site is **deployed**. Docs cover the **full supported feature set** with "planned" callouts for the unshipped minority; two wording rules bind user-facing copy — **no engine name** (continues ADR-0002) and **no "DSL"** ("simple mode" / "advanced mode"). Install docs cover **prebuilt binaries + package managers** (D1–D3 track the release tooling). Plan: [`docs/website/plans/20260604-website-implementation-plan.md`](../plans/20260604-website-implementation-plan.md)
|
||||
|
||||
Reference in New Issue
Block a user