Punch list item 3: UNATTENDED-BIRTH call site, verified live end-to-end

Implements the unattended-birth mechanism FABRIC-3.md §XXXII.2 designed:
UNATTENDED-BIRTH ( name-c name-u -- ok? ) births a VM from a named (p)
capsule via capsule_birth_baby() (completely unmodified, the same
generic build-time-capsule path CAPSULE-BIRTH already uses), then
installs its identity the same way capsule_wirebind.c already does
live for WIREBIND attaches -- vm_identity_from_cert() verification
followed by a plain post-birth struct assignment -- rather than
anything RUNCAP-shaped, since RUNCAP requires a real blkio_dev+
homeblocks_sig_t an unattended identity never has.

The born VM's own capsule payload is expected to lay down two CREATE'd
buffers (UNATTENDED-ID-UUID, UNATTENDED-ID-CERT) via MINT-SCRATCH-
EMIT's own literal format; their addresses are fetched by interpreting
a two-word line inside the *new* VM's own context
(vm_interpret(born_vm, ...)), the same "run inside that VM's own
dictionary" idiom capsule_wirebind.c already uses for VM-NAME-REG.

Explicit invariant preserved: never touches g_wirebind_attached_username
or any console-pairing state, births no console VM -- an unattended
identity stays un-promptable (§VIII.1) until a human pairs a console to
it later via the already-working VM-NAME-REG mechanism.

Verified live end-to-end on amd64: minted a real test identity via
MINT-SCRATCH, captured its MINT-SCRATCH-EMIT output, built a throwaway
test capsule from it (discovered along the way: mkcapsule's real block
constraints are range [2048,5120) and max 16 content lines per block --
neither matches this repo's own doc comment, corrected via ground
truth from the tool itself, not assumed), then ran UNATTENDED-BIRTH
against it: cert verified against Zuse's root pubkey, identity
installed, "no console attached" reported, Hera stayed healthy
afterward (5 6 + . -> 11). Test capsule reverted after capture per
this project's own probe convention -- not a real identity, never
committed. Clean 3-arch qemu boot (amd64/aarch64/riscv64) on the real
committed C-only change.

Remaining punch-list item (the ACL cap bit for console attachment) not
started.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
This commit is contained in:
Robert Allan James
2026-09-16 07:06:47 -04:00
co-authored by Claude Sonnet 5
parent 5fc709a228
commit 5879c8b3bc
13 changed files with 45903 additions and 1 deletions
+121
View File
@@ -48,6 +48,7 @@
#include "freestanding/stdio.h"
#include "starkernel/capsule_mint.h"
#include "starkernel/user_identity_seed.h"
#include "starkernel/vm_identity.h"
#include "starkernel/zuse_eligibility.h"
#include "starkernel/capsule_loader.h"
#include "starkernel/capsule_run.h"
@@ -1613,6 +1614,124 @@ void mama_word_capsule_birth(VM *vm)
vm_push(vm, (cell_t)new_vm_id.hi);
}
/**
* @brief UNATTENDED-BIRTH ( 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
* birth path CAPSULE-BIRTH above already uses), then a post-birth
* identity install matching capsule_wirebind.c's own proven pattern
* (verified cert -> plain struct assignment onto the born VM's own
* identity field) rather than anything RUNCAP-shaped -- RUNCAP requires
* a real blkio_dev+homeblocks_sig_t an unattended identity never has.
*
* The capsule's own IDENTITY code (run as part of capsule_birth_baby()
* itself) is expected to have already laid down two CREATE'd buffers
* in the new VM's own dictionary -- UNATTENDED-ID-UUID (16 bytes) and
* UNATTENDED-ID-CERT (4096 bytes), exactly the shape MINT-SCRATCH-EMIT
* prints for hand-transcription. Their addresses are fetched by
* interpreting a two-word line inside the *new* VM's own context
* (vm_interpret(born_vm, ...)) -- the same "run inside that VM's own
* dictionary" idiom capsule_wirebind.c already uses for VM-NAME-REG,
* not a new mechanism.
*
* 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.
*/
static void mama_word_unattended_birth(VM *vm)
{
char capsule_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");
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: capsule 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++) capsule_name[i] = (char)p[i];
}
capsule_name[u] = '\0';
if (!vm->zuse_root_pubkey_known) {
console_println("UNATTENDED-BIRTH: refused -- Zuse's root pubkey not yet loaded");
vm_push(vm, 0);
return;
}
VMUuid new_vm_id = vm_uuid_none();
void *new_vm_ctx = (void *)0;
CapsuleRunResult result = capsule_birth_baby(
capsule_name,
capsule_get_directory(),
capsule_get_descriptors(),
capsule_get_names(),
capsule_get_arena(),
vm->stadium_vm_id,
0, /* skip_pki_sig: normal build-time capsule, same as CAPSULE-BIRTH */
&new_vm_id,
&new_vm_ctx
);
if (result != CAPSULE_RUN_OK || !new_vm_ctx) {
console_println("UNATTENDED-BIRTH: birth FAILED");
vm_push(vm, 0);
return;
}
VM *born_vm = (VM *)new_vm_ctx;
cell_t dsp_before = born_vm->dsp;
vm_interpret(born_vm, "UNATTENDED-ID-UUID UNATTENDED-ID-CERT");
if (born_vm->error || born_vm->dsp != dsp_before + 2) {
console_println("UNATTENDED-BIRTH: refused -- capsule never defined "
"UNATTENDED-ID-UUID/UNATTENDED-ID-CERT");
vm_push(vm, 0);
return;
}
cell_t cert_addr = vm_pop(born_vm);
cell_t uuid_addr = vm_pop(born_vm);
const uint8_t *cert = vm_ptr(born_vm, (vaddr_t)cert_addr);
const uint8_t *uuid = vm_ptr(born_vm, (vaddr_t)uuid_addr);
if (!cert || !uuid) {
console_println("UNATTENDED-BIRTH: refused -- invalid identity data address");
vm_push(vm, 0);
return;
}
VMIdentity identity;
if (vm_identity_from_cert(&identity, cert, 4096,
vm->zuse_root_pubkey, uuid,
0 /* acl_caps: no bits assigned yet, §F.2 */) != 0) {
console_println("UNATTENDED-BIRTH: refused -- cert verification FAILED");
vm_push(vm, 0);
return;
}
born_vm->identity = identity;
console_println("UNATTENDED-BIRTH: identity installed, no console attached");
vm_push(vm, 1);
}
/**
* @brief CAPSULE-RUN ( capsule-id -- )
* Run an experiment (e) capsule on Mama.
@@ -1953,6 +2072,7 @@ void register_mama_forth_words(VM *vm)
register_word(vm, "MINT", mama_word_mint);
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, "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);
@@ -2014,6 +2134,7 @@ void register_mama_forth_words(VM *vm)
register_word(vm, "MINT", mama_word_mint);
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, "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);