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
62 changes: 35 additions & 27 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,17 @@ jobs:
- name: Compare the committed version against the published one
id: decide
run: |
VERSION=$(python3 -c "import tomllib;print(tomllib.load(open('Cargo.toml','rb'))['workspace']['package']['version'])")
# `cargo pkgid` rather than a TOML parser. The version is resolved by
# the tool that owns it, so this cannot disagree with what the build
# actually produces, and it needs no interpreter on the runner.
VERSION=$(cargo pkgid -p chuzz-gui | sed 's/.*@//')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

# `latest.json` is written by this workflow further down, so its shape
# is known: one "version" field. `sed` reads it rather than a JSON
# parser, which keeps the runner requirement at coreutils.
LIVE=$(curl -fsS --max-time 30 "$CDN_BASE/latest.json" 2>/dev/null \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["version"])' 2>/dev/null || echo "")
| sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)

if [ "${{ inputs.force }}" = "true" ]; then
echo "publish=true" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -237,31 +243,32 @@ jobs:
run: |
TARBALL="$BUNDLE_DIR/Chuzz.app.tar.gz"
test -f "$TARBALL.sig" || { echo "::error::missing $TARBALL.sig"; exit 1; }
python3 - "$VERSION" "$TARBALL.sig" "$CDN_BASE" <<'PY'
import json, sys, datetime
version, sig_path, base = sys.argv[1], sys.argv[2], sys.argv[3]
with open(sig_path) as f:
signature = f.read().strip()
manifest = {
"version": version,
"pub_date": datetime.datetime.now(datetime.timezone.utc)
.strftime("%Y-%m-%dT%H:%M:%SZ"),
"platforms": {
"darwin-aarch64": {
"signature": signature,
# The query string is a cache-buster, and it works because
# this pull zone keys its cache on the full URL including
# the query, so each release advertises a URL no edge has
# ever held. Brew fetches the bare URL and relies on the
# purge.
"url": f"{base}/Chuzz.app.tar.gz?v={version}",
}
},
# A minisign signature is base64 and the version is a semver string,
# so neither can hold a character JSON would need escaped, and this
# manifest is interpolation rather than serialisation. Guard that
# assumption instead of taking on a parser.
SIGNATURE=$(cat "$TARBALL.sig")
case $SIGNATURE in *[!A-Za-z0-9+/=]*)
echo "::error::signature is not plain base64; refusing to interpolate"; exit 1;; esac
case $VERSION in *[!0-9A-Za-z.+-]*)
echo "::error::version has unexpected characters; refusing to interpolate"; exit 1;; esac

# The url query string is a cache-buster, and it works because this
# pull zone keys its cache on the full URL including the query, so
# each release advertises a URL no edge has ever held. Brew fetches
# the bare URL and relies on the purge.
cat > latest.json <<JSON
{
"version": "$VERSION",
"pub_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"platforms": {
"darwin-aarch64": {
"signature": "$SIGNATURE",
"url": "$CDN_BASE/Chuzz.app.tar.gz?v=$VERSION"
}
}
}
with open("latest.json", "w") as f:
json.dump(manifest, f, indent=2)
f.write("\n")
PY
JSON
cat latest.json

# Ordering matters and is the whole reason these are separate steps. The
Expand Down Expand Up @@ -352,7 +359,8 @@ jobs:
# nobody sees the release. It is also what the next run compares
# against, so a stale copy would republish this version forever.
for attempt in 1 2 3 4 5 6; do
LIVE=$(curl -sS -L "$CDN_BASE/latest.json" | python3 -c 'import json,sys; print(json.load(sys.stdin)["version"])' 2>/dev/null || echo "")
LIVE=$(curl -sS -L "$CDN_BASE/latest.json" 2>/dev/null \
| sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
if [ "$LIVE" = "$VERSION" ]; then
echo "edge serves version $LIVE"
exit 0
Expand Down
39 changes: 26 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,39 @@
- Keep browser policy separate from the renderer. The browser app owns tabs, navigation, history, permissions, persistence, and downloads.
- Keep the local control surface protocol-compatible with AgencyZero's Blitz interface: `blitz.agent.control` and `blitz.diagnostics` over local MCP framing.
- Control is opt-in, local-only, and unauthenticated only because the socket and descriptor are owner-readable. Never bind it to a network interface.
- **The engine is a pinned revision, not a sibling checkout.** `ps-blitz`,
`tauri-runtime-blitz` and `endpoint-libs` are git dependencies with a `rev` in
`Cargo.toml`, so `Cargo.lock` records exactly what a release builds and an
ordinary `cargo build` fetches it. Do not turn them back into `path =
"../..."`. That is what this repository did before, and it put the revision CI
used in a `BLITZ_REF` env var in release.yml while every developer built
against whatever happened to be on disk. The pin sat 44 commits behind, and
the release job broke the day a new engine package was added, because a path
dependency resolves against the filesystem and no local build can notice.
- **Every dependency is a published version with a caret, not a sibling
checkout.** `ps-blitz`, the renderers and `endpoint-libs` are ordinary
crates.io dependencies on `^`, so `cargo update` can move them, `Cargo.lock`
records exactly what a release builds, and two crates asking for the same
range share one copy instead of getting a second. Do not turn them back into
`path = "../..."`. That is what this repository did before, and it put the
revision CI used in a `BLITZ_REF` env var in release.yml while every developer
built against whatever happened to be on disk. The pin sat 44 commits behind,
and the release job broke the day a new engine package was added, because a
path dependency resolves against the filesystem and no local build can notice.
Do not reach for `rev` or `=` either: a git revision cannot be published and
an exact pin is a range of one, so both split the graph the same way. A git
`endpoint-libs` alongside the registry copy is precisely how this repository
ended up with two of it.
- **`tauri-runtime-blitz` is the one exception, and it is temporary.** It is
still a git dependency with a `rev` because the crate sets `publish = false`
and depends on a patched `usvg` for `Tree::intrinsic_dimensions`; crates.io
rejects anything with a git dependency. `ps-usvg` is published now, so the
path forward is to repoint it there, drop `publish = false`, release, and
bring this one to a caret with the rest.
- **Building against a working checkout is opt-in and never edits a tracked
file.** Put the `[patch]` tables in `.cargo/local-engine.toml`, which is
gitignored, and reach for them per command:
`scripts/local-engine.sh check -p chuzz-gui`. The wrapper snapshots and
restores `Cargo.lock`, because a redirected build rewrites it to point at
directories that exist on one machine. Patch only the crates you are actually
changing; every entry is a pin that stops being tested.
- When you move the engine pin, move `tauri-runtime-blitz`'s to match. Both name
a ps-blitz revision, and two different ones put two engines in the graph,
which surfaces as missing methods and unrelated `PaintScene` traits rather
than as a version error.
- When you move the engine version, move `tauri-runtime-blitz`'s to match. Both
resolve a ps-blitz, and two different ones put two engines in the graph, which
surfaces as missing methods and unrelated `PaintScene` traits rather than as a
version error. This is why the exception above is worth closing: a caret on
both sides makes the shared range the thing that keeps them equal, instead of
a revision someone has to remember to move twice.
- Work on a branch and ship through a pull request. Do not commit to `main`.
- Run `cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, and `cargo test --workspace --all-features` before delivery.

Expand Down
Loading
Loading