Resolve Forge receipts through storage keys

4b08e65b9762 · AtlantisPleb · · parent e14af264d75f

Resolve Forge receipts through storage keys

Deploy story

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

built
3 modules in 22.1 s
deployed
live · 3 modules on 3 nodes · push→live —
deployed
needs_rolling_replace · 3 modules on 0 nodes · push→live —

Changed files

  • modified docs/operations/forge-hot-loop.md
  • modified lib/openagents/forge.ex
  • modified lib/openagents/forge/hot_loader.ex
  • modified lib/openagents/forge/pushes.ex
  • modified test/openagents/forge/git_http_test.exs
  • modified test/openagents/forge/hot_loader_test.exs

Diff

6 files changed, +54 -5

docs/operations/forge-hot-loop.md modified +3 -1

@@ -68,7 +68,9 @@ Every link below exists in code and runs in this order:

68 68
   the artifact to `OpenAgents.Forge.Deployment` for the transactional
69 69
   prepare → canary → fleet apply → verify → commit sequence.
70 70
6. **Receipt**: a `live` deployment writes the deploy receipt including
71
   `push_to_live_ms`, measured from the push receipt.
71
   `push_to_live_ms`, measured from the push receipt. Receipt lookup resolves
72
   the configured repository name to its canonical storage key because Git
73
   writes use the opaque storage key while targets use the public name.
72 74
73 75
## Activation state
74 76
lib/openagents/forge.ex modified +4 -2

@@ -15,7 +15,7 @@ defmodule OpenAgents.Forge do

15 15
16 16
  import Ecto.Query
17 17
18
  alias OpenAgents.Forge.PushReceipt
18
  alias OpenAgents.Forge.{Pushes, PushReceipt}
19 19
  alias OpenAgents.Repo
20 20
21 21
  @doc "Whether the forge endpoint is enabled (default true; env-gated in prod)."

@@ -25,8 +25,10 @@ defmodule OpenAgents.Forge do

25 25
26 26
  @doc "Recent push receipts for one repo, newest first, bounded."
27 27
  def recent_pushes(repo, limit \\ 20) do
28
    repo_keys = Pushes.receipt_repo_keys(repo)
29
28 30
    PushReceipt
29
    |> where([p], p.repo == ^repo)
31
    |> where([p], p.repo in ^repo_keys)
30 32
    |> order_by([p], desc: p.wal_seq)
31 33
    |> limit(^limit)
32 34
    |> Repo.all()
lib/openagents/forge/hot_loader.ex modified +4 -2

@@ -15,7 +15,7 @@ defmodule OpenAgents.Forge.HotLoader do

15 15
  alias OpenAgents.Forge.BuildArtifact
16 16
  alias OpenAgents.Forge.Deployment
17 17
  alias OpenAgents.Forge.DeployReceipt
18
  alias OpenAgents.Forge.PushReceipt
18
  alias OpenAgents.Forge.{Pushes, PushReceipt}
19 19
  alias OpenAgents.Forge.Targets
20 20
  alias OpenAgents.Repo
21 21

@@ -293,8 +293,10 @@ defmodule OpenAgents.Forge.HotLoader do

293 293
  # Push→live duration: the push receipt whose refs advanced some ref to
294 294
  # this sha marks when the loop started.
295 295
  defp push_to_live_ms(repo, sha) do
296
    repo_keys = Pushes.receipt_repo_keys(repo)
297
296 298
    PushReceipt
297
    |> where([p], p.repo == ^repo)
299
    |> where([p], p.repo in ^repo_keys)
298 300
    |> order_by([p], desc: p.inserted_at)
299 301
    |> limit(100)
300 302
    |> Repo.all()
lib/openagents/forge/pushes.ex modified +8

@@ -266,6 +266,14 @@ defmodule OpenAgents.Forge.Pushes do

266 266
267 267
  def mirror_storage_key(repo), do: repo
268 268
269
  @doc "Return the logical and canonical repository keys used by derived push receipts."
270
  def receipt_repo_keys(repo) when is_binary(repo) do
271
    [repo, mirror_storage_key(repo)]
272
    |> Enum.uniq()
273
  end
274
275
  def receipt_repo_keys(repo), do: [repo]
276
269 277
  defp storage_key_for_storage_key(storage_key) do
270 278
    Repo.one(
271 279
      from repository in Repository,
test/openagents/forge/git_http_test.exs modified +2

@@ -125,6 +125,8 @@ defmodule OpenAgents.Forge.GitHTTPTest do

125 125
    assert receipt.wal_seq == 0
126 126
    assert receipt.refs["refs/heads/main"]["new"] == sha
127 127
    assert receipt.refs["refs/heads/main"]["old"] == nil
128
    assert [logical_receipt] = Forge.recent_pushes(repository.name)
129
    assert logical_receipt.id == receipt.id
128 130
129 131
    # Deploy signal fired.
130 132
    assert_receive {:forge_push, %{repo: storage_key, wal_seq: 0}}, 2_000
test/openagents/forge/hot_loader_test.exs modified +33

@@ -298,6 +298,39 @@ defmodule OpenAgents.Forge.HotLoaderTest do

298 298
    assert receipt.push_to_live_ms >= 0
299 299
  end
300 300
301
  test "push_to_live_ms resolves a logical repository to its receipt storage key", %{
302
    loader: loader
303
  } do
304
    storage_key = Ecto.UUID.generate()
305
306
    OpenAgents.Repositories.Repository
307
    |> where([repository], repository.name == "openagents.com")
308
    |> Repo.update_all(set: [storage_key: storage_key])
309
310
    %{name: name, binary: binary} = compiled_scratch_module()
311
    sha = unique_sha()
312
    target = insert_target(sha, "built")
313
    artifact = artifact([{name, binary}], sha)
314
315
    {:ok, _push} =
316
      %PushReceipt{}
317
      |> PushReceipt.changeset(%{
318
        repo: storage_key,
319
        wal_seq: System.unique_integer([:positive]),
320
        principal: "test-op",
321
        refs: %{
322
          "refs/heads/main" => %{"old" => String.duplicate("0", 40), "new" => sha}
323
        }
324
      })
325
      |> Repo.insert()
326
327
    broadcast_build_ready(loader, build_payload(target, sha, artifact))
328
329
    receipt = deploy_receipt(sha)
330
    assert receipt.result == "live"
331
    assert is_integer(receipt.push_to_live_ms)
332
  end
333
301 334
  test "push_to_live_ms is nil when no push receipt matches", %{loader: loader} do
302 335
    %{name: name, binary: binary} = compiled_scratch_module()
303 336

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