Stage S-1a: parity hashing covers the whole dictionary; failures are logged, counted and marked INCOMPLETE (FABRIC-3.9.0 §8.1)

- MAX_DICT_ENTRIES (2048) removed: the entry list is allocated to the
  dictionary's size; a cyclic chain is detected (Floyd) instead of capped.
- Colon-body walk bounded by vm->here, not a 1024-cell cap.
- Every failure prints PARITY:INCOMPLETE #n with its full reason, logs at
  LOG_ERROR, bumps sk_parity_incomplete_count(), and the record reads
  hash=INCOMPLETE / PARITY:FAIL code=4 rather than a plausible hash.
- Pre-existing #GP fixed: the walker dereferenced inline body data (S"
  bytes, offsets); it now follows only known dictionary entries.
- LIT operands that are entry pointers are hashed by word_id, not address,
  so a mature dictionary hashes identically across ISAs.

Probe (reverted): 563 -> 2563 words hashed, identical on amd64/aarch64/
riscv64; cyclic chain -> INCOMPLETE, counter 2. Boot parity unchanged
(531, 0x572daac83b797332). 3-ISA acceptance passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-24 12:51:34 -04:00
co-authored by Claude Opus 5.5
parent 9bf2e8ec95
commit ee3db96ea8
35 changed files with 164134 additions and 37 deletions
+78
View File
@@ -145,3 +145,81 @@ Evidence is named **before** each stage runs.
- `dict_hash` unchanged (531, `0x572daac83b797332`);
- POST passes;
- clean kernel and hosted builds.
- [x] **Stage S-1a — parity hashing covers the whole dictionary (`FABRIC-3.9.0.md` §8.1, class C).**
2026-09-24. Captain Bob: *"all as proposed, commit and start S-1."*
**The change** (`src/starkernel/vm/parity.c`, `include/starkernel/vm/parity.h`):
- **`MAX_DICT_ENTRIES` (2048) removed.** The walk covers the whole link chain. The entry list
is allocated to the dictionary's actual size (`kmalloc` in the kernel, `malloc` hosted) and
freed after.
- **Cycle check replaces the cap.** The only thing the cap guarded against, a corrupt cyclic
chain, is now detected directly (Floyd: a second pointer moving two links per step).
- **The colon-body walk is bounded by `vm->here`,** not a 1024-cell cap. A body that reaches
`HERE` without `EXIT` or a NULL cell is marked incomplete, not quietly truncated.
- **Failures follow §8.1's rules.** Every failure (cyclic chain, entry list not allocated,
malformed body) is:
- printed in full to the console, `PARITY:INCOMPLETE #n -- <why>`. `log_message`'s line
buffer is 64 bytes and cut every reason off mid-sentence.
- logged at `LOG_ERROR` for the persisted record;
- counted (`sk_parity_incomplete_count()`).
- **An incomplete hash is marked as such.** The record prints `hash=INCOMPLETE` (new
`ParityPacket.hash_complete`) and `PARITY:FAIL code=4`
(`SK_BOOTSTRAP_PARITY_INCOMPLETE`), never a value that looks valid.
- `sk_dict_word_count` returns 0 on a cyclic chain, logged and counted.
**Two pre-existing defects found by the probe, both fixed in the walker:**
1. **The colon-body walker dereferenced inline data.** It treated every cell of a body as a
`DictEntry*` and followed it. Only `LIT`'s operand was known to be data. `S"` string
bytes, branch offsets and other inline operands were followed too.
- This was harmless while parity only ever ran at early boot, before any colon bodies
existed.
- The first hash of a mature dictionary took a #GP (vector 13) and halted the kernel. This
is the "interrupt exception" Captain Bob reported (`logs/20260924-094537/amd64/`,
`-095154/amd64/`).
- The **original** code faults the same way (`logs/20260924-095730/amd64/`), so the
defect predates S-1a.
- **Fix:** a cell is dereferenced only if it is a known dictionary entry, found by binary
search in a sorted copy of the entry list. Any other cell is hashed as its raw value and
never followed.
2. **A `LIT` operand holding an entry pointer was hashed by address.** `['] WORD` compiles
`LIT <DictEntry*>`, a native address that differs per ISA (seen in `ACL-BOOT`'s
`ACL-PIN` sequences). So the mature-dictionary hash differed across ISAs:
- amd64 `0x945a76bc6ace1fdc`, aarch64 `0x8cc1255e6a40c12e` (`-100252`, `-100732`).
- **Fix:** rule 2 (word_id, never address) now applies to such an operand. The walker
hashes a tag byte (`0xE7`) plus the entry's `word_id`, so it can't collide with a plain
number. Any other literal is still hashed as its value.
A third defect, an incomplete counter one too high on aarch64 only (`-101803`, `-102504`,
`-103207`, `-103820`), was not in parity. It was the out-of-bounds stack write fixed in its
own commit (the entry above).
**Evidence.** A probe (reverted) at the end of boot:
1. hashed Hera's full dictionary;
2. registered 2,000 more words (`PZ0000`…) and hashed again;
3. made the chain cyclic and asked for both the word count and the hash.
The results are byte-identical on all three ISAs (`logs/20260924-115524/amd64/`,
`-115954/aarch64/`, `-120533/riscv64/`):
| Probe step | Result |
|---|---|
| before | `word_count=563 hash=0x224007f9af328111`, `PARITY:OK` |
| after +2000 | `word_count=2563 hash=0xcf85876337e75a84`, `PARITY:OK`. Past the old 2048 cap: the whole dictionary is hashed. |
| cyclic | `PARITY:INCOMPLETE #1`/`#2 -- dictionary link chain is cyclic (corrupt)`, two `[HADES][ERROR]` lines, `word_count=0 hash=INCOMPLETE`, `PARITY:FAIL code=4` |
| counter | `incomplete_count=2` (word count + hash) |
The "after" record shows `latest_id=4294967295`. That is `WORD_ID_INVALID`: the 2,000
probe words ran past the 1024 stable word ids, and each logged "out of stable IDs, disabling
metrics". That is S-1b's defect, not S-1a's. The §8.1 row now notes that this path does log
a warning, but neither counts nor refuses.
**Acceptance** on the clean tree, with the probe reverted (`logs/20260924-121207/amd64/`,
`-122500/aarch64/`, `-123759/riscv64/`):
- Boot M7.1a is unchanged: `word_count=531 hash=0x572daac83b797332`. At boot there are no
colon bodies or entry-pointer literals yet, so neither fix changes the boot hash.
- The `dict_hash` set is unchanged: Hera `0xda9742ce9c3376a2`, Hestia `0x413e7d086f342d8b`,
Artemis `0xdfd68316d5f5ba85`.
- The O-A checks pass: G interleave A = 40/B = 40/48 runs, `HHH` precedence, and a typed
`1 2 + .` gives `3` mid-stream.
- Mint → WIREBIND → `USE` → `1 2 + .` → `3`. Idle log growth 0. Zero `UNKNOWN WORD` or
faults. QEMU exits cleanly.