Skip to content

fix(memory): make the Directory Inventory Status column enforceable - #1759

Open
anikinsasha wants to merge 1 commit into
danielmiessler:mainfrom
anikinsasha:pr-b/memory-inventory-provisioning-v2
Open

fix(memory): make the Directory Inventory Status column enforceable#1759
anikinsasha wants to merge 1 commit into
danielmiessler:mainfrom
anikinsasha:pr-b/memory-inventory-provisioning-v2

Conversation

@anikinsasha

Copy link
Copy Markdown

Implements #1757. Companion: #1758 (PR-A in the tables below).

Takes a fresh guided install from 14 permanent drift warnings to 2, and an
upgraded install that already has its trees on disk to zero — without scaffolding
a single directory to fake the checker quiet.

This is a re-cut, not a rebase. An earlier version of this change added a
separate Provisioning column. You solved the same problem this release by
putting on-demand into the Status column, and I think that was the better call:
one axis, no new cell for every future row. So this keeps your column and fixes
what is actually wrong with it, which is that it is not enforced.

What changes

File Change
LIFEOS/DOCUMENTATION/Memory/MemorySystem.md Status vocabulary defined; every row re-derived from the shipped tree; writer cells corrected; reserved restated self-containedly; governance rule; § Drift Detection prose; changelog entry
hooks/handlers/MemoryDirIntegrity.ts positional cell parse; unrecognised statuses reported instead of exempted; de-trapped remedy text; parse and drift extracted as pure exported functions
skills/LifeOS/Tools/DeployCore.ts unchanged, on purpose — stated here so this PR is checkable against scope creep

The axis

active (guided setup creates it → absent afterwards is drift → warn) ·
on-demand (created on first use → absence is normal → silent) · reserved
(nothing creates or reads it → silent, reason required) · pending (see below →
warn). Anything else is reported. Silence is only ever granted by an explicit
recognised value, so a row added later without a status is loud rather than
quietly invisible.

The two failure modes this fixes

A typo is currently an exemption. Enforcement is row.status === 'active'
with no allow-list and no else branch, so every other value grants silence.
MemoryDirIntegrity.ts:57 still declares the vocabulary as two values while the
table uses four, and the doc's own § Drift Detection prose (MemorySystem.md:679)
describes the same stale pair. Nothing can distinguish a deliberate exemption from
a misspelling.

A status with a space in it removes the row from the check entirely. The row
pattern at :103 anchors the status cell as [\w-]+, which cannot match a space,
so the whole line fails to match and is never pushed into rows. Running that
exact regex against the shipped section yields 27 rows out of 29;
RELATIONSHIP/ (dormant archive) is one of the two it drops.

That is not silencing in one direction — it is invisibility in both. A dropped row
is no longer checked for absence, and its directory on disk is no longer
recognised, so :187-192 reports it as an unknown subsystem. Every install that
has been running since v6 has MEMORY/RELATIONSHIP/ on disk; at this release
those installs traded one warning for a different one telling them to add a row
that already exists.

Cells are now read positionally by splitting on |, so a multi-word status is
captured verbatim and judged afterwards. An unclassifiable row is reported as
itself rather than disappearing, and its directory stays recognised.

The trap this had to avoid

Reclassifying a row silences the checker, and on-demand silences as effectively
as reserved. WISDOM/ and RELATIONSHIP/ both have live readers in
LoadContext.hook.ts (:429 and :414), and neither reader renders anything
when its tree is empty (learning-readback.ts:165, LoadContext.hook.ts:131
both return null). For a fresh install the drift warning is the only signal that
the channel has no data.

So this PR ships a rule —

A row may NOT be moved to a Status whose absence is silent (on-demand and
reserved both are) while a shipped component still reads the directory, unless
the same change ships that reader a visible empty state.

— and then obeys it: those two rows are pending, which still warns. The change
that gives each reader an empty state is the change that reclassifies its row.
That is the 2 remaining warnings, and they are a dated debt rather than noise.

The rule is about readers, and the diagnostic the checker prints says the same
word. A writer with no reader produces nothing anyone is waiting for — which is
exactly the case for the rows this PR moves into silence, so a remedy text
saying "reader or writer" would have forbidden the repair this PR performs.

The old remedy text is also part of the problem and is replaced. It read "Either
create it or change the row's status to reserved/on-demand" — the checker
instructing the operator to silence it.

Corrections that go the other way

Two rows the writer cells made look like phantoms are not:

  • SECURITY/EventLogger.hook.ts:336-356 self-mkdirs and writes
    SECURITY/YYYY/MM/stop-failures-*.jsonl. The named SecurityPipeline.hook.ts
    is what does not exist. Your own Hook Integration table already records the true
    writer at :640, one row above the false one at :642.
  • SYSTEMUPDATES/ActivityParser.ts:634-644 self-mkdirs, and CreateUpdate.ts
    ships publicly as of this release with IntegrityMaintenance.ts:114 pointing at
    it. That row is correct and is left alone.

Both are on-demand with corrected writers, not reserved. Getting these wrong
would have silenced two live subsystems.

