Repository navigation
372 lines (336 loc) · 17.5 KB
/
Copy pathrelease.yml
File metadata and controls
372 lines (336 loc) · 17.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
name: Release
# Trigger: push an exact semver tag (e.g., git tag v0.1.0 && git push origin v0.1.0).
#
# The filter accepts ONLY vX.Y.Z. GSD milestone tags (v1.0-milestone) also start
# with "v", and under the old any-v glob such a tag started this workflow: run
# 29185695331 queued 24 h and was cancelled only because no runner could take
# it. On a hosted runner it would have published that tag as a real GitHub
# Release. In GitHub's filter syntax `+` means one-or-more, `.` is literal and
# the whole tag must match, so milestone tags and pre-release suffixes
# (v0.1.0-rc.1) do not trigger. Widen deliberately in Phase 6 if a pre-release
# dry run needs it.
#
# This pipeline is the twice-proven injection-scanner/mcp-hub cargo-zigbuild
# template adapted to tar.gz packaging: one GitHub-hosted Linux host
# cross-compiles ALL targets via cargo-zigbuild (zig is host-arch-independent,
# so no per-arch runner, no Docker/cross, no macOS runner).
#
# ── RUNNER POLICY (issue #1, option 2 — injection-scanner #45 precedent) ───────
# This workflow runs on GitHub-hosted runners, an exception to the org default
# of self-hosted runners, for two reasons:
#
# 1. It uses NO org secrets — only the built-in GITHUB_TOKEN, which
# softprops/action-gh-release uses implicitly. There is no signing key, no
# Sonatype or npm token, no crates.io token. crates.io publishing lives in
# the separate publish-crates.yml (Trusted Publishing over OIDC, no
# secret either). The Homebrew formula in
# UnityInFlow/homebrew-tap is updated by hand, outside this workflow. The
# self-hosted policy exists to protect secrets this pipeline does not hold.
# 2. This repo is public and the org runner group enforces
# allows_public_repositories: false (OPS-02, enforced 2026-07-09), so a
# self-hosted job cannot be scheduled here at all — the previous
# self-hosted ARM64 runner label would queue until cancelled. v0.0.1
# shipped only because it was built while the repo was still private.
#
# Provenance: an ephemeral hosted runner is better provenance than a persistent
# self-hosted box that carries state between jobs, and this pipeline now emits
# a signed SLSA build-provenance attestation the old path could not produce.
#
# Safety: the workflow is tag-push triggered only, which a fork cannot fire.
# That is what makes the release job's `contents: write` / `id-token: write`
# acceptable. Adding any org secret to this file voids this exception.
#
# This resolves early the release-side runner decision Phase 3 D-12 deferred to
# Phase 6; ci.yml has used the same hosted D-02 split since Phase 3 D-12.
#
# ── ASSET CONTRACT ─────────────────────────────────────────────────────────────
# Assets are tarballs (agent-memory-<triple>.tar.gz) with the single
# `agent-memory` binary at the archive root — the Homebrew formula in
# UnityInFlow/homebrew-tap expects exactly this shape (a deliberate divergence
# from the injection-scanner analog's raw binaries).
#
# ── DARWIN DISPOSITION (DIST-01) ───────────────────────────────────────────────
# The two apple-darwin legs are HARD-required: DIST-01 mandates macOS
# arm64/x86_64 binaries, so a darwin failure fails the release (a deliberate
# divergence from the analog's experimental darwin legs). The darwin bundled-C
# zigbuild cross-link (sqlite3.c + sqlite-vec.c) was proven by the v0.0.1
# release run 28644256471, built on the old ARM64 host after the chrono
# features=[now] trim (Phase 02-04). It has not yet run from an x86_64 hosted
# host; the first tag run after this change (the Phase 6 release dry run) is
# that proof. spike-cross-compile.yml is a Windows + musl feasibility spike
# (Phase 3, bd45b87), not a release gate. Only the two musl legs are
# best-effort (continue-on-error) — gnu covers Linux if musl fails
# (RESEARCH Open Q2).
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
# Read-only by default; only the release job is elevated.
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
# Pinned toolchain for cargo-zigbuild cross-compilation (RESEARCH Pitfall 3).
# NEVER use "latest" — zig <-> cargo-zigbuild compatibility is version-coupled.
ZIG_VERSION: '0.14.1'
CARGO_ZIGBUILD_VERSION: '0.23.0'
jobs:
# ──────────────────────────────────────────────────────────────────────────
# JOB 1: test
# Full quality gate: fmt + clippy -D warnings + workspace tests + the same
# >80% line-coverage gate ci.yml enforces. Builds do not start until this
# passes — the tag must meet every gate main is held to.
# ──────────────────────────────────────────────────────────────────────────
test:
name: Test gate (fmt + clippy + test + coverage)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust stable
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
toolchain: stable
components: clippy, rustfmt
- name: cargo fmt check
run: cargo fmt --check
- name: cargo clippy (deny warnings)
run: cargo clippy --workspace --all-targets -- -D warnings
- name: cargo test
run: cargo test --workspace
- name: Install cargo-llvm-cov
run: cargo install cargo-llvm-cov --locked
- name: Coverage gate (>80% lines)
run: cargo llvm-cov --workspace --fail-under-lines 80
# ──────────────────────────────────────────────────────────────────────────
# JOB 2: build-binaries
# Each matrix leg gets its own fresh GitHub-hosted x86_64 runner and the six
# legs run in parallel; each cross-compiles its target via cargo-zigbuild.
#
# The four DIST-01 targets (both apple-darwin + both linux-gnu) are
# HARD-required. Only the two musl legs may fail without failing the
# release (best-effort static-linux bonus).
# ──────────────────────────────────────────────────────────────────────────
build-binaries:
name: Build ${{ matrix.target }}
needs: test
runs-on: ubuntu-latest
# Only the musl legs are non-blocking; darwin + gnu legs hard-fail (DIST-01).
continue-on-error: ${{ matrix['continue-on-error'] }}
strategy:
# fail-fast disabled: surface ALL target failures in one run. The release
# job still hard-requires the 4 DIST-01 tarballs, so a partial required
# set never ships.
fail-fast: false
matrix:
include:
# ── REQUIRED (DIST-01): macOS arm64/x86_64 — hard-fail on error ──
- target: aarch64-apple-darwin
continue-on-error: false
- target: x86_64-apple-darwin
continue-on-error: false
# ── REQUIRED (DIST-01): Linux gnu x86_64/aarch64 — hard-fail ──
- target: x86_64-unknown-linux-gnu
continue-on-error: false
- target: aarch64-unknown-linux-gnu
continue-on-error: false
# ── BEST-EFFORT: static musl pair (RESEARCH Open Question 2) ──
- target: x86_64-unknown-linux-musl
continue-on-error: true
- target: aarch64-unknown-linux-musl
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust stable + target
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
toolchain: stable
targets: ${{ matrix.target }}
- name: Install pinned zig + cargo-zigbuild
run: |
# Pinned versions (RESEARCH Pitfall 3): zig 0.14.1, cargo-zigbuild 0.23.0.
# Install zig from the official tarball matched to the HOST arch. Kept
# arch-dynamic on purpose: it costs nothing and keeps the job portable.
# ubuntu-latest is x86_64; zig itself cross-compiles to all targets, so
# only the host arch matters here. Pin BOTH versions -- never `latest`.
set -euo pipefail
case "$(uname -m)" in
aarch64|arm64) ZIG_ARCH=aarch64 ;;
x86_64|amd64) ZIG_ARCH=x86_64 ;;
*) echo "unsupported host arch: $(uname -m)" >&2; exit 1 ;;
esac
# zig 0.14.0+ uses arch-first artifact naming: zig-<arch>-linux-<ver>.
ZIG_DIR="zig-${ZIG_ARCH}-linux-${ZIG_VERSION}"
curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/${ZIG_DIR}.tar.xz" -o /tmp/zig.tar.xz
mkdir -p "$HOME/.zig" && tar -xJf /tmp/zig.tar.xz -C "$HOME/.zig"
echo "$HOME/.zig/${ZIG_DIR}" >> "$GITHUB_PATH"
"$HOME/.zig/${ZIG_DIR}/zig" version
cargo install --locked --version "${CARGO_ZIGBUILD_VERSION}" cargo-zigbuild
# The package is agent-memory-mcp (crates.io name, #9); its [[bin]] is
# still `agent-memory`, so the binary path and asset names are unchanged.
- name: Build (cargo zigbuild, cross-compiled from this Linux host)
run: cargo zigbuild --release --locked --target ${{ matrix.target }} -p agent-memory-mcp
- name: Smoke test (host-arch-aware --version; foreign-arch/darwin = presence-only)
run: |
set -euo pipefail
BIN="target/${{ matrix.target }}/release/agent-memory"
test -f "$BIN" || { echo "ERROR: $BIN missing"; exit 1; }
TARGET="${{ matrix.target }}"
case "$TARGET" in
*-apple-darwin)
# A macOS binary is never natively executable on a Linux host.
echo "apple-darwin build present -- presence-only (not executable on a Linux host)."
;;
*)
# Execute only when the target arch equals the runner arch —
# `Exec format error` otherwise (RESEARCH Pitfall 7, mcp-hub
# 03-04 precedent). Decide at runtime, never hardcode a triple.
# On the x86_64 hosted runner the x86_64 Linux binaries now run
# --version natively; aarch64 and darwin are presence-only.
TARGET_ARCH="${TARGET%%-*}" # x86_64 | aarch64
HOST_ARCH="$(uname -m)"; [ "$HOST_ARCH" = "arm64" ] && HOST_ARCH=aarch64
if [ "$TARGET_ARCH" = "$HOST_ARCH" ]; then
"$BIN" --version
else
echo "Cross-built $TARGET_ARCH on $HOST_ARCH host -- presence-only."
fi
;;
esac
- name: Package (agent-memory-<triple>.tar.gz, binary at archive root)
run: |
# Asset contract: the Homebrew formula downloads the tarball and runs
# `bin.install "agent-memory"` — the single binary must sit at the
# archive root with its plain name.
set -euo pipefail
OUT="agent-memory-${{ matrix.target }}.tar.gz"
STAGE="$(mktemp -d)"
cp "target/${{ matrix.target }}/release/agent-memory" "$STAGE/agent-memory"
chmod +x "$STAGE/agent-memory"
tar -czf "$OUT" -C "$STAGE" agent-memory
echo "Packaged: $OUT"
tar -tzf "$OUT"
# upload-artifact v7 and download-artifact v8 are the matched pair proven
# in injection-scanner's releases; they must move together.
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.target }}
path: agent-memory-${{ matrix.target }}.tar.gz
# ──────────────────────────────────────────────────────────────────────────
# JOB 3: release
# The 4 DIST-01 tarballs are HARD-required (fail loudly if any is missing).
# The two musl tarballs are included when present, otherwise warn-and-continue.
# Generates SHA256SUMS.txt over all present tarballs and creates the GitHub
# Release.
# ──────────────────────────────────────────────────────────────────────────
release:
name: Create GitHub Release
needs: [build-binaries]
runs-on: ubuntu-latest
permissions:
contents: write # create the Release and upload assets
id-token: write # sign the build-provenance attestation
attestations: write # record it
steps:
# Matched pair with upload-artifact v7 above; they must move together.
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
merge-multiple: true
path: artifacts/
- name: Validate required assets (DIST-01 hard gate) + musl disposition
run: |
set -euo pipefail
cd artifacts
# The four tarballs DIST-01 requires — any absence fails the release.
REQUIRED=(
"agent-memory-aarch64-apple-darwin.tar.gz"
"agent-memory-x86_64-apple-darwin.tar.gz"
"agent-memory-x86_64-unknown-linux-gnu.tar.gz"
"agent-memory-aarch64-unknown-linux-gnu.tar.gz"
)
for f in "${REQUIRED[@]}"; do
if test -f "$f"; then
echo "OK (required): $f"
else
echo "MISSING (required DIST-01 asset): $f"
exit 1
fi
done
# The two musl tarballs are best-effort (RESEARCH Open Question 2).
for f in \
"agent-memory-x86_64-unknown-linux-musl.tar.gz" \
"agent-memory-aarch64-unknown-linux-musl.tar.gz"; do
if test -f "$f"; then
echo "OK (musl bonus present): $f"
else
echo "::warning::musl static build unavailable -- gnu covers Linux (best-effort leg): $f"
fi
done
echo "All 4 required DIST-01 assets validated."
- name: Generate SHA256SUMS.txt (over all present tarballs)
run: |
set -euo pipefail
cd artifacts
: > SHA256SUMS.txt
for f in agent-memory-*.tar.gz; do
sha256sum "$f" >> SHA256SUMS.txt
done
echo "Generated SHA256SUMS.txt:"
cat SHA256SUMS.txt
# OIDC-signed SLSA build provenance over every published file, possible
# only on a hosted runner. Created BEFORE the Release so nothing ships
# unattested. Consumers verify with:
# gh attestation verify agent-memory-<triple>.tar.gz --repo UnityInFlow/agent-memory
- name: Attest build provenance
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: |
artifacts/agent-memory-*.tar.gz
artifacts/SHA256SUMS.txt
- name: Create GitHub Release
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
files: |
artifacts/agent-memory-*.tar.gz
artifacts/SHA256SUMS.txt
generate_release_notes: true
body: |
## agent-memory release
Tarballs cross-compiled from a GitHub-hosted Linux runner via
`cargo zigbuild`. Each tarball holds the single `agent-memory`
binary at its root.
### Install via Homebrew (macOS + Linux)
```
brew install unityinflow/tap/agent-memory
```
### Install via cargo
```
cargo install agent-memory-mcp --locked
```
The crate is `agent-memory-mcp`; the executable it installs is
`agent-memory`.
### Manual install
| Platform | Asset |
|----------|-------|
| macOS arm64 (Apple Silicon) | `agent-memory-aarch64-apple-darwin.tar.gz` |
| macOS x86_64 (Intel) | `agent-memory-x86_64-apple-darwin.tar.gz` |
| Linux x86_64 (gnu) | `agent-memory-x86_64-unknown-linux-gnu.tar.gz` |
| Linux aarch64 (gnu) | `agent-memory-aarch64-unknown-linux-gnu.tar.gz` |
| Linux x86_64 (musl, static) | `agent-memory-x86_64-unknown-linux-musl.tar.gz` (if present) |
| Linux aarch64 (musl, static) | `agent-memory-aarch64-unknown-linux-musl.tar.gz` (if present) |
Verify with `SHA256SUMS.txt`, extract, and run:
```
shasum -a 256 -c SHA256SUMS.txt --ignore-missing
tar -xzf agent-memory-<triple>.tar.gz
./agent-memory --version
```
### Verify provenance
Every tarball and `SHA256SUMS.txt` carries a signed SLSA
build-provenance attestation:
```
gh attestation verify agent-memory-<triple>.tar.gz --repo UnityInFlow/agent-memory
```
## Checksums
See `SHA256SUMS.txt` for verification.