Pin a 15-minute release candidate soak

1210ac68d0d1 · AtlantisPleb · · parent dd57039c6e20

Pin a 15-minute release candidate soak

Deploy story

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

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified docs/2026-08-20-integration-hardening-and-staging-readiness-recommendations.md
  • modified docs/architecture.md
  • modified docs/decisions/0006-isolate-web-and-distributed-fleet-staging.md
  • modified docs/operations/production-cutover.md
  • modified docs/operations/staging-report-template.md
  • modified docs/operations/staging-resilience-report-template.md
  • modified docs/operations/staging-resilience.md
  • modified docs/sarah-integration-plan.md
  • modified ops/staging/new-resilience-report.sh
  • modified ops/staging/resilience-matrix.json
  • modified ops/staging/resilience.sh
  • modified ops/staging/validate-resilience-report.sh
  • modified test/openagents/staging_resilience_contract_test.exs

Diff

13 files changed, +96 -66

docs/2026-08-20-integration-hardening-and-staging-readiness-recommendations.md modified +11 -10

@@ -1550,7 +1550,8 @@ Inject these failures one at a time:

1550 1550
1551 1551
After failure injection, run a staging soak:
1552 1552
1553
- Keep the web and distributed staging lanes active for at least 48 hours.
1553
- Keep the pinned web and distributed release-candidate lanes active for at
1554
  least 15 minutes. Deploy later commits to a separate staging service.
1554 1555
- Exercise scheduled typed, memory, voice, tracker, Git, and status canaries.
1555 1556
- Watch database connections, queue depth, mailbox growth, process count,
1556 1557
  memory, CPU, restart count, artifact cache, Ra state, and node convergence.

@@ -1575,8 +1576,8 @@ Implemented locally on 2026-08-20:

1575 1576
  and resilience reports while retaining failed attempts and refusing
1576 1577
  not-applicable controlled failures.
1577 1578
- Added a strict resilience validator. Completion requires all controlled
1578
  failures passed, at least 172,800 measured seconds without a redeploy, stable
1579
  candidate identity, 576 metric samples, the versioned status, typed, memory,
1579
  failures passed, at least 900 measured seconds on the pinned candidate track,
1580
  stable candidate identity, 15 metric samples, the versioned status, typed, memory,
1580 1581
  tracker, Git, and voice canary minimums, a post-soak smoke, and zero
1581 1582
  unexplained harm counts.
1582 1583
- Bound the main staging report's final state to one nested, complete resilience

@@ -1584,10 +1585,10 @@ Implemented locally on 2026-08-20:

1584 1585
  evidence tree is checksum-, path-, permission-, and content-scan validated.
1585 1586
- Added the [controlled-failure and soak runbook](operations/staging-resilience.md)
1586 1587
  and [resilience evidence contract](operations/staging-resilience-report-template.md).
1587
  The network-free dry run proves the 11-case and 48-hour contracts fail closed.
1588
  The network-free dry run proves the 11-case and 15-minute contracts fail closed.
1588 1589
1589 1590
The local Gate 15 harness and contract tests pass. Live controlled failures and
1590
the measured 48-hour soak remain blocked until Gates 12–14 complete on the
1591
the measured 15-minute soak remain blocked until Gates 12–14 complete on the
1591 1592
isolated staging target. No cloud or production resource changed while adding
1592 1593
the harness.
1593 1594

@@ -1662,7 +1663,7 @@ each handoff.

1662 1663
| 13 | Provision isolated web and distributed staging lanes, including a separate database instance | Isolation and configuration review pass |
1663 1664
| 14 | Add staging harnesses and the evidence report template | Regression harness dry run passes |
1664 1665
| 15 | Deploy one staging candidate and complete the full matrix | Staging report is complete |
1665
| 16 | Complete failure injection and the 48-hour soak | No unexplained blocking issues remain |
1666
| 16 | Complete failure injection and the 15-minute soak | No unexplained blocking issues remain |
1666 1667
1667 1668
## Final staging readiness checklist
1668 1669

@@ -1690,13 +1691,13 @@ each handoff.

1690 1691
- [x] Owned local gates produce exact-SHA receipts.
1691 1692
- [x] The versioned staging matrix, private evidence report, scanner, recorder,
1692 1693
      validator, and network-free harness dry run exist.
