Commit graph

4 commits

Author SHA1 Message Date
dadfa61b5f test(idle): capture the stack behind each flush narration — the line alone cannot name its brain
Some checks failed
Delta Gate / Delta gate — candidate vs control (push) Failing after 7s
CI / Node 22 (push) Successful in 12m24s
CI / Node 24 (push) Successful in 12m20s
CI / Integration + conformance (Node 22) (push) Failing after 15m56s
CI / Bun (latest) (push) Successful in 12m31s
The self-diagnosis from the last round worked: the box says this brain's own
providers were NOT called, so the flush pairs inside the 90 s window belong to
another brain in the same process. It could not say WHICH, and the advice it
gave — read the 'stdout | <file> > <test>' prefix — cannot work here: vitest
tags a stdout block with the test that is RUNNING, and these lines are captured
by this test's own console hook anyway. Teeing them through would only ever
print this test's name.

The call stack does name the driver, so it is captured beside each line and the
first one is reported: `kickBackgroundFlush('idle')` under `armIdleFlushTimer`
is some brain's cadence timer, the deferred-embed worker's commit path is a
brain still landing vectors, and a bare `flush()` is an explicit caller.

Why that distinction settles it. A flush only narrates PAST the dirty gate, and
`_dirtySinceLastFlush` is set in exactly three places — `noteWriteForPersistence()`
(both commit paths, and the deferred-embed worker lands its vectors through the
single-op one), `clear()`, and `repairIndex()`. So a narrating flush is a flush
whose brain really did commit a write; "0 ms" is the flush being cheap, not the
flush being empty. That reading rules OUT the re-arming-follow-up theory: the
queued follow-up is armed only by a concurrent flush() caller, cleared before
promotion, and a promoted run over a clean brain returns at the dirty gate
without touching a provider or printing a line.

Context the message now carries: the suite runs every file in ONE process, and
a create-versus-close scan puts 67 test files above the line — more brains made
than closed. This assertion is downstream of that, and the next red arrives with
the stack that names which one.
2026-09-02 16:17:55 -07:00
87d3a945a5 test(idle): the idle pin says WHICH brain narrated, and asserts the attributable half first
Some checks failed
CI / Node 22 (push) Waiting to run
CI / Node 24 (push) Waiting to run
CI / Integration + conformance (Node 22) (push) Waiting to run
CI / Bun (latest) (push) Waiting to run
Delta Gate / Delta gate — candidate vs control (push) Failing after 8s
"An idle brain prints nothing" is pinned two ways in this case, and only one of
them is attributable. The spies are bound to THIS brain's providers, so they
answer "did this brain flush?" exactly. The console filters cannot: the gate
config runs the whole suite in ONE process (pool: 'forks', singleFork: true —
verified, two files report the same process.pid), so console.log carries the
narration of every brain alive in that process, including one a previous file
opened and never closed whose unref'd cadence timer is still doing honest work.

Ordered as it was, a neighbour's honest flush and this engine breaking its own
law produced the same red, with a message that truncated the evidence to
"[ …(4) ]" — no way to tell which had happened, and nothing to chase.

So the spies assert first: their failure means the engine broke the law. The
console assertion follows, keeps both patterns, and carries the captured lines
in its message. vitest prefixes each stdout block with "stdout | <file> >
<test>", so the lines plus the surrounding log name the brain that printed
them, and the next red is diagnosable from the log alone.

No assertion is removed and no window is widened — the same two laws are
pinned, in the order that makes a failure readable.
2026-09-02 15:53:23 -07:00
fb1da1c56d perf(idle): the flush-request watch is event-driven; the heartbeat is observability
Three idle-burn items from the steady-state audit, and one correction.

THE FLUSH-REQUEST WATCH (the strongest of them). It readdir'd the request
directory every 500 ms, per brain, for the life of every writer — armed on
every non-reader brain whether or not any inspector process existed. In a process holding many stores that is tens of directory reads per second
on a completely idle service, plus a stale-request GC on every one of them. It now
uses fs.watch, so the arrival itself wakes it and a request is seen SOONER
than the poll saw it. Two concessions ride along, both stated in the code: a
30s safety sweep (fs.watch drops events on some network and fuse filesystems,
and the GC needs a tick of its own — two orders of magnitude fewer reads than
the poll made), and a fall back to the original 500 ms poll, narrated, on a
filesystem that cannot watch at all, because an inspector whose request is
never seen waits forever.

THE WRITER HEARTBEAT goes 10s → 60s. It is observability ONLY — staleness is
decided by pid liveness and the fence compares pid + hostname, so no decision
anywhere reads the timestamp — and at 10s it was a lock-file write every ten
seconds per brain forever, for a value nothing computes with. An operator
still sees a heartbeat inside the minute.

THE HEALTH NARRATION dedupes by CONTENT, not by the provider's generation
counter. That counter bumps on every ledger mutation and rebuild boundary, so
a provider bumping it on routine work re-emitted the same unchanged line on
every read, while one that never bumped could suppress a line whose reasons
had genuinely changed. The generation is still reported; it no longer decides
whether the line is worth saying.

CORRECTION, and it is against my own earlier claim: the idle-flush commit read
a reported idle-CPU observation (many stores, no writes, a flush every ~35s,
over a core burned) as caused by
the flush path. That does not follow — this engine's cadence is write-driven
(every trigger runs through noteWriteForPersistence, which only a committed
write calls), so something was CALLING flush() on those brains and the caller
is still unidentified. The clean-flush gate makes such a call free; it does not
account for it. The code comments and the idle lane now say exactly that.

Pins: tests/integration/flush-watcher-event-driven.test.ts — an idle writer
makes at most one request-directory read in 8 seconds (the old poll made ~16),
and a dropped request is still acked well inside the safety sweep.
2026-08-28 11:25:47 -07:00
f5a6cb3f61 perf(flush): an idle brain does no work — no periodic flush without a write
REPORTED from the field: a process holding many stores, with no writes for ten
minutes, printed "All indexes flushed to disk in 216-601ms" per store every
~35 seconds and burned over a core at idle. Every one of those flushes re-persisted
state identical to what was already on disk — the provider flushes, the
watermark stamps, the generation counter, the entity-tree stamp — because
flush() never asked whether anything had changed.

- flush() over a clean brain is now O(1) and silent: a dirty witness is set by
  every committed write (both commit paths end at noteWriteForPersistence, and
  the deferred-embed worker lands through the single-op path) and cleared by a
  flush that runs. A write landing DURING a flush sets it again, so no write's
  work is ever skipped — it is done by the next flush. Set before the policy
  check, so a `'manual'` consumer's explicit flush is never a no-op it didn't
  ask for.
- An explicit flush now tells the cadence it happened. It didn't, so the very
  next write saw "30s since the last flush" and kicked a background flush with
  nothing to do, and the idle timer fired two seconds later over writes the
  explicit flush had already persisted.
- The graph adjacency index's auto-flush asks before it acts: two O(1) reads
  of the LSM MemTables, and a tick over a quiet index returns without calling
  into the trees at all.

assessProviderHealth is NOT timer-driven — it is a synchronous O(1) read of a
provider's own healthReport(), called on the read gate, so it costs nothing on
an idle brain. No change needed there.

Pins: tests/integration/idle-costs-nothing.test.ts — 90 idle seconds produce
zero flushes, zero provider calls and zero log lines; three explicit flushes
over a clean brain call no provider; one write earns exactly one flush.
2026-08-28 10:44:38 -07:00