4.1 KiB
Hermes Channel Negotiation Smoke Test — Results
Date: 2026-06-27
Branch: lithosananke
Script: tools/hermes_channel_smoke.sh
Status: PASS — 16/16
Summary
Full channel negotiation lifecycle validated: CH-REQUEST → CH-ACCEPT → business messaging → CH-CLOSE → HERMES-TICK (hot, no reap) → zero heat → HERMES-TICK (reap) → CH-ACTIVE = COMMON-CH only.
Output
>>> [1] CH-REQUEST Hera->Hermes negotiate
>>> [2] CH-ACCEPT (Hermes creates ephemeral ch)
>>> [3] CH-STATE (expect 1=OPEN): 1
>>> [4] CH-ID (expect non-0): 2
>>> [5] CH-OWNER (expect 1=Hermes): 1
>>> [6] Y sends chanid to X on COMMON
>>> [7] COMMON-CH OPEN (expect 1): 1
>>> [8] business msg on ephemeral ch
>>> [9] CH-CLOSE
>>> [10] CH-STATE (expect 2=CLOSING): 2
>>> [11] HERMES-TICK (hot channel, no reap yet)
>>> [12] CH-STATE after tick (expect 2=CLOSING): 2
>>> [13] Zero heat + HERMES-TICK (reap)
>>> [14] COMMON-CH OPEN (expect 1): 1
>>> [15] CH-ACTIVE is COMMON-CH (expect 1): -1
>>> === Channel negotiation smoke complete ===
Validation Results
| # | Check | Result |
|---|---|---|
| 1 | test started | PASS |
| 2 | CH-ACCEPT ok | PASS |
| 3 | ch state OPEN | PASS |
| 4 | ch id non-zero | PASS |
| 5 | ch owner Hermes | PASS |
| 6 | chanid sent | PASS |
| 7 | COMMON still OPEN | PASS |
| 8 | business msg | PASS |
| 9 | CH-CLOSE called | PASS |
| 10 | state CLOSING | PASS |
| 11 | tick no reap | PASS |
| 12 | still CLOSING after tick | PASS |
| 13 | zero+tick reap | PASS |
| 14 | COMMON still OPEN after | PASS |
| 15 | CH-ACTIVE is COMMON-CH | PASS |
| 16 | smoke complete | PASS |
Result: 16 passed, 0 failed
Architecture Notes
Protocol path confirmed:
| Step | Actor | Action |
|---|---|---|
| 1 | Hera (0) | CH-REQUEST on COMMON → MSG-SEND with SPAWN-EVENT type |
| 2 | Hermes (1) | CH-ACCEPT → ephemeral channel allocated, ID=2, OWNER=1, STATE=OPEN |
| 3–5 | — | Verify ch fields: STATE=1, ID=2 (non-zero), OWNER=1 |
| 6 | Hermes | Sends ch ID to Hera on COMMON via MSG-SEND |
| 7 | — | COMMON-CH still OPEN after in-use |
| 8 | — | Business message on ephemeral channel |
| 9–10 | Hermes | CH-CLOSE → STATE becomes CH-CLOSING (2) |
| 11–12 | — | HERMES-TICK with heat > 0: channel not reaped, STATE still 2 |
| 13 | — | Zero heat manually; HERMES-TICK reaps ephemeral channel |
| 14–15 | — | COMMON-CH still OPEN; CH-ACTIVE = COMMON-CH only |
CH-REAP-SAFE rebuild-the-list pattern:
Resets CH-ACTIVE to 0 at the start of the walk, then re-inserts non-reaped channels. Freed channels are simply skipped. This avoids the prior double-free bug where freed nodes remained linked in CH-ACTIVE.
CH-ACCEPT stack fix:
CH-ACTIVE ! → DUP CH-ACTIVE ! — the original consumed the only copy of ch, returning an empty stack instead of ( ch ).
Block size constraint:
Test sequence split across blocks 9998 (helpers + VARIABLE MYCHAN + colon words) and 9999/9000 (two-part execution), each well under the 1024-byte limit. Block 9999 alone was 1159 bytes, causing silent truncation before steps [15] and "smoke complete".
Compile-only constraint:
IF THEN ELSE DO LOOP are compile-only in StarForth — cannot appear in interpreted block context. All control flow in helpers is wrapped in colon word definitions (block 9998). The abort check ASSERT-CH wraps the IF...BYE...THEN idiom. TRIP-ZERO-CH wraps the zero-heat + HERMES-TICK sequence. Both are defined before CD-INIT so they are available when called from block 9999/9000.
Bugs Fixed (prerequisite work)
| Bug | Description | Fix |
|---|---|---|
| H4 | CH-REAP-SAFE freed channel stayed in CH-ACTIVE → double-free on next tick |
Rebuild-the-list: reset CH-ACTIVE=0, re-insert non-reaped channels only |
| H5 | CH-ACCEPT CH-ACTIVE ! consumed the only copy of ch → empty stack return |
DUP CH-ACTIVE ! keeps ch on data stack |
What This Does Not Test
- Multi-VM channel negotiation across separate process boundaries
- MBR (member) records within channels
- CH-MAX exhaustion (free list empty path)
- Concurrent channel lifecycle (multiple ephemeral channels)