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:
co-authored by
Claude Sonnet 5
parent
71b6937deb
commit
b0f12710bb
+38
-37
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-28T20:09:16Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-28T20:37:43Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
@@ -10,7 +10,7 @@
|
||||
|---------|----------------|----------|--------|
|
||||
| `ACL.4th` | 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4015 | `0xd781d22148ff171d` | yes |
|
||||
| `artemis:init.4th` | 4110, 4111, 4112, 4113, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4160, 4161, 4162, 4163, 4164, 4165, 4166, 4167, 4168, 4169, 4170, 4171, 4172, 4173, 4174, 4177, 4178, 4179, 4180, 4181, 4182, 4851, 4852, 4853, 4854 | `0xfe6e570467368153` | yes |
|
||||
| `common:messaging.4th` | 5003, 5004, 5005, 5006, 5007, 5008, 5009, 5010, 5011, 5012, 5013, 5014, 5015, 5016, 5017, 5018, 5019, 5020, 5021, 5022, 5023, 5024, 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034, 5035, 5036, 5037 | `0x3eb5408bc357d1de` | yes |
|
||||
| `common:messaging.4th` | 5003, 5004, 5005, 5006, 5007, 5008, 5009, 5010, 5011, 5012, 5013, 5014, 5015, 5016, 5017, 5018, 5019, 5020, 5021, 5022, 5023, 5024, 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034, 5035, 5036, 5037, 5038 | `0x892fd1c86d8c175e` | yes |
|
||||
| `common:msg.4th` | 4055 | `0x850a0382344ea6c4` | yes |
|
||||
| `doe-campaign.4th` | 4060, 4061, 4062, 4063, 4064, 4065 | `0x3d4549142d91ec20` | yes |
|
||||
| `doe.4th` | 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107 | `0xf154616d248e861f` | yes |
|
||||
@@ -294,41 +294,42 @@
|
||||
| 5000 | `fabric.4th` | `0x9b1d061339cea98d` | ok |
|
||||
| 5001 | `fabric.4th` | `0x9b1d061339cea98d` | ok |
|
||||
| 5002 | `fabric.4th` | `0x9b1d061339cea98d` | ok |
|
||||
| 5003 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5004 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5005 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5006 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5007 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5008 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5009 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5010 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5011 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5012 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5013 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5014 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5015 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5016 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5017 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5018 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5019 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5020 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5021 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5022 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5023 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5024 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5025 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5026 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5027 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5028 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5029 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5030 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5031 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5032 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5033 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5034 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5035 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5036 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5037 | `common:messaging.4th` | `0x3eb5408bc357d1de` | ok |
|
||||
| 5003 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5004 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5005 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5006 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5007 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5008 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5009 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5010 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5011 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5012 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5013 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5014 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5015 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5016 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5017 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5018 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5019 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5020 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5021 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5022 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5023 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5024 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5025 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5026 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5027 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5028 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5029 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5030 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5031 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5032 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5033 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5034 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5035 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5036 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5037 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5038 | `common:messaging.4th` | `0x892fd1c86d8c175e` | ok |
|
||||
| 5100 | `turtle.4th` | `0x4d470418ca543365` | ok |
|
||||
| 5101 | `turtle.4th` | `0x4d470418ca543365` | ok |
|
||||
| 5102 | `turtle.4th` | `0x4d470418ca543365` | ok |
|
||||
|
||||
@@ -15,6 +15,10 @@ Block 5003
|
||||
64 CONSTANT MBR-MAX
|
||||
65208 CONSTANT Q-DECAY
|
||||
255 CONSTANT MSG-DELIVERED
|
||||
Block 5038
|
||||
( CONSOLE-CMD-EVENT: console-VM -> paired user-VM command )
|
||||
( relay message type, async MSG-SEND/MSG-DELIVER. )
|
||||
7 CONSTANT CONSOLE-CMD-EVENT
|
||||
Block 5004
|
||||
( StadiumBehaviour tags, match stadium.h's enum )
|
||||
0 CONSTANT SB-MIGRATE
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_console.h - Bare console-VM birth (FABRIC-3.md Phase F,
|
||||
* 2026-08-28): a minimal VM whose only job is loading
|
||||
* common:messaging.4th and being the physical REPL's relay target
|
||||
* (sk_repl_dispatch_line(), repl.c) for a paired user VM
|
||||
* (capsule_runcap_birth(), capsule_runcap.h). Not identity-bearing
|
||||
* content -- no thumbdrive read, fixed embedded source, same
|
||||
* heap-built-single-entry-directory shape RUNCAP already established
|
||||
* (§F.6/§F.18), just with compile-time content instead of a devblock
|
||||
* read.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_CONSOLE_H
|
||||
#define STARKERNEL_CAPSULE_CONSOLE_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include "starkernel/capsule_run.h"
|
||||
#include "starkernel/vm_uuid.h"
|
||||
|
||||
/**
|
||||
* capsule_console_birth - Birth a bare console VM.
|
||||
*
|
||||
* @param console_name Symbolic name for the new VM -- by convention
|
||||
* the same name a user's own identity uses (e.g.
|
||||
* "CaptBob"); sk_repl_dispatch_line() looks for a
|
||||
* live "<name>~user" counterpart to decide
|
||||
* whether a given active VM is a console.
|
||||
* @param out_vm_id Output: assigned VM ID.
|
||||
* @param out_vm_ctx Output: new VM context (may be NULL).
|
||||
* @return CAPSULE_RUN_OK on success, error code otherwise.
|
||||
*/
|
||||
CapsuleRunResult capsule_console_birth(const char *console_name,
|
||||
VMUuid *out_vm_id, void **out_vm_ctx);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_CONSOLE_H */
|
||||
@@ -54,6 +54,7 @@
|
||||
#define STARKERNEL_CONSOLE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "uefi.h"
|
||||
|
||||
/**
|
||||
@@ -141,11 +142,30 @@ int console_poll(void);
|
||||
/**
|
||||
* Set the active VM name shown as [Name] prefix on each output line.
|
||||
* Pass NULL to suppress the prefix (kernel-only output before any VM).
|
||||
* The pointer must remain valid for as long as it is active.
|
||||
* Copies into internal storage (FABRIC-3.md Phase F, 2026-08-28) -- the
|
||||
* caller's own pointer does not need to remain valid afterward.
|
||||
*/
|
||||
void console_set_vm_name(const char *name);
|
||||
const char *console_get_vm_name(void);
|
||||
|
||||
/**
|
||||
* console_save_vm_name - Copy the current active-VM name into the
|
||||
* caller's own buffer, for a later console_set_vm_name() restore.
|
||||
*
|
||||
* console_get_vm_name() alone is NOT safe for save-then-restore: it
|
||||
* returns a pointer into the single internal buffer console_set_vm_name()
|
||||
* copies into, so an intervening console_set_vm_name() call (the normal
|
||||
* "switch, do work, switch back" pattern every BIRTH/VM-EXEC/CONNECT-*
|
||||
* call site uses) overwrites the very bytes the saved pointer points at
|
||||
* before the restore ever runs -- found live 2026-08-28, the restore
|
||||
* silently no-ops. Copies at most cap-1 bytes plus a NUL terminator;
|
||||
* writes "" if there was no active name (NULL) to save.
|
||||
*
|
||||
* @param out Caller-owned buffer.
|
||||
* @param cap Its size in bytes.
|
||||
*/
|
||||
void console_save_vm_name(char *out, size_t cap);
|
||||
|
||||
/* Last FORTH word name set by the dispatcher before entry->func(vm).
|
||||
* Printed by the #GP fault handler to identify the faulting word. */
|
||||
extern volatile const char *g_sk_fault_word;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
#ifndef __STARKERNEL__
|
||||
#error "capsule_console.c is kernel-only"
|
||||
#endif
|
||||
|
||||
#include "starkernel/capsule_console.h"
|
||||
#include "starkernel/capsule.h"
|
||||
#include "starkernel/capsule_birth.h"
|
||||
#include "starkernel/xxhash64.h"
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include <string.h>
|
||||
|
||||
/* Real, minimal, working content -- just enough vocabulary to send/
|
||||
* receive messages (common:messaging.4th, same as every other VM).
|
||||
* No COMMON-CH subscription: a console's own traffic is direct 1:1
|
||||
* with its paired user VM (CONSOLE-CMD-EVENT), not broadcast, so
|
||||
* there's no need to resolve an index in Hermes's own routing table
|
||||
* for it. Needs the "Block NNNN" header capsule_exec_payload()
|
||||
* requires (§F.18) -- not optional. 4997 is an unused block number. */
|
||||
static const char CONSOLE_IDENTITY_SRC[] =
|
||||
"Block 4997\n"
|
||||
"S\" common:messaging.4th\" EXEC\n"
|
||||
"MSG-CD-INIT\n";
|
||||
|
||||
CapsuleRunResult capsule_console_birth(const char *console_name,
|
||||
VMUuid *out_vm_id, void **out_vm_ctx)
|
||||
{
|
||||
if (!console_name) return CAPSULE_RUN_ERR_INVALID;
|
||||
|
||||
size_t source_len = sizeof(CONSOLE_IDENTITY_SRC) - 1u;
|
||||
uint8_t *arena = (uint8_t *)kmalloc(source_len);
|
||||
if (!arena) return CAPSULE_RUN_ERR_STILLBORN;
|
||||
memcpy(arena, CONSOLE_IDENTITY_SRC, source_len);
|
||||
|
||||
/* Heap-built single-entry directory -- same shape RUNCAP already
|
||||
* established (§F.6/§F.18); never freed, matches that precedent
|
||||
* (a VM's IDENTITY exec reads directly from this arena). */
|
||||
CapsuleNameEntry name_entry;
|
||||
memset(name_entry.name, 0, sizeof(name_entry.name));
|
||||
{
|
||||
size_t n = strlen(console_name);
|
||||
if (n >= CAPSULE_NAME_MAX) n = CAPSULE_NAME_MAX - 1u;
|
||||
memcpy(name_entry.name, console_name, n);
|
||||
}
|
||||
|
||||
CapsuleDesc desc;
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
desc.magic = CAPSULE_MAGIC_PACK(CAPSULE_VERSION_0, CAPSULE_HASH_XXHASH64);
|
||||
desc.content_hash = xxhash64_capsule(arena, source_len);
|
||||
desc.capsule_id = desc.content_hash;
|
||||
desc.offset = 0;
|
||||
desc.length = source_len;
|
||||
desc.flags = CAPSULE_FLAG_ACTIVE | CAPSULE_FLAG_PRODUCTION;
|
||||
desc.owner_vm = 0;
|
||||
desc.birth_count = 0;
|
||||
desc.created_ns = 0;
|
||||
|
||||
CapsuleDirHeader dir;
|
||||
memset(&dir, 0, sizeof(dir));
|
||||
dir.magic = CAPSULE_DIR_MAGIC;
|
||||
dir.arena_base = (uint64_t)(uintptr_t)arena;
|
||||
dir.arena_size = source_len;
|
||||
dir.desc_count = 1;
|
||||
dir.desc_capacity = 1;
|
||||
dir.name_count = 1;
|
||||
dir.dir_hash = 0;
|
||||
|
||||
CapsuleRunResult r = capsule_birth_baby(
|
||||
console_name, &dir, &desc, &name_entry, arena,
|
||||
1 /* skip_pki_sig -- not build-time content, same rationale as RUNCAP */,
|
||||
out_vm_id, out_vm_ctx);
|
||||
|
||||
/* capsule_birth_baby() never sets the registry entry's own .name --
|
||||
* found live in RUNCAP (§F.18), same fix needed here. */
|
||||
if (r == CAPSULE_RUN_OK && out_vm_id) {
|
||||
capsule_vm_registry_set_name(*out_vm_id, console_name);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -160,18 +160,50 @@ static int serial_transmit_empty(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Active VM name for [Name] line prefix; NULL = no prefix */
|
||||
/* Active VM name for [Name] line prefix; NULL = no prefix.
|
||||
*
|
||||
* g_active_vm_name_buf owns the storage -- console_set_vm_name() copies
|
||||
* into it rather than storing the caller's own pointer. Found live
|
||||
* 2026-08-28 (FABRIC-3.md Phase F): mama_word_use() (USE) passes
|
||||
* entry.name, a local VMRegistryEntry's own field -- once USE returns,
|
||||
* that stack frame is reused and the old raw-pointer version left
|
||||
* g_active_vm_name dangling, corrupting every console tag after the
|
||||
* first USE (observed as garbled "[[]"/binary-looking prefixes). A
|
||||
* caller passing a string literal (e.g. console_set_vm_name("Hermes"))
|
||||
* was always safe; this fixes every caller uniformly instead of relying
|
||||
* on each one happening to pass static storage. */
|
||||
#define CONSOLE_VM_NAME_BUF 64
|
||||
static char g_active_vm_name_buf[CONSOLE_VM_NAME_BUF];
|
||||
static const char *g_active_vm_name = (void *)0;
|
||||
static int g_line_start = 1;
|
||||
|
||||
void console_set_vm_name(const char *name) {
|
||||
g_active_vm_name = name;
|
||||
/* Empty string treated the same as NULL: console_save_vm_name()
|
||||
* writes "" for "there was no active name," so this keeps that
|
||||
* round-trip correct (save-empty then restore-empty must mean
|
||||
* "still no prefix," not "prefix is now the empty string"). */
|
||||
if (!name || !name[0]) { g_active_vm_name = (void *)0; return; }
|
||||
size_t i;
|
||||
for (i = 0; i < CONSOLE_VM_NAME_BUF - 1u && name[i]; i++)
|
||||
g_active_vm_name_buf[i] = name[i];
|
||||
g_active_vm_name_buf[i] = '\0';
|
||||
g_active_vm_name = g_active_vm_name_buf;
|
||||
}
|
||||
|
||||
const char *console_get_vm_name(void) {
|
||||
return g_active_vm_name;
|
||||
}
|
||||
|
||||
void console_save_vm_name(char *out, size_t cap) {
|
||||
if (!out || cap == 0) return;
|
||||
size_t i = 0;
|
||||
if (g_active_vm_name) {
|
||||
for (; i < cap - 1u && g_active_vm_name[i]; i++)
|
||||
out[i] = g_active_vm_name[i];
|
||||
}
|
||||
out[i] = '\0';
|
||||
}
|
||||
|
||||
/* Raw single-character write — no prefix logic, called by emit_prefix() */
|
||||
static void raw_putc(char c) {
|
||||
#if defined(__aarch64__)
|
||||
|
||||
+59
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user