Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
name: Deploy Pages

on:
push:
branches: [main]
paths:
- 'docs/specs/**'
- 'website/**'
- '.github/workflows/deploy-pages.yml'
workflow_dispatch:

permissions:
Expand Down
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"crates/shipwright-manifest",
"crates/shipwright-zed",
"tools/shipwright-version-stamp",
"tools/shipwright-release-scope",
]

[workspace.package]
Expand All @@ -25,6 +26,7 @@ readme = "README.md"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
glob = "0.3"

# Workspace lints — deny-by-default. See REPO-STANDARDS-SPEC [LINT-RUST].
# Inherit in every member crate with `[lints] workspace = true`.
Expand Down
3 changes: 3 additions & 0 deletions coverage-thresholds.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
},
"tools/shipwright-version-stamp": {
"threshold": 100
},
"tools/shipwright-release-scope": {
"threshold": 100
}
}
}
3 changes: 3 additions & 0 deletions crates/shipwright-zed/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
//! language server starts. This crate re-exports the pure host resolver and
//! adds small helpers for representing deferred LSP checks and validating the
//! `serverInfo` payload returned from LSP `initialize`.
//!
//! Implements the Zed deployment contract SWR-IDE-ZED: digest-verified
//! `github-release` resolution and LSP-`initialize` version checks.

#![forbid(unsafe_code)]

