Punch list item 1: scratch-device MINT-SCRATCH, verified live on all 3 arches
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

Implements FABRIC-3.md §XXXII.2's scratch-thumbdrive mint mechanism:
capsule_mint_identity_scratch() (capsule_mint.c/.h) builds a throwaway
RAM-backed blkio_dev via blkio_ram.c's backend and runs
capsule_mint_identity() against it completely unmodified -- same live
Zuse-signing operation, same rng_get_bytes() draw for drive_uuid a real
thumbdrive gets. Reads back only drive_uuid + the cert devblock; the
seed devblock is written into the scratch buffer internally but never
read out (no seed is ever baked into a capsule, per the ratified
no-seed decision).

New FORTH word MINT-SCRATCH (mama_forth_words.c), same stack signature
as MINT, mints into the scratch device instead of any attached drive
and never touches sk_repl_get_attached_blk_dev() or console-pairing
state. Prints the drive_uuid as hex so a live boot log itself proves
each call drew fresh entropy.

Build correction found along the way: blkio_ram.c was excluded from
the kernel build (Makefile.starkernel VM_EXCLUDE) alongside
blkio_factory.c/blkio_file.c. blkio_factory_open() unconditionally
references blkio_file.c's real fopen()/fread() file I/O, which has no
freestanding-kernel equivalent, so the factory function couldn't be
used as-is. blkio_ram.c itself is pure memcpy over a caller buffer --
pulled it alone into the kernel build and wired it directly in
capsule_mint.c, the same way blkio_factory.c's own extern declarations
do internally.

Verified live on amd64: two MINT-SCRATCH calls produced two genuinely
different drive_uuids (b533246d.../ae11b2b2...), confirming fresh
entropy per call rather than stale reuse; VM stayed healthy afterward
(5 6 + . -> 11). Clean 3-arch qemu boot (amd64/aarch64/riscv64),
logs and DoE CSVs committed per standing convention.

