Skip to content

Commit e8cee38

Browse files
Zed deployment contract (SWR-IDE-ZED) and release change-detection scope (#34)
## TLDR Pin the Zed extension deployment model as `[SWR-IDE-ZED]` across the specs and the shipwright-compliance skill — the WASM extension verifies the downloaded binary's SHA-256 digest in-extension while the cosign signature is checked at the release boundary — and land the `shipwright-release-scope` change-detection tool with its spec, schema, template, and fixtures. ## Details **Zed deployment contract — `[SWR-IDE-ZED]`** - `docs/specs/ide-extension-deployment.md`: new `[SWR-IDE-ZED]` section. A Zed extension is WASM and cannot bundle a native binary; resolution order is user `binary.path` → cached download → verified `github-release` download (`latest_github_release` + `download_file` for the current platform → SHA-256 verified against `SHA256SUMS` → `make_file_executable`), then version-gated via LSP `initialize`. `~/.cargo/bin`, bare-PATH, and `worktree.which` are forbidden as silent defaults. A download branch made unreachable behind a never-true guard (dead-download) is a FAIL. Publishing is a reviewed PR to `zed-industries/extensions` (submodule + `extensions.toml` + `pnpm sort-extensions`), WASM built reproducibly in CI, never committed. - `docs/specs/supply-chain-security.md`: carve Zed out of cosign-signature-before-exec. The WASM sandbox has no cosign primitive, so the extension verifies the SHA-256 **digest** in-extension; the cosign **signature** over `SHA256SUMS` is verified at the release/CI boundary. Updated `[SWR-SEC-CHECKSUM]`, the per-channel table row, and the Zed paragraph. - shipwright-compliance skill (`SKILL.md`, `reference/audit-checklist.md`, `reference/implement-release.md`, `reference/manifest-and-platforms.md`): audit rules updated to match — digest-in-extension vs signature-at-boundary, `worktree.which`/`~/.cargo/bin` barred as silent defaults, dead-download as a FAIL, registry publishing mechanics. - `crates/shipwright-zed/src/lib.rs`: module doc references `SWR-IDE-ZED`. - `.github/workflows/deploy-pages.yml`: auto-deploy the website on push to `main` (paths `docs/specs/**`, `website/**`) instead of `workflow_dispatch` only, so the published specs stay current. **Release change-detection — `shipwright-release-scope`** - `tools/shipwright-release-scope/`: new Rust workspace member (adds the `glob` workspace dependency; `Cargo.toml`/`Cargo.lock` updated). - `docs/specs/release-change-detection.md` (`SWR-REL-CHANGES-*`), `schemas/release-scope.schema.json`, `templates/gh-actions/release-change-detection.yml`, `fixtures/release-scope/basilisk.json`. - `fixtures/spec-coverage.json`: coverage entry (`SWR-SPEC-RELEASE-CHANGE-DETECTION`) for the new spec. - `coverage-thresholds.json`: 100% threshold for the new crate. No breaking changes — spec/doc/skill updates plus additive tooling; all source version fields remain `0.0.0-dev`. ## How Do The Automated Tests Prove It Works? - `node --test tests/fixtures.test.mjs` (7/7): `each spec file has fixture coverage or a tracked ticket id` now passes for `docs/specs/release-change-detection.md` (it was red — the spec existed with no coverage entry); `tracked SWR ids do not use numeric suffixes` passes for the new `SWR-IDE-ZED` and `SWR-SPEC-RELEASE-CHANGE-DETECTION` ids. - `make test`: 100% line coverage across every crate including `crates/shipwright-zed` and `tools/shipwright-release-scope`; Dart resolver-vector tests and .NET tests pass. - TypeScript matrix: `@nimblesite/shipwright-core` (55 tests), `@nimblesite/shipwright-vscode` (16), `@nimblesite/shipwright-mcp` (5) all pass. - `make lint`: clippy (compiling the `shipwright-zed` doc-comment change), .NET warnings-as-errors (0), Dart analyze (no issues), and AJV manifest/schema validation all pass.
1 parent 434053e commit e8cee38

20 files changed

Lines changed: 1477 additions & 20 deletions

File tree

.github/workflows/deploy-pages.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
name: Deploy Pages
33

44
on:
5+
push:
6+
branches: [main]
7+
paths:
8+
- 'docs/specs/**'
9+
- 'website/**'
10+
- '.github/workflows/deploy-pages.yml'
511
workflow_dispatch:
612

713
permissions:

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ members = [
77
"crates/shipwright-manifest",
88
"crates/shipwright-zed",
99
"tools/shipwright-version-stamp",
10+
"tools/shipwright-release-scope",
1011
]
1112

1213
[workspace.package]
@@ -25,6 +26,7 @@ readme = "README.md"
2526
serde = { version = "1", features = ["derive"] }
2627
serde_json = "1"
2728
thiserror = "2"
29+
glob = "0.3"
2830

2931
# Workspace lints — deny-by-default. See REPO-STANDARDS-SPEC [LINT-RUST].
3032
# Inherit in every member crate with `[lints] workspace = true`.

coverage-thresholds.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
},
1818
"tools/shipwright-version-stamp": {
1919
"threshold": 100
20+
},
21+
"tools/shipwright-release-scope": {
22+
"threshold": 100
2023
}
2124
}
2225
}

