Resolve the replaced image from the registry, not from target details

c5fe18d7ae8e · AtlantisPleb · · parent b1f3cc17a30a

Resolve the replaced image from the registry, not from target details

The rolling authority is refused when the previous image digest equals the
next one, which is the right check: a replacement whose previous image is its
next image replaces nothing. The release computed that value as the live
target's details image_digest with this release's own digest as the fallback,
so whenever the details carried no digest the fallback produced exactly the
value the check rejects, and the promotion failed on a release that was
otherwise correct.

A target settled after its builder has written build details comes back with
no image digest at all, so the fallback was not a rare path. The registry
knows what the live sha was built as, so the live sha is read first and its
digest looked up there. A missing live release, a sha with no image, and an
image identical to this one are each refused by name rather than papered over
with a value that fails validation later.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SoZMfWRSGnf6FZX2Ar9rQ2
Co-Authored-By
Claude Fable 5 <noreply@anthropic.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

pushed
by user · WAL seq 424 · 2026-08-25T22:53:55.488225Z

Changed files

  • modified ops/deploy/release-to-production.sh

Diff

1 file changed, +33 -3

ops/deploy/release-to-production.sh modified +33 -3

@@ -99,12 +99,42 @@ if rpc_file sarah-fleet-1 us-central1-a "$live_now" 2>/dev/null | grep -q "alrea

99 99
fi
100 100
rm -f "$live_now"
101 101
102
# What the fleet is replacing, resolved before the promotion so the authority
103
# can be checked against something true. The live target's details do not
104
# reliably carry an image digest — a target settled after its builder wrote
105
# build details comes back with none — and the old fallback used this
106
# release's own digest, which is the one value the authority check refuses:
107
# a rolling replacement whose previous image equals its next image is not a
108
# replacement. The registry knows what the live sha was built as, so ask it.
109
echo "==> previous release"
110
live_sha=$(mktemp)
111
cat > "$live_sha" <<'ELIXIR'
112
live = OpenAgents.Forge.Targets.live("openagents.com")
113
IO.puts("live-sha=" <> ((live && live.sha) || "none"))
114
ELIXIR
115
previous_sha=$(rpc_file sarah-fleet-1 us-central1-a "$live_sha" 2>/dev/null |
116
  grep -o 'live-sha=[0-9a-f]*' | head -1 | cut -d= -f2)
117
rm -f "$live_sha"
118
119
[ -n "$previous_sha" ] && [ "$previous_sha" != "none" ] ||
120
  { echo "no live release to replace; refusing to guess a previous image" >&2; exit 1; }
121
122
previous_digest=$(gcloud artifacts docker images describe "$registry:$previous_sha" \
123
  --format='value(image_summary.digest)' --project="$project" 2>/dev/null || true)
124
125
[ -n "$previous_digest" ] ||
126
  { echo "no image in the registry for the live release $previous_sha" >&2; exit 1; }
127
128
[ "$previous_digest" != "$digest" ] ||
129
  { echo "the live release already runs this image; nothing to replace" >&2; exit 1; }
130
131
echo "previous: $previous_sha ($previous_digest)"
132
102 133
echo "==> promote"
103 134
promote=$(mktemp)
104 135
cat > "$promote" <<ELIXIR
105
live = OpenAgents.Forge.Targets.live("openagents.com")
106
previous_sha = (live && live.sha) || "$sha"
107
previous_image_digest = (live && live.details["image_digest"]) || "$digest"
136
previous_sha = "$previous_sha"
137
previous_image_digest = "$previous_digest"
108 138
expected_nodes = Enum.sort(["openagents@10.128.0.4", "openagents@10.128.0.110", "openagents@10.128.0.111"])
109 139
110 140
# The forge builds a promoted target itself: OpenAgents.Forge.Builder

This page updates live while a promote is in flight · changelog