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
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"version": "0.1.0"
},
"schemaVersion": "1.1.0",
"shardHash": "5150ae59ab5b77d17d616c92d89619dc93ffab501fce31b711c357720a932467"
"shardHash": "24063d448ed63b33cb2698dc9ceb4575e508d1893ba652b600aa02ec7e9b30cc"
}
2 changes: 1 addition & 1 deletion .derived/codebase-index/by-spec/001-cli-mcp-thesis.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@
"specStatus": "approved"
},
"schemaVersion": "1.1.0",
"shardHash": "6d5733b1b155ee7432d710a1558c55336777afbd6c2f325c54b709a207d33403"
"shardHash": "b43ccdb7944f7be531e44e3ddba623598f36ebb2654f363cb9c89b0921d181d4"
}
2 changes: 1 addition & 1 deletion .derived/codebase-index/by-spec/002-crate-scaffold.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,5 @@
"specStatus": "approved"
},
"schemaVersion": "1.1.0",
"shardHash": "c3dc2fca3ab2f6819289a8b5e27199169855bce3389d810dfd8b87d9fee7acc8"
"shardHash": "02af7cee124cbb9979d72cb8022a06bbc3632b672553c04b016e0fac8d6ecdf2"
}
2 changes: 1 addition & 1 deletion .derived/codebase-index/by-spec/003-auth-api-client.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@
"specStatus": "approved"
},
"schemaVersion": "1.1.0",
"shardHash": "322881dfb8679f2c3bcb478e841ddd668c73a599587eb7f55140b5a372e11c9b"
"shardHash": "aa4f92f23e82eeb882a12fbb6a165d505584d8e9ce454db397d3526a9dabd473"
}
2 changes: 1 addition & 1 deletion .derived/codebase-index/by-spec/004-governance-verbs.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
"specStatus": "approved"
},
"schemaVersion": "1.1.0",
"shardHash": "08ad0ffb9d6ed4b83972d0a82befdb156e73abad7ace2fd4e87e93e7c895c8a9"
"shardHash": "ccc353fc6d80c1ef4980f693a287abdd6f42bdd8ed622864c779cd5ccbf1e740"
}
2 changes: 1 addition & 1 deletion .derived/codebase-index/by-spec/005-mcp-server.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
"specStatus": "approved"
},
"schemaVersion": "1.1.0",
"shardHash": "1eae84bbae7e6a80af5ea54ff5c22132af97ca52a6faa540fe14e205f682cee4"
"shardHash": "b1ecba27b7769327ccab653070a957e5be1224e1fe0cdc86e0288a12c86d2b39"
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
"specStatus": "approved"
},
"schemaVersion": "1.1.0",
"shardHash": "86726ee96b3b570bce68cddf8d6c1ef11caa5e5b2154a4999ddfd9c4f24d0bad"
"shardHash": "2270f95ef02870d652785f7eadc2bbec80b5c261adb92195c369af4b490eff08"
}
49 changes: 49 additions & 0 deletions .derived/codebase-index/by-spec/007-release-distribution.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"mapping": {
"dependsOn": [
"002-crate-scaffold"
],
"implementingPaths": [
{
"path": ".github/workflows/release.yml",
"source": "spec-edge"
},
{
"path": "install.sh",
"source": "spec-edge"
}
],
"resolvedUnits": [
{
"locations": [
{
"file": ".github/workflows/release.yml"
}
],
"ownership": true,
"sourceField": "establishes",
"unit": {
"kind": "file",
"path": ".github/workflows/release.yml"
}
},
{
"locations": [
{
"file": "install.sh"
}
],
"ownership": true,
"sourceField": "establishes",
"unit": {
"kind": "file",
"path": "install.sh"
}
}
],
"specId": "007-release-distribution",
"specStatus": "approved"
},
"schemaVersion": "1.1.0",
"shardHash": "775d293277f36d26bd48854630315b34d9525eca17f5a4504b42528fc7d0deae"
}
2 changes: 1 addition & 1 deletion .derived/spec-registry/by-spec/001-cli-mcp-thesis.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
"summary": "The successor to OPC (the retired Tauri desktop cockpit): a single binary named statecraft that exposes the platform's governance verbs twice, as CLI subcommands for humans and as an MCP server for agents. The MCP face is the product's genuinely unique surface: any agent (Claude Code first) operates natively under Statecraft governance, requesting approvals, checking spec-code coupling, and triggering factory stages. This is milestone M4 in the Statecraft ladder; the spec records the thesis and the decided constraints so the repo is born governed ahead of its build.\n",
"title": "statecraft-cli: one binary, two faces (CLI verbs + MCP server)"
},
"shardHash": "a58ad16200ff335e20b8daa248e375284e48b2c62b668b75ce5f9cbb211f4862",
"shardHash": "44bcacd7382fc9a9245569ebebc1e3037e4b64b9ae7e3acbe82b69b8f2fe452a",
"specVersion": "1.1.0"
}
2 changes: 1 addition & 1 deletion .derived/spec-registry/by-spec/002-crate-scaffold.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@
"summary": "The Rust crate for the single binary named statecraft: clap-based command tree, layered configuration (flags > env > config file), structured output discipline (human tables on TTY, JSON with --output json), and a CI workflow (fmt, clippy -D warnings, test, release build). No network calls yet; spec 003 adds auth and the API client. After this spec, `cargo install --path .` yields a binary whose skeleton every later verb hangs off.\n",
"title": "The statecraft binary: crate scaffold, config, CI"
},
"shardHash": "8576aef9f1f550842270168da67a5fc80a2c886fdbecac54781f3963a854f3d2",
"shardHash": "60231e5bb6a52376587bd94a8625242cd040ccc2de120f0f28500b53a3f8deeb",
"specVersion": "1.1.0"
}
2 changes: 1 addition & 1 deletion .derived/spec-registry/by-spec/003-auth-api-client.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@
"summary": "The binary learns to authenticate against a Statecraft control plane and speak its API. Auth v1 is a browser-assisted session-cookie handoff (the control plane's chassis auth is cookie based, and the embedded rauthy exposes OIDC; the exact mechanism is DECIDE-AT-IMPLEMENTATION between OAuth device-flow-style polling and a localhost callback, constrained below). Tokens/cookies are stored in a 0600 credentials file, never in the config file. An api module gives every later verb a typed, authenticated request path with consistent error mapping.\n",
"title": "Auth + control-plane API client"
},
"shardHash": "46b6c05e0ca45e03fc21761df69431c89464e6a11e5e9088c4f76bf97e65cd59",
"shardHash": "a4ebb7d479a77aff10fad99fd38045e42563fe690793de244ab403a0bcf9297c",
"specVersion": "1.1.0"
}
2 changes: 1 addition & 1 deletion .derived/spec-registry/by-spec/004-governance-verbs.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@
"summary": "The CLI face becomes useful: the stub commands from spec 002 gain real implementations over the API client, mirroring the control plane's tenants (statecraft spec 004), factory (spec 005), and fleet (spec 006) services. Every verb has a stable JSON output shape, because spec 005 exposes these same verbs as MCP tools and the JSON is the shared contract between both faces.\n",
"title": "Governance verbs v1: tenants, stamps, fleet"
},
"shardHash": "c8282f2410f0072e4a10578af7d13cff12921806b154d05fe5144ef07cd12148",
"shardHash": "d4bbc4533f622959b2495d1d337e09754e840a698e52c3e26df3bba6d7a84a4b",
"specVersion": "1.1.0"
}
2 changes: 1 addition & 1 deletion .derived/spec-registry/by-spec/005-mcp-server.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
"summary": "Milestone M4's core: `statecraft mcp` runs a Model Context Protocol server over stdio exposing the governance verbs as tools, so a coding agent (Claude Code first) operates under Statecraft governance natively: listing tenants, launching and watching stamps, inspecting and operating fleets, all with the same auth, the same guards, and the same JSON shapes as the CLI face. The MCP face is not a privileged side door: it calls the identical verb layer from spec 004, and destructive guards (explicit posture, confirm-name) pass through to the agent verbatim.\n",
"title": "The MCP face: statecraft mcp (stdio server)"
},
"shardHash": "3b2d09c65623c532afd2e5329891a969ea1f4efe279772974e0bf832a6220252",
"shardHash": "9f949920b71fe9c60819a798101e42314e01354db95f5b3fcc98478976f13a4f",
"specVersion": "1.1.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
"summary": "The upgrade half of the 2026-07-14 packaging decision: templates stay small because the chassis ships as versioned npm packages (enrahitu spec 018), and upgrading a stamped app is a verb, not a migration project. `statecraft template upgrade`, run in a stamped app checkout, reads template.toml, bumps the chassis package pins, applies template-shipped codemods, runs the contract verify verb, and commits on a branch. The CLI orchestrates; all structure knowledge stays in the template and its packages. This verb is the boundary that keeps the CLI from ever becoming a build daemon.\n",
"title": "statecraft template upgrade: chassis upgrades as a governed verb"
},
"shardHash": "5c1f7935e130ca8d1a42dfe060bf18f01e8a19f24be733760bc9d37cfc98f646",
"shardHash": "7ad323fce9c96006b9804b90069aa11eb436cdd447d7120cf09b524f0fb4375c",
"specVersion": "1.1.0"
}
36 changes: 36 additions & 0 deletions .derived/spec-registry/by-spec/007-release-distribution.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"record": {
"created": "2026-07-22",
"dependsOn": [
"002-crate-scaffold"
],
"establishes": [
{
"kind": "file",
"path": ".github/workflows/release.yml"
},
{
"kind": "file",
"path": "install.sh"
}
],
"id": "007-release-distribution",
"implementation": "in-progress",
"sectionHeadings": [
"007: Release distribution",
"1. Purpose",
"2. Territory",
"3. Behavior: the pipeline",
"4. Behavior: install.sh",
"5. Release procedure",
"6. Acceptance",
"7. Out of scope"
],
"specPath": "specs/007-release-distribution/spec.md",
"status": "approved",
"summary": "Closes the gap between \"builds from source\" and \"installable product\": pushing a v<semver> tag builds a per-triple archive for the five supported targets, attaches checksums, a CycloneDX SBOM, and a SLSA build-provenance attestation to a GitHub Release, and install.sh (curl | sh) consumes the archives. Mirrors the spec-spine release pipeline (the family precedent this repo's own CI already consumes) plus the fail-fast version guard OPC's release workflow learned the hard way. No registry publishing: the crate is a binary product, not a library.\n",
"title": "Release distribution: tag-gated prebuilt binaries and installer"
},
"shardHash": "905bd5059b673e380bf011d965d61dbc18be5ec2821715c5aa110e76b6cbdda1",
"specVersion": "1.1.0"
}
164 changes: 164 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Tag-gated prebuilt-binary release (spec 007). Pushing a `v*` tag builds a
# per-triple archive (with a .sha256 sidecar) for each of the five supported
# targets, generates a per-target CycloneDX SBOM and a SLSA build-provenance
# attestation, and attaches everything to the GitHub Release. install.sh
# (curl | sh) consumes the archives.
#
# The version guard dies fast (zero build minutes) when the pushed tag does
# not match the committed Cargo.toml version, so a release can never ship
# assets whose --version output disagrees with its envelope.
#
# Four triples build natively on a matching-arch runner; x86_64-apple-darwin
# cross-compiles on the Apple Silicon runner (Xcode ships the x86_64 SDK)
# because the Intel macos-13 runner is deprecated and queues badly.
#
# Actions are pinned to a full commit SHA with a version comment (same rule
# as ci.yml) so an upstream tag rewrite cannot alter a release build.
# Re-running a tag is idempotent: the publish step updates the same release.
name: release

