Punch list item 4 (final): CONSOLE-ATTACH, full unattended-identity flow verified end-to-end
Closes FABRIC-3.md §XXXII.2's punch list. CONSOLE-ATTACH ( name-c name-u -- ok? ) pairs a fresh console VM to an already-live VM registered as "<name>~user". Deliberately a plain, unconditional primitive with no VMIdentity capability-bit check -- corrects this session's own first-pass design (§XXXII.2 amended in the same commit): identity.installed is 0 for Hera/Hermes/Artemis and for every console-proxy VM, so a capability-bit gate would be unreachable for every VM a human actually types at, Zuse included. Matches ZUSE-ELIGIBILITY-ADD's own "no bespoke gate" precedent in this file; real gating is `' CONSOLE-ATTACH ACL-PIN` in ACL.4th if ever wanted. Two real bugs found and fixed via live testing, not assumed correct: - capsule_birth_baby() never sets a VM's registry name (documented gap, same one capsule_runcap_birth()'s own history already hit) -- UNATTENDED-BIRTH gained a second `name` argument and now calls capsule_vm_registry_set_name(new_vm_id, "<name>~user") itself. - CONSOLE-ATTACH's first draft took an independent console name from the target's name. sk_repl_dispatch_line()'s pairing check (repl.c) reconstructs the target as console_get_vm_name()+"~user" -- a mismatched console name silently falls back to direct interpretation with no error. Caught live (typed `5 6 + .` at a mismatched console, got a direct `11` instead of a relay) and fixed by collapsing to one name argument, matching WIREBIND's own by-construction invariant. Full end-to-end live verification on amd64: minted a test identity, UNATTENDED-BIRTH'd it as "bob", CONSOLE-ATTACH'd a console named "bob", USE'd it, typed `5 6 + .` -- no direct output at [zuse@bob] (relay path taken), then `[zuse@bob~user] 11 ok>` appeared: the relayed command executed on the target identity VM itself and printed its own answer back through the shared console. Hera stayed healthy throughout (2 2 + . -> 4 after switching back). CONSOLE-ATTACH also verified to refuse cleanly on a nonexistent target with no orphaned VM. Test capsule reverted after capture per this project's probe convention -- never committed. FABRIC-3.md §XXXII.2 fully closed: all 4 original questions ratified, the mid-course drive_uuid and ACL-bit corrections both recorded plainly rather than silently folded in, and a doc-accuracy note left for CLAUDE.md's own stale "1024-byte block limit" framing (mkcapsule's real limits are range [2048,5120) and 16 content lines/block) -- flagged, not fixed, out of this punch list's scope. Clean 3-arch qemu boot (amd64/aarch64/riscv64) on the real committed C-only change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5879c8b3bc
commit
af351bff92
@@ -1615,7 +1615,7 @@ void mama_word_capsule_birth(VM *vm)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UNATTENDED-BIRTH ( name-c name-u -- ok? )
|
||||
* @brief UNATTENDED-BIRTH ( capsule-c capsule-u name-c name-u -- ok? )
|
||||
* Birth an unattended identity from a named (p) capsule -- the
|
||||
* punch-list item 3 call site (FABRIC-3.md §XXXII.2, 2026-09-16):
|
||||
* capsule_birth_baby() unmodified (same generic build-time-capsule
|
||||
@@ -1635,25 +1635,53 @@ void mama_word_capsule_birth(VM *vm)
|
||||
* dictionary" idiom capsule_wirebind.c already uses for VM-NAME-REG,
|
||||
* not a new mechanism.
|
||||
*
|
||||
* `name` becomes the VM's own registry name, suffixed "~user" exactly
|
||||
* as capsule_runcap_birth()/WIREBIND already do for a real thumbdrive
|
||||
* identity -- this is what CONSOLE-ATTACH (below) and sk_repl_dispatch_
|
||||
* line()'s own pairing check (repl.c) look up later. capsule_birth_
|
||||
* baby() itself never sets this (found live during capsule_runcap_
|
||||
* birth()'s own history, repeated here rather than left silently
|
||||
* missing): every caller does it as its own explicit step.
|
||||
*
|
||||
* Explicit invariant (FABRIC-3.md §XXXII.2's own ratified text): this
|
||||
* word never touches g_wirebind_attached_username or any other
|
||||
* WIREBIND/console-pairing state, and births no console VM -- an
|
||||
* unattended identity stays un-promptable (§VIII.1) until a human
|
||||
* pairs a console to it later via the existing VM-NAME-REG mechanism,
|
||||
* a separate, already-working step this word does not perform.
|
||||
* pairs a console to it later via CONSOLE-ATTACH, a separate step this
|
||||
* word does not perform.
|
||||
*/
|
||||
static void mama_word_unattended_birth(VM *vm)
|
||||
{
|
||||
char capsule_name[VM_NAME_MAX];
|
||||
char id_name[VM_NAME_MAX];
|
||||
cell_t u, caddr;
|
||||
uint32_t i;
|
||||
|
||||
if (vm->dsp < 1) {
|
||||
console_println("UNATTENDED-BIRTH: expects S\" capsule-name\" UNATTENDED-BIRTH");
|
||||
if (vm->dsp < 3) {
|
||||
console_println("UNATTENDED-BIRTH: expects S\" capsule\" S\" name\" UNATTENDED-BIRTH");
|
||||
vm->error = 1;
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
u = vm_pop(vm);
|
||||
caddr = vm_pop(vm);
|
||||
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
|
||||
console_println("UNATTENDED-BIRTH: identity name too long or empty");
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
{
|
||||
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
|
||||
if (!p) {
|
||||
console_println("UNATTENDED-BIRTH: invalid address on the stack");
|
||||
vm->error = 1;
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < (uint32_t)u; i++) id_name[i] = (char)p[i];
|
||||
}
|
||||
id_name[u] = '\0';
|
||||
|
||||
u = vm_pop(vm);
|
||||
caddr = vm_pop(vm);
|
||||
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
|
||||
@@ -1679,6 +1707,18 @@ static void mama_word_unattended_birth(VM *vm)
|
||||
return;
|
||||
}
|
||||
|
||||
char reg_name[VM_NAME_MAX + 8];
|
||||
{
|
||||
size_t nlen = strlen(id_name);
|
||||
if (nlen + 6 > sizeof(reg_name)) {
|
||||
console_println("UNATTENDED-BIRTH: identity name too long");
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
memcpy(reg_name, id_name, nlen);
|
||||
memcpy(reg_name + nlen, "~user", 6);
|
||||
}
|
||||
|
||||
VMUuid new_vm_id = vm_uuid_none();
|
||||
void *new_vm_ctx = (void *)0;
|
||||
CapsuleRunResult result = capsule_birth_baby(
|
||||
@@ -1727,11 +1767,124 @@ static void mama_word_unattended_birth(VM *vm)
|
||||
return;
|
||||
}
|
||||
born_vm->identity = identity;
|
||||
capsule_vm_registry_set_name(new_vm_id, reg_name);
|
||||
|
||||
console_println("UNATTENDED-BIRTH: identity installed, no console attached");
|
||||
vm_push(vm, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CONSOLE-ATTACH ( name-c name-u -- ok? )
|
||||
* Pair a fresh console VM to an already-live VM registered as
|
||||
* "<name>~user" -- the "ACL-gated console attachment later" step
|
||||
* FABRIC-3.md §XXXII.2's Q3 describes, for both an UNATTENDED-BIRTH'd
|
||||
* identity and, just as validly, a WIREBIND-attached one that lost its
|
||||
* console (same registry-name convention either way).
|
||||
*
|
||||
* Takes exactly one name, not an independently-chosen console name --
|
||||
* found live, not assumed: sk_repl_dispatch_line()'s own pairing check
|
||||
* (repl.c) reconstructs the target as console_get_vm_name() + "~user",
|
||||
* where console_get_vm_name() is whatever name USE last switched the
|
||||
* prompt to (the console's own birth name). A console named anything
|
||||
* other than the identity's own base name reconstructs the wrong
|
||||
* target string and silently falls back to direct interpretation --
|
||||
* no error, just quietly never relays. WIREBIND's own console+user
|
||||
* birth pair (capsule_wirebind_try_attach()) already enforces this by
|
||||
* construction (both sides built from the same `username`); this word
|
||||
* makes the same invariant explicit rather than accepting two names
|
||||
* that could mismatch.
|
||||
*
|
||||
* Deliberately a plain, unconditional primitive -- no VMIdentity
|
||||
* capability-bit check here or anywhere else in this codebase (§XXXII.2
|
||||
* amended 2026-09-16, correcting the first pass of this section, which
|
||||
* had proposed exactly that). Two independent reasons rule it out, not
|
||||
* one: CLAUDE.md's own hard rule ("ACL policy belongs in ACL.4th, never
|
||||
* in C"), and a mechanical fact traced live -- identity.installed is 0
|
||||
* for Hera/Hermes/Artemis *and* for every console-proxy VM, so a
|
||||
* vm_identity_has_cap() gate here would be unreachable for every VM a
|
||||
* human actually types at, Zuse included. Matches ZUSE-ELIGIBILITY-ADD's
|
||||
* own precedent in this same file: restricting who may call this word,
|
||||
* if ever wanted, is `' CONSOLE-ATTACH ACL-PIN` in ACL.4th, not a
|
||||
* bespoke check invented here.
|
||||
*
|
||||
* Target liveness is resolved *before* birthing anything -- a typo'd
|
||||
* name refuses cleanly with no orphaned console VM left behind, rather
|
||||
* than birthing first and discovering the mismatch after.
|
||||
*/
|
||||
static void mama_word_console_attach(VM *vm)
|
||||
{
|
||||
char id_name[VM_NAME_MAX];
|
||||
cell_t u, caddr;
|
||||
uint32_t i;
|
||||
|
||||
if (vm->dsp < 1) {
|
||||
console_println("CONSOLE-ATTACH: expects S\" name\" CONSOLE-ATTACH");
|
||||
vm->error = 1;
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
u = vm_pop(vm);
|
||||
caddr = vm_pop(vm);
|
||||
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
|
||||
console_println("CONSOLE-ATTACH: name too long or empty");
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
{
|
||||
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
|
||||
if (!p) {
|
||||
console_println("CONSOLE-ATTACH: invalid address on the stack");
|
||||
vm->error = 1;
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < (uint32_t)u; i++) id_name[i] = (char)p[i];
|
||||
}
|
||||
id_name[u] = '\0';
|
||||
|
||||
char target_name[VM_NAME_MAX + 8];
|
||||
{
|
||||
size_t nlen = strlen(id_name);
|
||||
if (nlen + 6 > sizeof(target_name)) {
|
||||
console_println("CONSOLE-ATTACH: target name too long");
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
memcpy(target_name, id_name, nlen);
|
||||
memcpy(target_name + nlen, "~user", 6);
|
||||
}
|
||||
|
||||
/* Resolve first -- refuse cleanly on a typo instead of birthing a
|
||||
* console VM with nothing live to pair it to. */
|
||||
VMRegistryEntry target_entry;
|
||||
if (capsule_vm_find_by_name(target_name, &target_entry) != 0 ||
|
||||
target_entry.state != VM_STATE_LIVE) {
|
||||
console_println("CONSOLE-ATTACH: refused -- no live VM registered under that name");
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
VMUuid console_id;
|
||||
void *console_ctx = (void *)0;
|
||||
if (capsule_console_birth(id_name, vm->stadium_vm_id, &console_id, &console_ctx)
|
||||
!= CAPSULE_RUN_OK) {
|
||||
console_println("CONSOLE-ATTACH: console birth FAILED");
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
char reg_cmd[VM_NAME_MAX + 32];
|
||||
int n = snprintf(reg_cmd, sizeof(reg_cmd), "S\" %s\" 3 VM-NAME-REG", target_name);
|
||||
if (n > 0 && (size_t)n < sizeof(reg_cmd)) {
|
||||
vm_interpret((VM *)console_ctx, reg_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
console_println("CONSOLE-ATTACH: console paired to target -- USE the console name to attach");
|
||||
vm_push(vm, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CAPSULE-RUN ( capsule-id -- )
|
||||
* Run an experiment (e) capsule on Mama.
|
||||
@@ -2073,6 +2226,7 @@ void register_mama_forth_words(VM *vm)
|
||||
register_word(vm, "MINT-SCRATCH", mama_word_mint_scratch);
|
||||
register_word(vm, "MINT-SCRATCH-EMIT", mama_word_mint_scratch_emit);
|
||||
register_word(vm, "UNATTENDED-BIRTH", mama_word_unattended_birth);
|
||||
register_word(vm, "CONSOLE-ATTACH", mama_word_console_attach);
|
||||
register_word(vm, "ZUSE-ELIGIBILITY-ADD", mama_word_zuse_eligibility_add);
|
||||
register_word(vm, "ZUSE-ELIGIBLE?", mama_word_zuse_eligible_query);
|
||||
register_word(vm, "NAME>XT", mama_word_name_to_xt);
|
||||
@@ -2135,6 +2289,7 @@ void register_mama_forth_words(VM *vm)
|
||||
register_word(vm, "MINT-SCRATCH", mama_word_mint_scratch);
|
||||
register_word(vm, "MINT-SCRATCH-EMIT", mama_word_mint_scratch_emit);
|
||||
register_word(vm, "UNATTENDED-BIRTH", mama_word_unattended_birth);
|
||||
register_word(vm, "CONSOLE-ATTACH", mama_word_console_attach);
|
||||
register_word(vm, "ZUSE-ELIGIBILITY-ADD", mama_word_zuse_eligibility_add);
|
||||
register_word(vm, "ZUSE-ELIGIBLE?", mama_word_zuse_eligible_query);
|
||||
register_word(vm, "NAME>XT", mama_word_name_to_xt);
|
||||
|
||||
Reference in New Issue
Block a user