Expand Down
33 changes: 29 additions & 4 deletions docs/agents/shipwright-compliance/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ Spec source (cite these URLs, never local paths — this skill runs on repos tha
| Library architecture | `.../docs/specs/library-architecture.md` (`SWR-ARCH-*`) |
| Source projects & survey | `.../docs/specs/source-projects.md` (`SWR-SRC-*`) |
| Release pipeline plan | `.../docs/plans/release-pipeline.md` (`SWR-REL-*`) |
| **Release change detection** (CI cost gate) | `.../docs/specs/release-change-detection.md` (`SWR-REL-CHANGES-*`) |

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

## Workflow

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

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

- **No PATH / package-manager runtime fallback.** A normal startup that reads or mutates PATH, shells
out to `which`/`where`, or launches a Homebrew/Scoop/npm-global/cargo/dotnet-tool binary is FAIL.
Bundled or explicit-override sources only. `[SWR-IDE-RESOLUTION]`, `[SWR-SEC-CONTROLS]`.
Bundled or explicit-override sources only. On Zed this also bars `worktree.which` and a `~/.cargo/bin`
default: a silent PATH/preinstalled fallback is FAIL; the Zed default is the verified `github-release`
download, and a download branch left unreachable behind a never-true guard (dead-download) is also
FAIL. `[SWR-IDE-RESOLUTION]`, `[SWR-IDE-ZED]`, `[SWR-SEC-CONTROLS]`.
- **One VSIX per target.** Native-binary extensions MUST package `npx vsce package --target <vsceTarget>`.
A single all-platform native VSIX is FAIL. `[SWR-VSIX-PACKAGE]`.
- **Verify package contents.** The release MUST inspect each produced artifact: exact `bin/<target>/`
Expand All @@ -144,9 +161,17 @@ Prove the changes locally before declaring done:
install`, never crash on missing .NET, never hand-roll a download. `[SWR-IDE-DOTNET-RUNTIME]`.
- **Supply-chain integrity is non-negotiable.** Mutable action tags (`@v4`/`@stable`), a missing or
over-broad top-level `permissions:`, `npm install` (vs `npm ci`) in a release/VSIX job, a release
with no provenance/SBOM/cosign-signed `SHA256SUMS`, a downloader (Neovim/Zed/host) that executes a
fetched binary without verifying its checksum AND signature, or a long-lived registry/marketplace
with no provenance/SBOM/cosign-signed `SHA256SUMS`, a host/Neovim/brew/scoop downloader that execs a
fetched binary without verifying its digest AND cosign signature, a Zed extension that execs a
downloaded binary with no in-extension SHA-256 digest check (its cosign signature is a release-boundary
check — the WASM sandbox cannot run cosign; `[SWR-IDE-ZED]`), or a long-lived registry/marketplace
token outside a protected environment are all FAIL. `[SWR-SEC-*]`, `[SWR-SIGN-*]`.
- **No blind full-matrix releases.** A tag-triggered `release.yml` that rebuilds the macOS/Windows binary
matrix on every tag — even a website-only change — is a cost FAIL. Gate the costly jobs on
`release-change-detection.yml` outputs. The cascade is mandatory: a binary change releases EVERYTHING; an
unclassified change releases EVERYTHING (fail-safe); a vsix/jetbrains change still builds the binary matrix
(single-version contract). Only a website-only change may skip the matrix. Never gate so aggressively that a
real binary change ships without a rebuild. `[SWR-REL-CHANGES-CASCADE]`, `[SWR-REL-CHANGES-FAILSAFE]`, `[SWR-REL-CHANGES-CONTRACT]`.
- **License must be honest.** A package's declared SPDX license (default single `MIT`) MUST match a
LICENSE file that actually ships; declaring `MIT OR Apache-2.0` (or any expression) without the
second license's text present is FAIL. `[SWR-REL-LICENSE]`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ pipeline can still have required controls outstanding. Cite the `SWR-SEC-*` / `S
| 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 |
| Open VSX | `node-ovsx-sign`; a **separate** short-expiry PAT in a protected env | SWR-SEC-OIDC-PUBLISH |
| JetBrains / Android Studio | `signPlugin` certificate signature; publish token in a protected env | SWR-SEC-OIDC-PUBLISH |
| Zed | no committed `.wasm` drift; runtime `github-release` download verifies checksum + signature; version via LSP `initialize` | SWR-SEC-CHECKSUM |
| 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 |
| Homebrew / Scoop | `sha256`/`hash` sourced from the verified `SHA256SUMS`; scoped `tap_token`/`bucket_token` in a protected env | SWR-SEC-CHECKSUM |
| Neovim | downloader verifies `SHA256SUMS` + cosign before exec; pins the resolved tag (never `/latest`) | SWR-SEC-CHECKSUM |
| crates.io / npm / NuGet / pub.dev | OIDC trusted publishing — no long-lived token (npm also `--provenance`) | SWR-SEC-OIDC-PUBLISH |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,9 @@ own workflows, apply the same:
4. **Provenance + SBOM** — `actions/attest-build-provenance` + a CycloneDX SBOM (`anchore/sbom-action` /
`cargo cyclonedx`) attested per artifact; `cargo-auditable` for Rust. `[SWR-SEC-PROVENANCE]`, `[SWR-SEC-SBOM]`.
5. **Signed checksums** — one `SHA256SUMS`, cosign keyless-signed; replace per-asset `.sha256`. The
host / brew / scoop / Neovim / Zed download path verifies digest **and** signature before exec. `[SWR-SEC-CHECKSUM]`.
host / brew / scoop / Neovim download path verifies digest **and** cosign signature before exec; the
Zed WASM extension verifies the **digest** in-extension (its cosign signature is a release-boundary
check — no cosign in the sandbox). `[SWR-SEC-CHECKSUM]`, `[SWR-IDE-ZED]`.
6. **OIDC publishing** — move crates.io / NuGet / pub.dev **and the VS Code Marketplace** off
long-lived tokens; npm keeps `--provenance`; the Marketplace uses Entra workload-identity
federation (`azure/login` → `az account get-access-token` → `vsce`, no PAT — `[SWR-VSIX-PUBLISH-OIDC]`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,13 @@ fall back to a system binary. `[SWR-IDE-RESOLUTION]`. Valid sources:
- `env` — a documented environment override (only if the host documents one).
- `bundled` — the binary shipped inside the package (VSIX/plugin). Default for IDE extensions.
- `github-release` — managed download/cache, allowed **only** where marketplace packaging prevents
bundling (e.g. Zed). Must verify version from LSP `initialize`.
bundling (e.g. Zed; the Zed `sources` cascade is `["user-setting", "github-release"]`). Verify the
SHA-256 digest in-extension before exec (the cosign signature is checked at the release boundary, not
the WASM sandbox), and verify version from LSP `initialize`. Full model: `[SWR-IDE-ZED]`.

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

## Version output contract

Expand Down
39 changes: 38 additions & 1 deletion docs/specs/ide-extension-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ bin/
shipwright.json
```

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.
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].

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

Expand Down Expand Up @@ -112,6 +112,43 @@ manager as a normal startup source.

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.

## [SWR-IDE-ZED] Zed Extension Deployment

Zed extensions are WebAssembly published through a human-reviewed PR to the `zed-industries/extensions`
registry; they cannot bundle a native binary (see [SWR-COMPAT-HOST-REQ] in the binary version contract).
Every Shipwright product's Zed extension MUST follow one model so the user experience matches the
bundled-VSIX flow: install the extension, get the matching binary, no manual steps.

**Resolution order** (in `language_server_command`, via `shipwright-zed` on the pure `shipwright-host`
resolver):

1. Explicit user override — the Zed LSP `binary.path` setting (or a documented env override).
2. A previously cached download in the extension's version-stamped work directory.
3. A verified `github-release` download: `latest_github_release` (pinned repo, resolved tag — never
`/latest` drift) → `download_file` for the current platform → SHA-256 digest verified against
`SHA256SUMS` → `make_file_executable`.

Then validate the running server's version from the LSP `initialize` `serverInfo` before enabling
product features; a mismatch stops startup with a precise error ([SWR-IDE-ERROR]). Zed cannot preflight
`--version`.

**Forbidden as silent defaults.** `~/.cargo/bin`, a bare command name on `PATH`, and `worktree.which`
MUST NOT be the unconfigured startup source. A preinstalled/PATH binary is honored only when the user
explicitly opts into it (equivalent to an override), never as the default — the default is the verified
`github-release` download.

**Anti-pattern — dead download (FAIL).** A `github-release` download branch that is unreachable because
it is gated behind a condition that never occurs (e.g. only when `$HOME` is unset), leaving
`cargo install` / `~/.cargo/bin` or another package-manager path as the de-facto default, is a release
blocker. The audit MUST confirm the download branch is actually reachable on a normal machine.

**Integrity.** The digest is verified in-extension; the cosign signature over `SHA256SUMS` is verified at
the release/CI boundary, not in the WASM sandbox ([SWR-SEC-CHECKSUM]).

**Publishing.** Reviewed PR to `zed-industries/extensions`: add the extension as a git submodule under
`extensions/<id>`, add an `extensions.toml` entry (`submodule`, `version`, optional `path`), and run
`pnpm sort-extensions`. The `.wasm` is built reproducibly in CI and is never committed.

## [SWR-IDE-ERROR] Error Reporting

Startup errors MUST include:
Expand Down
Loading