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
5 changes: 3 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- name: Validate distribution contract
run: |
node scripts/validate-distribution-contract.mjs
node --test scripts/distribution-contract.test.mjs
node --test scripts/distribution-contract.test.mjs scripts/aqua-registry.test.mjs
- name: Install Aqua for registry validation
uses: aquaproj/aqua-installer@96a9bc20066c5bf5e275b41019cfc165b25f4e2e # v4.0.5
with:
Expand All @@ -46,8 +46,8 @@ jobs:
AQUA_CONFIG: ${{ github.workspace }}/tests/aqua/aqua.yaml
AQUA_POLICY_CONFIG: ${{ github.workspace }}/tests/aqua/aqua-policy.yaml
run: |
aqua update-checksum
aqua update
aqua update-checksum
git diff --exit-code -- tests/aqua/aqua-checksums.json tests/aqua/aqua.yaml
for environment in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64; do
root="$RUNNER_TEMP/aqua-${environment//\//-}"
Expand Down Expand Up @@ -158,6 +158,7 @@ jobs:
test -s catalogs/azure.json
test -s catalogs/simple-icons.json
test -s scripts/generate-provider-catalogs.mjs
test -s scripts/aqua-registry.test.mjs
test -s scripts/sync-example-templates.mjs
test -s scripts/validate-provider-catalogs.mjs
test -s scripts/validate-distribution-contract.mjs
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

`stack-sh/cli` is the open-source native Rust `stack` command for Stack architecture diagrams.