1693
- [x] The versioned controlled-failure matrix and fail-closed 48-hour soak
1694
- [x] The versioned controlled-failure matrix and fail-closed 15-minute soak
1694 1695
      evidence contract exist.
1695 1696
- [ ] Web and distributed staging are isolated from production.
1696 1697
- [ ] Staging has a separate database instance and failure domain.
1697 1698
- [ ] The migration lineage is mapped and rehearsed for every nonempty target.
1698 1699
- [ ] The complete regression matrix passes on one SHA.
1699
- [ ] Failure injection and the 48-hour soak pass.
1700
- [ ] Failure injection and the 15-minute soak pass.
1700 1701
- [ ] The staging evidence report contains no secrets or private content.
1701 1702
- [ ] No production action has occurred.
1702 1703

@@ -1786,7 +1787,7 @@ passes, and the inspected local receipt records zero automatic retries.

1786 1787
## A2. Blocker: staging is not isolated from production today
1787 1788
1788 1789
Gate 12 lists staging isolation as a requirement and Gate 15 calls for failure
1789
injection and a 48-hour soak against staging. **Both are unsafe as currently
1790
injection and a 15-minute soak against staging. **Both are unsafe as currently
1790 1791
provisioned**, and the reason is not a missing control — it is the existing
1791 1792
topology:
1792 1793

@@ -1808,7 +1809,7 @@ Consequences the plan should state explicitly:

1808 1809
1809 1810
- Staging and production share one shared-core instance with a small connection
1810 1811
  budget. Staging load consumes production's connection headroom.
1811
- Running Gate 15 failure injection or a 48-hour soak against staging is
1812
- Running Gate 15 failure injection or a 15-minute soak against staging is
1812 1813
  therefore **a load test against production's database instance.**
1813 1814
- A single-zone instance means one zonal event takes staging and production
1814 1815
  together, so staging cannot serve as evidence of production resilience.
docs/architecture.md modified +1 -1

@@ -197,7 +197,7 @@ The accepted target has two isolated staging lanes:

197 197
Both lanes use staging-only hosts, credentials, buckets, repositories, service
198 198
accounts, and a PostgreSQL instance that does not share a production failure
199 199
domain. Production is out of scope until all hardening gates, the complete
200
staging matrix, failure injection, and the 48-hour soak pass.
200
staging matrix, failure injection, and the 15-minute pinned-candidate soak pass.
201 201
202 202
`OpenAgents.RuntimeConfig` validates the complete behavior-changing settings
203 203
boundary before migrations or traffic. The
docs/decisions/0006-isolate-web-and-distributed-fleet-staging.md modified +1 -1

@@ -23,7 +23,7 @@ Provision two staging lanes:

23 23
Give both lanes staging-only hosts, secrets, service accounts, buckets,
24 24
repositories, and a PostgreSQL instance that is separate from production.
25 25
Disable production promotion until both lanes pass the complete matrix,
26
failure injection, and a 48-hour soak on one exact candidate.
26
failure injection, and a 15-minute soak on one pinned exact candidate.
27 27
28 28
## Consequences
29 29
docs/operations/production-cutover.md modified +3 -1

@@ -40,7 +40,9 @@ waive a failed condition by rebuilding from another commit during the cutover.

40 40
  bucket, or secret.
41 41
- The candidate passes all Gate 14 cases on staging.
42 42
- The same candidate passes controlled failure injection and a measured
43
  48-hour soak without a redeploy.
43
  15-minute soak on the pinned `openagents-staging-release` service. Later
44
  commits can deploy to another staging service without replacing this release
45
  candidate or resetting its clock.
44 46
- A fresh production backup restores into a disposable Cloud SQL instance.
45 47
  The migration-lineage bridge, all remaining migrations, candidate startup,
46 48
  last-known-good startup, row-count checks, and integrity checks pass there.
docs/operations/staging-report-template.md modified +3 -3

@@ -86,9 +86,9 @@ its candidate SHA and image digest with Gate 14.

86 86
The resilience report proves:
87 87
88 88
- All 11 controlled-failure cases and their retry histories.
89
- At least 48 hours without a redeploy.
89
- At least 15 minutes on the pinned release-candidate track.
90 90
- Scheduled typed, memory, voice, tracker, Git, and status canaries.
91
- Five-minute resource samples and a post-soak smoke.
91
- One-minute resource samples and a post-soak smoke.
92 92
- Every known issue with ID, owner, severity, and disposition.
93 93
94 94
An empty known-issue list is valid only when review found no issues. A failure,

