Skip to content

Commit 3122294

Browse files
cscheidclaude
andauthored
GitHub release assets for q2: installers + minisign + bundled quarto-hub.com MCP defaults (bd-c6l13j79) (#278)
* feat(mcp-launcher): bundled quarto-hub.com defaults via option_env (bd-c6l13j79) Release builds of q2 compile in defaults for the three env vars the TS MCP server reads (QUARTO_HUB_MCP_CLIENT_ID/CLIENT_SECRET, QUARTO_HUB_SERVER) from QUARTO_HUB_BUNDLED_{CLIENT_ID,CLIENT_SECRET,SERVER} build-time env (set only by the release workflow, from GH secrets/vars). The launcher injects them into the node child's environment for any variable the user hasn't set to a non-blank value — readNonEmpty semantics mirrored from oauth-config.ts, so blank counts as unset. Source builds compile with no values and behave exactly as before (TS server's own error). The Desktop-app OAuth client_secret is a public client credential (RFC 8252) — embedding is the standard native-app distribution (gcloud, gh, rclone); CI-time injection only keeps GOCSPX- scanners away from the repo. Plan: claude-notes/plans/2026-06-12-q2-github-releases-bundled-mcp.md - src/defaults.rs: bundled_defaults() + classify/injections/sources pure logic, unit-tested (9 tests) - delegate.rs: build_command seam (testable — the Unix path execs and cannot be observed by a test); env injection ahead of the exec/spawn split (2 tests) - --launcher-info gains 'default <VAR>: env|bundled|absent' lines (values uniformly elided; printed for placeholder builds too) - .gitignore: /q2-release.{key,pub} — release signing keypair must never be committed Verified end-to-end with test values baked into a local build: child process receives bundled values only for unset vars; user env wins; absent-defaults build injects nothing. 35/35 launcher tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: q2 release installers (install.sh/install.ps1) + offline test harness (bd-c6l13j79) Ported from cscheid/braid's release tooling (study copy in external-sources/braid): checksum + minisign Ed25519 verification both mandatory and fail-closed, trusted comment = archive filename (replay protection), atomic install, non-interactive under curl|bash. q2 deltas: flat archive (the MCP bundle is embedded in the binary), Q2_* env vars, --from-source builds the MCP bundle when npm is present, unsupported-platform advice points at --from-source. The pinned signing key is 91F595A50BD20376 (public half; the secret lives in the MINISIGN_SECRET_KEY repo secret). - crates/quarto/tests/integration/bootstrap_sh.rs: 32 offline tests (file:// + --checksum; no network), covering refusal paths, signature replay, tampering, dest resolution, quiet mode, uninstall, shellcheck - test-suite.yml: install minisign on both runners (the suite requires it loudly — silent skip would leave the signature contract unguarded) - README: Installing section with pinned pubkey + manual verification Plan: claude-notes/plans/2026-06-12-q2-github-releases-bundled-mcp.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(hub-mcp): target-aware keyring staging for the bundle (bd-c6l13j79) The .node addon ships inside the bundle's mini node_modules and is loaded by the *user's* node, so it must match the release target, not the build host — braid-style release matrices cross-build darwin_amd64 on arm64 runners, where only keyring-darwin-arm64 is installed. scripts/stage-keyring.mjs (split from bundle.mjs, injectable fetcher): - KEYRING_PLATFORMS env (e.g. 'darwin-x64,darwin-arm64') pins exactly which platform packages ship; missing ones are fetched via npm pack at the loader's exact version - unset → original dev behavior (stage every installed platform pkg) - fail-closed: unfetchable platform or addon-less package aborts 10 offline unit tests (fake fetcher, fabricated @napi-rs trees) in src/keyring-staging.test.ts. Real-fetch path verified by hand on this arm64 mac: KEYRING_PLATFORMS=darwin-arm64,darwin-x64 staged a Mach-O x86_64 keyring.darwin-x64.node via npm pack. Full package suite green (194 passed; requires QUARTO_HUB_MCP_* unset — exported operator credentials make spawned test servers register auth tools). Plan: claude-notes/plans/2026-06-12-q2-github-releases-bundled-mcp.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: release workflow + real CLI version (bd-c6l13j79) .github/workflows/release.yml, ported from cscheid/braid with q2's three-embedded-payloads reality: - preflight: tag must match the workspace Cargo.toml version - web-payloads job builds the target-independent embeds once (WASM → preview SPA, trace viewer; toolchain mirrors hub-client-e2e.yml, wasm-bindgen-cli pinned from Cargo.lock); the 5-target matrix downloads them and builds the MCP bundle per target with KEYRING_PLATFORMS (user-node addons: both libcs on linux, both mac archs, both win archs) - bundled hub defaults injected from secrets/vars into the cargo env, release workflow only, with an empty-value fail-fast - per-target verify step: --version equals the tag version; launcher info shows no PLACEHOLDER, 'default …: bundled' ×3, and a keyring addon per requested platform (the anti-stale-embed gate) - archives + sha256 + minisign (trusted comment = filename), self-verified against the install.sh pinned key; combined checksums.sha256; gh release create (prerelease on -suffix tags) CLI version contract change (Carlos, in-session): q2 --version now reports the real workspace version ('quarto 0.1.0') instead of the 99.9.9-dev extension-compat placeholder — release artifacts must be verifiable against their tag, and Lua-side quarto.version already reported {0,1,0}. TDD'd (red first) in quarto-util. The since_version 99.9.9 markers in error_catalog.json are a separate concern, untouched. Also: musl-scoped openssl-sys/vendored in crates/quarto (samod → tokio-tungstenite(native-tls) needs openssl; static musl builds get it from source), and a semver-compliant deprecated-since in quarto-source-map (pre-existing deny-level clippy error, on-topic with the version decision). Remaining pre-existing clippy deny in quarto-core filed as bd-3up1mf8c. Full workspace suite after the version change: 10038/10038 passed. Plan: claude-notes/plans/2026-06-12-q2-github-releases-bundled-mcp.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 6bd9d59 commit 3122294

19 files changed

Lines changed: 3295 additions & 60 deletions

File tree

.github/workflows/release.yml

Lines changed: 521 additions & 0 deletions
Large diffs are not rendered by default.

.github/workflows/test-suite.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ jobs:
107107
if: runner.os == 'macOS'
108108
run: brew install tree-sitter-cli
109109

110+
# The installer test suite (crates/quarto/tests/integration/
111+
# bootstrap_sh.rs) REQUIRES minisign — signature verification is
112+
# part of the release-artifact contract and absence fails loudly
113+
# rather than skipping (bd-c6l13j79).
114+
- name: Set up minisign (Linux)
115+
if: runner.os == 'Linux'
116+
run: sudo apt-get install -y minisign
117+
118+
- name: Set up minisign (macOS)
119+
if: runner.os == 'macOS'
120+
run: brew install minisign
121+
110122
# Free disk space on Linux runners (14 GB SSD is tight for Rust monorepo).
111123
# `remove_tool_cache: true` is safe — no step in this job uses /opt/hostedtoolcache/
112124
# (no setup-node, setup-python, etc.). See claude-notes/2026-04-28-ci-disk-space-and-profile-ci.md.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ CLAUDE.local.md
4444
# marker (which only names the project) is intentionally NOT ignored.
4545
.braid.toml
4646
.braid-stragglers.jsonl
47+
48+
# Release signing keypair — NEVER commit (secret half lives in GH secrets + password manager)
49+
/q2-release.key
50+
/q2-release.pub

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,48 @@
66
77
This repository is a Rust implementation of the next version of [Quarto](https://quarto.org). The goal is to replace parts of the TypeScript/Deno runtime with a unified Rust implementation.
88

9+
## Installing
10+
11+
> Release binaries are experimental, like everything else here — they
12+
> exist so the team and early testers don't need a full Rust + Node
13+
> toolchain.
14+
15+
```sh
16+
curl -fsSL https://raw.githubusercontent.com/quarto-dev/q2/main/install.sh | bash
17+
```
18+
19+
On Windows (PowerShell):
20+
21+
```powershell
22+
irm https://raw.githubusercontent.com/quarto-dev/q2/main/install.ps1 | iex
23+
```
24+
25+
The installer downloads the release archive for your platform, verifies
26+
its SHA-256 checksum **and** its Ed25519 signature ([minisign](https://jedisct1.github.io/minisign/),
27+
required — `brew install minisign` / `apt install minisign`), and
28+
installs `q2` to `~/.local/bin` (override with `--dest` or
29+
`Q2_INSTALL_DIR`). The signing public key, pinned in `install.sh`:
30+
31+
```
32+
RWR2A9ILpZX1kVF3Q6uk5TRus8FDM25H2F+KKKHEuqlxv+JJSLyPalvN
33+
```
34+
35+
To verify a manually downloaded archive:
36+
37+
```sh
38+
minisign -Vm q2-<version>-<platform>.tar.gz -P RWR2A9ILpZX1kVF3Q6uk5TRus8FDM25H2F+KKKHEuqlxv+JJSLyPalvN
39+
```
40+
41+
The trusted comment should name exactly the file you downloaded.
42+
43+
`q2 mcp` (the Quarto Hub MCP server) additionally needs
44+
[Node.js](https://nodejs.org) 24+ at runtime; release binaries come
45+
with the MCP server and quarto-hub.com connection defaults built in, so
46+
no further configuration is needed. Private hub operators can still
47+
point elsewhere via the `QUARTO_HUB_MCP_CLIENT_ID`,
48+
`QUARTO_HUB_MCP_CLIENT_SECRET`, and `QUARTO_HUB_SERVER` environment
49+
variables, which always win over the built-in defaults.
50+
951
## Building
1052

1153
Requires Rust nightly (edition 2024).

0 commit comments

Comments
 (0)