feat(v4): no identity drive, headless; Zuse's drive, a console

The identity gate on the v4 boot (plan 2026-10-26-engine-step4-identity-gate,
task G2; ENGINE.md 2, ruled 2026-10-20). As v3, by the kernel with no v3 VM:
after POST and the block chain, Zuse's public key from the genesis marker,
every xHCI controller brought up, the heartbeat started; then headless until
her drive is attached and checked (capsule_zuse_boot_check, G1). A drive that
arrives is given to the block subsystem by the kernel (Artemis is the
kernel's); her session is kept in the kernel for Hera, never in a node's
memory. The prompt is v3's, [zuse@Hera] ok>; pulling her drive ends the
session and the system goes headless; putting it back brings the prompt.
A blank pair stays headless: genesis writes Artemis's disk, Phase 5 (D2).

kernel/Makefile: ARTDISK and ZUSEDISK default to the minted pair,
disk/zuse-scratch/ (D4); .claude/CLAUDE.md's acceptance says so.

Fixed with it, at Captain Bob's word:
- kernel/Makefile: the ROM-and-map rule is a grouped target (&:). As two
  targets of one plain rule its recipe ran twice at once under -j and the
  copies met ("cp: cannot create regular file ... File exists"): 3 of 20
  parallel builds failed, now 0 of 20.
- The v4 boot drops the logger to --log-level after POST, as v3 does
  (sk_log_level_after_post, shared by both paths); INFO lines after POST,
  3 before, 0 now.

Accepted: three clean qemu boots as built to [zuse@Hera] ok>, POST 538/538,
dict_hash 0xfec8c47cfc6f437f on all three (unchanged), 0 INFO after POST;
the hot-plug session (attach, line, detach, headless, re-attach) on amd64,
aarch64 and riscv64; headless with no drive; make -C v4 test test-32
sanitize hosted-check EXIT 0; make -C v4 test-32 0 failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-26 11:06:37 -04:00
co-authored by Claude Opus 5.5
parent 75785fb025
commit 8d9c91b2bc
6 changed files with 309 additions and 31 deletions
+37 -12
View File
@@ -10,7 +10,10 @@
* - what the node prints goes to the kernel's console;
* - the prompt, and the line: it is read and edited here and handed to the
* node whole (docs/v4.0.0/ENGINE.md 3.1), and how it ended is said here;
* - blocks are kept in memory, and are gone at power-off.
* - blocks are kept in memory, and are gone at power-off;
* - the identity gate: with no identity drive the system is headless,
* and a console exists only while Zuse's drive is attached and checked
* (sk_v4_identity.c; ENGINE.md 2, ruled 2026-10-20).
*
* This is the lone node, started before the kernel's fleet and beside it.
* It goes when the node is brought up in the VM's place (ENGINE.md step 4).
@@ -18,13 +21,19 @@
* Nothing of the v3 VM is started. docs/v4.0.0/DECOMPOSITION.md.
*/
#include "starkernel/v4/sk_v4.h"
#include "starkernel/v4/sk_v4_identity.h"
#include "starkernel/console.h"
#include "starkernel/arch.h"
#include "starkernel/timer.h"
#include "v4/boot.h"
#include "block_subsystem.h"
#define SK_V4_LINE 255u /* what the line editor here takes */
/* v3's prompt (repl.c's SK_PROMPT_TEXT): "ok> " in bright cyan, after the
* console's own "[zuse@Hera] " line prefix. */
#define SK_V4_PROMPT "\x1b[38;2;85;255;255mok> \x1b[39m"
static v4_node sk_v4_node;
static v4_exec_state sk_v4_es;
static v4_heat sk_v4_heat;
@@ -67,17 +76,28 @@ static void sk_v4_serve(v4_node *node, unsigned request)
}
/* Read a line from the keyboard: characters are echoed, backspace rubs one
* out, Enter ends it. Returns its length. */
static unsigned sk_v4_readline(void)
* out, Enter ends it. Returns its length; or -1 when, with nothing typed,
* Zuse's session has ended (her drive was pulled): the caller goes headless.
* While nothing is typed the idle beat runs, as v3's sk_console_readline(). */
static int sk_v4_readline(void)
{
unsigned len = 0;
int len = 0;
for (;;) {
int c = console_getc();
if (c < 0) continue;
if (c < 0) {
if (len == 0) {
sk_v4_identity_beat();
if (!sk_v4_identity_present()) return -1;
} else {
heartbeat_service();
}
arch_relax();
continue;
}
if (c == '\r' || c == '\n') { console_putc('\n'); return len; }
if (c == 8 || c == 127) {
if (len > 0) { len--; console_putc(8); console_putc(' '); console_putc(8); }
} else if (c >= 32 && c < 127 && len < SK_V4_LINE) {
} else if (c >= 32 && c < 127 && len < (int)SK_V4_LINE) {
sk_v4_text[len++] = (char)c;
console_putc((char)c);
}
@@ -107,24 +127,29 @@ void sk_v4_run(void (*chain)(void))
for (;;) { }
}
}
sk_v4_identity_boot();
/* The prompt is the host's: read a line, hand it to the node, say how it
* ended. */
for (;;) {
unsigned len;
int len;
int how;
console_puts("ok> ");
/* no identity, no console: headless until Zuse's drive is checked */
if (!sk_v4_identity_present()) sk_v4_identity_wait();
console_puts(SK_V4_PROMPT);
len = sk_v4_readline();
how = v4_boot_line(&boot, sk_v4_text, len);
if (len < 0) continue;
how = v4_boot_line(&boot, sk_v4_text, (unsigned)len);
/* The line is waiting for an answer no one here can send. The next
* line typed breaks the wait: the line that was waiting ends in an
* error, and then the line just typed is done (v4/boot.h). */
while (how == V4_BOOT_LINE_WAITING) {
len = sk_v4_readline();
how = v4_boot_line(&boot, sk_v4_text, len);
if (how == V4_TEXT_ERROR) console_puts(" ERROR\nok> ");
else if (how == V4_TEXT_COMPLETED) console_puts(" ok\nok> ");
if (len < 0) { sk_v4_identity_wait(); continue; }
how = v4_boot_line(&boot, sk_v4_text, (unsigned)len);
if (how == V4_TEXT_ERROR) console_puts(" ERROR\n" SK_V4_PROMPT);
else if (how == V4_TEXT_COMPLETED) console_puts(" ok\n" SK_V4_PROMPT);
else break;
how = v4_boot_line(&boot, 0, 0);
}