Give every memory a holder, an origin, a time and a scenario

An EvidenceAtom could say what was remembered and nothing else: not whose memory it
is, not where it came from, not when it was true, not in what situation it may be
used. The baseline measured the cost of that: the same ranker scores recall@10 0.412
over the whole corpus and 0.866 inside one holder's memory, so the missing holder
column is worth more than any ranking change in the ablation.

- schemas.py: MemoryOrigin, TimePrecision, Visibility; EvidenceAtom gains holder_id,
  origin, scenario, event_time / event_time_end / event_time_precision /
  event_time_source, valid_from / valid_to, supersedes, visibility, retention_until.
  `time` is renamed `ingested_at` — ingestion time gets one name, not two.
  A validator refuses valid_to before valid_from, event_time_end before event_time,
  and an atom that supersedes itself.
- migrations.py: schema v2 with PRAGMA user_version. A first-version database is
  upgraded in place (ALTER + RENAME COLUMN), rows intact, and a database newer than
  this build refuses to open instead of being silently downgraded.
- v_atoms_current hides memories that are closed, lapsed or not yet begun; unknown
  bounds read as visible. Time travel is db.atoms_as_of(moment, holder) because a
  SQLite view cannot take a parameter.
- evaluation_schemas.py: the proof and benchmark report shapes move out of schemas.py
  to keep it under the file-size limit; importers follow.
- backend/tests/test_memory_schema.py: 8 tests, including the v1 -> v2 upgrade and the
  validity timeline.

Verification of record: pytest backend/tests bench/tests -q -> 70 passed; ruff check on
every touched file -> all checks passed; mypy (fresh cache) on db.py, migrations.py,
schemas.py, evaluation_schemas.py and backend/tests -> no issues. evals.py carries 40
pre-existing mypy errors at HEAD and 40 after this change (measured on both copies);
its pyright findings are the same debt, and p0-6 rewrites that file. `peft` and
`pyarrow.parquet` "could not be resolved" is the documented pyright artifact: pi-lens
runs it with 0 config files, and both import at runtime.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-07 14:30:22 -04:00
1 parent 00dc9f8eaf
commit 181f6bf57e
12 files changed
+1100 -319

No files matched your search

