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
+59 -2
View File
@@ -44,6 +44,7 @@
#include "block_subsystem.h"
#include "word_source/include/keyboard_words.h"
#include "word_source/include/block_words.h"
#include "freestanding/stdio.h"
#include <stdint.h>
#include <string.h>
@@ -483,6 +484,62 @@ static void sk_fault_handler(VM *vm) {
}
#endif
/*===========================================================================
* sk_repl_dispatch_line - console-VM + user-VM pair relay (FABRIC-3.md
* Phase F, 2026-08-28).
*
* If `vm`'s own registered name has a live "<name>~user" counterpart,
* this is a console session: relay the raw line as a real, async
* CONSOLE-CMD-EVENT message (common:messaging.4th) instead of
* interpreting it directly -- "every line is a message," not a
* C-level redirect. This is one particular consumer of the general
* VM-to-VM messaging system built in Phase C: any VM can already
* MSG-SEND to any other VM for its own reasons regardless of a human
* ever being at a physical console at all; this hook only wires the
* physical-terminal-input path into that same general mechanism, it
* doesn't gate or replace it.
*
* Falls back to direct vm_interpret() (today's unchanged behavior) when
* there's no live paired user VM, or when the line contains a `"`
* character this simple S"-embedding can't safely carry yet (a known
* v1 limitation -- warned about, not silently mishandled).
*===========================================================================*/
static void sk_repl_dispatch_line(VM *vm, const char *input)
{
const char *vn = console_get_vm_name();
if (vn) {
char paired_name[VM_NAME_MAX + 8];
size_t vnlen = strlen(vn);
if (vnlen + 6 <= sizeof(paired_name)) {
memcpy(paired_name, vn, vnlen);
memcpy(paired_name + vnlen, "~user", 6); /* includes NUL */
VMRegistryEntry paired;
if (capsule_vm_find_by_name(paired_name, &paired) == 0 &&
paired.state == VM_STATE_LIVE) {
if (strchr(input, '"')) {
console_println("console: line contains '\"' -- can't relay "
"as a message safely yet, interpreting directly");
} else {
char cmd[INPUT_BUFFER_SIZE + 64];
/* to-index 3: the fixed convention this console's own
* VM-NAME-REG entry for its paired user VM uses (set
* once at pairing time -- see the pairing word). */
int n = snprintf(cmd, sizeof(cmd),
"CONSOLE-CMD-EVENT 0 3 S\" %s\" 0 MSG-SEND", input);
if (n > 0 && (size_t)n < sizeof(cmd)) {
vm_interpret(vm, cmd);
return;
}
}
}
}
}
vm_interpret(vm, input);
}
/*===========================================================================
* sk_repl_step - Execute one REPL turn on a VM and return.
*
@@ -520,7 +577,7 @@ int sk_repl_step(VM *vm)
return vm->halted ? 0 : 1;
}
vm_interpret(vm, input);
sk_repl_dispatch_line(vm, input);
/* ABORT stops mid-line but leaves the flag set for the caller to
* consume -- this REPL step is that boundary. Clear it here so the
@@ -563,7 +620,7 @@ void sk_repl_run(VM *vm)
continue;
}
vm_interpret(active, input);
sk_repl_dispatch_line(active, input);
/* ABORT stops mid-line but leaves the flag set for the caller to
* consume -- this REPL step is that boundary. Clear it here so the