Names with no implementation anywhere in the payload are removed from the writer
cells of OBSERVABILITY/ (TeammateIdle, observability-transport,
HomeSensorDetector, Speedtest), TEAMS/ and SKILLS/. A test pins that.

The reserved definition

:307 defined the status by citing ShadowRelease.ts and FLAT_README_ROOTS
a file that has never existed in this repository and a constant declared nowhere —
describing stub READMEs shipping into a MEMORY/ tree the payload has not carried
since the v6 format change. It is restated self-containedly and moved out of the
Class definitions block, where it did not belong: reserved is a Status value
and never appears in the Class column. The companion private-references issue
covers the wider class and deliberately does not touch this file, so the rewrite
has one author.

Alternative available on one word

The v8.1 changelog block is your narrative of your own tree, and I have left it
alone entirely in this version. If you would rather it were corrected too — the
"grown organically since v7.x" sentence and the FLAT_README_ROOTS scaffold claim
at :834 are the two lines that read oddly in a public artifact — say so and I
will add it. Nothing in the mechanism depends on it.

Shared files and rebase expectations

Measured by merging the branches against this base, not by reading the diffs.

File Also touched by Expectation
hooks/handlers/MemoryDirIntegrity.ts PR-A Conflicts — and one side of the resolution is load-bearing. See below.
LIFEOS/DOCUMENTATION/Memory/MemorySystem.md PR-E E reclassifies the RELATIONSHIP/ row of the table this PR rewrites. Land B first and E becomes a one-cell change.
PR-C, PR-D Merge clean.

Resolving the PR-A conflict. Two hunks, and neither is either/or. In the type
block, A adds the DriftItem docstring and key field and this PR adds the
status sets: keep both. In the handler, A keeps its findings inline while this PR
extracts them into a pure computeDrift(): keep computeDrift(), and carry A's
key fields into its pushes — unknown_on_disk:<dir>, missing_active:<NAME>,
unrecognised_status:<NAME>, inventory_unparseable:<class>.

Taking this PR's side verbatim drops every finding key, which is the identity A's
SessionStart readback compares to decide whether anything changed. Both suites
stay green either way — mine does not know about keys, A's tests the readback
rather than this handler — so a typechecker is what catches it, not bun test.

Co-requisite: PR-D. It is the change that gives WISDOM/ its visible empty
state, and its rebase onto this PR flips that one cell from pending to
on-demand. Landing B without D leaves the warning standing, which is correct but
noisy; landing D without B is fine.

Tests

hooks/handlers/MemoryDirIntegrity.test.ts — 27 cases, bun test, green.
Including the shipped table itself: every row carries a recognised status, exactly
the six DeployCore directories are active, a fresh install after setup
produces exactly one finding per pending row, and an upgraded install carrying
both pending directories produces none at all.

Four cases pin the two failure modes above directly — a status with a space is
read verbatim rather than dropped; an unrecognised status is reported even when
the directory exists; one row yields at most one finding; and a row the parse
could not classify keeps its directory recognised, so the unknown_on_disk
inversion cannot come back.

Nothing hard-codes which rows are pending. The set is read off the table, and
one assertion pins the table against the governance paragraph that explains it. A
pending row is a debt cleared by the release that gives its reader an empty
state, so the set shrinks by design: pinning the names would have made doing that
correctly break this suite while forgetting it cost nothing, which is the wrong
way round. PR-D's and PR-E's flips are the first two instances, and both bodies
say which sentence they touch. Additive and separable — this repository ships no
tests today.

A fresh guided install warns 14 times on every SessionEnd, forever: the
inventory marks 20 rows `active` while DeployCore scaffolds 6, and nothing
in the release creates the other 14. Six of them are touched by no shipped
code at all, so no amount of use can clear them.

The Status column is the right axis and it is already there — it is just not
enforced. Only the literal `active` produces drift, so every other value
grants silence, and a value the row-wide regex cannot match (`dormant
archive`) drops the row out of the parse entirely: absent from the missing
check AND absent from the unknown-subsystem check, which turns into a
spurious `unknown_on_disk` warning on any install that has the directory.

- Cells are read positionally so a multi-word status is captured verbatim.
- Recognised statuses are `active`, `on-demand`, `reserved`, `pending`;
  anything else is reported as unclassified, never exempted.
- `pending` is new: absence keeps warning while a shipped reader still has
  no empty state. WISDOM/ and RELATIONSHIP/ are pending for that reason.
- Governance rule: no row may be moved into silence while a shipped reader
  still consumes it, unless the same change gives that reader an empty state.
- Rows re-derived against the shipped tree; writer cells that named
  components with no implementation in the payload corrected or dropped.
- The `reserved` definition no longer cites release tooling that is not in
  the public payload, and moves out of the Class block: it is a Status.
- The remedy text no longer proposes reclassifying the row to silence it.

Fresh guided install: 14 permanent warnings -> 2, with no directory
scaffolded to fake the checker quiet. An upgraded install that already has
both trees on disk sees none. DeployCore is deliberately unchanged.

23 tests, bun test, green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant