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 e531f14..19b5856 100755 Binary files a/lfs/amd64/starforth and b/lfs/amd64/starforth differ 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