Files
HologramAgent-DSH-Memory-Port/lib
jianf 44f883745f feat(memory): give C5's reason an outlet, with a read that cannot schedule (C5 follow-up)
28866ac made `centroidStatus` name why centring is disabled. Nothing read it.
`stats()` reported `centroid` from `loadCentroid` — the raw row, which says WHAT
the centre is and not WHY centring is or is not happening — and the only two
callers of `centroidStatus` in `lib/` were `centroidRecallState` (which spreads
the result but is itself only used by tests) and `#ensureCentroid` on the write
path, which discards the return value. A grep for `.reason` under `lib/` returned
nothing but the definition itself.

Same failure as D2 one commit earlier: the value existed, was covered, and no
operator could see it.

## What this adds

`stats().centroid_state` — the verdict, alongside the existing raw `centroid`:

    centroid        the stored row (dim, mean, vectorCount, stats)
    centroid_state  { available, stale, reason, delta, rebuildScheduled, ... }
                    reason ∈ ok | no_centroid | dim_mismatch | zero_sample |
                             stale | isotropic

`centroid` keeps its exact previous shape, so nothing downstream changes.

## The part that is easy to get wrong

`centroidStatus` **registers a rebuild** when it judges a space stale. A stats
read must not mutate anything, so the call passes `schedule: () => {}` — with
the no-op scheduler `rebuildScheduled` is always false here, which is what a pure
read should report. Without it, every operator polling `stats()` on a drifted
space would keep the rebuild loop alive.

## Criterion

`test/phase-b-c5-reasons.test.js` gains a ninth case driving the real plugin:

    fresh space            -> reason 'no_centroid'
    after 8 commits + wait -> 'ok', available true, raw row and verdict both present
    drift past the ratio   -> 'stale', stale true, rebuildScheduled FALSE

The last assertion is the one that matters: it distinguishes "reports staleness"
from "acts on it".

Mutation — deleting the `centroid_state` field:

    not ok 9 - C5: stats() exposes the reason without scheduling a rebuild
    error: "Cannot read properties of undefined (reading 'reason')"

Also removed a tautology I had written in the same case
(`assert.equal(s.centroid, s.centroid, ...)`) — it can never fail, and a criterion
that cannot fail is worse than none.

Gates: npm test 300/300 (0 skipped), npm run check 12/12.
2026-11-05 11:54:12 +08:00
..