@@ -104,7 +104,7 @@ The validator recognizes four states:

104 104
| `draft` | `--draft` | Work is in progress; pending and failed cases may exist |
105 105
| `recorded` | `--recorded` | Every case has an outcome; failures or blocks may remain |
106 106
| `regression_passed` | `--regression` | Every applicable Gate 14 case passed and common evidence is complete |
107
| `complete` | `--final` | Gate 14, failure injection, and the 48-hour soak all passed |
107
| `complete` | `--final` | Gate 14, failure injection, and the 15-minute soak all passed |
108 108
109 109
Only `finalize-report.sh` should change the state and completion timestamp. It
110 110
validates a temporary copy before replacing the report and recomputes
docs/operations/staging-resilience-report-template.md modified +10 -7

@@ -7,7 +7,7 @@ Status: Current Gate 15 evidence contract

7 7
`new-resilience-report.sh` generates one report for the candidate that already
8 8
passed Gate 14. The report binds the candidate manifest, application image,
9 9
release, and Gate 14 report checksum to 11 controlled-failure results and one
10
48-hour soak.
10
15-minute soak on the pinned release-candidate track.
11 11
12 12
## Failure results
13 13

@@ -23,18 +23,21 @@ report schemas and selects the correct strict validator.

23 23
24 24
Completion requires:
25 25
26
- Start and completion UTC timestamps separated by at least 172,800 seconds.
27
- `candidate_identity_stable` set to true and `redeploy_count` set to zero.
28
- At least 576 five-minute metric samples and a sanitized metric receipt.
26
- Start and completion UTC timestamps separated by at least 900 seconds.
27
- A target track of `release-candidate` and service of
28
  `openagents-staging-release`.
29
- `candidate_identity_stable` set to true and `redeploy_count` set to zero for
30
  that pinned service. Deployments to other staging services do not count.
31
- At least 15 one-minute metric samples and a sanitized metric receipt.
29 32
- A sanitized soak timeline receipt.
30 33
- Exact canary IDs, cadences, minimums, completed counts, passed counts, and
31 34
  aggregate receipts.
32
- A full post-soak smoke receipt from the same candidate without a redeploy.
35
- A full post-soak smoke receipt from the same pinned candidate.
33 36
- Zero unexplained errors, data loss, authority expansion, fleet divergence,
34 37
  secret leakage, and unexplained restarts.
35 38
36
The canary minimums are 576 status, 96 typed, 96 memory, 96 tracker, 96 Git, and
37
24 fake-media voice passes.
39
The canary minimums are 15 status, 3 typed, 3 memory, 3 tracker, 3 Git, and 1
40
fake-media voice pass.
38 41
39 42
## Known issues
40 43
docs/operations/staging-resilience.md modified +27 -20

@@ -1,11 +1,12 @@

1
# Run controlled failures and the 48-hour staging soak
1
# Run controlled failures and the 15-minute staging soak
2 2
3 3
Date: 2026-08-20
4 4
5 5
Status: Harness implemented and locally proven; live Gate 15 execution pending
6 6
7 7
Use this runbook after one exact candidate has passed Gate 14. It records all
8
controlled-failure attempts, requires 48 measured hours without a redeploy,
8
controlled-failure attempts, requires 15 measured minutes on a pinned release
9
track,
9 10
enforces scheduled canary and metric minimums, and refuses completion when any
10 11
unexplained harm remains.
11 12

@@ -44,11 +45,11 @@ ops/staging/resilience.sh check

44 45
Expected result:
45 46
46 47
```text
47
Staging resilience harness dry run passed (11 failures; 48-hour soak; no network requests sent).
48
Staging resilience harness dry run passed (11 failures; 15-minute soak; no network requests sent).
48 49
```
49 50
50 51
The check validates the versioned matrix, report generator, evidence scanner,
51
attempt recorder, final validator, exact 48-hour duration, six canary schedules,
52
attempt recorder, final validator, exact 15-minute duration, six canary schedules,
52 53
and fail-closed finalization.
53 54
54 55
## Create the resilience report

@@ -194,24 +195,30 @@ ops/staging/finalize-report.sh --recorded "$resilience_report"