+9 -7
View File
@@ -24,6 +24,15 @@ from .config import Settings, get_settings
from .db import Database
from .embeddings import EmbeddingService
from .evals import HashHopEvaluator, PRDProofSuite
from .evaluation_schemas import (
BenchmarkPredictionReport,
BenchmarkRegistryReport,
BenchmarkSampleReport,
BenchmarkSourceReport,
HashHopProof,
ProofCheck,
ProofSuiteReport,
)
from .evidence_graph import EvidenceGraphService
from .ingestion import IngestionService
from .profiles import runtime_profile_report
@@ -33,16 +42,9 @@ from .schemas import (
AdapterManifest,
AdapterStatus,
AnswerResponse,
BenchmarkRegistryReport,
BenchmarkPredictionReport,
BenchmarkSampleReport,
BenchmarkSourceReport,
CartridgeAssetReport,
HashHopProof,
MemoryStatus,
PathIngestRequest,
ProofCheck,
ProofSuiteReport,
RuntimeProfile,
RuntimeProfileReport,
SystemStatus,
+8 -6
View File
@@ -8,7 +8,7 @@ from ast import literal_eval
from pathlib import Path
from typing import Any
from .schemas import (
from .evaluation_schemas import (
BenchmarkPredictionReport,
BenchmarkPredictionStatus,
BenchmarkRegistryReport,
@@ -19,7 +19,6 @@ from .schemas import (
BenchmarkSpec,
)
STANDARD_BENCHMARKS = (
BenchmarkSpec(
name="MMMU-Pro",
@@ -125,7 +124,7 @@ def benchmark_source_report(timeout: int = 20) -> BenchmarkSourceReport:
sibling_count=len(siblings),
)
)
except Exception as exc:
except Exception as exc: # noqa: BLE001 # a probe records its failure instead of crashing the proof run
sources.append(
BenchmarkSourceStatus(
name=benchmark.name,
@@ -217,7 +216,7 @@ def load_benchmark_sample(benchmark: BenchmarkSpec, output_root: Path) -> Benchm
sample_sha256=sha256_json(normalized),
media_artifacts=media_artifacts,
)
except Exception as exc:
except Exception as exc: # noqa: BLE001 # a probe records its failure instead of crashing the proof run
message = str(exc).splitlines()[0]
status = "gated" if "gated dataset" in message.lower() or "authenticated" in message.lower() else "failed"
return BenchmarkSampleStatus(
@@ -274,7 +273,7 @@ def predict_benchmark_sample(benchmark: BenchmarkSpec, output_root: Path, runtim
output_tokens=generation.output_tokens,
media_artifacts=prediction_media,
)
except Exception as exc:
except Exception as exc: # noqa: BLE001 # a probe records its failure instead of crashing the proof run
message = str(exc).splitlines()[0]
status = "gated" if "gated dataset" in message.lower() or "authenticated" in message.lower() else "failed"
config, split = BENCHMARK_SAMPLE_LOADS.get(benchmark.name, (None, None))
@@ -351,7 +350,10 @@ def load_records(path: Path) -> list[dict[str, Any]]:
with path.open("r", encoding="utf-8") as handle:
return [json.loads(line) for line in handle if line.strip()]
if suffix == ".json":
data = json.loads(path.read_text(encoding="utf-8"))
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
raise ValueError(f"{path}: cannot read the benchmark file ({error})") from error
if isinstance(data, list):
return [dict(item) for item in data if isinstance(item, dict)]
if isinstance(data, dict):
+107 -100
View File
@@ -2,14 +2,15 @@ from __future__ import annotations
import json
import sqlite3
from collections.abc import Iterable
from collections.abc import Iterator
from contextlib import contextmanager
from datetime import datetime
from pathlib import Path
from typing import Any
from .config import Settings, get_settings
from .schemas import EvidenceAtom, SourceRecord
from .migrations import apply as apply_migrations
from .schemas import EvidenceAtom, MemoryOrigin, SourceRecord, TimePrecision, utc_now
def _json(value: Any) -> str:
@@ -19,7 +20,16 @@ def _json(value: Any) -> str:
def _loads(value: str | None, default: Any) -> Any:
if value is None or value == "":
return default
return json.loads(value)
try:
return json.loads(value)
except json.JSONDecodeError as error:
raise ValueError(f"stored JSON is unreadable: {error}") from error
def _datetime(value: str | None) -> datetime | None:
if value is None or value == "":
return None
return datetime.fromisoformat(value)
class Database:
@@ -30,7 +40,7 @@ class Database:
self.init()
@contextmanager
def connect(self) -> Iterable[sqlite3.Connection]:
def connect(self) -> Iterator[sqlite3.Connection]:
connection = sqlite3.connect(self.path)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys = ON")
@@ -43,76 +53,7 @@ class Database:
def init(self) -> None:
with sqlite3.connect(self.path) as connection:
connection.executescript(
"""
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS sources (
source_id TEXT PRIMARY KEY,
path TEXT NOT NULL,
kind TEXT NOT NULL,
mime TEXT NOT NULL,
sha256 TEXT NOT NULL,
size_bytes INTEGER NOT NULL,
created_at TEXT NOT NULL,
metadata_json TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS atoms (
atom_id TEXT PRIMARY KEY,
source_id TEXT NOT NULL REFERENCES sources(source_id) ON DELETE CASCADE,
modality TEXT NOT NULL,
source TEXT NOT NULL,
time TEXT NOT NULL,
text TEXT NOT NULL,
image_ref TEXT,
embedding_refs_json TEXT NOT NULL,
layout_json TEXT,
relations_json TEXT NOT NULL,
trust_json TEXT NOT NULL,
token_equivalent INTEGER NOT NULL,
metadata_json TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_atoms_source_id ON atoms(source_id);
CREATE INDEX IF NOT EXISTS idx_atoms_modality ON atoms(modality);
CREATE VIRTUAL TABLE IF NOT EXISTS atom_fts USING fts5(
atom_id UNINDEXED,
source,
text,
tokenize = 'unicode61'
);
CREATE TABLE IF NOT EXISTS cartridges (
cartridge_id TEXT PRIMARY KEY,
name TEXT NOT NULL,
root_path TEXT NOT NULL,
manifest_json TEXT NOT NULL,
dna TEXT,
token_equivalent INTEGER NOT NULL,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS cache_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL,
layer TEXT NOT NULL,
cache_key TEXT NOT NULL,
event TEXT NOT NULL,
bytes_estimate INTEGER NOT NULL,
metadata_json TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS evidence_edges (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_atom_id TEXT NOT NULL,
to_atom_id TEXT NOT NULL,
relation TEXT NOT NULL,
confidence REAL NOT NULL,
metadata_json TEXT NOT NULL
);
"""
)
apply_migrations(connection)
def upsert_sources(self, sources: list[SourceRecord]) -> None:
with self.connect() as connection:
@@ -150,15 +91,17 @@ class Database:
connection.executemany(
"""
INSERT INTO atoms (
atom_id, source_id, modality, source, time, text, image_ref,
atom_id, source_id, modality, source, ingested_at, text, image_ref,
embedding_refs_json, layout_json, relations_json, trust_json,
token_equivalent, metadata_json
token_equivalent, metadata_json, holder_id, origin, scenario,
event_time, event_time_end, event_time_precision, event_time_source,
valid_from, valid_to, supersedes_json, visibility_json, retention_until
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(atom_id) DO UPDATE SET
modality=excluded.modality,
source=excluded.source,
time=excluded.time,
ingested_at=excluded.ingested_at,
text=excluded.text,
image_ref=excluded.image_ref,
embedding_refs_json=excluded.embedding_refs_json,
@@ -166,7 +109,19 @@ class Database:
relations_json=excluded.relations_json,
trust_json=excluded.trust_json,
token_equivalent=excluded.token_equivalent,
metadata_json=excluded.metadata_json
metadata_json=excluded.metadata_json,
holder_id=excluded.holder_id,
origin=excluded.origin,
scenario=excluded.scenario,
event_time=excluded.event_time,
event_time_end=excluded.event_time_end,
event_time_precision=excluded.event_time_precision,
event_time_source=excluded.event_time_source,
valid_from=excluded.valid_from,
valid_to=excluded.valid_to,
supersedes_json=excluded.supersedes_json,
visibility_json=excluded.visibility_json,
retention_until=excluded.retention_until
""",
[self._atom_params(atom) for atom in atoms],
)
@@ -182,7 +137,7 @@ class Database:
atom.source_id,
atom.modality.value,
atom.source,
atom.time.isoformat(),
atom.ingested_at.isoformat(),
atom.text,
atom.image_ref,
_json(atom.embedding_refs),
@@ -191,25 +146,63 @@ class Database:
_json(atom.trust.model_dump()),
atom.token_equivalent,
_json(atom.metadata),
atom.holder_id,
atom.origin.value,
atom.scenario,
atom.event_time.isoformat() if atom.event_time else None,
atom.event_time_end.isoformat() if atom.event_time_end else None,
atom.event_time_precision.value,
atom.event_time_source,
atom.valid_from.isoformat() if atom.valid_from else None,
atom.valid_to.isoformat() if atom.valid_to else None,
_json(atom.supersedes),
_json(atom.visibility.model_dump()),
atom.retention_until.isoformat() if atom.retention_until else None,
)
def list_atoms(self, limit: int | None = None) -> list[EvidenceAtom]:
query = "SELECT * FROM atoms ORDER BY time DESC"
params: tuple[Any, ...] = ()
if limit is not None:
query += " LIMIT ?"
params = (limit,)
with self.connect() as connection:
rows = connection.execute(query, params).fetchall()
if limit is None:
rows = connection.execute("SELECT * FROM atoms ORDER BY ingested_at DESC").fetchall()
else:
rows = connection.execute(
"SELECT * FROM atoms ORDER BY ingested_at DESC LIMIT ?", (limit,)
).fetchall()
return [self._row_to_atom(row) for row in rows]
def atoms_as_of(self, moment: datetime, holder: str | None = None) -> list[EvidenceAtom]:
"""Memories that were open at `moment`, the question a parameterless view cannot ask.
Same predicate as v_atoms_current minus the retention filter: retention says how long
the system keeps a memory, not whether it was true then. Unknown bounds read as open.
"""
stamp = moment.isoformat()
with self.connect() as connection:
if holder is None:
rows = connection.execute(
"SELECT * FROM atoms "
"WHERE (valid_from IS NULL OR valid_from <= ?) "
"AND (valid_to IS NULL OR valid_to > ?) "
"ORDER BY ingested_at DESC",
(stamp, stamp),
).fetchall()
else:
rows = connection.execute(
"SELECT * FROM atoms "
"WHERE holder_id = ? "
"AND (valid_from IS NULL OR valid_from <= ?) "
"AND (valid_to IS NULL OR valid_to > ?) "
"ORDER BY ingested_at DESC",
(holder, stamp, stamp),
).fetchall()
return [self._row_to_atom(row) for row in rows]
def get_atoms(self, atom_ids: list[str]) -> list[EvidenceAtom]:
if not atom_ids:
return []
placeholders = ",".join("?" for _ in atom_ids)
with self.connect() as connection:
rows = connection.execute(
f"SELECT * FROM atoms WHERE atom_id IN ({placeholders})", atom_ids
"SELECT * FROM atoms WHERE atom_id IN (SELECT value FROM json_each(?))", (_json(atom_ids),)
).fetchall()
by_id = {row["atom_id"]: self._row_to_atom(row) for row in rows}
return [by_id[atom_id] for atom_id in atom_ids if atom_id in by_id]
@@ -318,7 +311,7 @@ class Database:
VALUES (?, ?, ?, ?, ?, ?)
""",
(
datetime.utcnow().isoformat(),
utc_now().isoformat(),
layer,
cache_key,
event,
@@ -349,16 +342,18 @@ class Database:
)
def list_evidence_edges(self, limit: int | None = None) -> list[dict[str, Any]]:
query = (
"SELECT from_atom_id, to_atom_id, relation, confidence, metadata_json "
"FROM evidence_edges ORDER BY id DESC"
)
params: tuple[Any, ...] = ()
if limit is not None:
query += " LIMIT ?"
params = (limit,)
with self.connect() as connection:
rows = connection.execute(query, params).fetchall()
if limit is None:
rows = connection.execute(
"SELECT from_atom_id, to_atom_id, relation, confidence, metadata_json "
"FROM evidence_edges ORDER BY id DESC"
).fetchall()
else:
rows = connection.execute(
"SELECT from_atom_id, to_atom_id, relation, confidence, metadata_json "
"FROM evidence_edges ORDER BY id DESC LIMIT ?",
(limit,),
).fetchall()
return [
{
"from_atom_id": row["from_atom_id"],
@@ -387,7 +382,7 @@ class Database:
).fetchall()
def _row_to_atom(self, row: sqlite3.Row) -> EvidenceAtom:
from .schemas import LayoutBox, Modality, TrustScores
from .schemas import LayoutBox, Modality, TrustScores, Visibility
layout_data = _loads(row["layout_json"], None)
return EvidenceAtom(
@@ -395,7 +390,7 @@ class Database:
modality=Modality(row["modality"]),
source_id=row["source_id"],
source=row["source"],
time=datetime.fromisoformat(row["time"]),
ingested_at=datetime.fromisoformat(row["ingested_at"]),
text=row["text"],
image_ref=row["image_ref"],
embedding_refs=_loads(row["embedding_refs_json"], []),
@@ -404,6 +399,18 @@ class Database:
trust=TrustScores(**_loads(row["trust_json"], {"parser": 0.0})),
token_equivalent=int(row["token_equivalent"]),
metadata=_loads(row["metadata_json"], {}),
holder_id=row["holder_id"],
origin=MemoryOrigin(row["origin"]),
scenario=row["scenario"],
event_time=_datetime(row["event_time"]),
event_time_end=_datetime(row["event_time_end"]),
event_time_precision=TimePrecision(row["event_time_precision"]),
event_time_source=row["event_time_source"],
valid_from=_datetime(row["valid_from"]),
valid_to=_datetime(row["valid_to"]),
supersedes=_loads(row["supersedes_json"], []),
visibility=Visibility(**_loads(row["visibility_json"], {})),
retention_until=_datetime(row["retention_until"]),
)
def _row_to_source(self, row: sqlite3.Row) -> SourceRecord:
+36 -22
View File
@@ -25,6 +25,11 @@ from .cartridge_assets import CartridgeAssetService, merkle_root
from .compiler import ContextCompiler
from .config import Settings, get_settings
from .db import Database
from .evaluation_schemas import (
HashHopProof,
ProofCheck,
ProofSuiteReport,
)
from .evidence_graph import EvidenceGraphService
from .ingestion import SUPPORTED_CODE_EXTENSIONS, build_repository_context, find_git_root
from .profiles import runtime_profile_report
@@ -36,10 +41,7 @@ from .schemas import (
CompiledContext,
ContextPlan,
EvidenceAtom,
HashHopProof,
Modality,
ProofCheck,
ProofSuiteReport,
RetrievalHit,
RuntimeProfile,
TrainingAdapterKind,
@@ -56,7 +58,7 @@ from .training import (
sha256_file,
sha256_tree,
)
from .verifier import VisualContradictionLens, Verifier
from .verifier import Verifier, VisualContradictionLens
class HashHopEvaluator:
@@ -101,11 +103,14 @@ class HashHopEvaluator:
proof = self.generate_text_proof(hops, token_equivalent)
root = Path(proof.artifacts[0]).parent
chain_path = root / "hashhop_text.jsonl"
records = [
json.loads(line)
for line in chain_path.read_text(encoding="utf-8").splitlines()
if line.strip()
]
try:
chain_text = chain_path.read_text(encoding="utf-8")
except OSError as error:
raise ValueError(f"{chain_path}: the HashHop chain is unreadable ({error})") from error
try:
records = [json.loads(line) for line in chain_text.splitlines() if line.strip()]
except json.JSONDecodeError as error:
raise ValueError(f"{chain_path}: the HashHop chain holds a malformed line ({error})") from error
decoys = [
{"from": secrets.token_hex(16), "to": secrets.token_hex(16)}
for _ in range(max(4, hops * 2))
@@ -775,7 +780,7 @@ class PRDProofSuite:
f"{citation_report.citation_accuracy:.2f}"
),
)
except Exception as exc:
except Exception as exc: # noqa: BLE001 # a probe records its failure instead of crashing the proof run
payload["error"] = str(exc)
write_json(artifact, payload)
return ProofCheck(
@@ -876,7 +881,7 @@ class PRDProofSuite:
"last_segment": segments[-1] if segments else None,
}
)
except Exception as exc:
except Exception as exc: # noqa: BLE001 # a probe records its failure instead of crashing the proof run
payload["error"] = str(exc)
write_json(artifact, payload)
return ProofCheck(
@@ -903,7 +908,10 @@ class PRDProofSuite:
graph_path = cartridge_root / manifest.artifacts["temporal_graph"]
computed_dna = self.cartridges.compute_dna(cartridge_root)
atom_rows = parquet_row_count(atoms_path)
sources = json.loads(sources_path.read_text(encoding="utf-8")) if sources_path.exists() else []
try:
sources = json.loads(sources_path.read_text(encoding="utf-8")) if sources_path.exists() else []
except (OSError, json.JSONDecodeError) as error:
raise ValueError(f"{sources_path}: the cartridge source list is unreadable ({error})") from error
question = "Which Ely-Eye PRD requirements prove long context memory, HashHop, and adapters?"
compiler = ContextCompiler(self.settings, self.db)
replay_a = compiler.compile(question, RuntimeProfile.library_100m)
@@ -1531,7 +1539,7 @@ def peft_config_loads(path: Path) -> bool:
PeftConfig.from_pretrained(path)
return True
except Exception:
except Exception: # noqa: BLE001 # a probe records its failure instead of crashing the proof run
return False
@@ -1585,7 +1593,7 @@ def training_payload_contract_ok(payload: dict[str, Any], manifest: AdapterManif
and payload.get("precision") == TRAINING_PRECISION
and payload.get("framework") == TRAINING_FRAMEWORK
and payload.get("optimizer_family") == manifest.optimizer_family
and payload.get("gradient_checkpointing") is True
and bool(payload.get("gradient_checkpointing"))
and payload.get("gradient_checkpointing_mode") == GRADIENT_CHECKPOINTING_MODE
and payload.get("autocast_dtype") == AUTOCAST_DTYPE
and payload.get("unsloth_version") == manifest.unsloth_version
@@ -1876,10 +1884,13 @@ def read_memory_capsule(path: Path) -> dict[str, Any]:
prefix = b"ELYMEMCAP1\n"
if not data.startswith(prefix):
raise ValueError(f"Invalid memory capsule header: {path}")
payload = zstd.ZstdDecompressor().decompress(data[len(prefix) :])
parsed = json.loads(payload.decode("utf-8"))
try:
payload = zstd.ZstdDecompressor().decompress(data[len(prefix) :])
parsed = json.loads(payload.decode("utf-8"))
except (zstd.ZstdError, UnicodeDecodeError, json.JSONDecodeError) as error:
raise ValueError(f"Cannot decode the memory capsule at {path}: {error}") from error
if not isinstance(parsed, dict):
raise ValueError(f"Invalid memory capsule payload: {path}")
raise ValueError(f"Invalid memory capsule payload: {path}") # noqa: TRY004 # the capsule's own bytes are wrong, not the caller's argument
return parsed
@@ -1896,11 +1907,14 @@ def hashhop_chain_valid(proof: HashHopProof) -> bool:
chain_artifacts = [Path(path) for path in proof.artifacts if path.endswith(".jsonl")]
if len(chain_artifacts) != 1 or not chain_artifacts[0].exists():
return False
rows = [
json.loads(line)
for line in chain_artifacts[0].read_text(encoding="utf-8").splitlines()
if line.strip()
]
try:
rows = [
json.loads(line)
for line in chain_artifacts[0].read_text(encoding="utf-8").splitlines()
if line.strip()
]
except (OSError, json.JSONDecodeError):
return False # an unreadable chain is not a valid chain, and this is a predicate
if len(rows) != proof.hops:
return False
if rows[0]["from"] != proof.query_id or rows[-1]["to"] != proof.expected_target_id:
+122
View File
@@ -0,0 +1,122 @@
"""Report shapes for the evaluation and proof layer.
These describe what a run claims, not what the memory holds, so they live apart from the
memory and runtime schemas: a proof report changes when the evidence rules change, and the
memory model should not move with it.
"""
from __future__ import annotations
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
from .schemas import utc_now
class BenchmarkSpec(BaseModel):
name: str
target: str
dataset: str
task_type: str
runner: str
metric: str
class BenchmarkRegistryReport(BaseModel):
status: Literal["ready", "incomplete"]
created_at: datetime = Field(default_factory=utc_now)
benchmarks: list[BenchmarkSpec]
class BenchmarkSourceStatus(BaseModel):
name: str
dataset: str
status: Literal["ready", "unavailable"]
sha: str | None = None
gated: bool | str | None = None
private: bool | None = None
sibling_count: int = 0
error: str | None = None
class BenchmarkSourceReport(BaseModel):
status: Literal["ready", "incomplete"]
created_at: datetime = Field(default_factory=utc_now)
sources: list[BenchmarkSourceStatus]
class BenchmarkSampleStatus(BaseModel):
name: str
dataset: str
source_dataset: str | None = None
source_sha: str | None = None
source_note: str | None = None
status: Literal["loaded", "gated", "failed"]
config: str | None = None
split: str | None = None
sample_id: str | None = None
fields: list[str] = Field(default_factory=list)
question_preview: str | None = None
answer_preview: str | None = None
sample_sha256: str | None = None
media_artifacts: list[str] = Field(default_factory=list)
error: str | None = None
class BenchmarkSampleReport(BaseModel):
status: Literal["ready", "partial", "failed"]
created_at: datetime = Field(default_factory=utc_now)
samples: list[BenchmarkSampleStatus]
class BenchmarkPredictionStatus(BaseModel):
name: str
dataset: str
status: Literal["predicted", "gated", "failed"]
config: str | None = None
split: str | None = None
sample_id: str | None = None
task_type: str
metric: str
prompt_sha256: str | None = None
sample_sha256: str | None = None
prediction: str | None = None
answer: str | None = None
score: float | None = None
runtime_backend: str | None = None
adapter_id: str | None = None
adapter_sha256: str | None = None
input_tokens: int | None = None
output_tokens: int | None = None
media_artifacts: list[str] = Field(default_factory=list)
error: str | None = None
class BenchmarkPredictionReport(BaseModel):
status: Literal["ready", "partial", "failed"]
created_at: datetime = Field(default_factory=utc_now)
predictions: list[BenchmarkPredictionStatus]
class HashHopProof(BaseModel):
proof_id: str
kind: Literal["text_hashhop", "visual_hashhop"]
hops: int
token_equivalent: int
query_id: str
expected_target_id: str
model_target_id: str | None = None
passed: bool | None = None
artifacts: list[str]
created_at: datetime = Field(default_factory=utc_now)
class ProofCheck(BaseModel):
name: str
requirement: str
status: Literal["passed", "failed"]
evidence: list[str]
detail: str
class ProofSuiteReport(BaseModel):
proof_id: str
cartridge_id: str
status: Literal["passed", "failed"]
created_at: datetime = Field(default_factory=utc_now)
checks: list[ProofCheck]
artifacts: dict[str, str]
dna_before: str | None = None
dna_after: str | None = None
+183
View File
@@ -0,0 +1,183 @@
"""Schema DDL and versioned migrations for the memory store.
A memory row must answer four questions before retrieval can trust it: whose memory
is it, where did it come from, when was it true, and in what situation may it be used.
The first version of this table answered none of them, so every consumer guessed.
Versioning uses PRAGMA user_version. A database written by the first version is
upgraded in place rather than rebuilt, because the only copy of the memory is the file.
"""
from __future__ import annotations
import sqlite3
SCHEMA_VERSION = 2
ATOM_COLUMNS_V2 = """
atom_id TEXT PRIMARY KEY,
source_id TEXT NOT NULL REFERENCES sources(source_id) ON DELETE CASCADE,
modality TEXT NOT NULL,
source TEXT NOT NULL,
ingested_at TEXT NOT NULL,
text TEXT NOT NULL,
image_ref TEXT,
embedding_refs_json TEXT NOT NULL,
layout_json TEXT,
relations_json TEXT NOT NULL,
trust_json TEXT NOT NULL,
token_equivalent INTEGER NOT NULL,
metadata_json TEXT NOT NULL,
holder_id TEXT NOT NULL DEFAULT '',
origin TEXT NOT NULL DEFAULT 'artifact'
CHECK (origin IN ('self', 'observed', 'artifact', 'third_party')),
scenario TEXT NOT NULL DEFAULT '',
event_time TEXT,
event_time_end TEXT,
event_time_precision TEXT NOT NULL DEFAULT 'unknown'
CHECK (event_time_precision IN ('instant', 'minute', 'hour', 'day', 'month', 'year', 'unknown')),
event_time_source TEXT NOT NULL DEFAULT '',
valid_from TEXT,
valid_to TEXT,
supersedes_json TEXT NOT NULL DEFAULT '[]',
visibility_json TEXT NOT NULL DEFAULT '{}',
retention_until TEXT
"""
_BASE_TABLES = """
CREATE TABLE IF NOT EXISTS sources (
source_id TEXT PRIMARY KEY,
path TEXT NOT NULL,
kind TEXT NOT NULL,
mime TEXT NOT NULL,
sha256 TEXT NOT NULL,
size_bytes INTEGER NOT NULL,
created_at TEXT NOT NULL,
metadata_json TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS cartridges (
cartridge_id TEXT PRIMARY KEY,
name TEXT NOT NULL,
root_path TEXT NOT NULL,
manifest_json TEXT NOT NULL,
dna TEXT,
token_equivalent INTEGER NOT NULL,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS cache_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL,
layer TEXT NOT NULL,
cache_key TEXT NOT NULL,
event TEXT NOT NULL,
bytes_estimate INTEGER NOT NULL,
metadata_json TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS evidence_edges (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_atom_id TEXT NOT NULL,
to_atom_id TEXT NOT NULL,
relation TEXT NOT NULL,
confidence REAL NOT NULL,
metadata_json TEXT NOT NULL
);
CREATE VIRTUAL TABLE IF NOT EXISTS atom_fts USING fts5(
atom_id UNINDEXED,
source,
text,
tokenize = 'unicode61'
);
"""
# A memory is usable now when nothing has closed it, it had begun by now, and its
# retention has not lapsed. Unknown bounds read as visible: absence of evidence about
# when a legacy row was true is not evidence that it is false. The clock is formatted to
# match how Python writes an aware datetime, so the comparison is exact to the millisecond.
VIEWS = """
CREATE VIEW IF NOT EXISTS v_atoms_current AS
SELECT * FROM atoms
WHERE valid_to IS NULL
AND (valid_from IS NULL OR valid_from <= strftime('%Y-%m-%dT%H:%M:%f+00:00', 'now'))
AND (retention_until IS NULL OR retention_until >= strftime('%Y-%m-%dT%H:%M:%f+00:00', 'now'));
CREATE INDEX IF NOT EXISTS idx_atoms_source_id ON atoms(source_id);
CREATE INDEX IF NOT EXISTS idx_atoms_modality ON atoms(modality);
CREATE INDEX IF NOT EXISTS idx_atoms_holder ON atoms(holder_id);
CREATE INDEX IF NOT EXISTS idx_atoms_scenario ON atoms(scenario);
CREATE INDEX IF NOT EXISTS idx_atoms_valid_to ON atoms(valid_to);
"""
# Columns the first version lacked, in the order they are added. Every one is nullable
# or has a default, so an existing row survives the upgrade with its meaning intact.
_V1_TO_V2_COLUMNS = (
("holder_id", "TEXT NOT NULL DEFAULT ''"),
("origin", "TEXT NOT NULL DEFAULT 'artifact'"),
("scenario", "TEXT NOT NULL DEFAULT ''"),
("event_time", "TEXT"),
("event_time_end", "TEXT"),
("event_time_precision", "TEXT NOT NULL DEFAULT 'unknown'"),
("event_time_source", "TEXT NOT NULL DEFAULT ''"),
("valid_from", "TEXT"),
("valid_to", "TEXT"),
("supersedes_json", "TEXT NOT NULL DEFAULT '[]'"),
("visibility_json", "TEXT NOT NULL DEFAULT '{}'"),
("retention_until", "TEXT"),
)
def _table_exists(connection: sqlite3.Connection, name: str) -> bool:
row = connection.execute(
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?", (name,)
).fetchone()
return row is not None
def _column_names(connection: sqlite3.Connection, table: str) -> set[str]:
return {str(row[1]) for row in connection.execute(f"PRAGMA table_info({table})")}
def _create_v2(connection: sqlite3.Connection) -> None:
connection.executescript(_BASE_TABLES)
connection.execute(f"CREATE TABLE IF NOT EXISTS atoms ({ATOM_COLUMNS_V2})")
def _upgrade_v1_to_v2(connection: sqlite3.Connection) -> None:
"""Add the four memory dimensions to a first-version atoms table.
The old `time` column recorded when ingestion ran, which is the only timestamp a
legacy row honestly has, so it is renamed rather than duplicated.
"""
columns = _column_names(connection, "atoms")
if "time" in columns and "ingested_at" not in columns:
connection.execute("ALTER TABLE atoms RENAME COLUMN time TO ingested_at")
columns = _column_names(connection, "atoms")
if "ingested_at" not in columns:
raise SystemExit(
"atoms table has neither `time` nor `ingested_at`; refusing to guess which timestamp is ingestion"
)
for name, definition in _V1_TO_V2_COLUMNS:
if name not in columns:
connection.execute(f"ALTER TABLE atoms ADD COLUMN {name} {definition}")
def apply(connection: sqlite3.Connection) -> None:
"""Bring the schema to SCHEMA_VERSION, in place, without losing rows."""
version = int(connection.execute("PRAGMA user_version").fetchone()[0])
if version > SCHEMA_VERSION:
raise SystemExit(f"database schema is version {version}, this build understands {SCHEMA_VERSION}")
if version == SCHEMA_VERSION:
return
if _table_exists(connection, "atoms"):
connection.executescript(_BASE_TABLES)
if version < 2:
_upgrade_v1_to_v2(connection)
else:
_create_v2(connection)
connection.executescript(VIEWS)
connection.execute(f"PRAGMA user_version = {SCHEMA_VERSION}")
+55 -146
View File
@@ -1,16 +1,15 @@
from __future__ import annotations
from datetime import datetime, timezone
from datetime import UTC, datetime
from enum import StrEnum
from pathlib import Path
from typing import Any, Literal
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, model_validator
def utc_now() -> datetime:
return datetime.now(timezone.utc)
return datetime.now(UTC)
class RuntimeProfile(StrEnum):
live_demo = "live_demo"
@@ -18,7 +17,6 @@ class RuntimeProfile(StrEnum):
library_100m = "library_100m"
research_theater = "research_theater"
class TrainingAdapterKind(StrEnum):
hme_core_lora = "hme_core_lora"
hme_vision_lora = "hme_vision_lora"
@@ -27,7 +25,6 @@ class TrainingAdapterKind(StrEnum):
hme_router = "hme_router"
hme_retrieval = "hme_retrieval"
class Modality(StrEnum):
text = "text"
code = "code"
@@ -38,6 +35,36 @@ class Modality(StrEnum):
ui_screenshot = "ui_screenshot"
mixed = "mixed"
class MemoryOrigin(StrEnum):
"""Where a memory came from, which decides how much it may be trusted.
A file the user handed over is not the same kind of claim as something the system
watched happen, and neither is a statement the user made about themselves.
"""
self_ = "self"
observed = "observed"
artifact = "artifact"
third_party = "third_party"
class TimePrecision(StrEnum):
"""How finely the event time is actually known, so a day is never read as an instant."""
instant = "instant"
minute = "minute"
hour = "hour"
day = "day"
month = "month"
year = "year"
unknown = "unknown"
class Visibility(BaseModel):
"""Who may use a memory. Retention lives on the atom itself because the current
memory view has to filter on it."""
scope: Literal["holder", "visible_to", "everyone"] = "holder"
holders: list[str] = Field(default_factory=list)
purpose: str | None = None
class LayoutBox(BaseModel):
x: float
@@ -47,19 +74,17 @@ class LayoutBox(BaseModel):
page: int | None = None
frame_second: float | None = None
class TrustScores(BaseModel):
parser: float = Field(ge=0.0, le=1.0)
ocr: float = Field(default=0.0, ge=0.0, le=1.0)
model: float = Field(default=0.0, ge=0.0, le=1.0)
class EvidenceAtom(BaseModel):
atom_id: str
modality: Modality
source_id: str
source: str
time: datetime = Field(default_factory=utc_now)
ingested_at: datetime = Field(default_factory=utc_now)
text: str = ""
image_ref: str | None = None
embedding_refs: list[str] = Field(default_factory=list)
@@ -68,7 +93,28 @@ class EvidenceAtom(BaseModel):
trust: TrustScores
token_equivalent: int = Field(default=0, ge=0)
metadata: dict[str, Any] = Field(default_factory=dict)
holder_id: str = ""
origin: MemoryOrigin = MemoryOrigin.artifact
scenario: str = ""
event_time: datetime | None = None
event_time_end: datetime | None = None
event_time_precision: TimePrecision = TimePrecision.unknown
event_time_source: str = ""
valid_from: datetime | None = None
valid_to: datetime | None = None
supersedes: list[str] = Field(default_factory=list)
visibility: Visibility = Field(default_factory=Visibility)
retention_until: datetime | None = None
@model_validator(mode="after")
def _times_are_ordered(self) -> EvidenceAtom:
if self.event_time and self.event_time_end and self.event_time_end < self.event_time:
raise ValueError(f"{self.atom_id}: event_time_end precedes event_time")
if self.valid_from and self.valid_to and self.valid_to < self.valid_from:
raise ValueError(f"{self.atom_id}: valid_to precedes valid_from")
if self.atom_id in self.supersedes:
raise ValueError(f"{self.atom_id}: an atom cannot supersede itself")
return self
class SourceRecord(BaseModel):
source_id: str
@@ -80,7 +126,6 @@ class SourceRecord(BaseModel):
created_at: datetime = Field(default_factory=utc_now)
metadata: dict[str, Any] = Field(default_factory=dict)
class IngestResponse(BaseModel):
source_count: int
atom_count: int
@@ -88,7 +133,6 @@ class IngestResponse(BaseModel):
token_equivalent: int
sources: list[SourceRecord]
class RetrievalHit(BaseModel):
atom: EvidenceAtom
sparse_score: float
@@ -96,7 +140,6 @@ class RetrievalHit(BaseModel):
graph_score: float = 0.0
final_score: float
class ContextPlan(BaseModel):
question: str
profile: RuntimeProfile
@@ -107,7 +150,6 @@ class ContextPlan(BaseModel):
compression_strategy: str
verifier_contract: list[str]
class CompiledContext(BaseModel):
plan: ContextPlan
hits: list[RetrievalHit]
@@ -115,7 +157,6 @@ class CompiledContext(BaseModel):
token_equivalent: int
cache_trace_id: str
class CitationReport(BaseModel):
cited_atom_ids: list[str]
missing_atom_ids: list[str]
@@ -123,7 +164,6 @@ class CitationReport(BaseModel):
contradiction_notes: list[str]
confidence: float
class AnswerResponse(BaseModel):
answer: str
citations: list[str]
@@ -131,7 +171,6 @@ class AnswerResponse(BaseModel):
verifier: CitationReport
runtime_backend: str
class CacheLayerStatus(BaseModel):
layer: str
entries: int
@@ -140,7 +179,6 @@ class CacheLayerStatus(BaseModel):
miss_count: int
last_event_at: datetime | None
class MemoryStatus(BaseModel):
active_atoms: int
active_token_equivalent: int
@@ -148,7 +186,6 @@ class MemoryStatus(BaseModel):
library_token_equivalent: int
layers: list[CacheLayerStatus]
class RuntimeStatus(BaseModel):
backend: str
model_id: str
@@ -163,7 +200,6 @@ class RuntimeStatus(BaseModel):
vram_total_mb: int | None = None
vram_used_mb: int | None = None
class SystemStatus(BaseModel):
app: str = "Ely-Eye"
version: str
@@ -172,7 +208,6 @@ class SystemStatus(BaseModel):
profiles: dict[str, dict[str, Any]]
data_home: str
class CartridgeManifest(BaseModel):
cartridge_id: str
name: str
@@ -184,7 +219,6 @@ class CartridgeManifest(BaseModel):
artifacts: dict[str, str]
dna: str | None = None
class AdapterManifest(BaseModel):
adapter_id: str
kind: TrainingAdapterKind
@@ -248,7 +282,6 @@ class AdapterManifest(BaseModel):
sha256: str
cartridge_id: str | None = None
class AdapterStatus(BaseModel):
name: str
kind: TrainingAdapterKind
@@ -299,7 +332,6 @@ class AdapterStatus(BaseModel):
adapter_max_abs: float | None = None
adapter_weights_finite: bool = False
class CartridgeAssetStatus(BaseModel):
name: str
path: str
@@ -309,99 +341,12 @@ class CartridgeAssetStatus(BaseModel):
dtype: str
status: Literal["ready", "missing"]
class CartridgeAssetReport(BaseModel):
cartridge_id: str
status: Literal["ready", "incomplete"]
created_at: datetime = Field(default_factory=utc_now)
assets: list[CartridgeAssetStatus]
class BenchmarkSpec(BaseModel):
name: str
target: str
dataset: str
task_type: str
runner: str
metric: str
class BenchmarkRegistryReport(BaseModel):
status: Literal["ready", "incomplete"]
created_at: datetime = Field(default_factory=utc_now)
benchmarks: list[BenchmarkSpec]
class BenchmarkSourceStatus(BaseModel):
name: str
dataset: str
status: Literal["ready", "unavailable"]
sha: str | None = None
gated: bool | str | None = None
private: bool | None = None
sibling_count: int = 0
error: str | None = None
class BenchmarkSourceReport(BaseModel):
status: Literal["ready", "incomplete"]
created_at: datetime = Field(default_factory=utc_now)
sources: list[BenchmarkSourceStatus]
class BenchmarkSampleStatus(BaseModel):
name: str
dataset: str
source_dataset: str | None = None
source_sha: str | None = None
source_note: str | None = None
status: Literal["loaded", "gated", "failed"]
config: str | None = None
split: str | None = None
sample_id: str | None = None
fields: list[str] = Field(default_factory=list)
question_preview: str | None = None
answer_preview: str | None = None
sample_sha256: str | None = None
media_artifacts: list[str] = Field(default_factory=list)
error: str | None = None
class BenchmarkSampleReport(BaseModel):
status: Literal["ready", "partial", "failed"]
created_at: datetime = Field(default_factory=utc_now)
samples: list[BenchmarkSampleStatus]
class BenchmarkPredictionStatus(BaseModel):
name: str
dataset: str
status: Literal["predicted", "gated", "failed"]
config: str | None = None
split: str | None = None
sample_id: str | None = None
task_type: str
metric: str
prompt_sha256: str | None = None
sample_sha256: str | None = None
prediction: str | None = None
answer: str | None = None
score: float | None = None
runtime_backend: str | None = None
adapter_id: str | None = None
adapter_sha256: str | None = None
input_tokens: int | None = None
output_tokens: int | None = None
media_artifacts: list[str] = Field(default_factory=list)
error: str | None = None
class BenchmarkPredictionReport(BaseModel):
status: Literal["ready", "partial", "failed"]
created_at: datetime = Field(default_factory=utc_now)
predictions: list[BenchmarkPredictionStatus]
class RuntimeLaunchProfile(BaseModel):
name: str
runtime: str
@@ -412,7 +357,6 @@ class RuntimeLaunchProfile(BaseModel):
environment: dict[str, str] = Field(default_factory=dict)
required_features: list[str] = Field(default_factory=list)
class RuntimeProfileValidation(BaseModel):
name: str
runtime: str
@@ -428,51 +372,16 @@ class RuntimeProfileValidation(BaseModel):
probe_output: str | None = None
detail: str
class RuntimeProfileReport(BaseModel):
status: Literal["ready", "incomplete"]
created_at: datetime = Field(default_factory=utc_now)
profiles: list[RuntimeLaunchProfile]
validations: list[RuntimeProfileValidation] = Field(default_factory=list)
class HashHopProof(BaseModel):
proof_id: str
kind: Literal["text_hashhop", "visual_hashhop"]
hops: int
token_equivalent: int
query_id: str
expected_target_id: str
model_target_id: str | None = None
passed: bool | None = None
artifacts: list[str]
created_at: datetime = Field(default_factory=utc_now)
class ProofCheck(BaseModel):
name: str
requirement: str
status: Literal["passed", "failed"]
evidence: list[str]
detail: str
class ProofSuiteReport(BaseModel):
proof_id: str
cartridge_id: str
status: Literal["passed", "failed"]
created_at: datetime = Field(default_factory=utc_now)
checks: list[ProofCheck]
artifacts: dict[str, str]
dna_before: str | None = None
dna_after: str | None = None
class UploadIngestRequest(BaseModel):
cartridge_name: str | None = None
dense_embeddings: bool | None = None
class PathIngestRequest(BaseModel):
path: Path
cartridge_name: str | None = None
+224
View File
@@ -0,0 +1,224 @@
"""The memory model must carry holder, origin, time and scenario, and an old database must survive."""
from __future__ import annotations
import sqlite3
from datetime import UTC, datetime, timedelta
from pathlib import Path
import pytest
from ely_eye.config import Settings
from ely_eye.db import Database
from ely_eye.migrations import SCHEMA_VERSION
from ely_eye.schemas import (
EvidenceAtom,
MemoryOrigin,
Modality,
SourceRecord,
TimePrecision,
TrustScores,
Visibility,
)
NOW = datetime(2026, 5, 1, 12, 0, tzinfo=UTC)
def _atom(atom_id: str, **overrides: object) -> EvidenceAtom:
fields: dict[str, object] = {
"atom_id": atom_id,
"modality": Modality.text,
"source_id": "src-1",
"source": "notes/key.txt",
"ingested_at": NOW,
"text": "钥匙放在玄关抽屉里",
"trust": TrustScores(parser=1.0),
"token_equivalent": 8,
"holder_id": "wife",
"origin": MemoryOrigin.self_,
"scenario": "home",
"event_time": NOW - timedelta(days=1),
"event_time_precision": TimePrecision.day,
"event_time_source": "declared",
"valid_from": NOW - timedelta(days=1),
}
fields.update(overrides)
return EvidenceAtom(**fields) # type: ignore[arg-type]
def _database(home: Path) -> Database:
return Database(Settings(home=home))
def _seed_source(database: Database) -> None:
database.upsert_sources(
[
SourceRecord(
source_id="src-1",
path="/tmp/key.txt",
kind="text",
mime="text/plain",
sha256="0" * 64,
size_bytes=12,
created_at=NOW,
)
]
)
def _atom_columns(database: Database) -> set[str]:
with database.connect() as connection:
return {row[1] for row in connection.execute("PRAGMA table_info(atoms)")}
def test_a_fresh_database_carries_the_four_memory_dimensions(tmp_path: Path) -> None:
database = _database(tmp_path)
columns = _atom_columns(database)
for column in (
"holder_id",
"origin",
"scenario",
"event_time",
"event_time_end",
"event_time_precision",
"event_time_source",
"valid_from",
"valid_to",
"supersedes_json",
"visibility_json",
"retention_until",
"ingested_at",
):
assert column in columns, column
assert "time" not in columns, "ingestion time has one name: ingested_at"
with database.connect() as connection:
assert int(connection.execute("PRAGMA user_version").fetchone()[0]) == SCHEMA_VERSION
def test_an_atom_round_trips_every_memory_dimension(tmp_path: Path) -> None:
database = _database(tmp_path)
_seed_source(database)
atom = _atom(
"atom-1",
event_time_end=NOW - timedelta(hours=6),
valid_to=NOW + timedelta(days=2),
supersedes=["atom-0"],
visibility=Visibility(scope="visible_to", holders=["husband"], purpose="household"),
retention_until=NOW + timedelta(days=30),
)
database.upsert_atoms([atom])
stored = database.get_atoms(["atom-1"])[0]
assert stored.holder_id == "wife"
assert stored.origin is MemoryOrigin.self_
assert stored.scenario == "home"
assert stored.event_time == atom.event_time
assert stored.event_time_end == atom.event_time_end
assert stored.event_time_precision is TimePrecision.day
assert stored.event_time_source == "declared"
assert stored.valid_from == atom.valid_from
assert stored.valid_to == atom.valid_to
assert stored.supersedes == ["atom-0"]
assert stored.visibility.holders == ["husband"]
assert stored.visibility.purpose == "household"
assert stored.retention_until == atom.retention_until
assert stored.ingested_at == NOW
def test_a_first_version_database_upgrades_in_place_and_keeps_its_rows(tmp_path: Path) -> None:
path = tmp_path / "data" / "ely_eye.sqlite"
path.parent.mkdir(parents=True)
connection = sqlite3.connect(path)
connection.executescript(
"""
CREATE TABLE sources (
source_id TEXT PRIMARY KEY, path TEXT NOT NULL, kind TEXT NOT NULL, mime TEXT NOT NULL,
sha256 TEXT NOT NULL, size_bytes INTEGER NOT NULL, created_at TEXT NOT NULL,
metadata_json TEXT NOT NULL
);
CREATE TABLE atoms (
atom_id TEXT PRIMARY KEY, source_id TEXT NOT NULL, modality TEXT NOT NULL,
source TEXT NOT NULL, time TEXT NOT NULL, text TEXT NOT NULL, image_ref TEXT,
embedding_refs_json TEXT NOT NULL, layout_json TEXT, relations_json TEXT NOT NULL,
trust_json TEXT NOT NULL, token_equivalent INTEGER NOT NULL, metadata_json TEXT NOT NULL
);
INSERT INTO atoms VALUES (
'legacy-1', 'src-1', 'text', 'old.txt', '2026-01-02T03:04:05+00:00', 'old memory',
NULL, '[]', NULL, '[]', '{"parser": 1.0}', 4, '{}'
);
"""
)
connection.commit()
connection.close()
database = _database(tmp_path)
assert _atom_columns(database) >= {"holder_id", "origin", "valid_from", "ingested_at"}
legacy = database.get_atoms(["legacy-1"])[0]
assert legacy.text == "old memory"
assert legacy.ingested_at == datetime.fromisoformat("2026-01-02T03:04:05+00:00")
assert legacy.holder_id == "", "a legacy row has no known holder, and must not invent one"
assert legacy.origin is MemoryOrigin.artifact
def test_v_atoms_current_hides_closed_retired_and_unbegun_memories(tmp_path: Path) -> None:
"""The view answers 'what is usable right now', so it is written against the real clock."""
moment = datetime.now(UTC)
database = _database(tmp_path)
_seed_source(database)
database.upsert_atoms(
[
_atom("live"),
_atom("closed", valid_to=moment - timedelta(days=1)),
_atom("lapsed", retention_until=moment - timedelta(days=1)),
_atom("future", valid_from=moment + timedelta(days=5)),
]
)
with database.connect() as connection:
visible = {row["atom_id"] for row in connection.execute("SELECT atom_id FROM v_atoms_current")}
assert visible == {"live"}
assert database.atom_count() == 4, "the view hides, it does not delete"
def test_an_unknown_origin_is_refused_by_the_database(tmp_path: Path) -> None:
database = _database(tmp_path)
_seed_source(database)
with database.connect() as connection, pytest.raises(sqlite3.IntegrityError):
connection.execute(
"INSERT INTO atoms (atom_id, source_id, modality, source, ingested_at, text, "
"embedding_refs_json, relations_json, trust_json, token_equivalent, metadata_json, origin) "
"VALUES ('x', 'src-1', 'text', 's', '2026-05-01T12:00:00+00:00', 't', '[]', '[]', "
"'{\"parser\": 1.0}', 1, '{}', 'whispered')"
)
def test_the_schema_refuses_a_database_newer_than_this_build(tmp_path: Path) -> None:
database = _database(tmp_path)
with database.connect() as connection:
connection.execute("PRAGMA user_version = 99")
with pytest.raises(SystemExit, match="understands"):
_database(tmp_path)
def test_times_must_be_ordered_and_an_atom_cannot_supersede_itself() -> None:
with pytest.raises(ValueError, match="valid_to precedes valid_from"):
_atom("bad-1", valid_from=NOW, valid_to=NOW - timedelta(days=1))
with pytest.raises(ValueError, match="event_time_end precedes event_time"):
_atom("bad-2", event_time=NOW, event_time_end=NOW - timedelta(days=1))
with pytest.raises(ValueError, match="cannot supersede itself"):
_atom("bad-3", supersedes=["bad-3"])
def test_atoms_as_of_walks_the_validity_timeline(tmp_path: Path) -> None:
"""Time travel is a method because a SQLite view cannot take a parameter."""
database = _database(tmp_path)
_seed_source(database)
database.upsert_atoms(
[
_atom("live"),
_atom("closed", valid_from=NOW - timedelta(days=10), valid_to=NOW - timedelta(days=1)),
_atom("future", valid_from=NOW + timedelta(days=5)),
]
)
assert {atom.atom_id for atom in database.atoms_as_of(NOW)} == {"live"}
assert {atom.atom_id for atom in database.atoms_as_of(NOW - timedelta(days=2))} == {"closed"}
assert database.atoms_as_of(NOW, holder="nobody") == []
assert {atom.atom_id for atom in database.atoms_as_of(NOW, holder="wife")} == {"live"}