The repository contains native validation, formatting, and rendering commands. [Stack CLI 0.3.0](https://github.com/stack-sh/cli/releases/tag/v0.3.0) is the first supported native binary release for macOS 13 or newer and glibc-based Linux 2.31 or newer, on arm64 and x86_64. GitHub Releases and Homebrew are available; Cargo, Aqua, and self-update are still planned. The target matrix, artifact names, verification material, channel ownership, and rollback rules are defined by the [distribution contract](./docs/distribution.md), with signing and verification procedures in the [supply-chain guide](./docs/supply-chain.md).
The repository contains native validation, formatting, and rendering commands. [Stack CLI 0.3.0](https://github.com/stack-sh/cli/releases/tag/v0.3.0) is the first supported native binary release for macOS 13 or newer and glibc-based Linux 2.31 or newer, on arm64 and x86_64. GitHub Releases, Homebrew, and the owner-maintained Aqua registry are available; Cargo and self-update are still planned. The target matrix, artifact names, verification material, channel ownership, and rollback rules are defined by the [distribution contract](./docs/distribution.md), with signing and verification procedures in the [supply-chain guide](./docs/supply-chain.md).

## Install

Expand All @@ -12,6 +12,15 @@ On supported Homebrew hosts, install the owner-maintained formula:
brew install stack-sh/tap/stack
```

For Aqua, copy the pinned configuration and policy from the [Aqua installation section](./docs/distribution.md#aqua-installation), review them, allow the policy, and install the checksum-locked package:

```sh
aqua policy allow
aqua update-checksum
aqua install
stack --version
```

For a direct installation, download the archive for your target and its verification material from [GitHub Releases](https://github.com/stack-sh/cli/releases/tag/v0.3.0). Verify the checksum signature and both attestations by following the [supply-chain guide](./docs/supply-chain.md), then follow the [direct installation steps](./docs/distribution.md#direct-installation). The macOS artifacts are reproducibly ad-hoc signed, not Apple-notarized; Sigstore and GitHub attestations provide the publisher-identity check.

## Commands
Expand Down
4 changes: 2 additions & 2 deletions aqua/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ The registry supports only the release contract's tier-1 environments: macOS and
Install the pinned Aqua version used by CI, then test every supported mapping without executing a foreign-architecture binary:

```sh
aqua update-checksum
aqua update
aqua update-checksum
for environment in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64; do
AQUA_CONFIG=tests/aqua/aqua.yaml \
AQUA_POLICY_CONFIG=tests/aqua/aqua-policy.yaml \
Expand All @@ -20,7 +20,7 @@ for environment in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64; do
done
```

`aqua update-checksum` must reproduce `tests/aqua/aqua-checksums.json` exactly. The file locks all four release archives to the SHA-256 values obtained from the release checksum asset after Aqua verifies its Sigstore bundle. `aqua update` must leave the pinned fixture unchanged until a newer stable release exists.
`aqua update` must leave the pinned fixture unchanged until a newer stable release exists. `aqua update-checksum` must then reproduce `tests/aqua/aqua-checksums.json` exactly. The file locks all four release archives to the SHA-256 values obtained from the release checksum asset after Aqua verifies its Sigstore bundle.

On the native host, repeat without `--test` in an isolated `AQUA_ROOT_DIR`, then run `stack --version`, `stack init`, `stack check`, and `stack render`.

Expand Down
6 changes: 3 additions & 3 deletions distribution/distribution-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"availability": {
"state": "available",
"message": "Stack CLI 0.3.0 is available from GitHub Releases and Homebrew. Cargo, Aqua, and self-update remain planned."
"message": "Stack CLI 0.3.0 is available from GitHub Releases, Homebrew, and the owner-maintained Aqua registry. Cargo and self-update remain planned."
},
"versioning": {
"scheme": "Semantic Versioning",
Expand Down Expand Up @@ -144,14 +144,14 @@
},
{
"id": "aqua",
"state": "planned",
"state": "available",
"targets": [
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"aarch64-unknown-linux-gnu",
"x86_64-unknown-linux-gnu"
],
"owns": "registry metadata and version pinning mapped to GitHub archives and SHA-256 values",
"owns": "owner registry metadata, immutable registry revision, version pinning, and SHA-256 lock mapped to canonical GitHub archives",
"source": "github-release",
"updatePolicy": "Aqua owns upgrades; stack self-update must refuse replacement"
},
Expand Down
61 changes: 56 additions & 5 deletions docs/distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

This document defines the shared release contract for the Stack CLI. It is normative for GitHub Releases, Homebrew, Cargo, Aqua, and `stack` self-update implementations. The machine-readable source is [`distribution/distribution-contract.json`](../distribution/distribution-contract.json).

[Stack CLI 0.3.0](https://github.com/stack-sh/cli/releases/tag/v0.3.0) is available as a supported GitHub Release for every target below and through the owner-maintained Homebrew tap for the hosts marked below. Cargo, Aqua, and self-update remain **planned** and have no supported install command yet.
[Stack CLI 0.3.0](https://github.com/stack-sh/cli/releases/tag/v0.3.0) is available as a supported GitHub Release for every target below, through the owner-maintained Homebrew tap for the hosts marked below, and through the checksum-locked owner Aqua registry. Cargo and self-update remain **planned** and have no supported install command yet.

## Supported platform matrix

The first supported binary matrix is intentionally narrow:

| Rust target | OS | Architecture | Runtime floor | Direct | Homebrew | Cargo | Aqua | Self-update |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `aarch64-apple-darwin` | macOS | arm64 | macOS 13 | available | available | planned | planned | planned |
| `x86_64-apple-darwin` | macOS | x86_64 | macOS 13 | available | — | planned | planned | planned |
| `aarch64-unknown-linux-gnu` | Linux | arm64 | glibc 2.31 | available | available | planned | planned | planned |
| `x86_64-unknown-linux-gnu` | Linux | x86_64 | glibc 2.31 | available | available | planned | planned | planned |
| `aarch64-apple-darwin` | macOS | arm64 | macOS 13 | available | available | planned | available | planned |
| `x86_64-apple-darwin` | macOS | x86_64 | macOS 13 | available | — | planned | available | planned |
| `aarch64-unknown-linux-gnu` | Linux | arm64 | glibc 2.31 | available | available | planned | available | planned |
| `x86_64-unknown-linux-gnu` | Linux | x86_64 | glibc 2.31 | available | available | planned | available | planned |

Windows, musl-based Linux distributions such as Alpine, BSD, and 32-bit architectures are not supported release targets. A source build may happen to work elsewhere, but it is best-effort and does not block a release. Cargo installs on supported targets require Rust 1.85 or newer. Homebrew availability additionally follows [Homebrew's current tier-1 host requirements](https://docs.brew.sh/Support-Tiers); Stack does not label a host as supported when the package manager itself classifies it below tier 1.

Expand Down Expand Up @@ -74,6 +74,57 @@ The formula does not remove or replace Stack configuration and icon stores durin

Homebrew was activated after the immutable `v0.3.0` release assets were published. The release manifest therefore remains the publication-time record, while this contract and the tap CI record the later channel activation; release assets are not replaced to retrofit that state.

## Aqua installation

The owner registry is the [`aqua/registry.yaml`](../aqua/registry.yaml) file pinned to immutable commit `42702cda91a4156901b9a601bd143c43dcf05766`. Aqua maps `darwin/amd64`, `darwin/arm64`, `linux/amd64`, and `linux/arm64` to the four canonical GitHub Release archives, reads their SHA-256 values from the signed checksum asset, and verifies the checksum bundle against the tagged `release.yaml` workflow identity.

Add the following `aqua.yaml` to a Git repository:

```yaml
checksum:
enabled: true
require_checksum: true
registries:
- name: stack-sh
type: github_content
repo_owner: stack-sh
repo_name: cli
ref: 42702cda91a4156901b9a601bd143c43dcf05766
path: aqua/registry.yaml
packages:
- name: stack-sh/cli@v0.3.0
registry: stack-sh
```

Because Aqua denies non-standard registries by default, add and review this narrow `aqua-policy.yaml` rather than disabling policy:

```yaml
registries:
- name: stack-sh
type: github_content
repo_owner: stack-sh
repo_name: cli
ref: 'Version == "42702cda91a4156901b9a601bd143c43dcf05766"'
path: aqua/registry.yaml
packages:
- name: stack-sh/cli
registry: stack-sh
version: semver(">= 0.3.0")
```

Allow the reviewed policy once, generate the checksum lock, and install:

```sh
aqua policy allow
aqua update-checksum
aqua install
stack --version
```

Commit `aqua-checksums.json` with the configuration. To upgrade after a new stable Stack release, run `aqua update`, review the version change, then run `aqua update-checksum` and `aqua install`. Aqua owns the replacement; `stack` self-update must refuse to overwrite it. The registry maintainer procedure and four-target test command are in [`aqua/README.md`](../aqua/README.md).

Aqua was activated after the immutable `v0.3.0` release assets were published. The release manifest remains the publication-time record; the pinned registry commit, generated checksum lock, contract, and CI runs are the later activation evidence. No release asset is replaced.

## Direct installation

Download [Stack CLI 0.3.0](https://github.com/stack-sh/cli/releases/tag/v0.3.0), select the archive whose target matches the supported platform table, and obtain all matching verification material. Complete the [supply-chain verification](./supply-chain.md), then extract and install the verified binary. Replace `{target}` with the exact release target:
Expand Down
2 changes: 1 addition & 1 deletion docs/releases/v0.3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Highlights:
- Install on macOS 13 or newer and glibc-based Linux 2.31 or newer, on arm64 or x86_64.
- Verify every archive with SHA-256, keyless Sigstore, SLSA provenance, and an SPDX 2.3 SBOM attestation.

Download the archive for your target and all matching verification materials from this release. Follow the [release verification guide](https://github.com/stack-sh/cli/blob/v0.3.0/docs/supply-chain.md) before installing the `stack` binary. Homebrew, Cargo, Aqua, and self-update channels are not available in this release.
Download the archive for your target and all matching verification materials from this release. Follow the [release verification guide](https://github.com/stack-sh/cli/blob/v0.3.0/docs/supply-chain.md) before installing the `stack` binary. At publication time, Homebrew, Cargo, Aqua, and self-update were not available. Homebrew and Aqua were activated later without replacing these immutable assets; see the [current distribution contract](../distribution.md) for supported install commands.

The macOS binaries have a reproducible ad-hoc signature and are not Apple-notarized. Use the Sigstore verification flow above as the publisher-identity check. After verification, replace `{target}` with the downloaded target and install the binary:

Expand Down
63 changes: 63 additions & 0 deletions scripts/aqua-registry.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const configuration = fs.readFileSync(path.join(root, "tests/aqua/aqua.yaml"), "utf8");
const policy = fs.readFileSync(path.join(root, "tests/aqua/aqua-policy.yaml"), "utf8");
const registry = fs.readFileSync(path.join(root, "aqua/registry.yaml"), "utf8");
const distribution = fs.readFileSync(path.join(root, "docs/distribution.md"), "utf8");
const checksums = JSON.parse(
fs.readFileSync(path.join(root, "tests/aqua/aqua-checksums.json"), "utf8"),
);

const targets = [
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
];

test("the owner registry is pinned to one immutable revision", () => {
const revision = configuration.match(/^\s+ref: ([0-9a-f]{40})$/m)?.[1];

assert.match(revision ?? "", /^[0-9a-f]{40}$/);
assert.ok(policy.includes(`ref: 'Version == "${revision}"'`));
assert.ok(distribution.includes(`immutable commit \`${revision}\``));
assert.ok(distribution.includes(`ref: ${revision}`));
assert.ok(distribution.includes(`ref: 'Version == "${revision}"'`));
assert.ok(!configuration.includes("ref: main"));
});