on:
push:
tags: ["v*"]

permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
version-guard:
name: version guard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Tag matches the committed Cargo.toml version
run: |
set -euo pipefail
tag_version="${GITHUB_REF_NAME#v}"
crate_version="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "statecraft-cli") | .version')"
echo "tag=${GITHUB_REF_NAME} crate=${crate_version}"
if [ "$tag_version" != "$crate_version" ]; then
echo "::error::tag ${GITHUB_REF_NAME} does not match Cargo.toml version ${crate_version}; bump the version, merge, then retag"
exit 1
fi

build:
name: build / ${{ matrix.triple }}
needs: version-guard
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
- { os: ubuntu-24.04-arm, triple: aarch64-unknown-linux-gnu }
# x86_64-apple-darwin cross-compiles on the Apple Silicon runner.
- { os: macos-latest, triple: x86_64-apple-darwin }
- { os: macos-latest, triple: aarch64-apple-darwin }
- { os: windows-latest, triple: x86_64-pc-windows-msvc }
runs-on: ${{ matrix.os }}
# id-token + attestations are for the build-provenance attestation over
# each archive; nothing in this job writes repo contents.
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Add target (no-op if native)
run: rustup target add ${{ matrix.triple }}
- name: Build release binary
run: cargo build --release --locked --bin statecraft --target ${{ matrix.triple }}