194 195
Corrective attempts return it to draft. Do not start the soak until all 11 last
195 196
attempts pass, the candidate is healthy, and the test data is reconciled.
196 197
197
## Run 48 hours without a redeploy
198
## Run 15 minutes on the pinned release track
198 199
199 200
Record a UTC start time after the final failure-recovery smoke. Keep the web and
200
distributed staging lanes on the exact candidate for at least 172,800 seconds.
201
Any application, configuration, image, release, migration, or fleet redeploy
202
invalidates the soak; set `redeploy_count` honestly and start a new 48-hour run.
201
distributed release-candidate lanes on the exact candidate for at least 900
202
seconds. Deploy later development commits to the ordinary staging service. Do
203
not replace or reconfigure `openagents-staging-release` while its candidate is
204
soaking.
205
206
The report's `redeploy_count` applies only to the pinned release-candidate
207
service and distributed lane. Deployments to another staging service do not
208
reset the clock. A deployment that changes the candidate track still fails the
209
soak because the report must prove one stable SHA and image digest.
203 210
204 211
Use these minimum schedules:
205 212
206
| Canary or sample | Cadence | Minimum over 48 hours |
213
| Canary or sample | Cadence | Minimum over 15 minutes |
207 214
| --- | ---: | ---: |
208
| Status and candidate identity | 5 minutes | 576 passes |
209
| Resource metrics | 5 minutes | 576 complete samples |
210
| Typed chat | 30 minutes | 96 passes |
211
| Memory write/read/forget | 30 minutes | 96 passes |
212
| Tracker read/write/cleanup | 30 minutes | 96 passes |
213
| Git clone/fetch/push/cleanup | 30 minutes | 96 passes |
214
| Fake-media voice lifecycle | 2 hours | 24 passes |
215
| Status and candidate identity | 1 minute | 15 passes |
216
| Resource metrics | 1 minute | 15 complete samples |
217
| Typed chat | 5 minutes | 3 passes |
218
| Memory write/read/forget | 5 minutes | 3 passes |
219
| Tracker read/write/cleanup | 5 minutes | 3 passes |
220
| Git clone/fetch/push/cleanup | 5 minutes | 3 passes |
221
| Fake-media voice lifecycle | 15 minutes | 1 pass |
215 222
216 223
Each canary receipt must bind the candidate SHA, image digest, scheduled and
217 224
actual UTC time, bounded outcome, attempt count, and cleanup result. A retry does

@@ -229,8 +236,8 @@ leaked process, and content-bearing log entry during the soak. A high or critica

229 236
known issue must be resolved. A low or medium issue can be accepted only with a
230 237
named owner and an explicit non-blocking disposition.
231 238
232
After 48 hours, run the full Gate 14 public and authenticated smoke again without
233
redeploying. Record the exact end time after that smoke.
239
After 15 minutes, run the full Gate 14 public and authenticated smoke again
240
against the pinned candidate. Record the exact end time after that smoke.
234 241
235 242
## Complete and validate the resilience report
236 243

@@ -239,8 +246,8 @@ the resilience report's `evidence/` directory. Populate the soak counts and

239 246
booleans described in the
240 247
[resilience evidence contract](staging-resilience-report-template.md).
241 248
242
Finalization requires all failure cases passed, at least 48 measured hours,
243
zero redeploys, stable candidate identity, every canary minimum, at least 576
249
Finalization requires all failure cases passed, at least 15 measured minutes,
250
zero candidate-track redeploys, stable candidate identity, every canary minimum, at least 15
244 251
metric samples, and zero unexplained error, data-loss, authority-expansion,
245 252
fleet-divergence, secret-leak, or restart counts:
246 253
docs/sarah-integration-plan.md modified +1 -1

@@ -69,4 +69,4 @@ The source migration being complete does not make the application production

69 69
ready. Documentation reconciliation, dependency and component consolidation,
70 70
route authority, token lifecycle, tenant scoping, recovery, build isolation,
71 71
transactional fleet deployment, staging isolation, failure injection, and the
72
48-hour soak remain governed by the current hardening plan.
72
15-minute pinned-candidate soak remain governed by the current hardening plan.
ops/staging/new-resilience-report.sh modified +2