test("the registry maps exactly the four supported release targets", () => {
assert.ok(registry.includes("asset: stack-{{.Version}}-{{.Arch}}-{{.OS}}.{{.Format}}"));
assert.ok(registry.includes("asset: stack-{{.Version}}-checksums.txt"));
assert.ok(registry.includes("asset: stack-{{.Version}}-checksums.txt.sigstore.json"));
assert.ok(registry.includes("src: \"{{.AssetWithoutExt}}/stack\""));
for (const environment of ["darwin/amd64", "darwin/arm64", "linux/amd64", "linux/arm64"]) {
assert.ok(registry.includes(`- ${environment}`));
}
assert.ok(!registry.includes("windows/"));
});

test("the checksum lock covers every archive and the registry revision", () => {
const revision = configuration.match(/^\s+ref: ([0-9a-f]{40})$/m)?.[1];
const expectedIds = targets.map(
(target) =>
`github_release/github.com/stack-sh/cli/v0.3.0/stack-v0.3.0-${target}.tar.gz`,
);
expectedIds.push(
`registries/github_content/github.com/stack-sh/cli/${revision}/aqua/registry.yaml`,
);

assert.deepEqual(
checksums.checksums.map(({ id }) => id).sort(),
expectedIds.sort(),
);
for (const entry of checksums.checksums) {
assert.match(entry.checksum, /^[A-F0-9]+$/);
assert.equal(entry.algorithm, entry.id.startsWith("registries/") ? "sha512" : "sha256");
}
});
7 changes: 7 additions & 0 deletions scripts/distribution-contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ test("the activated Homebrew channel cannot regress to planned", () => {
assert.throws(() => validateDistributionContract(candidate, cargoToml), /homebrew state must be available/);
});

