feat(log): LOG-FLUSH -- a VM's log records reach Artemis's log ring; and the ring keeps its order when it grows
FABRIC-3 SXXXII.4's deferred LOG-FLUSH, built to the 2026-10-14 rulings (SXXXVIII): everything a VM logs, at or above the log level, goes into its own buffer; it goes to Artemis on LOG-FLUSH, when full, and at once on an ERROR, as one kernel-Hermes LOG_BATCH message she writes on her own turn, the source being the sender the kernel stamped. Records that cannot go yet are counted and reported with the next flush. LOG-SHOW (Hera only) reads the ring back -- it had no reader. Found by the first test that filled it: ring growth overwrote the oldest record and put blank slots inside the live range. Fixed test first. Three ISAs: POST 1052 passed; new parity baseline word_count=526 hash=0x82f4ae7cb2e2d424 (deliberate). CLAUDE.md: Phase 8 PKI closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5e26fad30d
commit
4da370f492
+8
-5
@@ -149,8 +149,11 @@ The word-level ACL system is fully implemented, including kernel parity. Key con
|
||||
confirmed **absent** from `v3/src/vm.c`. (The `feature/acl-rwt` branch this work was
|
||||
apparently done on is not traceable in `git log --all` — likely squash-merged without
|
||||
preserving the ref. The code is real and verified; the branch name is not.)
|
||||
8. ⬜ PKI / thumbdrive — Ed25519 challenge-response; user minting by zuse. **This is the open
|
||||
item — pick up here next.**
|
||||
8. ✅ PKI / thumbdrive — closed 2026-10-14. User minting by Zuse is built (`MINT`, `MINT-SCRATCH`,
|
||||
WIREBIND cert verification). Challenge-response against a cloned drive was ruled out by design
|
||||
("there's only the drive", `FABRIC-3.7.md`). Runtime word-ACL elevation (`ELEVATE-GRANT`, the
|
||||
Zuse eligibility list) was ruled not needed and removed, with the hole it carried
|
||||
(`FABRIC-3.md` §XXXVII).
|
||||
|
||||
**ACL-RWT DoE campaign (June 15–16 2026) — superseded, kept for historical record:**
|
||||
- 3×3 Latin square: 3 seeds × amd64/aarch64/riscv64, 30 reps each
|
||||
@@ -199,8 +202,8 @@ removed as an active target.)
|
||||
**Current status: M7.1** — capsule birth protocol, Mama FORTH vocabulary, Tripod multi-VM
|
||||
fleet (Hera/Artemis/Hestia — Hermes moved into the kernel as kernel-Hermes, FABRIC-3.6.md
|
||||
Phase 4, 2026-09-22), and word-level ACL (Phases 1–7) are live; POST at boot verifies parity
|
||||
hash across amd64/aarch64/riscv64 with a 530-word Mama capsule dictionary (word count as of
|
||||
the Phase 4 strip; verify against a live `PARITY:M7.1a word_count=` line before citing, it
|
||||
hash across amd64/aarch64/riscv64 with a 526-word Mama capsule dictionary (word count as of
|
||||
2026-10-14, `FABRIC-3.md` §XXXVIII, hash `0x82f4ae7cb2e2d424`; verify against a live `PARITY:M7.1a word_count=` line before citing, it
|
||||
changes with any new C word registration).
|
||||
|
||||
**Key distinguishing features:**
|
||||
@@ -433,7 +436,7 @@ UEFI Firmware → uefi_loader.c (BOOTX64.EFI / BOOTAA64.EFI / BOOTRISCV64.EFI)
|
||||
- ✅ M0–M6: complete, v1.5.1-FINAL
|
||||
- ✅ M7: StarForth VM integration + parity validation, complete
|
||||
- 🔄 M7.1: capsule birth protocol · Mama FORTH vocabulary · Tripod fleet · word-level ACL —
|
||||
in progress (Phase 8 PKI is the open item, see ACL section above)
|
||||
in progress (Phase 8 PKI closed 2026-10-14, see ACL section above)
|
||||
- Planned: M8 (REPL keyboard input), M9 (block storage — see the virtio note above; source
|
||||
exists but milestone-complete status not independently verified here)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-10-14T15:35:50Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-10-14T16:07:07Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
|
||||
@@ -512,8 +512,9 @@ Block 4856
|
||||
Block 4860
|
||||
( LOG-APPEND ( level ts msg-addr msg-u -- ) )
|
||||
( FABRIC-3.md SXXVI follow-on Step 4, 2026-09-13: real per-VM )
|
||||
( log persistence, reached via a sender's LOG-FLUSH/MSG-SEND )
|
||||
( round trip, or by hand at the console for a smoke test. )
|
||||
( log persistence, by hand at the console for a smoke test. )
|
||||
( LOG-FLUSH does not come through here: since 2026-10-14 a )
|
||||
( VM's records reach the ring in C, vm_log_buffer.c. )
|
||||
( Thin wrapper -- kernel-only (LOG-APPEND-RAW) (log_words.c/ )
|
||||
( log_region.c) does the real disk write; never calls )
|
||||
( log_message() (see log_region.c's own doc comment on why). )
|
||||
|
||||
+44
-1
@@ -4602,7 +4602,7 @@ call sites (`repl.c` has 18); most are direct interactive replies already correc
|
||||
this rule -- reclassifying the specific sites that aren't is Stage D's job (the same files Stage
|
||||
C's triage already walks), not repeated here.
|
||||
|
||||
**Decision on the two §XXVII gaps: fix eviction now, defer `LOG-FLUSH` again.**
|
||||
**Decision on the two §XXVII gaps: fix eviction now, defer `LOG-FLUSH` again.** *`LOG-FLUSH` built 2026-10-14 -- §XXXVIII.*
|
||||
`LOG-FLUSH` needs real new machinery first -- the per-VM log buffer its own doc comment already
|
||||
presumes (`vm_log_buffer.h`) does not exist anywhere in the tree, confirmed by grep; building it
|
||||
means designing that buffer format and the flush word together, genuine feature work deserving
|
||||
@@ -5633,3 +5633,46 @@ kernel's freestanding `snprintf` does not do.
|
||||
|
||||
**Found, not fixed (pre-existing, unrelated):** Hera prints two and Hestia three `( Unterminated
|
||||
comment` warnings at every boot, in logs from before this work too.
|
||||
|
||||
## XXXVIII. `LOG-FLUSH` built: a VM's log records reach Artemis's log ring (2026-10-14)
|
||||
|
||||
Deferred in §XXXII.4 as its own initiative; Captain Bob, 2026-10-14: build it before the
|
||||
bare-metal builds. His rulings: **everything the VM logs** goes in its buffer (at or above the
|
||||
log level -- the one, global `LOG-LEVEL!` setting, the same filter `log_message()` applies);
|
||||
it goes to disk **on `LOG-FLUSH`, when the buffer fills, and at once on an ERROR record**.
|
||||
|
||||
**Built.**
|
||||
- Each VM has a buffer of its own (`VM.log_buf`, 900 bytes, packed records: level, timestamp,
|
||||
length, text; `vm_log_buffer.c`/`.h`). `log_message()` (`shim.c`) adds every record it prints
|
||||
to the buffer of the VM it is attributed to (`vm_log_attributed_vm()`); a record made outside
|
||||
any VM's text -- the kernel's own -- belongs to none and is not kept.
|
||||
- A flush is one kernel-Hermes message to Artemis, type `SK_HERMES_MSG_TYPE_LOG_BATCH` (10), the
|
||||
one message type that is not FORTH text: `sk_hermes_drain_checkpoint()` hands it to
|
||||
`vm_log_batch_deliver()`, which writes each record with `log_region_append()` -- in Artemis's
|
||||
own context, on her turn (§XXXVI). The record's source is the sender the kernel stamped on the
|
||||
message, never a name the sender gives: a VM cannot write under another's name.
|
||||
- Nothing is lost silently: records that cannot go (Artemis not yet live -- all of Hera's POST --
|
||||
or the send refused) are counted, and the next flush that can be sent leads with "N log
|
||||
records not sent: Artemis not ready" (175 at a normal boot).
|
||||
- `LOG-FLUSH ( -- )`, every VM. `LOG-SHOW ( n -- )`, Hera only: the newest n records, oldest of
|
||||
them first, as source, level, text -- added because the ring had no reader at all, so nothing
|
||||
written there could be read or checked (`log_region_read_recent()`). Hera only because it shows
|
||||
every VM's records, and Hera's console is Zuse's.
|
||||
|
||||
**A real defect in the ring, found by the first test that filled it:** growth
|
||||
(`log_region_append()`) claimed to be "safe regardless of wrap state" and was not. The ring is
|
||||
full when it grows, so head == tail; nothing moved and tail stayed put, so the next write
|
||||
overwrote the oldest record and the new, blank slots read back as records inside the live
|
||||
range. 30 records written to a fresh ring read back as 28 of them and 12 blank
|
||||
(`logs/20261014-115648`; also `-115434` on a ring already damaged by an earlier test). Fixed:
|
||||
the records from head on move up by the slots added, and the next write goes into the gap (with
|
||||
head at 0 -- the only case growth meets, since nothing is evicted before the ring is at its full
|
||||
size -- simply at the old end). §XXXII.4 had flagged eviction as never live-exercised.
|
||||
|
||||
**Verified.** Test first: `LOG-FLUSH` and `LOG-SHOW` unknown (`logs/20261014-114028`). Then:
|
||||
Hera's WARN probe and her boot warnings, under source `Hera`, after `LOG-FLUSH`; identity `00`'s
|
||||
ERROR probe, sent by itself at once, under source `00` (`-114847`); the lost-records notice
|
||||
(`-115434`); 30 records through a growth, in order, no blank, on amd64 (`-115841`), aarch64
|
||||
(`-120526`) and riscv64 (`-120746`). Standard acceptance, all three ISAs: `zuse@Hera`, POST 1052
|
||||
passed, no unknown word, **new baseline `word_count=526 ... hash=0x82f4ae7cb2e2d424`**
|
||||
(deliberate: Hera's new words) (`logs/20261014-120027`, `-120148`, `-120356`).
|
||||
|
||||
+2
-1
@@ -592,7 +592,8 @@ LOADER_EXTRA_SRCS := \
|
||||
$(KERNEL_SRC)/vm/stadium_words.c \
|
||||
$(KERNEL_SRC)/vm/stadium_blocks.c \
|
||||
$(KERNEL_SRC)/vm/session.c \
|
||||
$(KERNEL_SRC)/vm/kernel_hermes.c
|
||||
$(KERNEL_SRC)/vm/kernel_hermes.c \
|
||||
$(KERNEL_SRC)/vm/vm_log_buffer.c
|
||||
|
||||
KERNEL_EXTRA_SRCS := $(LOADER_EXTRA_SRCS)
|
||||
|
||||
|
||||
@@ -182,6 +182,16 @@ int log_region_append(uint8_t level, uint64_t timestamp,
|
||||
const char *source, uint32_t source_len,
|
||||
const char *msg, uint32_t msg_len);
|
||||
|
||||
/**
|
||||
* log_region_read_recent - read one record back (2026-10-14, for LOG-SHOW).
|
||||
*
|
||||
* @param back 0 for the newest record, 1 for the one before it, ...
|
||||
* @param out Filled with the record.
|
||||
* @return 0 on success, 1 if there is no such record (the ring holds fewer
|
||||
* than back+1, or none at all), -1 on a read failure or no ring.
|
||||
*/
|
||||
int log_region_read_recent(uint32_t back, log_slot_t *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -728,6 +728,12 @@ int sk_hermes_reassemble(SkHermesMessage **chunks, int n_chunks,
|
||||
* new one invented alongside it. */
|
||||
#define SK_HERMES_MSG_TYPE_BLK_ATTACH 9
|
||||
|
||||
/* SK_HERMES_MSG_TYPE_LOG_BATCH (2026-10-14, FABRIC-3.md SXXXVIII): a VM's
|
||||
* log records, binary (vm_log_buffer.h), for Artemis's log ring. The one
|
||||
* type that is not FORTH text: sk_hermes_drain_checkpoint() hands it to
|
||||
* vm_log_batch_deliver() instead of interpreting it. */
|
||||
#define SK_HERMES_MSG_TYPE_LOG_BATCH 10
|
||||
|
||||
/* SK_HERMES_MSG_TYPE_CONSOLE_CMD (FABRIC-3.6.md task 3.9, Stage D) --
|
||||
* same reasoning as SK_HERMES_MSG_TYPE_BLK_ATTACH above: deliberately
|
||||
* reuses messaging.4th's own CONSOLE-CMD-EVENT value (7), since this is
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2026 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* vm_log_buffer.h - a VM's log records, on their way to Artemis's log ring
|
||||
* (FABRIC-3.md §XXXVIII, 2026-10-14; Captain Bob's rulings that day).
|
||||
*
|
||||
* Every record log_message() makes while a VM is the one attributed
|
||||
* (vm_log_attributed_vm()) is packed into that VM's own buffer (VM.log_buf).
|
||||
* The buffer goes to Artemis as one kernel-Hermes message (type
|
||||
* SK_HERMES_MSG_TYPE_LOG_BATCH) on LOG-FLUSH, when the next record would not
|
||||
* fit, and at once after an ERROR record. Artemis takes it on her own turn
|
||||
* (the one delivery path, §XXXVI) and writes each record to her log ring
|
||||
* (log_region.c), its source the VM the kernel stamped as sender -- never a
|
||||
* name the sender supplies. Records that cannot be sent yet (Artemis not
|
||||
* live, the send refused) are counted, never silently lost: the next flush
|
||||
* that can be sent carries "N log records not sent" first.
|
||||
*
|
||||
* Record layout in a buffer and a batch: level (1 byte), timestamp (8,
|
||||
* shim.c's KRELTSC base), length (2), then the text.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VM_LOG_BUFFER_H
|
||||
#define STARKERNEL_VM_LOG_BUFFER_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/vm_uuid.h"
|
||||
|
||||
struct VM;
|
||||
|
||||
/* Adds one record to vm's buffer (flushing first if it would not fit, and
|
||||
* after it if it is an ERROR). Never logs; a call made while the buffer is
|
||||
* already being worked on (a log line from inside a flush) is dropped and
|
||||
* counted. */
|
||||
void vm_log_buffer_add(struct VM *vm, uint8_t level, uint64_t timestamp,
|
||||
const char *msg, uint32_t len);
|
||||
|
||||
/* Sends vm's buffer to Artemis. Returns the number of records sent, 0 when
|
||||
* there was nothing to send, -1 when it could not be sent (the records are
|
||||
* then counted in vm->log_lost). */
|
||||
int vm_log_buffer_flush(struct VM *vm);
|
||||
|
||||
/* Artemis's side, called by sk_hermes_drain_checkpoint() for a
|
||||
* SK_HERMES_MSG_TYPE_LOG_BATCH message, in her own context: writes each
|
||||
* record to the log ring, its source the sender `from`. Returns the
|
||||
* number written; -1 if vm is not Artemis (the batch is then dropped). */
|
||||
int vm_log_batch_deliver(struct VM *vm, VMUuid from,
|
||||
const uint8_t *payload, uint32_t payload_len);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_VM_LOG_BUFFER_H */
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "starkernel/capsule_mint.h"
|
||||
#include "starkernel/user_identity_seed.h"
|
||||
#include "starkernel/vm_identity.h"
|
||||
#include "starkernel/log_region.h" /* LOG-SHOW */
|
||||
#include "starkernel/capsule_loader.h"
|
||||
#include "starkernel/capsule_run.h"
|
||||
#include "starkernel/capsule_loader.h"
|
||||
@@ -1379,6 +1380,49 @@ static void mama_word_name_to_xt(VM *vm)
|
||||
vm_push(vm, (cell_t)(uintptr_t)e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief LOG-SHOW ( n -- )
|
||||
* Prints the newest n records of Artemis's log ring, oldest of them first:
|
||||
* source, level, text (2026-10-14, FABRIC-3.md SXXXVIII). Hera's only --
|
||||
* it shows every VM's records, and Hera's console is Zuse's.
|
||||
*/
|
||||
static void mama_word_log_show(VM *vm)
|
||||
{
|
||||
static const char *names[] = { "ERROR", "WARN ", "INFO ", "TEST ", "DEBUG" };
|
||||
static log_slot_t slot; /* 1 KiB: static, not on the stack */
|
||||
static char text[LOG_SLOT_MSG_MAX + 1];
|
||||
cell_t n;
|
||||
int32_t back;
|
||||
|
||||
if (vm->dsp < 0) {
|
||||
console_println("LOG-SHOW: expects a count -- nothing on the stack");
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
n = vm_pop(vm);
|
||||
if (n > 128) n = 128;
|
||||
for (back = (int32_t)n - 1; back >= 0; back--) {
|
||||
int rc = log_region_read_recent((uint32_t)back, &slot);
|
||||
uint32_t len;
|
||||
char src[LOG_SLOT_SOURCE_MAX + 1];
|
||||
if (rc < 0) {
|
||||
console_println("LOG-SHOW: no log ring -- Artemis's disk not attached");
|
||||
return;
|
||||
}
|
||||
if (rc > 0) continue; /* fewer records than asked for */
|
||||
len = slot.msg_len < LOG_SLOT_MSG_MAX ? slot.msg_len : LOG_SLOT_MSG_MAX;
|
||||
memcpy(text, slot.msg, len);
|
||||
text[len] = '\0';
|
||||
memcpy(src, slot.source, LOG_SLOT_SOURCE_MAX);
|
||||
src[LOG_SLOT_SOURCE_MAX] = '\0';
|
||||
console_puts(src);
|
||||
console_puts(" ");
|
||||
console_puts(names[slot.level <= 4 ? slot.level : 0]);
|
||||
console_puts(" ");
|
||||
console_println(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RUNCAP-TEST ( caddr u -- ok? rc )
|
||||
* Diagnostic-only word (FABRIC-2.md §F.6/§F.18): calls
|
||||
@@ -2275,6 +2319,7 @@ void register_mama_forth_words(VM *vm)
|
||||
register_word(vm, "WORKER-BIRTH", mama_word_worker_birth);
|
||||
register_word(vm, "UNATTENDED-BIRTH", mama_word_unattended_birth);
|
||||
register_word(vm, "CONSOLE-ATTACH", mama_word_console_attach);
|
||||
register_word(vm, "LOG-SHOW", mama_word_log_show);
|
||||
register_word(vm, "NAME>XT", mama_word_name_to_xt);
|
||||
register_word(vm, "RUNCAP-TEST", mama_word_runcap_test);
|
||||
register_word(vm, "PAIR-TEST", mama_word_pair_test);
|
||||
@@ -2337,6 +2382,7 @@ void register_mama_forth_words(VM *vm)
|
||||
register_word(vm, "WORKER-BIRTH", mama_word_worker_birth);
|
||||
register_word(vm, "UNATTENDED-BIRTH", mama_word_unattended_birth);
|
||||
register_word(vm, "CONSOLE-ATTACH", mama_word_console_attach);
|
||||
register_word(vm, "LOG-SHOW", mama_word_log_show);
|
||||
register_word(vm, "NAME>XT", mama_word_name_to_xt);
|
||||
register_word(vm, "RUNCAP-TEST", mama_word_runcap_test);
|
||||
register_word(vm, "PAIR-TEST", mama_word_pair_test);
|
||||
|
||||
+35
-5
@@ -116,14 +116,32 @@ int log_region_append(uint8_t level, uint64_t timestamp,
|
||||
uint32_t total_slots = ctrl.devblocks * LOG_SLOTS_PER_DEVBLOCK;
|
||||
if (ctrl.record_count >= total_slots) {
|
||||
if (ctrl.devblocks < LOG_REGION_MAX_DEVBLOCKS) {
|
||||
/* Grow: new slots appear at indices [old_total_slots ..
|
||||
* new_total_slots-1], nothing already written moves -- safe
|
||||
* to do at any point in the ring's lifecycle regardless of
|
||||
* wrap state, since head/tail are slot INDICES modulo
|
||||
* total_slots, and the modulus is simply larger from here on. */
|
||||
/* Grow: new slots appear at indices [old_total .. new_total-1].
|
||||
* The ring is full here, so head == tail, and in ring order the
|
||||
* records run head..old_total-1 then 0..head-1. The new slots
|
||||
* must come after the newest record and before the oldest, so
|
||||
* the records from head on move up by the slots added, and the
|
||||
* next write goes into the gap they leave (or, with head at 0,
|
||||
* simply at old_total). Until 2026-10-14 nothing moved and
|
||||
* tail stayed put: the next write overwrote the oldest record,
|
||||
* and the blank new slots read back as records inside the live
|
||||
* range (FABRIC-3.md SXXXVIII -- first exercised by LOG-FLUSH). */
|
||||
uint32_t old_total = total_slots, added, i;
|
||||
ctrl.devblocks += LOG_REGION_GROWTH_INCREMENT;
|
||||
if (ctrl.devblocks > LOG_REGION_MAX_DEVBLOCKS) ctrl.devblocks = LOG_REGION_MAX_DEVBLOCKS;
|
||||
total_slots = ctrl.devblocks * LOG_SLOTS_PER_DEVBLOCK;
|
||||
added = total_slots - old_total;
|
||||
if (ctrl.head_slot == 0) {
|
||||
ctrl.tail_slot = old_total;
|
||||
} else {
|
||||
for (i = old_total; i-- > ctrl.head_slot; ) {
|
||||
log_slot_t moving;
|
||||
if (log_region_read_slot(i, &moving) != 0) return -1;
|
||||
if (log_region_write_slot(i + added, &moving) != 0) return -1;
|
||||
}
|
||||
ctrl.tail_slot = ctrl.head_slot;
|
||||
ctrl.head_slot += added;
|
||||
}
|
||||
}
|
||||
if (ctrl.record_count >= total_slots) {
|
||||
/* Still full (growth ceiling already reached) -- evict oldest,
|
||||
@@ -175,3 +193,15 @@ int log_region_append(uint8_t level, uint64_t timestamp,
|
||||
|
||||
return log_region_ctrl_write(&ctrl);
|
||||
}
|
||||
|
||||
int log_region_read_recent(uint32_t back, log_slot_t *out) {
|
||||
log_region_ctrl_t ctrl;
|
||||
uint32_t total_slots;
|
||||
|
||||
if (!out) return -1;
|
||||
if (log_region_ctrl_read(&ctrl) != 0) return -1;
|
||||
if (back >= ctrl.record_count) return 1;
|
||||
total_slots = ctrl.devblocks * LOG_SLOTS_PER_DEVBLOCK;
|
||||
if (total_slots == 0) return 1;
|
||||
return log_region_read_slot((ctrl.tail_slot + total_slots - 1u - back) % total_slots, out);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@
|
||||
* message line length (renamed LOG_MSG_LINE_MAX, 256) no longer share a
|
||||
* name, so include order is irrelevant here. */
|
||||
#include "starkernel/repl.h"
|
||||
#include "starkernel/log_attrib.h" /* vm_log_attributed_vm() */
|
||||
#include "starkernel/vm_log_buffer.h" /* vm_log_buffer_add() */
|
||||
#include "starkernel/vm/bootstrap/sk_vm_bootstrap.h"
|
||||
#include "log.h"
|
||||
#include "vm_host.h"
|
||||
@@ -335,6 +337,14 @@ void log_message(LogLevel level, const char *fmt, ...) {
|
||||
console_puts(reset);
|
||||
console_puts(prefix);
|
||||
console_println(buf);
|
||||
|
||||
/* And into the log buffer of the VM it belongs to, for Artemis's log
|
||||
* ring (2026-10-14, FABRIC-3.md SXXXVIII). A record made outside any
|
||||
* VM's text -- the kernel's own -- belongs to none and is not kept. */
|
||||
{
|
||||
struct VM *owner = vm_log_attributed_vm();
|
||||
if (owner) vm_log_buffer_add(owner, (uint8_t)idx, rel_tsc, buf, (uint32_t)strlen(buf));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include "starkernel/vm/switch.h" /* one path: sk_vm_switch_current_vm() */
|
||||
#include "starkernel/log_attrib.h" /* one path: vm_log_attributed_vm() */
|
||||
#include "starkernel/capsule_vm_switch_signal.h" /* one path: sk_vm_switch_signal_mark_work() */
|
||||
#include "starkernel/vm_log_buffer.h" /* vm_log_batch_deliver() */
|
||||
|
||||
/* Freestanding: no libc printf. Prints an unsigned decimal, no leading
|
||||
* zeros -- same small helper stadium.c/capsule_vm_switch_signal.c each
|
||||
@@ -656,6 +657,16 @@ int sk_hermes_drain_checkpoint(VM *vm) {
|
||||
console_println(line);
|
||||
}
|
||||
|
||||
/* A log batch is records, not FORTH text: written to Artemis's log ring
|
||||
* here, in her own context (2026-10-14, FABRIC-3.md SXXXVIII). */
|
||||
if (msg->type == SK_HERMES_MSG_TYPE_LOG_BATCH) {
|
||||
sk_hermes_pending_pop(self);
|
||||
(void)vm_log_batch_deliver(vm, msg->from, (const uint8_t *)msg->payload_addr,
|
||||
msg->payload_len);
|
||||
sk_hermes_release(msg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Off the queue BEFORE it runs (2026-10-14): running it can reach a
|
||||
* colon-word checkpoint that drains this same VM again, and that
|
||||
* drain must find the next message, not this one -- popped only after,
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2026 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* vm_log_buffer.c - a VM's log records, on their way to Artemis's log ring.
|
||||
* See the header for the contract (FABRIC-3.md §XXXVIII, 2026-10-14).
|
||||
*/
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include "starkernel/vm_log_buffer.h"
|
||||
#include "vm.h"
|
||||
#include "log.h" /* LOG_ERROR, LOG_WARN */
|
||||
#include "starkernel/vm/kernel_hermes.h" /* sk_hermes_send_one(), SK_HERMES_MSG_TYPE_LOG_BATCH */
|
||||
#include "starkernel/capsule_birth.h" /* capsule_vm_find_by_name_nocase(), capsule_vm_registry_get() */
|
||||
#include "starkernel/log_region.h" /* log_region_append() */
|
||||
#include "starkernel/console.h" /* console_println() -- never log_message() here */
|
||||
#include "freestanding/stdio.h" /* snprintf() */
|
||||
#include <string.h>
|
||||
|
||||
#define REC_HEAD 11u /* level 1 + timestamp 8 + length 2 */
|
||||
|
||||
typedef char vm_log_buf_fits[(VM_LOG_BUF_SIZE + 64 <= SK_HERMES_CHUNK_MAX_PAYLOAD) ? 1 : -1];
|
||||
|
||||
/* Set while a record is being added or a buffer sent: a log line made from
|
||||
* inside that work (a kernel-Hermes refusal, say) must not re-enter it. */
|
||||
static int g_busy;
|
||||
|
||||
static uint32_t put_record(uint8_t *at, uint8_t level, uint64_t ts,
|
||||
const char *msg, uint16_t len) {
|
||||
at[0] = level;
|
||||
memcpy(at + 1, &ts, 8);
|
||||
memcpy(at + 9, &len, 2);
|
||||
memcpy(at + REC_HEAD, msg, len);
|
||||
return REC_HEAD + len;
|
||||
}
|
||||
|
||||
static uint32_t count_records(const uint8_t *buf, uint32_t len) {
|
||||
uint32_t at = 0, n = 0;
|
||||
while (at + REC_HEAD <= len) {
|
||||
uint16_t l;
|
||||
memcpy(&l, buf + at + 9, 2);
|
||||
at += REC_HEAD + l;
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static int send_buffer(VM *vm) {
|
||||
uint8_t payload[SK_HERMES_CHUNK_MAX_PAYLOAD];
|
||||
uint32_t len = 0, records = count_records(vm->log_buf, vm->log_buf_len);
|
||||
VMRegistryEntry artemis;
|
||||
|
||||
if (vm->log_buf_len == 0 && vm->log_lost == 0) return 0;
|
||||
|
||||
if (vm->log_lost > 0) {
|
||||
char note[64];
|
||||
int n = snprintf(note, sizeof(note), "%u log records not sent: Artemis not ready",
|
||||
(unsigned)vm->log_lost);
|
||||
if (n > 0) len += put_record(payload, LOG_WARN, 0, note, (uint16_t)n);
|
||||
}
|
||||
memcpy(payload + len, vm->log_buf, vm->log_buf_len);
|
||||
len += vm->log_buf_len;
|
||||
|
||||
if (capsule_vm_find_by_name_nocase("Artemis", &artemis) != 0 || !artemis.vm_ptr ||
|
||||
(artemis.state != VM_STATE_LIVE && artemis.state != VM_STATE_SWITCHED_OUT) ||
|
||||
sk_hermes_send_one(vm->stadium_vm_id, artemis.vm_id, SK_HERMES_MSG_TYPE_LOG_BATCH,
|
||||
0, payload, len) != 0) {
|
||||
vm->log_lost += records;
|
||||
vm->log_buf_len = 0;
|
||||
return -1;
|
||||
}
|
||||
vm->log_lost = 0;
|
||||
vm->log_buf_len = 0;
|
||||
return (int)records;
|
||||
}
|
||||
|
||||
void vm_log_buffer_add(VM *vm, uint8_t level, uint64_t timestamp,
|
||||
const char *msg, uint32_t len) {
|
||||
if (!vm || !msg) return;
|
||||
if (g_busy) { vm->log_lost++; return; }
|
||||
g_busy = 1;
|
||||
|
||||
if (len > VM_LOG_BUF_SIZE - REC_HEAD) len = VM_LOG_BUF_SIZE - REC_HEAD;
|
||||
if (vm->log_buf_len + REC_HEAD + len > VM_LOG_BUF_SIZE) (void)send_buffer(vm);
|
||||
vm->log_buf_len = (uint16_t)(vm->log_buf_len +
|
||||
put_record(vm->log_buf + vm->log_buf_len, level, timestamp, msg, (uint16_t)len));
|
||||
if (level == LOG_ERROR) (void)send_buffer(vm);
|
||||
|
||||
g_busy = 0;
|
||||
}
|
||||
|
||||
int vm_log_buffer_flush(VM *vm) {
|
||||
int rc;
|
||||
if (!vm || g_busy) return -1;
|
||||
g_busy = 1;
|
||||
rc = send_buffer(vm);
|
||||
g_busy = 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int vm_log_batch_deliver(VM *vm, VMUuid from, const uint8_t *payload, uint32_t payload_len) {
|
||||
VMRegistryEntry artemis, sender;
|
||||
const char *source = "?";
|
||||
uint32_t at = 0;
|
||||
int written = 0, failed = 0;
|
||||
|
||||
if (capsule_vm_find_by_name_nocase("Artemis", &artemis) != 0 || artemis.vm_ptr != (void *)vm) {
|
||||
return -1;
|
||||
}
|
||||
/* The source is the sender the kernel stamped on the message: a VM
|
||||
* cannot write a record under another's name. */
|
||||
if (capsule_vm_registry_get(from, &sender) == 0) source = sender.name;
|
||||
|
||||
while (at + REC_HEAD <= payload_len) {
|
||||
uint8_t level = payload[at];
|
||||
uint64_t ts;
|
||||
uint16_t len;
|
||||
memcpy(&ts, payload + at + 1, 8);
|
||||
memcpy(&len, payload + at + 9, 2);
|
||||
if (at + REC_HEAD + len > payload_len) break; /* malformed tail: stop */
|
||||
if (log_region_append(level, ts, source, (uint32_t)strlen(source),
|
||||
(const char *)payload + at + REC_HEAD, len) < 0) {
|
||||
failed++;
|
||||
} else {
|
||||
written++;
|
||||
}
|
||||
at += REC_HEAD + len;
|
||||
}
|
||||
if (failed) console_println("Artemis: log ring write failed -- records not kept");
|
||||
return written;
|
||||
}
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
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
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
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
File diff suppressed because it is too large
Load Diff
@@ -677,6 +677,18 @@ typedef struct VM
|
||||
* not be entered again to deliver a message. */
|
||||
/** @} */
|
||||
|
||||
/** @name Log buffer (kernel only; FABRIC-3.md §XXXVIII, 2026-10-14)
|
||||
* Every log record attributed to this VM, packed, waiting to go to
|
||||
* Artemis's log ring in one kernel-Hermes message (vm_log_buffer.c).
|
||||
* @{
|
||||
*/
|
||||
#define VM_LOG_BUF_SIZE 900 /**< one message's payload, leaving room for a lost-records notice */
|
||||
uint8_t log_buf[VM_LOG_BUF_SIZE];
|
||||
uint16_t log_buf_len; /**< bytes used in log_buf */
|
||||
uint32_t log_lost; /**< records that could not be sent, reported
|
||||
* with the next flush that can be */
|
||||
/** @} */
|
||||
|
||||
/** @name Stadium Identity (item 4.2, FABRIC-0.md §25.5)
|
||||
* @{
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "starkernel/log_attrib.h" /* vm_log_attributed_vm() */
|
||||
#include "starkernel/capsule_birth.h" /* capsule_vm_registry_get, VMRegistryEntry */
|
||||
#include "starkernel/console.h" /* console_println() -- (LOG-APPEND-RAW) diagnostics */
|
||||
#include "starkernel/vm_log_buffer.h" /* vm_log_buffer_flush() -- LOG-FLUSH */
|
||||
#include <string.h> /* strlen */
|
||||
#endif
|
||||
|
||||
@@ -294,6 +295,20 @@ static void log_word_append_raw(VM *vm)
|
||||
vm->error = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── LOG-FLUSH ( -- ) ─────────────────────────────────────────────────── */
|
||||
/*
|
||||
* Sends this VM's log buffer to Artemis's log ring now, rather than when it
|
||||
* fills or an ERROR record comes (vm_log_buffer.c; FABRIC-3.md SXXXVIII,
|
||||
* 2026-10-14). Records that cannot go yet are counted, and the count goes
|
||||
* with the next flush that can.
|
||||
*/
|
||||
static void log_word_flush(VM *vm)
|
||||
{
|
||||
if (vm_log_buffer_flush(vm) < 0) {
|
||||
console_println("LOG-FLUSH: Artemis not ready -- records counted, sent with the next flush");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ── Registration ────────────────────────────────────────────────────── */
|
||||
@@ -308,6 +323,7 @@ static void log_word_append_raw(VM *vm)
|
||||
* String literals: LOG-ERROR" LOG-WARN" LOG-INFO" LOG-TEST" LOG-DEBUG"
|
||||
* Stack string: LOG-ERROR-STR LOG-WARN-STR LOG-INFO-STR
|
||||
* LOG-TEST-STR LOG-DEBUG-STR
|
||||
* Kernel only: (LOG-APPEND-RAW) LOG-FLUSH
|
||||
*/
|
||||
void register_log_words(VM *vm)
|
||||
{
|
||||
@@ -344,5 +360,6 @@ void register_log_words(VM *vm)
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
register_word(vm, "(LOG-APPEND-RAW)", log_word_append_raw);
|
||||
register_word(vm, "LOG-FLUSH", log_word_flush);
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user