- name: Stage archive contents
shell: bash
run: |
set -euo pipefail
EXT=""; [ "${{ runner.os }}" = "Windows" ] && EXT=".exe"
mkdir -p staging
cp "target/${{ matrix.triple }}/release/statecraft${EXT}" staging/
cp LICENSE README.md staging/
echo "ARCHIVE_BASE=statecraft-${GITHUB_REF_NAME}-${{ matrix.triple }}" >> "$GITHUB_ENV"

- name: Package (tar.gz + sha256) for Unix
if: runner.os != 'Windows'
shell: bash
run: |
set -euo pipefail
tar -C staging -czf "${ARCHIVE_BASE}.tar.gz" .
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "${ARCHIVE_BASE}.tar.gz" > "${ARCHIVE_BASE}.tar.gz.sha256"
else
shasum -a 256 "${ARCHIVE_BASE}.tar.gz" > "${ARCHIVE_BASE}.tar.gz.sha256"
fi
cat "${ARCHIVE_BASE}.tar.gz.sha256"
echo "ARCHIVE_FILE=${ARCHIVE_BASE}.tar.gz" >> "$GITHUB_ENV"

- name: Package (zip + sha256) for Windows
if: runner.os == 'Windows'
shell: pwsh
run: |
$base = $env:ARCHIVE_BASE
Compress-Archive -Path staging/* -DestinationPath "$base.zip" -Force
$hash = (Get-FileHash "$base.zip" -Algorithm SHA256).Hash.ToLower()
"$hash $base.zip" | Out-File -Encoding ascii -NoNewline "$base.zip.sha256"
Get-Content "$base.zip.sha256"
Add-Content -Path $env:GITHUB_ENV -Value "ARCHIVE_FILE=$base.zip"

# Supply-chain evidence (spec 007 §3.4): per-target CycloneDX SBOM read
# from the committed Cargo.lock, then a provenance attestation whose
# subject is the archive itself.
- name: Generate per-target CycloneDX SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
path: .
format: cyclonedx-json
output-file: ${{ env.ARCHIVE_BASE }}.cdx.json
upload-artifact: false
- name: Fail closed if the SBOM has no components
shell: bash
run: |
set -euo pipefail
n="$(jq '.components | length' "${ARCHIVE_BASE}.cdx.json")"
echo "SBOM components: $n"
if [ "$n" -eq 0 ]; then
echo "::error::refusing to ship an SBOM with zero components (cataloger regression?)"
exit 1
fi
- name: Attest build provenance for the archive
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: ${{ env.ARCHIVE_FILE }}

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: archive-${{ matrix.triple }}
path: |
statecraft-*.tar.gz
statecraft-*.tar.gz.sha256
statecraft-*.zip
statecraft-*.zip.sha256
statecraft-*.cdx.json
if-no-files-found: error

publish:
name: publish GitHub Release
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: dist
pattern: archive-*
merge-multiple: true
- name: List release assets
run: ls -la dist
- name: Create or update the GitHub Release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
files: dist/*
fail_on_unmatched_files: true
generate_release_notes: true
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,27 @@ desktop app is retired, the governance verbs live on here.

## Status

Born governed, pre-code. This is milestone M4 in the Statecraft ladder;
the thesis and decided constraints (binary name, Rust, stdio MCP,
Apache-2.0, no TUI) live in `specs/001-cli-mcp-thesis/spec.md`. The crate
lands when M4 starts.
Milestone M4 in the Statecraft ladder, implemented: the crate scaffold
(002), auth + API client (003), the governance verbs (004), the MCP
stdio server (005), and the template upgrade verb (006). The thesis and
decided constraints (binary name, Rust, stdio MCP, Apache-2.0, no TUI)
live in `specs/001-cli-mcp-thesis/spec.md`.

## Install

Prebuilt binaries for macOS and Linux (spec 007):

```sh
curl -fsSL https://raw.githubusercontent.com/statecrafting/statecraft-cli/main/install.sh | sh
```

Windows: download the `.zip` from the
[Releases](https://github.com/statecrafting/statecraft-cli/releases)
page. Every release archive ships a `.sha256` sidecar, a CycloneDX
SBOM, and a SLSA build-provenance attestation; the installer verifies
the checksum and (best-effort) the attestation. From source:
`cargo install --git https://github.com/statecrafting/statecraft-cli`
(rustls only, no OpenSSL).

## Governance

Expand Down
Loading
Loading