crates/shipwright-zed/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
//! language server starts. This crate re-exports the pure host resolver and
55
//! adds small helpers for representing deferred LSP checks and validating the
66
//! `serverInfo` payload returned from LSP `initialize`.
7+
//!
8+
//! Implements the Zed deployment contract SWR-IDE-ZED: digest-verified
9+
//! `github-release` resolution and LSP-`initialize` version checks.
710
811
#![forbid(unsafe_code)]
912

docs/agents/shipwright-compliance/SKILL.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ Spec source (cite these URLs, never local paths — this skill runs on repos tha
2727
| Library architecture | `.../docs/specs/library-architecture.md` (`SWR-ARCH-*`) |
2828
| Source projects & survey | `.../docs/specs/source-projects.md` (`SWR-SRC-*`) |
2929
| Release pipeline plan | `.../docs/plans/release-pipeline.md` (`SWR-REL-*`) |
30+
| **Release change detection** (CI cost gate) | `.../docs/specs/release-change-detection.md` (`SWR-REL-CHANGES-*`) |
3031

3132
Reusable workflow templates (fetch the raw file and adapt — do not hand-roll from memory):
3233
`https://raw.githubusercontent.com/Nimblesite/Shipwright/main/templates/gh-actions/<file>`
3334
where `<file>``release-binary-multiplatform.yml`, `publish-brew-tap.yml`,
34-
`publish-scoop-bucket.yml`, `publish-vsix-per-platform.yml`.
35+
`publish-scoop-bucket.yml`, `publish-vsix-per-platform.yml`,
36+
`release-change-detection.yml`.
3537

3638
## Workflow
3739

@@ -45,6 +47,7 @@ Shipwright Compliance Progress:
4547
- [ ] Phase A: Emit the audit report (conformity + security holes)
4648
- [ ] Phase B: Implement — manifest, version stamping, libraries, release.yml (see reference/implement-release.md)
4749
- [ ] Phase B: Wire GitHub Release + Homebrew + Scoop + per-platform VSIX + registries as applicable
50+
- [ ] Phase B: Add the release change-detection cost gate (release-scope.json + scope job + per-surface `if:`)
4851
- [ ] Phase B: Close the supply-chain holes — pinned actions, least-priv tokens, frozen installs, provenance, SBOM, signed checksums, OIDC publishing, per-channel verification
4952
- [ ] Phase C: Verify locally (manifest validates, `--version` matches, CI gate green)
5053
- [ ] Emit the change summary
@@ -110,6 +113,17 @@ order. The implementation playbook is the authoritative step list; the high-leve
110113
staged under `bin/<vsceTarget>/`, verified package contents, Marketplace publish on tag. `[SWR-VSIX-*]`.
111114
8. **Acceptance gates in CI** — validate the manifest, run `--version` / `--version --json`, and verify
112115
the produced package against the manifest. `[SWR-GATE-*]`.
116+
9. **Release change-detection cost gate** — add `.github/release-scope.json` (a per-repo ruleset mapping
117+
path globs to `binary`/`vsix`/`jetbrains`/`website`/`ignore`, validated by `release-scope.schema.json`),
118+
copy in `release-change-detection.yml`, and add a `scope:` job as the FIRST job of `release.yml`. Then
119+
gate every other job on its outputs: the native binary matrix `if: needs.scope.outputs.build_matrix == 'true'`,
120+
the standalone binary release + Homebrew + Scoop on `full`, VSIX jobs on `vsix`, JetBrains on `jetbrains`,
121+
website on `website`. CONTRACT (`[SWR-REL-CHANGES-CONTRACT]`): a tag stamps ONE version and host
122+
activation-verify is `onMismatch:error`, so a published VSIX/JetBrains plugin MUST bundle binaries built
123+
at the new version — therefore a vsix/jetbrains-only change STILL runs the binary matrix (do NOT reuse a
124+
prior release's binary). Only a website-only change skips the matrix. Pin `shipwright_rev` to a full
125+
Shipwright commit SHA. `[SWR-REL-CHANGES-*]`. Tailor the ruleset to the repo's actual layout — `binary`
126+
MUST cover every compiled source + lockfile (fail-safe favours over-releasing, never under-releasing).
113127

114128
Reuse the canonical templates (fetch the raw URLs above and adapt to this repo's binary/extension
115129
names) instead of writing workflows from scratch. Make the **smallest diff that achieves conformity**.
@@ -130,7 +144,10 @@ Prove the changes locally before declaring done:
130144

131145
- **No PATH / package-manager runtime fallback.** A normal startup that reads or mutates PATH, shells
132146
out to `which`/`where`, or launches a Homebrew/Scoop/npm-global/cargo/dotnet-tool binary is FAIL.
133-
Bundled or explicit-override sources only. `[SWR-IDE-RESOLUTION]`, `[SWR-SEC-CONTROLS]`.
147+
Bundled or explicit-override sources only. On Zed this also bars `worktree.which` and a `~/.cargo/bin`
148+
default: a silent PATH/preinstalled fallback is FAIL; the Zed default is the verified `github-release`
149+
download, and a download branch left unreachable behind a never-true guard (dead-download) is also
150+
FAIL. `[SWR-IDE-RESOLUTION]`, `[SWR-IDE-ZED]`, `[SWR-SEC-CONTROLS]`.
134151
- **One VSIX per target.** Native-binary extensions MUST package `npx vsce package --target <vsceTarget>`.
135152
A single all-platform native VSIX is FAIL. `[SWR-VSIX-PACKAGE]`.
136153
- **Verify package contents.** The release MUST inspect each produced artifact: exact `bin/<target>/`
@@ -144,9 +161,17 @@ Prove the changes locally before declaring done:
144161
install`, never crash on missing .NET, never hand-roll a download. `[SWR-IDE-DOTNET-RUNTIME]`.
145162
- **Supply-chain integrity is non-negotiable.** Mutable action tags (`@v4`/`@stable`), a missing or
146163
over-broad top-level `permissions:`, `npm install` (vs `npm ci`) in a release/VSIX job, a release
147-
with no provenance/SBOM/cosign-signed `SHA256SUMS`, a downloader (Neovim/Zed/host) that executes a
148-
fetched binary without verifying its checksum AND signature, or a long-lived registry/marketplace
164+
with no provenance/SBOM/cosign-signed `SHA256SUMS`, a host/Neovim/brew/scoop downloader that execs a
165+
fetched binary without verifying its digest AND cosign signature, a Zed extension that execs a
166+
downloaded binary with no in-extension SHA-256 digest check (its cosign signature is a release-boundary
167+
check — the WASM sandbox cannot run cosign; `[SWR-IDE-ZED]`), or a long-lived registry/marketplace
149168
token outside a protected environment are all FAIL. `[SWR-SEC-*]`, `[SWR-SIGN-*]`.
169+
- **No blind full-matrix releases.** A tag-triggered `release.yml` that rebuilds the macOS/Windows binary
170+
matrix on every tag — even a website-only change — is a cost FAIL. Gate the costly jobs on
171+
`release-change-detection.yml` outputs. The cascade is mandatory: a binary change releases EVERYTHING; an
172+
unclassified change releases EVERYTHING (fail-safe); a vsix/jetbrains change still builds the binary matrix
173+
(single-version contract). Only a website-only change may skip the matrix. Never gate so aggressively that a
174+
real binary change ships without a rebuild. `[SWR-REL-CHANGES-CASCADE]`, `[SWR-REL-CHANGES-FAILSAFE]`, `[SWR-REL-CHANGES-CONTRACT]`.
150175
- **License must be honest.** A package's declared SPDX license (default single `MIT`) MUST match a
151176
LICENSE file that actually ships; declaring `MIT OR Apache-2.0` (or any expression) without the
152177
second license's text present is FAIL. `[SWR-REL-LICENSE]`.

docs/agents/shipwright-compliance/reference/audit-checklist.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ pipeline can still have required controls outstanding. Cite the `SWR-SEC-*` / `S
173173
| VS Code Marketplace | per-VSIX provenance; bundled binary verified vs signed release (`SWR-VSIX-BUNDLE-VERIFY`); **Entra OIDC publish, no stored PAT**`id-token: write`, publisher-member SP, in a protected env | SWR-VSIX-PUBLISH-OIDC / SWR-SEC-OIDC-PUBLISH |
174174
| Open VSX | `node-ovsx-sign`; a **separate** short-expiry PAT in a protected env | SWR-SEC-OIDC-PUBLISH |
175175
| JetBrains / Android Studio | `signPlugin` certificate signature; publish token in a protected env | SWR-SEC-OIDC-PUBLISH |
176-
| Zed | no committed `.wasm` drift; runtime `github-release` download verifies checksum + signature; version via LSP `initialize` | SWR-SEC-CHECKSUM |
176+
| Zed | no committed `.wasm` drift; the `github-release` download verifies the SHA-256 digest **in-extension** (cosign signature is a release-boundary check, not in the WASM sandbox); the download branch is reachable (no dead-download behind a never-true guard); no `~/.cargo/bin`/`worktree.which`/PATH default; reviewed-PR publish (submodule + `extensions.toml`); version via LSP `initialize` | SWR-SEC-CHECKSUM, SWR-IDE-ZED |
177177
| Homebrew / Scoop | `sha256`/`hash` sourced from the verified `SHA256SUMS`; scoped `tap_token`/`bucket_token` in a protected env | SWR-SEC-CHECKSUM |
178178
| Neovim | downloader verifies `SHA256SUMS` + cosign before exec; pins the resolved tag (never `/latest`) | SWR-SEC-CHECKSUM |
179179
| crates.io / npm / NuGet / pub.dev | OIDC trusted publishing — no long-lived token (npm also `--provenance`) | SWR-SEC-OIDC-PUBLISH |

docs/agents/shipwright-compliance/reference/implement-release.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,9 @@ own workflows, apply the same:
207207
4. **Provenance + SBOM** — `actions/attest-build-provenance` + a CycloneDX SBOM (`anchore/sbom-action` /
208208
`cargo cyclonedx`) attested per artifact; `cargo-auditable` for Rust. `[SWR-SEC-PROVENANCE]`, `[SWR-SEC-SBOM]`.
209209
5. **Signed checksums** — one `SHA256SUMS`, cosign keyless-signed; replace per-asset `.sha256`. The
210-
host / brew / scoop / Neovim / Zed download path verifies digest **and** signature before exec. `[SWR-SEC-CHECKSUM]`.
210+
host / brew / scoop / Neovim download path verifies digest **and** cosign signature before exec; the
211+
Zed WASM extension verifies the **digest** in-extension (its cosign signature is a release-boundary
212+
check — no cosign in the sandbox). `[SWR-SEC-CHECKSUM]`, `[SWR-IDE-ZED]`.
211213
6. **OIDC publishing** — move crates.io / NuGet / pub.dev **and the VS Code Marketplace** off
212214
long-lived tokens; npm keeps `--provenance`; the Marketplace uses Entra workload-identity
213215
federation (`azure/login` → `az account get-access-token` → `vsce`, no PAT — `[SWR-VSIX-PUBLISH-OIDC]`);

docs/agents/shipwright-compliance/reference/manifest-and-platforms.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,13 @@ fall back to a system binary. `[SWR-IDE-RESOLUTION]`. Valid sources:
7171
- `env` — a documented environment override (only if the host documents one).
7272
- `bundled` — the binary shipped inside the package (VSIX/plugin). Default for IDE extensions.
7373
- `github-release` — managed download/cache, allowed **only** where marketplace packaging prevents
74-
bundling (e.g. Zed). Must verify version from LSP `initialize`.
74+
bundling (e.g. Zed; the Zed `sources` cascade is `["user-setting", "github-release"]`). Verify the
75+
SHA-256 digest in-extension before exec (the cosign signature is checked at the release boundary, not
76+
the WASM sandbox), and verify version from LSP `initialize`. Full model: `[SWR-IDE-ZED]`.
7577

76-
**Never** include PATH / Homebrew / Scoop / npm-global / cargo / dotnet-tool as a normal startup source.
77-
They are repair flows only, and only after an explicit prompt. `[SWR-IDE-PKG-REPAIR]`, `[SWR-SEC-CONTROLS]`.
78+
**Never** include PATH / Homebrew / Scoop / npm-global / cargo / dotnet-tool as a normal startup source
79+
— on Zed this also bars `worktree.which` and a `~/.cargo/bin` default. They are repair flows or explicit
80+
user overrides only, never the silent default. `[SWR-IDE-PKG-REPAIR]`, `[SWR-IDE-ZED]`, `[SWR-SEC-CONTROLS]`.
7881

7982
## Version output contract
8083

docs/specs/ide-extension-deployment.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ bin/
6060
shipwright.json
6161
```
6262

63-
Zed extensions may download and cache release assets when marketplace packaging prevents bundling native binaries. In that case, the extension MUST validate the server version from LSP initialize before enabling product features.
63+
Zed extensions may download and cache release assets when marketplace packaging prevents bundling native binaries. In that case, the extension MUST validate the server version from LSP initialize before enabling product features. The canonical model — resolution order, integrity, and publishing — is [SWR-IDE-ZED].
6464

6565
## [SWR-IDE-TEST-ISOLATION] Extension Test Isolation
6666

@@ -112,6 +112,43 @@ manager as a normal startup source.
112112

113113
If the user explicitly configured a path and it mismatches, do not fall back. The user needs a precise error so they can fix the configured path.
114114

115+
## [SWR-IDE-ZED] Zed Extension Deployment
116+
117+
Zed extensions are WebAssembly published through a human-reviewed PR to the `zed-industries/extensions`
118+
registry; they cannot bundle a native binary (see [SWR-COMPAT-HOST-REQ] in the binary version contract).
119+
Every Shipwright product's Zed extension MUST follow one model so the user experience matches the
120+
bundled-VSIX flow: install the extension, get the matching binary, no manual steps.
121+
122+
**Resolution order** (in `language_server_command`, via `shipwright-zed` on the pure `shipwright-host`
123+
resolver):
124+
125+
1. Explicit user override — the Zed LSP `binary.path` setting (or a documented env override).
126+
2. A previously cached download in the extension's version-stamped work directory.
127+
3. A verified `github-release` download: `latest_github_release` (pinned repo, resolved tag — never
128+
`/latest` drift) → `download_file` for the current platform → SHA-256 digest verified against
129+
`SHA256SUMS``make_file_executable`.
130+
131+
Then validate the running server's version from the LSP `initialize` `serverInfo` before enabling
132+
product features; a mismatch stops startup with a precise error ([SWR-IDE-ERROR]). Zed cannot preflight
133+
`--version`.
134+
135+
**Forbidden as silent defaults.** `~/.cargo/bin`, a bare command name on `PATH`, and `worktree.which`
136+
MUST NOT be the unconfigured startup source. A preinstalled/PATH binary is honored only when the user
137+
explicitly opts into it (equivalent to an override), never as the default — the default is the verified
138+
`github-release` download.
139+
140+
**Anti-pattern — dead download (FAIL).** A `github-release` download branch that is unreachable because
141+
it is gated behind a condition that never occurs (e.g. only when `$HOME` is unset), leaving
142+
`cargo install` / `~/.cargo/bin` or another package-manager path as the de-facto default, is a release
143+
blocker. The audit MUST confirm the download branch is actually reachable on a normal machine.
144+
145+
**Integrity.** The digest is verified in-extension; the cosign signature over `SHA256SUMS` is verified at
146+
the release/CI boundary, not in the WASM sandbox ([SWR-SEC-CHECKSUM]).
147+
148+
**Publishing.** Reviewed PR to `zed-industries/extensions`: add the extension as a git submodule under
149+
`extensions/<id>`, add an `extensions.toml` entry (`submodule`, `version`, optional `path`), and run
150+
`pnpm sort-extensions`. The `.wasm` is built reproducibly in CI and is never committed.
151+
115152
## [SWR-IDE-ERROR] Error Reporting
116153

117154
Startup errors MUST include:

0 commit comments

Comments
 (0)