Remaining punch-list items (hand-transcription into a .4th block, the
unattended-birth call site, the ACL cap bit) 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 04:42:15 -04:00
co-authored by Claude Sonnet 5
parent 60bcdc09a7
commit 63864c4b01
12 changed files with 27358 additions and 2 deletions
+1 -1
View File
@@ -530,7 +530,7 @@ VM_ALL_SRCS := \
VM_EXCLUDE := \
src/main.c src/cli.c src/repl.c \
src/platform/% \
src/blkio_factory.c src/blkio_file.c src/blkio_ram.c \
src/blkio_factory.c src/blkio_file.c \
src/log.c src/doe_metrics.c \
src/vm.c src/vm_core.c src/vm_bootstrap.c src/vm_runtime.c src/vm_time.c \
src/word_source/q48_16_words.c \
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-16T00:15:36Z -->
<!-- Generated by mkcapsule --manifest 2026-09-16T08:40:33Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
+31
View File
@@ -133,6 +133,37 @@ MintResult capsule_mint_identity(struct blkio_dev *dev, VM *issuer_vm,
MintPersonality personality,
int drive_known_blank);
/**
* capsule_mint_identity_scratch - Mint into a throwaway RAM-backed device
* instead of a real thumbdrive (FABRIC-3.md §XXXII.2, unattended-identity
* punch list item 1). Runs capsule_mint_identity() completely unmodified
* against a scratch blkio_dev built from the shared RAM backend
* (blkio_ram.c) -- same live Zuse-signing operation, same rng_get_bytes()
* draw for drive_uuid a real thumbdrive gets. "Scratch" describes only
* where the bytes are written; nothing about verification changes, and
* vm_identity_from_cert() needs no changes to consume the result later.
*
* The seed devblock capsule_mint_identity() writes internally is never
* read back here -- out_uuid/out_cert are the only two fields an
* unattended identity needs (FABRIC-3.md §XXXII.2 decision, 2026-09-16:
* no seed is ever baked into a capsule).
*
* @param issuer_vm Same meaning as capsule_mint_identity()'s own --
* Zuse's cert lives here (in practice Hera).
* @param out_uuid 16 bytes, populated with the minted drive_uuid.
* @param out_cert 4096 bytes, populated with the raw (zero-padded)
* cert devblock -- matches capsule_wirebind_verify_
* cert()'s own read shape, so the same DER-length
* handling (parsed from the ASN.1 header, trailing
* padding ignored) applies at birth time later.
* @return Same MintResult capsule_mint_identity() itself returns.
*/
MintResult capsule_mint_identity_scratch(VM *issuer_vm,
const char *full_name, const char *username,
const char *email, const char *phone,
MintPersonality personality,
uint8_t out_uuid[16], uint8_t out_cert[4096]);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_CAPSULE_MINT_H */
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
+85
View File
@@ -23,6 +23,21 @@
#include <string.h>
#include <stddef.h>
/* blkio_ram.c's own backend entry points -- no header exists for these
* (blkio_factory.c, the vendored hosted-only caller, declares them the
* same way: plain externs, not a dedicated blkio_ram.h). blkio_factory.c
* itself stays out of the kernel build (it unconditionally references
* blkio_file.c's real fopen()/fread() file I/O, which has no freestanding
* kernel equivalent); blkio_ram.c alone -- pure memcpy over a caller
* buffer -- was pulled into Makefile.starkernel's kernel build for this
* function specifically (FABRIC-3.md §XXXII.2, 2026-09-16). */
extern size_t blkio_ram_state_size(void);
extern const blkio_vtable_t *blkio_ram_vtable(void);
extern int blkio_ram_init_state(void *state_mem, size_t state_len,
uint8_t *base, uint32_t total_blocks,
uint32_t fbs, uint8_t read_only,
void **out_opaque);
/* Fixed layout, devblock offsets from the drive's own start -- see
* capsule_mint.h's own doc comment. */
#define MINT_CERT_DEVBLOCK 2u
@@ -298,3 +313,73 @@ MintResult capsule_mint_identity(struct blkio_dev *dev, VM *issuer_vm,
return MINT_OK;
}
MintResult capsule_mint_identity_scratch(VM *issuer_vm,
const char *full_name, const char *username,
const char *email, const char *phone,
MintPersonality personality,
uint8_t out_uuid[16], uint8_t out_cert[4096]) {
if (!out_uuid || !out_cert) return MINT_ERR_WRITE_FAIL;
/* MINT_METADATA_DEVBLOCKS 4KiB devblocks, expressed in blkio's own
* 1KiB unit -- same conversion write_devblock()/read_devblock() above
* already use. Static, not stack: 20480 bytes is fine on either, but
* this buffer's whole reason to exist is to be reused call after call
* without re-touching any real device, so giving it a fixed home
* matches g_wirebind_attached_username's own precedent for one-shot,
* non-reentrant kernel-side scratch state. */
static uint8_t scratch_data[MINT_METADATA_DEVBLOCKS * 4u * BLKIO_FORTH_BLOCK_SIZE];
static uint8_t scratch_state[64]; /* comfortably larger than blkio_ram's
* own state struct; blkio_factory_
* open() checks the real size itself
* and fails BLKIO_ENOSPACE if this
* ever stops being enough. */
memset(scratch_data, 0, sizeof(scratch_data));
if (sizeof(scratch_state) < blkio_ram_state_size()) return MINT_ERR_WRITE_FAIL;
void *opaque = NULL;
if (blkio_ram_init_state(scratch_state, sizeof(scratch_state),
scratch_data, MINT_METADATA_DEVBLOCKS * 4u,
0, 0, &opaque) != BLKIO_OK) {
return MINT_ERR_WRITE_FAIL;
}
blkio_dev_t dev;
const blkio_params_t p = {
.forth_block_size = 0,
.total_blocks = MINT_METADATA_DEVBLOCKS * 4u,
.opaque = opaque
};
if (blkio_open(&dev, blkio_ram_vtable(), &p) != BLKIO_OK) {
return MINT_ERR_WRITE_FAIL;
}
/* Fresh, zeroed buffer every call -- never a stale sig to misread as
* "already minted," so the real homeblocks_sig_check() pass inside
* capsule_mint_identity() is redundant here, not skipped for speed. */
MintResult r = capsule_mint_identity((struct blkio_dev *)&dev, issuer_vm,
full_name, username, email, phone,
(uint8_t *)0, (uint8_t *)0,
personality, 1 /* drive_known_blank */);
if (r != MINT_OK) {
blkio_close(&dev);
return r;
}
uint8_t sig_block[4096];
if (read_devblock((struct blkio_dev *)&dev, HOMEBLOCKS_SIG_START_FBLOCK / 4u,
sig_block) != 0) {
blkio_close(&dev);
return MINT_ERR_VERIFY_FAILED;
}
memcpy(out_uuid, sig_block + offsetof(homeblocks_sig_t, drive_uuid), 16);
if (read_devblock((struct blkio_dev *)&dev, MINT_CERT_DEVBLOCK, out_cert) != 0) {
blkio_close(&dev);
return MINT_ERR_VERIFY_FAILED;
}
blkio_close(&dev);
return MINT_OK;
}
+84
View File
@@ -1149,6 +1149,88 @@ static void mama_word_mint(VM *vm)
vm_push(vm, 0);
}
/**
* @brief MINT-SCRATCH ( fname-c fname-u uname-c uname-u email-c email-u
* phone-c phone-u restrict? -- ok? )
* Same arguments and dictionary meaning as MINT, but mints into a
* throwaway RAM-backed scratch device instead of the currently attached
* drive -- never touches sk_repl_get_attached_blk_dev() or any real
* block device at all (FABRIC-3.md §XXXII.2, unattended-identity punch
* list item 1, 2026-09-16). The resulting drive_uuid + cert are kept in
* g_mint_scratch_uuid/g_mint_scratch_cert for a follow-up word to read
* out for hand-transcription into a capsule -- that packaging step is
* its own punch-list item, not built here. The uuid is printed as hex
* immediately so a live boot log itself proves each mint drew fresh
* entropy rather than reusing stale state.
*/
static uint8_t g_mint_scratch_uuid[16];
static uint8_t g_mint_scratch_cert[4096];
static void mama_word_mint_scratch(VM *vm)
{
char phone[USER_IDENTITY_PHONE_MAX];
char email[USER_IDENTITY_EMAIL_MAX];
char username[USER_IDENTITY_USERNAME_MAX];
char full_name[USER_IDENTITY_FULL_NAME_MAX];
if (vm->dsp < 0) {
console_println("MINT-SCRATCH: expects a restrict? flag on top of the stack");
vm->error = 1;
return;
}
cell_t restrict_flag = vm_pop(vm);
if (mint_pop_string(vm, phone, sizeof(phone), "phone") != 0) return;
if (mint_pop_string(vm, email, sizeof(email), "email") != 0) return;
if (mint_pop_string(vm, username, sizeof(username), "username") != 0) return;
if (mint_pop_string(vm, full_name, sizeof(full_name), "full_name") != 0) return;
MintPersonality personality = restrict_flag
? MINT_PERSONALITY_STD79_LOCKDOWN
: MINT_PERSONALITY_DEFAULT;
MintResult r = capsule_mint_identity_scratch(vm, full_name, username, email, phone,
personality,
g_mint_scratch_uuid, g_mint_scratch_cert);
switch (r) {
case MINT_OK: {
static const char hex[] = "0123456789abcdef";
char uuid_hex[33];
int i;
for (i = 0; i < 16; i++) {
uuid_hex[i * 2] = hex[(g_mint_scratch_uuid[i] >> 4) & 0xF];
uuid_hex[i * 2 + 1] = hex[g_mint_scratch_uuid[i] & 0xF];
}
uuid_hex[32] = '\0';
console_puts("MINT-SCRATCH: identity minted (scratch device, no real drive touched) -- drive_uuid ");
console_println(uuid_hex);
vm_push(vm, 1);
return;
}
case MINT_ERR_ALREADY_MINTED:
console_println("MINT-SCRATCH: refused -- scratch device already reads as minted");
break;
case MINT_ERR_NO_ZUSE_CERT:
console_println("MINT-SCRATCH: refused -- Zuse has no installed cert to sign with");
break;
case MINT_ERR_NO_ENTROPY:
console_println("MINT-SCRATCH: refused -- no entropy source");
break;
case MINT_ERR_CERT_BUILD:
console_println("MINT-SCRATCH: FAILED -- cert construction error");
break;
case MINT_ERR_WRITE_FAIL:
console_println("MINT-SCRATCH: FAILED -- scratch device write error");
break;
case MINT_ERR_INVALID_PROFILE:
console_println("MINT-SCRATCH: refused -- full_name/username missing or a field too long");
break;
case MINT_ERR_VERIFY_FAILED:
console_println("MINT-SCRATCH: FAILED -- minted but post-write verification failed "
"(see log for which check)");
break;
}
vm_push(vm, 0);
}
/**
* @brief ZUSE-ELIGIBILITY-ADD ( c-addr -- ok? )
* Add the 32-byte Ed25519 public key at c-addr to Zuse's elevation
@@ -1802,6 +1884,7 @@ void register_mama_forth_words(VM *vm)
register_word(vm, "CAPSULE-BIRTH", mama_word_capsule_birth);
register_word(vm, "CAPSULE-RUN", mama_word_capsule_run);
register_word(vm, "MINT", mama_word_mint);
register_word(vm, "MINT-SCRATCH", mama_word_mint_scratch);
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);
@@ -1861,6 +1944,7 @@ void register_mama_forth_words(VM *vm)
register_word(vm, "CAPSULE-BIRTH", mama_word_capsule_birth);
register_word(vm, "CAPSULE-RUN", mama_word_capsule_run);
register_word(vm, "MINT", mama_word_mint);
register_word(vm, "MINT-SCRATCH", mama_word_mint_scratch);
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);