Console-VM + user-VM pair: real async message-passing relay

Console sessions now route through the same general VM-to-VM messaging
system (Phase C) any VM can already use for its own reasons -- not a
synchronous shortcut. Per direct instruction: real async MSG-SEND/
MSG-DELIVER (Option B), not a VM-EXEC-based synchronous relay, because
messaging is a general capability, not a console-specific mechanism.

New CONSOLE-CMD-EVENT message type (common:messaging.4th). New
sk_repl_dispatch_line() (repl.c), called from both sk_repl_step and
sk_repl_run in place of a direct vm_interpret(): if the active VM's own
name has a live "<name>~user" counterpart registered, the raw input
line is wrapped as an S"-embedded CONSOLE-CMD-EVENT MSG-SEND and
interpreted on the console VM instead of being run directly -- the
console's own next MSG-TICK (Hera's idle pump) delivers it into the
paired user VM via VM-EXEC, same mechanism every other message already
uses. Falls back to direct interpretation if there's no pairing, or if
the line contains a `"` (known v1 limitation, warned about explicitly
rather than silently mishandled).

New capsule_console_birth() (capsule_console.h/.c): a bare VM whose
only content is loading common:messaging.4th -- the console side of a
pairing, parallel in shape to RUNCAP's user-VM birth but with fixed
embedded content instead of a devblock read (no identity, no thumbdrive
involved). New PAIR-TEST diagnostic word (mama_forth_words.c, matches
RUNCAP-TEST's own precedent): births both halves of a pairing and
registers the "<name>~user" mapping. Not the real pairing call site --
that's the eventual attach/onboarding flow -- this exists to exercise
the relay live before that flow exists.

Found and fixed a real, serious bug live: console_set_vm_name() stored
the caller's raw pointer instead of copying it. mama_word_use() (USE)
passes a VMRegistryEntry field living on its own stack frame -- once
USE returns, that pointer dangles, corrupting every console tag after
the first USE (observed directly as garbled "[[]" / binary-looking
prefixes instead of "[CaptBob]"). Fixed at the source: console_set_
vm_name() now copies into internal storage. That surfaced a second,
related bug across every console_get_vm_name()-based save/restore call
site in mama_forth_words.c (BIRTH, VM-STEP, VM-EXEC, CONNECT-HERMES,
CONNECT-ARTEMIS): saving just a pointer into the single internal buffer
meant an intervening console_set_vm_name() call silently corrupted the
saved value before the restore ever ran. New console_save_vm_name()
copies into caller-owned storage; every save/restore site updated.

Verified end-to-end, live in QEMU: typed WELCOME at a paired console
VM -- it did not execute directly (no UNKNOWN WORD), printed ok
immediately (queued, async), and on the next idle tick
"[CaptBob~user] Minted identity -- default personality" appeared on
its own -- genuine delivery and execution in the paired user VM through
the real MSG-SEND/MSG-DELIVER pipeline. Console tags confirmed clean
(no garbling) across all three architectures' full regression boot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
This commit is contained in:
Robert Allan James
2026-08-28 16:39:07 -04:00
co-authored by Claude Sonnet 5
parent 71b6937deb
commit b0f12710bb
14 changed files with 46122 additions and 55 deletions
+92 -13
View File
@@ -41,6 +41,8 @@
#include "starkernel/capsule.h"
#include "starkernel/capsule_birth.h"
#include "starkernel/capsule_runcap.h"
#include "starkernel/capsule_console.h"
#include "freestanding/stdio.h"
#include "starkernel/capsule_mint.h"
#include "starkernel/user_identity_seed.h"
#include "starkernel/capsule_loader.h"
@@ -288,7 +290,8 @@ void mama_word_birth(VM *vm)
/* Switch console prefix to the baby's name so its init capsule output
* appears tagged [Hermes] / [Artemis] rather than [Hera]. */
{
const char *saved_prefix = console_get_vm_name();
char saved_prefix[VM_NAME_MAX];
console_save_vm_name(saved_prefix, sizeof(saved_prefix));
console_set_vm_name(name_buf);
vm_state_push(vm);
@@ -557,7 +560,7 @@ static void mama_word_vm_step(VM *vm)
const char *src;
VMRegistryEntry entry;
VM *target;
const char *saved_name;
char saved_name[VM_NAME_MAX];
if (vm->dsp < 1) { vm->error = 1; return; }
@@ -592,7 +595,7 @@ static void mama_word_vm_step(VM *vm)
vm_physics_touch(entry.vm_id);
saved_name = console_get_vm_name();
console_save_vm_name(saved_name, sizeof(saved_name));
console_set_vm_name(entry.name);
sk_repl_step(target);
console_set_vm_name(saved_name);
@@ -618,7 +621,7 @@ static void mama_word_vm_exec(VM *vm)
const char *src;
VMRegistryEntry entry;
VM *target;
const char *saved_name;
char saved_name[VM_NAME_MAX];
if (vm->dsp < 3) { vm->error = 1; return; }
@@ -669,7 +672,7 @@ static void mama_word_vm_exec(VM *vm)
vm_physics_touch(entry.vm_id);
log_message(LOG_INFO, "VM-EXEC: '%s' -> '%s'", cmd_buf, vm_name);
saved_name = console_get_vm_name();
console_save_vm_name(saved_name, sizeof(saved_name));
console_set_vm_name(entry.name);
vm_state_push(vm);
vm_interpret(target, cmd_buf);
@@ -700,7 +703,7 @@ static void mama_word_vm_call(VM *vm)
const char *src;
VMRegistryEntry entry;
VM *target;
const char *saved_name;
char saved_name[VM_NAME_MAX];
if (vm->dsp < 3) { vm->error = 1; return; }
@@ -750,7 +753,7 @@ static void mama_word_vm_call(VM *vm)
vm_physics_touch(entry.vm_id);
log_message(LOG_DEBUG, "VM-CALL: '%s' -> '%s'", cmd_buf, vm_name);
saved_name = console_get_vm_name();
console_save_vm_name(saved_name, sizeof(saved_name));
console_set_vm_name(entry.name);
vm_state_push(vm);
vm_interpret(target, cmd_buf);
@@ -905,6 +908,80 @@ static void mama_word_runcap_test(VM *vm)
vm_push(vm, (cell_t)r);
}
/**
* @brief PAIR-TEST ( caddr u -- ok? )
* Diagnostic-only word (FABRIC-3.md Phase F, 2026-08-28): births a
* console VM (bare, capsule_console.h) named by the given string, and a
* user VM (capsule_runcap_birth(), from whatever drive sk_repl_get_
* homeblocks_dev()/sig() currently report) named "<string>~user" --
* the pairing convention sk_repl_dispatch_line() (repl.c) looks for.
* Registers the pairing in the console's own VM-name routing table at
* the fixed index (3) that relay uses. Not the real pairing call site
* -- that's the eventual attach/onboarding flow; this exists to
* exercise the console-VM + user-VM relay live before that exists.
*/
static void mama_word_pair_test(VM *vm)
{
char console_name[VM_NAME_MAX];
char user_name[VM_NAME_MAX + 8];
cell_t u, caddr;
uint32_t i;
if (vm->dsp < 1) { vm->error = 1; return; }
u = vm_pop(vm);
caddr = vm_pop(vm);
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
console_println("PAIR-TEST: name too long or empty");
vm_push(vm, 0);
return;
}
{
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
if (!p) { vm->error = 1; return; }
for (i = 0; i < (uint32_t)u; i++) console_name[i] = (char)p[i];
}
console_name[u] = '\0';
memcpy(user_name, console_name, (size_t)u);
memcpy(user_name + u, "~user", 6); /* includes NUL */
struct blkio_dev *dev = sk_repl_get_homeblocks_dev();
const homeblocks_sig_t *sig = sk_repl_get_homeblocks_sig();
if (!dev || !sig) {
console_println("PAIR-TEST: no home-blocks drive attached");
vm_push(vm, 0);
return;
}
VMUuid console_id, user_id;
void *console_ctx = (void *)0;
if (capsule_console_birth(console_name, &console_id, &console_ctx) != CAPSULE_RUN_OK) {
console_println("PAIR-TEST: console birth FAILED");
vm_push(vm, 0);
return;
}
if (capsule_runcap_birth(dev, sig, user_name, &user_id, (void **)0) != CAPSULE_RUN_OK) {
console_println("PAIR-TEST: user birth FAILED");
vm_push(vm, 0);
return;
}
/* Register the pairing in the console's own routing table, index 3
* -- the fixed convention sk_repl_dispatch_line()'s constructed
* MSG-SEND text uses. */
{
char reg_cmd[VM_NAME_MAX + 32];
int n = snprintf(reg_cmd, sizeof(reg_cmd), "S\" %s\" 3 VM-NAME-REG", user_name);
if (n > 0 && (size_t)n < sizeof(reg_cmd)) {
vm_interpret((VM *)console_ctx, reg_cmd);
}
}
console_println("PAIR-TEST: console + user VM pair live");
vm_push(vm, 1);
}
/**
* @brief CAPSULE-BIRTH ( capsule-id -- vm-id-hi vm-id-lo )
* Birth a baby VM from a production (p) capsule.
@@ -1113,13 +1190,13 @@ static void mama_word_connect_artemis(VM *vm __attribute__((unused)))
{
VMRegistryEntry entry;
VM *artemis;
const char *saved_name;
char saved_name[VM_NAME_MAX];
if (capsule_vm_find_by_name_nocase("Artemis", &entry) != 0 ||
entry.state == VM_STATE_DEAD ||
entry.state == VM_STATE_STILLBORN) {
VMUuid new_vm_id = vm_uuid_none();
const char *saved = console_get_vm_name();
char saved[VM_NAME_MAX]; console_save_vm_name(saved, sizeof(saved));
CapsuleRunResult r;
console_set_vm_name("Artemis");
@@ -1150,7 +1227,7 @@ static void mama_word_connect_artemis(VM *vm __attribute__((unused)))
return;
}
saved_name = console_get_vm_name();
console_save_vm_name(saved_name, sizeof(saved_name));
console_set_vm_name(entry.name);
capsule_vm_set_state(entry.vm_id, VM_STATE_LIVE);
@@ -1184,14 +1261,14 @@ static void mama_word_connect_hermes(VM *vm __attribute__((unused)))
{
VMRegistryEntry entry;
VM *hermes;
const char *saved_name;
char saved_name[VM_NAME_MAX];
/* Birth if not found or previously dead/stillborn */
if (capsule_vm_find_by_name_nocase("Hermes", &entry) != 0 ||
entry.state == VM_STATE_DEAD ||
entry.state == VM_STATE_STILLBORN) {
VMUuid new_vm_id = vm_uuid_none();
const char *saved = console_get_vm_name();
char saved[VM_NAME_MAX]; console_save_vm_name(saved, sizeof(saved));
CapsuleRunResult r;
console_set_vm_name("Hermes");
@@ -1222,7 +1299,7 @@ static void mama_word_connect_hermes(VM *vm __attribute__((unused)))
return;
}
saved_name = console_get_vm_name();
console_save_vm_name(saved_name, sizeof(saved_name));
console_set_vm_name(entry.name);
capsule_vm_set_state(entry.vm_id, VM_STATE_LIVE);
@@ -1260,6 +1337,7 @@ void register_mama_forth_words(VM *vm)
register_word(vm, "CAPSULE-RUN", mama_word_capsule_run);
register_word(vm, "MINT", mama_word_mint);
register_word(vm, "RUNCAP-TEST", mama_word_runcap_test);
register_word(vm, "PAIR-TEST", mama_word_pair_test);
register_word(vm, "MAMA-VM-ID", mama_word_mama_vm_id);
register_word(vm, "VM-COUNT", mama_word_vm_count);
register_word(vm, "VM-CONSERVED?", mama_word_vm_conserved);
@@ -1291,6 +1369,7 @@ void register_mama_forth_words(VM *vm)
register_word(vm, "CAPSULE-RUN", mama_word_capsule_run);
register_word(vm, "MINT", mama_word_mint);
register_word(vm, "RUNCAP-TEST", mama_word_runcap_test);
register_word(vm, "PAIR-TEST", mama_word_pair_test);
register_word(vm, "MAMA-VM-ID", mama_word_mama_vm_id);
register_word(vm, "VM-COUNT", mama_word_vm_count);
register_word(vm, "VM-CONSERVED?", mama_word_vm_conserved);