pbuffer 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,317 @@
1
+ """patternbuffer: an append-only world-state substrate.
2
+
3
+ One append-only log of perspective-scoped, time-indexed assertions per
4
+ world; every other structure — current state, space, knowledge, history,
5
+ the rendered world — is a disposable projection over it.
6
+
7
+ The engine is host-blind: its single outside dependency is an injected
8
+ model callable ``(prompt, schema) -> json``. See docs/WHITEPAPER.md.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import time
14
+ from pathlib import Path
15
+ from typing import Any, Callable
16
+
17
+ from patternbuffer.buffer import PatternBuffer
18
+ from patternbuffer.classify import Classifier
19
+ from patternbuffer.identity import IdentityRegistry
20
+ from patternbuffer.indexes import Indexes
21
+ from patternbuffer.ingest import Ingestor
22
+ from patternbuffer.model import ATTR_PREFIX, CANON, META_ATTRIBUTES
23
+ from patternbuffer.project import Materialization, Projector
24
+ from patternbuffer.refer import Refer, Resolution
25
+ from patternbuffer.roles import _make_engine_roles
26
+ from patternbuffer.salience import SalienceIndex
27
+ from patternbuffer.semantics import AttributeSemantics
28
+ from patternbuffer.thunks import (
29
+ INVENT_UNDER_CANON,
30
+ OBSERVE_OR_UNKNOWN,
31
+ POLICIES,
32
+ Resolver,
33
+ )
34
+ from patternbuffer.tmaint import TruthMaintenance
35
+
36
+ __version__ = "0.0.1"
37
+
38
+ STANCES = frozenset({"fiction", "reality", "hypothetical"}) # letter 026; fixed enum
39
+ __all__ = ["World", "Materialization", "Resolution", "__version__"]
40
+
41
+
42
+ class World:
43
+ """The named, individual unit: one PatternBuffer + its physics
44
+ (resolution policy, decay configuration) + derived indexes (§16).
45
+
46
+ ``World("campaign.world", world_id="w:campaign", model=callable)`` is
47
+ the entire harness interface — a parameter, not a framework. Role
48
+ capabilities are minted here and nowhere else.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ path: str | Path,
54
+ world_id: str,
55
+ model: Callable[[str, dict], Any] | None = None,
56
+ policy: str = INVENT_UNDER_CANON,
57
+ clock: Callable[[], float] = time.time,
58
+ stance: str = "fiction",
59
+ title: str = "",
60
+ description: str = "",
61
+ attribute_default: Callable[[str], dict | None] | None = None,
62
+ ) -> None:
63
+ if policy not in POLICIES:
64
+ raise ValueError(f"unknown policy {policy!r}")
65
+ if stance not in STANCES:
66
+ raise ValueError(f"unknown stance {stance!r} (fixed enum; letter 026)")
67
+ self.world_id = world_id
68
+ self.policy = policy
69
+ self.buffer = PatternBuffer(path, world_id)
70
+ self.semantics = AttributeSemantics(self.buffer)
71
+ roles = _make_engine_roles()
72
+ self.classifier = Classifier(self.buffer, model or _no_model, self.semantics)
73
+ self.registry = IdentityRegistry(self.buffer, roles["ingestor"], self.semantics)
74
+ self.indexes = Indexes(
75
+ self.buffer, self.classifier, self.registry.resolve, self.semantics
76
+ )
77
+ self.indexes.set_closure_provider(self.registry.closure)
78
+ self.indexes.set_correlation_provider(self.registry.correlation_set)
79
+ self.registry.set_kind_provider(lambda e: self.indexes.fold_key(e, "kind"))
80
+ self.salience_index = SalienceIndex(self.buffer, self.classifier, self.indexes)
81
+ self.indexes.set_salience_provider(self.salience_index.salience)
82
+ self.truth = TruthMaintenance(
83
+ self.buffer, self.classifier, self.indexes, roles["truth_maintenance"],
84
+ self.semantics,
85
+ )
86
+ # SHAPE-FIX-V1 retype wiring: retraction authority + rules-mode
87
+ # durability for the corrected kind row (the set_kind_provider idiom —
88
+ # truth/classifier post-date the registry).
89
+ self.registry.set_retract_provider(self.truth.retract)
90
+ self.registry.set_classify_provider(
91
+ lambda rows: self.classifier.classify_rows(rows, model=False)
92
+ )
93
+ # Win 4 (durable-contradiction veto, HD 089): the registry reads
94
+ # standing folds + durability verdicts through late-bound lookups.
95
+ self.registry.set_fold_provider(self.indexes.fold_key)
96
+ self.registry.set_durability_provider(self.classifier.durability)
97
+ self.resolver = Resolver(
98
+ self.buffer, self.classifier, self.indexes, roles["resolver"],
99
+ model or _no_model, policy,
100
+ )
101
+ self.projector = Projector(
102
+ self.buffer, self.classifier, self.indexes, self.semantics,
103
+ self.salience_index.salience,
104
+ )
105
+ self.ingestor = Ingestor(
106
+ self.buffer, self.classifier, self.registry, roles["ingestor"],
107
+ model, observe_mode=(policy == OBSERVE_OR_UNKNOWN), clock=clock,
108
+ resolver_role=roles["resolver"],
109
+ semantics=self.semantics,
110
+ attribute_default=attribute_default,
111
+ # Write-time containment-cycle gate (HD 002): the ancestor walk
112
+ # is the derived containment chain — locate folds the family.
113
+ containment_ancestors=lambda parent, frame, vf: set(
114
+ self.indexes.locate(parent, frame=frame, valid_as_of=vf)
115
+ ),
116
+ )
117
+ self.refer = Refer(self.buffer, self.indexes, self.registry, model,
118
+ ingestor=self.ingestor)
119
+ # The World Charter (letter 026): a fresh world's genesis write
120
+ # asserts its self-description — stance is ontological stored
121
+ # truth (does this world claim to describe reality?), distinct
122
+ # from operational policy. Ordinary appends; amendable forever.
123
+ if self.buffer.head() == 0:
124
+ charter = [
125
+ {"entity": "world:self", "attribute": "kind", "value": "world",
126
+ "timeless": True},
127
+ {"entity": "world:self", "attribute": "stance", "value": stance,
128
+ "timeless": True},
129
+ ]
130
+ if title:
131
+ charter.append({"entity": "world:self", "attribute": "title",
132
+ "value": title, "timeless": True})
133
+ if description:
134
+ charter.append({"entity": "world:self", "attribute": "description",
135
+ "value": description, "timeless": True})
136
+ self.ingestor.classify_inline = False
137
+ self.ingest_structured(charter)
138
+ self.ingestor.classify_inline = True
139
+ for row in self.buffer.all_rows():
140
+ self.classifier.set(row.id, "CONSTITUTIVE", 1.0)
141
+
142
+ # Reads (deterministic, no LLM — P7).
143
+
144
+ def locate(self, entity: str, **kw) -> list[str]:
145
+ return self.indexes.locate(entity, **kw)
146
+
147
+ def contents(self, container: str, **kw) -> list[str]:
148
+ return self.indexes.contents(container, **kw)
149
+
150
+ # PLACE-FEATURE-ABSTRACTION-V1 — the compositional axis (part_of).
151
+ def composition(self, entity: str, **kw) -> list[str]:
152
+ return self.indexes.composition(entity, **kw)
153
+
154
+ def features(self, place: str, **kw) -> list[str]:
155
+ return self.indexes.features(place, **kw)
156
+
157
+ # WHO-KNOWS-INVERSE-V1 — the computed "who knows X" read.
158
+ def who_knows(self, entity: str, attribute: str, value: Any = None, **kw) -> list[str]:
159
+ return self.indexes.who_knows(entity, attribute, value, **kw)
160
+
161
+ def state(self, entity: str, attribute: str, frame: str = CANON, **kw):
162
+ return self.indexes.fold_key(entity, attribute, frame, **kw)
163
+
164
+ # AKA-CORRELATION-V1 — the explicit, opt-in correlated identity surface.
165
+ def state_union(self, entity: str, attribute: str, frame: str = CANON, **kw):
166
+ return self.indexes.state_union(entity, attribute, frame, **kw)
167
+
168
+ def correlations(self, entity: str, **kw) -> list[str]:
169
+ return self.registry.correlations(entity, **kw)
170
+
171
+ def correlate(self, a: str, b: str, evidence: str, **kw) -> dict:
172
+ return self.registry.correlate(a, b, evidence, **kw)
173
+
174
+ def correlation_conflicts(self, **kw) -> list[dict]:
175
+ return self.registry.correlation_conflicts(**kw)
176
+
177
+ def path(self, a: str, b: str, **kw) -> list[str] | None:
178
+ return self.indexes.path(a, b, **kw)
179
+
180
+ def route(self, a: str, b: str, **kw) -> dict:
181
+ return self.indexes.route(a, b, **kw)
182
+
183
+ def salience(
184
+ self, entity: str, frame: str = CANON, as_of: float | None = None
185
+ ) -> float:
186
+ return self.salience_index.salience(entity, frame, as_of)
187
+
188
+ def confidence(
189
+ self,
190
+ entity: str,
191
+ attribute: str,
192
+ frame: str | list[str] = CANON,
193
+ as_of: float | None = None,
194
+ asserted_as_of: int | None = None,
195
+ ) -> dict:
196
+ return self.indexes.confidence(
197
+ entity, attribute, frame=frame, as_of=as_of,
198
+ asserted_as_of=asserted_as_of,
199
+ )
200
+
201
+ def neighborhood(self, entity: str, **kw) -> dict:
202
+ return self.indexes.neighborhood(entity, **kw)
203
+
204
+ def aggregate(self, container: str, member_attribute: str, op: str, **kw) -> dict:
205
+ return self.indexes.aggregate(container, member_attribute, op, **kw)
206
+
207
+ def fidelity_audit(self, frame: str = CANON, as_of: float | None = None) -> dict:
208
+ """Structural ingestion-fidelity gaps, derived (INGESTION-FIDELITY-V1).
209
+ Read-only; run after a build's classification + `truth.scan()` (it never
210
+ classifies or scans). The engine surfaces gaps; the host joins arc/cast
211
+ severity and drives targeted re-extraction."""
212
+ from patternbuffer.classify import EVENT, STATE
213
+
214
+ collisions = self.registry.name_collisions(frame=frame, valid_as_of=as_of)
215
+
216
+ unstamped: list[dict] = []
217
+ entities: set[str] = set()
218
+ for row in self.buffer.visible(frame=frame):
219
+ if row.entity.startswith("a:") or row.entity.startswith(ATTR_PREFIX):
220
+ continue
221
+ eid = self.registry.resolve(row.entity)
222
+ if eid.startswith(("obj:", "person:")):
223
+ entities.add(eid)
224
+ # Meta/identity edges (same_as/distinct_from/aka/source…) are
225
+ # classified EVENT and carry no valid_from BY DESIGN — bookkeeping,
226
+ # never a spine fact; they are not an unstamped gap.
227
+ if row.attribute in META_ATTRIBUTES:
228
+ continue
229
+ if row.valid_from is None and self.classifier.get(row.id) is not None \
230
+ and self.classifier.durability(row.id) in (STATE, EVENT):
231
+ unstamped.append({"entity": eid, "attribute": row.attribute,
232
+ "assertion_id": row.id})
233
+
234
+ orphans = sorted(
235
+ e for e in entities
236
+ if not self.indexes.locate(e, frame=frame, valid_as_of=as_of)
237
+ )
238
+
239
+ conflicts = [
240
+ {"entity": c.entity, "attribute": c.attribute, "frame": c.frame,
241
+ "kind": c.kind, "assertion_ids": list(c.assertion_ids)}
242
+ for c in self.truth.open_conflicts()
243
+ ]
244
+
245
+ live = sum(1 for g in collisions if g["live"])
246
+ return {
247
+ "frame": frame,
248
+ "name_collisions": collisions,
249
+ "unstamped_timed": unstamped,
250
+ "orphan_entities": orphans,
251
+ "open_conflicts": conflicts,
252
+ "summary": {
253
+ "name_collisions": live, # live-fragmentation groups only
254
+ "name_collisions_total": len(collisions),
255
+ "unstamped_timed": len(unstamped),
256
+ "orphan_entities": len(orphans),
257
+ "open_conflicts": len(conflicts),
258
+ },
259
+ }
260
+
261
+ def materialize(self, scope, **kw) -> Materialization:
262
+ return self.projector.materialize(scope, **kw)
263
+
264
+ # Writes (each behind its role).
265
+
266
+ def ingest(self, text: str, context: str = "", frame: str | None = None,
267
+ classify: str = "inline", extract: str = "full",
268
+ cursor_authoritative: bool = False, pov: str | None = None) -> list:
269
+ return self.ingestor.ingest(text, context, frame=frame, classify=classify,
270
+ extract=extract,
271
+ cursor_authoritative=cursor_authoritative,
272
+ pov=pov)
273
+
274
+ def extract(self, text: str, context: str = "", extract: str = "full",
275
+ pov: str | None = None) -> list:
276
+ """Read-only extraction (INGEST-LATENCY-V2): the host parallelizes these,
277
+ then ingest_structured()s the results serially. No write. `pov`
278
+ (SHAPE-FIX-V1 4c): the viewpoint entity id for deixis binding."""
279
+ return self.ingestor.extract(text, context, extract=extract, pov=pov)
280
+
281
+ def ingest_structured(self, items: list[dict], frame: str | None = None,
282
+ classify: str = "inline",
283
+ cursor_authoritative: bool = False) -> list:
284
+ return self.ingestor.ingest_structured(
285
+ items, frame=frame, classify=classify,
286
+ cursor_authoritative=cursor_authoritative)
287
+
288
+ def resolve(self, entity: str, aspect: str, frame: str = CANON, access=None):
289
+ return self.resolver.resolve(entity, aspect, frame, access)
290
+
291
+ @property
292
+ def porcelain(self):
293
+ """The frozen host surface (PORCELAIN-V1). Lazy; import-local to
294
+ avoid circularity."""
295
+ if not hasattr(self, "_porcelain"):
296
+ from patternbuffer.porcelain import Porcelain
297
+ self._porcelain = Porcelain(self)
298
+ return self._porcelain
299
+
300
+ def charter(self) -> dict[str, Any]:
301
+ """The world's self-description, read from world:self (letter 026)."""
302
+ out: dict[str, Any] = {}
303
+ for attr, result in self.indexes.current_state("world:self").items():
304
+ if result.winner is not None:
305
+ out[attr] = result.winner.value
306
+ return out
307
+
308
+ def close(self) -> None:
309
+ # An open build session never outlives the buffer: abort (restore the
310
+ # classify toggle, classify nothing) before closing (BUILD-SESSION-V1).
311
+ if hasattr(self, "_porcelain"):
312
+ self._porcelain.abort_build()
313
+ self.buffer.close()
314
+
315
+
316
+ def _no_model(prompt: str, schema: dict) -> Any:
317
+ raise RuntimeError("no model callable injected for this operation")
@@ -0,0 +1,312 @@
1
+ """PatternBuffer: the append-only assertion log — the only truth (P1).
2
+
3
+ One SQLite file per world, and ``world_id`` stamped on every row as a
4
+ cross-wiring guard (the 1:1 invariant, spec §3.1/§4). Append-only is
5
+ enforced twice: the Python surface exposes no update/delete, and SQL
6
+ triggers raise on UPDATE/DELETE as belt-and-braces against future code.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import logging
13
+ import sqlite3
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ from patternbuffer.codec import decode_hook, json_default
18
+ from patternbuffer.model import (
19
+ ATTR_PREFIX,
20
+ CANON,
21
+ INVIOLABLE_CORE,
22
+ SEMANTICS_PREDICATES,
23
+ STATUSES,
24
+ VALUE_TYPES,
25
+ Assertion,
26
+ )
27
+ from patternbuffer.roles import WriterRole
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ _SCHEMA = """
32
+ CREATE TABLE IF NOT EXISTS world_meta (
33
+ key TEXT PRIMARY KEY,
34
+ value TEXT NOT NULL
35
+ );
36
+ CREATE TABLE IF NOT EXISTS assertions (
37
+ seq INTEGER PRIMARY KEY,
38
+ id TEXT UNIQUE NOT NULL,
39
+ world_id TEXT NOT NULL,
40
+ entity TEXT NOT NULL,
41
+ attribute TEXT NOT NULL,
42
+ value_type TEXT NOT NULL,
43
+ value TEXT NOT NULL,
44
+ valid_from REAL,
45
+ valid_to REAL,
46
+ frame TEXT NOT NULL DEFAULT 'canon',
47
+ status TEXT NOT NULL,
48
+ confidence REAL,
49
+ asserted_at INTEGER NOT NULL
50
+ );
51
+ CREATE INDEX IF NOT EXISTS ix_assertions_key
52
+ ON assertions (entity, attribute, frame);
53
+ CREATE INDEX IF NOT EXISTS ix_assertions_attr_value
54
+ ON assertions (attribute, value);
55
+ CREATE INDEX IF NOT EXISTS ix_assertions_value
56
+ ON assertions (value);
57
+ CREATE INDEX IF NOT EXISTS ix_assertions_frame
58
+ ON assertions (frame, attribute);
59
+ CREATE TRIGGER IF NOT EXISTS assertions_append_only_update
60
+ BEFORE UPDATE ON assertions
61
+ BEGIN SELECT RAISE(ABORT, 'append-only: UPDATE forbidden'); END;
62
+ CREATE TRIGGER IF NOT EXISTS assertions_append_only_delete
63
+ BEFORE DELETE ON assertions
64
+ BEGIN SELECT RAISE(ABORT, 'append-only: DELETE forbidden'); END;
65
+ """
66
+
67
+
68
+ class WorldMismatch(ValueError):
69
+ """A buffer was opened or written with the wrong world_id (1:1 invariant)."""
70
+
71
+
72
+ class PatternBuffer:
73
+ """The append-only store. Holds exactly one world's log, forever."""
74
+
75
+ def __init__(self, path: str | Path, world_id: str) -> None:
76
+ self.path = Path(path)
77
+ self._conn = sqlite3.connect(self.path)
78
+ self._conn.executescript(_SCHEMA)
79
+ existing = self._meta("world_id")
80
+ if existing is None:
81
+ self._conn.execute(
82
+ "INSERT INTO world_meta (key, value) VALUES ('world_id', ?)", (world_id,)
83
+ )
84
+ self._conn.commit()
85
+ elif existing != world_id:
86
+ self._conn.close()
87
+ raise WorldMismatch(
88
+ f"buffer at {self.path} belongs to world {existing!r}, not {world_id!r}"
89
+ )
90
+ self.world_id = world_id
91
+ self.rows_read = 0 # deserialization counter (read-path scaling guard, 037)
92
+
93
+ def _meta(self, key: str) -> str | None:
94
+ row = self._conn.execute(
95
+ "SELECT value FROM world_meta WHERE key = ?", (key,)
96
+ ).fetchone()
97
+ return row[0] if row else None
98
+
99
+ # ---------------------------------------------------------------- write
100
+
101
+ def append(
102
+ self,
103
+ *,
104
+ entity: str,
105
+ attribute: str,
106
+ value: Any,
107
+ role: WriterRole,
108
+ status: str,
109
+ value_type: str = "literal",
110
+ valid_from: float | None = None,
111
+ valid_to: float | None = None,
112
+ frame: str = CANON,
113
+ confidence: float | None = None,
114
+ ) -> Assertion:
115
+ """Append one assertion. The only write path that exists."""
116
+ role.check(status)
117
+ if status not in STATUSES:
118
+ raise ValueError(f"unknown status {status!r}")
119
+ if value_type not in VALUE_TYPES:
120
+ raise ValueError(f"unknown value_type {value_type!r}")
121
+ seq = self.head() + 1
122
+ row = Assertion(
123
+ seq=seq,
124
+ id=f"a:{seq}",
125
+ world_id=self.world_id,
126
+ entity=entity,
127
+ attribute=attribute,
128
+ value_type=value_type,
129
+ value=value,
130
+ valid_from=valid_from,
131
+ valid_to=valid_to,
132
+ frame=frame,
133
+ status=status,
134
+ confidence=confidence,
135
+ asserted_at=seq,
136
+ )
137
+ self._insert(row)
138
+ return row
139
+
140
+ def _insert(self, row: Assertion) -> None:
141
+ if row.world_id != self.world_id:
142
+ raise WorldMismatch(
143
+ f"assertion stamped for world {row.world_id!r} cannot enter "
144
+ f"buffer of world {self.world_id!r}"
145
+ )
146
+ self._validate_attr_semantics_insert(row)
147
+ self._conn.execute(
148
+ "INSERT INTO assertions (seq, id, world_id, entity, attribute, value_type,"
149
+ " value, valid_from, valid_to, frame, status, confidence, asserted_at)"
150
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
151
+ (
152
+ row.seq,
153
+ row.id,
154
+ row.world_id,
155
+ row.entity,
156
+ row.attribute,
157
+ row.value_type,
158
+ json.dumps(row.value, sort_keys=True, default=json_default),
159
+ row.valid_from,
160
+ row.valid_to,
161
+ row.frame,
162
+ row.status,
163
+ row.confidence,
164
+ row.asserted_at,
165
+ ),
166
+ )
167
+ self._conn.commit()
168
+ logger.debug("append %s: (%s · %s)", row.id, row.entity, row.attribute)
169
+
170
+ def _validate_attr_semantics_insert(self, row: Assertion) -> None:
171
+ """Shared attr:* authority guard for append() and dump.build replay."""
172
+ if not row.entity.startswith(ATTR_PREFIX) or row.attribute not in SEMANTICS_PREDICATES:
173
+ return
174
+ target = row.entity[len(ATTR_PREFIX):]
175
+ if target in INVIOLABLE_CORE:
176
+ raise ValueError(
177
+ f"cannot declare semantics for inviolable core attribute {target!r}"
178
+ )
179
+ prior = self._conn.execute(
180
+ "SELECT id FROM assertions"
181
+ " WHERE attribute = ? AND entity NOT LIKE ? AND entity NOT LIKE ?"
182
+ " ORDER BY seq LIMIT 1",
183
+ (target, ATTR_PREFIX + "%", "a:%"),
184
+ ).fetchone()
185
+ if prior is not None:
186
+ raise ValueError(
187
+ f"cannot declare semantics for attribute {target!r} after "
188
+ f"folded data already exists ({prior[0]})"
189
+ )
190
+
191
+ # ----------------------------------------------------------------- read
192
+
193
+ def head(self) -> int:
194
+ """The current log head (last seq; 0 for an empty log)."""
195
+ row = self._conn.execute("SELECT COALESCE(MAX(seq), 0) FROM assertions").fetchone()
196
+ return int(row[0])
197
+
198
+ def get(self, assertion_id: str) -> Assertion | None:
199
+ rows = self._select("WHERE id = ?", (assertion_id,))
200
+ return rows[0] if rows else None
201
+
202
+ def all_rows(self) -> list[Assertion]:
203
+ """The full log in append order (dump/rebuild substrate)."""
204
+ return self._select("", ())
205
+
206
+ def visible(
207
+ self,
208
+ *,
209
+ entity: str | None = None,
210
+ entity_in: list[str] | None = None,
211
+ entity_prefix: str | None = None,
212
+ attribute: str | None = None,
213
+ attribute_in: list[str] | None = None,
214
+ value: Any | None = None,
215
+ value_type: str | None = None,
216
+ frame: str | None = None,
217
+ valid_as_of: float | None = None,
218
+ asserted_as_of: int | None = None,
219
+ ) -> list[Assertion]:
220
+ """Rows visible at ``(valid_as_of, asserted_as_of)`` — spec §4.2.
221
+
222
+ Visibility is class-blind; which visible row a fold serves is
223
+ durability-dependent and lives in the derived indexes, not here.
224
+ Omitted bounds mean no bound (now / log head). Retraction
225
+ meta-rows themselves are excluded from folds, and a row is
226
+ invisible once a ``retracts`` row targeting it is itself visible
227
+ on the asserted axis.
228
+ """
229
+ aao = self.head() if asserted_as_of is None else asserted_as_of
230
+ clauses = ["a.asserted_at <= ?", "a.status != 'retracted'"]
231
+ params: list[Any] = [aao]
232
+ if entity is not None:
233
+ clauses.append("a.entity = ?")
234
+ params.append(entity)
235
+ if entity_in is not None:
236
+ clauses.append(f"a.entity IN ({','.join('?' * len(entity_in))})")
237
+ params.extend(entity_in)
238
+ if entity_prefix is not None:
239
+ clauses.append("a.entity LIKE ?")
240
+ params.append(entity_prefix + "%")
241
+ if attribute is not None:
242
+ clauses.append("a.attribute = ?")
243
+ params.append(attribute)
244
+ if attribute_in is not None:
245
+ clauses.append(f"a.attribute IN ({','.join('?' * len(attribute_in))})")
246
+ params.extend(attribute_in)
247
+ if value is not None:
248
+ clauses.append("a.value = ?")
249
+ # Encoded-byte equality: a Decimal query matches its stored row at
250
+ # the authored scale ("12.50" != "12.5" — append-only fidelity).
251
+ params.append(json.dumps(value, sort_keys=True, default=json_default))
252
+ if value_type is not None:
253
+ clauses.append("a.value_type = ?")
254
+ params.append(value_type)
255
+ if frame is not None:
256
+ clauses.append("a.frame = ?")
257
+ params.append(frame)
258
+ if valid_as_of is not None:
259
+ clauses.append("(a.valid_from IS NULL OR a.valid_from <= ?)")
260
+ params.append(valid_as_of)
261
+ clauses.append("(a.valid_to IS NULL OR a.valid_to > ?)")
262
+ params.append(valid_as_of)
263
+ clauses.append(
264
+ "NOT EXISTS (SELECT 1 FROM assertions r"
265
+ " WHERE r.entity = a.id AND r.attribute = 'retracts' AND r.asserted_at <= ?)"
266
+ )
267
+ params.append(aao)
268
+ return self._select("WHERE " + " AND ".join(clauses), tuple(params))
269
+
270
+ def max_valid_from(self) -> float | None:
271
+ """The valid-time high-water mark over ALL rows, all frames (None when
272
+ no timed rows exist). Log coordinates are real after retraction —
273
+ monotone by construction (AXIS-HEAD-V1)."""
274
+ row = self._conn.execute("SELECT MAX(valid_from) FROM assertions").fetchone()
275
+ return row[0]
276
+
277
+ def _select(self, where: str, params: tuple) -> list[Assertion]:
278
+ cur = self._conn.execute(
279
+ "SELECT seq, id, world_id, entity, attribute, value_type, value,"
280
+ " valid_from, valid_to, frame, status, confidence, asserted_at"
281
+ f" FROM assertions a {where} ORDER BY seq",
282
+ params,
283
+ )
284
+ out = [
285
+ Assertion(
286
+ seq=r[0],
287
+ id=r[1],
288
+ world_id=r[2],
289
+ entity=r[3],
290
+ attribute=r[4],
291
+ value_type=r[5],
292
+ value=json.loads(r[6], object_hook=decode_hook),
293
+ valid_from=r[7],
294
+ valid_to=r[8],
295
+ frame=r[9],
296
+ status=r[10],
297
+ confidence=r[11],
298
+ asserted_at=r[12],
299
+ )
300
+ for r in cur.fetchall()
301
+ ]
302
+ self.rows_read += len(out)
303
+ return out
304
+
305
+ # ------------------------------------------------------------- plumbing
306
+
307
+ def raw_connection(self) -> sqlite3.Connection:
308
+ """For tests asserting the triggers; never used by engine code."""
309
+ return self._conn
310
+
311
+ def close(self) -> None:
312
+ self._conn.close()