Channel table + common channel, inert (B1) -- FABRIC-3.6.md task 3.2

Adds SkHermesChannel: a channel is an index into a boot-time,
stadium_max_vm_count()-sized table (same sizing pattern task 3.1
established for the switch table -- no separate numeric rule was ruled
for this table, so task 3.1's bound is extended directly, flagged as
such rather than restated as a new ruling). No name field, mirroring
messaging.4th's own nameless CH-ARENA.

The common channel (index 0) is created at boot and permanent. Hera
subscribes explicitly in kernel_main.c (she is the one VM never born
through capsule_birth_baby()); every other VM -- Tripod fleet and
future WIREBIND identities alike -- subscribes inside
capsule_birth_baby() itself, the single choke point every other birth
already passes through.

Inert: no publish, no dispatch, no ACK/NACK, no ACL hook (tasks 3.3,
3.6, 3.7). Verified live on all three architectures: channel table
sized to 50/202/50 slots (matching switch-signal's own per-arch
sizing), common-channel fleet self-test confirms all four Tripod
members are members, and a synthetic create/subscribe/unsubscribe/
destroy round-trip against a private topic passes, including refusing
to destroy the common channel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-22 00:25:13 -04:00
co-authored by Claude Sonnet 5
parent c19ef365fe
commit a4afdfa591
8 changed files with 28102 additions and 1 deletions
+11
View File
@@ -38,6 +38,7 @@
#include "starkernel/session.h" /* session_register()/session_set_pinned() -- FABRIC-2.md §H.12 step 5 */
#include "starkernel/timer.h" /* timer_calibration_record()->vm_mode -- FABRIC-2.md §I.5 CONTRIB trust tier */
#include "starkernel/capsule_vm_switch_signal.h" /* FABRIC-3.md §XXVIII Stage 4 -- capsule_vm_force_reap() */
#include "starkernel/vm/kernel_hermes.h" /* FABRIC-3.6.md task 3.2 -- sk_hermes_channel_subscribe() */
#include "word_source/include/framebuffer_words.h" /* FABRIC-3.6.md task 1.8 -- register_framebuffer_words() */
#include "vm.h"
#include "platform_alloc.h"
@@ -857,6 +858,16 @@ CapsuleRunResult capsule_birth_baby(
entry->state = VM_STATE_LIVE;
entry->birth_dict_hash = dict_hash;
/* FABRIC-3.6.md task 3.2 (B1 / FABRIC-3.5.md §XLV.1): every VM joins
* the common channel at birth. This is the single choke point every
* non-Hera birth passes through (BIRTH, WIREBIND identities, DoE
* workload births alike) -- Hera herself is subscribed explicitly in
* kernel_main.c, the one birth path that never reaches here. Soft
* failure, same posture as every other call in this function -- a
* refused subscription (table exhausted) is logged nowhere special
* and does not fail the birth itself. */
(void)sk_hermes_channel_subscribe(SK_HERMES_CHANNEL_COMMON, vm_id);
capsule_parity_log_birth(vm_id, cap->capsule_id, cap->content_hash, dict_hash);
/* §H.12 step 11: applied AFTER dict_hash/parity logging above, not
+74
View File
@@ -535,12 +535,29 @@ static void kernel_main_deep(BootInfo *boot_info) {
* (capacity 0) rather than treating this as fatal. */
(void)sk_vm_switch_signal_boot_init();
/* Kernel-Hermes channel table: boot-time allocation (FABRIC-3.6.md
* task 3.2, B1 / FABRIC-3.5.md §XLV.1), sized from
* stadium_max_vm_count() -- same ordering requirement and soft-failure
* posture as the allocations immediately above. Creates the permanent
* common channel (empty); every VM joins it at birth (Hera explicitly
* below, every baby VM via capsule_birth_baby()'s own task 3.2
* wiring). */
(void)sk_hermes_channels_boot_init();
/* item 4.1, FABRIC-0.md item 3.6/§17.7: actually enforce "Hera is patron
* zero" before anything else can land on cell 0 via the free list, then
* bring up the word layer's map. Both must happen before the first word
* ever dispatches -- capsule birth below runs init.4th, which dispatches
* words. */
(void)stadium_birth_hera();
/* Task 3.2: Hera is the one VM never born through capsule_birth_baby()
* (she is Mama, registered directly in capsule_vm_registry_init() and
* granted her quota by stadium_birth_hera() just above) -- so her
* common-channel subscription is explicit here rather than reached
* through the shared baby-birth hook below. */
(void)sk_hermes_channel_subscribe(SK_HERMES_CHANNEL_COMMON, vm_uuid_hera());
stadium_words_init();
stadium_blocks_init(); /* FABRIC-2.md §B: block-patron layer, same ordering as words */
@@ -1277,6 +1294,63 @@ static void kernel_main_deep(BootInfo *boot_info) {
}
}
/* FABRIC-3.6.md task 3.2 (B1) self-test: confirm every live fleet
* member is a common-channel member (the birth-time subscription just
* exercised for real, above -- Hera explicitly, Hermes/Hestia/Artemis
* through capsule_birth_baby()'s own task 3.2 hook), then create and
* destroy a synthetic private topic against a synthetic VM id (lo=5,
* distinct from every other synthetic id this file already uses --
* lo=1 Stadium quota grant, lo=3 kernel-Hermes alloc/release). Diagnostic
* only, same posture as every other self-test block in this function:
* never used for anything else, never perturbs the real fleet. */
{
VMRegistryEntry hera_ck, hermes_ck, hestia_ck, artemis_ck;
int fleet_ok = 1;
if (capsule_vm_find_by_name_nocase("Hera", &hera_ck) != 0 ||
!sk_hermes_channel_is_member(SK_HERMES_CHANNEL_COMMON, hera_ck.vm_id)) fleet_ok = 0;
if (capsule_vm_find_by_name_nocase("Hermes", &hermes_ck) != 0 ||
hermes_ck.state != VM_STATE_LIVE ||
!sk_hermes_channel_is_member(SK_HERMES_CHANNEL_COMMON, hermes_ck.vm_id)) fleet_ok = 0;
if (capsule_vm_find_by_name_nocase("Hestia", &hestia_ck) != 0 ||
hestia_ck.state != VM_STATE_LIVE ||
!sk_hermes_channel_is_member(SK_HERMES_CHANNEL_COMMON, hestia_ck.vm_id)) fleet_ok = 0;
if (capsule_vm_find_by_name_nocase("Artemis", &artemis_ck) != 0 ||
artemis_ck.state != VM_STATE_LIVE ||
!sk_hermes_channel_is_member(SK_HERMES_CHANNEL_COMMON, artemis_ck.vm_id)) fleet_ok = 0;
console_puts("Kernel-Hermes common-channel fleet self-test: ");
console_println(fleet_ok ? "PASS" : "FAIL");
print_uint(" common channel members=", (uint64_t)sk_hermes_channel_member_count(SK_HERMES_CHANNEL_COMMON));
print_uint(" channel table capacity=", (uint64_t)sk_hermes_channel_capacity());
{
VMUuid topic_test_id;
int ch;
int topic_ok = 1;
topic_test_id.hi = 0;
topic_test_id.lo = 5;
ch = sk_hermes_channel_create();
if (ch < 0) topic_ok = 0;
if (topic_ok && sk_hermes_channel_is_member(ch, topic_test_id)) topic_ok = 0;
if (topic_ok && sk_hermes_channel_subscribe(ch, topic_test_id) != 0) topic_ok = 0;
if (topic_ok && !sk_hermes_channel_is_member(ch, topic_test_id)) topic_ok = 0;
if (topic_ok && sk_hermes_channel_member_count(ch) != 1) topic_ok = 0;
if (topic_ok && sk_hermes_channel_unsubscribe(ch, topic_test_id) != 0) topic_ok = 0;
if (topic_ok && sk_hermes_channel_is_member(ch, topic_test_id)) topic_ok = 0;
if (topic_ok && sk_hermes_channel_destroy(ch) != 0) topic_ok = 0;
/* Destroyed channel must refuse every further op against it. */
if (topic_ok && sk_hermes_channel_subscribe(ch, topic_test_id) == 0) topic_ok = 0;
/* The common channel must never be destroyable. */
if (topic_ok && sk_hermes_channel_destroy(SK_HERMES_CHANNEL_COMMON) == 0) topic_ok = 0;
console_puts("Kernel-Hermes synthetic private-topic self-test: ");
console_println(topic_ok ? "PASS" : "FAIL");
}
}
/* Decided 2026-09-05: no console for the running system unless a
* thumbdrive is present -- headless by default (EMERGENCY_CONSOLE_
* ENABLED off), reusing that flag's own existing "does this build
+145
View File
@@ -48,8 +48,27 @@
#include "starkernel/vm/kernel_hermes.h"
#include "starkernel/vm/stadium.h"
#include "starkernel/q48_16.h"
#include "starkernel/kmalloc.h"
#include "console.h"
/* Freestanding: no libc printf. Prints an unsigned decimal, no leading
* zeros -- same small helper stadium.c/capsule_vm_switch_signal.c each
* carry their own copy of. */
static void console_put_u64(uint64_t v) {
char buf[21];
int i = 20;
buf[20] = '\0';
if (v == 0) {
console_puts("0");
return;
}
while (v > 0 && i > 0) {
buf[--i] = (char)('0' + (v % 10));
v /= 10;
}
console_puts(&buf[i]);
}
static SkHermesMessage sk_hermes_msgs[SK_HERMES_MSG_MAX];
/* Task 2.4 (item 28), FABRIC-3.5.md SXXXVII.3/SXL.4's four counters.
@@ -247,4 +266,130 @@ int sk_hermes_decay(SkHermesMessage *msg) {
return 0;
}
/* Task 3.2 (item 27, B1 / FABRIC-3.5.md SXLV.1): the channel table. See
* kernel_hermes.h's own doc comment on this section for scope and sizing
* reasoning. */
static SkHermesChannel *sk_hermes_channels = (SkHermesChannel *)0;
static int sk_hermes_channel_capacity_val = 0;
int sk_hermes_channels_boot_init(void) {
size_t max_vm_count = stadium_max_vm_count();
SkHermesChannel *table;
size_t i;
if (max_vm_count == 0) return -1; /* Stadium not yet initialized */
table = (SkHermesChannel *)kmalloc(max_vm_count * sizeof(SkHermesChannel));
if (!table) return -1;
for (i = 0; i < max_vm_count; i++) {
memset(&table[i], 0, sizeof(SkHermesChannel));
}
/* The common channel exists from boot (SXLV.1), empty -- VMs join it
* at birth (kernel_main.c/capsule_birth.c task 3.2 wiring), not here. */
table[SK_HERMES_CHANNEL_COMMON].in_use = 1;
sk_hermes_channels = table;
sk_hermes_channel_capacity_val = (int)max_vm_count;
console_puts("Kernel-Hermes: ");
console_put_u64((uint64_t)max_vm_count);
console_println(" channel slots");
return 0;
}
int sk_hermes_channel_capacity(void) {
return sk_hermes_channel_capacity_val;
}
static int channel_valid(int channel_id) {
return sk_hermes_channels &&
channel_id >= 0 &&
channel_id < sk_hermes_channel_capacity_val &&
sk_hermes_channels[channel_id].in_use;
}
int sk_hermes_channel_create(void) {
int i;
if (!sk_hermes_channels) return -1;
/* Index 0 is always in_use (the common channel), so this scan never
* returns it -- start at 1 purely as a scan-cost micro-optimisation,
* not for correctness (the in_use check alone would already skip it). */
for (i = 1; i < sk_hermes_channel_capacity_val; i++) {
if (!sk_hermes_channels[i].in_use) {
memset(&sk_hermes_channels[i], 0, sizeof(SkHermesChannel));
sk_hermes_channels[i].in_use = 1;
return i;
}
}
return -1;
}
int sk_hermes_channel_destroy(int channel_id) {
if (channel_id == SK_HERMES_CHANNEL_COMMON) return -1; /* permanent, SXLV.1 */
if (!channel_valid(channel_id)) return -1;
memset(&sk_hermes_channels[channel_id], 0, sizeof(SkHermesChannel));
return 0;
}
int sk_hermes_channel_subscribe(int channel_id, VMUuid vm_id) {
SkHermesMembership *m;
size_t i;
if (!channel_valid(channel_id)) return -1;
m = &sk_hermes_channels[channel_id].membership;
for (i = 0; i < m->count; i++) {
if (vm_uuid_equal(m->members[i], vm_id)) return -1; /* already a member */
}
if (m->count >= SK_HERMES_MEMBER_MAX) return -1;
m->members[m->count] = vm_id;
m->count++;
return 0;
}
int sk_hermes_channel_unsubscribe(int channel_id, VMUuid vm_id) {
SkHermesMembership *m;
size_t i;
if (!channel_valid(channel_id)) return -1;
m = &sk_hermes_channels[channel_id].membership;
for (i = 0; i < m->count; i++) {
if (vm_uuid_equal(m->members[i], vm_id)) {
size_t j;
for (j = i; j < m->count - 1; j++) {
m->members[j] = m->members[j + 1];
}
m->count--;
return 0;
}
}
return -1; /* not a member */
}
int sk_hermes_channel_is_member(int channel_id, VMUuid vm_id) {
SkHermesMembership *m;
size_t i;
if (!channel_valid(channel_id)) return 0;
m = &sk_hermes_channels[channel_id].membership;
for (i = 0; i < m->count; i++) {
if (vm_uuid_equal(m->members[i], vm_id)) return 1;
}
return 0;
}
int sk_hermes_channel_member_count(int channel_id) {
if (!channel_valid(channel_id)) return -1;
return (int)sk_hermes_channels[channel_id].membership.count;
}
#endif /* __STARKERNEL__ */