Shared memory and context tools for agentic work.
Code Rooms
name: Release
on:
workflow_dispatch:
push:
tags:
- "v*"
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: "0"
BINARY_NAME: m1nd-mcp
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
tag-guard:
name: Exact immutable tag guard
runs-on: ubuntu-latest
outputs:
version: ${{ steps.identity.outputs.version }}
crate_core_version: ${{ steps.identity.outputs.crate_core_version }}
crate_control_version: ${{ steps.identity.outputs.crate_control_version }}
crate_ingest_version: ${{ steps.identity.outputs.crate_ingest_version }}
crate_mcp_version: ${{ steps.identity.outputs.crate_mcp_version }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
fetch-depth: 0
persist-credentials: false
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
node-version: "22"
- id: identity
name: Bind tag, package version, and exact commit
shell: bash
run: |
case "${GITHUB_REF}" in
refs/tags/v*) ;;
*)
echo "Release workflows must run against an existing v* tag; got ${GITHUB_REF}." >&2
exit 1
;;
esac
VERSION="${GITHUB_REF_NAME#v}"
PACKAGE_VERSION="$(node -p "require('./package.json').version")"
test "${VERSION}" = "${PACKAGE_VERSION}"
test "$(git rev-parse HEAD)" = "${GITHUB_SHA}"
test "$(git rev-parse "${GITHUB_REF}^{commit}")" = "${GITHUB_SHA}"
git fetch --no-tags --prune origin \
'+refs/heads/main:refs/remotes/origin/main'
MAIN_HEAD="$(git rev-parse refs/remotes/origin/main)"
if [ "${GITHUB_SHA}" != "${MAIN_HEAD}" ]; then
echo "Release commit ${GITHUB_SHA} is not the exact origin/main head ${MAIN_HEAD}." >&2
fi
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
EXPECTED_VERSION="${VERSION}" python3 - <<'PY' >> "${GITHUB_OUTPUT}"
import json
import os
import subprocess
metadata = json.loads(subprocess.check_output(
["cargo", "metadata", "--locked", "--no-deps", "--format-version", "1"]
))
versions = {package["name"]: package["version"] for package in metadata["packages"]}
expected = os.environ["EXPECTED_VERSION"]
for name in ("m1nd-core", "m1nd-ingest", "m1nd-mcp"):
if versions.get(name) != expected:
raise SystemExit(f"{name} version {versions.get(name)!r} != release {expected}")
for name in ("m1nd-core", "m1nd-control", "m1nd-ingest", "m1nd-mcp"):
print(f"crate_{name.removeprefix('m1nd-')}_version={versions[name]}")
PY
- name: Refuse private, operator-only, secret, or cache paths in the candidate commit
python3 scripts/m1nd10_candidate_source_guard.py \
--repo . \
--revision "${GITHUB_SHA}"
- name: Scan the complete immutable candidate history for secrets
GITLEAKS_VERSION: 8.30.1
GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
curl --proto '=https' --tlsv1.2 --location --silent --show-error --fail \
--output gitleaks.tar.gz \
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
echo "${GITLEAKS_SHA256} gitleaks.tar.gz" | sha256sum --check --strict
tar --extract --gzip --file gitleaks.tar.gz gitleaks
./gitleaks git --redact --no-banner --exit-code 1 .
rm -f gitleaks gitleaks.tar.gz
- name: Probe immutable public release identities without passing a token to repository code
RELEASE_REPOSITORY: ${{ github.repository }}
RELEASE_REPOSITORY_PRIVATE: ${{ github.event.repository.private }}
RELEASE_TAG: ${{ github.ref_name }}
RELEASE_VERSION: ${{ steps.identity.outputs.version }}
CRATE_CORE_VERSION: ${{ steps.identity.outputs.crate_core_version }}
CRATE_CONTROL_VERSION: ${{ steps.identity.outputs.crate_control_version }}
CRATE_INGEST_VERSION: ${{ steps.identity.outputs.crate_ingest_version }}
CRATE_MCP_VERSION: ${{ steps.identity.outputs.crate_mcp_version }}
python3 - <<'PY'
import re
import urllib.error
import urllib.parse
import urllib.request
SEMVER = re.compile(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?")
repository = os.environ["RELEASE_REPOSITORY"]
repository_private = os.environ["RELEASE_REPOSITORY_PRIVATE"]
tag = os.environ["RELEASE_TAG"]
release_version = os.environ["RELEASE_VERSION"]
crates = {
"m1nd-core": os.environ["CRATE_CORE_VERSION"],
"m1nd-control": os.environ["CRATE_CONTROL_VERSION"],
"m1nd-ingest": os.environ["CRATE_INGEST_VERSION"],
"m1nd-mcp": os.environ["CRATE_MCP_VERSION"],
}
if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repository):
raise SystemExit("invalid GitHub repository identity")
if repository_private != "false":
raise SystemExit(
"unauthenticated GitHub release nonexistence is valid only for a public repository"
)
if tag != f"v{release_version}" or not SEMVER.fullmatch(release_version):
raise SystemExit("invalid release tag identity")
if any(not SEMVER.fullmatch(version) for version in crates.values()):
raise SystemExit("invalid crate version identity")
class RefuseRedirects(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
raise urllib.error.HTTPError(req.full_url, code, "redirect refused", headers, fp)
opener = urllib.request.build_opener(RefuseRedirects())
def status_for(url):
request = urllib.request.Request(
url,
headers={"Accept": "application/json", "User-Agent": "m1nd-public-release-guard/1"},
method="GET",
try:
with opener.open(request, timeout=20) as response:
payload = response.read(2 * 1024 * 1024 + 1)
status = int(response.status)
except urllib.error.HTTPError as error:
payload = error.read(2 * 1024 * 1024 + 1)
status = int(error.code)
except urllib.error.URLError as error:
raise SystemExit(f"public release authority probe failed closed: {error.reason}")
if len(payload) > 2 * 1024 * 1024:
raise SystemExit("public release authority response is oversized")
return status
github_url = (
f"https://api.github.com/repos/{repository}/releases/tags/"
f"{urllib.parse.quote(tag, safe='')}"
github_status = status_for(github_url)
if github_status == 200:
raise SystemExit(f"GitHub release {repository}@{tag} already exists")
if github_status != 404:
raise SystemExit(f"GitHub release nonexistence is NOT_PROVEN (HTTP {github_status})")
npm_identity = urllib.parse.quote("@maxkle1nz/m1nd", safe="")
npm_url = (
f"https://registry.npmjs.org/{npm_identity}/"
f"{urllib.parse.quote(release_version, safe='')}"
npm_status = status_for(npm_url)
if npm_status == 200:
raise SystemExit(f"npm package @maxkle1nz/m1nd@{release_version} already exists")
if npm_status != 404:
raise SystemExit(f"npm package nonexistence is NOT_PROVEN (HTTP {npm_status})")
for name, version in crates.items():
crate_url = (
"https://crates.io/api/v1/crates/"
f"{urllib.parse.quote(name, safe='')}/{urllib.parse.quote(version, safe='')}"
crate_status = status_for(crate_url)
if crate_status == 200:
print(
f"{name}@{version} already exists; exact checksum recovery "
"must pass against the signed candidate before release"
elif crate_status != 404:
f"{name}@{version} registry state is NOT_PROVEN (HTTP {crate_status})"
ui-artifact:
name: Rebuild and seal the only release UI input
needs: tag-guard
timeout-minutes: 20
sha256: ${{ steps.ui_identity.outputs.sha256 }}
cache: npm
cache-dependency-path: m1nd-ui/package-lock.json
- name: Test and rebuild UI from the locked source tree
working-directory: m1nd-ui
npm ci
npm test
npm run lint:soft
rm -rf dist
npm run build
- id: ui_identity
name: Seal UI tree, lockfile, source commit, and tool versions
rm -rf release-ui
mkdir -p release-ui
cp -R m1nd-ui/dist release-ui/dist
UI_SHA256="$(python3 scripts/m1nd10_ui_bundle.py create \
--dist release-ui/dist \
--package-json m1nd-ui/package.json \
--package-lock m1nd-ui/package-lock.json \
--commit "${GITHUB_SHA}" \
--node-version "$(node --version)" \
--npm-version "$(npm --version)" \
--output release-ui/UI-BUNDLE-PROVENANCE.json)"
echo "sha256=${UI_SHA256}" >> "${GITHUB_OUTPUT}"
python3 scripts/m1nd10_ui_bundle.py verify \
--provenance release-ui/UI-BUNDLE-PROVENANCE.json \
--expected-commit "${GITHUB_SHA}" \
--expected-sha256 "${UI_SHA256}"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
name: m1nd-ui-bundle-${{ github.sha }}
path: release-ui/
if-no-files-found: error
retention-days: 14
npm-artifact:
name: Pack the exact npm package once
timeout-minutes: 10
- name: Pack and validate the immutable npm tarball
mkdir -p release-npm
npm pack --json --pack-destination release-npm > npm-pack.json
EXPECTED_VERSION="${{ needs.tag-guard.outputs.version }}" node - <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const records = JSON.parse(fs.readFileSync("npm-pack.json", "utf8"));
if (!Array.isArray(records) || records.length !== 1) {
throw new Error(`npm pack produced ${Array.isArray(records) ? records.length : "invalid"} records`);
const record = records[0];
if (record.name !== "@maxkle1nz/m1nd" || record.version !== process.env.EXPECTED_VERSION) {
throw new Error(`npm pack identity mismatch: ${record.name}@${record.version}`);
const tarballs = fs.readdirSync("release-npm").filter((name) => name.endsWith(".tgz"));
if (tarballs.length !== 1 || path.basename(record.filename) !== tarballs[0]) {
throw new Error("npm pack did not produce exactly the declared tarball");
NODE
name: m1nd-npm-package-${{ github.sha }}
path: release-npm/*.tgz
release-gate:
name: Candidate source gate
needs: [tag-guard, ui-artifact]
timeout-minutes: 90
M1ND_RELEASE_UI_REQUIRED: "1"
M1ND_EXPECTED_UI_BUNDLE_SHA256: ${{ needs.ui-artifact.outputs.sha256 }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
path: release-ui
- name: Install and verify the sealed UI input
rm -rf m1nd-ui/dist
cp -R release-ui/dist m1nd-ui/dist
--dist m1nd-ui/dist \
--expected-sha256 "${M1ND_EXPECTED_UI_BUNDLE_SHA256}"
components: clippy,rustfmt
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
key: release-source-gate
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
python-version: "3.12"
- name: Verify frozen ratified contracts
printf '%s %s\n' \
'2745560daf6e5cf6237b84663f895e81e2c4979de4190dfef649b032b680f87b' \
'docs/M1ND-10-PRD.md' \
'd5bc29776f516c300cb1a0668f0a53844286f75395fd0ad1e875b52ea3a067a5' \
'docs/M1ND-10-UML.md' | sha256sum --check --strict
- name: Rust source and release gates
cargo check --locked --workspace --all-targets
cargo test --locked --workspace --all-targets
cargo clippy --locked --workspace --all-targets -- -D warnings
cargo fmt --all --check
cargo build --locked --release --workspace
- name: Host pack, update, and rollback gates
npm run m1nd:pack-check
npm run m1nd:pack-routing-check
- name: Python proof harnesses
run: python3 -m unittest discover -s tests -p 'test_*.py' -v
- name: Cross-language canonical release contract vectors
python3 scripts/m1nd10_release_candidate.py verify-canonical-vectors \
--vectors tests/fixtures/M1ND10-CANONICAL-VECTORS.json
node - <<'NODE'
const path = require("path");
const { verifyCanonicalReleaseVectors } = require("./npm/lib/cli");
const result = verifyCanonicalReleaseVectors(
path.resolve("tests/fixtures/M1ND10-CANONICAL-VECTORS.json")
);
if (!result.ok) process.exit(1);
console.log(result.status);
crate-artifact:
name: Package each crates.io crate exactly once
needs: [tag-guard, ui-artifact, release-gate]
timeout-minutes: 30
path: ${{ runner.temp }}/release-ui
key: release-crate-package
- name: Stage the exact sealed UI inside the m1nd-mcp package source
test ! -e m1nd-mcp/ui-dist
test ! -e m1nd-mcp/ui-package.json
mkdir -p m1nd-mcp/ui-dist
cp -R "${RUNNER_TEMP}/release-ui/dist/." m1nd-mcp/ui-dist/
cp m1nd-ui/package.json m1nd-mcp/ui-package.json
--dist m1nd-mcp/ui-dist \
--package-json m1nd-mcp/ui-package.json \
--provenance "${RUNNER_TEMP}/release-ui/UI-BUNDLE-PROVENANCE.json" \
git diff --quiet
git diff --cached --quiet
untracked = subprocess.check_output(
["git", "ls-files", "--others", "--exclude-standard"], text=True
).splitlines()
ignored = subprocess.check_output(
[
"git",
"ls-files",
"--others",
"--ignored",
"--exclude-standard",
"--",
"m1nd-mcp",
],
text=True,
package_overlay = sorted(set(untracked + ignored))
unexpected = [
path for path in package_overlay
if path != "m1nd-mcp/ui-package.json"
and not path.startswith("m1nd-mcp/ui-dist/")
]
if unexpected:
raise SystemExit(f"uncontrolled package-source files: {unexpected}")
if "m1nd-mcp/ui-dist/index.html" not in package_overlay:
raise SystemExit("sealed UI index is not visible to Cargo packaging")
- name: Package the four-crate workspace overlay exactly once
cargo package --locked --allow-dirty --no-verify \
-p m1nd-core \
-p m1nd-control \
-p m1nd-ingest \
-p m1nd-mcp
- name: Inspect and stage the four exact Cargo package artifacts
mkdir -p release-crates
cp "target/package/m1nd-core-${{ needs.tag-guard.outputs.crate_core_version }}.crate" release-crates/
cp "target/package/m1nd-control-${{ needs.tag-guard.outputs.crate_control_version }}.crate" release-crates/
cp "target/package/m1nd-ingest-${{ needs.tag-guard.outputs.crate_ingest_version }}.crate" release-crates/
cp "target/package/m1nd-mcp-${{ needs.tag-guard.outputs.crate_mcp_version }}.crate" release-crates/
python3 scripts/m1nd10_crates_io_upload.py inspect \
--crate "release-crates/m1nd-core-${{ needs.tag-guard.outputs.crate_core_version }}.crate" \
--expected-name m1nd-core \
--expected-version "${{ needs.tag-guard.outputs.crate_core_version }}" \
--expected-commit "${GITHUB_SHA}" >/dev/null
--crate "release-crates/m1nd-control-${{ needs.tag-guard.outputs.crate_control_version }}.crate" \
--expected-name m1nd-control \
--expected-version "${{ needs.tag-guard.outputs.crate_control_version }}" \
--crate "release-crates/m1nd-ingest-${{ needs.tag-guard.outputs.crate_ingest_version }}.crate" \
--expected-name m1nd-ingest \
--expected-version "${{ needs.tag-guard.outputs.crate_ingest_version }}" \
--crate "release-crates/m1nd-mcp-${{ needs.tag-guard.outputs.crate_mcp_version }}.crate" \
--expected-name m1nd-mcp \
--expected-version "${{ needs.tag-guard.outputs.crate_mcp_version }}" \
name: m1nd-cargo-packages-${{ github.sha }}
path: release-crates/*.crate
retention-days: 30
build:
name: Build once (${{ matrix.target }})
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
archive: m1nd-mcp-linux-x86_64.tar.gz
artifact: m1nd-mcp-linux-x86_64
raw: m1nd-mcp-linux-x86_64
- target: x86_64-apple-darwin
os: macos-15-intel
archive: m1nd-mcp-macos-x86_64.tar.gz
artifact: m1nd-mcp-macos-x86_64
raw: m1nd-mcp-macos-x86_64
- target: aarch64-apple-darwin
os: macos-latest
archive: m1nd-mcp-macos-aarch64.tar.gz
artifact: m1nd-mcp-macos-aarch64
raw: m1nd-mcp-macos-aarch64
- name: Install and verify the sole UI build input
targets: ${{ matrix.target }}
key: release-${{ matrix.target }}
- name: Build target exactly once
run: cargo build --locked --release --bin m1nd-mcp --target ${{ matrix.target }}
# macOS distribution gate: an unsigned, un-notarized 69MB binary makes the
# FIRST run on a user's Mac block in Gatekeeper verification (observed
# 2026-07-26: process stuck in uninterruptible `UE` state). Sign with
# Developer ID + notarize so the check is fast and trusted. Honest posture:
# when the Apple secrets are absent the step SKIPS loudly (today's
# behaviour, unsigned); when they are present any failure FAILS the build —
# it never silently ships an unsigned binary claiming to be signed.
- name: Sign and notarize the macOS runtime
if: runner.os == 'macOS'
APPLE_CERT_P12_BASE64: ${{ secrets.APPLE_CERT_P12_BASE64 }}
APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
APPLE_API_KEY_P8_BASE64: ${{ secrets.APPLE_API_KEY_P8_BASE64 }}
BIN_PATH: target/${{ matrix.target }}/release/m1nd-mcp
set -euo pipefail
if [ -z "${APPLE_CERT_P12_BASE64:-}" ] || [ -z "${APPLE_API_KEY_P8_BASE64:-}" ]; then
echo "::warning title=Unsigned macOS runtime::Apple signing secrets absent — shipping UNSIGNED. First run on a user Mac may block in Gatekeeper. Configure APPLE_* secrets to enable signing + notarization."
exit 0
# 1) ephemeral keychain holding only the Developer ID certificate
KEYCHAIN="$RUNNER_TEMP/m1nd-signing.keychain-db"
KEYCHAIN_PASS="$(uuidgen)"
security create-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN"
security set-keychain-settings -lut 3600 "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN"
printf '%s' "$APPLE_CERT_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security import "$RUNNER_TEMP/cert.p12" -k "$KEYCHAIN" -P "$APPLE_CERT_PASSWORD" \
-T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASS" "$KEYCHAIN" >/dev/null
security list-keychain -d user -s "$KEYCHAIN" login.keychain-db
rm -f "$RUNNER_TEMP/cert.p12"
# 2) sign with hardened runtime + secure timestamp (notarization requires
# both) and NO entitlements — deliberately.
#
# `keychain-access-groups` is a RESTRICTED entitlement. AMFI honours it
# only when an embedded provisioning profile authorizes it, and a raw
# Mach-O executable has nowhere to embed one: a profile lives at
# `Contents/embedded.provisionprofile` INSIDE a bundle. Apple states the
# rule in TN3137 (On Mac keychain APIs and implementations,
# § Implementation differences): the data-protection keychain access
# groups are built from code signing entitlements, and "These
# entitlements must be authorized by a provisioning profile. Your
# program needs an app-like bundle structure in which to embed that
# profile. This is standard for app and app extensions but not for
# command-line tools."
# v1.6.0 shipped the entitlement and the kernel SIGKILLed the product at
# launch while every signature check still passed — `codesign --verify
# --strict` ok, notarization Accepted, spctl "Notarized Developer ID",
# the entitlement provably on the bytes. The artifact smoke caught it:
# `--version` died with SIGKILL on both macOS legs. AMFI's reason, from
# the kernel log: "Code has restricted entitlements, but the validation
# of its code signature failed", with amfid reporting
# AppleMobileFileIntegrityError -413 "No matching profile found".
# Measured 2026-07-30 on the exact v1.6.0 artifact; the same bytes
# re-signed without the entitlement launch and print their version.
# So the custody entitlement CANNOT ride on the shipped raw binary. G9
# prerequisite P4 is owner-side — see build/README.md and
# docs/benchmarks/G9-CUSTODY-CEREMONY.md §1 P4.
codesign --force --timestamp --options runtime \
--sign "$APPLE_SIGNING_IDENTITY" "$BIN_PATH"
codesign --verify --strict --verbose=2 "$BIN_PATH"
# Refuse the regression in the direction that actually kills the product.
# A provisioning-profile-restricted entitlement on a raw executable leaves
# every downstream signature check green and the binary unlaunchable, so
# the ban has to be mechanical, not remembered.
if codesign -d --entitlements - "$BIN_PATH" 2>/dev/null \
| grep -qE 'keychain-access-groups|application-identifier|com\.apple\.developer\.'; then
echo "::error::signed binary claims a provisioning-profile-restricted entitlement — AMFI SIGKILLs a raw executable that does (build/README.md)"
# And prove the signed bytes LAUNCH, at the point of the decision. The
# runner is native for this target, so this costs one process — and it is
# the check that would have failed v1.6.0 in the signing step instead of
# two jobs downstream. It does not replace the artifact smoke, which
# exercises the archived bytes after a full round trip.
"$BIN_PATH" --version >/dev/null \
|| { echo "::error::the signed binary does not launch — see the restricted-entitlement note above"; exit 1; }
# 3) notarize (a bare executable is submitted zipped; stapling is not
# possible for a raw binary, so Gatekeeper resolves the ticket online)
printf '%s' "$APPLE_API_KEY_P8_BASE64" | base64 --decode > "$RUNNER_TEMP/AuthKey.p8"
ditto -c -k --keepParent "$BIN_PATH" "$RUNNER_TEMP/notarize.zip"
xcrun notarytool submit "$RUNNER_TEMP/notarize.zip" \
--key "$RUNNER_TEMP/AuthKey.p8" \
--key-id "$APPLE_API_KEY_ID" \
--issuer "$APPLE_API_ISSUER_ID" \
--wait --timeout 30m
rm -f "$RUNNER_TEMP/AuthKey.p8" "$RUNNER_TEMP/notarize.zip"
# 4) prove the shipped bytes carry a Developer ID signature
codesign -dv --verbose=4 "$BIN_PATH" 2>&1 | grep -q "Authority=Developer ID Application" \
|| { echo "signed binary does not carry a Developer ID authority" >&2; exit 1; }
security delete-keychain "$KEYCHAIN" || true
echo "macOS runtime signed + notarized"
# The SECOND macOS artifact, and the only one allowed to carry the custody
# entitlement. The ordinary runtime above stays unentitled forever: AMFI
# SIGKILLs a raw executable that claims a restricted entitlement, which is
# what killed v1.6.0. Apple's own answer for exactly this case (Signing a
# daemon with a restricted entitlement) is an app-like bundle with the
# authorizing profile at Contents/embedded.provisionprofile — the road the
# owner ratified, docs/benchmarks/G9-PLATFORM-DECISION.md.
# The two artifacts are checked against OPPOSITE contracts and neither
# refusal is relaxed for the other: the runtime for the ABSENCE of a
# restricted entitlement plus a launch, this bundle for its PRESENCE plus a
# launch. The bundle is built from the bytes the build step already
# produced — copied, never rebuilt — and it is deliberately outside the
# signed candidate/release file set, the same posture the verified-updater
# receipts hold: the ordinary download is untouched by it.
# The profile is an owner input, not a repo file. It is generated once in
# the Apple Developer portal and held in APPLE_CUSTODY_PROFILE_BASE64; with
# that secret absent this step SKIPS LOUDLY and publishes nothing, because
# a bundle without a profile is killed exactly like the raw binary was and
# a silently-unentitled "ceremony" artifact is worse than no artifact.
- name: Package, sign, and prove the entitled custody-ceremony bundle
id: custody-bundle
APPLE_CUSTODY_PROFILE_BASE64: ${{ secrets.APPLE_CUSTODY_PROFILE_BASE64 }}
ENTITLEMENTS: build/m1nd-mcp.entitlements.plist
APP_BUNDLE: ${{ runner.temp }}/m1nd-custody-ceremony.app
PROFILE: ${{ runner.temp }}/custody.provisionprofile
PROFILE_PLIST: ${{ runner.temp }}/custody-profile.plist
MATRIX_ARTIFACT: ${{ matrix.artifact }}
RELEASE_VERSION: ${{ needs.tag-guard.outputs.version }}
if [ -z "${APPLE_CUSTODY_PROFILE_BASE64:-}" ] \
|| [ -z "${APPLE_CERT_P12_BASE64:-}" ] \
|| [ -z "${APPLE_API_KEY_P8_BASE64:-}" ]; then
echo "::warning title=No custody-ceremony bundle::APPLE_CUSTODY_PROFILE_BASE64 or the Apple signing secrets are absent — this release publishes NO entitled ceremony artifact. The ordinary m1nd-mcp runtime is unaffected and stays unentitled; G9 prerequisite P4 stays unsatisfied (docs/benchmarks/G9-CUSTODY-CEREMONY.md §1 P4)."
# 1) the owner's profile, decoded to its plist so it can be READ before
# anything is built, signed or published
printf '%s' "$APPLE_CUSTODY_PROFILE_BASE64" | base64 --decode > "$PROFILE"
security cms -D -i "$PROFILE" > "$PROFILE_PLIST"
# 2) refuse every profile that would produce a bundle the kernel kills,
# and derive the bundle identity FROM the entitlement instead of
# guessing it. Each refusal names what it read.
rm -rf "$APP_BUNDLE"
import plistlib
from datetime import datetime, timedelta, timezone
from pathlib import Path
def refuse(message: str) -> None:
# A failed release must say why where the operator looks first.
print(f"::error title=Custody-ceremony bundle refused::{message}")
raise SystemExit(1)
# The margin is a refusal to ship an artifact that is already
# effectively dead — not a promise about its shelf life. An embedded
# profile expires (Apple issues them for about a year) and the bundle
# stops launching the moment it does, so the artifact is only good
# until the date this step prints. Thirty days is the window in which
# the owner can notice, regenerate and re-tag without the ceremony
# surface ever being unavailable.
MARGIN_DAYS = 30
temp = Path(os.environ["RUNNER_TEMP"])
entitlements = plistlib.loads(Path(os.environ["ENTITLEMENTS"]).read_bytes())
groups = entitlements.get("keychain-access-groups") or []
if len(groups) != 1:
refuse(
"the custody entitlement must declare exactly one keychain access "
f"group; it declares {len(groups)}"
group = groups[0]
team, _, bundle_id = group.partition(".")
if not re.fullmatch(r"[A-Z0-9]{10}", team) or not re.fullmatch(
r"[A-Za-z0-9][A-Za-z0-9.-]*", bundle_id
):
f"the keychain access group {group!r} is not <TEAM>.<bundle-id>, so no "
"bundle identifier can be derived from it"
# The bundle identifier IS the access group's own suffix. That is not a
# convention: a program's default data-protection keychain access group
# is its application-identifier, so a bundle identified this way asks
# for exactly the group the entitlement grants, and the App ID the
# owner's profile must cover is <TEAM>.<bundle-id> by construction.
# Regenerate the profile for a different App ID and this step refuses
# rather than shipping a bundle the kernel will kill.
profile = plistlib.loads(Path(os.environ["PROFILE_PLIST"]).read_bytes())
expires = profile.get("ExpirationDate")
if not isinstance(expires, datetime):
refuse("the custody provisioning profile carries no ExpirationDate")
if expires.tzinfo is None:
expires = expires.replace(tzinfo=timezone.utc)
remaining = expires - datetime.now(timezone.utc)
if remaining < timedelta(days=MARGIN_DAYS):
f"the custody provisioning profile expires {expires.isoformat()} "
f"({remaining.days} days from now) and the release refuses under "
f"{MARGIN_DAYS} days: the bundle stops launching when the profile "
"does. Generate a fresh profile, update the secret, re-tag."
if profile.get("ProvisionedDevices"):
"the custody provisioning profile is device-scoped (ProvisionedDevices) "
"— a development profile authorizes only enrolled Macs, and this bundle "
"is signed and launch-proven on a CI runner that is not one of them. "
"Generate the macOS Developer ID profile instead."
platforms = profile.get("Platform") or []
if platforms and not {"osx", "macos"}.intersection(
str(value).casefold() for value in platforms
f"the custody provisioning profile declares platforms {platforms!r}; "
"the ceremony bundle is macOS"
def covers(granted: str, wanted: str) -> bool:
if granted.endswith("*"):
return wanted.startswith(granted[:-1])
return granted == wanted
granted = profile.get("Entitlements") or {}
profile_team = granted.get("com.apple.developer.team-identifier")
if profile_team is None:
profile_team = (profile.get("TeamIdentifier") or [None])[0]
if profile_team != team:
"the custody provisioning profile belongs to a different team than the "
"entitlement's access group; the profile and build/m1nd-mcp.entitlements.plist "
"must name the same team"
granted_groups = granted.get("keychain-access-groups") or []
if not any(covers(value, group) for value in granted_groups):
f"the custody provisioning profile does not authorize the access group "
f"{group!r}; without that authorization AMFI kills the bundle exactly as "
"it killed the raw v1.6.0 binary"
app_id = granted.get("application-identifier") or granted.get(
"com.apple.application-identifier"
if app_id is None:
"the custody provisioning profile grants no application-identifier"
if not covers(app_id, f"{team}.{bundle_id}"):
f"the custody provisioning profile is issued for App ID {app_id!r}, which "
f"does not cover the bundle identifier {bundle_id!r} derived from the "
"entitlement's access group. Regenerate the profile for that App ID, or "
"move the access group in build/m1nd-mcp.entitlements.plist — they travel "
"together."
app = Path(os.environ["APP_BUNDLE"])
(app / "Contents" / "MacOS").mkdir(parents=True)
numeric = re.match(r"\d+(?:\.\d+){0,2}", release_version)
if numeric is None:
refuse(f"release version {release_version!r} has no numeric prefix")
plistlib.dump(
{
"CFBundleExecutable": os.environ["BINARY_NAME"],
"CFBundleIdentifier": bundle_id,
"CFBundleInfoDictionaryVersion": "6.0",
"CFBundleName": app.stem,
"CFBundlePackageType": "APPL",
"CFBundleShortVersionString": numeric.group(0),
"CFBundleVersion": numeric.group(0),
},
(app / "Contents" / "Info.plist").open("wb"),
(temp / "custody-bundle-id").write_text(bundle_id, encoding="utf-8")
(temp / "custody-access-group").write_text(group, encoding="utf-8")
(temp / "custody-profile-expiry").write_text(
expires.isoformat(), encoding="utf-8"
BUNDLE_ID="$(cat "$RUNNER_TEMP/custody-bundle-id")"
CUSTODY_GROUP="$(cat "$RUNNER_TEMP/custody-access-group")"
PROFILE_EXPIRES="$(cat "$RUNNER_TEMP/custody-profile-expiry")"
# 3) the bundle: the SAME bytes the build produced, plus the profile in
# the one place AMFI reads it
cp "$BIN_PATH" "$APP_BUNDLE/Contents/MacOS/$BINARY_NAME"
cp "$PROFILE" "$APP_BUNDLE/Contents/embedded.provisionprofile"
rm -f "$PROFILE" "$PROFILE_PLIST"
# 4) ephemeral keychain holding only the Developer ID certificate
KEYCHAIN="$RUNNER_TEMP/m1nd-custody-signing.keychain-db"
printf '%s' "$APPLE_CERT_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/custody-cert.p12"
security import "$RUNNER_TEMP/custody-cert.p12" -k "$KEYCHAIN" -P "$APPLE_CERT_PASSWORD" \
rm -f "$RUNNER_TEMP/custody-cert.p12"
# 5) sign the bundle WITH the entitlement — the inverse of the runtime
# above, and legal only because the profile inside authorizes it
--entitlements "$ENTITLEMENTS" \
--sign "$APPLE_SIGNING_IDENTITY" "$APP_BUNDLE"
codesign --verify --strict --verbose=2 "$APP_BUNDLE"
if ! codesign -d --entitlements - "$APP_BUNDLE" 2>/dev/null | grep -qF "$CUSTODY_GROUP"; then
echo "::error::the ceremony bundle does not carry the custody access group — an unentitled bundle cannot run the G9 ceremony and must never be published as one"
SIGNATURE="$(codesign -dv --verbose=4 "$APP_BUNDLE" 2>&1)"
case "$SIGNATURE" in
*"Authority=Developer ID Application"*) ;;
*) echo "::error::the ceremony bundle does not carry a Developer ID authority"; exit 1 ;;
*"Identifier=$BUNDLE_ID"*) ;;
*) echo "::error::codesign recorded an identifier other than $BUNDLE_ID — the signed identity must equal the identifier the profile covers"; exit 1 ;;
# 6) prove it LAUNCHES, on the native runner, after signing. This is the
# whole lesson of v1.6.0: a signature that verifies is not a binary
# that runs, and AMFI's verdict on a restricted entitlement is only
# observable at launch.
BUNDLED_EXE="$APP_BUNDLE/Contents/MacOS/$BINARY_NAME"
VERSION_OUTPUT="$("$BUNDLED_EXE" --version)" || {
echo "::error::the signed ceremony bundle does not launch — AMFI refused the restricted entitlement. Read the kernel log: the usual causes are a profile that does not authorize the access group and a profile issued for another App ID."
case "$VERSION_OUTPUT" in
*"$RELEASE_VERSION"*) ;;
*) echo "::error::the bundled executable reports '$VERSION_OUTPUT', not release $RELEASE_VERSION"; exit 1 ;;
# 7) notarize and STAPLE — a bundle can hold the ticket a raw binary
# cannot, so the owner's Mac needs no online check to launch it
printf '%s' "$APPLE_API_KEY_P8_BASE64" | base64 --decode > "$RUNNER_TEMP/CustodyAuthKey.p8"
ditto -c -k --keepParent "$APP_BUNDLE" "$RUNNER_TEMP/custody-notarize.zip"
xcrun notarytool submit "$RUNNER_TEMP/custody-notarize.zip" \
--key "$RUNNER_TEMP/CustodyAuthKey.p8" \
rm -f "$RUNNER_TEMP/CustodyAuthKey.p8" "$RUNNER_TEMP/custody-notarize.zip"
xcrun stapler staple "$APP_BUNDLE"
xcrun stapler validate "$APP_BUNDLE"
# 8) package with ditto (the only container that preserves a bundle's
# permissions, symlinks and signature) and prove the round trip:
# what the owner unzips is what was signed, and it still launches
BUNDLE_ZIP="m1nd-custody-ceremony-${MATRIX_ARTIFACT#m1nd-mcp-}.zip"
ditto -c -k --keepParent --sequesterRsrc "$APP_BUNDLE" "$BUNDLE_ZIP"
ROUND_TRIP="$RUNNER_TEMP/custody-round-trip"
rm -rf "$ROUND_TRIP"
mkdir -p "$ROUND_TRIP"
ditto -x -k "$BUNDLE_ZIP" "$ROUND_TRIP"
UNPACKED="$ROUND_TRIP/$(basename "$APP_BUNDLE")"
codesign --verify --strict --verbose=2 "$UNPACKED"
"$UNPACKED/Contents/MacOS/$BINARY_NAME" --version >/dev/null \
|| { echo "::error::the packaged ceremony bundle does not launch after a round trip"; exit 1; }
# 9) the ordinary runtime must be exactly as the step above left it —
# the two artifacts share bytes on disk and nothing else
echo "::error::the shipped runtime acquired a restricted entitlement while the bundle was built — it would be SIGKILLed at launch"
|| { echo "::error::the shipped runtime stopped launching while the bundle was built"; exit 1; }
echo "built=true" >> "$GITHUB_OUTPUT"
echo "::notice title=Custody-ceremony bundle::$BUNDLE_ZIP signed, notarized and stapled. Its embedded profile expires $PROFILE_EXPIRES — the bundle stops launching then, so re-tag before that date."
if: steps.custody-bundle.outputs.built == 'true'
name: m1nd-custody-ceremony-${{ matrix.target }}
path: m1nd-custody-ceremony-*.zip
# Longer than the runtime artifacts' 14 days on purpose: this one is
# deliberately not promoted into the Release, so the run IS where the
# owner fetches it, and the ceremony is scheduled by a human.
retention-days: 90
- name: Stage updater-facing Unix runtime
if: runner.os != 'Windows'
run: cp "target/${{ matrix.target }}/release/${BINARY_NAME}" "${{ matrix.raw }}"
- name: Archive Unix runtime
tar -C "target/${{ matrix.target }}/release" \
-czf "${{ matrix.archive }}" "${BINARY_NAME}"
name: ${{ matrix.artifact }}
path: |
${{ matrix.archive }}
${{ matrix.raw }}
artifact-smoke:
name: Installed artifact smoke (${{ matrix.target }})
needs: [tag-guard, ui-artifact, build]
timeout-minutes: 15
- target: linux-x86_64
binary: runtime/m1nd-mcp
- target: macos-x86_64
- target: macos-aarch64
- name: Install Unix archive bytes
mkdir runtime
tar -xzf "${{ matrix.archive }}" -C runtime
- name: Restore raw Unix executable permission
run: chmod +x "${{ matrix.raw }}" "${{ matrix.binary }}"
- name: Prove archive member equals updater-facing raw runtime
ARCHIVE_BINARY: ${{ matrix.binary }}
RAW_BINARY: ${{ matrix.raw }}
python - <<'PY'
import hashlib
archive_binary = Path(os.environ["ARCHIVE_BINARY"])
raw_binary = Path(os.environ["RAW_BINARY"])
archive_sha = hashlib.sha256(archive_binary.read_bytes()).hexdigest()
raw_sha = hashlib.sha256(raw_binary.read_bytes()).hexdigest()
if archive_sha != raw_sha:
f"archive/raw mismatch: {archive_binary}={archive_sha}, {raw_binary}={raw_sha}"
- name: Exercise exact installed binary
run: >-
python scripts/m1nd10_release_artifact_smoke.py
--binary "${{ matrix.binary }}"
--expected-version "${{ needs.tag-guard.outputs.version }}"
--expected-commit "${{ github.sha }}"
--expected-ui-sha256 "${{ needs.ui-artifact.outputs.sha256 }}"
--target "${{ matrix.target }}"
--output "GATE-ARTIFACT-SMOKE-${{ matrix.target }}.json"
name: m1nd-artifact-smoke-${{ matrix.target }}
GATE-ARTIFACT-SMOKE-${{ matrix.target }}.json
candidate-assembly:
name: Assemble candidate bytes, SBOM, provenance, and rollback without credentials
needs: [tag-guard, ui-artifact, npm-artifact, release-gate, crate-artifact, build, artifact-smoke]
pattern: m1nd-mcp-*
path: release-bins
merge-multiple: true
pattern: m1nd-artifact-smoke-*
path: release-npm
path: release-crates
- name: Bind rebuilt UI provenance into the release candidate
cp release-ui/UI-BUNDLE-PROVENANCE.json release-bins/
cp release-npm/*.tgz release-bins/
cp release-crates/*.crate release-bins/
- name: Generate SPDX JSON SBOM over exact runtime artifacts
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
format: spdx-json
output-file: release-bins/m1nd-mcp.spdx.json
upload-artifact: false
upload-release-assets: false
- name: Prepare canonical non-circular updater and rollback inputs
mkdir -p canonical-inputs
python3 scripts/m1nd10_release_candidate.py prepare-canonical-operational \
--artifacts release-bins \
--version "${{ needs.tag-guard.outputs.version }}" \
--source-ref "${GITHUB_REF}" \
--expected-target linux-x86_64 \
--expected-target macos-x86_64 \
--expected-target macos-aarch64 \
--compatibility-output canonical-inputs/RELEASE-COMPATIBILITY.json \
--rollback-output canonical-inputs/M1ND10-ROLLBACK.json \
--digest-output canonical-inputs/CANONICAL-OPERATIONAL-DIGESTS.json
- name: Assemble ordinary G8 candidate without claiming M1ND-10 convergence
python3 scripts/m1nd10_release_candidate.py assemble \
--run-id "${GITHUB_RUN_ID}" \
--required-job tag-guard \
--required-job release-gate \
--required-job build \
--required-job artifact-smoke \
--required-job npm-artifact \
--required-job crate-artifact \
--output release-bins/CANDIDATE.json \
--receipt-output release-bins/GATE-RECEIPT.json \
--rollback-output release-bins/ROLLBACK.json
cp canonical-inputs/RELEASE-COMPATIBILITY.json release-bins/
cp canonical-inputs/M1ND10-ROLLBACK.json release-bins/
cp canonical-inputs/CANONICAL-OPERATIONAL-DIGESTS.json release-bins/
python3 scripts/m1nd10_release_candidate.py verify \
--manifest release-bins/CANDIDATE.json
- name: Generate portable SHA-256 inventory
root = Path("release-bins")
paths = sorted(
path for path in root.iterdir()
if path.is_file()
and path.name != "SHA256SUMS"
and not path.name.endswith(".sigstore.json")
with (root / "SHA256SUMS").open("w", encoding="utf-8") as output:
for path in paths:
output.write(f"{hashlib.sha256(path.read_bytes()).hexdigest()} {path.name}\n")
name: m1nd-release-candidate-unsigned-${{ github.sha }}
path: release-bins/
candidate:
name: Attest and sign the credentialless candidate assembly
needs: candidate-assembly
id-token: write
attestations: write
- name: GitHub Sigstore build provenance
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
subject-path: |
release-bins/*.tar.gz
release-bins/*.zip
release-bins/*.tgz
release-bins/*.crate
release-bins/m1nd-mcp-linux-x86_64
release-bins/m1nd-mcp-macos-x86_64
release-bins/m1nd-mcp-macos-aarch64
release-bins/*.json
release-bins/SHA256SUMS
- name: GitHub Sigstore SBOM attestation
release-bins/CANDIDATE.json
release-bins/GATE-RECEIPT.json
release-bins/ROLLBACK.json
sbom-path: release-bins/m1nd-mcp.spdx.json
- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Emit portable keyless signature bundles without repository code
while IFS= read -r -d '' file; do
cosign sign-blob --yes \
--bundle "${file}.sigstore.json" \
"${file}"
done < <(find release-bins -maxdepth 1 -type f \
! -name '*.sigstore.json' -print0 | sort -z)
name: m1nd-release-candidate-${{ github.sha }}
verified-update-smoke:
name: Verified public updater smoke (${{ matrix.target }})
needs: [tag-guard, candidate]
- name: Place cosign in a trusted fixed path for the updater
# The updater resolves cosign only from fixed system directories (never
# an arbitrary PATH), but cosign-installer drops it in the runner
# tool-cache. Copy the just-installed, signature-verified cosign into a
# trusted directory so the updater's own resolver finds it.
src="$(command -v cosign)"
sudo install -m 0755 "$src" /usr/local/bin/cosign
/usr/local/bin/cosign version >/dev/null
- name: Restore updater-facing Unix executable permission
run: chmod +x "release-bins/${{ matrix.raw }}"
- name: Verify signed candidate, apply, rollback, and refuse stale overwrite
node scripts/m1nd10_update_rollback_smoke.js
--binary "release-bins/${{ matrix.raw }}"
--output "GATE-VERIFIED-UPDATE-SMOKE-${{ matrix.target }}.json"
name: m1nd-verified-update-smoke-${{ matrix.target }}
path: GATE-VERIFIED-UPDATE-SMOKE-${{ matrix.target }}.json
release-verification:
name: Verify candidate and updater receipts without mutation credentials
needs: [tag-guard, candidate, verified-update-smoke]
npm_tarball: ${{ steps.npmmeta.outputs.tarball }}
npm_version: ${{ steps.npmmeta.outputs.version }}
npm_tag: ${{ steps.npmmeta.outputs.tag }}
pattern: m1nd-verified-update-smoke-*
path: verified-update-receipts
- id: npmmeta
name: Reverify candidate bytes, updater receipts, and npm identity
(cd release-bins && sha256sum --check --strict SHA256SUMS)
cosign verify-blob \
--bundle release-bins/CANDIDATE.json.sigstore.json \
--certificate-identity "https://github.com/maxkle1nz/m1nd/.github/workflows/release.yml@${GITHUB_REF}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
python3 scripts/m1nd10_release_candidate.py verify-update-receipts \
--receipts verified-update-receipts \
python3 - <<'PY' >> "${GITHUB_OUTPUT}"
manifest = json.loads(Path("release-bins/CANDIDATE.json").read_text())
package = manifest.get("npm_package", {})
expected = "${{ needs.tag-guard.outputs.version }}"
name = package.get("name")
if (
package.get("kind") != "npm_package_tarball"
or package.get("package_name") != "@maxkle1nz/m1nd"
or package.get("package_version") != expected
or not isinstance(name, str)
or not re.fullmatch(r"[A-Za-z0-9._-]+\.tgz", name)
or Path(name).name != name
or not (Path("release-bins") / name).is_file()
or not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?", expected)
raise SystemExit("candidate npm package binding is invalid")
print(f"tarball={name}")
print(f"version={expected}")
print(f"tag={'beta' if '-' in expected else 'latest'}")
crate-publish-verification:
name: Compile and prepare exact crates.io request bodies without registry credentials
needs: [tag-guard, candidate, release-verification]
key: exact-candidate-crate-verification
- name: Reverify signed candidate and resolve its exact Cargo packages
id: crates
packages = manifest.get("cargo_packages")
expected_order = ["m1nd-core", "m1nd-control", "m1nd-ingest", "m1nd-mcp"]
if not isinstance(packages, list) or [p.get("package_name") for p in packages] != expected_order:
raise SystemExit("candidate Cargo package order is invalid")
for position, package in enumerate(packages, start=1):
name = package["package_name"]
key = name.removeprefix("m1nd-")
filename = package.get("name")
version = package.get("package_version")
digest = package.get("sha256")
path = Path("release-bins") / str(filename)
package.get("kind") != "cargo_crate_package"
or package.get("publish_order") != position
or not isinstance(filename, str)
or Path(filename).name != filename
or not path.is_file()
or not isinstance(version, str)
or not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?", version)
or not isinstance(digest, str)
or not re.fullmatch(r"[0-9a-f]{64}", digest)
raise SystemExit(f"invalid candidate Cargo package binding for {name}")
if name != "m1nd-control" and version != expected:
raise SystemExit(f"{name} version {version} != release {expected}")
print(f"{key}_file={filename}")
print(f"{key}_version={version}")
print(f"{key}_sha256={digest}")
mcp = packages[-1]
print(f"mcp_ui_sha256={mcp['ui_bundle_sha256']}")
- name: Build sealed crates.io request bodies and reject foreign existing bytes
EXPECTED_RELEASE_VERSION: ${{ needs.tag-guard.outputs.version }}
EXPECTED_RELEASE_COMMIT: ${{ github.sha }}
import importlib.util
script = Path("scripts/m1nd10_crates_io_upload.py")
spec = importlib.util.spec_from_file_location("m1nd10_crates_io_upload", script)
if spec is None or spec.loader is None:
raise SystemExit("exact crate publisher module cannot be loaded")
publisher = importlib.util.module_from_spec(spec)
spec.loader.exec_module(publisher)
if publisher.CRATES_IO_UPLOAD_URL != "https://crates.io/api/v1/crates/new":
raise SystemExit("crates.io upload authority drifted")
source = Path("release-bins")
destination = Path("crates-publish-ready")
destination.mkdir(mode=0o700)
manifest = json.loads((source / "CANDIDATE.json").read_text())
if not isinstance(packages, list) or [row.get("package_name") for row in packages] != expected_order:
release_version = os.environ["EXPECTED_RELEASE_VERSION"]
release_commit = os.environ["EXPECTED_RELEASE_COMMIT"]
def published_checksum(name, version):
url = (
headers={"Accept": "application/json", "User-Agent": "m1nd-candidate-crate-guard/1"},
raise SystemExit(f"crates.io checksum probe failed closed: {error.reason}")
raise SystemExit(f"crates.io checksum response is oversized for {name}@{version}")
if status == 404:
return None
if status != 200:
raise SystemExit(f"{name}@{version} registry state is NOT_PROVEN (HTTP {status})")
value = json.loads(payload)
except json.JSONDecodeError as error:
raise SystemExit(f"invalid crates.io checksum response for {name}@{version}: {error}")
published = value.get("version") if isinstance(value, dict) else None
checksum = published.get("checksum") if isinstance(published, dict) else None
not isinstance(published, dict)
or published.get("crate") != name
or published.get("num") != version
or not isinstance(checksum, str)
or not re.fullmatch(r"[0-9a-f]{64}", checksum)
raise SystemExit(f"invalid crates.io checksum identity for {name}@{version}")
return checksum
rows = []
name = package.get("package_name")
crate_sha256 = package.get("sha256")
or name != expected_order[position - 1]
or not isinstance(crate_sha256, str)
or not re.fullmatch(r"[0-9a-f]{64}", crate_sha256)
or (name != "m1nd-control" and version != release_version)
raise SystemExit(f"invalid candidate Cargo package binding at position {position}")
crate_path = source / filename
inspected = publisher.inspect_crate(crate_path)
inspected["name"] != name
or inspected["version"] != version
or inspected["sha256"] != crate_sha256
raise SystemExit(f"candidate Cargo archive differs from manifest for {name}")
observed_checksum = published_checksum(name, version)
if observed_checksum is not None and observed_checksum != crate_sha256:
f"foreign immutable bytes already exist for {name}@{version}: "
f"registry={observed_checksum}, candidate={crate_sha256}"
body = publisher.build_upload_body(crate_path, inspected)
body_name = f"{name}.upload-body"
(destination / body_name).write_bytes(body)
rows.append({
"body_file": body_name,
"body_sha256": hashlib.sha256(body).hexdigest(),
"body_size": len(body),
"crate_sha256": crate_sha256,
"name": name,
"observed_checksum": observed_checksum,
"publish_order": position,
"registry_state": "absent" if observed_checksum is None else "exact_existing",
"version": version,
})
plan = {
"schema": "m1nd-crates-publish-plan-v1",
"release_commit": release_commit,
"release_version": release_version,
"packages": rows,
(destination / "CRATES-PUBLISH-PLAN.json").write_text(
json.dumps(plan, sort_keys=True, separators=(",", ":")),
encoding="utf-8",
name: m1nd-crates-publish-ready-${{ github.sha }}
path: crates-publish-ready/
- name: Compile every exact package only after publish-ready bytes are immutable
M1ND_EXPECTED_UI_BUNDLE_SHA256: ${{ steps.crates.outputs.mcp_ui_sha256 }}
CORE_ROOT="$(python3 scripts/m1nd10_crates_io_upload.py extract \
--crate "release-bins/${{ steps.crates.outputs.core_file }}" \
--destination "${RUNNER_TEMP}/exact-crates/core")"
CONTROL_ROOT="$(python3 scripts/m1nd10_crates_io_upload.py extract \
--crate "release-bins/${{ steps.crates.outputs.control_file }}" \
--destination "${RUNNER_TEMP}/exact-crates/control")"
INGEST_ROOT="$(python3 scripts/m1nd10_crates_io_upload.py extract \
--crate "release-bins/${{ steps.crates.outputs.ingest_file }}" \
--destination "${RUNNER_TEMP}/exact-crates/ingest")"
MCP_ROOT="$(python3 scripts/m1nd10_crates_io_upload.py extract \
--crate "release-bins/${{ steps.crates.outputs.mcp_file }}" \
--destination "${RUNNER_TEMP}/exact-crates/mcp")"
cargo check --locked --manifest-path "${CORE_ROOT}/Cargo.toml"
cargo check --locked --manifest-path "${CONTROL_ROOT}/Cargo.toml"
cargo check --manifest-path "${INGEST_ROOT}/Cargo.toml" \
--config "patch.crates-io.m1nd-core.path='${CORE_ROOT}'"
cargo check --manifest-path "${MCP_ROOT}/Cargo.toml" \
--config "patch.crates-io.m1nd-core.path='${CORE_ROOT}'" \
--config "patch.crates-io.m1nd-control.path='${CONTROL_ROOT}'" \
--config "patch.crates-io.m1nd-ingest.path='${INGEST_ROOT}'"
release:
name: Promote the already-verified sealed candidate to GitHub Release
needs: [tag-guard, candidate, verified-update-smoke, release-verification, crate-publish-verification]
environment: release
contents: write
- name: Create immutable-byte release without repository checkout
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
files: release-bins/*
generate_release_notes: true
draft: false
prerelease: ${{ contains(github.ref_name, '-') }}
# Verified-updater receipts remain CI artifacts: they gate promotion but
# are deliberately outside the already-signed candidate/release file set.
publish:
name: Publish preverified exact crates.io request bodies
needs: [tag-guard, candidate, release, crate-publish-verification]
permissions: {}
path: crates-publish-ready
- name: Verify the signed candidate root without repository checkout
- name: Upload and observe the four exact crates.io bodies
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
import struct
import time
API_ORIGIN = "https://crates.io"
UPLOAD_URL = f"{API_ORIGIN}/api/v1/crates/new"
EXPECTED_ORDER = ["m1nd-core", "m1nd-control", "m1nd-ingest", "m1nd-mcp"]
root = Path("crates-publish-ready")
candidate_root = Path("release-bins")
plan_path = root / "CRATES-PUBLISH-PLAN.json"
if not plan_path.is_file() or plan_path.stat().st_size > 1024 * 1024:
raise SystemExit("sealed crates.io publish plan is missing or oversized")
plan = json.loads(plan_path.read_text(encoding="utf-8"))
candidate_path = candidate_root / "CANDIDATE.json"
not candidate_path.is_file()
or candidate_path.is_symlink()
or candidate_path.stat().st_size > 4 * 1024 * 1024
raise SystemExit("signed candidate manifest is missing, unsafe, or oversized")
candidate = json.loads(candidate_path.read_text(encoding="utf-8"))
expected_version = os.environ["EXPECTED_RELEASE_VERSION"]
expected_commit = os.environ["EXPECTED_RELEASE_COMMIT"]
token = os.environ.get("CARGO_REGISTRY_TOKEN", "")
if not token or len(token) > 512 or "\r" in token or "\n" in token:
raise SystemExit("CARGO_REGISTRY_TOKEN is absent or invalid")
set(plan) != {"schema", "release_commit", "release_version", "packages"}
or plan.get("schema") != "m1nd-crates-publish-plan-v1"
or plan.get("release_commit") != expected_commit
or plan.get("release_version") != expected_version
or not isinstance(plan.get("packages"), list)
or len(plan["packages"]) != len(EXPECTED_ORDER)
raise SystemExit("sealed crates.io publish plan identity is invalid")
signed_packages = candidate.get("cargo_packages") if isinstance(candidate, dict) else None
not isinstance(candidate, dict)
or candidate.get("schema") != "m1nd-release-candidate-v1"
or candidate.get("commit") != expected_commit
or candidate.get("version") != expected_version
or candidate.get("source_ref") != os.environ.get("GITHUB_REF")
or not isinstance(signed_packages, list)
or not all(isinstance(package, dict) for package in signed_packages)
or [package.get("package_name") for package in signed_packages] != EXPECTED_ORDER
raise SystemExit("signed candidate Cargo identity is invalid")
def version_url(name, version):
return (
f"{API_ORIGIN}/api/v1/crates/"
url = version_url(name, version)
headers={"Accept": "application/json", "User-Agent": "m1nd-sealed-crate-publisher/1"},
raise SystemExit(f"crates.io authority probe failed closed: {error.reason}")
for position, (name, row, signed) in enumerate(
zip(EXPECTED_ORDER, plan["packages"], signed_packages), start=1
expected_keys = {
"body_file", "body_sha256", "body_size", "crate_sha256",
"name", "observed_checksum", "publish_order", "registry_state", "version",
version = row.get("version") if isinstance(row, dict) else None
body_name = row.get("body_file") if isinstance(row, dict) else None
signed_filename = signed.get("name") if isinstance(signed, dict) else None
signed_sha256 = signed.get("sha256") if isinstance(signed, dict) else None
registry_state = row.get("registry_state") if isinstance(row, dict) else None
observed_checksum = row.get("observed_checksum") if isinstance(row, dict) else None
not isinstance(row, dict)
or not isinstance(signed, dict)
or set(row) != expected_keys
or row.get("name") != name
or row.get("publish_order") != position
or signed.get("kind") != "cargo_crate_package"
or signed.get("package_name") != name
or signed.get("publish_order") != position
or signed.get("package_version") != version
or not isinstance(signed_filename, str)
or Path(signed_filename).name != signed_filename
or not signed_filename.endswith(".crate")
or not isinstance(signed_sha256, str)
or not re.fullmatch(r"[0-9a-f]{64}", signed_sha256)
or (name != "m1nd-control" and version != expected_version)
or body_name != f"{name}.upload-body"
or not isinstance(row.get("body_sha256"), str)
or not re.fullmatch(r"[0-9a-f]{64}", row["body_sha256"])
or not isinstance(row.get("crate_sha256"), str)
or not re.fullmatch(r"[0-9a-f]{64}", row["crate_sha256"])
or row.get("crate_sha256") != signed_sha256
or registry_state not in {"absent", "exact_existing"}
or (registry_state == "absent" and observed_checksum is not None)
or (registry_state == "exact_existing" and observed_checksum != signed_sha256)
or not isinstance(row.get("body_size"), int)
or row["body_size"] <= 8
or row["body_size"] > 16 * 1024 * 1024
raise SystemExit(f"invalid sealed publish row for {name}")
signed_crate_path = candidate_root / signed_filename
not signed_crate_path.is_file()
or signed_crate_path.is_symlink()
or signed_crate_path.stat().st_size > 16 * 1024 * 1024
raise SystemExit(f"signed candidate crate is missing, unsafe, or oversized for {name}")
signed_crate_bytes = signed_crate_path.read_bytes()
if hashlib.sha256(signed_crate_bytes).hexdigest() != signed_sha256:
raise SystemExit(f"signed candidate crate digest mismatch for {name}")
body_path = root / body_name
not body_path.is_file()
or body_path.is_symlink()
or body_path.stat().st_size != row["body_size"]
raise SystemExit(f"sealed upload body size mismatch for {name}")
body = body_path.read_bytes()
if hashlib.sha256(body).hexdigest() != row["body_sha256"]:
raise SystemExit(f"sealed upload body digest mismatch for {name}")
metadata_length = struct.unpack("<I", body[:4])[0]
crate_length_offset = 4 + metadata_length
if crate_length_offset + 4 > len(body):
raise SystemExit(f"invalid registry framing for {name}")
metadata = json.loads(body[4:crate_length_offset])
crate_length = struct.unpack("<I", body[crate_length_offset:crate_length_offset + 4])[0]
crate_bytes = body[crate_length_offset + 4:]
crate_length != len(crate_bytes)
or crate_length_offset + 4 + crate_length != len(body)
or metadata.get("name") != name
or metadata.get("vers") != version
or crate_bytes != signed_crate_bytes
or hashlib.sha256(crate_bytes).hexdigest() != signed_sha256
raise SystemExit(f"sealed registry framing differs from signed candidate crate {name}")
existing_checksum = published_checksum(name, version)
if existing_checksum is not None:
if existing_checksum != signed_sha256:
f"foreign immutable bytes exist for {name}@{version}: "
f"registry={existing_checksum}, candidate={signed_sha256}"
print(f"already published exact signed candidate bytes: {name}@{version}")
continue
UPLOAD_URL,
data=body,
headers={
"Accept": "application/json",
"Authorization": token,
"Content-Type": "application/octet-stream",
"User-Agent": "m1nd-sealed-crate-publisher/1",
method="PUT",
with opener.open(request, timeout=120) as response:
upload_status = int(response.status)
error.read(64 * 1024)
recovered_checksum = published_checksum(name, version)
if recovered_checksum == signed_sha256:
print(f"recovered exact signed candidate publication: {name}@{version}")
raise SystemExit(f"crates.io refused {name}@{version} (HTTP {error.code})")
raise SystemExit(f"{name}@{version} upload outcome is indeterminate: {error.reason}")
if not 200 <= upload_status < 300 or len(payload) > 2 * 1024 * 1024:
raise SystemExit(f"invalid crates.io upload response for {name}@{version}")
response_value = json.loads(payload or b"{}")
if not isinstance(response_value, dict) or response_value.get("errors"):
raise SystemExit(f"crates.io reported an upload error for {name}@{version}")
for attempt in range(1, 41):
visible_checksum = published_checksum(name, version)
if visible_checksum == signed_sha256:
print(f"published exact sealed bytes: {name}@{version}")
break
if visible_checksum is not None:
f"foreign bytes became visible for {name}@{version}: "
f"registry={visible_checksum}, candidate={signed_sha256}"
if attempt == 40:
raise SystemExit(f"timed out waiting for {name}@{version} visibility")
time.sleep(5)
publish-npm:
name: Publish the preverified npm tarball with registry provenance
needs: [tag-guard, candidate, release, release-verification]
registry-url: "https://registry.npmjs.org"
- name: Publish the exact candidate tarball once without lifecycle scripts
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_IGNORE_SCRIPTS: "true"
NPM_CONFIG_REGISTRY: "https://registry.npmjs.org"
EXPECTED_NPM_TARBALL: ${{ needs.release-verification.outputs.npm_tarball }}
EXPECTED_NPM_VERSION: ${{ needs.release-verification.outputs.npm_version }}
probe_registry_integrity() {
import base64
package_name = "@maxkle1nz/m1nd"
filename = os.environ["EXPECTED_NPM_TARBALL"]
version = os.environ["EXPECTED_NPM_VERSION"]
not re.fullmatch(r"[A-Za-z0-9._-]+\.tgz", filename)
raise SystemExit("candidate npm identity is invalid")
tarball = Path("release-bins") / filename
not tarball.is_file()
or tarball.is_symlink()
or tarball.stat().st_size <= 0
or tarball.stat().st_size > 128 * 1024 * 1024
raise SystemExit("signed candidate npm tarball is missing, unsafe, or oversized")
digest = hashlib.sha512()
with tarball.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
expected_integrity = "sha512-" + base64.b64encode(digest.digest()).decode("ascii")
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), RefuseRedirects())
"https://registry.npmjs.org/"
f"{urllib.parse.quote(package_name, safe='')}/{urllib.parse.quote(version, safe='')}"
headers={"Accept": "application/json", "User-Agent": "m1nd-sealed-npm-publisher/1"},
raise SystemExit(f"npm registry integrity probe failed closed: {error.reason}")
raise SystemExit("npm registry integrity response is oversized")
print("absent")
raise SystemExit(0)
raise SystemExit(f"npm registry state is NOT_PROVEN (HTTP {status})")
raise SystemExit(f"invalid npm registry integrity response: {error}")
dist = value.get("dist") if isinstance(value, dict) else None
observed_integrity = dist.get("integrity") if isinstance(dist, dict) else None
not isinstance(value, dict)
or value.get("name") != package_name
or value.get("version") != version
or not isinstance(observed_integrity, str)
raise SystemExit("npm registry integrity identity is invalid")
if observed_integrity != expected_integrity:
raise SystemExit("foreign immutable npm tarball exists for the release version")
print("exact_existing")
REGISTRY_STATE="$(probe_registry_integrity)"
if [ "${REGISTRY_STATE}" = "exact_existing" ]; then
echo "already published exact signed candidate npm tarball"
test "${REGISTRY_STATE}" = "absent"
test -n "${NODE_AUTH_TOKEN}"
set +e
npm publish "./release-bins/${EXPECTED_NPM_TARBALL}" \
--registry "https://registry.npmjs.org" \
--access public \
--ignore-scripts \
--provenance \
--tag "${{ needs.release-verification.outputs.npm_tag }}"
PUBLISH_STATUS=$?
set -e
for attempt in $(seq 1 40); do
if [ "${PUBLISH_STATUS}" -eq 0 ]; then
echo "published exact signed candidate npm tarball"
else
echo "recovered exact signed candidate npm publication"
if [ "${attempt}" -lt 40 ]; then
sleep 5
done
echo "npm publish returned success but exact registry integrity is NOT_PROVEN" >&2
echo "npm publish failed and exact registry integrity is NOT_PROVEN" >&2