rememberstack 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.
- rememberstack/__init__.py +9 -0
- rememberstack/adapters/__init__.py +42 -0
- rememberstack/adapters/codex_writer.py +221 -0
- rememberstack/adapters/markitdown_converter.py +42 -0
- rememberstack/adapters/openrouter.py +136 -0
- rememberstack/adapters/selfhost/__init__.py +54 -0
- rememberstack/adapters/selfhost/forget.py +66 -0
- rememberstack/adapters/selfhost/git.py +374 -0
- rememberstack/adapters/selfhost/lance.py +328 -0
- rememberstack/adapters/selfhost/minio.py +279 -0
- rememberstack/adapters/selfhost/mounts.py +249 -0
- rememberstack/adapters/selfhost/object_store.py +130 -0
- rememberstack/adapters/selfhost/projection.py +80 -0
- rememberstack/adapters/selfhost/queue.py +137 -0
- rememberstack/adapters/selfhost/telemetry.py +45 -0
- rememberstack/adapters/selfhost/watcher.py +70 -0
- rememberstack/adapters/testing/__init__.py +15 -0
- rememberstack/adapters/testing/cost_meter.py +13 -0
- rememberstack/adapters/testing/model_provider.py +83 -0
- rememberstack/adapters/testing/queue.py +43 -0
- rememberstack/adapters/testing/telemetry.py +22 -0
- rememberstack/client.py +19 -0
- rememberstack/core/__init__.py +127 -0
- rememberstack/core/blockizer.py +189 -0
- rememberstack/core/chunker.py +216 -0
- rememberstack/core/consumption_skill.py +275 -0
- rememberstack/core/conversion.py +76 -0
- rememberstack/core/core_manifest.py +598 -0
- rememberstack/core/extension_packs.py +124 -0
- rememberstack/core/forget.py +17 -0
- rememberstack/core/knowledge_authored.py +276 -0
- rememberstack/core/knowledge_compile.py +215 -0
- rememberstack/core/knowledge_fact_sheet.py +210 -0
- rememberstack/core/knowledge_hashing.py +68 -0
- rememberstack/core/knowledge_planner.py +64 -0
- rememberstack/core/knowledge_writer.py +175 -0
- rememberstack/core/ranking.py +200 -0
- rememberstack/core/recipe_linter.py +149 -0
- rememberstack/core/section_snap.py +209 -0
- rememberstack/core/storage_routing.py +27 -0
- rememberstack/eval/__init__.py +53 -0
- rememberstack/eval/consumption.py +141 -0
- rememberstack/eval/contradiction.py +184 -0
- rememberstack/eval/harness.py +136 -0
- rememberstack/eval/lifecycle.py +400 -0
- rememberstack/eval/operational_scale.py +49 -0
- rememberstack/eval/resolution.py +255 -0
- rememberstack/eval/retrieval_spikes.py +50 -0
- rememberstack/eval/skeleton.py +231 -0
- rememberstack/llm/__init__.py +1 -0
- rememberstack/model/__init__.py +589 -0
- rememberstack/model/adjudication.py +100 -0
- rememberstack/model/auth.py +27 -0
- rememberstack/model/blocks.py +30 -0
- rememberstack/model/chunks.py +190 -0
- rememberstack/model/claims.py +162 -0
- rememberstack/model/client.py +98 -0
- rememberstack/model/clustering.py +54 -0
- rememberstack/model/component_version.py +124 -0
- rememberstack/model/consumption.py +88 -0
- rememberstack/model/conversion.py +31 -0
- rememberstack/model/deployment.py +53 -0
- rememberstack/model/documents.py +168 -0
- rememberstack/model/envelope.py +513 -0
- rememberstack/model/evaluation.py +72 -0
- rememberstack/model/forget.py +143 -0
- rememberstack/model/git.py +13 -0
- rememberstack/model/knowledge.py +840 -0
- rememberstack/model/knowledge_authored.py +325 -0
- rememberstack/model/knowledge_planner.py +431 -0
- rememberstack/model/lifecycle.py +42 -0
- rememberstack/model/model_provider.py +78 -0
- rememberstack/model/mounts.py +24 -0
- rememberstack/model/object_store.py +21 -0
- rememberstack/model/operational_scale.py +59 -0
- rememberstack/model/operations.py +153 -0
- rememberstack/model/processing.py +228 -0
- rememberstack/model/queue.py +73 -0
- rememberstack/model/recipes.py +83 -0
- rememberstack/model/relations.py +79 -0
- rememberstack/model/resolution.py +83 -0
- rememberstack/model/retrieval_spikes.py +62 -0
- rememberstack/model/sections.py +120 -0
- rememberstack/model/telemetry.py +30 -0
- rememberstack/ports/__init__.py +29 -0
- rememberstack/ports/auth.py +16 -0
- rememberstack/ports/connector.py +23 -0
- rememberstack/ports/cost_meter.py +17 -0
- rememberstack/ports/forget.py +20 -0
- rememberstack/ports/git.py +20 -0
- rememberstack/ports/model_provider.py +28 -0
- rememberstack/ports/mounts.py +16 -0
- rememberstack/ports/object_store.py +27 -0
- rememberstack/ports/p1_index.py +92 -0
- rememberstack/ports/purge.py +93 -0
- rememberstack/ports/queue.py +23 -0
- rememberstack/ports/telemetry.py +21 -0
- rememberstack/profiles/__init__.py +22 -0
- rememberstack/profiles/selfhost.py +324 -0
- rememberstack/profiles/selfhost_forget.py +158 -0
- rememberstack/profiles/selfhost_operations.py +95 -0
- rememberstack/py.typed +1 -0
- rememberstack/spine/__init__.py +93 -0
- rememberstack/spine/admission.py +26 -0
- rememberstack/spine/backfill.py +168 -0
- rememberstack/spine/catalog_contract.py +742 -0
- rememberstack/spine/chunk_catalog.py +237 -0
- rememberstack/spine/claim_catalog.py +298 -0
- rememberstack/spine/clustering.py +740 -0
- rememberstack/spine/component_versions.py +208 -0
- rememberstack/spine/consumption.py +81 -0
- rememberstack/spine/deployment_bootstrap.py +445 -0
- rememberstack/spine/document_catalog.py +621 -0
- rememberstack/spine/entity_registry.py +205 -0
- rememberstack/spine/extension_packs.py +220 -0
- rememberstack/spine/fact_catalog.py +571 -0
- rememberstack/spine/forget.py +1753 -0
- rememberstack/spine/knowledge.py +5467 -0
- rememberstack/spine/lifecycle.py +1071 -0
- rememberstack/spine/migrations/__init__.py +1 -0
- rememberstack/spine/migrations/_helpers.py +153 -0
- rememberstack/spine/migrations/env.py +58 -0
- rememberstack/spine/migrations/script.py.mako +27 -0
- rememberstack/spine/migrations/versions/__init__.py +1 -0
- rememberstack/spine/migrations/versions/p0_02_0001_extensions_enums.py +189 -0
- rememberstack/spine/migrations/versions/p0_02_0002_infrastructure_registries.py +321 -0
- rememberstack/spine/migrations/versions/p0_02_0003_entities_evaluation_e0_e1.py +631 -0
- rememberstack/spine/migrations/versions/p0_02_0004_claims_facts_evidence.py +411 -0
- rememberstack/spine/migrations/versions/p0_02_0005_projection_knowledge_retrieval.py +391 -0
- rememberstack/spine/migrations/versions/p0_02_0006_partitions_views.py +158 -0
- rememberstack/spine/migrations/versions/p2_06_0007_invalidated_outcome.py +26 -0
- rememberstack/spine/migrations/versions/p3_01_0008_document_version_target.py +58 -0
- rememberstack/spine/migrations/versions/p3_05_0009_reconcile_stage.py +27 -0
- rememberstack/spine/migrations/versions/p3_07_0010_lifecycle_eval_suite.py +25 -0
- rememberstack/spine/migrations/versions/p4_01_0011_survivor_view_rewrite.py +57 -0
- rememberstack/spine/migrations/versions/p6_02_0012_knowledge_compile_recovery.py +58 -0
- rememberstack/spine/migrations/versions/p6_04_0013_knowledge_writer_ledger.py +46 -0
- rememberstack/spine/migrations/versions/p6_05_0014_knowledge_planner_runtime.py +217 -0
- rememberstack/spine/migrations/versions/p6_06_0015_authored_dispatch_runtime.py +38 -0
- rememberstack/spine/migrations/versions/p7_02_0016_operational_eval_suite.py +19 -0
- rememberstack/spine/migrations/versions/p7_05_0017_hard_forget.py +55 -0
- rememberstack/spine/observation_adjudication.py +778 -0
- rememberstack/spine/operations.py +298 -0
- rememberstack/spine/projection.py +662 -0
- rememberstack/spine/recipes.py +276 -0
- rememberstack/spine/resolver.py +763 -0
- rememberstack/spine/review.py +650 -0
- rememberstack/spine/settings.py +22 -0
- rememberstack/spine/supersession.py +510 -0
- rememberstack/spine/sync.py +128 -0
- rememberstack/spine/work_ledger.py +816 -0
- rememberstack/surfaces/__init__.py +110 -0
- rememberstack/surfaces/cli.py +447 -0
- rememberstack/surfaces/consumption_skill.py +87 -0
- rememberstack/surfaces/graph_queries.py +698 -0
- rememberstack/surfaces/http_api.py +377 -0
- rememberstack/surfaces/mcp.py +67 -0
- rememberstack/surfaces/query_engine.py +1591 -0
- rememberstack/surfaces/recipe_executor.py +185 -0
- rememberstack/surfaces/recipe_surface.py +219 -0
- rememberstack/surfaces/remote_mcp.py +133 -0
- rememberstack/surfaces/sdk.py +324 -0
- rememberstack/workers/__init__.py +155 -0
- rememberstack/workers/base.py +312 -0
- rememberstack/workers/e0.py +577 -0
- rememberstack/workers/e1.py +425 -0
- rememberstack/workers/e2.py +525 -0
- rememberstack/workers/e3.py +434 -0
- rememberstack/workers/forget.py +299 -0
- rememberstack/workers/knowledge_authored.py +146 -0
- rememberstack/workers/knowledge_driver.py +735 -0
- rememberstack/workers/knowledge_fact_sheet.py +123 -0
- rememberstack/workers/knowledge_planner.py +325 -0
- rememberstack/workers/knowledge_writer.py +393 -0
- rememberstack/workers/operations.py +42 -0
- rememberstack/workers/p1.py +234 -0
- rememberstack/workers/p2.py +513 -0
- rememberstack/workers/p2_analytics.py +276 -0
- rememberstack/workers/p3.py +673 -0
- rememberstack/workers/reconcile.py +485 -0
- rememberstack/workers/sync.py +168 -0
- rememberstack-0.1.0.dist-info/METADATA +213 -0
- rememberstack-0.1.0.dist-info/RECORD +186 -0
- rememberstack-0.1.0.dist-info/WHEEL +4 -0
- rememberstack-0.1.0.dist-info/entry_points.txt +2 -0
- rememberstack-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
"""The response envelope (D49): the answer's machine-readable self-account.
|
|
2
|
+
|
|
3
|
+
Every query-engine result carries its grain, validity, freshness stamps, the
|
|
4
|
+
nominate-then-drop honesty count (D48), and — when the answer is a "no" — a
|
|
5
|
+
typed negative from the fixed taxonomy (retrieval §5). The walking skeleton
|
|
6
|
+
carries the minimal envelope; the full contract grows on these same fields.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from enum import StrEnum
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
from uuid import UUID
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
from pydantic import ConfigDict
|
|
15
|
+
from pydantic import Field
|
|
16
|
+
from pydantic import model_validator
|
|
17
|
+
|
|
18
|
+
from rememberstack.model.adjudication import TranscriptEntry
|
|
19
|
+
from rememberstack.model.queue import UTCDateTime
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Grain(StrEnum):
|
|
23
|
+
"""The D49 grain type-system: what kind of truth a result is."""
|
|
24
|
+
|
|
25
|
+
FACT = "fact"
|
|
26
|
+
EVIDENCE = "evidence"
|
|
27
|
+
COMPILED = "compiled"
|
|
28
|
+
COMPOSITE = "composite"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NegativeKind(StrEnum):
|
|
32
|
+
"""The fixed negative-answer taxonomy (S29/S39/S55).
|
|
33
|
+
|
|
34
|
+
Deliberately no `denied` kind: content-level authorization is a library
|
|
35
|
+
non-goal (retrieval §9), and hard-deleted (forgotten) content is
|
|
36
|
+
indistinguishable-from-never-existed (S55), so it surfaces as
|
|
37
|
+
`unknown_entity`/`known_empty`, never a distinct kind. Freezing the
|
|
38
|
+
taxonomy now is safe precisely because of these two omissions —
|
|
39
|
+
retrofitting a kind onto a deployed API breaks consumers.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
UNKNOWN_ENTITY = "unknown_entity"
|
|
43
|
+
KNOWN_EMPTY = "known_empty"
|
|
44
|
+
BOUNDARY = "boundary"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class IdentityRegime(StrEnum):
|
|
48
|
+
"""Which identity boundary answered a read (S61).
|
|
49
|
+
|
|
50
|
+
`current` (the default) follows today's aliases and merge redirects even
|
|
51
|
+
under a past `believed_at`; `as_of` means the identity boundary was
|
|
52
|
+
reconstructed as it stood at the queried instant (the transcript-based
|
|
53
|
+
`identity_as_of` recipe). The envelope always states which, so an audit
|
|
54
|
+
read can never silently mix today's identities with yesterday's beliefs.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
CURRENT = "current"
|
|
58
|
+
AS_OF = "as_of"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class FactSupport(StrEnum):
|
|
62
|
+
"""Whether a fact still has current-testimony support (D54).
|
|
63
|
+
|
|
64
|
+
`current` is the normal state; `withdrawn` means every source that
|
|
65
|
+
asserted the fact has stopped (an open `support_withdrawn` review flag) —
|
|
66
|
+
the fact is *flagged, not vanished*, so an agent sees the ground moved
|
|
67
|
+
before planning against it. A withdrawn fact is still returned.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
CURRENT = "current"
|
|
71
|
+
WITHDRAWN = "withdrawn"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Negative(BaseModel):
|
|
75
|
+
"""One typed 'no': each kind demands a different agent reaction."""
|
|
76
|
+
|
|
77
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
78
|
+
|
|
79
|
+
kind: NegativeKind
|
|
80
|
+
explanation: Annotated[str, Field(min_length=1)]
|
|
81
|
+
workaround: str | None = None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Validity(BaseModel):
|
|
85
|
+
"""A result's bi-temporal state as hydration re-read it (D48)."""
|
|
86
|
+
|
|
87
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
88
|
+
|
|
89
|
+
valid_from: UTCDateTime | None
|
|
90
|
+
valid_until: UTCDateTime | None
|
|
91
|
+
ingested_at: UTCDateTime
|
|
92
|
+
invalidated_at: UTCDateTime | None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class KFreshness(BaseModel):
|
|
96
|
+
"""The compiled-grain honesty block (retrieval §5): a K page's timestamp.
|
|
97
|
+
|
|
98
|
+
A compiled answer is pre-paid synthesis *with a timestamp*, so any answer
|
|
99
|
+
that consumed a K page carries when it compiled, whether it is stale
|
|
100
|
+
(inputs changed since), and how many evidence-change flags are still open
|
|
101
|
+
against it — the reader-facing flag surface (k_layers spike 9). An agent
|
|
102
|
+
sees "this page has 3 unresolved flags" before planning against it (S34).
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
106
|
+
|
|
107
|
+
compiled_at: UTCDateTime | None = None
|
|
108
|
+
stale: bool = False
|
|
109
|
+
open_flags: int = Field(default=0, ge=0)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Freshness(BaseModel):
|
|
113
|
+
"""Per-source freshness stamps (S42): what lag the answer could carry.
|
|
114
|
+
|
|
115
|
+
Each contributing channel also exposes its **`believed_at` horizon**: the
|
|
116
|
+
oldest system-time a query can reach before the channel can no longer
|
|
117
|
+
answer. `None` means unbounded — under D69 the hot P2 relation view keeps
|
|
118
|
+
every relation whose endpoints stay emitted, so P2's horizon is null.
|
|
119
|
+
Whenever a horizon is finite, a `believed_at` before it must return a
|
|
120
|
+
`boundary` (retrieval §3), never a silent truncation.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
124
|
+
|
|
125
|
+
pg_live_ts: UTCDateTime
|
|
126
|
+
p1_written_inline: bool = True # the skeleton writes P1 inline; a real
|
|
127
|
+
# write-lag horizon replaces this constant with measurement (retrieval §5)
|
|
128
|
+
p1_believed_at_horizon: UTCDateTime | None = None # None = unbounded
|
|
129
|
+
p2_snapshot_version: str | None = None # which graph snapshot answered
|
|
130
|
+
p2_snapshot_ts: UTCDateTime | None = None
|
|
131
|
+
p2_believed_at_horizon: UTCDateTime | None = None # None = unbounded (D69)
|
|
132
|
+
k: KFreshness | None = None # present only when the answer consumed a K page
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class EntityCandidate(BaseModel):
|
|
136
|
+
"""One ranked resolve candidate (never a silent guess, S51)."""
|
|
137
|
+
|
|
138
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
139
|
+
|
|
140
|
+
entity_id: UUID
|
|
141
|
+
canonical_name: str
|
|
142
|
+
type: str
|
|
143
|
+
tier: str # which resolution tier surfaced it (T0 in the skeleton)
|
|
144
|
+
context_hits: int = 0
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class CoMember(BaseModel):
|
|
148
|
+
"""One other side of a contradiction, surfaced with the fact (S23).
|
|
149
|
+
|
|
150
|
+
A light record — enough to see the competing claim and hydrate it — so a
|
|
151
|
+
contradiction block can carry several sides without recursion.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
155
|
+
|
|
156
|
+
fact_id: UUID
|
|
157
|
+
label: str
|
|
158
|
+
evidence_count: int
|
|
159
|
+
validity: Validity
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class Contradiction(BaseModel):
|
|
163
|
+
"""The S23 contract block: a fact's live contradiction, never one-sided.
|
|
164
|
+
|
|
165
|
+
Returning one side of a live contradiction group without its others is a
|
|
166
|
+
contract violation, not a ranking choice ("contradictions are surfaced,
|
|
167
|
+
never silently resolved"). The bounded form: co-members come back INLINE
|
|
168
|
+
up to a guaranteed cap (typical groups are 2–3 sides — both FY2023
|
|
169
|
+
revenue figures together, each with its own evidence handle); beyond the
|
|
170
|
+
cap the block still always carries `group_id`, `returned`, `total`, and a
|
|
171
|
+
`continuation`. One-sided is never a valid answer.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
175
|
+
|
|
176
|
+
group_id: UUID
|
|
177
|
+
co_members: tuple[CoMember, ...] = ()
|
|
178
|
+
returned: int = Field(ge=0)
|
|
179
|
+
total: int = Field(ge=0)
|
|
180
|
+
continuation: str | None = None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class FactResult(BaseModel):
|
|
184
|
+
"""One fact-grain record: a live relation or observation, hydrated."""
|
|
185
|
+
|
|
186
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
187
|
+
|
|
188
|
+
fact_id: UUID
|
|
189
|
+
kind: str # relation | observation
|
|
190
|
+
label: str
|
|
191
|
+
evidence_count: int
|
|
192
|
+
validity: Validity
|
|
193
|
+
contradiction_group: UUID | None = None # the raw group id (S23)
|
|
194
|
+
contradiction: Contradiction | None = None # the surfaced co-members (S23)
|
|
195
|
+
support: FactSupport = FactSupport.CURRENT # D54: withdrawn is flagged, not gone
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class EvidenceResult(BaseModel):
|
|
199
|
+
"""One evidence-grain record: a claim with its provenance anchors."""
|
|
200
|
+
|
|
201
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
202
|
+
|
|
203
|
+
claim_id: UUID
|
|
204
|
+
doc_id: UUID
|
|
205
|
+
chunk_id: UUID
|
|
206
|
+
claim_text: str
|
|
207
|
+
source_span: str
|
|
208
|
+
char_start: int
|
|
209
|
+
char_end: int
|
|
210
|
+
is_attributed: bool
|
|
211
|
+
is_current_testimony: bool
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class SourceRecord(BaseModel):
|
|
215
|
+
"""One hydrated source document handle (S5: down to the artifact URI)."""
|
|
216
|
+
|
|
217
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
218
|
+
|
|
219
|
+
doc_id: UUID
|
|
220
|
+
title: str | None
|
|
221
|
+
source_kind: str
|
|
222
|
+
markdown_uri: str | None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class GraphNode(BaseModel):
|
|
226
|
+
"""One entity the traversal reached, with its hop distance."""
|
|
227
|
+
|
|
228
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
229
|
+
|
|
230
|
+
entity_id: UUID
|
|
231
|
+
name: str
|
|
232
|
+
type: str
|
|
233
|
+
hops: int = Field(ge=0)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class GraphEdge(BaseModel):
|
|
237
|
+
"""One traversed relation, carrying its bi-temporal state."""
|
|
238
|
+
|
|
239
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
240
|
+
|
|
241
|
+
relation_id: UUID
|
|
242
|
+
subject_id: UUID
|
|
243
|
+
object_id: UUID
|
|
244
|
+
predicate: str
|
|
245
|
+
fact: str | None
|
|
246
|
+
evidence_count: int
|
|
247
|
+
valid_from: UTCDateTime | None
|
|
248
|
+
valid_until: UTCDateTime | None
|
|
249
|
+
ingested_at: UTCDateTime | None
|
|
250
|
+
invalidated_at: UTCDateTime | None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class GraphPath(BaseModel):
|
|
254
|
+
"""One connection between two entities — a COMPOUND result.
|
|
255
|
+
|
|
256
|
+
A path revalidates as a unit (S17/S21): if hydration drops any edge,
|
|
257
|
+
the whole path drops, because a path with a hole is not a shorter
|
|
258
|
+
path — it is a different (and false) claim about connection.
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
262
|
+
|
|
263
|
+
length: int = Field(ge=1)
|
|
264
|
+
nodes: tuple[GraphNode, ...] = Field(min_length=2)
|
|
265
|
+
edges: tuple[GraphEdge, ...] = Field(min_length=1)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class RankedItem(BaseModel):
|
|
269
|
+
"""One item in a fused or reranked ordering (retrieval §3: `fuse`/`rerank`).
|
|
270
|
+
|
|
271
|
+
`score` is the operator's output — the RRF sum for `fuse`, the signal
|
|
272
|
+
value for `rerank` — and the tuple order IS the rank. `signals` keeps
|
|
273
|
+
each contributing value visible, because the rerankers are meant to be
|
|
274
|
+
inspectable stages (D9), not a black-box sort.
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
278
|
+
|
|
279
|
+
item_id: UUID
|
|
280
|
+
score: float
|
|
281
|
+
signals: dict[str, float] = Field(default_factory=dict)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class ChangeRecord(BaseModel):
|
|
285
|
+
"""One entry in the `delta` change feed (S13/S14/S30).
|
|
286
|
+
|
|
287
|
+
`kind` is what changed (relation | observation | claim | page) and
|
|
288
|
+
`change` is how (new | invalidated | capped | recompiled). `at` is the
|
|
289
|
+
instant that placed it in the feed — the ingestion, invalidation, or
|
|
290
|
+
recompilation time the caller's `since` was compared against — so a
|
|
291
|
+
follow-up `delta` can resume from the last `at` it saw.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
295
|
+
|
|
296
|
+
kind: str # relation | observation | claim | page
|
|
297
|
+
change: str # new | invalidated | capped | recompiled
|
|
298
|
+
id: UUID
|
|
299
|
+
label: str | None
|
|
300
|
+
at: UTCDateTime
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class AggregateBucket(BaseModel):
|
|
304
|
+
"""One group in an enumerated aggregate (retrieval §9): a key and its count.
|
|
305
|
+
|
|
306
|
+
`key` is the group label — a predicate, an object entity, a timeline
|
|
307
|
+
period, or an entity id rendered as text — and `null` for the single
|
|
308
|
+
bucket of a plain count. `entity_id` is populated when the group IS an
|
|
309
|
+
entity (group-by-object, delta-top-entities, typed-absence), so the
|
|
310
|
+
agent can hop straight to it without re-resolving the label.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
314
|
+
|
|
315
|
+
key: str | None
|
|
316
|
+
count: int = Field(ge=0)
|
|
317
|
+
entity_id: UUID | None = None
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class AggregateReport(BaseModel):
|
|
321
|
+
"""An enumerated aggregate's result: the form asked, and its buckets.
|
|
322
|
+
|
|
323
|
+
Aggregation is enumerated, never general (retrieval §9): each `form`
|
|
324
|
+
is a bounded SQL shape with a predictable cost. `total` is the sum
|
|
325
|
+
across buckets (or the single count); `bounded_by` names the cap when
|
|
326
|
+
the shape rides a bounded feed (e.g. delta-top-entities), so a reader
|
|
327
|
+
knows the ranking is over the window, not all of history.
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
331
|
+
|
|
332
|
+
form: str
|
|
333
|
+
buckets: tuple[AggregateBucket, ...] = ()
|
|
334
|
+
total: int = Field(ge=0)
|
|
335
|
+
bounded_by: str | None = None
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class PageRef(BaseModel):
|
|
339
|
+
"""One K page the `pages_about` discovery index reports (S31/S45).
|
|
340
|
+
|
|
341
|
+
The rule-key inverted index that routes writes, read backwards: which
|
|
342
|
+
pages exist about an entity or key. `stale` mirrors the refresh state —
|
|
343
|
+
a page whose inputs changed but has not recompiled — so discovery never
|
|
344
|
+
presents an out-of-date page as fresh without saying so.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
348
|
+
|
|
349
|
+
artifact_id: UUID
|
|
350
|
+
page_kind: str
|
|
351
|
+
git_path: str | None
|
|
352
|
+
page_summary: str | None
|
|
353
|
+
last_compiled_at: UTCDateTime | None
|
|
354
|
+
status: str
|
|
355
|
+
stale: bool = False
|
|
356
|
+
open_review_flags: int = Field(default=0, ge=0)
|
|
357
|
+
redaction_required: bool = False
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class ScanRow(BaseModel):
|
|
361
|
+
"""One row of a `scan` batch export (S53): id, kind, label, feed instant.
|
|
362
|
+
|
|
363
|
+
The batch surface streams the same zero-LLM reads as the interactive
|
|
364
|
+
primitives under a separate resource pool (retrieval §9). A row is
|
|
365
|
+
deliberately minimal — id plus enough to route a hydrate — because a
|
|
366
|
+
scan is an export to a compiler or auditor, not a rendered answer.
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
370
|
+
|
|
371
|
+
kind: str
|
|
372
|
+
id: UUID
|
|
373
|
+
label: str | None
|
|
374
|
+
at: UTCDateTime | None = None
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class Truncation(BaseModel):
|
|
378
|
+
"""The explicit cap marker (S18/S49): no silent top-k ever.
|
|
379
|
+
|
|
380
|
+
``estimated_total`` is what the traversal could see before the cap;
|
|
381
|
+
``continuation`` carries the opaque cursor a follow-up call passes back.
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
385
|
+
|
|
386
|
+
truncated: bool
|
|
387
|
+
returned: int = Field(ge=0)
|
|
388
|
+
estimated_total: int = Field(ge=0)
|
|
389
|
+
total_is_exact: bool = True # false when the count itself hit its cap
|
|
390
|
+
continuation: str | None = None
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class EnvelopePart(BaseModel):
|
|
394
|
+
"""One single-grain section of a composite answer (S47).
|
|
395
|
+
|
|
396
|
+
A mixed answer — S47's "everything Alice *said* about pricing, plus what
|
|
397
|
+
we *believe*" — is EXPLICITLY two-part, never blended: each part carries
|
|
398
|
+
its own grain and its own single-grain results, so the fact/evidence
|
|
399
|
+
discipline is never diluted. Single-grain answers skip `parts` and read
|
|
400
|
+
flat off the top-level fields.
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
404
|
+
|
|
405
|
+
grain: Grain # strictly single-grain: fact | evidence | compiled (S47)
|
|
406
|
+
label: str | None = None # e.g. "said" vs "believed"
|
|
407
|
+
facts: tuple[FactResult, ...] = ()
|
|
408
|
+
evidence: tuple[EvidenceResult, ...] = ()
|
|
409
|
+
sources: tuple[SourceRecord, ...] = ()
|
|
410
|
+
nodes: tuple[GraphNode, ...] = ()
|
|
411
|
+
aggregate: AggregateReport | None = None
|
|
412
|
+
pages: tuple[PageRef, ...] = ()
|
|
413
|
+
truncation: Truncation | None = None
|
|
414
|
+
|
|
415
|
+
@model_validator(mode="after")
|
|
416
|
+
def _payload_matches_grain(self) -> "EnvelopePart":
|
|
417
|
+
"""A part is strictly single-grain: it carries only the payload its
|
|
418
|
+
own grain owns — a fact part holds facts (and graph nodes / an
|
|
419
|
+
aggregate), an evidence part holds claims, a compiled part holds K
|
|
420
|
+
pages. Carrying another grain's payload, or being composite, is the
|
|
421
|
+
blending S47 forbids. `sources` (hydration handles) is cross-grain."""
|
|
422
|
+
if self.grain is Grain.COMPOSITE:
|
|
423
|
+
raise ValueError("a composite part is not single-grain (S47)")
|
|
424
|
+
owned = {
|
|
425
|
+
Grain.FACT: {"facts", "nodes", "aggregate"},
|
|
426
|
+
Grain.EVIDENCE: {"evidence"},
|
|
427
|
+
Grain.COMPILED: {"pages"},
|
|
428
|
+
}[self.grain]
|
|
429
|
+
populated = {
|
|
430
|
+
name
|
|
431
|
+
for name, value in (
|
|
432
|
+
("facts", self.facts),
|
|
433
|
+
("evidence", self.evidence),
|
|
434
|
+
("nodes", self.nodes),
|
|
435
|
+
("aggregate", self.aggregate),
|
|
436
|
+
("pages", self.pages),
|
|
437
|
+
)
|
|
438
|
+
if value
|
|
439
|
+
}
|
|
440
|
+
stray = populated - owned
|
|
441
|
+
if stray:
|
|
442
|
+
raise ValueError(
|
|
443
|
+
f"a {self.grain.value}-grain part carries {sorted(stray)}"
|
|
444
|
+
" belonging to another grain (S47)"
|
|
445
|
+
)
|
|
446
|
+
return self
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
class Envelope(BaseModel):
|
|
450
|
+
"""The D49 envelope: results plus the answer's machine-readable self-account.
|
|
451
|
+
|
|
452
|
+
A single-grain answer (the common case) reads flat: the top-level
|
|
453
|
+
`grain` and the matching result tuple. A `composite` answer sets
|
|
454
|
+
`grain = composite` and carries `parts[]`, each strictly single-grain
|
|
455
|
+
(S47) — so a caller never has to disentangle blended grains.
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
459
|
+
|
|
460
|
+
grain: Grain
|
|
461
|
+
parts: tuple["EnvelopePart", ...] = () # S47: composite ⇒ read parts[]
|
|
462
|
+
as_of_valid_at: UTCDateTime | None = None # echo of the applied valid_at
|
|
463
|
+
as_of_believed_at: UTCDateTime | None = None # echo of the applied believed_at
|
|
464
|
+
identity_regime: IdentityRegime = IdentityRegime.CURRENT # S61: which regime
|
|
465
|
+
entities: tuple[EntityCandidate, ...] = ()
|
|
466
|
+
facts: tuple[FactResult, ...] = ()
|
|
467
|
+
evidence: tuple[EvidenceResult, ...] = ()
|
|
468
|
+
sources: tuple[SourceRecord, ...] = ()
|
|
469
|
+
transcript: tuple["TranscriptEntry", ...] = () # S8: the audit surface
|
|
470
|
+
nodes: tuple[GraphNode, ...] = () # S18: neighborhood members
|
|
471
|
+
paths: tuple[GraphPath, ...] = () # S17/S21: compound connections
|
|
472
|
+
edges: tuple[GraphEdge, ...] = () # the traversed relations
|
|
473
|
+
ranking: tuple[RankedItem, ...] = () # S46: fused / reranked order
|
|
474
|
+
changes: tuple[ChangeRecord, ...] = () # S13/S14/S30: the delta feed
|
|
475
|
+
aggregate: AggregateReport | None = None # S26–S30/S40: enumerated only
|
|
476
|
+
pages: tuple[PageRef, ...] = () # S31/S45: pages_about discovery
|
|
477
|
+
freshness: Freshness
|
|
478
|
+
truncation: Truncation | None = None # S18/S49: caps are never silent
|
|
479
|
+
dropped_by_hydration: int = 0
|
|
480
|
+
negative: Negative | None = None
|
|
481
|
+
|
|
482
|
+
@model_validator(mode="after")
|
|
483
|
+
def _composite_uses_parts(self) -> "Envelope":
|
|
484
|
+
"""Enforce the S47 discipline: `parts` belong only to a composite
|
|
485
|
+
answer, and a composite's data lives IN its parts, never blended into
|
|
486
|
+
the top-level result tuples. (Each part's own single-grain purity is
|
|
487
|
+
checked on `EnvelopePart`.) A flat compound answer with no parts —
|
|
488
|
+
hydrate's fact-with-evidence bundle — is untouched."""
|
|
489
|
+
if not self.parts:
|
|
490
|
+
return self
|
|
491
|
+
if self.grain is not Grain.COMPOSITE:
|
|
492
|
+
raise ValueError("only a composite envelope carries parts[]")
|
|
493
|
+
blended = self.aggregate is not None or any(
|
|
494
|
+
(
|
|
495
|
+
self.entities,
|
|
496
|
+
self.facts,
|
|
497
|
+
self.evidence,
|
|
498
|
+
self.sources,
|
|
499
|
+
self.transcript,
|
|
500
|
+
self.nodes,
|
|
501
|
+
self.paths,
|
|
502
|
+
self.edges,
|
|
503
|
+
self.ranking,
|
|
504
|
+
self.changes,
|
|
505
|
+
self.pages,
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
if blended:
|
|
509
|
+
raise ValueError(
|
|
510
|
+
"a composite answer's data lives in parts[], never blended"
|
|
511
|
+
" into the top-level result tuples (S47)"
|
|
512
|
+
)
|
|
513
|
+
return self
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Typed records for the D22 evaluation harness: suites, canaries, reports."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from pydantic import ConfigDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EvalSuite(StrEnum):
|
|
11
|
+
"""Exact values of the binding Postgres ``eval_suite`` enum."""
|
|
12
|
+
|
|
13
|
+
RESOLUTION = "resolution"
|
|
14
|
+
SELECTION = "selection"
|
|
15
|
+
GROUNDING = "grounding"
|
|
16
|
+
RETRIEVAL = "retrieval"
|
|
17
|
+
CONTRADICTION = "contradiction"
|
|
18
|
+
LIFECYCLE = "lifecycle"
|
|
19
|
+
OPERATIONAL = "operational"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CanaryCase(BaseModel):
|
|
23
|
+
"""One known-tricky regression case re-run per version (registries §10)."""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
26
|
+
|
|
27
|
+
canary_id: UUID
|
|
28
|
+
suite: EvalSuite
|
|
29
|
+
description: str
|
|
30
|
+
input: dict[str, object]
|
|
31
|
+
expected: dict[str, object]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CaseFailure(BaseModel):
|
|
35
|
+
"""One failed case with the reason a reviewer needs."""
|
|
36
|
+
|
|
37
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
38
|
+
|
|
39
|
+
canary_id: UUID
|
|
40
|
+
description: str
|
|
41
|
+
reason: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SuiteReport(BaseModel):
|
|
45
|
+
"""One suite run: totals, failures, and the pass verdict CI gates on."""
|
|
46
|
+
|
|
47
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
48
|
+
|
|
49
|
+
suite: EvalSuite
|
|
50
|
+
total_cases: int
|
|
51
|
+
failures: tuple[CaseFailure, ...]
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def passed(self) -> bool:
|
|
55
|
+
"""A suite passes only with zero failures (empty suites pass)."""
|
|
56
|
+
return not self.failures
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class LifecycleReport(BaseModel):
|
|
60
|
+
"""One lifecycle-suite run: invariant verdicts + the flag-rate metric.
|
|
61
|
+
|
|
62
|
+
``passed`` reflects the invariants alone; the flag rate is the watched
|
|
63
|
+
rollout canary (its alarm threshold is an operations decision).
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
67
|
+
|
|
68
|
+
passed: bool
|
|
69
|
+
quiescent: bool = True # count/closure checks defer while mid-flight
|
|
70
|
+
violations: dict[str, tuple[str, ...]] = {}
|
|
71
|
+
canary_failures: tuple[str, ...] = ()
|
|
72
|
+
flag_rate_by_extractor: dict[str, dict[str, float]] = {}
|