docs(mesh): live-gate step 8 — single-oldest-central rotation discriminator for the per-node comm-actor property (replaces the dropped Task 6 assertion)

This commit is contained in:
Joseph Doherty
2026-07-22 17:18:49 -04:00
parent 68b804c88c
commit d246a7e44d
2 changed files with 13 additions and 4 deletions
@@ -917,6 +917,14 @@ Use `AwaitAssert` with explicit timeouts throughout; no `Thread.Sleep`.
> The Phase 1 lesson that earns this task: *"the bad thing didn't happen"* is not evidence the mechanism works. Assertion 3 is what converts assertions 12 from a hope into a measurement.
> **Scope limit — what this test can NOT see:** with exactly one node per side, "registered per
> node" and "registered as a singleton" are indistinguishable by construction, so this test does
> NOT cover the per-node property whose structural assertions Task 6 dropped (both discriminators
> were unfalsifiable — see the dead-end record there). That property's discriminating coverage is
> **live-gate step 8** (stop the OLDEST central; the survivor's comm actor must receive a node ack
> inside the downing window — a singleton comm actor, hosted on the stopped oldest, cannot). Do not
> close the phase with step 8 skipped: it is the honest replacement for the dropped Task 6 test.
If this test proves irreducibly flaky, **do not delete it and do not weaken it into a relay test** — mark it `[Trait("Category","MeshBoundary")]`, exclude it from the default run, and record the flakiness in the live-gate doc as an open item. A quarantined honest test beats a green vacuous one.
**Commit**
@@ -969,9 +977,10 @@ git commit -m "docs(mesh): MeshTransport config, Phase 2 as shipped, design-doc
| 5 | Set that node's `MaintenanceMode = 1`, redeploy | Seals green without it |
| 6 | AdminUI Restart + Reconnect on a driver | Reaches the owning node; UI reports "dispatched" |
| 7 | AdminUI alarm ack on `/alerts` | Reaches the owning node's engine |
| 8 | Stop **both** central nodes, then start a driver node | Node boots; ack is dropped with a Warning and **not** buffered; no crash loop |
| 9 | Restart central, redeploy | Recovers with no operator action beyond the redeploy |
| 10 | Grep every node's logs for frame-size warnings | None (the deploy path is payload-free — this is the canary that stays quiet) |
| 8 | **Contact rotation / the per-node discriminator.** Stop the **OLDEST** central node only (the singleton host — stopping the younger proves nothing, a singleton would also live on the still-up oldest), then immediately restart one driver node so it emits its boot-time ack | The **surviving** central's `central-communication` actor logs receipt of the ack **within seconds — inside the 15 s downing window**, before singleton migration could explain delivery. This is the discriminating test for "registered per node, NOT a singleton" — the property Task 6's structural assertions could not falsify (both dead ends recorded there): a singleton-shaped comm actor was hosted on the node just stopped, so nothing could receive on the survivor until downing + migration (~25 s). Receipt at the comm actor is the pass signal; the onward `Forward` to the coordinator singleton proxy is EXPECTED to buffer until the singleton migrates — do not count that against the step (full recovery is step 10's job) |
| 9 | Stop **both** central nodes, then start a driver node | Node boots; ack is dropped with a Warning and **not** buffered; no crash loop |
| 10 | Restart central, redeploy | Recovers with no operator action beyond the redeploy |
| 11 | Grep every node's logs for frame-size warnings | None (the deploy path is payload-free — this is the canary that stays quiet) |
Step 8 is the one that proves the `buffer-size = 0` decision. If a command *does* arrive late after central returns, buffering is still on somewhere and the DB is no longer the single record of truth.
@@ -123,7 +123,7 @@
},
{
"id": 10,
"subject": "Task 10: live gate on docker-dev (10 steps; step 4 and step 8 are the ones that matter)",
"subject": "Task 10: live gate on docker-dev (11 steps; step 4 = deploy-seal semantics, step 8 = per-node/contact-rotation discriminator [replaces the dropped Task 6 assertion \u2014 do not skip], step 9 = both-centrals drop behavior)",
"status": "pending",
"classification": "high-risk",
"blockedBy": [