Build the FIRSTTOUCH overflow trigger flagged in FABRIC-2.md §I.2

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4
This commit is contained in:
Robert Allan James
2026-09-05 16:17:19 -04:00
co-authored by Claude Sonnet 5
parent d8ac27abb2
commit 4d4ab59189
7 changed files with 162 additions and 0 deletions
+29
View File
@@ -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 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. 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`/ **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_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()`/ (`blk_bam_entry_t{allocated,dirty}`, the generic free/allocated bitmap `blk_allocate()`/
+21
View File
@@ -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); 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 /* blk_meta_relocate_devblock - the real migration primitive (FABRIC-2.md
* §I.2, migration state machine, 2026-09-04): moves one devblock's full * §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. * content and blk_meta_t ownership from home_devblock to target_devblock.
+23
View File
@@ -122,6 +122,29 @@ void capsule_wirebind_unclean_detach(void);
*/ */
const char *capsule_wirebind_attached_username(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__ */
#endif /* STARKERNEL_CAPSULE_WIREBIND_H */ #endif /* STARKERNEL_CAPSULE_WIREBIND_H */
Binary file not shown.
+25
View File
@@ -1481,6 +1481,31 @@ int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *ou
return BLK_OK; 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) { int blk_meta_relocate_devblock(uint32_t home_devblock, uint32_t target_devblock) {
if (home_devblock == target_devblock) return BLK_EINVAL; if (home_devblock == target_devblock) return BLK_EINVAL;
+57
View File
@@ -39,6 +39,11 @@ static int g_wirebind_attached_valid = 0;
* user_vm_name ("<username>~user"), not the bare form the (user) prompt * user_vm_name ("<username>~user"), not the bare form the (user) prompt
* segment wants (FABRIC-2.md §I.1/4.4s). */ * segment wants (FABRIC-2.md §I.1/4.4s). */
static char g_wirebind_attached_username[USER_IDENTITY_USERNAME_MAX] = {0}; 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 /* WIREBIND_CERT_MAX_DEVBLOCKS: a sane upper bound on how much cert
* content this reads, independent of whatever sig->cert_devblocks * 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. */ * generous headroom, not a real constraint. */
#define WIREBIND_CERT_MAX_DEVBLOCKS 4u #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` /* Read exactly one devblock (4096 bytes) at devblock offset `devblock`
* -- same convention capsule_runcap.c/capsule_mint.c already use. */ * -- same convention capsule_runcap.c/capsule_mint.c already use. */
static int read_devblock(struct blkio_dev *dev, uint32_t devblock, uint8_t *buf4096) { 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). */ * actually owns block-subsystem state). */
g_wirebind_attached_vm_id = user_id; g_wirebind_attached_vm_id = user_id;
g_wirebind_attached_valid = 1; g_wirebind_attached_valid = 1;
g_wirebind_attached_dev = dev;
memcpy(g_wirebind_attached_username, username, sizeof(g_wirebind_attached_username)); memcpy(g_wirebind_attached_username, username, sizeof(g_wirebind_attached_username));
/* Register the pairing in the console's own routing table, index 3 /* 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); capsule_vm_kill(entry.name);
g_wirebind_attached_valid = 0; g_wirebind_attached_valid = 0;
g_wirebind_attached_dev = (struct blkio_dev *) 0;
g_wirebind_attached_username[0] = '\0'; g_wirebind_attached_username[0] = '\0';
console_puts("EJECT: "); console_puts("EJECT: ");
@@ -254,6 +272,7 @@ void capsule_wirebind_unclean_detach(void) {
capsule_vm_kill(entry.name); capsule_vm_kill(entry.name);
g_wirebind_attached_valid = 0; g_wirebind_attached_valid = 0;
g_wirebind_attached_dev = (struct blkio_dev *) 0;
g_wirebind_attached_username[0] = '\0'; g_wirebind_attached_username[0] = '\0';
console_puts("WIREBIND: "); console_puts("WIREBIND: ");
@@ -265,3 +284,41 @@ const char *capsule_wirebind_attached_username(void) {
if (!g_wirebind_attached_valid) return (const char *)0; if (!g_wirebind_attached_valid) return (const char *)0;
return g_wirebind_attached_username; 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");
}
+7
View File
@@ -368,6 +368,13 @@ static void sk_repl_idle(VM *active_vm)
* migration, needs a call site threaded from WIREBIND). */ * migration, needs a call site threaded from WIREBIND). */
blk_migration_idle_check(); 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 /* 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 * 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 * MSG-TICK word (see capsules/common/messaging.4th) instead of only