@@ -51,6 +51,8 @@ write_report() {

51 51
      target: {
52 52
        environment: "staging",
53 53
        base_url: "https://staging.openagents.com",
54
        track: "release-candidate",
55
        service: "openagents-staging-release",
54 56
        project: $candidate.target.project,
55 57
        region: $candidate.target.region
56 58
      },
ops/staging/resilience-matrix.json modified +10 -10

@@ -1,6 +1,6 @@

1 1
{
2 2
  "schema": "openagents.staging-resilience-matrix.v1",
3
  "revision": 1,
3
  "revision": 2,
4 4
  "failure_injections": [
5 5
    {"id": "failure-001", "title": "Provider timeout, malformed event, and premature stream closure recover without a false completion"},
6 6
    {"id": "failure-002", "title": "PostgreSQL restart and temporary connection exhaustion recover without data loss"},

@@ -15,16 +15,16 @@

15 15
    {"id": "failure-011", "title": "Recording upload failure and late final chunk remain generation-fenced and recoverable"}
16 16
  ],
17 17
  "soak": {
18
    "required_duration_seconds": 172800,
19
    "metric_sample_cadence_seconds": 300,
20
    "minimum_metric_samples": 576,
18
    "required_duration_seconds": 900,
19
    "metric_sample_cadence_seconds": 60,
20
    "minimum_metric_samples": 15,
21 21
    "canaries": [
22
      {"id": "status", "cadence_seconds": 300, "minimum_passes": 576},
23
      {"id": "typed", "cadence_seconds": 1800, "minimum_passes": 96},
24
      {"id": "memory", "cadence_seconds": 1800, "minimum_passes": 96},
25
      {"id": "tracker", "cadence_seconds": 1800, "minimum_passes": 96},
26
      {"id": "git", "cadence_seconds": 1800, "minimum_passes": 96},
27
      {"id": "voice", "cadence_seconds": 7200, "minimum_passes": 24}
22
      {"id": "status", "cadence_seconds": 60, "minimum_passes": 15},
23
      {"id": "typed", "cadence_seconds": 300, "minimum_passes": 3},
24
      {"id": "memory", "cadence_seconds": 300, "minimum_passes": 3},
25
      {"id": "tracker", "cadence_seconds": 300, "minimum_passes": 3},
26
      {"id": "git", "cadence_seconds": 300, "minimum_passes": 3},
27
      {"id": "voice", "cadence_seconds": 900, "minimum_passes": 1}
28 28
    ]
29 29
  }
30 30
}
ops/staging/resilience.sh modified +6 -6

@@ -17,20 +17,20 @@ fi

17 17
18 18
jq -e '
19 19
  .schema == "openagents.staging-resilience-matrix.v1" and
20
  .revision == 1 and
20
  .revision == 2 and
21 21
  (.failure_injections | type == "array" and length == 11) and
22 22
  ([.failure_injections[].id] | length == (unique | length)) and
23 23
  all(.failure_injections[];
24 24
    (.id | test("^failure-[0-9]{3}$")) and
25 25
    (.title | type == "string" and length > 0)) and
26
  .soak.required_duration_seconds == 172800 and
27
  .soak.metric_sample_cadence_seconds == 300 and
28
  .soak.minimum_metric_samples == 576 and
26
  .soak.required_duration_seconds == 900 and
27
  .soak.metric_sample_cadence_seconds == 60 and
28
  .soak.minimum_metric_samples == 15 and
29 29
  (.soak.canaries | type == "array" and length == 6) and
30 30
  ([.soak.canaries[].id] | sort) == ["git", "memory", "status", "tracker", "typed", "voice"] and
31 31
  all(.soak.canaries[]; . as $canary |
32 32
    ($canary.cadence_seconds | type == "number" and . > 0 and floor == .) and
33
    ($canary.minimum_passes | type == "number" and . >= (172800 / $canary.cadence_seconds) and floor == .))
33
    ($canary.minimum_passes | type == "number" and . >= (900 / $canary.cadence_seconds) and floor == .))
34 34
' "$matrix" >/dev/null || {
35 35
  echo "staging resilience matrix contract failed" >&2
36 36
  exit 1

@@ -58,4 +58,4 @@ chmod 600 "$evidence"

