superlocalmemory 4.1.0 → 4.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +29 -1
- package/CHANGELOG.md +107 -0
- package/README.md +37 -72
- package/ide/configs/codex-mcp.toml +3 -1
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -3
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +100 -31
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-loop/SKILL.md +1 -1
- package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
- package/plugin-src/skills/slm-profile/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +94 -0
- package/src/superlocalmemory/server/recall_health.py +87 -10
- package/src/superlocalmemory/server/unified_daemon.py +55 -2
- package/src/superlocalmemory/storage/_migration_internals.py +23 -2
- package/src/superlocalmemory/storage/migrations/M043_quarantine_display_summaries.py +60 -36
- package/src/superlocalmemory/storage/schema.py +23 -0
|
@@ -409,45 +409,31 @@ def _sync_lifecycle_mirror(conn: sqlite3.Connection) -> None:
|
|
|
409
409
|
""")
|
|
410
410
|
|
|
411
411
|
|
|
412
|
-
def
|
|
413
|
-
"""
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
412
|
+
def unmet(conn: sqlite3.Connection) -> str:
|
|
413
|
+
"""Which check does not hold, named. Empty string when all of them do.
|
|
414
|
+
|
|
415
|
+
``verify()`` returns a bare boolean, so when a completed migration stops
|
|
416
|
+
verifying the runner can only say "safe repair did not restore M043". This
|
|
417
|
+
checks five separate things, and that sentence names none of them -- a user
|
|
418
|
+
hitting it had to come back and ask which, and so did we. This is the same
|
|
419
|
+
gap that ``migration_failure_reasons`` closed one level up, left open one
|
|
420
|
+
level down.
|
|
418
421
|
"""
|
|
419
422
|
if not _table_exists(conn, "atomic_facts"):
|
|
420
|
-
return
|
|
423
|
+
return ""
|
|
421
424
|
if not _has_column(conn, "atomic_facts", "quarantined"):
|
|
422
|
-
return
|
|
425
|
+
return "atomic_facts has no 'quarantined' column"
|
|
423
426
|
if not _table_exists(conn, "consolidated_summaries"):
|
|
424
|
-
return
|
|
425
|
-
|
|
427
|
+
return "the consolidated_summaries display table is missing"
|
|
426
428
|
if _table_exists(conn, "fact_consolidations"):
|
|
427
|
-
|
|
429
|
+
n = _count(
|
|
428
430
|
conn,
|
|
429
431
|
"SELECT COUNT(*) FROM atomic_facts WHERE COALESCE(quarantined, 0) = 0 "
|
|
430
432
|
" AND fact_id IN (" + _CONSOLIDATOR_ROWS + ")",
|
|
431
433
|
)
|
|
432
|
-
if
|
|
433
|
-
return
|
|
434
|
-
|
|
435
|
-
# Every withheld row must still be visible somewhere, or the repair has
|
|
436
|
-
# deleted the owner's view of it rather than moved it.
|
|
437
|
-
#
|
|
438
|
-
# BY IDENTITY *OR* CONTENT, and the "or" is what makes this an invariant
|
|
439
|
-
# rather than a trap. Matching on content alone could never become true
|
|
440
|
-
# once a row's content changed after being preserved: the display copy
|
|
441
|
-
# keeps the old text, verify stays false, repair() runs apply() again,
|
|
442
|
-
# apply() cannot change the past, and the migration is reported failed
|
|
443
|
-
# on every start for the rest of the store's life. Matching on identity
|
|
444
|
-
# alone fails the other way, because two withheld rows with identical
|
|
445
|
-
# text collapse into one display row under the unique triple, leaving the
|
|
446
|
-
# second with no row of its own id.
|
|
447
|
-
#
|
|
448
|
-
# Either match satisfies the guarantee that actually matters: nothing
|
|
449
|
-
# the owner could see has stopped being visible.
|
|
450
|
-
unpreserved = _count(conn, """
|
|
434
|
+
if n:
|
|
435
|
+
return f"{n} model-written summaries are not withheld from recall"
|
|
436
|
+
n = _count(conn, """
|
|
451
437
|
SELECT COUNT(*) FROM atomic_facts af
|
|
452
438
|
WHERE af.fact_id IN (""" + _CONSOLIDATOR_ROWS + """)
|
|
453
439
|
AND NOT EXISTS (
|
|
@@ -457,13 +443,51 @@ def verify(conn: sqlite3.Connection) -> bool:
|
|
|
457
443
|
OR cs.content = af.content)
|
|
458
444
|
)
|
|
459
445
|
""")
|
|
460
|
-
if
|
|
461
|
-
return
|
|
462
|
-
|
|
446
|
+
if n:
|
|
447
|
+
return f"{n} withheld summaries have no display copy"
|
|
463
448
|
if _table_exists(conn, "fact_retention"):
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
449
|
+
n = _count(conn, "SELECT COUNT(*) FROM (" + _wrongly_hidden(conn) + ")")
|
|
450
|
+
if n:
|
|
451
|
+
return f"{n} real memories are hidden from recall and should not be"
|
|
452
|
+
return ""
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def blocks_serving(conn: sqlite3.Connection) -> bool:
|
|
456
|
+
"""Should a daemon refuse to serve while this check does not hold?
|
|
457
|
+
|
|
458
|
+
Only when the SCHEMA is missing. The two schema conditions here -- the
|
|
459
|
+
column and the display table -- mean queries would hit something that is not
|
|
460
|
+
there, so refusing is right. The other three are about DATA: a summary that
|
|
461
|
+
should be withheld is not withheld, or a real memory is hidden. Those make
|
|
462
|
+
some answers worse; they do not stop the store working.
|
|
463
|
+
|
|
464
|
+
The distinction matters because this ``verify()`` is a standing guard over
|
|
465
|
+
data that ordinary use can re-violate -- a consolidation pass hiding one more
|
|
466
|
+
memory is enough. Treating that like a missing table meant one drifted row
|
|
467
|
+
could return 503 on every route indefinitely, with a manual restart the only
|
|
468
|
+
way out. That is an outage caused by a quality check, which is worse than the
|
|
469
|
+
thing the check is for.
|
|
470
|
+
|
|
471
|
+
Reported as #125, where a user's daemon sat unusable on exactly this.
|
|
472
|
+
"""
|
|
473
|
+
if not _table_exists(conn, "atomic_facts"):
|
|
474
|
+
return False
|
|
475
|
+
if not _has_column(conn, "atomic_facts", "quarantined"):
|
|
476
|
+
return True
|
|
477
|
+
return not _table_exists(conn, "consolidated_summaries")
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def verify(conn: sqlite3.Connection) -> bool:
|
|
481
|
+
"""Whether the repair's end-state holds.
|
|
482
|
+
|
|
483
|
+
Called on every start for an already-complete migration. Returning False
|
|
484
|
+
routes to ``repair()``, which makes this a standing guard: if pollution ever
|
|
485
|
+
reappears, the next daemon start withholds it without anyone asking.
|
|
486
|
+
|
|
487
|
+
Thin wrapper over ``unmet()`` so the two can never disagree about what
|
|
488
|
+
"verified" means.
|
|
489
|
+
"""
|
|
490
|
+
return not unmet(conn)
|
|
467
491
|
|
|
468
492
|
|
|
469
493
|
def repair(conn: sqlite3.Connection) -> None:
|
|
@@ -972,6 +972,18 @@ _DDL_ORDERED: Final[tuple[str, ...]] = (
|
|
|
972
972
|
#: which SQLite offers no IF NOT EXISTS form of, so presence is checked first.
|
|
973
973
|
_ADDITIVE_COLUMNS: Final[tuple[tuple[str, str, str], ...]] = (
|
|
974
974
|
("atomic_facts", "quarantined", "INTEGER NOT NULL DEFAULT 0"),
|
|
975
|
+
# ``pinned`` arrives with M015, which is a DEFERRED migration -- it runs
|
|
976
|
+
# after the engine is up. But the DDL below indexes it
|
|
977
|
+
# (``idx_facts_pinned``), and that DDL runs during engine start. On a store
|
|
978
|
+
# old enough to predate M015 the index therefore raised "no such column:
|
|
979
|
+
# pinned" before anything could add it, and every start failed the same way:
|
|
980
|
+
# the deferred pass could not run because the engine could not start, and
|
|
981
|
+
# the engine could not start because the deferred pass had not run.
|
|
982
|
+
# ``slm db migrate`` did not break the loop either -- it reports Failed=0
|
|
983
|
+
# and skips deferred migrations by definition.
|
|
984
|
+
# The column has to exist before its own index either way, so it belongs
|
|
985
|
+
# here, where a store gets it at start regardless of migration state.
|
|
986
|
+
("atomic_facts", "pinned", "INTEGER NOT NULL DEFAULT 0"),
|
|
975
987
|
)
|
|
976
988
|
|
|
977
989
|
|
|
@@ -1011,6 +1023,17 @@ def create_all_tables(conn: sqlite3.Connection) -> None:
|
|
|
1011
1023
|
"""
|
|
1012
1024
|
_set_pragmas(conn)
|
|
1013
1025
|
|
|
1026
|
+
# Before the DDL, not only after it. The DDL below indexes columns that an
|
|
1027
|
+
# upgraded store may not have yet, and an index on a column that does not
|
|
1028
|
+
# exist is a hard error, not a skipped statement -- so the whole of
|
|
1029
|
+
# create_all_tables would raise and the engine would never start.
|
|
1030
|
+
#
|
|
1031
|
+
# On a fresh database this pass does nothing: the tables do not exist yet,
|
|
1032
|
+
# which the helper treats as "nothing to alter". On an upgraded one the
|
|
1033
|
+
# tables are already there and this is exactly where the columns are owed.
|
|
1034
|
+
# It runs again at the end for tables created during this call.
|
|
1035
|
+
_add_missing_columns(conn)
|
|
1036
|
+
|
|
1014
1037
|
for ddl in _DDL_ORDERED:
|
|
1015
1038
|
conn.executescript(ddl)
|
|
1016
1039
|
|