test("the activated Aqua channel cannot regress to planned", () => {
const candidate = changed((value) => {
value.channels.find(({ id }) => id === "aqua").state = "planned";
});
assert.throws(() => validateDistributionContract(candidate, cargoToml), /aqua state must be available/);
});

test("an unactivated package-manager channel cannot become available", () => {
const candidate = changed((value) => {
value.channels.find(({ id }) => id === "cargo").state = "available";
Expand Down
7 changes: 4 additions & 3 deletions scripts/validate-distribution-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const expectedTargets = [
"x86_64-unknown-linux-gnu",
];
const expectedChannels = ["aqua", "cargo", "github-release", "homebrew", "self-update"];
const availableChannels = new Set(["github-release", "homebrew"]);
const availableChannels = new Set(["aqua", "github-release", "homebrew"]);
const requiredArchiveEntries = ["LICENSE", "NOTICE", "THIRD_PARTY_LICENSES.md", "stack"];
const requiredUnsupportedTerms = ["32-bit", "BSD", "Windows", "musl"];
const requiredActivationTerms = ["Cargo package version", "SBOMs", "provenance", "stack --version"];
Expand Down Expand Up @@ -57,8 +57,9 @@ export function validateDistributionContract(contract, cargoToml) {
invariant(
contract.availability?.message?.includes("Stack CLI 0.3.0") &&
contract.availability.message.includes("GitHub Releases") &&
contract.availability.message.includes("Homebrew"),
"availability message must identify the verified stable GitHub release and Homebrew channel",
contract.availability.message.includes("Homebrew") &&
contract.availability.message.includes("Aqua"),
"availability message must identify the verified stable GitHub release, Homebrew, and Aqua channels",
);
invariant(
contract.product.currentSourceVersion === cargoValue(cargoToml, "version"),
Expand Down
5 changes: 5 additions & 0 deletions tests/aqua/aqua-checksums.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
"id": "github_release/github.com/stack-sh/cli/v0.3.0/stack-v0.3.0-x86_64-unknown-linux-gnu.tar.gz",
"checksum": "6C9AB21D96FC6FB0A12A6FF4F7F56103D8DD8513AC85C8D30AB6E072C5C6A7D9",
"algorithm": "sha256"
},
{
"id": "registries/github_content/github.com/stack-sh/cli/42702cda91a4156901b9a601bd143c43dcf05766/aqua/registry.yaml",
"checksum": "FCF882176F20C6C85A07B848935633EC45F19B0A04A7010E14D051A71EAD5AAC1A6BE2F60B4F730D72821DFB293CF3CEFCE92A680059A3259D8FD85A32A3D4BF",
"algorithm": "sha512"
}
]
}
7 changes: 5 additions & 2 deletions tests/aqua/aqua-policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

registries:
- name: stack-sh
type: local
path: ../../aqua/registry.yaml
type: github_content
repo_owner: stack-sh
repo_name: cli
ref: 'Version == "42702cda91a4156901b9a601bd143c43dcf05766"'
path: aqua/registry.yaml
packages:
- name: stack-sh/cli
registry: stack-sh
Expand Down
7 changes: 5 additions & 2 deletions tests/aqua/aqua.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ checksum:
- all
registries:
- name: stack-sh
type: local
path: ../../aqua/registry.yaml
type: github_content
repo_owner: stack-sh
repo_name: cli
ref: 42702cda91a4156901b9a601bd143c43dcf05766
path: aqua/registry.yaml
packages:
- name: stack-sh/cli@v0.3.0
registry: stack-sh