chore(wolf): sync anatomy/cerebrum/memory/hooks/token-ledger bookkeeping

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
This commit is contained in:
Rongsong Shen 2026-08-25 12:29:23 +00:00
parent 977607615f
commit 1b86e04078
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
5 changed files with 1243 additions and 39 deletions

View file

@ -121,6 +121,9 @@
- **(2026-08-25, bug-1689) The bug-1687 apply-seam pattern (split a shared-payload WAL fact type out of `apply_wal_entry()`'s shared `case` group into its own case, extend `apply_committed_entry()`'s existing inline `else if (wal_type == X)` branch, add a `restore_job_<type>_state()` sibling in job.c, add a `persist_submit_job_<type>_fact()` sibling in persist.c/persist.h) generalizes cleanly to a THIRD job-fact type (WAL_JOB_FINISH) with one twist: not every such fact needs Cap'n Proto.** `job_exitinfo_t` (`xce/types.h`) is a single `{int exitcode;}` with no existing capnp mapping (confirmed via grep — no `JobExitInfo` in `controller.capnp`), so the trailing payload was a small NATIVE (raw memcpy) fixed-size trailer instead of a capnp-encoded blob — no malloc needed either, since the whole payload is fixed-size (unlike WAL_JOB_START's variable-length JobPIDInfo or WAL_JOB_DISPATCH's JobAlloc). General rule going forward: before reaching for capnp on a new WAL trailing payload, check whether the struct being carried already has a capnp mapping AND has any variable-length/nested-pointer fields — if it's a small fixed POD with neither, a raw `memcpy`'d native trailer (mirroring `wal_jobstate_t`'s own style) is simpler and correct, matching this codebase's own "small native trailing struct" precedent for `wal_jobstate_t` itself. Also discovered mid-fix: `apply_job_finish_transition()` (the "richer" core that DOES set exit_info/finish_time/run_time correctly) is effectively DEAD CODE on the real production apply path today — raft-apply-decoupling's redesign routes all real WAL_JOB_FINISH mutation through the leaner `apply_job_update_transition()` (state-only) instead, so `exit_info` was never populated through the live commit-apply pipeline at all, restart or not, prior to this fix (worse than "only breaks on restart"). When auditing a "richer transition function exists but the apply seam uses a leaner one" pattern (already documented for WAL_JOB_START in bug-1687's persist.h comment), always check whether the richer function has ANY live caller at all, not just whether it's correct.
- **(2026-08-25, bug-1689) Reconfirms the bug-1687 "multiple concurrent sessions write `.wolf/buglog.json`" lesson with a fresh, independent collision — re-check the max id immediately before EVERY write, every time, not just once per session.** Checked max id at session start (1687, free), implemented the full fix using "bug-1688" throughout code comments/test file, then re-checked right before writing the buglog entry (per this project's own mandated protocol) and found bug-1688 had been claimed by a sibling session's unrelated scheduler REPL bug in the interim — required a `sed -i 's/bug-1688/bug-1689/g'` across all 5 touched files before writing the entry as bug-1689. Zero cost when caught by re-checking as instructed; would have been a duplicate-id buglog corruption if skipped.
- **(2026-08-25, bug-1687) `ptrace` is fully unavailable in this sandbox — `gdb -p <pid>` fails with "Operation not permitted" even with `dangerouslyDisableSandbox: true`, and `/proc/<pid>/task/*/stack` is also unreadable (no permission).** The only process-introspection signal available without ptrace is `/proc/<pid>/task/*/{wchan,status,comm}` (shows which kernel function a thread is blocked in, e.g. `futex_do_wait`, and its state, e.g. `S (sleeping)`) plus `ps -o pid,etime,time` (wall-clock ELAPSED vs. CPU TIME — a process with large ELAPSED but near-zero TIME is genuinely blocked/deadlocked, not slow-computing; this distinction is what first proved the bug-1687 hang was a real deadlock and not just a slow test run). To actually isolate WHICH test in a monolithic Unity binary is hanging when `-t <group>` only filters at group-function granularity (see the existing entry on this): temporarily insert an early `return;` inside the group's own driver function (e.g. `test_commit_barrier_reply()`) right after a candidate `RUN_TEST()` call, rebuild+run with a bounded `timeout N`, and binary-search by moving the `return;` — each step is a full, clean process exit (no ptrace needed) so the log's last line IS trustworthy for that step (unlike a genuinely-hung run, where stdio is fully-buffered when redirected to a file/pipe and the last visible log line can be UP TO ~4KB of buffered-but-unflushed output stale relative to where the hang actually occurred — do not trust "last line before a real hang" as the hanging test's identity without this bisection). **After finishing this kind of diagnostic bisection, grep the group's own driver function for accidentally-DUPLICATED `RUN_TEST()` calls before considering the file clean** — inserting new `RUN_TEST()` lines ahead of a temporary `return;` marker and then later deleting only the marker (not the inserted lines) leaves the original, further-down calls to the same tests still present, silently double-running them (caught here via `git diff` showing a nonzero `+RUN_TEST`/`-RUN_TEST` net and a suspiciously-inflated total test count).
- **(2026-08-25, bug-1691) `/tmp` in this shared sandbox can silently fill to 0 bytes free across many unrelated sessions, because `mkdtemp()`-based test fixtures across this whole codebase only `rm -rf` their scratch dir on the NORMAL end-of-test path — any run aborted mid-way by ENOSPC (or a crash, or a killed process) leaves its 64-128MB scratch dir behind forever, compounding: fuller disk -> more runs abort early -> more orphans -> fuller disk still.** Found live: a build+test run failed with `OSError: [Errno 28] No space left on device`, both from meson's own JSON/XML report writer AND, worse, from genuine mid-test `mkdtemp failed`/spurious `Expected Non-NULL` failures in UNRELATED test files once disk hit 0 during actual test execution — that second failure mode produces a CORRUPTED, untrustworthy pass/fail signal, not just a build-tool crash, so a run showing many unrelated failures right after an ENOSPC condition should be re-run after fixing disk, never trusted as real regression evidence. Root cause found via `ls -1 /tmp | grep -c '^xce_'`: 15,437 orphaned `/tmp/xce_*` scratch directories (~658GB) had accumulated from prior sessions. Verified none were held open by any live process first (`for p in /proc/[0-9]*; do ls -la "$p/fd" | grep <pattern>; done` — empty output = safe to delete; a live `xce-deploy-*` HA cluster's OWN workdir, e.g. `/tmp/xce-deploy-1679`, DOES show up in a live process's cmdline/cwd and must NOT be deleted). Fix: `rm -rf /tmp/xce_*` recovered 658GB instantly (359MB free -> 659GB free). **Whenever a build/test run in this sandbox fails with ENOSPC or `df -h /` shows near-0 available, check for this exact pattern before assuming it's a real disk-capacity problem or someone else's live process** — it is very likely safe, accumulated test-fixture debris, not a resource genuinely in use.
- **(2026-08-25, bug-1691) A shared-fixture footgun found copy-pasting a sibling WAL-fact test file's "drive the real production entry point through a real restart cycle" test: `persist_init(&c.persist, &cfg, NULL)` (the exact line every prior sibling test — bug-1687/1689's START/FINISH suites — uses) is WRONG for a hook-driven fact type, even though it is byte-identical to what those working sibling tests do.** `persist_start()` only registers `persist_job_hook_fun`/`persist_queue_hook_fun`/`persist_worker_hook_fun` on `p->cluster->hooksys` when `p->cluster != NULL` (persist.c). DISPATCH/START/FINISH's production functions call `persist_submit_job_*_fact()` DIRECTLY, bypassing the hook system entirely, so those sibling tests' `NULL` 3rd-arg is harmless. But CKPT (like bug-1690's RUSAGE) is HOOK-DRIVEN: `on_job_ckpt()` only ever calls `xce_run_hooks(..., HOOK_DETAIL_JOB_CKPT, ...)` and relies ENTIRELY on `persist_job_hook_fun` being a registered subscriber to reach the WAL at all. With `NULL` passed, the real-call test silently appends NOTHING (no crash — `xce_run_hooks` on an unregistered detail is simply a no-op), so the restart-replay assertion fails with "ckptinfo NULL" even though the fix code itself was already fully correct — a genuine RED-signal red herring that looks exactly like a fix defect. Diagnosed by noticing the 6 hand-built-payload tests all passed (proving the apply-seam decode/adopt logic itself was right) while ONLY the real-production-call test failed, then re-reading `persist_start()`'s actual gating condition rather than assuming the copied fixture was correct because it was copied from a passing sibling. **General rule: before copying a sibling WAL-fact test file's "drive the real production function" fixture, check whether YOUR fact type's producer calls `persist_submit_job_*_fact()` directly (safe to pass NULL) or is purely hook-driven via `xce_run_hooks()` (MUST pass a real `cluster_t*` to `persist_init()`'s 3rd arg so `persist_start()` registers the hook) — grep the producer function's own body, don't assume parity from a structurally-similar sibling test.**
- **(2026-08-25, bug-1691) `controller_unit_test` (not just `xce_test`, extending the existing entry on this) also loses ALL buffered stdout (both PASS and FAIL lines) when run directly with output redirected to a file/pipe AND `ASAN_OPTIONS`'s default `detect_leaks=1` finds leaks** — LeakSanitizer's atexit-registered check appears to bypass libc's normal `exit()`-time stdio flush, so a process that ran 7/7 tests successfully to completion produced a log with ONE clean LeakSanitizer summary and ZERO printed PASS/FAIL text anywhere, first reading as "harness produced no output, something is badly wrong" before realizing it was a buffering artifact. `stdbuf -oL` does NOT fix it (worse: it breaks ASAN's LD_PRELOAD-ordering requirement, producing `ASan runtime does not come first in initial library list`) — the correct fix is `ASAN_OPTIONS=detect_leaks=0 ./controller_unit_test -t <group>` when reading stdout directly (matches meson's own test-harness invocation, which already sets this — the SAME binary run through `meson test` shows real PASS/FAIL text fine). The leaks themselves (spy-fixture jobs created via `makeJob()` into a `jobmgr` with no `.datakiller`, never individually torn down) are PRE-EXISTING and IDENTICAL across every sibling `test_wal_job_*_fact.c` suite (confirmed: `test_wal_job_rusage_fact` in isolation shows 78 leaked allocations too) — not a new leak introduced by any one fix, not worth chasing.
## Do-Not-Repeat
- **When translating a `fp_ok`-tracked, per-step-gated failure-detection pattern (like `xce-build.sh`'s dirty-fingerprint computation) into a Make `$(shell sh -c '...')` one-liner, do NOT collapse two commands into `{ cmd1; cmd2; } >"$$fp_tmp"` and gate the whole group with one `if`** — a POSIX sequential group's exit status reflects only its LAST command, so `cmd1`'s failure is silently discarded whenever `cmd2` still succeeds (2026-08-21, bug-1612: `git diff HEAD` failing was swallowed because the trailing `git ls-files | sort | xargs cat` pipe still exited 0, producing a fabricated fingerprint instead of degrading to empty). Give each step its own explicit `if ! step; then fp_ok=0; fi` (or `&&`-chain), exactly mirroring the reference script's discipline, even inside a cramped one-liner. Caught by adversarial code review with a targeted failure-injection test (wrapper `git` on `PATH` that fails only the targeted subcommand) — that test pattern is the reusable way to verify this class of fix.