personal memory agent
1#!/usr/bin/env bash
2# SPDX-License-Identifier: AGPL-3.0-only
3# Copyright (c) 2026 sol pbc
4#
5# solstone release-candidate rail.
6#
7# This script is only a local dispatcher. It can finalize a provider-neutral
8# release candidate, revalidate retained candidate evidence, or run the narrowed
9# Linux structural dry-run. Publication is temporarily locked out here; a later
10# aggregate publisher is responsible for authentication, upload, tagging, and
11# hosted release creation after a candidate is proven locally.
12#
13# DESTRUCTIVE: --candidate is fresh construction; before policy or build work it
14# deletes prior raw build/dist outputs and that version's stale payload/evidence.
15#
16# Candidate flow:
17# 1. Verify the expected source commit, clean source tree, and core lock.
18# 2. Delete prior raw outputs and stale retained payload/evidence for the
19# version being constructed.
20# 3. Build local artifacts and receive macOS artifacts through the externally
21# configured build-host channel.
22# 4. Collect per-target install/smoke proofs through configured proof-host
23# channels, then pair-promote payload and evidence.
24# 5. Revalidate payload, manifests, ledger, and proofs, then print canonical
25# local readiness JSON. This is not publication authorization.
26#
27# Recovery flow:
28# `--recover <version> <source-commit>` is retained-byte-only, read-only
29# validation. It preserves retained payload, ledger, and proofs and never
30# rebuilds, refreshes advisories, contacts hosts, installs wheels, reads
31# authentication, or uses the network.
32#
33# Dry-run flow:
34# `--dry-run-linux` is structural only. It emits no ready payload, manifest,
35# ledger, proof, or clean-source claim.
36
37set -euo pipefail
38
39PUBLISH_LOCKOUT_MESSAGE="publishing is locked out here; use the aggregate publisher after a release candidate is finalized"
40
41usage() {
42 cat <<'EOF'
43Usage: scripts/release.sh [--candidate|--recover <version> <source-commit>|--dry-run-linux]
44
45Modes:
46 --candidate Finalize a non-publishing all-host release candidate, write
47 retained local evidence, and report readiness digests.
48 DESTRUCTIVE: fresh construction deletes prior raw
49 build/dist outputs and that version's stale payload/evidence
50 before policy or build work begins.
51 --recover VERSION SOURCE_COMMIT
52 Retained-byte-only, read-only validation of payload, ledger,
53 and target install/smoke proofs. Preserves retained bytes
54 and never rebuilds or refreshes.
55 --dry-run-linux Structural Linux-only dry-run. Emits no ready payload,
56 manifest, ledger, proof, or clean-source claim.
57 -h, --help Show this help.
58
59Required environment:
60 --candidate:
61 EXPECTED_RELEASE_COMMIT expected lowercase source commit
62 RELEASE_MODEL_PACKAGES include or exclude
63 RELEASE_ADVISORY_SOURCE_NAME public advisory source id
64 RELEASE_ADVISORY_DB_URL explicit non-GitHub advisory DB source
65 RELEASE_ADVISORY_DB_ROOT cargo-deny advisory db parent; see
66 scripts/release_advisory_policy.py
67 RELEASE_BUILD_HOST_CHANNEL external build-host adapter command
68 RELEASE_PROOF_HOST_LINUX_X86_64_MUSL_CHANNEL
69 external proof-host adapter command
70 RELEASE_PROOF_HOST_LINUX_AARCH64_MUSL_CHANNEL
71 external proof-host adapter command
72 RELEASE_PROOF_HOST_MACOS_ARM64_CHANNEL
73 external proof-host adapter command
74
75 --recover:
76 VERSION and SOURCE_COMMIT are required positional selectors. Recovery does
77 not read current source metadata to decide what retained bytes to validate.
78
79 --dry-run-linux:
80 RELEASE_MODEL_PACKAGES include or exclude
81
82Publication entry points:
83 Running with no args, --test, make release, or make release-test fails closed
84 before any external seam. Publication is handled by the later aggregate
85 publisher after local candidate evidence is complete.
86EOF
87}
88
89fail_closed_publish() {
90 echo "$PUBLISH_LOCKOUT_MESSAGE" >&2
91 exit 1
92}
93
94MODE=""
95RECOVER_VERSION=""
96RECOVER_SOURCE_COMMIT=""
97
98if [[ $# -eq 0 ]]; then
99 fail_closed_publish
100fi
101
102set_mode() {
103 local next_mode="$1"
104 if [[ -n "$MODE" ]]; then
105 echo "only one release mode may be selected" >&2
106 usage >&2
107 exit 2
108 fi
109 MODE="$next_mode"
110}
111
112while [[ $# -gt 0 ]]; do
113 case "$1" in
114 --candidate)
115 set_mode "candidate"
116 shift
117 ;;
118 --recover)
119 set_mode "recover"
120 if [[ $# -lt 3 ]]; then
121 echo "--recover requires VERSION and SOURCE_COMMIT" >&2
122 usage >&2
123 exit 2
124 fi
125 RECOVER_VERSION="$2"
126 RECOVER_SOURCE_COMMIT="$3"
127 shift 3
128 ;;
129 --dry-run-linux)
130 set_mode "dry-run-linux"
131 shift
132 ;;
133 --test)
134 fail_closed_publish
135 ;;
136 -h|--help)
137 usage
138 exit 0
139 ;;
140 *)
141 echo "unknown argument: $1" >&2
142 usage >&2
143 exit 2
144 ;;
145 esac
146done
147
148if [[ -z "$MODE" ]]; then
149 fail_closed_publish
150fi
151
152REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
153cd "$REPO_ROOT"
154
155if [[ "$MODE" == "recover" ]]; then
156 exec python3 scripts/release_candidate_driver.py recover \
157 --version "$RECOVER_VERSION" \
158 --source-commit "$RECOVER_SOURCE_COMMIT"
159fi
160
161exec python3 scripts/release_candidate_driver.py "$MODE"