From 4d4ab591898cc1f4c97665da6ee092ec701e7b81 Mon Sep 17 00:00:00 2001 From: Robert Allan James Date: Sat, 5 Sep 2026 16:17:19 -0400 Subject: [PATCH] =?UTF-8?q?Build=20the=20FIRSTTOUCH=20overflow=20trigger?= =?UTF-8?q?=20flagged=20in=20FABRIC-2.md=20=C2=A7I.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overflow-triggered migration path (a WIREBIND-attached identity's own drive running low on space) was scoped but never built -- only the trigger-detection call site was missing, per this section's own text. - capsule_wirebind.c now tracks the attached blkio_dev* alongside the already-tracked VM id, set in try_attach() and cleared in both EJECT/UNCLEAN paths. - New capsule_wirebind_overflow_idle_check(), called once per idle tick in repl.c right alongside blk_migration_idle_check() (same cadence): reads the attached drive's free/total via blk_get_device_free_blocks(), and if free space is below a fixed 10% threshold, extends the identity's pool with a one-time blk_firsttouch_claim() of 8 additional devblocks on Artemis's system-resident device. - New blk_owner_has_claim(owner_fp) in block_subsystem.c answers the debounce question blk_firsttouch_claim()'s own doc comment had left open: a disk scan, not a RAM flag, so the already-extended answer survives reboot/reattach, matching BMAPFMT's "ownership travels with the block" model. Premise checked before building (does a WIREBIND-attached drive actually give a real free/total signal, or does it stay PROVISIONAL/raw): traced repl.c's attach sequence and confirmed blk_subsys_attach_device() runs on the same dev pointer right after WIREBIND, and a WIREBIND-eligible drive is always already STFR/v2-formatted, so the signal is real. Premise held, unlike the BAM item's overstated one. Verified with the mandatory 3-arch QEMU acceptance (identical dictionary hashes, no regression) plus a live logic test of blk_owner_has_claim(): a temporary TEST-OWNER-CLAIM word, run once via SK_CMD and reverted, confirmed it correctly detects the claiming owner and rejects an unrelated one. The low-disk-space-triggers-a-claim path itself is not verified end-to-end -- that needs a real minted WIREBIND-user thumbdrive with deliberately tiny capacity, out of scope for this pass; noted as such in the FABRIC-2.md §I.2 closure note rather than overclaimed. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4 --- FABRIC-2.md | 29 +++++++++++ include/block_subsystem.h | 21 ++++++++ include/starkernel/capsule_wirebind.h | 23 +++++++++ lfs/amd64/starforth | Bin 1544744 -> 1544744 bytes src/block_subsystem.c | 25 ++++++++++ src/starkernel/capsule/capsule_wirebind.c | 57 ++++++++++++++++++++++ src/starkernel/repl.c | 7 +++ 7 files changed, 162 insertions(+) diff --git a/FABRIC-2.md b/FABRIC-2.md index f5d5873..fdbac7b 100644 --- a/FABRIC-2.md +++ b/FABRIC-2.md @@ -4431,6 +4431,35 @@ stale the way the original carry-forwards did. scoped, mirroring `blk_subsys_detach_device()`'s own internal lookup, now exposed) exist specifically so that call site is a small addition when someone picks it up, not a redesign. + **CLOSED 2026-09-05.** Premise checked before building, not trusted from this item's own + text (same discipline that caught the BAM item's overstatement): traced `repl.c`'s USB-attach + sequence and confirmed `capsule_wirebind_try_attach(&usb_blk_dev, ...)` (the regular-identity + WIREBIND path, `HOMEBLOCKS_SIG_OK` only) runs immediately before `blk_subsys_attach_device + (&usb_blk_dev)` on the same pointer — since a WIREBIND-eligible drive is already a real + STFR/v2-formatted home-blocks drive, `blk_format_or_load_disk()` takes the recognized-header + branch, not PROVISIONAL, so `blk_get_device_free_blocks()` returns real numbers by idle-check + time. Premise held. **Built**: `capsule_wirebind.c` now tracks the attached `blkio_dev*` + (`g_wirebind_attached_dev`, set alongside `g_wirebind_attached_vm_id` in `try_attach()`, + cleared in both `EJECT`/`UNCLEAN`); new `capsule_wirebind_overflow_idle_check()`, called once + per idle tick in `repl.c` right alongside `blk_migration_idle_check()` (same cadence), reads + the attached drive's free/total via `blk_get_device_free_blocks()` and, if free space is + below a fixed 10% (`WIREBIND_OVERFLOW_FREE_THRESHOLD_PCT`, "fixed first" per this project's + own standing sequencing), extends the identity's pool with a **one-time** + `blk_firsttouch_claim()` of 8 additional devblocks (`WIREBIND_OVERFLOW_CLAIM_DEVBLOCKS`) on + Artemis's system-resident device. **New `blk_owner_has_claim(owner_fp)`** (`block_subsystem.c`) + answers the debounce question this section's own doc comment on `blk_firsttouch_claim()` + had left as "a separate, not-yet-decided question" — decided on the reboot-survival axis: a + disk scan (same shape as `blk_firsttouch_claim()`'s own scan), not a RAM flag, matching + BMAPFMT's "ownership travels with the block, no centralized table" model, so the + already-extended answer survives reboot/reattach for free. **Verified**: full 3-arch QEMU + acceptance (identical dictionary hashes, no regression) plus a live logic test of + `blk_owner_has_claim()` itself — a temporary `TEST-OWNER-CLAIM` word, run once via `SK_CMD` + and reverted, confirmed it correctly detects the claiming owner and correctly rejects an + unrelated one. **Not verified end-to-end**: the actual low-disk-space-triggers-a-claim path + itself, which needs a real minted WIREBIND-user thumbdrive with deliberately tiny capacity to + reproduce naturally — out of scope for this pass; the plumbing exercises its no-op branch + every idle tick with no crash, but the "drive actually runs low" branch is unexercised. + **Also left open, a real correctness gap, not swept under**: `blk_meta_t`'s `owner_fp`/ `BLK_FLAG_CLAIMED` (BMAPFMT's distributed ownership) and the pre-existing, separate BAM (`blk_bam_entry_t{allocated,dirty}`, the generic free/allocated bitmap `blk_allocate()`/ diff --git a/include/block_subsystem.h b/include/block_subsystem.h index 510c94d..204f4a7 100644 --- a/include/block_subsystem.h +++ b/include/block_subsystem.h @@ -444,6 +444,27 @@ int blk_get_first_disk_range(uint32_t *out_start_lbn, uint32_t *out_count); */ int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *out_chain_head); +/* blk_owner_has_claim - FABRIC-2.md §I.2's own "an existing claim is a + * separate, not-yet-decided question" (see blk_firsttouch_claim()'s doc + * comment above), decided 2026-09-05 for the overflow-trigger call site + * (capsule_wirebind.c): answered by a disk scan, not a RAM flag, matching + * BMAPFMT's own "ownership travels with the block, no centralized table" + * model -- the answer survives a reboot/reattach for free, the same way + * blk_firsttouch_claim()'s own free/claimed scan already does. + * + * One linear scan over blk_get_first_disk_range()'s representative LBNs + * (same shape as blk_firsttouch_claim()'s own scan); returns as soon as + * any devblock's blk_meta_t has BLK_FLAG_CLAIMED set and owner_fp equal + * to the argument. + * + * @param owner_fp 8-byte fingerprint, same convention as + * blk_firsttouch_claim()'s own owner_fp argument. + * @return 1 if owner_fp already owns at least one claimed devblock, 0 if + * not (including "no disk-backed device attached" -- nothing to + * own in that case). + */ +int blk_owner_has_claim(const uint8_t owner_fp[8]); + /* blk_meta_relocate_devblock - the real migration primitive (FABRIC-2.md * §I.2, migration state machine, 2026-09-04): moves one devblock's full * content and blk_meta_t ownership from home_devblock to target_devblock. diff --git a/include/starkernel/capsule_wirebind.h b/include/starkernel/capsule_wirebind.h index d01f404..e692ae4 100644 --- a/include/starkernel/capsule_wirebind.h +++ b/include/starkernel/capsule_wirebind.h @@ -122,6 +122,29 @@ void capsule_wirebind_unclean_detach(void); */ const char *capsule_wirebind_attached_username(void); +/** + * capsule_wirebind_overflow_idle_check - FABRIC-2.md §I.2's own "overflow + * trigger," decided and built 2026-09-05. Called once per idle tick + * (sk_repl_idle(), repl.c, alongside blk_migration_idle_check() -- same + * ~1 Hz cadence), same as that function's own convention. + * + * No-op if nothing is attached via WIREBIND. Otherwise reads the attached + * drive's own free/total via blk_get_device_free_blocks() (real numbers: + * a WIREBIND-attached drive is always HOMEBLOCKS_SIG_OK, i.e. already + * STFR/v2-formatted, by the time blk_subsys_attach_device() runs on the + * same dev pointer right after WIREBIND itself -- not PROVISIONAL, not + * raw). If free space is below a fixed threshold AND the attached + * identity does not already own a claim (blk_owner_has_claim() -- a disk + * scan, not a RAM flag, so this decision survives reboot/reattach for + * free), claims a fixed number of additional devblocks on Artemis's own + * device via blk_firsttouch_claim() -- a one-time-per-identity extension, + * not a growth loop, deliberately: this does not free space on the + * user's own drive, it only extends their pool onto system-resident + * space, so re-claiming every tick once already extended would walk + * Artemis's device to exhaustion for no benefit. + */ +void capsule_wirebind_overflow_idle_check(void); + #endif /* __STARKERNEL__ */ #endif /* STARKERNEL_CAPSULE_WIREBIND_H */ diff --git a/lfs/amd64/starforth b/lfs/amd64/starforth index e531f14870b30cb53b9bfe3bd2f67376d33db76f..19b5856ce0dfde2a72a7e4265f863ea4c056f17d 100755 GIT binary patch delta 140 zcmZ3nByPo$xD7j)M4F!6_3ACF(^tONrD1*f>-|YqyTqE$FtwjyVgzCk%?!jWK+Fon zY(UHo#2i4(3B+7L%nigmK+FroeA~}3@z2a+GqWGrH PSQLoGwrA&x?^OW+E8R9` diff --git a/src/block_subsystem.c b/src/block_subsystem.c index 659aed8..7f38d9a 100644 --- a/src/block_subsystem.c +++ b/src/block_subsystem.c @@ -1481,6 +1481,31 @@ int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *ou return BLK_OK; } +/* blk_owner_has_claim - FABRIC-2.md §I.2, 2026-09-05. Disk-scan answer to + * "does owner_fp already own a claim," per the decision recorded in + * block_subsystem.h: matching BLK_FLAG_CLAIMED devblock, not a RAM flag. */ +int blk_owner_has_claim(const uint8_t owner_fp[8]) { + if (!owner_fp) return 0; + + blk_dev_slot_t *slot = first_disk_slot(); + if (!slot) return 0; + + for (uint32_t lbn = slot->start_lbn; + lbn < slot->start_lbn + slot->user_blocks; + lbn++) { + uint32_t rel_pbn = lbn_to_slot_pbn(slot, lbn); + if (slot_pbn_pack_offset(rel_pbn) != 0) continue; + + blk_meta_t meta; + if (blk_get_meta(lbn, &meta) != BLK_OK) continue; + if ((meta.flags & BLK_FLAG_CLAIMED) && + memcmp(meta.owner_fp, owner_fp, 8) == 0) { + return 1; + } + } + return 0; +} + int blk_meta_relocate_devblock(uint32_t home_devblock, uint32_t target_devblock) { if (home_devblock == target_devblock) return BLK_EINVAL; diff --git a/src/starkernel/capsule/capsule_wirebind.c b/src/starkernel/capsule/capsule_wirebind.c index bf73051..21ed0e9 100644 --- a/src/starkernel/capsule/capsule_wirebind.c +++ b/src/starkernel/capsule/capsule_wirebind.c @@ -39,6 +39,11 @@ static int g_wirebind_attached_valid = 0; * user_vm_name ("~user"), not the bare form the (user) prompt * segment wants (FABRIC-2.md §I.1/4.4s). */ static char g_wirebind_attached_username[USER_IDENTITY_USERNAME_MAX] = {0}; +/* FABRIC-2.md §I.2 overflow trigger, 2026-09-05: the attached drive's own + * blkio_dev pointer, tracked alongside the two fields above so + * capsule_wirebind_overflow_idle_check() can query its free space later + * without a new attach-time argument threaded through the idle loop. */ +static struct blkio_dev *g_wirebind_attached_dev = (struct blkio_dev *) 0; /* WIREBIND_CERT_MAX_DEVBLOCKS: a sane upper bound on how much cert * content this reads, independent of whatever sig->cert_devblocks @@ -47,6 +52,17 @@ static char g_wirebind_attached_username[USER_IDENTITY_USERNAME_MAX] = {0}; * generous headroom, not a real constraint. */ #define WIREBIND_CERT_MAX_DEVBLOCKS 4u +/* FABRIC-2.md §I.2 overflow trigger, 2026-09-05: fixed constants, "fixed + * first" per this project's own standing sequencing (MIGRATION_WEAR_ + * THRESHOLD, ZUSE_SESSION_TTL_SECONDS -- adaptive only after a real, + * measured baseline exists, not from day one). A single DoE-tunable knob + * apiece, not yet wired to Kconfig -- revisit once real usage data exists. + * 10% is a conservative first guess (extend well before actually full); + * 8 devblocks (24 Forth blocks, ~24 KiB) is a modest first extension, not + * a large land-grab. */ +#define WIREBIND_OVERFLOW_FREE_THRESHOLD_PCT 10u +#define WIREBIND_OVERFLOW_CLAIM_DEVBLOCKS 8u + /* Read exactly one devblock (4096 bytes) at devblock offset `devblock` * -- same convention capsule_runcap.c/capsule_mint.c already use. */ static int read_devblock(struct blkio_dev *dev, uint32_t devblock, uint8_t *buf4096) { @@ -184,6 +200,7 @@ void capsule_wirebind_try_attach(struct blkio_dev *dev, * actually owns block-subsystem state). */ g_wirebind_attached_vm_id = user_id; g_wirebind_attached_valid = 1; + g_wirebind_attached_dev = dev; memcpy(g_wirebind_attached_username, username, sizeof(g_wirebind_attached_username)); /* Register the pairing in the console's own routing table, index 3 @@ -233,6 +250,7 @@ int capsule_wirebind_eject(void) { capsule_vm_kill(entry.name); g_wirebind_attached_valid = 0; + g_wirebind_attached_dev = (struct blkio_dev *) 0; g_wirebind_attached_username[0] = '\0'; console_puts("EJECT: "); @@ -254,6 +272,7 @@ void capsule_wirebind_unclean_detach(void) { capsule_vm_kill(entry.name); g_wirebind_attached_valid = 0; + g_wirebind_attached_dev = (struct blkio_dev *) 0; g_wirebind_attached_username[0] = '\0'; console_puts("WIREBIND: "); @@ -265,3 +284,41 @@ const char *capsule_wirebind_attached_username(void) { if (!g_wirebind_attached_valid) return (const char *)0; return g_wirebind_attached_username; } + +void capsule_wirebind_overflow_idle_check(void) { + if (!g_wirebind_attached_valid || !g_wirebind_attached_dev) return; + + VMRegistryEntry entry; + if (capsule_vm_registry_get(g_wirebind_attached_vm_id, &entry) != 0 || + entry.state != VM_STATE_LIVE || !entry.vm_ptr) { + return; + } + + uint64_t free_blocks, total_blocks; + if (blk_get_device_free_blocks(g_wirebind_attached_dev, &free_blocks, &total_blocks) + != BLK_OK || total_blocks == 0) { + return; + } + + if (free_blocks * 100u >= total_blocks * WIREBIND_OVERFLOW_FREE_THRESHOLD_PCT) { + return; /* plenty of room left on the drive itself */ + } + + uint8_t owner_fp[8]; + memcpy(owner_fp, ((VM *)entry.vm_ptr)->identity.owner_pubkey, sizeof(owner_fp)); + + if (blk_owner_has_claim(owner_fp)) { + return; /* already extended once -- not a growth loop, see header doc */ + } + + uint32_t chain_head; + if (blk_firsttouch_claim(owner_fp, WIREBIND_OVERFLOW_CLAIM_DEVBLOCKS, &chain_head) + != BLK_OK) { + console_println("WIREBIND: overflow trigger fired but Artemis has no room to extend into"); + return; + } + + console_puts("WIREBIND: "); + console_puts(g_wirebind_attached_username); + console_println(" running low on drive space -- extended onto system-resident storage"); +} diff --git a/src/starkernel/repl.c b/src/starkernel/repl.c index 74171f7..16ca9ca 100644 --- a/src/starkernel/repl.c +++ b/src/starkernel/repl.c @@ -368,6 +368,13 @@ static void sk_repl_idle(VM *active_vm) * migration, needs a call site threaded from WIREBIND). */ blk_migration_idle_check(); + /* FABRIC-2.md §I.2's own overflow trigger, closed 2026-09-05: the + * call site named above, now built. Same cadence, same idle-tick + * neighbor -- see capsule_wirebind_overflow_idle_check()'s own doc + * comment for what it does and why it's a one-time extension, not a + * growth loop. */ + capsule_wirebind_overflow_idle_check(); + /* FABRIC-2.md Phase C (2026-08-28): distributed messaging pump. Every * live VM except Hera herself now owns its own MSG-ARENA/CH-ARENA and * MSG-TICK word (see capsules/common/messaging.4th) instead of only