Skip to content

Commit c73eb09

Browse files
test(english-gonol): prove full construct preserves specified identities and boundaries
1 parent 0a97b09 commit c73eb09

1 file changed

Lines changed: 326 additions & 0 deletions

File tree

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
# === CHECKS ===
2+
# id: check_full_construct_identity_reuse
3+
# proves: full_construct_has_one_character_identity, full_construct_has_one_word_identity
4+
# call: self::test_one_character_and_one_word_identity_are_reused
5+
# requires: python3
6+
# timeout: 30
7+
# mutates: filesystem
8+
# cleanup: tempdir_teardown
9+
#
10+
# id: check_full_construct_definition_origin_and_evidence
11+
# proves: full_construct_definitions_share_word_origin, full_construct_keeps_probability_evidence_distinct
12+
# call: self::test_definitions_share_origin_and_preserve_distinct_evidence
13+
# requires: python3
14+
# timeout: 30
15+
# mutates: filesystem
16+
# cleanup: tempdir_teardown
17+
#
18+
# id: check_full_construct_no_evidence_promotion
19+
# proves: full_construct_does_not_promote_evidence_to_gonols, full_construct_never_invents_geometry
20+
# call: self::test_no_old_singleton_atlas_or_invented_geometry_exists
21+
# requires: python3
22+
# timeout: 30
23+
# mutates: filesystem
24+
# cleanup: tempdir_teardown
25+
#
26+
# id: check_full_construct_logical_replay
27+
# proves: full_construct_replays_logically
28+
# call: self::test_logical_receipt_is_deterministic_and_detects_tamper
29+
# requires: python3
30+
# timeout: 30
31+
# mutates: filesystem
32+
# cleanup: tempdir_teardown
33+
# === END CHECKS ===
34+
35+
from __future__ import annotations
36+
37+
import json
38+
import sqlite3
39+
from pathlib import Path
40+
41+
import pytest
42+
43+
from english_gonol.full_construct_run import (
44+
GEOMETRY_STATE,
45+
HMMM,
46+
RESOLUTION_RULE,
47+
SCHEMA,
48+
VERSION,
49+
FullConstructError,
50+
build_construct,
51+
verify_replay,
52+
)
53+
from english_gonol.language.source import (
54+
LexemeRecord,
55+
SenseRecord,
56+
SynsetRecord,
57+
WordnetSnapshot,
58+
)
59+
60+
61+
def _snapshot() -> WordnetSnapshot:
62+
# The intentionally non-lexicographic sense order is load-bearing evidence.
63+
return WordnetSnapshot(
64+
lexemes=(
65+
LexemeRecord(
66+
lemma="alpha",
67+
part_of_speech="n",
68+
forms=(),
69+
senses=(
70+
SenseRecord(
71+
sense_id="z-sense",
72+
synset_id="s-z",
73+
relations=(("also", ("other-sense",)),),
74+
),
75+
SenseRecord(
76+
sense_id="a-sense",
77+
synset_id="s-a",
78+
relations=(),
79+
),
80+
),
81+
),
82+
LexemeRecord(
83+
lemma="other",
84+
part_of_speech="n",
85+
forms=(),
86+
senses=(
87+
SenseRecord(
88+
sense_id="other-sense",
89+
synset_id="s-other",
90+
relations=(),
91+
),
92+
),
93+
),
94+
),
95+
synsets=(
96+
SynsetRecord(
97+
synset_id="s-a",
98+
part_of_speech="n",
99+
members=("alpha",),
100+
definitions=("second alpha",),
101+
relations=(),
102+
),
103+
SynsetRecord(
104+
synset_id="s-other",
105+
part_of_speech="n",
106+
members=("other",),
107+
definitions=("another word",),
108+
relations=(),
109+
),
110+
SynsetRecord(
111+
synset_id="s-z",
112+
part_of_speech="n",
113+
members=("alpha", "other"),
114+
definitions=("alpha letter alpha.",),
115+
relations=(("similar", ("s-other",)),),
116+
),
117+
),
118+
source_tree_sha256="0" * 64,
119+
source_file_count=3,
120+
)
121+
122+
123+
def _public_position(scalar: str) -> int | None:
124+
# Deterministic fixture only; this test is about English construction,
125+
# not the production UCNS authority verification performed by run().
126+
return ord(scalar) % 157
127+
128+
129+
def _build(path: Path) -> dict:
130+
return build_construct(
131+
_snapshot(),
132+
db_path=path,
133+
public_position=_public_position,
134+
)
135+
136+
137+
def _table_names(db: sqlite3.Connection) -> set[str]:
138+
return {
139+
row[0]
140+
for row in db.execute(
141+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
142+
)
143+
}
144+
145+
146+
def test_one_character_and_one_word_identity_are_reused(tmp_path: Path) -> None:
147+
db_path = tmp_path / "construct.db"
148+
_build(db_path)
149+
db = sqlite3.connect(db_path)
150+
try:
151+
alpha_id = db.execute(
152+
"SELECT id FROM words WHERE surface='alpha'"
153+
).fetchone()[0]
154+
assert db.execute(
155+
"SELECT COUNT(*) FROM words WHERE surface='alpha'"
156+
).fetchone()[0] == 1
157+
158+
# The same alpha word is both the definition origin and a participant
159+
# in its own sentence context; no occurrence-specific word is minted.
160+
origin_ids = {
161+
row[0]
162+
for row in db.execute(
163+
"SELECT DISTINCT origin_word_id FROM definitions WHERE sense_id IN ('z-sense','a-sense')"
164+
)
165+
}
166+
assert origin_ids == {alpha_id}
167+
assert db.execute(
168+
"SELECT COUNT(*) FROM definition_words WHERE word_id=?",
169+
(alpha_id,),
170+
).fetchone()[0] >= 3
171+
172+
letter_id = db.execute(
173+
"SELECT id FROM words WHERE surface='letter'"
174+
).fetchone()[0]
175+
character_ids = [
176+
row[0]
177+
for row in db.execute(
178+
"SELECT character_id FROM word_characters WHERE word_id=? ORDER BY ordinal",
179+
(letter_id,),
180+
)
181+
]
182+
# l e t t e r: repeated e and t positions reuse one character identity.
183+
assert character_ids[1] == character_ids[4]
184+
assert character_ids[2] == character_ids[3]
185+
assert db.execute(
186+
"SELECT COUNT(*) FROM characters WHERE scalar='t'"
187+
).fetchone()[0] == 1
188+
finally:
189+
db.close()
190+
191+
192+
def test_definitions_share_origin_and_preserve_distinct_evidence(tmp_path: Path) -> None:
193+
db_path = tmp_path / "construct.db"
194+
_build(db_path)
195+
db = sqlite3.connect(db_path)
196+
try:
197+
alpha_id = db.execute(
198+
"SELECT id FROM words WHERE surface='alpha'"
199+
).fetchone()[0]
200+
definitions = db.execute(
201+
"SELECT id, origin_word_id, ordinal, sense_id, text "
202+
"FROM definitions WHERE origin_word_id=? ORDER BY ordinal",
203+
(alpha_id,),
204+
).fetchall()
205+
assert [(row[2], row[3]) for row in definitions] == [
206+
(1, "z-sense"),
207+
(2, "a-sense"),
208+
]
209+
assert all(row[1] == alpha_id for row in definitions)
210+
211+
first_id = definitions[0][0]
212+
spans = db.execute(
213+
"SELECT ordinal, w.surface, start_offset, end_offset "
214+
"FROM definition_words dw JOIN words w ON w.id=dw.word_id "
215+
"WHERE definition_id=? ORDER BY ordinal",
216+
(first_id,),
217+
).fetchall()
218+
assert spans == [
219+
(1, "alpha", 0, 5),
220+
(2, "letter", 6, 12),
221+
(3, "alpha", 13, 18),
222+
]
223+
224+
other_id = db.execute(
225+
"SELECT id FROM words WHERE surface='other'"
226+
).fetchone()[0]
227+
evidence = db.execute(
228+
"SELECT channel, relation, target_word_id, target_ref "
229+
"FROM semantic_evidence WHERE definition_id=? ORDER BY source_ordinal, target_word_id",
230+
(first_id,),
231+
).fetchall()
232+
assert ("sense", "also", other_id, "other-sense") in evidence
233+
assert ("synset", "similar", other_id, "s-other") in evidence
234+
assert ("synset-membership", "co-member", other_id, "other") in evidence
235+
236+
meta = dict(db.execute("SELECT key, value FROM meta"))
237+
assert meta["geometry_state"] == GEOMETRY_STATE
238+
assert meta["resolution_rule"] == RESOLUTION_RULE
239+
assert json.loads(meta["evidence_channels"]) == [
240+
"ordinal",
241+
"semantic",
242+
"sentence-context",
243+
]
244+
assert HMMM in meta["hmmm"]
245+
finally:
246+
db.close()
247+
248+
249+
def test_no_old_singleton_atlas_or_invented_geometry_exists(tmp_path: Path) -> None:
250+
db_path = tmp_path / "construct.db"
251+
_build(db_path)
252+
db = sqlite3.connect(db_path)
253+
try:
254+
assert _table_names(db) == {
255+
"meta",
256+
"characters",
257+
"words",
258+
"word_characters",
259+
"definitions",
260+
"definition_words",
261+
"semantic_evidence",
262+
"unresolved_semantic_evidence",
263+
}
264+
forbidden_fragments = {
265+
"weight",
266+
"vector",
267+
"coordinate",
268+
"center",
269+
"radius",
270+
"tangent",
271+
"motion",
272+
}
273+
for table in _table_names(db):
274+
columns = {
275+
row[1].lower()
276+
for row in db.execute(f"PRAGMA table_info({table})")
277+
}
278+
assert not any(
279+
fragment in column
280+
for column in columns
281+
for fragment in forbidden_fragments
282+
)
283+
284+
# Sense and synset identifiers are provenance fields on definitions;
285+
# they never become independent object tables.
286+
assert "senses" not in _table_names(db)
287+
assert "synsets" not in _table_names(db)
288+
assert "sentences" not in _table_names(db)
289+
assert "occurrences" not in _table_names(db)
290+
assert "relation_circles" not in _table_names(db)
291+
assert "tangencies" not in _table_names(db)
292+
finally:
293+
db.close()
294+
295+
296+
def test_logical_receipt_is_deterministic_and_detects_tamper(tmp_path: Path) -> None:
297+
first_dir = tmp_path / "first"
298+
second_dir = tmp_path / "second"
299+
first_dir.mkdir()
300+
second_dir.mkdir()
301+
302+
first = _build(first_dir / "construct.db")
303+
second = _build(second_dir / "construct.db")
304+
assert first["receipt_sha256"] == second["receipt_sha256"]
305+
306+
manifest = {
307+
"schema": SCHEMA,
308+
"version": VERSION,
309+
"construct_file": "construct.db",
310+
"receipt_sha256": first["receipt_sha256"],
311+
}
312+
(first_dir / "manifest.json").write_text(
313+
json.dumps(manifest, sort_keys=True) + "\n",
314+
encoding="utf-8",
315+
)
316+
assert verify_replay(first_dir)["receipt_sha256"] == first["receipt_sha256"]
317+
318+
db = sqlite3.connect(first_dir / "construct.db")
319+
try:
320+
db.execute("UPDATE words SET surface='tampered' WHERE surface='alpha'")
321+
db.commit()
322+
finally:
323+
db.close()
324+
325+
with pytest.raises(FullConstructError, match="receipt mismatch"):
326+
verify_replay(first_dir)

0 commit comments

Comments
 (0)