58 58
"$script_dir/record-result.sh" "$report" failure-001 passed dry-run "$evidence" >/dev/null
59 59
"$script_dir/validate-resilience-report.sh" --draft "$report" >/dev/null
60 60
61
echo "Staging resilience harness dry run passed (11 failures; 48-hour soak; no network requests sent)."
61
echo "Staging resilience harness dry run passed (11 failures; 15-minute soak; no network requests sent)."
ops/staging/validate-resilience-report.sh modified +2

@@ -87,6 +87,8 @@ jq -e \

87 87
  (.completed_at == null or (.completed_at | timestamp)) and
88 88
  .target.environment == "staging" and
89 89
  .target.base_url == "https://staging.openagents.com" and
90
  .target.track == "release-candidate" and
91
  .target.service == "openagents-staging-release" and
90 92
  (.target.project | type == "string" and test("stag"; "i")) and
91 93
  (.target.region | nonempty) and
92 94
  (.candidate.git_sha | type == "string" and test("^[0-9a-f]{40}$")) and
test/openagents/staging_resilience_contract_test.exs modified +19 -6

@@ -26,18 +26,31 @@ defmodule OpenAgents.StagingResilienceContractTest do

26 26
    assert Enum.uniq_by(matrix["failure_injections"], & &1["id"]) ==
27 27
             matrix["failure_injections"]
28 28
29
    assert matrix["soak"]["required_duration_seconds"] == 48 * 60 * 60
30
    assert matrix["soak"]["minimum_metric_samples"] == 576
29
    assert matrix["revision"] == 2
30
    assert matrix["soak"]["required_duration_seconds"] == 15 * 60
31
    assert matrix["soak"]["minimum_metric_samples"] == 15
31 32
32 33
    assert matrix["soak"]["canaries"] |> Enum.map(& &1["id"]) |> Enum.sort() ==
33 34
             ~w(git memory status tracker typed voice)
35
36
    report =
37
      Path.join(
38
        System.tmp_dir!(),
39
        "openagents-resilience-target-#{System.unique_integer([:positive])}.json"
40
      )
41
42
    on_exit(fn -> File.rm(report) end)
43
    assert {_, 0} = command("new-resilience-report.sh", ["--dry-run", report])
44
    target = report |> File.read!() |> Jason.decode!() |> Map.fetch!("target")
45
    assert target["track"] == "release-candidate"
46
    assert target["service"] == "openagents-staging-release"
34 47
  end
35 48
36 49
  test "the resilience dry run is local and fail-closed" do
37 50
    {output, 0} = command("resilience.sh", ["check"])
38 51
39 52
    assert output =~ "11 failures"
40
    assert output =~ "48-hour soak"
53
    assert output =~ "15-minute soak"
41 54
    assert output =~ "no network requests sent"
42 55
  end
43 56

@@ -49,7 +62,7 @@ defmodule OpenAgents.StagingResilienceContractTest do

49 62
    assert validator =~ "report_dir=$(realpath"
50 63
  end
51 64
52
  test "finalization requires every recovery, 48 measured hours, canaries, and zero unexplained harm",
65
  test "finalization requires every recovery, 15 measured minutes, canaries, and zero unexplained harm",
53 66
       %{test_root: test_root} do
54 67
    report = Path.join(test_root, "report.json")
55 68
    evidence = Path.join(test_root, "recovery.json")

@@ -68,7 +81,7 @@ defmodule OpenAgents.StagingResilienceContractTest do

68 81
    decoded = report |> File.read!() |> Jason.decode!()
69 82
    [reference | _] = hd(decoded["failure_injections"])["evidence"]
70 83
    completed_at = DateTime.utc_now() |> DateTime.add(-1, :second) |> DateTime.truncate(:second)
71
    started_at = DateTime.add(completed_at, -(48 * 60 * 60), :second)
84
    started_at = DateTime.add(completed_at, -(15 * 60), :second)
72 85
73 86
    soak = decoded["soak"]
74 87

@@ -86,7 +99,7 @@ defmodule OpenAgents.StagingResilienceContractTest do

86 99
      |> Map.put("completed_at", DateTime.to_iso8601(completed_at))
87 100
      |> Map.put("candidate_identity_stable", true)
88 101
      |> Map.put("redeploy_count", 0)
89
      |> Map.put("metric_sample_count", 576)
102
      |> Map.put("metric_sample_count", 15)
90 103
      |> Map.put("timeline_receipt", reference)
91 104
      |> Map.put("metrics_receipt", reference)
92 105
      |> Map.put("canaries", canaries)

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