Unify console prompt to [user@VM]; fix real personality-block truncation; correct §XXV's wrong lockdown conclusion (FABRIC-3.md §XXVI)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s

Investigating the std79 lockdown finding from FABRIC-3.md §XXV led
to a real discovery: WIREBIND births TWO VMs per identity, a console
proxy under the plain username and the actual restricted identity
under <username>~user (capsule_wirebind.c). Every test in §XXV
targeted the console proxy, which was never locked down at all.
Retested against the correct target (rajames~user): the lockdown
works exactly as designed. §XXV's "lockdown never engages" conclusion
was wrong -- corrected here, not deleted, since the mistake and how
it was caught are worth keeping (see the new feedback memory:
confirm which specific VM a name resolves to before concluding
anything, when a subsystem is known to birth more than one VM per
identity).

Two real, separate things found along the way are kept regardless
of that correction:

- capsule_runcap.c: the reserved personality devblock was read in
  full (mostly zero-padding after a short ~200-byte string) with no
  terminator, producing "WARN: block 4998 exceeds 1KB, truncating"
  on every std79-locked identity's birth, universal, since at least
  2026-09-10. Fixed by trimming to the first NUL byte actually found
  -- real, but harmless to execution (real content sat in the
  truncated block's surviving head); it mattered for capsule_id/
  content_hash being computed over padding instead of real content.

- console.h/console.c/repl.c: unified the prompt from a separately-
  computed "[VMName] (user)" into a single "[user@VMName]" line
  prefix -- exactly the ambiguity that caused the original
  misdiagnosis (the prompt showed only the WIREBIND username,
  identical whether USE had targeted the console proxy or the real
  ~user identity). Implemented as a registered callback
  (console_set_user_prefix_provider()) rather than console.c calling
  into WIREBIND/session logic directly, since console.c is a clean
  HAL module with no prior dependency on capsule-level subsystems.

Verified: clean build on all 3 architectures, zero new warnings,
identical dict_hash/capsule_hash to every prior boot this session
(console/prompt-only change). Full 9-identity messaging campaign
re-run end to end: 202s, zero faults, all 8 identities at 99/99
tokens, zero regression.

Also surfaced, not yet acted on: the full campaign's own console
tags now visibly show which VM each identity's tests actually
reached ([zuse@rajames], not [zuse@rajames~user]) -- messaging.4th's
VM-NAMES-INIT registers identities by plain username, so std79-doe.
fth's turn-attractor has been dispatching to each identity's console
proxy, not the actual locked-down identity, since the messaging
rewrite. Flagged for a deliberate decision, not investigated further.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXieurDfDSsDFdnSyusuWo
This commit is contained in:
Robert Allan James
2026-09-13 06:44:58 -04:00
co-authored by Claude Sonnet 5
parent cb32e6632b
commit a8b16d41da
25 changed files with 84722 additions and 694 deletions
+69
View File
@@ -3325,3 +3325,72 @@ fixed: an explicit, automated check that `ACL-STD79-ALLOWED?` (or any allowlist
*exists* and denies a known off-list word for a live std79-locked identity, rather than trusting *exists* and denies a known off-list word for a live std79-locked identity, rather than trusting
a clean mint + a clean boot as proof the lockdown engaged. a clean mint + a clean boot as proof the lockdown engaged.
## XXVI. Correction to §XXV: the lockdown was never broken -- wrong VM tested. Truncation fix
kept (real, separate bug); unified `[user@VM]` console prefix; a bigger methodology finding
surfaced along the way (2026-09-13)
**§XXV's "the std79 lockdown likely never engages" conclusion was wrong, and the reason is worth
recording plainly rather than quietly editing away.** `WIREBIND` births *two* VMs per identity,
confirmed by re-reading `capsule_wirebind_try_attach()`: a console VM registered under the plain
username (`capsule_console_birth(username, ...)`), and the actual std79-locked identity VM
registered as `<username>~user` (`capsule_runcap_birth(dev, sig, user_vm_name, ...)`, where
`user_vm_name = username + "~user"`). Every test in §XXV targeted `rajames` -- the console proxy,
which is never locked down at all -- not `rajames~user`, the real restricted identity. Re-run
against the correct target: `' EXEC ACL-STD79-ALLOWED? .` on `rajames~user` returns `0` (`EXEC`
correctly denied, exactly as `acl-std79.4th`'s own header comment says it should be once
`ACL-LOCKDOWN-STD79` finishes) -- the lockdown has been working correctly the whole time. **The
std79 lockdown gap described in §XXV did not exist. Recorded here as a correction, not deleted,
because the mistake and how it was caught are worth keeping: always confirm which specific VM
identity a name actually resolves to before drawing a conclusion from testing it, especially
when a subsystem (WIREBIND) is known to birth more than one VM per external identity.**
**The `capsule_runcap.c` truncation fix from §XXV is kept -- it's a real, separate bug,
independently confirmed, just not the cause of a lockdown failure that never happened.** `WARN:
block 4998 exceeds 1KB, truncating` genuinely fired on every std79-locked identity's birth
(confirmed universal and long-standing in §XXV); the fix (trimming `source_len` to the first NUL
byte actually found, since `MINT_IDENTITY_SRC_DEVBLOCKS`'s one reserved devblock is always
`memset` to 0 before the short personality string is written into it) is correct regardless, and
matters for a reason independent of lockdown correctness: `desc.content_hash`/`capsule_id` were
being computed over ~4KB of mostly zero-padding instead of the real ~200-byte content, which
matters for this project's content-addressing model even though the real content sitting in the
truncated block's surviving head meant execution itself was never actually affected.
**New: unified console prefix, `[user@VMName]` replacing the separately-computed `[VMName]`
bracket and `(user)` prompt segment.** Directly motivated by the confusion that produced this
correction -- console tags already distinguished `rajames` from `rajames~user` correctly the
whole time, but the *prompt* only ever showed the WIREBIND username, identical regardless of
which VM `USE` had actually targeted, so a human (or an agent) could not tell from the prompt
alone which VM a command was about to reach. Implemented as a registered callback
(`console_set_user_prefix_provider()`, `console.h`/`console.c`) rather than `console.c` calling
into WIREBIND/session logic directly -- console.c is a clean HAL module with zero prior
dependency on capsule-level subsystems, and pulling one in for this would have been a real
layering violation, not just a style question. `repl.c` registers `sk_console_user_prefix()`
(the same `zuse_session`/`capsule_wirebind_attached_username()` logic `sk_print_prompt()` used to
compute inline) once at `sk_repl_run()` startup; `sk_print_prompt()` itself now just prints the
bare `"ok> "` text, since the user segment moved into the shared line prefix that already reaches
every line, prompt included. Verified live: `[zuse@Hera] ok>` at the bare console, `[zuse@
rajames~user] ok>` after `S" rajames~user" USE` -- one tag, always accurate about both who's
logged in and which VM a command actually reaches. Built clean on all 3 architectures, zero new
warnings; booted all 3, identical dict_hash/capsule_hash to every prior boot this session (this
change touches only console/prompt code, not the dictionary); re-ran the full 9-identity
messaging campaign end to end -- 202s (matching the established 194-212s range), zero faults, all
8 identities at 99/99 tokens, zero regression.
**A bigger methodology finding surfaced while re-verifying the campaign against the new prefix
format, not yet acted on:** the full-campaign log's own console tags read `[zuse@rajames]`,
`[zuse@00]`, etc. -- **the plain console-proxy names, not `rajames~user`/`00~user`.**
`common:messaging.4th`'s `VM-NAMES-INIT` registers identities by their plain username
(`S" rajames" 1 DOE-IDX>MSG-IDX VM-NAME-REG`, confirmed against the file directly) -- meaning
`std79-doe.fth`'s messaging-based turn-attractor (§XXI onward) has been dispatching every trial's
24 tests to each identity's **console proxy VM**, not the actual std79-locked identity VM,
since the messaging rewrite landed. The turn-attractor's own mechanics (rep-granularity
batching, priority ordering, K conservation, all of §XXI/§XXII/§XXIII's verification) are
unaffected by this -- the messaging/Stadium/heat accounting is identical regardless of which
specific VM receives the dispatched text. What's genuinely in question is the campaign's own
premise: if the intent was ever to specifically exercise standard-word behavior *inside* a
locked-down, standards-only environment (as opposed to just "some live VM, whichever WIREBIND
happens to hand back"), every messaging-based run so far has been testing the wrong one. Not
investigated further this session -- flagged here for a deliberate decision (retarget
`VM-NAMES-INIT` to the `~user` identities and re-run, or confirm the console-proxy target was
actually fine for what this campaign measures) rather than assumed either way.
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated # Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-13T02:53:31Z --> <!-- Generated by mkcapsule --manifest 2026-09-13T10:39:15Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. --> <!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live --> <!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. --> <!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+22
View File
@@ -208,6 +208,28 @@ const char *console_get_vm_name(void);
*/ */
void console_save_vm_name(char *out, size_t cap); void console_save_vm_name(char *out, size_t cap);
/**
* FABRIC-3.md SXXV follow-up (2026-09-13): the line prefix used to be a
* bare "[VMName] ", ambiguous between a console-proxy VM and the actual
* identity VM behind it (e.g. "rajames" the console vs. "rajames~user"
* the real WIREBIND-restricted identity) -- confirmed live to cause real
* confusion during testing. Unified to "[user@VMName] " when a user
* context is available, still "[VMName] " when not (unauthenticated/
* pre-login, unchanged from today).
*
* console.c is a clean HAL module with no dependency on capsule/WIREBIND
* logic (zuse_session, capsule_wirebind_attached_username()) -- pulling
* either in directly here would be a real layering violation, not just a
* style preference. This callback lets repl.c (which already computes
* exactly this for sk_print_prompt()) supply the "user" half without
* console.c knowing anything about VMs, sessions, or WIREBIND. Returns
* NULL/empty for "no user context" (falls back to the bare "[VMName] "
* form); the returned pointer must remain valid until the next call
* (matches console_get_vm_name()'s own single-buffer convention).
*/
typedef const char *(*console_user_prefix_fn)(void);
void console_set_user_prefix_provider(console_user_prefix_fn fn);
/* Last FORTH word name set by the dispatcher before entry->func(vm). /* Last FORTH word name set by the dispatcher before entry->func(vm).
* Printed by the #GP fault handler to identify the faulting word. */ * Printed by the #GP fault handler to identify the faulting word. */
extern volatile const char *g_sk_fault_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
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,42 @@
epoch_s,elapsed_s,qemu_utime_jiffies,mem_used_kb,mem_available_kb,swap_used_kb
1789295956,2,0,2366572,9773108,601876
1789295961,7,0,2461672,9678008,601876
1789295967,13,0,2422984,9716696,601876
1789295972,18,0,2410368,9729312,601876
1789295977,23,0,2451728,9687952,601876
1789295982,28,0,2470656,9669024,601876
1789295987,33,0,2461968,9677712,601876
1789295992,38,0,2480280,9659400,601876
1789295997,43,0,2494284,9645396,601876
1789296002,48,0,2495996,9643684,601876
1789296007,53,0,2493760,9645920,601876
1789296012,58,0,2452384,9687296,601876
1789296017,63,0,2448592,9691088,601876
1789296022,68,0,2478176,9661504,601876
1789296027,73,0,2497964,9641716,601876
1789296032,78,0,2476676,9663004,601876
1789296037,83,0,2477772,9661908,601876
1789296042,88,0,2495920,9643760,601876
1789296047,93,0,2515288,9624392,601876
1789296052,98,0,2514900,9624780,601876
1789296057,103,0,2529988,9609692,601876
1789296062,108,0,2520348,9619332,601876
1789296067,113,0,2506172,9633508,601876
1789296072,118,0,2520368,9619312,601876
1789296077,123,0,2529232,9610448,601876
1789296082,128,0,2534760,9604920,601876
1789296087,133,0,2546732,9592948,601876
1789296092,138,0,2518060,9621620,601876
1789296097,143,0,2510028,9629652,601876
1789296102,148,0,2502964,9636716,601876
1789296107,153,0,2504376,9635304,601876
1789296112,158,0,2514676,9625004,601876
1789296118,164,0,2538768,9600912,601876
1789296123,169,0,2549384,9590296,601876
1789296128,174,0,2530072,9609608,601876
1789296133,179,0,2540248,9599432,601876
1789296138,184,0,2583444,9556236,601876
1789296143,189,0,2538108,9601572,601876
1789296148,194,0,2536188,9603492,601876
1789296153,199,0,2547044,9592636,601876
1789296158,204,0,2505156,9634524,601876
1 epoch_s elapsed_s qemu_utime_jiffies mem_used_kb mem_available_kb swap_used_kb
2 1789295956 2 0 2366572 9773108 601876
3 1789295961 7 0 2461672 9678008 601876
4 1789295967 13 0 2422984 9716696 601876
5 1789295972 18 0 2410368 9729312 601876
6 1789295977 23 0 2451728 9687952 601876
7 1789295982 28 0 2470656 9669024 601876
8 1789295987 33 0 2461968 9677712 601876
9 1789295992 38 0 2480280 9659400 601876
10 1789295997 43 0 2494284 9645396 601876
11 1789296002 48 0 2495996 9643684 601876
12 1789296007 53 0 2493760 9645920 601876
13 1789296012 58 0 2452384 9687296 601876
14 1789296017 63 0 2448592 9691088 601876
15 1789296022 68 0 2478176 9661504 601876
16 1789296027 73 0 2497964 9641716 601876
17 1789296032 78 0 2476676 9663004 601876
18 1789296037 83 0 2477772 9661908 601876
19 1789296042 88 0 2495920 9643760 601876
20 1789296047 93 0 2515288 9624392 601876
21 1789296052 98 0 2514900 9624780 601876
22 1789296057 103 0 2529988 9609692 601876
23 1789296062 108 0 2520348 9619332 601876
24 1789296067 113 0 2506172 9633508 601876
25 1789296072 118 0 2520368 9619312 601876
26 1789296077 123 0 2529232 9610448 601876
27 1789296082 128 0 2534760 9604920 601876
28 1789296087 133 0 2546732 9592948 601876
29 1789296092 138 0 2518060 9621620 601876
30 1789296097 143 0 2510028 9629652 601876
31 1789296102 148 0 2502964 9636716 601876
32 1789296107 153 0 2504376 9635304 601876
33 1789296112 158 0 2514676 9625004 601876
34 1789296118 164 0 2538768 9600912 601876
35 1789296123 169 0 2549384 9590296 601876
36 1789296128 174 0 2530072 9609608 601876
37 1789296133 179 0 2540248 9599432 601876
38 1789296138 184 0 2583444 9556236 601876
39 1789296143 189 0 2538108 9601572 601876
40 1789296148 194 0 2536188 9603492 601876
41 1789296153 199 0 2547044 9592636 601876
42 1789296158 204 0 2505156 9634524 601876
File diff suppressed because it is too large Load Diff
+28
View File
@@ -58,6 +58,34 @@ CapsuleRunResult capsule_runcap_birth(
} }
} }
/* FABRIC-3.md SXXV follow-up (2026-09-13): source_len above is the
* FULL reserved region (source_devblocks worth of 4KiB devblocks) --
* MINT (capsule_mint.c) only ever writes a short personality string
* into it and memset()s the rest of that one devblock to 0 first, but
* never touches whatever devblocks beyond the first were reserved for
* identity_src, and this loop reads all of them regardless. Real
* content is a handful of lines; everything past it, for the whole
* remaining reserved region, is zero bytes with no "Block N" header
* of its own to stop capsule_exec_payload()'s own block-flush scan --
* confirmed live as the actual cause of "WARN: block 4998 exceeds
* 1KB, truncating", firing for every std79-locked identity, every
* boot, since before this was found: the parser had no way to tell
* "real content ends here" from "just more of this same block," so it
* walked the whole zero-padded region as one oversized block. FORTH
* source never legitimately contains a raw NUL, so it's a safe,
* ordinary C-string-style terminator here: trim source_len down to
* the first NUL actually found, restoring the block-parser's normal
* behavior instead of teaching it a special case for this one
* caller's own padding convention. */
{
uint64_t real_len = source_len;
uint64_t k;
for (k = 0; k < source_len; k++) {
if (arena[k] == '\0') { real_len = k; break; }
}
source_len = real_len;
}
/* Heap-built single-entry directory -- exact shape §F.6 traced /* Heap-built single-entry directory -- exact shape §F.6 traced
* against capsule_birth_baby()'s own parameters, not a new mechanism. * against capsule_birth_baby()'s own parameters, not a new mechanism.
* Never freed: matches kernel_main.c's own compile-time-directory * Never freed: matches kernel_main.c's own compile-time-directory
+21
View File
@@ -195,6 +195,18 @@ const char *console_get_vm_name(void) {
return g_active_vm_name; return g_active_vm_name;
} }
/* FABRIC-3.md SXXV follow-up: see console.h's own doc comment on
* console_set_user_prefix_provider() for why this is a registered
* callback rather than console.c calling into capsule/WIREBIND logic
* directly. NULL (the default, until repl.c registers one at boot) means
* "no provider yet" -- emit_prefix() falls back to the bare "[VMName] "
* form, identical to today's behavior. */
static console_user_prefix_fn g_user_prefix_fn = (void *)0;
void console_set_user_prefix_provider(console_user_prefix_fn fn) {
g_user_prefix_fn = fn;
}
void console_save_vm_name(char *out, size_t cap) { void console_save_vm_name(char *out, size_t cap) {
if (!out || cap == 0) return; if (!out || cap == 0) return;
size_t i = 0; size_t i = 0;
@@ -239,10 +251,19 @@ static void emit_prefix(void) {
int fb = fb_is_available(); int fb = fb_is_available();
static const char *color_on = "\x1b[38;2;255;165;0m"; static const char *color_on = "\x1b[38;2;255;165;0m";
static const char *color_off = "\x1b[39m"; static const char *color_off = "\x1b[39m";
const char *user = g_user_prefix_fn ? g_user_prefix_fn() : (void *)0;
for (p = color_on; *p; p++) { raw_putc(*p); if (fb) vt100_putc(*p); } for (p = color_on; *p; p++) { raw_putc(*p); if (fb) vt100_putc(*p); }
raw_putc('['); raw_putc('[');
if (fb) vt100_putc('['); if (fb) vt100_putc('[');
if (user && user[0]) {
for (p = user; *p; p++) {
raw_putc(*p);
if (fb) vt100_putc(*p);
}
raw_putc('@');
if (fb) vt100_putc('@');
}
for (p = g_active_vm_name; *p; p++) { for (p = g_active_vm_name; *p; p++) {
raw_putc(*p); raw_putc(*p);
if (fb) vt100_putc(*p); if (fb) vt100_putc(*p);
+46 -37
View File
@@ -60,31 +60,35 @@
const char lithos_version[64] = LITHOS_VERSION_STR; const char lithos_version[64] = LITHOS_VERSION_STR;
/* FABRIC-0.md §27.8/4.4s, unblocked 2026-09-04: extends the prompt to /* FABRIC-3.md SXXV follow-up (2026-09-13): was "[VM name] (user) ok>"
* "[VM name] (user) ok>" (e.g. "[Hera] (zuse) ok>") whenever an * (e.g. "[Hera] (zuse) ok>") -- the VM-name bracket and the user segment
* identity is currently attached -- Zuse (mama_vm->zuse_session; there * were computed independently, and neither one told you whether the
* is only ever one, so no username lookup needed) or a regular WIREBIND * bracketed VM was the console proxy WIREBIND births per identity or the
* user (capsule_wirebind_attached_username()). Checked independently of * actual restricted identity VM behind it (e.g. "rajames" vs.
* which VM's own bracket console.c is currently showing: both identities * "rajames~user") -- confirmed live to cause real confusion debugging
* are console-level attach state, not per-VM dictionary state, so the * the std79 lockdown. Unified into the single "[user@VM name] ok>" line
* segment reflects "who is at the console" the same way regardless of * prefix (console.c's emit_prefix(), via console_set_user_prefix_
* which VM you've USE'd into. Prints nothing (bare "ok> ", today's * provider() below) -- one tag, always accurate about both who's at the
* existing format, unchanged) when neither is attached -- Hera's own * console AND which VM a line actually came from/a command actually
* documented normal steady state (FABRIC-2.md §D.2). */ * reaches. This function now only prints the bare prompt text; the user
* segment moved into the line prefix, which is a different call path per
* console.c's own commenting elsewhere but reaches every line including
* this prompt. */
static void sk_print_prompt(void) { static void sk_print_prompt(void) {
VM *mama_vm = (VM *)sk_get_mama_vm();
const char *username;
if (mama_vm && mama_vm->zuse_session) {
console_puts("(zuse) ");
} else if ((username = capsule_wirebind_attached_username()) != (const char *)0) {
console_puts("(");
console_puts(username);
console_puts(") ");
}
console_puts(SK_PROMPT_TEXT); console_puts(SK_PROMPT_TEXT);
} }
/* console_user_prefix_fn provider (console.h) -- same zuse_session/
* WIREBIND-username logic sk_print_prompt() used to compute inline
* before this moved into the shared line prefix. Registered once at
* boot (sk_repl_run()'s own init, see below); NULL until then, which
* console.c's emit_prefix() already treats as "no provider yet." */
static const char *sk_console_user_prefix(void) {
VM *mama_vm = (VM *)sk_get_mama_vm();
if (mama_vm && mama_vm->zuse_session) return "zuse";
return capsule_wirebind_attached_username();
}
/*=========================================================================== /*===========================================================================
* USE-word dispatch: which VM receives REPL input. * USE-word dispatch: which VM receives REPL input.
@@ -1215,16 +1219,15 @@ int sk_repl_step(VM *vm)
{ {
/* Unified prompt (FABRIC-0.md 4.4a): console_putc()'s existing per-line /* Unified prompt (FABRIC-0.md 4.4a): console_putc()'s existing per-line
* "[VMName] " prefix (console.c, g_active_vm_name) already supplies the * "[user@VMName] " prefix (console.c's emit_prefix(), FABRIC-3.md
* bracket -- print only "ok> " here, don't build a second one. * SXXV follow-up) already supplies the bracket -- print only
* emergency_console is no longer set from here (FABRIC-2.md §F.20/ * "ok> " here, don't build a second one. emergency_console is no
* §F.21: the emergency-CLI ACL bypass is retired) -- it's driven * longer set from here (FABRIC-2.md §F.20/§F.21: the emergency-CLI
* only by the genuine C-level fault handler now (vm.c's own * ACL bypass is retired) -- it's driven only by the genuine C-level
* emergency-fault-recovery use, EMERGENCY_CONSOLE_ENABLED). Every * fault handler now (vm.c's own emergency-fault-recovery use,
* word run from this REPL, Hera's bare prompt included, goes * EMERGENCY_CONSOLE_ENABLED). Every word run from this REPL,
* through ordinary ACL enforcement. FABRIC-0.md 4.4s (2026-09-04): * Hera's bare prompt included, goes through ordinary ACL
* sk_print_prompt() extends this with a "(user)" segment when an * enforcement. */
* identity is attached -- see its own doc comment. */
sk_print_prompt(); sk_print_prompt();
} }
@@ -1276,6 +1279,12 @@ void sk_repl_run(VM *vm)
char input[INPUT_BUFFER_SIZE]; /* FABRIC-0.md 4.4w: matches the strip's input width */ char input[INPUT_BUFFER_SIZE]; /* FABRIC-0.md 4.4w: matches the strip's input width */
VM *active; VM *active;
/* FABRIC-3.md SXXV follow-up: one-time registration so console.c's
* emit_prefix() can compose "[user@VMName] " instead of the bare
* "[VMName] " it falls back to while this is still unregistered
* (early boot output, before sk_repl_run() is ever reached). */
console_set_user_prefix_provider(sk_console_user_prefix);
vm->halted = 0; vm->halted = 0;
while (!vm->halted) { while (!vm->halted) {
@@ -1299,12 +1308,12 @@ void sk_repl_run(VM *vm)
active = g_repl_active_vm ? g_repl_active_vm : vm; active = g_repl_active_vm ? g_repl_active_vm : vm;
/* Unified prompt (FABRIC-0.md 4.4a): console_putc()'s existing per-line /* Unified prompt (FABRIC-0.md 4.4a): console_putc()'s existing per-line
* "[VMName] " prefix (console.c, g_active_vm_name) already supplies the * "[user@VMName] " prefix (console.c's emit_prefix(), FABRIC-3.md
* bracket -- print only "ok> " here, don't build a second one. * SXXV follow-up) already supplies the bracket -- print only "ok> "
* emergency_console is no longer set from here (FABRIC-2.md §F.20/ * here, don't build a second one. emergency_console is no longer
* §F.21: the emergency-CLI ACL bypass is retired) -- see sk_repl_ * set from here (FABRIC-2.md §F.20/§F.21: the emergency-CLI ACL
* step()'s matching comment above. FABRIC-0.md 4.4s (2026-09-04): * bypass is retired) -- see sk_repl_step()'s matching comment
* sk_print_prompt() extends this with a "(user)" segment. */ * above. */
sk_print_prompt(); sk_print_prompt();
int n = sk_console_readline(input, sizeof(input), active, 1); int n = sk_console_readline(input, sizeof(input), active, 1);