ACK/NACK and private-channel negotiation -- FABRIC-3.6.md task 3.6

Extracted sk_hermes_send_one() from sk_hermes_publish()'s own
per-subscriber body -- one code path for both point-to-point and
fan-out delivery, so the ledger can never diverge between them.
Point-to-point addressing turned out to be load-bearing, not
incidental: sk_hermes_publish()'s fan-out sets msg->to to whichever
member it is iterating, so a negotiation message "published" to the
common channel would spuriously reach every member, not just the real
target (checked with advisor() before building the naive version).
"Over the common channel" means every VM is reachable from birth (task
3.2), not that the exchange itself fans out -- messaging.4th's own
CH-REQUEST carried an explicit `to` for the same reason.

sk_hermes_channel_request/respond/close build the mechanics: request ->
grant (creates a private channel, subscribes both parties, sends
CH_GRANT + one ACK) or NACK ("a deny is a NACK", SXLV.1 -- no separate
type); close authorized by membership alone. The grant/deny decision is
a plain caller-supplied `approved` bool -- task 3.7 replaces the call
site that produces it with a real ACL.4th query, not this signature.

Self-test covers the task's own three checks plus a sibling advisor()
flagged: an approved respond() whose channel creation itself fails
(table exhausted) must still fall through to NACK, not a silent false
grant or half-open channel -- verified by exhausting the whole channel
table and confirming the fallback.

Bug found and fixed before this was called done: the first draft
dropped a message via pending_pop() alone, without releasing it first,
leaking its Stadium heat and failing the self-test's own ledger
baseline check (logs/20260922-065946/amd64/, kept as audit trail).
Fixed and re-verified PASS on all three architectures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-22 07:25:51 -04:00
co-authored by Claude Sonnet 5
parent 8a2ee0fdad
commit 205a49ecd0
8 changed files with 37482 additions and 44 deletions
+42 -1
View File
@@ -938,11 +938,52 @@ ruling]** cannot be written precisely until Captain Bob settles the named sub-it
byte-exact against the original. Ledger audit and `stadium_conserved()` hold before and
after. `dict_hash` for Hermes/Hestia unmoved and identical across all three architectures
(this task touches no capsule).
- [ ] **3.6** — **ACK/NACK and private-channel negotiation** (B1) **[needs ruling 3.0a]**.
- [x] **3.6** — **ACK/NACK and private-channel negotiation** (B1) **[needs ruling 3.0a]**.
`request` → `grant`/`deny` on the common channel; a grant creates a private topic;
close tears it down. ACK/NACK are message types on the existing allocator.
*Check:* grant path, deny path, close path; heat conserved (ledger + `stadium_conserved()`)
across all three; a NACK'd request leaves no topic behind.
2026-09-22 · `logs/20260922-070455/amd64/`, `logs/20260922-071004/aarch64/`,
`logs/20260922-071525/riscv64/` (the successful runs; `logs/20260922-065946/amd64/` is a
genuine first attempt that hit a real bug, caught by the self-test itself before this task
was called done -- kept, not deleted, per the never-delete-logs convention) — all three
reach `[zuse@Hera] ok>`, zero `UNKNOWN WORD`, `mkcapsule --lint capsules/` clean (38 files,
0 violations, unchanged -- no capsule touched).
**Design finding (checked with `advisor()`):** the ruling's "over the common channel"
cannot mean `sk_hermes_publish()`-style fan-out -- that sets `msg->to` to whichever member
it is iterating, so every common-channel member would receive its own copy of a request
believing it was the addressee, drawing heat for a message only one VM should ever see.
`messaging.4th`'s own `CH-REQUEST ( type from to paddr plen -- )` carried an explicit `to`
for the same reason -- point-to-point addressing was in the original protocol from the
start; "over the common channel" means every VM is reachable from birth (task 3.2), not
that the exchange itself fans out. Extracted `sk_hermes_send_one()` from
`sk_hermes_publish()`'s own per-subscriber body (alloc/set-fields/find-or-create-queue/
push/roll-back-on-refusal) so both callers share one code path and the ledger can never
diverge between them -- `sk_hermes_publish()` now just loops it per member.
Message types: `CH_REQUEST`/`CH_GRANT`/`ACK`/`NACK`/`CH_CLOSE`, chosen clear of
`messaging.4th`'s own live/reserved type space (2/3/4/7/8/9/253/255), not itself a ruling.
"A deny is a NACK" (SXLV.1) -- no separate `CH_DENY` type. ACK sent once, for the ruled
channel-open+delivery moment. The grant/deny *decision* is a plain caller-supplied
`approved` bool (named for what it is, not `allow`) -- task 3.7 replaces the call site that
produces it with a real `ACL.4th` query, not this function's signature. Close authority is
membership alone (either party may close a channel it belongs to) -- narrowest defensible
rule given both are already trusted members, not itself a ruling.
**Sibling case `advisor()` flagged as the one a green boot would hide**, beyond the task's
own three checks: an `approved==1` respond() whose channel creation itself fails (table
exhausted) must still fall through to NACK, not a silent false grant or a half-open
channel -- self-test exhausts the entire channel table, confirms the fallback, then
restores capacity.
**Bug found and fixed before this was called done:** the first self-test draft
(`logs/20260922-065946/amd64/`, `FAIL`) called `sk_hermes_pending_pop()` directly on the
deny path's dropped request copy without releasing it first -- `pending_pop()` only
advances the queue per its own doc comment, so the message's Stadium heat was never
returned, leaking held heat and failing the self-test's own `held0`/`held1` baseline check.
Fixed to peek+release+pop, matching every other drain in the same self-test; re-verified
PASS on all three architectures after the fix.
`dict_hash` for Hermes/Hestia unmoved and identical across all three architectures (this
task touches no capsule). Artemis's own `dict_hash` differs run-to-run as already
documented (task 3.1's own finding) -- shared `disk/artemis.img` state accumulated across
this session's many prior boots, not an architecture divergence.
- [ ] **3.7** — **Channel-open policy hook** **[needs ruling 3.0b]**. Kernel-Hermes asks
`ACL.4th`, never decides in C, never gates on `zuse_session`. *Check:* a denied open is
denied by FORTH policy, with the C unchanged when the policy changes.