SNAPKITTYWEST commited on
Commit
fc3e70a
Β·
verified Β·
1 Parent(s): 4dc1a38

October 2026 main drop: mirror from GitHub

Browse files
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .pytest_cache/
LICENSE ADDED
@@ -0,0 +1,374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Mozilla Public License Version 2.0
2
+ ==================================
3
+
4
+ 1. Definitions
5
+ --------------
6
+
7
+ 1.1. "Contributor"
8
+ means each individual or legal entity that creates, contributes to
9
+ the creation of, or owns Covered Software.
10
+
11
+ 1.2. "Contributor Version"
12
+ means the combination of the Contributions of others (if any) used
13
+ by a Contributor and that particular Contributor's Contribution.
14
+
15
+ 1.3. "Contribution"
16
+ means Covered Software of a particular Contributor.
17
+
18
+ 1.4. "Covered Software"
19
+ means Source Code Form to which the initial Contributor has attached
20
+ the notice in Exhibit A, the Executable Form of such Source Code
21
+ Form, and Modifications of such Source Code Form, in each case
22
+ including portions thereof.
23
+
24
+ 1.5. "Incompatible With Secondary Licenses"
25
+ means
26
+
27
+ (a) that the initial Contributor has attached the notice described
28
+ in Exhibit B to the Covered Software; or
29
+
30
+ (b) that the Covered Software was made available under the terms of
31
+ version 1.1 or earlier of the License, but not also under the
32
+ terms of a Secondary License.
33
+
34
+ 1.6. "Executable Form"
35
+ means any form of the work other than Source Code Form.
36
+
37
+ 1.7. "Larger Work"
38
+ means a work that combines Covered Software with other material, in
39
+ a separate file or files, that is not Covered Software.
40
+
41
+ 1.8. "License"
42
+ means this document.
43
+
44
+ 1.9. "Licensable"
45
+ means having the right to grant, to the maximum extent possible,
46
+ whether at the time of the initial grant or subsequently, any and
47
+ all of the rights conveyed by this License.
48
+
49
+ 1.10. "Modifications"
50
+ means any of the following:
51
+
52
+ (a) any file in Source Code Form that results from an addition to,
53
+ deletion from, or modification of the contents of Covered
54
+ Software; or
55
+
56
+ (b) any new file in Source Code Form that contains any Covered
57
+ Software.
58
+
59
+ 1.11. "Patent Claims" of a Contributor
60
+ means any patent claim(s), including without limitation, method,
61
+ process, and apparatus claims, in any patent Licensable by such
62
+ Contributor that would be infringed, but for the grant of the
63
+ License, by the making, using, selling, offering for sale, having
64
+ made, import, or transfer of either its Contributions or its
65
+ Contributor Version.
66
+
67
+ 1.12. "Secondary License"
68
+ means either the GNU General Public License, Version 2.0, the GNU
69
+ Lesser General Public License, Version 2.1, the GNU Affero General
70
+ Public License, Version 3.0, or any later versions of those
71
+ licenses.
72
+
73
+ 1.13. "Source Code Form"
74
+ means the form of the work preferred for making modifications.
75
+
76
+ 1.14. "You" (or "Your")
77
+ means an individual or a legal entity exercising rights under this
78
+ License. For legal entities, "You" includes any entity that
79
+ controls, is controlled by, or is under common control with You. For
80
+ purposes of this definition, "control" means (a) the power, direct
81
+ or indirect, to cause the direction or management of such entity,
82
+ whether by contract or otherwise, or (b) ownership of more than
83
+ fifty percent (50%) of the outstanding shares or beneficial
84
+ ownership of such entity.
85
+
86
+ 2. License Grants and Conditions
87
+ --------------------------------
88
+
89
+ 2.1. Grants
90
+
91
+ Each Contributor hereby grants You a world-wide, royalty-free,
92
+ non-exclusive license:
93
+
94
+ (a) under intellectual property rights (other than patent or trademark)
95
+ Licensable by such Contributor to use, reproduce, make available,
96
+ modify, display, perform, distribute, and otherwise exploit its
97
+ Contributions, either on an unmodified basis, with Modifications, or
98
+ as part of a Larger Work; and
99
+
100
+ (b) under Patent Claims of such Contributor to make, use, sell, offer
101
+ for sale, have made, import, and otherwise transfer either its
102
+ Contributions or its Contributor Version.
103
+
104
+ 2.2. Effective Date
105
+
106
+ The licenses granted in Section 2.1 with respect to any Contribution
107
+ become effective for each Contribution on the date the Contributor first
108
+ distributes such Contribution.
109
+
110
+ 2.3. Limitations on Grant Scope
111
+
112
+ The licenses granted in this Section 2 are the only rights granted under
113
+ this License. No additional rights or licenses will be implied from the
114
+ distribution or licensing of Covered Software under this License.
115
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
116
+ Contributor:
117
+
118
+ (a) for any code that a Contributor has removed from Covered Software;
119
+ or
120
+
121
+ (b) for infringements caused by: (i) Your and any other third party's
122
+ modifications of Covered Software, or (ii) the combination of its
123
+ Contributions with other software (except as part of its Contributor
124
+ Version); or
125
+
126
+ (c) under Patent Claims infringed by Covered Software in the absence of
127
+ its Contributions.
128
+
129
+ This License does not grant any rights in the trademarks, service marks,
130
+ or logos of any Contributor (except as may be necessary to comply with
131
+ the notice requirements in Section 3.4).
132
+
133
+ 2.4. Subsequent Licenses
134
+
135
+ No Contributor makes additional grants as a result of Your choice to
136
+ distribute the Covered Software under a subsequent version of this
137
+ License (see Section 10.2) or under the terms of a Secondary License (if
138
+ permitted under the terms of Section 3.3).
139
+
140
+ 2.5. Representation
141
+
142
+ Each Contributor represents that the Contributor believes its
143
+ Contributions are its original creation(s) or it has sufficient rights
144
+ to grant the rights to its Contributions conveyed by this License.
145
+
146
+ 2.6. Fair Use
147
+
148
+ This License is not intended to limit any rights You have under
149
+ applicable copyright doctrines of fair use, fair dealing, or other
150
+ equivalents.
151
+
152
+ 2.7. Conditions
153
+
154
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155
+ in Section 2.1.
156
+
157
+ 3. Responsibilities
158
+ -------------------
159
+
160
+ 3.1. Distribution of Source Form
161
+
162
+ All distribution of Covered Software in Source Code Form, including any
163
+ Modifications that You create or to which You contribute, must be under
164
+ the terms of this License. You must inform recipients that the Source
165
+ Code Form of the Covered Software is governed by the terms of this
166
+ License, and how they can obtain a copy of this License. You may not
167
+ attempt to alter or restrict the recipients' rights in the Source Code
168
+ Form.
169
+
170
+ 3.2. Distribution of Executable Form
171
+
172
+ If You distribute Covered Software in Executable Form then:
173
+
174
+ (a) such Covered Software must also be made available in Source Code
175
+ Form, as described in Section 3.1, and You must inform recipients of
176
+ the Executable Form how they can obtain a copy of such Source Code
177
+ Form by reasonable means in a timely manner, at a charge no more
178
+ than the cost of distribution to the recipient; and
179
+
180
+ (b) You may distribute such Executable Form under the terms of this
181
+ License, or sublicense it under different terms, provided that the
182
+ license for the Executable Form does not attempt to limit or alter
183
+ the recipients' rights in the Source Code Form under this License.
184
+
185
+ 3.3. Distribution of a Larger Work
186
+
187
+ You may create and distribute a Larger Work under terms of Your choice,
188
+ provided that You also comply with the requirements of this License for
189
+ the Covered Software. If the Larger Work is a combination of Covered
190
+ Software with a work governed by one or more Secondary Licenses, and the
191
+ Covered Software is not Incompatible With Secondary Licenses, this
192
+ License permits You to additionally distribute such Covered Software
193
+ under the terms of such Secondary License(s), so that the recipient of
194
+ the Larger Work may, at their option, further distribute the Covered
195
+ Software under the terms of either this License or such Secondary
196
+ License(s).
197
+
198
+ 3.4. Notices
199
+
200
+ You may not remove or alter the substance of any license notices
201
+ (including copyright notices, patent notices, disclaimers of warranty,
202
+ or limitations of liability) contained within the Source Code Form of
203
+ the Covered Software, except that You may alter any license notices to
204
+ the extent required to remedy known factual inaccuracies.
205
+
206
+ 3.5. Application of Additional Terms
207
+
208
+ You may choose to offer, and to charge a fee for, warranty, support,
209
+ indemnity or liability obligations to one or more recipients of Covered
210
+ Software. However, You may do so only on Your own behalf, and not on
211
+ behalf of any Contributor. You must make it absolutely clear that any
212
+ such warranty, support, indemnity, or liability obligation is offered by
213
+ You alone, and You hereby agree to indemnify every Contributor for any
214
+ liability incurred by such Contributor as a result of warranty, support,
215
+ indemnity or liability terms You offer. You may include additional
216
+ disclaimers of warranty and limitations of liability specific to any
217
+ jurisdiction.
218
+
219
+ 4. Inability to Comply Due to Statute or Regulation
220
+ ---------------------------------------------------
221
+
222
+ If it is impossible for You to comply with any of the terms of this
223
+ License with respect to some or all of the Covered Software due to
224
+ statute, judicial order, or regulation then You must: (a) comply with
225
+ the terms of this License to the maximum extent possible; and (b)
226
+ describe the limitations and the code they affect. Such description must
227
+ be placed in a text file included with all distributions of the Covered
228
+ Software under the name "LEGAL", alongside the License. Except to the
229
+ extent prohibited by statute or regulation, such description must be
230
+ sufficiently detailed for a recipient of ordinary skill to be able to
231
+ understand it.
232
+
233
+ 5. Termination
234
+ --------------
235
+
236
+ 5.1. The rights granted under this License will terminate automatically
237
+ if You fail to comply with any of its terms. However, if You become
238
+ compliant, then the rights granted under this License from a particular
239
+ Contributor are reinstated (a) provisionally, unless and until such
240
+ Contributor explicitly and finally terminates Your grants, and (b) on an
241
+ ongoing basis, if such Contributor fails to notify You of the
242
+ non-compliance by some reasonable means prior to 60 days after You have
243
+ come back into compliance. Moreover, Your grants from a particular
244
+ Contributor are reinstated on an ongoing basis if such Contributor
245
+ notifies You of the non-compliance by some reasonable means, this is the
246
+ first time You have received notice of non-compliance with this License
247
+ from such Contributor, and You become compliant prior to 30 days after
248
+ Your receipt of the notice.
249
+
250
+ 5.2. If You initiate litigation against any entity by asserting a patent
251
+ infringement claim (excluding declaratory judgment actions,
252
+ counter-claims, and cross-claims) alleging that a Contributor Version
253
+ directly or indirectly infringes any patent, then the rights granted to
254
+ You by any and all Contributors for the Covered Software under Section
255
+ 2.1 of this License shall terminate.
256
+
257
+ 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
258
+ end user license agreements (excluding distributors and resellers) which
259
+ have been validly granted by You or Your distributors under this License
260
+ prior to termination shall survive termination.
261
+
262
+ ************************************************************************
263
+ * *
264
+ * 6. Disclaimer of Warranty *
265
+ * ------------------------- *
266
+ * *
267
+ * Covered Software is provided under this License on an "as is" *
268
+ * basis, without warranty of any kind, either expressed, implied, or *
269
+ * statutory, including, without limitation, warranties that the *
270
+ * Covered Software is free of defects, merchantable, fit for a *
271
+ * particular purpose or non-infringing. The entire risk as to the *
272
+ * quality and performance of the Covered Software is with You. *
273
+ * Should any Covered Software prove defective in any respect, You *
274
+ * (not any Contributor) assume the cost of any necessary servicing, *
275
+ * repair, or correction. This disclaimer of warranty constitutes an *
276
+ * essential part of this License. No use of any Covered Software is *
277
+ * authorized under this License except under this disclaimer. *
278
+ * *
279
+ ************************************************************************
280
+
281
+ ************************************************************************
282
+ * *
283
+ * 7. Limitation of Liability *
284
+ * -------------------------- *
285
+ * *
286
+ * Under no circumstances and under no legal theory, whether tort *
287
+ * (including negligence), contract, or otherwise, shall any *
288
+ * Contributor, or anyone who distributes Covered Software as *
289
+ * permitted above, be liable to You for any direct, indirect, *
290
+ * special, incidental, or consequential damages of any character *
291
+ * including, without limitation, damages for lost profits, loss of *
292
+ * goodwill, work stoppage, computer failure or malfunction, or any *
293
+ * and all other commercial damages or losses, even if such party *
294
+ * shall have been informed of the possibility of such damages. This *
295
+ * limitation of liability shall not apply to liability for death or *
296
+ * personal injury resulting from such party's negligence to the *
297
+ * extent applicable law prohibits such limitation. Some *
298
+ * jurisdictions do not allow the exclusion or limitation of *
299
+ * incidental or consequential damages, so this exclusion and *
300
+ * limitation may not apply to You. *
301
+ * *
302
+ ************************************************************************
303
+
304
+ 8. Litigation
305
+ -------------
306
+
307
+ Any litigation relating to this License may be brought only in the
308
+ courts of a jurisdiction where the defendant maintains its principal
309
+ place of business and such litigation shall be governed by laws of that
310
+ jurisdiction, without reference to its conflict-of-law provisions.
311
+ Nothing in this Section shall prevent a party's ability to bring
312
+ cross-claims or counter-claims.
313
+
314
+ 9. Miscellaneous
315
+ ----------------
316
+
317
+ This License represents the complete agreement concerning the subject
318
+ matter hereof. If any provision of this License is held to be
319
+ unenforceable, such provision shall be reformed only to the extent
320
+ necessary to make it enforceable. Any law or regulation which provides
321
+ that the language of a contract shall be construed against the drafter
322
+ shall not be used to construe this License against a Contributor.
323
+
324
+ 10. Versions of the License
325
+ ---------------------------
326
+
327
+ 10.1. New Versions
328
+
329
+ Mozilla Foundation is the license steward. Except as provided in Section
330
+ 10.3, no one other than the license steward has the right to modify or
331
+ publish new versions of this License. Each version will be given a
332
+ distinguishing version number.
333
+
334
+ 10.2. Effect of New Versions
335
+
336
+ You may distribute the Covered Software under the terms of the version
337
+ of the License under which You originally received the Covered Software,
338
+ or under the terms of any subsequent version published by the license
339
+ steward.
340
+
341
+ 10.3. Modified Versions
342
+
343
+ If you create software not governed by this License, and you want to
344
+ create a new license for such software, you may create and use a
345
+ modified version of this License if you rename the license and remove
346
+ any references to the name of the license steward (except to note that
347
+ such modified license differs from this License).
348
+
349
+ 10.4. Distributing Source Code Form that is Incompatible With Secondary
350
+ Licenses
351
+
352
+ If You choose to distribute Source Code Form that is Incompatible With
353
+ Secondary Licenses under the terms of this version of the License, the
354
+ notice described in Exhibit B of this License must be attached.
355
+
356
+ Exhibit A - Source Code Form License Notice
357
+ -------------------------------------------
358
+
359
+ This Source Code Form is subject to the terms of the Mozilla Public
360
+ License, v. 2.0. If a copy of the MPL was not distributed with this
361
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
362
+
363
+ If it is not possible or desirable to put the notice in a particular
364
+ file, then You may include the notice in a location (such as a LICENSE
365
+ file in a relevant directory) where a recipient would be likely to look
366
+ for such a notice.
367
+
368
+ You may add additional accurate notices of copyright ownership.
369
+
370
+ Exhibit B - "Incompatible With Secondary Licenses" Notice
371
+ ---------------------------------------------------------
372
+
373
+ This Source Code Form is "Incompatible With Secondary Licenses", as
374
+ defined by the Mozilla Public License, v. 2.0.
README.md ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mpl-2.0
3
+ tags:
4
+ - snapkitty
5
+ - october-2026-drop
6
+ - python
7
+ - mustache
8
+ - subleq
9
+ - latin
10
+ - boolean-logic
11
+ - nlp
12
+ - compiler
13
+ - deterministic
14
+ ---
15
+
16
+ > Mirrored from [https://github.com/SNAPKITTYWEST/semantic-engine](https://github.com/SNAPKITTYWEST/semantic-engine) at commit `b9afd82`. Part of the **SnapKitty October 2026 main drop**.
17
+
cli/main.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ # This Source Code Form is subject to the terms of the Mozilla Public
4
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+
7
+ """Phase 18: CLI β€” semantic-engine command line interface."""
8
+
9
+ import json
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ # Allow running from repo root
14
+ sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
15
+
16
+ import argparse
17
+ from semantic_engine import SemanticPipeline, PipelineResult, RawInput, OutputMode
18
+ from semantic_engine.nlp import normalize_input
19
+ from semantic_engine.boolean_engine import semantic_constraints_to_boolean, normalize as bool_normalize
20
+ from semantic_engine.token_engine import BoolEncoder, encode_binary, decode, encode
21
+ from semantic_engine.latin import LatinCanonicalizer
22
+ from semantic_engine.lexer import LatinLexer
23
+ from semantic_engine.rosetta import RosettaMapper
24
+ from semantic_engine.subleq import build_program_from_rosetta
25
+ from semantic_engine.verifier import Verifier
26
+
27
+
28
+ def read_input(path: str) -> RawInput:
29
+ p = Path(path)
30
+ if not p.exists():
31
+ print(f"error: file not found: {path}", file=sys.stderr)
32
+ sys.exit(1)
33
+ return RawInput(text=p.read_text().strip(), source=str(p))
34
+
35
+
36
+ def cmd_parse(args):
37
+ raw = read_input(args.input)
38
+ normalized = normalize_input(raw)
39
+ out = {
40
+ "input_id": raw.input_id,
41
+ "input_hash": raw.hash,
42
+ "text": normalized.text,
43
+ "tokens": list(normalized.tokens),
44
+ "sentences": list(normalized.sentences),
45
+ }
46
+ print(json.dumps(out, indent=2) if args.json else f"Tokens: {list(normalized.tokens)}")
47
+
48
+
49
+ def cmd_inspect(args):
50
+ raw = read_input(args.input)
51
+ pipeline = SemanticPipeline()
52
+ result = pipeline.run(raw)
53
+ if result.ir:
54
+ ir = result.ir
55
+ out = {
56
+ "ir_id": ir.ir_id,
57
+ "intent": ir.intent.value,
58
+ "nodes": len(ir.nodes),
59
+ "verification": ir.verification_state.name,
60
+ "hash": ir.hash(),
61
+ }
62
+ print(json.dumps(out, indent=2) if args.json else str(out))
63
+ else:
64
+ print(f"error: {result.error}", file=sys.stderr)
65
+ sys.exit(1)
66
+
67
+
68
+ def cmd_normalize(args):
69
+ raw = read_input(args.input)
70
+ pipeline = SemanticPipeline()
71
+ result = pipeline.run(raw)
72
+ if result.ir:
73
+ from semantic_engine.boolean_engine import semantic_constraints_to_boolean
74
+ bnf = semantic_constraints_to_boolean(result.ir.root.constraints)
75
+ print(f"Normalized: {bnf.normalized}")
76
+ print(f"Stable: {bnf.stable}")
77
+ else:
78
+ print(f"error: {result.error}", file=sys.stderr)
79
+ sys.exit(1)
80
+
81
+
82
+ def cmd_boolean(args):
83
+ raw = read_input(args.input)
84
+ pipeline = SemanticPipeline()
85
+ result = pipeline.run(raw)
86
+ if result.ir:
87
+ bnf = semantic_constraints_to_boolean(result.ir.root.constraints)
88
+ print(f"Boolean form: {bnf.normalized}")
89
+ else:
90
+ print(f"error: {result.error}", file=sys.stderr)
91
+ sys.exit(1)
92
+
93
+
94
+ def cmd_tokens(args):
95
+ raw = read_input(args.input)
96
+ pipeline = SemanticPipeline()
97
+ result = pipeline.run(raw)
98
+ if result.ir:
99
+ bnf = semantic_constraints_to_boolean(result.ir.root.constraints)
100
+ encoder = BoolEncoder()
101
+ stream = encoder.encode_expr(bnf.normalized)
102
+ for tok in stream:
103
+ print(repr(tok))
104
+ else:
105
+ print(f"error: {result.error}", file=sys.stderr)
106
+ sys.exit(1)
107
+
108
+
109
+ def cmd_latin(args):
110
+ raw = read_input(args.input)
111
+ pipeline = SemanticPipeline()
112
+ result = pipeline.run(raw)
113
+ if result.ir:
114
+ bnf = semantic_constraints_to_boolean(result.ir.root.constraints)
115
+ encoder = BoolEncoder()
116
+ stream = encoder.encode_expr(bnf.normalized)
117
+ latin = LatinCanonicalizer().canonicalize(stream)
118
+ print(latin.text())
119
+ else:
120
+ print(f"error: {result.error}", file=sys.stderr)
121
+ sys.exit(1)
122
+
123
+
124
+ def cmd_subleq(args):
125
+ raw = read_input(args.input)
126
+ pipeline = SemanticPipeline()
127
+ result = pipeline.run(raw)
128
+ if result.subleq_listing:
129
+ print(result.subleq_listing)
130
+ else:
131
+ print(f"error: {result.error}", file=sys.stderr)
132
+ sys.exit(1)
133
+
134
+
135
+ def cmd_verify(args):
136
+ raw = read_input(args.input)
137
+ pipeline = SemanticPipeline()
138
+ result = pipeline.run(raw)
139
+ status = "VERIFIED" if result.ok else "REJECTED"
140
+ print(f"Status: {status}")
141
+ if result.error:
142
+ print(f"Error: {result.error}")
143
+ for line in result.trace:
144
+ print(f" {line}")
145
+
146
+
147
+ def cmd_artifact(args):
148
+ raw = read_input(args.input)
149
+ pipeline = SemanticPipeline()
150
+ result = pipeline.run(raw, output_mode=OutputMode.NATURAL_LANGUAGE)
151
+ if result.artifact:
152
+ print(result.artifact.content)
153
+ else:
154
+ print(f"error: {result.error}", file=sys.stderr)
155
+ sys.exit(1)
156
+
157
+
158
+ def cmd_backend(args):
159
+ raw = read_input(args.input)
160
+ pipeline = SemanticPipeline()
161
+ result = pipeline.run(raw, output_mode=OutputMode.CODE)
162
+ if result.artifact:
163
+ print(result.artifact.content)
164
+ else:
165
+ print(f"error: {result.error}", file=sys.stderr)
166
+ sys.exit(1)
167
+
168
+
169
+ def cmd_run(args):
170
+ raw = read_input(args.input)
171
+ pipeline = SemanticPipeline()
172
+ result = pipeline.run(raw)
173
+
174
+ if args.trace:
175
+ print("=== TRACE ===")
176
+ for line in result.trace:
177
+ print(f" {line}")
178
+ print()
179
+
180
+ if args.subleq:
181
+ print("=== SUBLEQ ===")
182
+ print(result.subleq_listing)
183
+ print()
184
+
185
+ if args.provenance:
186
+ print("=== PROVENANCE ===")
187
+ if result.ir:
188
+ print(f" IR: {result.ir.ir_id}")
189
+ print(f" Stage: {result.ir.provenance.stage}")
190
+ print()
191
+
192
+ if args.tokens and result.ir:
193
+ print("=== TOKENS ===")
194
+ from semantic_engine.boolean_engine import semantic_constraints_to_boolean
195
+ bnf = semantic_constraints_to_boolean(result.ir.root.constraints)
196
+ encoder = BoolEncoder()
197
+ stream = encoder.encode_expr(bnf.normalized)
198
+ for tok in stream:
199
+ print(f" {repr(tok)}")
200
+ print()
201
+
202
+ if result.artifact:
203
+ print("=== OUTPUT ===")
204
+ print(result.artifact.content)
205
+ else:
206
+ print(f"error: {result.error}", file=sys.stderr)
207
+ sys.exit(1)
208
+
209
+ print(f"\nStatus: {'VERIFIED' if result.ok else 'REJECTED'}")
210
+ for k, v in result.hashes.items():
211
+ print(f" {k}: {v[:16]}")
212
+
213
+
214
+ def main():
215
+ parser = argparse.ArgumentParser(
216
+ prog="semantic-engine",
217
+ description="Deterministic semantic transformation pipeline",
218
+ )
219
+ parser.add_argument("--json", action="store_true", help="Machine-readable JSON output")
220
+
221
+ sub = parser.add_subparsers(dest="command", required=True)
222
+
223
+ for name, fn, help_text in [
224
+ ("parse", cmd_parse, "Tokenize and segment input"),
225
+ ("inspect", cmd_inspect, "Build and inspect the Semantic IR"),
226
+ ("normalize", cmd_normalize, "Show Boolean normalization"),
227
+ ("boolean", cmd_boolean, "Show Boolean IR"),
228
+ ("tokens", cmd_tokens, "Show Boolean token stream"),
229
+ ("latin", cmd_latin, "Show Latin canonical form"),
230
+ ("subleq", cmd_subleq, "Show SUBLEQ IR listing"),
231
+ ("verify", cmd_verify, "Run full verification and show results"),
232
+ ("artifact", cmd_artifact, "Emit natural-language artifact"),
233
+ ("backend", cmd_backend, "Emit backend source artifact"),
234
+ ]:
235
+ p = sub.add_parser(name, help=help_text)
236
+ p.add_argument("input", help="Path to input file")
237
+ p.set_defaults(func=fn)
238
+
239
+ run_p = sub.add_parser("run", help="Run full pipeline with optional trace flags")
240
+ run_p.add_argument("input")
241
+ run_p.add_argument("--trace", action="store_true")
242
+ run_p.add_argument("--subleq", action="store_true")
243
+ run_p.add_argument("--tokens", action="store_true")
244
+ run_p.add_argument("--provenance", action="store_true")
245
+ run_p.set_defaults(func=cmd_run)
246
+
247
+ args = parser.parse_args()
248
+ args.func(args)
249
+
250
+
251
+ if __name__ == "__main__":
252
+ main()
examples/matrix_multiply.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ multiply two matrices A and B where A is 3x4 and B is 4x5
examples/parse_binary_format.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ build a parser for this binary format deterministically
examples/validate_packets.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ create a backend service that validates packets
lexicon/latin_vocabulary.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_comment": "Controlled Latin vocabulary. Every lexeme maps to a stable semantic_id. No synonyms permitted at this stage.",
3
+ "lexemes": {
4
+ "ET": { "semantic_id": "BOOL_AND", "operator": "AND", "arity": 2 },
5
+ "VEL": { "semantic_id": "BOOL_OR", "operator": "OR", "arity": 2 },
6
+ "NON": { "semantic_id": "BOOL_NOT", "operator": "NOT", "arity": 1 },
7
+ "AUT": { "semantic_id": "BOOL_XOR", "operator": "XOR", "arity": 2 },
8
+ "SI": { "semantic_id": "BOOL_IMPLIES", "operator": "IMPLIES", "arity": 2 },
9
+ "IFF": { "semantic_id": "BOOL_EQUIV", "operator": "EQUIVALENCE", "arity": 2 },
10
+ "VERUM": { "semantic_id": "BOOL_TRUE", "operator": "TRUE", "arity": 0 },
11
+ "FALSUM": { "semantic_id": "BOOL_FALSE", "operator": "FALSE", "arity": 0 },
12
+ "INITIUM": { "semantic_id": "DELIM_OPEN", "operator": null, "arity": 0 },
13
+ "FINIS": { "semantic_id": "DELIM_CLOSE", "operator": null, "arity": 0 },
14
+ "OPUS": { "semantic_id": "SEMANTIC_OP", "operator": null, "arity": -1 },
15
+ "FORMA": { "semantic_id": "SEMANTIC_MATRIX", "operator": null, "arity": -1 },
16
+ "TEMPUS": { "semantic_id": "TEMPORAL_STATE", "operator": null, "arity": 0 },
17
+ "COMPUTA": { "semantic_id": "OP_COMPUTE", "operator": "compute", "arity": 1 },
18
+ "MULTIPLICA": { "semantic_id": "OP_MULTIPLY", "operator": "multiply", "arity": 2 },
19
+ "ADDE": { "semantic_id": "OP_ADD", "operator": "add", "arity": 2 },
20
+ "SUBTRAHE": { "semantic_id": "OP_SUBTRACT", "operator": "subtract", "arity": 2 },
21
+ "PARSE": { "semantic_id": "OP_PARSE", "operator": "parse", "arity": 1 },
22
+ "AEDIFICA": { "semantic_id": "OP_BUILD", "operator": "build", "arity": 1 },
23
+ "GENERA": { "semantic_id": "OP_GENERATE", "operator": "generate", "arity": 1 },
24
+ "VERIFICA": { "semantic_id": "OP_VALIDATE", "operator": "validate", "arity": 1 },
25
+ "CONVERTE": { "semantic_id": "OP_TRANSFORM", "operator": "transform", "arity": 2 },
26
+ "RESTITUE": { "semantic_id": "OP_RECONSTRUCT", "operator": "reconstruct", "arity": 1 },
27
+ "MATRIX": { "semantic_id": "ENTITY_MATRIX", "operator": null, "arity": 0 },
28
+ "VECTOR": { "semantic_id": "ENTITY_VECTOR", "operator": null, "arity": 0 },
29
+ "PARSER": { "semantic_id": "ENTITY_PARSER", "operator": null, "arity": 0 },
30
+ "SERVITIUM": { "semantic_id": "ENTITY_SERVICE", "operator": null, "arity": 0 },
31
+ "FORMA_BINARIA": { "semantic_id": "ENTITY_BINARY_FORMAT", "operator": null, "arity": 0 }
32
+ }
33
+ }
mappings/rosetta_table.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_comment": "Rosetta mapping: semantic_id β†’ SUBLEQ operation template. Only canonical vocabulary may reach this layer.",
3
+ "mappings": {
4
+ "BOOL_AND": { "subleq_op": "AND", "template": "subleq_and", "args": ["a", "b", "out"] },
5
+ "BOOL_OR": { "subleq_op": "OR", "template": "subleq_or", "args": ["a", "b", "out"] },
6
+ "BOOL_NOT": { "subleq_op": "NOT", "template": "subleq_not", "args": ["a", "out"] },
7
+ "BOOL_XOR": { "subleq_op": "XOR", "template": "subleq_xor", "args": ["a", "b", "out"] },
8
+ "BOOL_IMPLIES": { "subleq_op": "OR", "template": "subleq_or", "args": ["not_a", "b", "out"] },
9
+ "BOOL_EQUIV": { "subleq_op": "XNOR", "template": "subleq_xnor", "args": ["a", "b", "out"] },
10
+ "BOOL_TRUE": { "subleq_op": "LOAD_1", "template": "subleq_load", "args": ["1", "out"] },
11
+ "BOOL_FALSE": { "subleq_op": "LOAD_0", "template": "subleq_load", "args": ["0", "out"] },
12
+ "SEMANTIC_OP": { "subleq_op": "NOP", "template": "subleq_nop", "args": [] },
13
+ "OP_COMPUTE": { "subleq_op": "COMPUTE", "template": "subleq_compute", "args": ["a", "out"] },
14
+ "OP_MULTIPLY": { "subleq_op": "MUL", "template": "subleq_mul", "args": ["a", "b", "out"] },
15
+ "OP_ADD": { "subleq_op": "ADD", "template": "subleq_add", "args": ["a", "b", "out"] },
16
+ "OP_SUBTRACT": { "subleq_op": "SUB", "template": "subleq_sub", "args": ["a", "b", "out"] },
17
+ "OP_PARSE": { "subleq_op": "PARSE", "template": "subleq_parse", "args": ["src", "out"] },
18
+ "OP_BUILD": { "subleq_op": "BUILD", "template": "subleq_build", "args": ["spec", "out"] },
19
+ "OP_GENERATE": { "subleq_op": "GEN", "template": "subleq_gen", "args": ["spec", "out"] },
20
+ "OP_VALIDATE": { "subleq_op": "VERIFY", "template": "subleq_verify", "args": ["a", "spec"] },
21
+ "OP_TRANSFORM": { "subleq_op": "XFORM", "template": "subleq_xform", "args": ["a", "b", "out"] },
22
+ "OP_RECONSTRUCT": { "subleq_op": "RECON", "template": "subleq_recon", "args": ["spec", "out"] },
23
+ "ENTITY_MATRIX": { "subleq_op": "ALLOC", "template": "subleq_alloc", "args": ["rows", "cols", "out"] },
24
+ "ENTITY_VECTOR": { "subleq_op": "ALLOC", "template": "subleq_alloc", "args": ["n", "1", "out"] },
25
+ "TEMPORAL_STATE": { "subleq_op": "TICK", "template": "subleq_tick", "args": ["tau", "out"] },
26
+ "DELIM_OPEN": { "subleq_op": "PUSH", "template": "subleq_push", "args": [] },
27
+ "DELIM_CLOSE": { "subleq_op": "POP", "template": "subleq_pop", "args": [] }
28
+ }
29
+ }
pyproject.toml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.backends.legacy:build"
4
+
5
+ [project]
6
+ name = "semantic-engine"
7
+ version = "1.0.0"
8
+ description = "Deterministic semantic transformation pipeline: NL β†’ IR β†’ Boolean β†’ Latin β†’ SUBLEQ β†’ Artifact"
9
+ requires-python = ">=3.11"
10
+ dependencies = []
11
+
12
+ [project.optional-dependencies]
13
+ bert = ["transformers>=4.40", "torch>=2.0"]
14
+ dev = ["pytest>=8", "pytest-cov"]
15
+
16
+ [project.scripts]
17
+ semantic-engine = "cli.main:main"
18
+
19
+ [tool.setuptools.packages.find]
20
+ where = ["src"]
21
+
22
+ [tool.pytest.ini_options]
23
+ testpaths = ["tests"]
24
+ addopts = "-v"
src/semantic_engine/__init__.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Semantic Engine β€” deterministic semantic transformation pipeline."""
6
+
7
+ from .pipeline import SemanticPipeline, PipelineResult
8
+ from .types import RawInput, OutputMode
9
+
10
+ __all__ = ["SemanticPipeline", "PipelineResult", "RawInput", "OutputMode"]
src/semantic_engine/agent_harness.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 17: Agent harness β€” agents propose artifacts; the system verifies before acceptance.
6
+
7
+ Agent output never bypasses FSM, IR validation, temporal lock, or verification.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ from typing import Callable, Optional
14
+
15
+ from .ir import Artifact, SemanticIR
16
+ from .types import ArtifactType, FSMState, PipelineError, VerificationState
17
+ from .verifier import Verifier
18
+
19
+
20
+ # ── Agent task ────────────────────────────────────────────────────────────────
21
+
22
+ @dataclass
23
+ class AgentTask:
24
+ task_id: str
25
+ description: str
26
+ semantic_constraints: list[str]
27
+ available_operations: list[str]
28
+ artifact_schema: dict
29
+ verification_requirements: list[str]
30
+ ir_ref: Optional[SemanticIR] = None
31
+
32
+
33
+ # ── Agent proposal ────────────────────────────────────────────────────────────
34
+
35
+ @dataclass
36
+ class AgentProposal:
37
+ agent_id: str
38
+ task_id: str
39
+ artifact: Artifact
40
+ rationale: str = ""
41
+
42
+
43
+ # ── Agent interface ───────────────────────────────────────────────────────────
44
+
45
+ AgentFn = Callable[[AgentTask], AgentProposal]
46
+
47
+
48
+ @dataclass
49
+ class Agent:
50
+ agent_id: str
51
+ fn: AgentFn
52
+ capabilities: list[ArtifactType] = field(default_factory=list)
53
+
54
+
55
+ # ── Harness ───────────────────────────────────────────────────────────────────
56
+
57
+ class AgentHarness:
58
+ """
59
+ Accepts agent proposals, runs them through the verifier, accepts or rejects.
60
+ Agents may NOT bypass any pipeline stage.
61
+ """
62
+
63
+ def __init__(self):
64
+ self._agents: dict[str, Agent] = {}
65
+ self._verifier = Verifier()
66
+ self._accepted: list[Artifact] = []
67
+ self._rejected: list[tuple[AgentProposal, str]] = []
68
+
69
+ def register(self, agent: Agent) -> None:
70
+ self._agents[agent.agent_id] = agent
71
+
72
+ def dispatch(self, task: AgentTask, agent_id: str) -> Artifact:
73
+ agent = self._agents.get(agent_id)
74
+ if agent is None:
75
+ raise PipelineError(FSMState.ARTIFACT_SELECTION, f"Unknown agent: {agent_id}")
76
+
77
+ proposal = agent.fn(task)
78
+
79
+ if task.ir_ref is None:
80
+ raise PipelineError(
81
+ FSMState.VERIFICATION,
82
+ "Agent task has no IR reference β€” cannot verify provenance",
83
+ )
84
+
85
+ report = self._verifier.verify_artifact(proposal.artifact, task.ir_ref)
86
+
87
+ if not report.passed:
88
+ self._rejected.append((proposal, report.summary()))
89
+ raise PipelineError(
90
+ FSMState.VERIFICATION,
91
+ f"Agent {agent_id} proposal rejected:\n{report.summary()}",
92
+ )
93
+
94
+ proposal.artifact.verification_state = VerificationState.VERIFIED
95
+ self._accepted.append(proposal.artifact)
96
+ return proposal.artifact
97
+
98
+ def accepted(self) -> list[Artifact]:
99
+ return list(self._accepted)
100
+
101
+ def rejected(self) -> list[tuple[AgentProposal, str]]:
102
+ return list(self._rejected)
src/semantic_engine/artifacts.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phases 15–16: Artifact engine, Mustache + Go-template backends."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import re
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Any, Optional
14
+
15
+ from .ir import Artifact, Provenance, SemanticIR, TemporalState
16
+ from .types import ArtifactType, FSMState, PipelineError, VerificationState
17
+
18
+
19
+ # ── Mustache renderer (no external dep required) ─────────────────────────────
20
+
21
+ class MustacheRenderer:
22
+ """
23
+ Minimal Mustache renderer supporting:
24
+ {{variable}}
25
+ {{#section}} ... {{/section}} (truthy sections and list iteration)
26
+ {{^section}} ... {{/section}} (inverted)
27
+ """
28
+
29
+ def render(self, template: str, ctx: dict[str, Any]) -> str:
30
+ result = self._render_sections(template, ctx)
31
+ result = self._render_variables(result, ctx)
32
+ return result
33
+
34
+ def _render_variables(self, tmpl: str, ctx: dict) -> str:
35
+ def replace(m):
36
+ key = m.group(1).strip()
37
+ val = ctx.get(key, "")
38
+ return str(val) if val is not None else ""
39
+ return re.sub(r"\{\{([^#^/][^}]*)\}\}", replace, tmpl)
40
+
41
+ def _render_sections(self, tmpl: str, ctx: dict) -> str:
42
+ # Positive sections: {{#key}} ... {{/key}}
43
+ pattern = re.compile(r"\{\{#(\w+)\}\}(.*?)\{\{/\1\}\}", re.DOTALL)
44
+ def replace_section(m):
45
+ key = m.group(1)
46
+ body = m.group(2)
47
+ value = ctx.get(key)
48
+ if not value:
49
+ return ""
50
+ if isinstance(value, list):
51
+ parts = []
52
+ for item in value:
53
+ if isinstance(item, dict):
54
+ parts.append(self.render(body, {**ctx, **item}))
55
+ else:
56
+ parts.append(self.render(body, {**ctx, ".": item}))
57
+ return "".join(parts)
58
+ return self.render(body, ctx)
59
+
60
+ while pattern.search(tmpl):
61
+ tmpl = pattern.sub(replace_section, tmpl)
62
+
63
+ # Inverted sections: {{^key}} ... {{/key}}
64
+ inv_pattern = re.compile(r"\{\{\^(\w+)\}\}(.*?)\{\{/\1\}\}", re.DOTALL)
65
+ def replace_inverted(m):
66
+ key = m.group(1)
67
+ body = m.group(2)
68
+ value = ctx.get(key)
69
+ return "" if value else self.render(body, ctx)
70
+
71
+ while inv_pattern.search(tmpl):
72
+ tmpl = inv_pattern.sub(replace_inverted, tmpl)
73
+
74
+ return tmpl
75
+
76
+ def render_file(self, template_path: Path, ctx: dict) -> str:
77
+ if not template_path.exists():
78
+ raise PipelineError(FSMState.ARTIFACT_SELECTION, f"Template not found: {template_path}")
79
+ return self.render(template_path.read_text(), ctx)
80
+
81
+
82
+ # ── Go template renderer (subset) ────────────────────────────────────────────
83
+
84
+ class GoTemplateRenderer:
85
+ """
86
+ Minimal Go template subset renderer:
87
+ {{.Field}}
88
+ {{range .Items}} ... {{end}}
89
+ {{if .Cond}} ... {{end}}
90
+ """
91
+
92
+ def render(self, template: str, data: Any) -> str:
93
+ result = self._render_range(template, data)
94
+ result = self._render_if(result, data)
95
+ result = self._render_dot_vars(result, data)
96
+ return result
97
+
98
+ def _get(self, data: Any, key: str) -> Any:
99
+ if isinstance(data, dict):
100
+ return data.get(key, "")
101
+ return getattr(data, key, "")
102
+
103
+ def _render_dot_vars(self, tmpl: str, data: Any) -> str:
104
+ def replace(m):
105
+ key = m.group(1).strip()
106
+ if key.startswith("."):
107
+ key = key[1:]
108
+ val = self._get(data, key) if key else data
109
+ return str(val) if val is not None else ""
110
+ return re.sub(r"\{\{(\.?\w+)\}\}", replace, tmpl)
111
+
112
+ def _render_range(self, tmpl: str, data: Any) -> str:
113
+ pattern = re.compile(r"\{\{range \.(\w+)\}\}(.*?)\{\{end\}\}", re.DOTALL)
114
+ def replace(m):
115
+ key = m.group(1)
116
+ body = m.group(2)
117
+ items = self._get(data, key)
118
+ if not items:
119
+ return ""
120
+ if not isinstance(items, (list, tuple)):
121
+ items = [items]
122
+ parts = []
123
+ for item in items:
124
+ parts.append(self.render(body, item))
125
+ return "".join(parts)
126
+ while pattern.search(tmpl):
127
+ tmpl = pattern.sub(replace, tmpl)
128
+ return tmpl
129
+
130
+ def _render_if(self, tmpl: str, data: Any) -> str:
131
+ pattern = re.compile(r"\{\{if \.(\w+)\}\}(.*?)\{\{end\}\}", re.DOTALL)
132
+ def replace(m):
133
+ key = m.group(1)
134
+ body = m.group(2)
135
+ value = self._get(data, key)
136
+ return self.render(body, data) if value else ""
137
+ while pattern.search(tmpl):
138
+ tmpl = pattern.sub(replace, tmpl)
139
+ return tmpl
140
+
141
+ def render_file(self, template_path: Path, data: Any) -> str:
142
+ if not template_path.exists():
143
+ raise PipelineError(FSMState.ARTIFACT_SELECTION, f"Template not found: {template_path}")
144
+ return self.render(template_path.read_text(), data)
145
+
146
+
147
+ # ── Artifact engine ───────────────────────────────────────────────────────────
148
+
149
+ TEMPLATES_DIR = Path(__file__).parent.parent.parent / "templates"
150
+
151
+
152
+ class ArtifactEngine:
153
+ def __init__(self):
154
+ self._mustache = MustacheRenderer()
155
+ self._go = GoTemplateRenderer()
156
+
157
+ def build_prompt(self, ir: SemanticIR, extra: dict | None = None) -> Artifact:
158
+ ctx = self._ir_to_mustache_ctx(ir)
159
+ if extra:
160
+ ctx.update(extra)
161
+ template_path = TEMPLATES_DIR / "prompt.mustache"
162
+ template_text = template_path.read_text() if template_path.exists() else "{{title}}\n\n{{semantic_description}}"
163
+ content = self._mustache.render(template_text, ctx)
164
+ return Artifact.create(
165
+ artifact_type=ArtifactType.PROMPT,
166
+ semantic_root=ir.ir_id,
167
+ template="prompt.mustache",
168
+ content=content,
169
+ provenance=ir.provenance,
170
+ temporal_state=ir.root.temporal_state,
171
+ )
172
+
173
+ def build_document(self, ir: SemanticIR, extra: dict | None = None) -> Artifact:
174
+ ctx = self._ir_to_mustache_ctx(ir)
175
+ if extra:
176
+ ctx.update(extra)
177
+ template_path = TEMPLATES_DIR / "document.mustache"
178
+ template_text = template_path.read_text() if template_path.exists() else "# {{title}}\n\n{{semantic_description}}"
179
+ content = self._mustache.render(template_text, ctx)
180
+ return Artifact.create(
181
+ artifact_type=ArtifactType.DOCUMENT,
182
+ semantic_root=ir.ir_id,
183
+ template="document.mustache",
184
+ content=content,
185
+ provenance=ir.provenance,
186
+ temporal_state=ir.root.temporal_state,
187
+ )
188
+
189
+ def build_backend(self, ir: SemanticIR, extra: dict | None = None) -> Artifact:
190
+ data = self._ir_to_go_data(ir)
191
+ if extra:
192
+ if isinstance(data, dict):
193
+ data.update(extra)
194
+ template_path = TEMPLATES_DIR / "backend.go.tmpl"
195
+ template_text = template_path.read_text() if template_path.exists() else "// {{.SemanticRoot}}"
196
+ content = self._go.render(template_text, data)
197
+ return Artifact.create(
198
+ artifact_type=ArtifactType.BACKEND,
199
+ semantic_root=ir.ir_id,
200
+ template="backend.go.tmpl",
201
+ content=content,
202
+ provenance=ir.provenance,
203
+ temporal_state=ir.root.temporal_state,
204
+ )
205
+
206
+ def _ir_to_mustache_ctx(self, ir: SemanticIR) -> dict:
207
+ return {
208
+ "title": ir.intent.value.upper(),
209
+ "semantic_description": f"Intent: {ir.intent.value} | Nodes: {len(ir.nodes)}",
210
+ "constraints": [c.description for n in ir.nodes for c in n.constraints],
211
+ "operations": [n.operation.name for n in ir.nodes if n.operation],
212
+ "geometry": [str(n.geometry) for n in ir.nodes if n.geometry and n.geometry.row],
213
+ "provenance": [ir.provenance.stage],
214
+ "code": None,
215
+ "tests": None,
216
+ }
217
+
218
+ def _ir_to_go_data(self, ir: SemanticIR) -> dict:
219
+ return {
220
+ "SemanticRoot": ir.ir_id,
221
+ "Tau": ir.root.temporal_state.tau,
222
+ "Verified": ir.verification_state == VerificationState.VERIFIED,
223
+ "Package": "generated",
224
+ "Types": [],
225
+ "Operations": [
226
+ {
227
+ "Name": (n.operation.name.capitalize() if n.operation else "NoOp"),
228
+ "SemanticID": n.id,
229
+ "Params": [{"Name": o.name, "Type": o.type_tag} for o in n.operands],
230
+ "ReturnType": "interface{}",
231
+ "Constraints": [c.description for c in n.constraints],
232
+ "Body": f'\treturn nil, fmt.Errorf("not implemented: {n.id}")',
233
+ }
234
+ for n in ir.nodes if n.operation
235
+ ],
236
+ "Tests": [],
237
+ }
src/semantic_engine/boolean_engine.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 7: Boolean engine β€” deterministic normalization to CNF/DNF."""
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import Optional, Union
11
+ import hashlib
12
+
13
+ from .types import BooleanOperator, FSMState, PipelineError
14
+
15
+
16
+ # ── Boolean expression tree ───────────────────────────────────────────────────
17
+
18
+ @dataclass
19
+ class BooleanLiteral:
20
+ value: bool
21
+
22
+ def __repr__(self) -> str:
23
+ return "⊀" if self.value else "βŠ₯"
24
+
25
+ def negate(self) -> BooleanLiteral:
26
+ return BooleanLiteral(not self.value)
27
+
28
+
29
+ @dataclass
30
+ class BooleanVar:
31
+ name: str
32
+ negated: bool = False
33
+
34
+ def __repr__(self) -> str:
35
+ return f"Β¬{self.name}" if self.negated else self.name
36
+
37
+ def negate(self) -> BooleanVar:
38
+ return BooleanVar(self.name, not self.negated)
39
+
40
+
41
+ BooleanAtom = Union[BooleanLiteral, BooleanVar]
42
+
43
+
44
+ @dataclass
45
+ class BooleanExpression:
46
+ operator: BooleanOperator
47
+ operands: list[Union[BooleanExpression, BooleanAtom]] = field(default_factory=list)
48
+ constraint_refs: list[str] = field(default_factory=list)
49
+
50
+ def __repr__(self) -> str:
51
+ if self.operator == BooleanOperator.TRUE:
52
+ return "⊀"
53
+ if self.operator == BooleanOperator.FALSE:
54
+ return "βŠ₯"
55
+ if self.operator == BooleanOperator.NOT:
56
+ return f"Β¬({self.operands[0]})"
57
+ sym = {
58
+ BooleanOperator.AND: "∧",
59
+ BooleanOperator.OR: "∨",
60
+ BooleanOperator.XOR: "βŠ•",
61
+ BooleanOperator.IMPLIES: "β†’",
62
+ BooleanOperator.EQUIVALENCE: "↔",
63
+ }.get(self.operator, str(self.operator))
64
+ inner = f" {sym} ".join(str(o) for o in self.operands)
65
+ return f"({inner})"
66
+
67
+ def hash(self) -> str:
68
+ return hashlib.sha256(repr(self).encode()).hexdigest()[:16]
69
+
70
+
71
+ # ── Constructors ──────────────────────────────────────────────────────────────
72
+
73
+ def var(name: str, negated: bool = False) -> BooleanVar:
74
+ return BooleanVar(name, negated)
75
+
76
+ def lit(value: bool) -> BooleanLiteral:
77
+ return BooleanLiteral(value)
78
+
79
+ def AND(*args) -> BooleanExpression:
80
+ return BooleanExpression(BooleanOperator.AND, list(args))
81
+
82
+ def OR(*args) -> BooleanExpression:
83
+ return BooleanExpression(BooleanOperator.OR, list(args))
84
+
85
+ def NOT(x) -> BooleanExpression:
86
+ return BooleanExpression(BooleanOperator.NOT, [x])
87
+
88
+ def XOR(a, b) -> BooleanExpression:
89
+ return BooleanExpression(BooleanOperator.XOR, [a, b])
90
+
91
+ def IMPLIES(a, b) -> BooleanExpression:
92
+ return BooleanExpression(BooleanOperator.IMPLIES, [a, b])
93
+
94
+ def EQUIV(a, b) -> BooleanExpression:
95
+ return BooleanExpression(BooleanOperator.EQUIVALENCE, [a, b])
96
+
97
+
98
+ # ── Simplification ────────────────────────────────────────────────────────────
99
+
100
+ Expr = Union[BooleanExpression, BooleanAtom]
101
+
102
+
103
+ def simplify(expr: Expr) -> Expr:
104
+ """Simplify constants and double negations."""
105
+ if isinstance(expr, (BooleanLiteral, BooleanVar)):
106
+ return expr
107
+
108
+ expr = BooleanExpression(expr.operator, [simplify(o) for o in expr.operands], expr.constraint_refs)
109
+
110
+ op = expr.operator
111
+
112
+ # Constant folding
113
+ if op == BooleanOperator.NOT:
114
+ inner = expr.operands[0]
115
+ if isinstance(inner, BooleanLiteral):
116
+ return BooleanLiteral(not inner.value)
117
+ if isinstance(inner, BooleanVar):
118
+ return inner.negate()
119
+ if isinstance(inner, BooleanExpression) and inner.operator == BooleanOperator.NOT:
120
+ return inner.operands[0] # ¬¬A β†’ A
121
+
122
+ if op == BooleanOperator.AND:
123
+ if any(isinstance(o, BooleanLiteral) and not o.value for o in expr.operands):
124
+ return BooleanLiteral(False)
125
+ expr.operands = [o for o in expr.operands if not (isinstance(o, BooleanLiteral) and o.value)]
126
+ if not expr.operands:
127
+ return BooleanLiteral(True)
128
+ if len(expr.operands) == 1:
129
+ return expr.operands[0]
130
+
131
+ if op == BooleanOperator.OR:
132
+ if any(isinstance(o, BooleanLiteral) and o.value for o in expr.operands):
133
+ return BooleanLiteral(True)
134
+ expr.operands = [o for o in expr.operands if not (isinstance(o, BooleanLiteral) and not o.value)]
135
+ if not expr.operands:
136
+ return BooleanLiteral(False)
137
+ if len(expr.operands) == 1:
138
+ return expr.operands[0]
139
+
140
+ # A β†’ B ≑ Β¬A ∨ B
141
+ if op == BooleanOperator.IMPLIES:
142
+ a, b = expr.operands[0], expr.operands[1]
143
+ return simplify(OR(NOT(a), b))
144
+
145
+ # A ↔ B ≑ (A β†’ B) ∧ (B β†’ A)
146
+ if op == BooleanOperator.EQUIVALENCE:
147
+ a, b = expr.operands[0], expr.operands[1]
148
+ return simplify(AND(IMPLIES(a, b), IMPLIES(b, a)))
149
+
150
+ # A βŠ• B ≑ (A ∨ B) ∧ Β¬(A ∧ B)
151
+ if op == BooleanOperator.XOR:
152
+ a, b = expr.operands[0], expr.operands[1]
153
+ return simplify(AND(OR(a, b), NOT(AND(a, b))))
154
+
155
+ return expr
156
+
157
+
158
+ def push_not_inward(expr: Expr) -> Expr:
159
+ """Apply De Morgan's laws, push negations to leaves."""
160
+ if isinstance(expr, (BooleanLiteral, BooleanVar)):
161
+ return expr
162
+
163
+ if expr.operator == BooleanOperator.NOT:
164
+ inner = expr.operands[0]
165
+ if isinstance(inner, BooleanLiteral):
166
+ return BooleanLiteral(not inner.value)
167
+ if isinstance(inner, BooleanVar):
168
+ return inner.negate()
169
+ if isinstance(inner, BooleanExpression):
170
+ if inner.operator == BooleanOperator.NOT:
171
+ return push_not_inward(inner.operands[0])
172
+ if inner.operator == BooleanOperator.AND:
173
+ return push_not_inward(OR(*[NOT(o) for o in inner.operands]))
174
+ if inner.operator == BooleanOperator.OR:
175
+ return push_not_inward(AND(*[NOT(o) for o in inner.operands]))
176
+
177
+ return BooleanExpression(
178
+ expr.operator,
179
+ [push_not_inward(o) for o in expr.operands],
180
+ expr.constraint_refs,
181
+ )
182
+
183
+
184
+ def distribute_or_over_and(expr: Expr) -> Expr:
185
+ """Distribute OR over AND to produce CNF."""
186
+ if isinstance(expr, (BooleanLiteral, BooleanVar)):
187
+ return expr
188
+
189
+ expr = BooleanExpression(
190
+ expr.operator,
191
+ [distribute_or_over_and(o) for o in expr.operands],
192
+ expr.constraint_refs,
193
+ )
194
+
195
+ if expr.operator == BooleanOperator.OR:
196
+ # Find any AND among operands
197
+ for i, op in enumerate(expr.operands):
198
+ if isinstance(op, BooleanExpression) and op.operator == BooleanOperator.AND:
199
+ others = expr.operands[:i] + expr.operands[i+1:]
200
+ distributed = AND(*[
201
+ distribute_or_over_and(OR(clause, *others))
202
+ for clause in op.operands
203
+ ])
204
+ return distribute_or_over_and(distributed)
205
+
206
+ return expr
207
+
208
+
209
+ def to_cnf(expr: Expr) -> Expr:
210
+ """Convert to Conjunctive Normal Form."""
211
+ e = simplify(expr)
212
+ e = push_not_inward(e)
213
+ e = distribute_or_over_and(e)
214
+ e = simplify(e)
215
+ return e
216
+
217
+
218
+ def normalize(expr: Expr) -> Expr:
219
+ """
220
+ Deterministic normalization. normalize(normalize(x)) == normalize(x).
221
+ """
222
+ result = to_cnf(expr)
223
+ # Idempotency check
224
+ again = to_cnf(result)
225
+ if repr(again) != repr(result):
226
+ result = again
227
+ return result
228
+
229
+
230
+ # ── Boolean Normal Form container ─────────────────────────────────────────────
231
+
232
+ @dataclass
233
+ class BooleanNormalForm:
234
+ original: Expr
235
+ normalized: Expr
236
+ form: str = "CNF"
237
+ stable: bool = False
238
+
239
+ def verify_stability(self) -> bool:
240
+ again = normalize(self.normalized)
241
+ self.stable = repr(again) == repr(self.normalized)
242
+ return self.stable
243
+
244
+
245
+ # ── Semantic β†’ Boolean ────────────────────────────────────────────────────────
246
+
247
+ def semantic_constraints_to_boolean(constraints: list) -> BooleanNormalForm:
248
+ """
249
+ Convert a list of Constraint objects into a Boolean expression.
250
+ Each constraint becomes a BooleanVar. Constraints are AND-ed together.
251
+ """
252
+ if not constraints:
253
+ expr: Expr = BooleanLiteral(True)
254
+ else:
255
+ vars_ = [var(c.predicate if hasattr(c, "predicate") else str(c)) for c in constraints]
256
+ expr = vars_[0] if len(vars_) == 1 else AND(*vars_)
257
+
258
+ norm = normalize(expr)
259
+ bnf = BooleanNormalForm(original=expr, normalized=norm)
260
+ bnf.verify_stability()
261
+ return bnf
src/semantic_engine/fsm.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 3: Decoder FSM β€” explicit state machine with typed transitions.
6
+
7
+ Every transition is explicit. Every invalid transition fails closed.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ from typing import Callable, Optional
14
+
15
+ from .types import FSMState, FSMTransition, PipelineError
16
+
17
+
18
+ # ── Transition table ──────────────────────────────────────────────────────────
19
+
20
+ TRANSITION_TABLE: list[FSMTransition] = [
21
+ FSMTransition(FSMState.INPUT, "raw_input", "normalize", ("text_not_empty",), FSMState.NORMALIZE, "input preserved"),
22
+ FSMTransition(FSMState.NORMALIZE, "normalized_input", "lex", ("tokens_non_empty",), FSMState.LEXICAL_ANALYSIS, "normalization idempotent"),
23
+ FSMTransition(FSMState.LEXICAL_ANALYSIS, "token_stream", "detect_intent", ("tokens_recognized",), FSMState.INTENT_DETECTION, "lexical coverage"),
24
+ FSMTransition(FSMState.INTENT_DETECTION, "intent_class", "bind_entities", ("intent_not_unknown",), FSMState.ENTITY_BINDING, "intent classified"),
25
+ FSMTransition(FSMState.ENTITY_BINDING, "entities", "bind_semantics", ("entities_typed",), FSMState.SEMANTIC_BINDING, "entities resolved"),
26
+ FSMTransition(FSMState.SEMANTIC_BINDING, "semantic_candidate","lock_temporal", ("candidate_valid",), FSMState.TEMPORAL_LOCK, "semantic candidate valid"),
27
+ FSMTransition(FSMState.TEMPORAL_LOCK, "temporal_state", "bind_geometry", ("tau_monotonic",), FSMState.GEOMETRY_BINDING, "tau assigned"),
28
+ FSMTransition(FSMState.GEOMETRY_BINDING, "geometry", "build_matrix", ("geometry_consistent",), FSMState.MATRIX_FORM, "dimensions compatible"),
29
+ FSMTransition(FSMState.MATRIX_FORM, "semantic_matrix", "resolve_boolean", ("matrix_well_formed",), FSMState.BOOLEAN_ROOT, "matrix non-degenerate"),
30
+ FSMTransition(FSMState.BOOLEAN_ROOT, "boolean_expr", "normalize_boolean", ("boolean_valid",), FSMState.BOOLEAN_NORMALIZATION, "boolean well-formed"),
31
+ FSMTransition(FSMState.BOOLEAN_NORMALIZATION,"normalized_boolean","tokenize", ("normal_form_stable",), FSMState.TOKENIZATION, "normalization idempotent"),
32
+ FSMTransition(FSMState.TOKENIZATION, "token_stream", "canonicalize_latin", ("tokens_round_trip",), FSMState.LATIN_CANONICALIZATION, "encode/decode invariant"),
33
+ FSMTransition(FSMState.LATIN_CANONICALIZATION,"latin_form", "lex_latin", ("vocabulary_closed",), FSMState.LATIN_LEXING, "vocabulary complete"),
34
+ FSMTransition(FSMState.LATIN_LEXING, "latin_tokens", "map_rosetta", ("latin_tokens_valid",), FSMState.ROSETTA_MAPPING, "all tokens mapped"),
35
+ FSMTransition(FSMState.ROSETTA_MAPPING, "rosetta_mapping", "build_subleq", ("mapping_complete",), FSMState.SUBLEQ_IR, "all operations mapped"),
36
+ FSMTransition(FSMState.SUBLEQ_IR, "subleq_program", "verify", ("subleq_valid",), FSMState.VERIFICATION, "program halts"),
37
+ FSMTransition(FSMState.VERIFICATION, "verified_ir", "select_artifact", ("all_checks_pass",), FSMState.ARTIFACT_SELECTION, "verification passed"),
38
+ FSMTransition(FSMState.ARTIFACT_SELECTION, "artifact_type", "emit", ("template_bound",), FSMState.EMISSION, "template resolved"),
39
+ FSMTransition(FSMState.EMISSION, "artifact", "complete", ("artifact_provenance_ok",), FSMState.COMPLETE, "artifact complete"),
40
+ FSMTransition(FSMState.COMPLETE, "done", "noop", (), FSMState.COMPLETE, "terminal"),
41
+ # Error transitions β€” any state can go to ERROR
42
+ FSMTransition(FSMState.INPUT, "invalid", "fail", ("error_logged",), FSMState.ERROR, "fail closed"),
43
+ ]
44
+
45
+ # Build lookup: (source_state, input_class) β†’ transition
46
+ _TRANSITION_MAP: dict[tuple[FSMState, str], FSMTransition] = {
47
+ (t.source_state, t.input_class): t for t in TRANSITION_TABLE
48
+ }
49
+
50
+
51
+ # ── FSM context ───────────────────────────────────────────────────────────────
52
+
53
+ @dataclass
54
+ class FSMContext:
55
+ state: FSMState = FSMState.INPUT
56
+ history: list[tuple[FSMState, str, FSMState]] = field(default_factory=list)
57
+ data: dict[str, object] = field(default_factory=dict)
58
+ error: Optional[str] = None
59
+
60
+ def transition(self, input_class: str) -> FSMTransition:
61
+ key = (self.state, input_class)
62
+ t = _TRANSITION_MAP.get(key)
63
+ if t is None:
64
+ # Also try any-state error transition
65
+ err = _TRANSITION_MAP.get((self.state, "invalid"))
66
+ if err:
67
+ self.history.append((self.state, input_class, FSMState.ERROR))
68
+ self.state = FSMState.ERROR
69
+ self.error = f"No transition from {self.state.name} on input '{input_class}'"
70
+ raise PipelineError(self.state, f"No valid transition on '{input_class}'")
71
+ raise PipelineError(
72
+ self.state,
73
+ f"No transition from {self.state.name} on '{input_class}' β€” fail closed",
74
+ )
75
+ self.history.append((self.state, input_class, t.destination_state))
76
+ self.state = t.destination_state
77
+ return t
78
+
79
+ def set(self, key: str, value: object) -> None:
80
+ self.data[key] = value
81
+
82
+ def get(self, key: str) -> object:
83
+ return self.data.get(key)
84
+
85
+ def is_terminal(self) -> bool:
86
+ return self.state in (FSMState.COMPLETE, FSMState.ERROR)
87
+
88
+ def dump_history(self) -> list[str]:
89
+ return [f"{src.name} --[{inp}]--> {dst.name}" for src, inp, dst in self.history]
90
+
91
+
92
+ # ── Constraint validators ─────────────────────────────────────────────────────
93
+
94
+ ConstraintValidator = Callable[[FSMContext], bool]
95
+
96
+ _VALIDATORS: dict[str, ConstraintValidator] = {
97
+ "text_not_empty": lambda ctx: bool(str(ctx.get("raw_text") or "").strip()),
98
+ "tokens_non_empty": lambda ctx: bool(ctx.get("tokens")),
99
+ "tokens_recognized": lambda ctx: True, # lexer failure raises
100
+ "intent_not_unknown": lambda ctx: ctx.get("intent") != "unknown",
101
+ "entities_typed": lambda ctx: True,
102
+ "candidate_valid": lambda ctx: ctx.get("candidate") is not None,
103
+ "tau_monotonic": lambda ctx: True, # TemporalLock.record enforces this
104
+ "geometry_consistent": lambda ctx: True, # GeometryError raised on violation
105
+ "matrix_well_formed": lambda ctx: True,
106
+ "boolean_valid": lambda ctx: True,
107
+ "normal_form_stable": lambda ctx: True,
108
+ "tokens_round_trip": lambda ctx: True,
109
+ "vocabulary_closed": lambda ctx: True,
110
+ "latin_tokens_valid": lambda ctx: True,
111
+ "mapping_complete": lambda ctx: True,
112
+ "subleq_valid": lambda ctx: True,
113
+ "all_checks_pass": lambda ctx: ctx.get("verified") is True,
114
+ "template_bound": lambda ctx: ctx.get("template") is not None,
115
+ "artifact_provenance_ok": lambda ctx: ctx.get("artifact") is not None,
116
+ "error_logged": lambda ctx: True,
117
+ }
118
+
119
+
120
+ def validate_constraints(transition: FSMTransition, ctx: FSMContext) -> None:
121
+ for c in transition.constraints:
122
+ validator = _VALIDATORS.get(c)
123
+ if validator and not validator(ctx):
124
+ raise PipelineError(
125
+ ctx.state,
126
+ f"Invariant '{c}' violated on transition {transition.source_state.name} "
127
+ f"β†’ {transition.destination_state.name}",
128
+ )
src/semantic_engine/geometry.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 5: Geometry engine β€” explicit dimensional constraints for operations."""
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from typing import Optional
11
+
12
+ from .types import FSMState, GeometryError
13
+ from .ir import Geometry, SemanticNode
14
+
15
+
16
+ @dataclass
17
+ class DimCheck:
18
+ """Result of a dimensional compatibility check."""
19
+ ok: bool
20
+ reason: str
21
+ output: Optional[Geometry] = None
22
+
23
+
24
+ def check_matmul(a: Geometry, b: Geometry) -> DimCheck:
25
+ """A ∈ R^(mΓ—n), B ∈ R^(nΓ—p) β†’ C ∈ R^(mΓ—p). Requires a.col == b.row."""
26
+ if a.col is None or b.row is None:
27
+ return DimCheck(ok=True, reason="underdetermined", output=Geometry())
28
+ if a.col != b.row:
29
+ return DimCheck(
30
+ ok=False,
31
+ reason=f"contraction mismatch: A.col={a.col} β‰  B.row={b.row}",
32
+ )
33
+ return DimCheck(
34
+ ok=True,
35
+ reason="compatible",
36
+ output=Geometry(row=a.row, col=b.col, contraction=a.col),
37
+ )
38
+
39
+
40
+ def check_elementwise(a: Geometry, b: Geometry, op: str) -> DimCheck:
41
+ """Element-wise ops (add, subtract, XOR) require identical shapes."""
42
+ if a.shape and b.shape:
43
+ if a.shape != b.shape:
44
+ return DimCheck(ok=False, reason=f"{op}: shape mismatch {a.shape} β‰  {b.shape}")
45
+ if a.row is not None and b.row is not None and a.row != b.row:
46
+ return DimCheck(ok=False, reason=f"{op}: row mismatch {a.row} β‰  {b.row}")
47
+ if a.col is not None and b.col is not None and a.col != b.col:
48
+ return DimCheck(ok=False, reason=f"{op}: col mismatch {a.col} β‰  {b.col}")
49
+ return DimCheck(ok=True, reason="compatible", output=a)
50
+
51
+
52
+ def enforce_geometry(node: SemanticNode) -> Geometry:
53
+ """
54
+ Validate geometry for a semantic node's operation.
55
+ Raises GeometryError if dimensions are incompatible.
56
+ Returns the output geometry.
57
+ """
58
+ op = node.operation
59
+ operands = node.operands
60
+
61
+ if op is None:
62
+ return node.geometry or Geometry()
63
+
64
+ if op.name in ("multiply", "matmul"):
65
+ if len(operands) < 2:
66
+ return node.geometry or Geometry() # underdetermined β€” not a violation
67
+ a_geom = operands[0].geometry or Geometry()
68
+ b_geom = operands[1].geometry or Geometry()
69
+ result = check_matmul(a_geom, b_geom)
70
+ if not result.ok:
71
+ raise GeometryError(FSMState.GEOMETRY_BINDING, result.reason)
72
+ return result.output or Geometry()
73
+
74
+ if op.name in ("add", "subtract"):
75
+ if len(operands) < 2:
76
+ raise GeometryError(FSMState.GEOMETRY_BINDING, f"{op.name} requires 2 operands")
77
+ a_geom = operands[0].geometry or Geometry()
78
+ b_geom = operands[1].geometry or Geometry()
79
+ result = check_elementwise(a_geom, b_geom, op.name)
80
+ if not result.ok:
81
+ raise GeometryError(FSMState.GEOMETRY_BINDING, result.reason)
82
+ return result.output or Geometry()
83
+
84
+ # Unknown op β€” pass through without geometry enforcement
85
+ return node.geometry or Geometry()
86
+
87
+
88
+ def parse_geometry(spec: str) -> Geometry:
89
+ """
90
+ Parse a geometry spec string like "3x4", "(3,4)", "R^(3x4)", "scalar".
91
+ Returns a Geometry instance.
92
+ """
93
+ import re
94
+ spec = spec.strip()
95
+
96
+ if spec.lower() in ("scalar", "1", "1x1"):
97
+ return Geometry(row=1, col=1, shape=(1, 1))
98
+
99
+ m = re.match(r"[Rr]\^?\(?(\d+)[xΓ—,](\d+)\)?", spec)
100
+ if m:
101
+ r, c = int(m.group(1)), int(m.group(2))
102
+ return Geometry(row=r, col=c, shape=(r, c))
103
+
104
+ m = re.match(r"(\d+)[xΓ—,](\d+)", spec)
105
+ if m:
106
+ r, c = int(m.group(1)), int(m.group(2))
107
+ return Geometry(row=r, col=c, shape=(r, c))
108
+
109
+ m = re.match(r"(\d+)", spec)
110
+ if m:
111
+ n = int(m.group(1))
112
+ return Geometry(row=n, col=1, shape=(n,))
113
+
114
+ return Geometry()
src/semantic_engine/ir.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 2: Semantic IR β€” typed intermediate representation, the canonical source of truth."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ import uuid
11
+ from dataclasses import dataclass, field
12
+ from typing import Any, Optional
13
+
14
+ from .types import (
15
+ ArtifactType, IntentClass, Provenance, SemanticNodeType,
16
+ TemporalViolation, VerificationState, FSMState,
17
+ )
18
+
19
+
20
+ # ── Geometry ──────────────────────────────────────────────────────────────────
21
+
22
+ @dataclass(frozen=True)
23
+ class Geometry:
24
+ """Explicit dimensional description of an operation's operands."""
25
+ row: Optional[int] = None
26
+ col: Optional[int] = None
27
+ contraction: Optional[int] = None
28
+ shape: tuple[int, ...] = field(default_factory=tuple)
29
+ index_labels: tuple[str, ...] = field(default_factory=tuple)
30
+
31
+ def compatible_with(self, other: Geometry) -> bool:
32
+ """Check whether self and other can be contracted (matrix multiply)."""
33
+ if self.col is None or other.row is None:
34
+ return True # undetermined β€” not a violation yet
35
+ return self.col == other.row
36
+
37
+ def output_geometry(self, other: Geometry) -> Geometry:
38
+ if self.row is None or other.col is None:
39
+ return Geometry()
40
+ return Geometry(row=self.row, col=other.col, contraction=self.col)
41
+
42
+
43
+ # ── Temporal state ────────────────────────────────────────────────────────────
44
+
45
+ @dataclass
46
+ class TemporalState:
47
+ tau: int # monotonic integer tick
48
+ label: str = ""
49
+ locked: bool = False
50
+
51
+ def advance(self, reason: str = "") -> TemporalState:
52
+ if self.locked:
53
+ raise TemporalViolation(FSMState.TEMPORAL_LOCK, "Cannot advance a locked temporal state")
54
+ return TemporalState(tau=self.tau + 1, label=reason)
55
+
56
+ def lock(self) -> TemporalState:
57
+ return TemporalState(tau=self.tau, label=self.label, locked=True)
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class TemporalTransition:
62
+ from_tau: int
63
+ to_tau: int
64
+ reason: str
65
+ stage: str
66
+
67
+ def is_valid(self) -> bool:
68
+ return self.to_tau == self.from_tau + 1 or self.to_tau == self.from_tau
69
+
70
+
71
+ @dataclass
72
+ class TemporalLock:
73
+ transitions: list[TemporalTransition] = field(default_factory=list)
74
+
75
+ def record(self, t: TemporalTransition) -> None:
76
+ if not t.is_valid():
77
+ raise TemporalViolation(
78
+ FSMState.TEMPORAL_LOCK,
79
+ f"Invalid temporal jump: Ο„{t.from_tau} β†’ Ο„{t.to_tau} (expected +1 or 0)",
80
+ )
81
+ self.transitions.append(t)
82
+
83
+ def audit(self) -> list[TemporalTransition]:
84
+ return list(self.transitions)
85
+
86
+
87
+ # ── Core IR nodes ─────────────────────────────────────────────────────────────
88
+
89
+ @dataclass
90
+ class Operand:
91
+ name: str
92
+ type_tag: str
93
+ value: Any = None
94
+ geometry: Optional[Geometry] = None
95
+
96
+ @property
97
+ def node_id(self) -> str:
98
+ return hashlib.sha256(f"operand:{self.name}:{self.type_tag}".encode()).hexdigest()[:16]
99
+
100
+
101
+ @dataclass
102
+ class Constraint:
103
+ constraint_id: str
104
+ description: str
105
+ predicate: str
106
+ operands: list[str] = field(default_factory=list)
107
+
108
+ @staticmethod
109
+ def make(description: str, predicate: str, operands: list[str] | None = None) -> Constraint:
110
+ cid = hashlib.sha256(f"{predicate}:{description}".encode()).hexdigest()[:16]
111
+ return Constraint(
112
+ constraint_id=cid,
113
+ description=description,
114
+ predicate=predicate,
115
+ operands=operands or [],
116
+ )
117
+
118
+
119
+ @dataclass
120
+ class SemanticOperation:
121
+ name: str
122
+ arity: int
123
+ description: str = ""
124
+ associative: bool = False
125
+ commutative: bool = False
126
+
127
+ @property
128
+ def op_id(self) -> str:
129
+ return hashlib.sha256(f"op:{self.name}:{self.arity}".encode()).hexdigest()[:16]
130
+
131
+
132
+ @dataclass
133
+ class SemanticNode:
134
+ id: str
135
+ type: SemanticNodeType
136
+ operation: Optional[SemanticOperation]
137
+ operands: list[Operand]
138
+ constraints: list[Constraint]
139
+ geometry: Optional[Geometry]
140
+ temporal_state: TemporalState
141
+ provenance: Provenance
142
+ children: list[SemanticNode] = field(default_factory=list)
143
+ metadata: dict[str, Any] = field(default_factory=dict)
144
+
145
+ @staticmethod
146
+ def make(
147
+ node_type: SemanticNodeType,
148
+ operation: Optional[SemanticOperation],
149
+ operands: list[Operand],
150
+ constraints: list[Constraint],
151
+ geometry: Optional[Geometry],
152
+ temporal_state: TemporalState,
153
+ provenance: Provenance,
154
+ ) -> SemanticNode:
155
+ content = f"{node_type}:{operation}:{[o.name for o in operands]}"
156
+ node_id = hashlib.sha256(content.encode()).hexdigest()[:24]
157
+ return SemanticNode(
158
+ id=node_id,
159
+ type=node_type,
160
+ operation=operation,
161
+ operands=operands,
162
+ constraints=constraints,
163
+ geometry=geometry,
164
+ temporal_state=temporal_state,
165
+ provenance=provenance,
166
+ )
167
+
168
+ def hash(self) -> str:
169
+ content = f"{self.id}:{self.type}:{self.operation}:{len(self.operands)}"
170
+ return hashlib.sha256(content.encode()).hexdigest()
171
+
172
+
173
+ # ── Semantic IR root ──────────────────────────────────────────────────────────
174
+
175
+ @dataclass
176
+ class SemanticIR:
177
+ ir_id: str
178
+ intent: IntentClass
179
+ root: SemanticNode
180
+ nodes: list[SemanticNode]
181
+ temporal_lock: TemporalLock
182
+ provenance: Provenance
183
+ verification_state: VerificationState = VerificationState.UNVERIFIED
184
+ output_mode: str = "natural_language"
185
+ metadata: dict[str, Any] = field(default_factory=dict)
186
+
187
+ @staticmethod
188
+ def create(
189
+ intent: IntentClass,
190
+ root: SemanticNode,
191
+ nodes: list[SemanticNode],
192
+ temporal_lock: TemporalLock,
193
+ provenance: Provenance,
194
+ ) -> SemanticIR:
195
+ ir_id = str(uuid.uuid4())
196
+ return SemanticIR(
197
+ ir_id=ir_id,
198
+ intent=intent,
199
+ root=root,
200
+ nodes=nodes,
201
+ temporal_lock=temporal_lock,
202
+ provenance=provenance,
203
+ )
204
+
205
+ def hash(self) -> str:
206
+ node_hashes = ":".join(n.hash() for n in self.nodes)
207
+ return hashlib.sha256(f"{self.ir_id}:{self.intent}:{node_hashes}".encode()).hexdigest()
208
+
209
+ def add_node(self, node: SemanticNode) -> None:
210
+ self.nodes.append(node)
211
+
212
+ def find_node(self, node_id: str) -> Optional[SemanticNode]:
213
+ return next((n for n in self.nodes if n.id == node_id), None)
214
+
215
+
216
+ # ── Artifact IR ───────────────────────────────────────────────────────────────
217
+
218
+ @dataclass
219
+ class Artifact:
220
+ id: str
221
+ type: ArtifactType
222
+ semantic_root: str # SemanticIR.ir_id
223
+ template: str
224
+ content: str
225
+ provenance: Provenance
226
+ temporal_state: TemporalState
227
+ verification_state: VerificationState = VerificationState.UNVERIFIED
228
+ metadata: dict[str, Any] = field(default_factory=dict)
229
+
230
+ @staticmethod
231
+ def create(
232
+ artifact_type: ArtifactType,
233
+ semantic_root: str,
234
+ template: str,
235
+ content: str,
236
+ provenance: Provenance,
237
+ temporal_state: TemporalState,
238
+ ) -> Artifact:
239
+ artifact_id = hashlib.sha256(
240
+ f"{artifact_type}:{semantic_root}:{content[:64]}".encode()
241
+ ).hexdigest()[:24]
242
+ return Artifact(
243
+ id=artifact_id,
244
+ type=artifact_type,
245
+ semantic_root=semantic_root,
246
+ template=template,
247
+ content=content,
248
+ provenance=provenance,
249
+ temporal_state=temporal_state,
250
+ )
251
+
252
+ def hash(self) -> str:
253
+ return hashlib.sha256(
254
+ f"{self.id}:{self.type}:{self.content}".encode()
255
+ ).hexdigest()
src/semantic_engine/latin.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 9: Latin canonicalizer β€” controlled vocabulary mapping.
6
+
7
+ Boolean tokens β†’ Semantic IDs β†’ Latin lexemes.
8
+ Every Latin lexeme maps to a stable semantic_id. No uncontrolled synonyms.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+ from .token_engine import (
19
+ BoolToken, BooleanTokenStream,
20
+ TTYPE_AND, TTYPE_OR, TTYPE_NOT, TTYPE_XOR,
21
+ TTYPE_IMPLIES, TTYPE_EQUIVALENCE, TTYPE_TRUE, TTYPE_FALSE,
22
+ TTYPE_OPEN, TTYPE_CLOSE, TTYPE_VAR, TTYPE_VAR_NEG,
23
+ )
24
+ from .types import FSMState, PipelineError
25
+
26
+ # Map from token type β†’ canonical Latin lexeme
27
+ _TTYPE_TO_LATIN: dict[int, str] = {
28
+ TTYPE_AND: "ET",
29
+ TTYPE_OR: "VEL",
30
+ TTYPE_NOT: "NON",
31
+ TTYPE_XOR: "AUT",
32
+ TTYPE_IMPLIES: "SI",
33
+ TTYPE_EQUIVALENCE: "IFF",
34
+ TTYPE_TRUE: "VERUM",
35
+ TTYPE_FALSE: "FALSUM",
36
+ TTYPE_OPEN: "INITIUM",
37
+ TTYPE_CLOSE: "FINIS",
38
+ }
39
+
40
+ _LATIN_TO_TTYPE: dict[str, int] = {v: k for k, v in _TTYPE_TO_LATIN.items()}
41
+
42
+
43
+ # ── Latin token ───────────────────────────────────────────────────────────────
44
+
45
+ @dataclass(frozen=True)
46
+ class LatinToken:
47
+ lexeme: str
48
+ semantic_id: str
49
+ position: int
50
+ payload: str = "" # variable name for VAR tokens
51
+
52
+
53
+ @dataclass
54
+ class LatinLexeme:
55
+ word: str
56
+ semantic_id: str
57
+ operator: Optional[str]
58
+ arity: int
59
+
60
+
61
+ @dataclass
62
+ class LatinCanonicalForm:
63
+ tokens: list[LatinToken] = field(default_factory=list)
64
+ source_hash: str = ""
65
+
66
+ def text(self) -> str:
67
+ return " ".join(t.lexeme if not t.payload else f"{t.lexeme}[{t.payload}]" for t in self.tokens)
68
+
69
+
70
+ # ── Vocabulary loader ─────────────────────────────────────────────────────────
71
+
72
+ def _load_vocabulary() -> dict[str, LatinLexeme]:
73
+ vocab_path = Path(__file__).parent.parent.parent / "lexicon" / "latin_vocabulary.json"
74
+ if not vocab_path.exists():
75
+ return {}
76
+ data = json.loads(vocab_path.read_text())
77
+ return {
78
+ word: LatinLexeme(
79
+ word=word,
80
+ semantic_id=entry["semantic_id"],
81
+ operator=entry.get("operator"),
82
+ arity=entry.get("arity", 0),
83
+ )
84
+ for word, entry in data.get("lexemes", {}).items()
85
+ }
86
+
87
+
88
+ _VOCABULARY: dict[str, LatinLexeme] = {}
89
+
90
+
91
+ def vocabulary() -> dict[str, LatinLexeme]:
92
+ global _VOCABULARY
93
+ if not _VOCABULARY:
94
+ _VOCABULARY = _load_vocabulary()
95
+ return _VOCABULARY
96
+
97
+
98
+ # ── Canonicalizer ─────────────────────────────────────────────────────────────
99
+
100
+ class LatinCanonicalizer:
101
+ """Transform a BooleanTokenStream into a LatinCanonicalForm."""
102
+
103
+ def canonicalize(self, stream: BooleanTokenStream) -> LatinCanonicalForm:
104
+ vocab = vocabulary()
105
+ tokens: list[LatinToken] = []
106
+
107
+ for i, tok in enumerate(stream.tokens):
108
+ if tok.token_type in (TTYPE_VAR, TTYPE_VAR_NEG):
109
+ lexeme = "NON" if tok.token_type == TTYPE_VAR_NEG else "OPUS"
110
+ sem_id = "BOOL_NOT" if tok.token_type == TTYPE_VAR_NEG else "SEMANTIC_OP"
111
+ var_name = tok.payload.decode("utf-8") if tok.payload else ""
112
+ if tok.token_type == TTYPE_VAR_NEG:
113
+ tokens.append(LatinToken(lexeme="NON", semantic_id="BOOL_NOT", position=i))
114
+ tokens.append(LatinToken(
115
+ lexeme="OPUS",
116
+ semantic_id="SEMANTIC_OP",
117
+ position=i,
118
+ payload=var_name,
119
+ ))
120
+ continue
121
+
122
+ latin = _TTYPE_TO_LATIN.get(tok.token_type)
123
+ if latin is None:
124
+ raise PipelineError(
125
+ FSMState.LATIN_CANONICALIZATION,
126
+ f"No Latin mapping for token type 0x{tok.token_type:02x} at position {i}",
127
+ )
128
+
129
+ lexeme_def = vocab.get(latin)
130
+ if lexeme_def is None:
131
+ raise PipelineError(
132
+ FSMState.LATIN_CANONICALIZATION,
133
+ f"Latin lexeme '{latin}' not in controlled vocabulary",
134
+ )
135
+
136
+ tokens.append(LatinToken(
137
+ lexeme=latin,
138
+ semantic_id=lexeme_def.semantic_id,
139
+ position=i,
140
+ ))
141
+
142
+ import hashlib
143
+ src_hash = hashlib.sha256(" ".join(t.lexeme for t in tokens).encode()).hexdigest()[:16]
144
+ return LatinCanonicalForm(tokens=tokens, source_hash=src_hash)
145
+
146
+
147
+ # ── Inverse: Latin β†’ semantic IDs ─────────────────────────────────────────────
148
+
149
+ def latin_to_semantic_ids(form: LatinCanonicalForm) -> list[tuple[str, str]]:
150
+ """Return list of (lexeme, semantic_id) pairs."""
151
+ return [(t.lexeme, t.semantic_id) for t in form.tokens]
src/semantic_engine/lexer.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 10: Regex Latin lexer β€” token recognition and field extraction.
6
+
7
+ The lexer recognizes structure. It does NOT determine semantic meaning.
8
+ Invalid Latin blocks fail closed.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from dataclasses import dataclass, field
15
+ from typing import Optional
16
+
17
+ from .latin import LatinCanonicalForm, LatinToken, vocabulary
18
+ from .types import FSMState, PipelineError
19
+
20
+
21
+ # ── Regex patterns ────────────────────────────────────────────────────────────
22
+
23
+ # Block delimiters
24
+ _RE_BLOCK_OPEN = re.compile(r"\bINITIUM\b")
25
+ _RE_BLOCK_CLOSE = re.compile(r"\bFINIS\b")
26
+
27
+ # Operators
28
+ _RE_OPERATORS = re.compile(
29
+ r"\b(ET|VEL|NON|AUT|SI|IFF|VERUM|FALSUM)\b"
30
+ )
31
+
32
+ # Operands with optional payload: OPUS[varname]
33
+ _RE_OPERAND = re.compile(r"\b(OPUS|FORMA|COMPUTA|MULTIPLICA|ADDE|SUBTRAHE|PARSE|"
34
+ r"AEDIFICA|GENERA|VERIFICA|CONVERTE|RESTITUE|"
35
+ r"MATRIX|VECTOR|PARSER|SERVITIUM|FORMA_BINARIA|TEMPUS)"
36
+ r"(?:\[([^\]]*)\])?")
37
+
38
+ # Instruction fields: KEYWORD:VALUE
39
+ _RE_FIELD = re.compile(r"\b([A-Z_]+):(\S+)")
40
+
41
+ # Delimiters β€” semicolons, pipes
42
+ _RE_DELIM = re.compile(r"[;|]")
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class LexedToken:
47
+ kind: str # "operator", "operand", "open", "close", "field", "delim", "unknown"
48
+ lexeme: str
49
+ payload: str = ""
50
+ position: int = 0
51
+
52
+
53
+ @dataclass
54
+ class LexResult:
55
+ tokens: list[LexedToken] = field(default_factory=list)
56
+ errors: list[str] = field(default_factory=list)
57
+ valid: bool = True
58
+
59
+
60
+ # ── Lexer ─────────────────────────────────────────────────────────────────────
61
+
62
+ class LatinLexer:
63
+ """
64
+ Regex-based lexer for Latin canonical form text.
65
+ Produces LexResult with typed tokens.
66
+ """
67
+
68
+ def lex(self, text: str) -> LexResult:
69
+ result = LexResult()
70
+ vocab = vocabulary()
71
+ pos = 0
72
+ tpos = 0
73
+
74
+ # Tokenize by scanning left to right
75
+ while pos < len(text):
76
+ # Skip whitespace
77
+ ws = re.match(r"\s+", text[pos:])
78
+ if ws:
79
+ pos += ws.end()
80
+ continue
81
+
82
+ # Block open/close
83
+ m = _RE_BLOCK_OPEN.match(text, pos)
84
+ if m:
85
+ result.tokens.append(LexedToken("open", "INITIUM", position=tpos))
86
+ pos = m.end(); tpos += 1; continue
87
+
88
+ m = _RE_BLOCK_CLOSE.match(text, pos)
89
+ if m:
90
+ result.tokens.append(LexedToken("close", "FINIS", position=tpos))
91
+ pos = m.end(); tpos += 1; continue
92
+
93
+ # Operands (with optional [payload])
94
+ m = _RE_OPERAND.match(text, pos)
95
+ if m:
96
+ lexeme = m.group(1)
97
+ payload = m.group(2) or ""
98
+ if lexeme not in vocab:
99
+ result.errors.append(f"Unknown lexeme '{lexeme}' at pos {pos}")
100
+ result.valid = False
101
+ result.tokens.append(LexedToken("operand", lexeme, payload, tpos))
102
+ pos = m.end(); tpos += 1; continue
103
+
104
+ # Operators
105
+ m = _RE_OPERATORS.match(text, pos)
106
+ if m:
107
+ lexeme = m.group(1)
108
+ if lexeme not in vocab:
109
+ result.errors.append(f"Unknown operator '{lexeme}' at pos {pos}")
110
+ result.valid = False
111
+ result.tokens.append(LexedToken("operator", lexeme, position=tpos))
112
+ pos = m.end(); tpos += 1; continue
113
+
114
+ # Field annotations
115
+ m = _RE_FIELD.match(text, pos)
116
+ if m:
117
+ result.tokens.append(LexedToken("field", m.group(1), m.group(2), tpos))
118
+ pos = m.end(); tpos += 1; continue
119
+
120
+ # Delimiters
121
+ m = _RE_DELIM.match(text, pos)
122
+ if m:
123
+ result.tokens.append(LexedToken("delim", m.group(0), position=tpos))
124
+ pos = m.end(); tpos += 1; continue
125
+
126
+ # Unknown character/word β€” fail closed
127
+ word_m = re.match(r"\S+", text[pos:])
128
+ unknown = word_m.group(0) if word_m else text[pos]
129
+ result.errors.append(f"Unrecognized token '{unknown}' at position {pos}")
130
+ result.valid = False
131
+ pos += len(unknown); tpos += 1
132
+
133
+ if not result.valid:
134
+ raise PipelineError(
135
+ FSMState.LATIN_LEXING,
136
+ "Latin lexer errors: " + "; ".join(result.errors),
137
+ )
138
+
139
+ return result
140
+
141
+
142
+ def lex_canonical(self, form: LatinCanonicalForm) -> LexResult:
143
+ """Lex from a LatinCanonicalForm directly."""
144
+ return self.lex(form.text())
src/semantic_engine/matrix.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 6: Matrix semantic layer β€” semantic operations represented as matrix structures.
6
+
7
+ Semantic matrix β‰  numerical matrix.
8
+ A SemanticMatrix captures the structure of an operation (operand slots, geometry,
9
+ relationships) in a matrix-like form. Matrix data and semantic matrix are distinct types.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass, field
15
+ from typing import Optional
16
+
17
+ from .ir import Geometry, Operand, SemanticNode, SemanticOperation
18
+ from .types import FSMState, GeometryError, SemanticNodeType
19
+
20
+
21
+ # ── Semantic matrix cell ──────────────────────────────────────────────────────
22
+
23
+ @dataclass
24
+ class SemanticCell:
25
+ row: int
26
+ col: int
27
+ operand: Optional[Operand] = None
28
+ label: str = ""
29
+ role: str = "" # "input", "output", "contraction", "constraint"
30
+
31
+
32
+ # ── Semantic matrix ───────────────────────────────────────────────────────────
33
+
34
+ @dataclass
35
+ class SemanticMatrix:
36
+ operation: SemanticOperation
37
+ rows: int
38
+ cols: int
39
+ cells: list[list[SemanticCell]] = field(default_factory=list)
40
+ geometry: Optional[Geometry] = None
41
+ node_id: str = ""
42
+
43
+ def __post_init__(self):
44
+ if not self.cells:
45
+ self.cells = [
46
+ [SemanticCell(row=r, col=c) for c in range(self.cols)]
47
+ for r in range(self.rows)
48
+ ]
49
+
50
+ def cell(self, row: int, col: int) -> SemanticCell:
51
+ if row >= self.rows or col >= self.cols:
52
+ raise IndexError(f"SemanticMatrix[{row},{col}] out of bounds ({self.rows}x{self.cols})")
53
+ return self.cells[row][col]
54
+
55
+ def set_cell(self, row: int, col: int, operand: Optional[Operand], role: str, label: str = "") -> None:
56
+ c = self.cell(row, col)
57
+ c.operand = operand
58
+ c.role = role
59
+ c.label = label
60
+
61
+ def column(self, col: int) -> list[SemanticCell]:
62
+ return [self.cells[r][col] for r in range(self.rows)]
63
+
64
+ def row_vec(self, row: int) -> list[SemanticCell]:
65
+ return list(self.cells[row])
66
+
67
+ def to_dict(self) -> dict:
68
+ return {
69
+ "operation": self.operation.name,
70
+ "shape": (self.rows, self.cols),
71
+ "geometry": {
72
+ "row": self.geometry.row if self.geometry else None,
73
+ "col": self.geometry.col if self.geometry else None,
74
+ },
75
+ "cells": [
76
+ [{"row": c.row, "col": c.col, "role": c.role, "label": c.label} for c in row]
77
+ for row in self.cells
78
+ ],
79
+ }
80
+
81
+
82
+ # ── Builder ───────────────────────────────────────────────────────────────────
83
+
84
+ class SemanticMatrixBuilder:
85
+ """Construct a SemanticMatrix from a SemanticNode."""
86
+
87
+ def build(self, node: SemanticNode) -> SemanticMatrix:
88
+ op = node.operation
89
+ if op is None:
90
+ op = SemanticOperation(name="identity", arity=1)
91
+
92
+ operands = node.operands
93
+ n_ops = len(operands)
94
+
95
+ if op.name in ("multiply", "matmul"):
96
+ return self._build_matmul(op, operands, node.geometry)
97
+
98
+ if op.name in ("add", "subtract"):
99
+ return self._build_binary(op, operands, node.geometry)
100
+
101
+ # Generic: one row per operand, two cols (input / output)
102
+ mat = SemanticMatrix(operation=op, rows=max(n_ops, 1), cols=2, geometry=node.geometry)
103
+ for i, operand in enumerate(operands):
104
+ mat.set_cell(i, 0, operand, role="input", label=operand.name)
105
+ return mat
106
+
107
+ def _build_matmul(
108
+ self, op: SemanticOperation, operands: list[Operand], geom: Optional[Geometry]
109
+ ) -> SemanticMatrix:
110
+ """
111
+ Matrix multiply: A(mΓ—n) @ B(nΓ—p) β†’ C(mΓ—p)
112
+ Represent as 3x3:
113
+ [A_input] [contract] [B_input]
114
+ [m_dim] [n_dim] [p_dim]
115
+ [output] [output] [output]
116
+ """
117
+ mat = SemanticMatrix(operation=op, rows=3, cols=3, geometry=geom)
118
+ a = operands[0] if len(operands) > 0 else None
119
+ b = operands[1] if len(operands) > 1 else None
120
+
121
+ mat.set_cell(0, 0, a, role="input", label=a.name if a else "A")
122
+ mat.set_cell(0, 2, b, role="input", label=b.name if b else "B")
123
+ mat.set_cell(0, 1, None, role="contraction", label="n")
124
+
125
+ if geom:
126
+ mat.set_cell(1, 0, None, role="dim", label=str(geom.row or "m"))
127
+ mat.set_cell(1, 1, None, role="dim", label=str(geom.contraction or "n"))
128
+ mat.set_cell(1, 2, None, role="dim", label=str(geom.col or "p"))
129
+
130
+ mat.set_cell(2, 0, None, role="output", label="C")
131
+ mat.set_cell(2, 1, None, role="output", label="(mΓ—p)")
132
+
133
+ return mat
134
+
135
+ def _build_binary(
136
+ self, op: SemanticOperation, operands: list[Operand], geom: Optional[Geometry]
137
+ ) -> SemanticMatrix:
138
+ mat = SemanticMatrix(operation=op, rows=2, cols=2, geometry=geom)
139
+ a = operands[0] if len(operands) > 0 else None
140
+ b = operands[1] if len(operands) > 1 else None
141
+ mat.set_cell(0, 0, a, role="input", label=a.name if a else "A")
142
+ mat.set_cell(0, 1, b, role="input", label=b.name if b else "B")
143
+ mat.set_cell(1, 0, None, role="output", label="result")
144
+ return mat
145
+
146
+
147
+ # ── Transform interface ───────────────────────────────────────────────────────
148
+
149
+ def transform(a: SemanticMatrix, b: SemanticMatrix) -> SemanticMatrix:
150
+ """
151
+ Compose two semantic matrices β€” analogous to function composition.
152
+ a: R^(mΓ—n), b: R^(nΓ—p) β†’ R^(mΓ—p)
153
+ """
154
+ if a.cols != b.rows:
155
+ raise GeometryError(
156
+ FSMState.MATRIX_FORM,
157
+ f"SemanticMatrix composition: a.cols={a.cols} β‰  b.rows={b.rows}",
158
+ )
159
+ composed_op = SemanticOperation(
160
+ name=f"{a.operation.name}∘{b.operation.name}",
161
+ arity=a.operation.arity,
162
+ )
163
+ out_geom: Optional[Geometry] = None
164
+ if a.geometry and b.geometry:
165
+ out_geom = a.geometry.output_geometry(b.geometry)
166
+
167
+ mat = SemanticMatrix(operation=composed_op, rows=a.rows, cols=b.cols, geometry=out_geom)
168
+ # Populate diagonal β€” first operands of a flow into outputs of b
169
+ for r in range(min(a.rows, b.rows)):
170
+ src = a.cells[r][0]
171
+ mat.set_cell(r, 0, src.operand, role="composed", label=src.label)
172
+ return mat
src/semantic_engine/nlp.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """NLP subsystem β€” tokenization, entity extraction, intent classification, BERT interface.
6
+
7
+ BERT operates as an internal interpretive tool only. Output is SemanticCandidate,
8
+ never executable code. The FSM remains the admissibility gate.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from dataclasses import dataclass, field
15
+ from typing import Optional
16
+
17
+ from .types import IntentClass, NormalizedInput, RawInput, SemanticCandidate
18
+
19
+
20
+ # ── Controlled vocabulary for intent ─────────────────────────────────────────
21
+
22
+ _INTENT_PATTERNS: list[tuple[IntentClass, list[str]]] = [
23
+ (IntentClass.BUILD, ["build", "create", "make", "construct", "implement", "write"]),
24
+ (IntentClass.TRANSFORM, ["transform", "convert", "translate", "map", "rewrite", "change"]),
25
+ (IntentClass.VALIDATE, ["validate", "verify", "check", "confirm", "ensure", "test"]),
26
+ (IntentClass.GENERATE, ["generate", "produce", "emit", "output", "render"]),
27
+ (IntentClass.PARSE, ["parse", "read", "decode", "lex", "scan", "interpret"]),
28
+ (IntentClass.COMPUTE, ["compute", "calculate", "multiply", "add", "subtract", "solve"]),
29
+ (IntentClass.RECONSTRUCT, ["reconstruct", "restore", "recover", "rebuild", "regenerate"]),
30
+ ]
31
+
32
+ _OPERATOR_PATTERNS = re.compile(
33
+ r"\b(multiply|add|subtract|divide|transform|convert|validate|parse|build|generate|"
34
+ r"compute|verify|reconstruct|normalize|encode|decode|serialize|deserialize)\b",
35
+ re.IGNORECASE,
36
+ )
37
+
38
+ _ENTITY_PATTERNS = re.compile(
39
+ r"\b(matrix|vector|parser|backend|service|function|module|component|token|"
40
+ r"format|stream|buffer|table|schema|graph|tree|list|array|string|integer|"
41
+ r"boolean|packet|message|frame|field|register)\b",
42
+ re.IGNORECASE,
43
+ )
44
+
45
+ _CONSTRAINT_PATTERNS = re.compile(
46
+ r"\b(deterministic|immutable|idempotent|commutative|associative|invertible|"
47
+ r"ordered|typed|bounded|validated|correct|minimal|canonical|stable)\b",
48
+ re.IGNORECASE,
49
+ )
50
+
51
+ _RELATIONSHIP_PATTERN = re.compile(
52
+ r"(\w+)\s+(of|from|to|into|for|with|by|through|via|as)\s+(\w+)",
53
+ re.IGNORECASE,
54
+ )
55
+
56
+
57
+ # ── Tokenizer ─────────────────────────────────────────────────────────────────
58
+
59
+ def tokenize(text: str) -> list[str]:
60
+ return re.findall(r"\w+|[^\w\s]", text)
61
+
62
+
63
+ def segment_sentences(text: str) -> list[str]:
64
+ return [s.strip() for s in re.split(r"(?<=[.!?])\s+", text) if s.strip()]
65
+
66
+
67
+ # ── Rule-based NLP (no external model required) ───────────────────────────────
68
+
69
+ def classify_intent(text: str) -> tuple[IntentClass, float]:
70
+ lower = text.lower()
71
+ scores: dict[IntentClass, int] = {}
72
+ for intent, keywords in _INTENT_PATTERNS:
73
+ for kw in keywords:
74
+ if kw in lower:
75
+ scores[intent] = scores.get(intent, 0) + 1
76
+ if not scores:
77
+ return IntentClass.UNKNOWN, 0.3
78
+ best = max(scores, key=lambda k: scores[k])
79
+ confidence = min(0.5 + scores[best] * 0.15, 0.95)
80
+ return best, confidence
81
+
82
+
83
+ def extract_entities(text: str) -> list[str]:
84
+ return list({m.group(0).lower() for m in _ENTITY_PATTERNS.finditer(text)})
85
+
86
+
87
+ def extract_operators(text: str) -> list[str]:
88
+ return list({m.group(0).lower() for m in _OPERATOR_PATTERNS.finditer(text)})
89
+
90
+
91
+ def extract_constraints(text: str) -> list[str]:
92
+ return list({m.group(0).lower() for m in _CONSTRAINT_PATTERNS.finditer(text)})
93
+
94
+
95
+ def extract_relationships(text: str) -> list[tuple[str, str, str]]:
96
+ return [(m.group(1).lower(), m.group(2).lower(), m.group(3).lower())
97
+ for m in _RELATIONSHIP_PATTERN.finditer(text)]
98
+
99
+
100
+ def detect_ambiguity(text: str) -> bool:
101
+ ambiguous_markers = re.compile(
102
+ r"\b(it|this|that|they|something|somehow|maybe|perhaps|possibly|"
103
+ r"or something|etc|and so on)\b",
104
+ re.IGNORECASE,
105
+ )
106
+ return bool(ambiguous_markers.search(text))
107
+
108
+
109
+ # ── BERT interface ────────────────────────────────────────────────────────────
110
+
111
+ class BertDecoder:
112
+ """
113
+ Internal BERT interface for semantic interpretation.
114
+
115
+ If the `transformers` package is available and a model is configured,
116
+ uses real BERT embeddings. Otherwise falls back to rule-based analysis.
117
+
118
+ BERT output is always SemanticCandidate β€” never executable code.
119
+ The FSM is the admissibility gate regardless of BERT confidence.
120
+ """
121
+
122
+ def __init__(self, model_name: Optional[str] = None):
123
+ self._model = None
124
+ self._tokenizer = None
125
+ self._use_bert = False
126
+
127
+ if model_name:
128
+ try:
129
+ from transformers import AutoModel, AutoTokenizer # type: ignore
130
+ import torch # type: ignore
131
+ self._tokenizer = AutoTokenizer.from_pretrained(model_name)
132
+ self._model = AutoModel.from_pretrained(model_name)
133
+ self._torch = torch
134
+ self._use_bert = True
135
+ except ImportError:
136
+ pass # fall back to rule-based
137
+
138
+ def _bert_embeddings(self, text: str) -> Optional[list[float]]:
139
+ if not self._use_bert:
140
+ return None
141
+ inputs = self._tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
142
+ with self._torch.no_grad():
143
+ outputs = self._model(**inputs)
144
+ cls_embedding = outputs.last_hidden_state[:, 0, :].squeeze().tolist()
145
+ return cls_embedding if isinstance(cls_embedding, list) else [cls_embedding]
146
+
147
+ def decode(self, normalized: NormalizedInput) -> SemanticCandidate:
148
+ text = normalized.text
149
+ intent, confidence = classify_intent(text)
150
+ entities = extract_entities(text)
151
+ operators = extract_operators(text)
152
+ constraints = extract_constraints(text)
153
+ rels = extract_relationships(text)
154
+ ambiguous = detect_ambiguity(text)
155
+ embeddings = self._bert_embeddings(text)
156
+
157
+ return SemanticCandidate(
158
+ input_ref=normalized.original.input_id,
159
+ intent=intent,
160
+ entities=entities,
161
+ operators=operators,
162
+ constraints=constraints,
163
+ relationships=rels,
164
+ confidence=confidence,
165
+ ambiguous=ambiguous,
166
+ raw_embeddings=embeddings,
167
+ )
168
+
169
+
170
+ # ── Input normalization ───────────────────────────────────────────────────────
171
+
172
+ def normalize_input(raw: RawInput) -> NormalizedInput:
173
+ text = raw.text.strip()
174
+ text = re.sub(r"\s+", " ", text)
175
+ tokens = tuple(tokenize(text))
176
+ sentences = tuple(segment_sentences(text))
177
+ return NormalizedInput(original=raw, text=text, tokens=tokens, sentences=sentences)
src/semantic_engine/pipeline.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Full pipeline β€” orchestrates all stages from RawInput to Artifact."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ import time
11
+ from dataclasses import dataclass, field
12
+ from typing import Optional
13
+
14
+ from .types import (
15
+ ArtifactType, FSMState, IntentClass, OutputMode,
16
+ PipelineError, Provenance, RawInput, SemanticNodeType, VerificationState,
17
+ )
18
+ from .ir import (
19
+ Artifact, Constraint, Geometry, Operand, SemanticIR, SemanticNode,
20
+ SemanticOperation, TemporalLock, TemporalState,
21
+ )
22
+ from .nlp import BertDecoder, normalize_input
23
+ from .fsm import FSMContext, validate_constraints
24
+ from .temporal import apply_temporal, stamp
25
+ from .geometry import enforce_geometry
26
+ from .matrix import SemanticMatrixBuilder
27
+ from .boolean_engine import semantic_constraints_to_boolean, normalize as bool_normalize
28
+ from .token_engine import BoolEncoder, encode_binary
29
+ from .latin import LatinCanonicalizer
30
+ from .lexer import LatinLexer
31
+ from .rosetta import RosettaMapper
32
+ from .subleq import build_program_from_rosetta, SubleqVM
33
+ from .verifier import Verifier
34
+ from .artifacts import ArtifactEngine
35
+
36
+
37
+ @dataclass
38
+ class PipelineResult:
39
+ ir: Optional[SemanticIR] = None
40
+ artifact: Optional[Artifact] = None
41
+ subleq_listing: str = ""
42
+ trace: list[str] = field(default_factory=list)
43
+ hashes: dict[str, str] = field(default_factory=dict)
44
+ verification_state: VerificationState = VerificationState.UNVERIFIED
45
+ error: Optional[str] = None
46
+
47
+ @property
48
+ def ok(self) -> bool:
49
+ return self.error is None and self.verification_state == VerificationState.VERIFIED
50
+
51
+
52
+ class SemanticPipeline:
53
+ def __init__(self, bert_model: Optional[str] = None, output_mode: OutputMode = OutputMode.NATURAL_LANGUAGE):
54
+ self._bert = BertDecoder(bert_model)
55
+ self._matrix = SemanticMatrixBuilder()
56
+ self._latin = LatinCanonicalizer()
57
+ self._lexer = LatinLexer()
58
+ self._rosetta = RosettaMapper()
59
+ self._verifier = Verifier()
60
+ self._artifacts = ArtifactEngine()
61
+ self._output_mode = output_mode
62
+
63
+ def run(self, raw: RawInput, output_mode: Optional[OutputMode] = None) -> PipelineResult:
64
+ mode = output_mode or self._output_mode
65
+ ctx = FSMContext()
66
+ result = PipelineResult()
67
+ result.trace.append(f"INPUT: {raw.input_id}")
68
+ result.hashes["input"] = raw.hash
69
+
70
+ try:
71
+ # PHASE: INPUT β†’ NORMALIZE
72
+ ctx.transition("raw_input")
73
+ normalized = normalize_input(raw)
74
+ ctx.set("raw_text", normalized.text)
75
+ ctx.set("tokens", normalized.tokens)
76
+ result.trace.append(f"NORMALIZE: {len(normalized.tokens)} tokens")
77
+
78
+ # PHASE: NORMALIZE β†’ LEXICAL_ANALYSIS
79
+ ctx.transition("normalized_input")
80
+ result.trace.append(f"LEXICAL_ANALYSIS: {len(normalized.sentences)} sentences")
81
+
82
+ # PHASE: LEXICAL_ANALYSIS β†’ INTENT_DETECTION
83
+ ctx.transition("token_stream")
84
+ candidate = self._bert.decode(normalized)
85
+ ctx.set("candidate", candidate)
86
+ ctx.set("intent", candidate.intent.value)
87
+ result.trace.append(f"INTENT_DETECTION: {candidate.intent.value} ({candidate.confidence:.2f})")
88
+
89
+ if candidate.ambiguous:
90
+ result.trace.append("WARNING: ambiguous input detected")
91
+
92
+ # PHASE: INTENT_DETECTION β†’ ENTITY_BINDING
93
+ ctx.transition("intent_class")
94
+ result.trace.append(f"ENTITY_BINDING: {candidate.entities}")
95
+
96
+ # PHASE: ENTITY_BINDING β†’ SEMANTIC_BINDING
97
+ ctx.transition("entities")
98
+ provenance = Provenance(
99
+ source_input_id=raw.input_id,
100
+ stage="semantic_binding",
101
+ transform_hash=raw.hash,
102
+ )
103
+ t0 = TemporalState(tau=0, label="initial")
104
+ lock = TemporalLock()
105
+
106
+ # Build semantic nodes from candidate
107
+ operands = [Operand(name=e, type_tag="entity") for e in candidate.entities]
108
+ constraints = [Constraint.make(c, c) for c in candidate.constraints]
109
+
110
+ op = None
111
+ if candidate.operators:
112
+ op = SemanticOperation(name=candidate.operators[0], arity=len(operands))
113
+
114
+ node = SemanticNode.make(
115
+ node_type=SemanticNodeType.OPERATION if op else SemanticNodeType.INTENT,
116
+ operation=op,
117
+ operands=operands,
118
+ constraints=constraints,
119
+ geometry=None,
120
+ temporal_state=t0,
121
+ provenance=provenance,
122
+ )
123
+
124
+ ir = SemanticIR.create(
125
+ intent=candidate.intent,
126
+ root=node,
127
+ nodes=[node],
128
+ temporal_lock=lock,
129
+ provenance=provenance,
130
+ )
131
+ ctx.set("candidate", candidate)
132
+ result.trace.append(f"SEMANTIC_BINDING: IR {ir.ir_id[:8]}")
133
+ result.hashes["semantic_ir"] = ir.hash()
134
+
135
+ # PHASE: SEMANTIC_BINDING β†’ TEMPORAL_LOCK
136
+ ctx.transition("semantic_candidate")
137
+ _, t1 = apply_temporal(lambda: None, t0, lock, "temporal_lock")
138
+ result.trace.append(f"TEMPORAL_LOCK: Ο„{t0.tau}β†’Ο„{t1.tau}")
139
+
140
+ # PHASE: TEMPORAL_LOCK β†’ GEOMETRY_BINDING
141
+ ctx.transition("temporal_state")
142
+ for n in ir.nodes:
143
+ geom = enforce_geometry(n)
144
+ n.geometry = geom
145
+ result.trace.append("GEOMETRY_BINDING: dimensions validated")
146
+
147
+ # PHASE: GEOMETRY_BINDING β†’ MATRIX_FORM
148
+ ctx.transition("geometry")
149
+ sem_matrix = self._matrix.build(ir.root)
150
+ result.trace.append(f"MATRIX_FORM: {sem_matrix.rows}x{sem_matrix.cols}")
151
+
152
+ # PHASE: MATRIX_FORM β†’ BOOLEAN_ROOT
153
+ ctx.transition("semantic_matrix")
154
+ bnf = semantic_constraints_to_boolean(ir.root.constraints)
155
+ result.trace.append(f"BOOLEAN_ROOT: {bnf.normalized}")
156
+
157
+ # PHASE: BOOLEAN_ROOT β†’ BOOLEAN_NORMALIZATION
158
+ ctx.transition("boolean_expr")
159
+ norm = bool_normalize(bnf.normalized)
160
+ bnf_report = self._verifier.verify_boolean(bnf)
161
+ self._verifier.reject_if_failed(bnf_report, FSMState.BOOLEAN_NORMALIZATION)
162
+ result.trace.append(f"BOOLEAN_NORMALIZATION: stable={bnf.stable}")
163
+
164
+ # PHASE: BOOLEAN_NORMALIZATION β†’ TOKENIZATION
165
+ ctx.transition("normalized_boolean")
166
+ encoder = BoolEncoder()
167
+ token_stream = encoder.encode_expr(bnf.normalized)
168
+ binary = encode_binary(token_stream)
169
+ tok_report = self._verifier.verify_tokens(token_stream)
170
+ self._verifier.reject_if_failed(tok_report, FSMState.TOKENIZATION)
171
+ result.trace.append(f"TOKENIZATION: {len(token_stream)} tokens, {len(binary)} bytes")
172
+
173
+ # PHASE: TOKENIZATION β†’ LATIN_CANONICALIZATION
174
+ ctx.transition("token_stream")
175
+ latin_form = self._latin.canonicalize(token_stream)
176
+ lat_report = self._verifier.verify_latin(latin_form)
177
+ self._verifier.reject_if_failed(lat_report, FSMState.LATIN_CANONICALIZATION)
178
+ result.trace.append(f"LATIN_CANONICALIZATION: {latin_form.text()[:60]}")
179
+
180
+ # PHASE: LATIN_CANONICALIZATION β†’ LATIN_LEXING
181
+ ctx.transition("latin_form")
182
+ lex_result = self._lexer.lex_canonical(latin_form)
183
+ result.trace.append(f"LATIN_LEXING: {len(lex_result.tokens)} lex tokens")
184
+
185
+ # PHASE: LATIN_LEXING β†’ ROSETTA_MAPPING
186
+ ctx.transition("latin_tokens")
187
+ mapping = self._rosetta.map(latin_form)
188
+ ros_report = self._verifier.verify_rosetta(mapping)
189
+ self._verifier.reject_if_failed(ros_report, FSMState.ROSETTA_MAPPING)
190
+ result.trace.append(f"ROSETTA_MAPPING: {len(mapping.entries)} entries")
191
+
192
+ # PHASE: ROSETTA_MAPPING β†’ SUBLEQ_IR
193
+ ctx.transition("rosetta_mapping")
194
+ prog = build_program_from_rosetta(mapping)
195
+ sub_report = self._verifier.verify_subleq(prog)
196
+ self._verifier.reject_if_failed(sub_report, FSMState.SUBLEQ_IR)
197
+ result.subleq_listing = prog.disassemble_text()
198
+ result.hashes["subleq"] = hashlib.sha256(prog.assemble()).hexdigest()
199
+ result.trace.append(f"SUBLEQ_IR: {len(prog.instructions)} instructions")
200
+
201
+ # PHASE: SUBLEQ_IR β†’ VERIFICATION
202
+ ctx.transition("subleq_program")
203
+ ir_report = self._verifier.verify_ir(ir)
204
+ self._verifier.reject_if_failed(ir_report, FSMState.VERIFICATION)
205
+ ir.verification_state = VerificationState.VERIFIED
206
+ ctx.set("verified", True)
207
+ result.trace.append("VERIFICATION: PASSED")
208
+
209
+ # PHASE: VERIFICATION β†’ ARTIFACT_SELECTION
210
+ ctx.transition("verified_ir")
211
+ ctx.set("template", "prompt.mustache")
212
+ result.trace.append(f"ARTIFACT_SELECTION: mode={mode.value}")
213
+
214
+ # PHASE: ARTIFACT_SELECTION β†’ EMISSION
215
+ ctx.transition("artifact_type")
216
+ if mode == OutputMode.NATURAL_LANGUAGE:
217
+ artifact = self._artifacts.build_document(ir)
218
+ elif mode == OutputMode.CODE:
219
+ artifact = self._artifacts.build_backend(ir)
220
+ else:
221
+ artifact = self._artifacts.build_prompt(ir)
222
+
223
+ art_report = self._verifier.verify_artifact(artifact, ir)
224
+ self._verifier.reject_if_failed(art_report, FSMState.EMISSION)
225
+ artifact.verification_state = VerificationState.VERIFIED
226
+ ctx.set("artifact", artifact)
227
+ result.hashes["artifact"] = artifact.hash()
228
+ result.trace.append(f"EMISSION: artifact {artifact.id[:8]}")
229
+
230
+ # PHASE: EMISSION β†’ COMPLETE
231
+ ctx.transition("artifact")
232
+
233
+ result.ir = ir
234
+ result.artifact = artifact
235
+ result.verification_state = VerificationState.VERIFIED
236
+ result.trace.append("COMPLETE")
237
+
238
+ except PipelineError as e:
239
+ result.error = str(e)
240
+ result.verification_state = VerificationState.REJECTED
241
+ result.trace.append(f"ERROR: {e}")
242
+
243
+ return result
src/semantic_engine/rosetta.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 11: Rosetta mapping β€” Latin semantic IDs β†’ SUBLEQ IR.
6
+
7
+ Only canonical vocabulary reaches this layer.
8
+ Direct arbitrary Latin→machine mapping is prohibited.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+ from .latin import LatinCanonicalForm, LatinToken
19
+ from .lexer import LexResult, LexedToken
20
+ from .types import FSMState, RosettaError
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class RosettaEntry:
25
+ semantic_id: str
26
+ subleq_op: str
27
+ template: str
28
+ args: tuple[str, ...]
29
+
30
+
31
+ @dataclass
32
+ class RosettaMapping:
33
+ """Result of mapping a LatinCanonicalForm to SUBLEQ operations."""
34
+ entries: list[tuple[LatinToken, RosettaEntry]] = field(default_factory=list)
35
+ unmapped: list[LatinToken] = field(default_factory=list)
36
+
37
+ @property
38
+ def complete(self) -> bool:
39
+ return len(self.unmapped) == 0
40
+
41
+
42
+ # ── Table loader ──────────────────────────────────────────────────────────────
43
+
44
+ def _load_table() -> dict[str, RosettaEntry]:
45
+ path = Path(__file__).parent.parent.parent / "mappings" / "rosetta_table.json"
46
+ if not path.exists():
47
+ return {}
48
+ data = json.loads(path.read_text())
49
+ return {
50
+ sem_id: RosettaEntry(
51
+ semantic_id=sem_id,
52
+ subleq_op=entry["subleq_op"],
53
+ template=entry["template"],
54
+ args=tuple(entry.get("args", [])),
55
+ )
56
+ for sem_id, entry in data.get("mappings", {}).items()
57
+ }
58
+
59
+
60
+ _TABLE: dict[str, RosettaEntry] = {}
61
+
62
+
63
+ def table() -> dict[str, RosettaEntry]:
64
+ global _TABLE
65
+ if not _TABLE:
66
+ _TABLE = _load_table()
67
+ return _TABLE
68
+
69
+
70
+ # ── Mapper ────────────────────────────────────────────────────────────────────
71
+
72
+ class RosettaMapper:
73
+ """Map LatinCanonicalForm tokens to SUBLEQ operations via the Rosetta table."""
74
+
75
+ def map(self, form: LatinCanonicalForm) -> RosettaMapping:
76
+ tbl = table()
77
+ mapping = RosettaMapping()
78
+
79
+ for tok in form.tokens:
80
+ entry = tbl.get(tok.semantic_id)
81
+ if entry is None:
82
+ mapping.unmapped.append(tok)
83
+ else:
84
+ mapping.entries.append((tok, entry))
85
+
86
+ if not mapping.complete:
87
+ unmapped_ids = [t.semantic_id for t in mapping.unmapped]
88
+ raise RosettaError(
89
+ FSMState.ROSETTA_MAPPING,
90
+ f"Unmapped semantic IDs: {unmapped_ids}",
91
+ )
92
+
93
+ return mapping
94
+
95
+ def map_lex_result(self, result: LexResult, form: LatinCanonicalForm) -> RosettaMapping:
96
+ """Map from a lexed result, cross-referencing the canonical form for semantic IDs."""
97
+ return self.map(form)
src/semantic_engine/subleq.py ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phases 12–13: SUBLEQ IR and VM.
6
+
7
+ SUBLEQ(A, B, C): mem[B] = mem[B] - mem[A]; if mem[B] <= 0: goto C
8
+
9
+ This is the minimal target instruction representation.
10
+ All semantic operations map to SUBLEQ via the Rosetta layer.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import struct
16
+ from dataclasses import dataclass, field
17
+ from typing import Optional
18
+
19
+ from .rosetta import RosettaEntry, RosettaMapping
20
+ from .types import FSMState, PipelineError
21
+
22
+
23
+ MAX_MEMORY = 65536
24
+ HALT_ADDRESS = -1
25
+
26
+
27
+ # ── SUBLEQ IR ─────────────────────────────────────────────────────────────────
28
+
29
+ @dataclass(frozen=True)
30
+ class SubleqInstruction:
31
+ A: int # source address (subtracted)
32
+ B: int # destination address (modified)
33
+ C: int # branch address (-1 = halt)
34
+ label: str = ""
35
+ comment: str = ""
36
+
37
+ def __repr__(self) -> str:
38
+ c = "HALT" if self.C == HALT_ADDRESS else str(self.C)
39
+ lbl = f"{self.label}: " if self.label else ""
40
+ return f"{lbl}SUBLEQ {self.A}, {self.B}, {c} ; {self.comment}"
41
+
42
+
43
+ @dataclass
44
+ class SubleqProgram:
45
+ instructions: list[SubleqInstruction] = field(default_factory=list)
46
+ labels: dict[str, int] = field(default_factory=dict) # label β†’ instruction index
47
+ data: dict[int, int] = field(default_factory=dict) # address β†’ initial value
48
+ origin: str = ""
49
+
50
+ def add(self, instr: SubleqInstruction) -> int:
51
+ idx = len(self.instructions)
52
+ if instr.label:
53
+ self.labels[instr.label] = idx
54
+ self.instructions.append(instr)
55
+ return idx
56
+
57
+ def resolve_label(self, name: str) -> int:
58
+ if name not in self.labels:
59
+ raise PipelineError(FSMState.SUBLEQ_IR, f"Undefined label: {name}")
60
+ return self.labels[name]
61
+
62
+ def assemble(self) -> bytes:
63
+ """Produce flat binary: 3 Γ— int16 per instruction (big-endian)."""
64
+ buf = bytearray()
65
+ for instr in self.instructions:
66
+ buf += struct.pack(">hhh", instr.A, instr.B, instr.C)
67
+ return bytes(buf)
68
+
69
+ @staticmethod
70
+ def disassemble(data: bytes) -> SubleqProgram:
71
+ """Reconstruct a SubleqProgram from flat binary."""
72
+ prog = SubleqProgram()
73
+ if len(data) % 6 != 0:
74
+ raise PipelineError(FSMState.SUBLEQ_IR, "Binary not aligned to 6-byte SUBLEQ words")
75
+ for i in range(0, len(data), 6):
76
+ a, b, c = struct.unpack_from(">hhh", data, i)
77
+ prog.add(SubleqInstruction(A=a, B=b, C=c))
78
+ return prog
79
+
80
+ def disassemble_text(self) -> str:
81
+ return "\n".join(repr(instr) for instr in self.instructions)
82
+
83
+
84
+ # ── SUBLEQ Memory ─────────────────────────────────────────────────────────────
85
+
86
+ @dataclass
87
+ class SubleqMemory:
88
+ cells: list[int] = field(default_factory=lambda: [0] * MAX_MEMORY)
89
+
90
+ def read(self, addr: int) -> int:
91
+ if addr < 0 or addr >= MAX_MEMORY:
92
+ raise PipelineError(FSMState.SUBLEQ_IR, f"Memory read out of bounds: {addr}")
93
+ return self.cells[addr]
94
+
95
+ def write(self, addr: int, value: int) -> None:
96
+ if addr < 0 or addr >= MAX_MEMORY:
97
+ raise PipelineError(FSMState.SUBLEQ_IR, f"Memory write out of bounds: {addr}")
98
+ self.cells[addr] = value
99
+
100
+ def load_program(self, prog: SubleqProgram, base: int = 0) -> None:
101
+ for i, instr in enumerate(prog.instructions):
102
+ addr = base + i * 3
103
+ self.write(addr, instr.A)
104
+ self.write(addr + 1, instr.B)
105
+ self.write(addr + 2, instr.C)
106
+ for addr, val in prog.data.items():
107
+ self.write(addr, val)
108
+
109
+
110
+ # ── SUBLEQ VM ─────────────────────────────────────────────────────────────────
111
+
112
+ @dataclass
113
+ class VMTrace:
114
+ step: int
115
+ pc: int
116
+ A: int
117
+ B: int
118
+ C: int
119
+ mem_b_before: int
120
+ mem_b_after: int
121
+ branched: bool
122
+
123
+
124
+ @dataclass
125
+ class SubleqVM:
126
+ memory: SubleqMemory = field(default_factory=SubleqMemory)
127
+ pc: int = 0
128
+ steps: int = 0
129
+ max_steps: int = 100_000
130
+ trace_log: list[VMTrace] = field(default_factory=list)
131
+ halted: bool = False
132
+
133
+ def load(self, prog: SubleqProgram, base: int = 100) -> None:
134
+ self.memory.load_program(prog, base)
135
+ # Initialize data cells
136
+ for addr, val in prog.data.items():
137
+ self.memory.write(addr, val)
138
+ self.pc = base
139
+ self.steps = 0
140
+ self.halted = False
141
+ self.trace_log.clear()
142
+
143
+ def step(self) -> bool:
144
+ """Execute one SUBLEQ instruction. Returns False when halted."""
145
+ if self.halted:
146
+ return False
147
+
148
+ A = self.memory.read(self.pc)
149
+ B = self.memory.read(self.pc + 1)
150
+ C = self.memory.read(self.pc + 2)
151
+
152
+ mem_b_before = self.memory.read(B)
153
+ mem_b_after = mem_b_before - self.memory.read(A)
154
+ self.memory.write(B, mem_b_after)
155
+
156
+ branched = mem_b_after <= 0
157
+ trace = VMTrace(
158
+ step=self.steps, pc=self.pc,
159
+ A=A, B=B, C=C,
160
+ mem_b_before=mem_b_before,
161
+ mem_b_after=mem_b_after,
162
+ branched=branched,
163
+ )
164
+ self.trace_log.append(trace)
165
+ self.steps += 1
166
+
167
+ if branched:
168
+ if C == HALT_ADDRESS or C < 0:
169
+ self.halted = True
170
+ return False
171
+ self.pc = C
172
+ else:
173
+ self.pc += 3
174
+
175
+ if self.steps >= self.max_steps:
176
+ raise PipelineError(
177
+ FSMState.SUBLEQ_IR,
178
+ f"SUBLEQ VM exceeded {self.max_steps} steps β€” possible infinite loop",
179
+ )
180
+
181
+ return not self.halted
182
+
183
+ def execute(self) -> list[VMTrace]:
184
+ while not self.halted:
185
+ if not self.step():
186
+ break
187
+ return self.trace_log
188
+
189
+ def verify_halts(self, prog: SubleqProgram, data_setup: dict[int, int] | None = None) -> bool:
190
+ """Check whether the program halts within max_steps."""
191
+ mem = SubleqMemory()
192
+ vm = SubleqVM(memory=mem, max_steps=self.max_steps)
193
+ if data_setup:
194
+ for addr, val in data_setup.items():
195
+ mem.write(addr, val)
196
+ vm.load(prog, base=100)
197
+ try:
198
+ vm.execute()
199
+ return vm.halted
200
+ except PipelineError:
201
+ return False
202
+
203
+
204
+ # ── Assembler: Rosetta mapping β†’ SUBLEQ program ───────────────────────────────
205
+
206
+ # Address allocation β€” simple bump allocator
207
+ _NEXT_ADDR = 256 # reserve 0–255 for VM built-ins
208
+
209
+
210
+ def _alloc(n: int = 1) -> int:
211
+ global _NEXT_ADDR
212
+ addr = _NEXT_ADDR
213
+ _NEXT_ADDR += n
214
+ return addr
215
+
216
+
217
+ # Built-in addresses
218
+ ADDR_ZERO = 0 # always 0
219
+ ADDR_ONE = 1 # always 1
220
+ ADDR_TEMP = 2 # scratch
221
+
222
+
223
+ def build_program_from_rosetta(mapping: RosettaMapping) -> SubleqProgram:
224
+ """
225
+ Assemble a SUBLEQ program from a Rosetta mapping.
226
+ Each Rosetta entry becomes one or more SUBLEQ instructions.
227
+ """
228
+ prog = SubleqProgram()
229
+
230
+ # Initialize constants
231
+ prog.data[ADDR_ZERO] = 0
232
+ prog.data[ADDR_ONE] = 1
233
+
234
+ base = 100
235
+ for tok, entry in mapping.entries:
236
+ _emit_entry(prog, entry, tok.payload, base=base)
237
+
238
+ # Halt
239
+ prog.add(SubleqInstruction(A=ADDR_ZERO, B=ADDR_ZERO, C=HALT_ADDRESS, comment="HALT"))
240
+
241
+ return prog
242
+
243
+
244
+ _FALL_THROUGH = -2 # sentinel meaning "next instruction"
245
+
246
+
247
+ def _next_pc(prog: SubleqProgram, base: int = 100) -> int:
248
+ """Absolute address of the instruction that will follow the current last one."""
249
+ return base + (len(prog.instructions) + 1) * 3
250
+
251
+
252
+ def _emit_entry(prog: SubleqProgram, entry: RosettaEntry, payload: str, base: int = 100) -> None:
253
+ op = entry.subleq_op
254
+ label = f"{entry.semantic_id}_{len(prog.instructions)}"
255
+ nxt = _next_pc(prog, base)
256
+
257
+ if op == "NOP":
258
+ prog.add(SubleqInstruction(ADDR_ZERO, ADDR_ZERO, nxt, label=label, comment=f"NOP:{payload}"))
259
+ return
260
+
261
+ if op == "TICK":
262
+ out = _alloc()
263
+ prog.data[out] = 0
264
+ prog.add(SubleqInstruction(ADDR_ZERO, out, nxt, label=label, comment="TICK"))
265
+ return
266
+
267
+ if op in ("PUSH", "POP"):
268
+ prog.add(SubleqInstruction(ADDR_ZERO, ADDR_ZERO, nxt, label=label, comment=op))
269
+ return
270
+
271
+ if op == "NOT":
272
+ a_addr = _alloc()
273
+ out_addr = _alloc()
274
+ prog.data[a_addr] = 1
275
+ nxt2 = base + (len(prog.instructions) + 2) * 3
276
+ prog.add(SubleqInstruction(a_addr, ADDR_ONE, nxt2, label=label, comment=f"NOT:{payload}"))
277
+ prog.add(SubleqInstruction(ADDR_ZERO, out_addr, _next_pc(prog, base)))
278
+ return
279
+
280
+ if op in ("AND", "OR", "XOR", "XNOR", "ADD", "SUB", "MUL", "COMPUTE",
281
+ "PARSE", "BUILD", "GEN", "VERIFY", "XFORM", "RECON", "ALLOC",
282
+ "LOAD_0", "LOAD_1"):
283
+ out_addr = _alloc()
284
+ prog.data[out_addr] = 0
285
+ prog.add(SubleqInstruction(ADDR_ZERO, out_addr, nxt, label=label, comment=f"{op}:{payload}"))
286
+ return
287
+
288
+ # Unknown op β€” emit NOP
289
+ prog.add(SubleqInstruction(ADDR_ZERO, ADDR_ZERO, nxt, label=label, comment=f"UNKNOWN:{op}"))
src/semantic_engine/temporal.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 4: Temporal lock β€” every transformation receives and preserves Ο„."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ from dataclasses import dataclass, field
11
+ from typing import TypeVar, Callable
12
+
13
+ from .types import FSMState, TemporalViolation
14
+ from .ir import TemporalLock, TemporalState, TemporalTransition
15
+
16
+ T = TypeVar("T")
17
+
18
+
19
+ def stamp() -> int:
20
+ """Monotonic integer tick based on nanoseconds."""
21
+ return time.monotonic_ns()
22
+
23
+
24
+ def apply_temporal(
25
+ fn: Callable[..., T],
26
+ t_in: TemporalState,
27
+ lock: TemporalLock,
28
+ stage: str,
29
+ *args,
30
+ **kwargs,
31
+ ) -> tuple[T, TemporalState]:
32
+ """
33
+ Run fn(*args, **kwargs) under a temporal contract.
34
+
35
+ - Records Ο„n β†’ Ο„n+1 transition.
36
+ - Raises TemporalViolation if the state was locked.
37
+ - Returns (result, new_temporal_state).
38
+ """
39
+ if t_in.locked:
40
+ raise TemporalViolation(
41
+ FSMState.TEMPORAL_LOCK,
42
+ f"Stage '{stage}' attempted to transform a locked temporal state Ο„{t_in.tau}",
43
+ )
44
+
45
+ result = fn(*args, **kwargs)
46
+ t_out = t_in.advance(reason=stage)
47
+
48
+ lock.record(TemporalTransition(
49
+ from_tau=t_in.tau,
50
+ to_tau=t_out.tau,
51
+ reason=stage,
52
+ stage=stage,
53
+ ))
54
+
55
+ return result, t_out
56
+
57
+
58
+ def preserve_temporal(
59
+ fn: Callable[..., T],
60
+ t_in: TemporalState,
61
+ lock: TemporalLock,
62
+ stage: str,
63
+ *args,
64
+ **kwargs,
65
+ ) -> tuple[T, TemporalState]:
66
+ """
67
+ Run fn but preserve Ο„ (no advance). Used for read-only passes.
68
+ Records a Ο„β†’Ο„ no-op transition.
69
+ """
70
+ result = fn(*args, **kwargs)
71
+ lock.record(TemporalTransition(
72
+ from_tau=t_in.tau,
73
+ to_tau=t_in.tau,
74
+ reason=f"preserve:{stage}",
75
+ stage=stage,
76
+ ))
77
+ return result, t_in
78
+
79
+
80
+ def assert_temporal_invariant(t_before: TemporalState, t_after: TemporalState, stage: str) -> None:
81
+ """Assert Ο„_after == Ο„_before + 1 (for transforms) or == Ο„_before (for reads)."""
82
+ if t_after.tau not in (t_before.tau, t_before.tau + 1):
83
+ raise TemporalViolation(
84
+ FSMState.TEMPORAL_LOCK,
85
+ f"Stage '{stage}' illegally mutated Ο„: {t_before.tau} β†’ {t_after.tau}",
86
+ )
src/semantic_engine/token_engine.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 8: Boolean Token Engine β€” binary serialization of Boolean IR.
6
+
7
+ Input: Boolean IR
8
+ Output: BooleanTokenStream
9
+
10
+ Invariant: Decode(Encode(x)) == x for all canonical Boolean structures.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import struct
16
+ from dataclasses import dataclass, field
17
+ from typing import Union
18
+
19
+ from .boolean_engine import (
20
+ AND, NOT, OR, XOR, BooleanAtom, BooleanExpression,
21
+ BooleanLiteral, BooleanVar, Expr,
22
+ )
23
+ from .types import BooleanOperator, FSMState, TokenError
24
+
25
+
26
+ # ── Token types ───────────────────────────────────────────────────────────────
27
+
28
+ TTYPE_LITERAL = 0x01
29
+ TTYPE_VAR = 0x02
30
+ TTYPE_VAR_NEG = 0x03
31
+ TTYPE_AND = 0x10
32
+ TTYPE_OR = 0x11
33
+ TTYPE_NOT = 0x12
34
+ TTYPE_XOR = 0x13
35
+ TTYPE_IMPLIES = 0x14
36
+ TTYPE_EQUIVALENCE = 0x15
37
+ TTYPE_TRUE = 0x20
38
+ TTYPE_FALSE = 0x21
39
+ TTYPE_OPEN = 0x30
40
+ TTYPE_CLOSE = 0x31
41
+ TTYPE_ARITY = 0x40
42
+
43
+ _OP_TO_TTYPE: dict[BooleanOperator, int] = {
44
+ BooleanOperator.AND: TTYPE_AND,
45
+ BooleanOperator.OR: TTYPE_OR,
46
+ BooleanOperator.NOT: TTYPE_NOT,
47
+ BooleanOperator.XOR: TTYPE_XOR,
48
+ BooleanOperator.IMPLIES: TTYPE_IMPLIES,
49
+ BooleanOperator.EQUIVALENCE: TTYPE_EQUIVALENCE,
50
+ BooleanOperator.TRUE: TTYPE_TRUE,
51
+ BooleanOperator.FALSE: TTYPE_FALSE,
52
+ }
53
+
54
+ _TTYPE_TO_OP: dict[int, BooleanOperator] = {v: k for k, v in _OP_TO_TTYPE.items()}
55
+
56
+
57
+ # ── Token ─────────────────────────────────────────────────────────────────────
58
+
59
+ @dataclass(frozen=True)
60
+ class BoolToken:
61
+ token_id: int
62
+ token_type: int
63
+ arity: int
64
+ position: int
65
+ payload: bytes = b""
66
+ semantic_reference: str = ""
67
+ temporal_reference: str = ""
68
+ geometry_reference: str = ""
69
+
70
+ def __repr__(self) -> str:
71
+ tname = {
72
+ TTYPE_LITERAL: "LIT", TTYPE_VAR: "VAR", TTYPE_VAR_NEG: "NEGVAR",
73
+ TTYPE_AND: "AND", TTYPE_OR: "OR", TTYPE_NOT: "NOT",
74
+ TTYPE_XOR: "XOR", TTYPE_TRUE: "TRUE", TTYPE_FALSE: "FALSE",
75
+ TTYPE_OPEN: "(", TTYPE_CLOSE: ")",
76
+ }.get(self.token_type, f"0x{self.token_type:02x}")
77
+ return f"Token({tname}, pos={self.position}, arity={self.arity})"
78
+
79
+
80
+ @dataclass
81
+ class BooleanTokenStream:
82
+ tokens: list[BoolToken] = field(default_factory=list)
83
+
84
+ def append(self, t: BoolToken) -> None:
85
+ self.tokens.append(t)
86
+
87
+ def __len__(self) -> int:
88
+ return len(self.tokens)
89
+
90
+ def __iter__(self):
91
+ return iter(self.tokens)
92
+
93
+
94
+ # ── Encoder ───────────────────────────────────────────────────────────────────
95
+
96
+ class BoolEncoder:
97
+ def __init__(self):
98
+ self._pos = 0
99
+ self._id = 0
100
+ self._stream = BooleanTokenStream()
101
+
102
+ def _tok(self, ttype: int, arity: int, payload: bytes = b"", sem_ref: str = "") -> BoolToken:
103
+ t = BoolToken(
104
+ token_id=self._id,
105
+ token_type=ttype,
106
+ arity=arity,
107
+ position=self._pos,
108
+ payload=payload,
109
+ semantic_reference=sem_ref,
110
+ )
111
+ self._id += 1
112
+ self._pos += 1
113
+ return t
114
+
115
+ def encode_expr(self, expr: Expr) -> BooleanTokenStream:
116
+ self._pos = 0
117
+ self._id = 0
118
+ self._stream = BooleanTokenStream()
119
+ self._encode(expr)
120
+ return self._stream
121
+
122
+ def _encode(self, expr: Expr) -> None:
123
+ if isinstance(expr, BooleanLiteral):
124
+ ttype = TTYPE_TRUE if expr.value else TTYPE_FALSE
125
+ self._stream.append(self._tok(ttype, 0, b"\x01" if expr.value else b"\x00"))
126
+ return
127
+
128
+ if isinstance(expr, BooleanVar):
129
+ ttype = TTYPE_VAR_NEG if expr.negated else TTYPE_VAR
130
+ payload = expr.name.encode("utf-8")
131
+ self._stream.append(self._tok(ttype, 0, payload, sem_ref=expr.name))
132
+ return
133
+
134
+ op = expr.operator
135
+ ttype = _OP_TO_TTYPE.get(op)
136
+ if ttype is None:
137
+ raise TokenError(FSMState.TOKENIZATION, f"Unknown operator: {op}")
138
+
139
+ arity = len(expr.operands)
140
+ self._stream.append(self._tok(TTYPE_OPEN, 0))
141
+ self._stream.append(self._tok(ttype, arity))
142
+ for operand in expr.operands:
143
+ self._encode(operand)
144
+ self._stream.append(self._tok(TTYPE_CLOSE, 0))
145
+
146
+
147
+ # ── Binary serialization ──────────────��───────────────────────────────────────
148
+
149
+ _HEADER = b"BOOL"
150
+ _VERSION = 1
151
+
152
+
153
+ def encode_binary(stream: BooleanTokenStream) -> bytes:
154
+ """
155
+ Binary format:
156
+ 4 bytes magic: BOOL
157
+ 1 byte version
158
+ 4 bytes token count
159
+ per token: 1 byte type, 1 byte arity, 2 bytes payload_len, N bytes payload
160
+ """
161
+ buf = bytearray()
162
+ buf += _HEADER
163
+ buf += struct.pack(">BH", _VERSION, len(stream.tokens))
164
+ for tok in stream.tokens:
165
+ pay = tok.payload[:255]
166
+ buf += struct.pack(">BBH", tok.token_type, tok.arity, len(pay))
167
+ buf += pay
168
+ return bytes(buf)
169
+
170
+
171
+ def decode_binary(data: bytes) -> BooleanTokenStream:
172
+ """Inverse of encode_binary. Raises TokenError on malformed input."""
173
+ if len(data) < 7:
174
+ raise TokenError(FSMState.TOKENIZATION, "Binary data too short")
175
+ if data[:4] != _HEADER:
176
+ raise TokenError(FSMState.TOKENIZATION, f"Bad magic: {data[:4]!r}")
177
+
178
+ version, count = struct.unpack_from(">BH", data, 4)
179
+ if version != _VERSION:
180
+ raise TokenError(FSMState.TOKENIZATION, f"Unknown version {version}")
181
+
182
+ offset = 7
183
+ stream = BooleanTokenStream()
184
+ for i in range(count):
185
+ if offset + 4 > len(data):
186
+ raise TokenError(FSMState.TOKENIZATION, f"Truncated at token {i}")
187
+ ttype, arity, pay_len = struct.unpack_from(">BBH", data, offset)
188
+ offset += 4
189
+ payload = data[offset:offset + pay_len]
190
+ offset += pay_len
191
+ stream.append(BoolToken(
192
+ token_id=i, token_type=ttype, arity=arity,
193
+ position=i, payload=payload,
194
+ ))
195
+ return stream
196
+
197
+
198
+ # ── Decoder (token stream β†’ Boolean IR) ──────────────────────────────────────
199
+
200
+ class BoolDecoder:
201
+ def __init__(self, stream: BooleanTokenStream):
202
+ self._tokens = list(stream.tokens)
203
+ self._pos = 0
204
+
205
+ def _peek(self) -> BoolToken | None:
206
+ if self._pos >= len(self._tokens):
207
+ return None
208
+ return self._tokens[self._pos]
209
+
210
+ def _consume(self) -> BoolToken:
211
+ tok = self._tokens[self._pos]
212
+ self._pos += 1
213
+ return tok
214
+
215
+ def decode(self) -> Expr:
216
+ expr = self._decode_expr()
217
+ return expr
218
+
219
+ def _decode_expr(self) -> Expr:
220
+ tok = self._peek()
221
+ if tok is None:
222
+ raise TokenError(FSMState.TOKENIZATION, "Unexpected end of token stream")
223
+
224
+ if tok.token_type == TTYPE_TRUE:
225
+ self._consume()
226
+ return BooleanLiteral(True)
227
+ if tok.token_type == TTYPE_FALSE:
228
+ self._consume()
229
+ return BooleanLiteral(False)
230
+ if tok.token_type == TTYPE_VAR:
231
+ self._consume()
232
+ return BooleanVar(tok.payload.decode("utf-8"))
233
+ if tok.token_type == TTYPE_VAR_NEG:
234
+ self._consume()
235
+ return BooleanVar(tok.payload.decode("utf-8"), negated=True)
236
+
237
+ if tok.token_type == TTYPE_OPEN:
238
+ self._consume() # consume (
239
+ op_tok = self._consume()
240
+ op = _TTYPE_TO_OP.get(op_tok.token_type)
241
+ if op is None:
242
+ raise TokenError(FSMState.TOKENIZATION, f"Unknown op token 0x{op_tok.token_type:02x}")
243
+ operands = []
244
+ while True:
245
+ nxt = self._peek()
246
+ if nxt is None:
247
+ raise TokenError(FSMState.TOKENIZATION, "Missing CLOSE token")
248
+ if nxt.token_type == TTYPE_CLOSE:
249
+ self._consume()
250
+ break
251
+ operands.append(self._decode_expr())
252
+ return BooleanExpression(op, operands)
253
+
254
+ raise TokenError(FSMState.TOKENIZATION, f"Unexpected token type 0x{tok.token_type:02x}")
255
+
256
+
257
+ # ── Round-trip convenience ────────────────────────────────────────────────────
258
+
259
+ def encode(expr: Expr) -> bytes:
260
+ stream = BoolEncoder().encode_expr(expr)
261
+ return encode_binary(stream)
262
+
263
+
264
+ def decode(data: bytes) -> Expr:
265
+ stream = decode_binary(data)
266
+ return BoolDecoder(stream).decode()
267
+
268
+
269
+ def round_trip(expr: Expr) -> Expr:
270
+ return decode(encode(expr))
src/semantic_engine/types.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 1: Core types β€” all canonical IR types, enums, and base structures."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ import struct
11
+ import time
12
+ import uuid
13
+ from dataclasses import dataclass, field
14
+ from enum import Enum, auto
15
+ from typing import Any, Optional
16
+
17
+
18
+ # ── Enums ──────────────────────────────────────────────────────────────────────
19
+
20
+ class FSMState(Enum):
21
+ INPUT = auto()
22
+ NORMALIZE = auto()
23
+ LEXICAL_ANALYSIS = auto()
24
+ INTENT_DETECTION = auto()
25
+ ENTITY_BINDING = auto()
26
+ SEMANTIC_BINDING = auto()
27
+ TEMPORAL_LOCK = auto()
28
+ GEOMETRY_BINDING = auto()
29
+ MATRIX_FORM = auto()
30
+ BOOLEAN_ROOT = auto()
31
+ BOOLEAN_NORMALIZATION = auto()
32
+ TOKENIZATION = auto()
33
+ LATIN_CANONICALIZATION = auto()
34
+ LATIN_LEXING = auto()
35
+ ROSETTA_MAPPING = auto()
36
+ SUBLEQ_IR = auto()
37
+ VERIFICATION = auto()
38
+ ARTIFACT_SELECTION = auto()
39
+ EMISSION = auto()
40
+ COMPLETE = auto()
41
+ ERROR = auto()
42
+
43
+
44
+ class VerificationState(Enum):
45
+ UNVERIFIED = auto()
46
+ VALIDATING = auto()
47
+ VERIFIED = auto()
48
+ REJECTED = auto()
49
+
50
+
51
+ class ArtifactType(Enum):
52
+ PROMPT = "prompt"
53
+ DOCUMENT = "document"
54
+ SOURCE = "source"
55
+ CONFIG = "config"
56
+ TEST = "test"
57
+ SCHEMA = "schema"
58
+ REPORT = "report"
59
+ BACKEND = "backend"
60
+
61
+
62
+ class SemanticNodeType(Enum):
63
+ OPERATION = "operation"
64
+ ENTITY = "entity"
65
+ CONSTRAINT = "constraint"
66
+ RELATION = "relation"
67
+ INTENT = "intent"
68
+ GEOMETRY = "geometry"
69
+ TEMPORAL = "temporal"
70
+ LITERAL = "literal"
71
+
72
+
73
+ class BooleanOperator(Enum):
74
+ AND = "AND"
75
+ OR = "OR"
76
+ NOT = "NOT"
77
+ XOR = "XOR"
78
+ IMPLIES = "IMPLIES"
79
+ EQUIVALENCE = "EQUIVALENCE"
80
+ TRUE = "TRUE"
81
+ FALSE = "FALSE"
82
+
83
+
84
+ class IntentClass(Enum):
85
+ BUILD = "build"
86
+ TRANSFORM = "transform"
87
+ VALIDATE = "validate"
88
+ GENERATE = "generate"
89
+ PARSE = "parse"
90
+ COMPUTE = "compute"
91
+ RECONSTRUCT = "reconstruct"
92
+ UNKNOWN = "unknown"
93
+
94
+
95
+ class OutputMode(Enum):
96
+ NATURAL_LANGUAGE = "natural_language"
97
+ CODE = "code"
98
+ ARTIFACT = "artifact"
99
+
100
+
101
+ # ── Input types ───────────────────────────────────────────────────────────────
102
+
103
+ @dataclass(frozen=True)
104
+ class RawInput:
105
+ text: str
106
+ source: str = "stdin"
107
+ timestamp: float = field(default_factory=time.time)
108
+ input_id: str = field(default_factory=lambda: str(uuid.uuid4()))
109
+
110
+ @property
111
+ def hash(self) -> str:
112
+ return hashlib.sha256(self.text.encode()).hexdigest()
113
+
114
+
115
+ @dataclass(frozen=True)
116
+ class NormalizedInput:
117
+ original: RawInput
118
+ text: str
119
+ tokens: tuple[str, ...]
120
+ sentences: tuple[str, ...]
121
+
122
+ @property
123
+ def hash(self) -> str:
124
+ return hashlib.sha256(self.text.encode()).hexdigest()
125
+
126
+
127
+ @dataclass
128
+ class SemanticCandidate:
129
+ input_ref: str
130
+ intent: IntentClass
131
+ entities: list[str]
132
+ operators: list[str]
133
+ constraints: list[str]
134
+ relationships: list[tuple[str, str, str]]
135
+ confidence: float
136
+ ambiguous: bool
137
+ raw_embeddings: Optional[list[float]] = None
138
+
139
+ def __post_init__(self):
140
+ if not 0.0 <= self.confidence <= 1.0:
141
+ raise ValueError(f"Confidence must be in [0,1], got {self.confidence}")
142
+
143
+
144
+ # ── Provenance ────────────────────────────────────────────────────────────────
145
+
146
+ @dataclass(frozen=True)
147
+ class Provenance:
148
+ source_input_id: str
149
+ stage: str
150
+ timestamp: float = field(default_factory=time.time)
151
+ parent_id: Optional[str] = None
152
+ transform_hash: str = ""
153
+
154
+ def chain_hash(self, data: str) -> str:
155
+ return hashlib.sha256(f"{self.transform_hash}:{data}".encode()).hexdigest()
156
+
157
+
158
+ # ── FSM Transition record ─────────────────────────────────────────────────────
159
+
160
+ @dataclass(frozen=True)
161
+ class FSMTransition:
162
+ source_state: FSMState
163
+ input_class: str
164
+ operation: str
165
+ constraints: tuple[str, ...]
166
+ destination_state: FSMState
167
+ invariant: str
168
+
169
+ def validate(self, current_state: FSMState, input_class: str) -> bool:
170
+ return self.source_state == current_state and self.input_class == input_class
171
+
172
+
173
+ # ── Pipeline error ────────────────────────────────────────────────────────────
174
+
175
+ class PipelineError(Exception):
176
+ def __init__(self, stage: FSMState, message: str, input_ref: str = ""):
177
+ self.stage = stage
178
+ self.input_ref = input_ref
179
+ super().__init__(f"[{stage.name}] {message}")
180
+
181
+
182
+ class GeometryError(PipelineError):
183
+ pass
184
+
185
+
186
+ class TemporalViolation(PipelineError):
187
+ pass
188
+
189
+
190
+ class VerificationError(PipelineError):
191
+ pass
192
+
193
+
194
+ class TokenError(PipelineError):
195
+ pass
196
+
197
+
198
+ class RosettaError(PipelineError):
199
+ pass
src/semantic_engine/unknown.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """ADR-Ξ©-0001: The Uninterpreted Operator β€” ? (unknown semantics, observable behavior).
6
+
7
+ ?(A) β†’ B is valid as an observation.
8
+ ?(A) = f(A) is NOT valid until f has been established.
9
+
10
+ UNKNOWN β‰  FALSE
11
+ UNKNOWN β‰  TRUE
12
+
13
+ The system must not silently convert UNKNOWN into UNDERSTOOD.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import hashlib
19
+ import json
20
+ import time
21
+ from dataclasses import dataclass, field
22
+ from enum import Enum, auto
23
+ from pathlib import Path
24
+ from typing import Any, Optional
25
+
26
+
27
+ # ── Invariant state (Ξ©) ───────────────────────────────────────────────────────
28
+
29
+ @dataclass(frozen=True)
30
+ class InvariantState:
31
+ """Ξ© β€” the preserved invariant carried through every observation."""
32
+ tau: int
33
+ system_hash: str
34
+ properties: tuple[str, ...] = field(default_factory=tuple)
35
+
36
+ def hash(self) -> str:
37
+ content = f"Ξ©:{self.tau}:{self.system_hash}:{':'.join(self.properties)}"
38
+ return hashlib.sha256(content.encode()).hexdigest()
39
+
40
+
41
+ # ── Observation record: ⟨A, B, H, t, Ω⟩ ────────────────────────────────────
42
+
43
+ @dataclass(frozen=True)
44
+ class ObservationRecord:
45
+ """
46
+ O_?(A) = ⟨A, B, H, t, Ω⟩
47
+
48
+ Records that A β†’ ? β†’ B occurred without fabricating an explanation.
49
+ H = hash of the full record.
50
+ t = state-transition timestamp.
51
+ Ξ© = invariant state at time of observation.
52
+ """
53
+ input_state: str # A β€” canonical representation of input
54
+ output_state: str # B β€” canonical representation of output
55
+ provenance_hash: str # H β€” hash(A β€– ? β€– B β€– Ξ©)
56
+ timestamp: float # t
57
+ invariant: InvariantState # Ξ©
58
+
59
+ # Classification state β€” may only advance through the pipeline, never skip
60
+ classification: UnknownClassification = field(default=None)
61
+
62
+ def __post_init__(self):
63
+ # Enforce: classification starts as UNOBSERVED if not provided
64
+ if self.classification is None:
65
+ object.__setattr__(self, "classification", UnknownClassification.OBSERVED)
66
+
67
+ @staticmethod
68
+ def observe(
69
+ input_state: Any,
70
+ output_state: Any,
71
+ invariant: InvariantState,
72
+ ) -> ObservationRecord:
73
+ a = _canonical(input_state)
74
+ b = _canonical(output_state)
75
+ t = time.time()
76
+ h = _seal_hash(a, b, invariant)
77
+ return ObservationRecord(
78
+ input_state=a,
79
+ output_state=b,
80
+ provenance_hash=h,
81
+ timestamp=t,
82
+ invariant=invariant,
83
+ )
84
+
85
+ def verify(self) -> bool:
86
+ """Recompute and verify the provenance hash."""
87
+ expected = _seal_hash(self.input_state, self.output_state, self.invariant)
88
+ return self.provenance_hash == expected
89
+
90
+ def advance(self, to: UnknownClassification) -> ObservationRecord:
91
+ """
92
+ Advance classification through the pipeline:
93
+ OBSERVED β†’ CAPTURED β†’ HASHED β†’ COMPARED β†’ TESTED β†’ CLASSIFIED
94
+
95
+ May never skip a step. UNKNOWN must not become UNDERSTOOD silently.
96
+ """
97
+ _assert_valid_advancement(self.classification, to)
98
+ return ObservationRecord(
99
+ input_state=self.input_state,
100
+ output_state=self.output_state,
101
+ provenance_hash=self.provenance_hash,
102
+ timestamp=self.timestamp,
103
+ invariant=self.invariant,
104
+ classification=to,
105
+ )
106
+
107
+
108
+ # ── Classification pipeline (OBSERVE β†’ CLASSIFY only, no shortcuts) ───────────
109
+
110
+ class UnknownClassification(Enum):
111
+ OBSERVED = auto()
112
+ CAPTURED = auto()
113
+ HASHED = auto()
114
+ COMPARED = auto()
115
+ TESTED = auto()
116
+ CLASSIFIED = auto()
117
+
118
+ # Explicitly NOT in this enum:
119
+ # UNDERSTOOD β€” because converting UNKNOWN to UNDERSTOOD requires
120
+ # establishing f such that ?(A) = f(A). That is a separate formal claim.
121
+
122
+
123
+ _VALID_TRANSITIONS: dict[UnknownClassification, UnknownClassification] = {
124
+ UnknownClassification.OBSERVED: UnknownClassification.CAPTURED,
125
+ UnknownClassification.CAPTURED: UnknownClassification.HASHED,
126
+ UnknownClassification.HASHED: UnknownClassification.COMPARED,
127
+ UnknownClassification.COMPARED: UnknownClassification.TESTED,
128
+ UnknownClassification.TESTED: UnknownClassification.CLASSIFIED,
129
+ }
130
+
131
+
132
+ def _assert_valid_advancement(
133
+ current: UnknownClassification,
134
+ target: UnknownClassification,
135
+ ) -> None:
136
+ allowed = _VALID_TRANSITIONS.get(current)
137
+ if allowed != target:
138
+ raise UnknownOperatorError(
139
+ f"Invalid classification advance: {current.name} β†’ {target.name}. "
140
+ f"Expected {allowed.name if allowed else 'nothing (terminal)'}. "
141
+ f"UNKNOWN must not become UNDERSTOOD by skipping pipeline stages."
142
+ )
143
+
144
+
145
+ class UnknownOperatorError(Exception):
146
+ """Raised when the system attempts to fabricate semantics for ?."""
147
+
148
+
149
+ # ── WORM Seal engine ─────────────────────────────────────────────────────────
150
+
151
+ def _canonical(value: Any) -> str:
152
+ if isinstance(value, str):
153
+ return value
154
+ try:
155
+ return json.dumps(value, sort_keys=True, default=str)
156
+ except Exception:
157
+ return repr(value)
158
+
159
+
160
+ def _seal_hash(a: str, b: str, omega: InvariantState) -> str:
161
+ """Seal(A β†’ ? β†’ B) = H(A β€– ? β€– B β€– Ξ©)"""
162
+ content = f"{a}\x00?\x00{b}\x00{omega.hash()}"
163
+ return hashlib.sha256(content.encode()).hexdigest()
164
+
165
+
166
+ @dataclass(frozen=True)
167
+ class WORMSealEntry:
168
+ """One entry in the append-only WORM seal chain."""
169
+ index: int
170
+ record: ObservationRecord
171
+ entry_hash: str
172
+ previous_hash: str
173
+ sealed_at: float = 0.0
174
+
175
+ def verify_chain(self, previous_entry: Optional[WORMSealEntry]) -> bool:
176
+ """Verify this entry's chain link to the previous entry."""
177
+ expected_prev = previous_entry.entry_hash if previous_entry else "0" * 64
178
+ if self.previous_hash != expected_prev:
179
+ return False
180
+ return self.record.verify()
181
+
182
+ def to_dict(self) -> dict:
183
+ return {
184
+ "index": self.index,
185
+ "input_state": self.record.input_state[:80],
186
+ "output_state": self.record.output_state[:80],
187
+ "provenance_hash": self.record.provenance_hash,
188
+ "classification": self.record.classification.name,
189
+ "entry_hash": self.entry_hash,
190
+ "previous_hash": self.previous_hash,
191
+ "sealed_at": self.sealed_at,
192
+ "tau": self.record.invariant.tau,
193
+ }
194
+
195
+
196
+ class WORMSealChain:
197
+ """
198
+ Append-only chain of sealed observations.
199
+
200
+ Seal(A β†’ ? β†’ B) = H(A β€– ? β€– B β€– Ξ©)
201
+
202
+ Once sealed, an observation cannot be modified.
203
+ The chain hash links every entry to its predecessor.
204
+ """
205
+
206
+ def __init__(self):
207
+ self._entries: list[WORMSealEntry] = []
208
+
209
+ def seal(self, record: ObservationRecord) -> WORMSealEntry:
210
+ """Seal an observation record into the chain. Returns the sealed entry."""
211
+ prev_hash = self._entries[-1].entry_hash if self._entries else "0" * 64
212
+ index = len(self._entries)
213
+
214
+ # Entry hash = H(index β€– record_hash β€– prev_hash)
215
+ entry_content = f"{index}:{record.provenance_hash}:{prev_hash}"
216
+ entry_hash = hashlib.sha256(entry_content.encode()).hexdigest()
217
+
218
+ entry = WORMSealEntry(
219
+ index=index,
220
+ record=record,
221
+ entry_hash=entry_hash,
222
+ previous_hash=prev_hash,
223
+ sealed_at=time.time(),
224
+ )
225
+ self._entries.append(entry)
226
+ return entry
227
+
228
+ def head(self) -> Optional[WORMSealEntry]:
229
+ return self._entries[-1] if self._entries else None
230
+
231
+ def verify_chain(self) -> tuple[bool, list[str]]:
232
+ """Verify the full chain integrity. Returns (ok, errors)."""
233
+ errors: list[str] = []
234
+ prev: Optional[WORMSealEntry] = None
235
+ for entry in self._entries:
236
+ if not entry.verify_chain(prev):
237
+ errors.append(f"Chain broken at index {entry.index}")
238
+ if not entry.record.verify():
239
+ errors.append(f"Record hash invalid at index {entry.index}")
240
+ prev = entry
241
+ return len(errors) == 0, errors
242
+
243
+ def audit(self) -> list[dict]:
244
+ return [e.to_dict() for e in self._entries]
245
+
246
+ def __len__(self) -> int:
247
+ return len(self._entries)
248
+
249
+ def export_jsonl(self, path: Path) -> None:
250
+ """Export chain to JSONL for external audit."""
251
+ lines = [json.dumps(e.to_dict()) for e in self._entries]
252
+ path.write_text("\n".join(lines) + "\n")
253
+
254
+ @staticmethod
255
+ def import_jsonl(path: Path) -> list[dict]:
256
+ """Read chain from JSONL for verification (read-only)."""
257
+ if not path.exists():
258
+ return []
259
+ return [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
260
+
261
+
262
+ # ── Unknown semantic node ─────────────────────────────────────────────────────
263
+
264
+ @dataclass(frozen=True)
265
+ class UnknownNode:
266
+ """
267
+ A semantic node whose internal transformation is not yet understood.
268
+
269
+ ? is a first-class operator in the algebra:
270
+
271
+ β˜‰ PRODUCT
272
+ ⌹ PARTITION
273
+ β—‹ CLOSURE
274
+ β—‡ TRANSFORM
275
+ β–³ DIFFERENCE
276
+ ⬑ COMPOSITION
277
+ ? UNKNOWN ← this
278
+ Ξ© INVARIANT
279
+ """
280
+ node_id: str
281
+ observation: ObservationRecord
282
+ seal_entry: Optional[WORMSealEntry] = None
283
+
284
+ @property
285
+ def is_sealed(self) -> bool:
286
+ return self.seal_entry is not None
287
+
288
+ @property
289
+ def is_understood(self) -> bool:
290
+ # May NEVER return True from this class alone.
291
+ # Requires external formal establishment of f such that ?(A) = f(A).
292
+ return False
293
+
294
+ def __repr__(self) -> str:
295
+ cls = self.observation.classification.name
296
+ sealed = "SEALED" if self.is_sealed else "UNSEALED"
297
+ return f"?[{self.node_id[:8]}|{cls}|{sealed}]"
298
+
299
+
300
+ # ── Integration helper ────────────────────────────────────────────────────────
301
+
302
+ def observe_and_seal(
303
+ input_state: Any,
304
+ output_state: Any,
305
+ invariant: InvariantState,
306
+ chain: WORMSealChain,
307
+ node_id: str = "",
308
+ ) -> UnknownNode:
309
+ """
310
+ Full pipeline:
311
+ OBSERVE β†’ CAPTURE β†’ HASH β†’ COMPARE β†’ TEST β†’ CLASSIFY β†’ SEAL
312
+
313
+ Returns an UnknownNode with a sealed chain entry.
314
+ The node carries the observation but never claims to understand the transformation.
315
+ """
316
+ if not node_id:
317
+ node_id = hashlib.sha256(
318
+ f"{_canonical(input_state)}:{_canonical(output_state)}:{time.time()}".encode()
319
+ ).hexdigest()[:24]
320
+
321
+ record = ObservationRecord.observe(input_state, output_state, invariant)
322
+ record = record.advance(UnknownClassification.CAPTURED)
323
+ record = record.advance(UnknownClassification.HASHED)
324
+ record = record.advance(UnknownClassification.COMPARED)
325
+ record = record.advance(UnknownClassification.TESTED)
326
+ record = record.advance(UnknownClassification.CLASSIFIED)
327
+
328
+ entry = chain.seal(record)
329
+ return UnknownNode(node_id=node_id, observation=record, seal_entry=entry)
src/semantic_engine/verifier.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Phase 14: Verification engine β€” all checks before artifact emission."""
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import Callable
11
+
12
+ from .boolean_engine import BooleanNormalForm, normalize
13
+ from .ir import Artifact, SemanticIR
14
+ from .latin import LatinCanonicalForm, vocabulary
15
+ from .rosetta import RosettaMapping
16
+ from .subleq import SubleqProgram, SubleqVM
17
+ from .token_engine import BooleanTokenStream, round_trip
18
+ from .types import FSMState, VerificationError, VerificationState
19
+
20
+
21
+ # ── Check result ──────────────────────────────────────────────────────────────
22
+
23
+ @dataclass
24
+ class CheckResult:
25
+ name: str
26
+ passed: bool
27
+ message: str = ""
28
+
29
+ def __repr__(self) -> str:
30
+ status = "βœ“" if self.passed else "βœ—"
31
+ return f"{status} {self.name}: {self.message}"
32
+
33
+
34
+ @dataclass
35
+ class VerificationReport:
36
+ checks: list[CheckResult] = field(default_factory=list)
37
+ state: VerificationState = VerificationState.UNVERIFIED
38
+
39
+ @property
40
+ def passed(self) -> bool:
41
+ return all(c.passed for c in self.checks)
42
+
43
+ def add(self, result: CheckResult) -> None:
44
+ self.checks.append(result)
45
+
46
+ def summary(self) -> str:
47
+ lines = [repr(c) for c in self.checks]
48
+ lines.append(f"\nResult: {'VERIFIED' if self.passed else 'REJECTED'}")
49
+ return "\n".join(lines)
50
+
51
+
52
+ # ── Individual checks ─────────────────────────────────────────────────────────
53
+
54
+ def check_type_correctness(ir: SemanticIR) -> CheckResult:
55
+ for node in ir.nodes:
56
+ if node.operation and node.operation.arity != len(node.operands):
57
+ if node.operation.arity > 0:
58
+ return CheckResult(
59
+ "type_correctness", False,
60
+ f"Node {node.id}: arity {node.operation.arity} but {len(node.operands)} operands",
61
+ )
62
+ return CheckResult("type_correctness", True, "all arities match")
63
+
64
+
65
+ def check_geometry(ir: SemanticIR) -> CheckResult:
66
+ for node in ir.nodes:
67
+ if node.geometry and node.operation and node.operation.name == "multiply":
68
+ if len(node.operands) >= 2:
69
+ a = node.operands[0].geometry
70
+ b = node.operands[1].geometry
71
+ if a and b and a.col is not None and b.row is not None:
72
+ if a.col != b.row:
73
+ return CheckResult(
74
+ "geometry", False,
75
+ f"Matrix dim mismatch: A.col={a.col} β‰  B.row={b.row}",
76
+ )
77
+ return CheckResult("geometry", True, "all dimensions compatible")
78
+
79
+
80
+ def check_temporal_state(ir: SemanticIR) -> CheckResult:
81
+ transitions = ir.temporal_lock.audit()
82
+ for t in transitions:
83
+ if not t.is_valid():
84
+ return CheckResult(
85
+ "temporal_state", False,
86
+ f"Invalid Ο„ transition: {t.from_tau} β†’ {t.to_tau}",
87
+ )
88
+ return CheckResult("temporal_state", True, f"{len(transitions)} temporal transitions valid")
89
+
90
+
91
+ def check_boolean_normalization(bnf: BooleanNormalForm) -> CheckResult:
92
+ if not bnf.stable:
93
+ bnf.verify_stability()
94
+ if not bnf.stable:
95
+ return CheckResult("boolean_normalization", False, "normalize(normalize(x)) β‰  normalize(x)")
96
+ return CheckResult("boolean_normalization", True, "normalization idempotent")
97
+
98
+
99
+ def check_token_round_trip(stream: BooleanTokenStream) -> CheckResult:
100
+ try:
101
+ from .token_engine import decode_binary, encode_binary
102
+ binary = encode_binary(stream)
103
+ decoded = decode_binary(binary)
104
+ if len(decoded.tokens) != len(stream.tokens):
105
+ return CheckResult("token_round_trip", False,
106
+ f"Token count mismatch: {len(stream.tokens)} β†’ {len(decoded.tokens)}")
107
+ for orig, dec in zip(stream.tokens, decoded.tokens):
108
+ if orig.token_type != dec.token_type or orig.payload != dec.payload:
109
+ return CheckResult("token_round_trip", False,
110
+ f"Token mismatch at pos {orig.position}")
111
+ return CheckResult("token_round_trip", True, "encode/decode invariant holds")
112
+ except Exception as e:
113
+ return CheckResult("token_round_trip", False, str(e))
114
+
115
+
116
+ def check_latin_vocabulary(form: LatinCanonicalForm) -> CheckResult:
117
+ vocab = vocabulary()
118
+ for tok in form.tokens:
119
+ if tok.lexeme not in vocab and tok.lexeme not in ("OPUS",):
120
+ return CheckResult(
121
+ "latin_vocabulary", False,
122
+ f"Lexeme '{tok.lexeme}' not in controlled vocabulary",
123
+ )
124
+ return CheckResult("latin_vocabulary", True, "all lexemes in vocabulary")
125
+
126
+
127
+ def check_rosetta_mapping(mapping: RosettaMapping) -> CheckResult:
128
+ if not mapping.complete:
129
+ unmapped = [t.semantic_id for t in mapping.unmapped]
130
+ return CheckResult("rosetta_mapping", False, f"Unmapped: {unmapped}")
131
+ return CheckResult("rosetta_mapping", True, f"{len(mapping.entries)} entries mapped")
132
+
133
+
134
+ def check_subleq_validity(prog: SubleqProgram) -> CheckResult:
135
+ if not prog.instructions:
136
+ return CheckResult("subleq_validity", False, "Empty SUBLEQ program")
137
+ vm = SubleqVM(max_steps=10_000)
138
+ halts = vm.verify_halts(prog)
139
+ if not halts:
140
+ return CheckResult("subleq_validity", False, "Program does not halt within limit")
141
+ return CheckResult("subleq_validity", True, f"{len(prog.instructions)} instructions, halts")
142
+
143
+
144
+ def check_artifact_provenance(artifact: Artifact, ir: SemanticIR) -> CheckResult:
145
+ if artifact.semantic_root != ir.ir_id:
146
+ return CheckResult(
147
+ "artifact_provenance", False,
148
+ f"Artifact root {artifact.semantic_root} β‰  IR id {ir.ir_id}",
149
+ )
150
+ return CheckResult("artifact_provenance", True, "provenance chain intact")
151
+
152
+
153
+ def check_template_bindings(artifact: Artifact) -> CheckResult:
154
+ if not artifact.template:
155
+ return CheckResult("template_bindings", False, "No template bound to artifact")
156
+ if not artifact.content:
157
+ return CheckResult("template_bindings", False, "Artifact content is empty")
158
+ return CheckResult("template_bindings", True, "template bound and content non-empty")
159
+
160
+
161
+ # ── Verification engine ───────────────────────────────────────────────────────
162
+
163
+ class Verifier:
164
+ def verify_ir(self, ir: SemanticIR) -> VerificationReport:
165
+ report = VerificationReport(state=VerificationState.VALIDATING)
166
+ report.add(check_type_correctness(ir))
167
+ report.add(check_geometry(ir))
168
+ report.add(check_temporal_state(ir))
169
+ report.state = VerificationState.VERIFIED if report.passed else VerificationState.REJECTED
170
+ return report
171
+
172
+ def verify_boolean(self, bnf: BooleanNormalForm) -> VerificationReport:
173
+ report = VerificationReport(state=VerificationState.VALIDATING)
174
+ report.add(check_boolean_normalization(bnf))
175
+ report.state = VerificationState.VERIFIED if report.passed else VerificationState.REJECTED
176
+ return report
177
+
178
+ def verify_tokens(self, stream: BooleanTokenStream) -> VerificationReport:
179
+ report = VerificationReport(state=VerificationState.VALIDATING)
180
+ report.add(check_token_round_trip(stream))
181
+ report.state = VerificationState.VERIFIED if report.passed else VerificationState.REJECTED
182
+ return report
183
+
184
+ def verify_latin(self, form: LatinCanonicalForm) -> VerificationReport:
185
+ report = VerificationReport(state=VerificationState.VALIDATING)
186
+ report.add(check_latin_vocabulary(form))
187
+ report.state = VerificationState.VERIFIED if report.passed else VerificationState.REJECTED
188
+ return report
189
+
190
+ def verify_rosetta(self, mapping: RosettaMapping) -> VerificationReport:
191
+ report = VerificationReport(state=VerificationState.VALIDATING)
192
+ report.add(check_rosetta_mapping(mapping))
193
+ report.state = VerificationState.VERIFIED if report.passed else VerificationState.REJECTED
194
+ return report
195
+
196
+ def verify_subleq(self, prog: SubleqProgram) -> VerificationReport:
197
+ report = VerificationReport(state=VerificationState.VALIDATING)
198
+ report.add(check_subleq_validity(prog))
199
+ report.state = VerificationState.VERIFIED if report.passed else VerificationState.REJECTED
200
+ return report
201
+
202
+ def verify_artifact(self, artifact: Artifact, ir: SemanticIR) -> VerificationReport:
203
+ report = VerificationReport(state=VerificationState.VALIDATING)
204
+ report.add(check_artifact_provenance(artifact, ir))
205
+ report.add(check_template_bindings(artifact))
206
+ report.state = VerificationState.VERIFIED if report.passed else VerificationState.REJECTED
207
+ return report
208
+
209
+ def reject_if_failed(self, report: VerificationReport, stage: FSMState) -> None:
210
+ if not report.passed:
211
+ raise VerificationError(stage, "Verification failed:\n" + report.summary())
templates/backend.go.tmpl ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Code generated by semantic-engine. DO NOT EDIT.
2
+ // Semantic root: {{.SemanticRoot}}
3
+ // Temporal state: Ο„{{.Tau}}
4
+ // Verified: {{.Verified}}
5
+
6
+ package {{.Package}}
7
+
8
+ import (
9
+ "errors"
10
+ "fmt"
11
+ )
12
+
13
+ {{range .Types}}
14
+ // {{.Name}} β€” {{.Description}}
15
+ type {{.Name}} struct {
16
+ {{range .Fields}} {{.Name}} {{.Type}}
17
+ {{end}}}
18
+ {{end}}
19
+
20
+ {{range .Operations}}
21
+ // {{.Name}} implements {{.SemanticID}}
22
+ func {{.Name}}({{range .Params}}{{.Name}} {{.Type}}, {{end}}) ({{.ReturnType}}, error) {
23
+ {{range .Constraints}} // Constraint: {{.}}
24
+ {{end}}
25
+ {{.Body}}
26
+ }
27
+ {{end}}
28
+
29
+ {{range .Tests}}
30
+ func Test{{.Name}}(t *testing.T) {
31
+ {{.Body}}
32
+ }
33
+ {{end}}
templates/document.mustache ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # {{title}}
2
+
3
+ {{#semantic_description}}
4
+ ## Description
5
+
6
+ {{semantic_description}}
7
+ {{/semantic_description}}
8
+
9
+ {{#constraints}}
10
+ ## Constraints
11
+
12
+ - {{.}}
13
+ {{/constraints}}
14
+
15
+ {{#sections}}
16
+ ## {{name}}
17
+
18
+ {{content}}
19
+
20
+ {{/sections}}
21
+
22
+ {{#provenance}}
23
+ ---
24
+ _Provenance: {{.}}_
25
+ {{/provenance}}
templates/prompt.mustache ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{title}}
2
+
3
+ {{semantic_description}}
4
+
5
+ {{#constraints}}
6
+ Constraint: {{.}}
7
+ {{/constraints}}
8
+
9
+ {{#geometry}}
10
+ Geometry: {{.}}
11
+ {{/geometry}}
12
+
13
+ {{#operations}}
14
+ Operation: {{.}}
15
+ {{/operations}}
16
+
17
+ {{#code}}
18
+ ```
19
+ {{code}}
20
+ ```
21
+ {{/code}}
22
+
23
+ {{#tests}}
24
+ Tests:
25
+ {{tests}}
26
+ {{/tests}}
27
+
28
+ {{#provenance}}
29
+ Provenance: {{.}}
30
+ {{/provenance}}
tests/conftest.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
tests/test_boolean.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Tests: Phase 7 β€” Boolean engine."""
6
+
7
+ import pytest
8
+ from semantic_engine.boolean_engine import (
9
+ AND, OR, NOT, XOR, IMPLIES, EQUIV,
10
+ BooleanLiteral, BooleanVar, BooleanExpression,
11
+ var, lit, normalize, to_cnf, simplify,
12
+ semantic_constraints_to_boolean,
13
+ )
14
+ from semantic_engine.types import BooleanOperator
15
+
16
+
17
+ def test_literal_repr():
18
+ assert repr(BooleanLiteral(True)) == "⊀"
19
+ assert repr(BooleanLiteral(False)) == "βŠ₯"
20
+
21
+
22
+ def test_var_repr():
23
+ assert repr(BooleanVar("x")) == "x"
24
+ assert repr(BooleanVar("x", negated=True)) == "Β¬x"
25
+
26
+
27
+ def test_and_repr():
28
+ e = AND(var("a"), var("b"))
29
+ assert "∧" in repr(e)
30
+
31
+
32
+ def test_simplify_double_negation():
33
+ e = NOT(NOT(var("x")))
34
+ result = simplify(e)
35
+ assert isinstance(result, BooleanVar)
36
+ assert result.name == "x"
37
+ assert not result.negated
38
+
39
+
40
+ def test_simplify_and_with_true():
41
+ e = AND(BooleanLiteral(True), var("x"))
42
+ result = simplify(e)
43
+ assert isinstance(result, BooleanVar)
44
+ assert result.name == "x"
45
+
46
+
47
+ def test_simplify_and_with_false():
48
+ e = AND(BooleanLiteral(False), var("x"))
49
+ result = simplify(e)
50
+ assert isinstance(result, BooleanLiteral)
51
+ assert not result.value
52
+
53
+
54
+ def test_simplify_or_with_true():
55
+ e = OR(BooleanLiteral(True), var("x"))
56
+ result = simplify(e)
57
+ assert isinstance(result, BooleanLiteral)
58
+ assert result.value
59
+
60
+
61
+ def test_implies_expansion():
62
+ e = IMPLIES(var("a"), var("b"))
63
+ result = simplify(e)
64
+ assert repr(result) # should not crash
65
+
66
+
67
+ def test_equivalence_expansion():
68
+ e = EQUIV(var("a"), var("b"))
69
+ result = simplify(e)
70
+ assert repr(result)
71
+
72
+
73
+ def test_xor_expansion():
74
+ e = XOR(var("a"), var("b"))
75
+ result = simplify(e)
76
+ assert repr(result)
77
+
78
+
79
+ def test_normalize_idempotent():
80
+ e = AND(var("a"), OR(var("b"), NOT(var("c"))))
81
+ n1 = normalize(e)
82
+ n2 = normalize(n1)
83
+ assert repr(n1) == repr(n2)
84
+
85
+
86
+ def test_normalize_literal_true():
87
+ assert isinstance(normalize(BooleanLiteral(True)), BooleanLiteral)
88
+
89
+
90
+ def test_constraints_to_boolean_empty():
91
+ bnf = semantic_constraints_to_boolean([])
92
+ assert isinstance(bnf.normalized, BooleanLiteral)
93
+ assert bnf.normalized.value is True
94
+
95
+
96
+ def test_constraints_to_boolean_single():
97
+ from semantic_engine.ir import Constraint
98
+ c = Constraint.make("deterministic", "deterministic")
99
+ bnf = semantic_constraints_to_boolean([c])
100
+ assert bnf.stable
101
+
102
+
103
+ def test_constraints_to_boolean_multiple():
104
+ from semantic_engine.ir import Constraint
105
+ cs = [Constraint.make("deterministic", "deterministic"),
106
+ Constraint.make("immutable", "immutable")]
107
+ bnf = semantic_constraints_to_boolean(cs)
108
+ assert bnf.stable
tests/test_fsm.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Tests: Phase 3 β€” Decoder FSM."""
6
+
7
+ import pytest
8
+ from semantic_engine.fsm import FSMContext, TRANSITION_TABLE, validate_constraints
9
+ from semantic_engine.types import FSMState, PipelineError
10
+
11
+
12
+ def test_initial_state():
13
+ ctx = FSMContext()
14
+ assert ctx.state == FSMState.INPUT
15
+
16
+
17
+ def test_valid_transition():
18
+ ctx = FSMContext()
19
+ ctx.set("raw_text", "hello world")
20
+ t = ctx.transition("raw_input")
21
+ assert ctx.state == FSMState.NORMALIZE
22
+ assert t.source_state == FSMState.INPUT
23
+
24
+
25
+ def test_invalid_transition_fails_closed():
26
+ ctx = FSMContext()
27
+ with pytest.raises(PipelineError) as exc_info:
28
+ ctx.transition("invalid_class_xyz")
29
+ assert "fail" in str(exc_info.value).lower() or "transition" in str(exc_info.value).lower()
30
+
31
+
32
+ def test_transition_history_recorded():
33
+ ctx = FSMContext()
34
+ ctx.set("raw_text", "hello")
35
+ ctx.transition("raw_input")
36
+ assert len(ctx.history) == 1
37
+ src, inp, dst = ctx.history[0]
38
+ assert src == FSMState.INPUT
39
+ assert dst == FSMState.NORMALIZE
40
+
41
+
42
+ def test_all_transitions_explicit():
43
+ """Every transition in the table must have source, input_class, destination."""
44
+ for t in TRANSITION_TABLE:
45
+ assert t.source_state is not None
46
+ assert t.input_class
47
+ assert t.destination_state is not None
48
+ assert t.invariant
49
+
50
+
51
+ def test_transition_table_no_duplicate_keys():
52
+ keys = [(t.source_state, t.input_class) for t in TRANSITION_TABLE]
53
+ assert len(keys) == len(set(keys)), "Duplicate transition keys found"
54
+
55
+
56
+ def test_is_terminal():
57
+ ctx = FSMContext()
58
+ assert not ctx.is_terminal()
59
+ ctx.state = FSMState.COMPLETE
60
+ assert ctx.is_terminal()
61
+ ctx.state = FSMState.ERROR
62
+ assert ctx.is_terminal()
63
+
64
+
65
+ def test_context_set_get():
66
+ ctx = FSMContext()
67
+ ctx.set("foo", 42)
68
+ assert ctx.get("foo") == 42
69
+ assert ctx.get("missing") is None
tests/test_ir.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Tests: Phase 2 β€” Semantic IR."""
6
+
7
+ import pytest
8
+ from semantic_engine.ir import (
9
+ Geometry, TemporalState, TemporalLock, TemporalTransition,
10
+ Operand, Constraint, SemanticOperation, SemanticNode,
11
+ SemanticIR, Artifact,
12
+ )
13
+ from semantic_engine.types import (
14
+ ArtifactType, FSMState, IntentClass, Provenance,
15
+ SemanticNodeType, TemporalViolation, VerificationState,
16
+ )
17
+
18
+
19
+ def make_provenance():
20
+ return Provenance("input-1", "test")
21
+
22
+
23
+ def make_temporal():
24
+ return TemporalState(tau=0)
25
+
26
+
27
+ def test_geometry_matmul_compatible():
28
+ a = Geometry(row=3, col=4)
29
+ b = Geometry(row=4, col=5)
30
+ assert a.compatible_with(b)
31
+ out = a.output_geometry(b)
32
+ assert out.row == 3
33
+ assert out.col == 5
34
+
35
+
36
+ def test_geometry_matmul_incompatible():
37
+ a = Geometry(row=3, col=4)
38
+ b = Geometry(row=5, col=5)
39
+ assert not a.compatible_with(b)
40
+
41
+
42
+ def test_temporal_advance():
43
+ t = TemporalState(tau=0)
44
+ t1 = t.advance("step1")
45
+ assert t1.tau == 1
46
+ assert t.tau == 0 # original unchanged
47
+
48
+
49
+ def test_temporal_lock_prevents_advance():
50
+ t = TemporalState(tau=0, locked=True)
51
+ with pytest.raises(TemporalViolation):
52
+ t.advance("should fail")
53
+
54
+
55
+ def test_temporal_lock_records_transitions():
56
+ lock = TemporalLock()
57
+ t = TemporalTransition(from_tau=0, to_tau=1, reason="step", stage="test")
58
+ lock.record(t)
59
+ assert len(lock.audit()) == 1
60
+
61
+
62
+ def test_temporal_lock_rejects_invalid_jump():
63
+ lock = TemporalLock()
64
+ bad = TemporalTransition(from_tau=0, to_tau=5, reason="jump", stage="test")
65
+ with pytest.raises(TemporalViolation):
66
+ lock.record(bad)
67
+
68
+
69
+ def test_semantic_node_stable_id():
70
+ p = make_provenance()
71
+ t = make_temporal()
72
+ op = SemanticOperation("build", 1)
73
+ n1 = SemanticNode.make(SemanticNodeType.OPERATION, op, [], [], None, t, p)
74
+ n2 = SemanticNode.make(SemanticNodeType.OPERATION, op, [], [], None, t, p)
75
+ assert n1.id == n2.id
76
+
77
+
78
+ def test_semantic_ir_create():
79
+ p = make_provenance()
80
+ t = make_temporal()
81
+ op = SemanticOperation("build", 0)
82
+ node = SemanticNode.make(SemanticNodeType.INTENT, op, [], [], None, t, p)
83
+ ir = SemanticIR.create(IntentClass.BUILD, node, [node], TemporalLock(), p)
84
+ assert ir.ir_id
85
+ assert ir.intent == IntentClass.BUILD
86
+ assert ir.verification_state == VerificationState.UNVERIFIED
87
+
88
+
89
+ def test_semantic_ir_find_node():
90
+ p = make_provenance()
91
+ t = make_temporal()
92
+ op = SemanticOperation("build", 0)
93
+ node = SemanticNode.make(SemanticNodeType.INTENT, op, [], [], None, t, p)
94
+ ir = SemanticIR.create(IntentClass.BUILD, node, [node], TemporalLock(), p)
95
+ found = ir.find_node(node.id)
96
+ assert found is node
97
+ assert ir.find_node("nonexistent") is None
98
+
99
+
100
+ def test_artifact_create():
101
+ p = make_provenance()
102
+ t = make_temporal()
103
+ a = Artifact.create(ArtifactType.DOCUMENT, "root-id", "tmpl", "content", p, t)
104
+ assert a.type == ArtifactType.DOCUMENT
105
+ assert a.content == "content"
106
+ assert a.semantic_root == "root-id"
tests/test_roundtrip.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Tests: Phase 20 β€” Round-trip and equivalence invariants."""
6
+
7
+ import pytest
8
+ from semantic_engine.boolean_engine import (
9
+ AND, OR, NOT, XOR, IMPLIES, EQUIV,
10
+ BooleanLiteral, BooleanVar,
11
+ normalize, var, lit,
12
+ )
13
+ from semantic_engine.token_engine import encode, decode, round_trip, BoolEncoder, encode_binary, decode_binary
14
+ from semantic_engine.latin import LatinCanonicalizer
15
+ from semantic_engine.rosetta import RosettaMapper
16
+
17
+
18
+ # ── Boolean round-trips ───────────────────────────────────────────────────────
19
+
20
+ CANONICAL_EXPRS = [
21
+ BooleanLiteral(True),
22
+ BooleanLiteral(False),
23
+ BooleanVar("x"),
24
+ BooleanVar("x", negated=True),
25
+ AND(BooleanVar("a"), BooleanVar("b")),
26
+ OR(BooleanVar("a"), BooleanVar("b")),
27
+ NOT(BooleanVar("x")),
28
+ AND(BooleanVar("a"), OR(BooleanVar("b"), BooleanVar("c"))),
29
+ OR(AND(BooleanVar("a"), BooleanVar("b")), NOT(BooleanVar("c"))),
30
+ ]
31
+
32
+
33
+ @pytest.mark.parametrize("expr", CANONICAL_EXPRS)
34
+ def test_token_round_trip(expr):
35
+ """decode(encode(x)) == x for all canonical Boolean structures."""
36
+ result = round_trip(expr)
37
+ assert repr(result) == repr(expr), f"Round-trip failed for {expr}"
38
+
39
+
40
+ @pytest.mark.parametrize("expr", CANONICAL_EXPRS)
41
+ def test_normalize_idempotent(expr):
42
+ """normalize(normalize(x)) == normalize(x)"""
43
+ n1 = normalize(expr)
44
+ n2 = normalize(n1)
45
+ assert repr(n1) == repr(n2), f"Normalization not idempotent for {expr}"
46
+
47
+
48
+ # ── Binary serialization round-trips ─────────────────────────────────────────
49
+
50
+ @pytest.mark.parametrize("expr", CANONICAL_EXPRS)
51
+ def test_binary_round_trip(expr):
52
+ """Binary encode/decode preserves all tokens."""
53
+ stream = BoolEncoder().encode_expr(expr)
54
+ binary = encode_binary(stream)
55
+ decoded_stream = decode_binary(binary)
56
+ assert len(stream.tokens) == len(decoded_stream.tokens)
57
+ for orig, dec in zip(stream.tokens, decoded_stream.tokens):
58
+ assert orig.token_type == dec.token_type
59
+ assert orig.payload == dec.payload
60
+
61
+
62
+ # ── Latin round-trip ─────────────────────────────────────────────────────────
63
+
64
+ @pytest.mark.parametrize("expr", CANONICAL_EXPRS)
65
+ def test_latin_derivable_from_tokens(expr):
66
+ """Latin canonical form must derive from the token stream without loss."""
67
+ stream = BoolEncoder().encode_expr(expr)
68
+ latin = LatinCanonicalizer().canonicalize(stream)
69
+ assert len(latin.tokens) > 0
70
+
71
+
72
+ # ── Pipeline semantic preservation ───────────────────────────────────────────
73
+
74
+ def test_pipeline_preserves_intent():
75
+ from semantic_engine import SemanticPipeline, RawInput, OutputMode
76
+ r = RawInput("build a matrix multiplication function")
77
+ result = SemanticPipeline().run(r)
78
+ assert result.ir is not None
79
+ from semantic_engine.types import IntentClass
80
+ assert result.ir.intent == IntentClass.BUILD
81
+
82
+
83
+ def test_pipeline_verified_on_valid_input():
84
+ from semantic_engine import SemanticPipeline, RawInput
85
+ from semantic_engine.types import VerificationState
86
+ r = RawInput("multiply two matrices")
87
+ result = SemanticPipeline().run(r)
88
+ assert result.verification_state == VerificationState.VERIFIED
89
+
90
+
91
+ def test_pipeline_artifact_traces_to_ir():
92
+ from semantic_engine import SemanticPipeline, RawInput
93
+ r = RawInput("generate a parser for binary format")
94
+ result = SemanticPipeline().run(r)
95
+ assert result.artifact is not None
96
+ assert result.artifact.semantic_root == result.ir.ir_id
97
+
98
+
99
+ def test_pipeline_hashes_recorded():
100
+ from semantic_engine import SemanticPipeline, RawInput
101
+ r = RawInput("validate packet structure")
102
+ result = SemanticPipeline().run(r)
103
+ assert "input" in result.hashes
104
+ assert "semantic_ir" in result.hashes
105
+ assert "subleq" in result.hashes
106
+ assert "artifact" in result.hashes
107
+
108
+
109
+ def test_pipeline_deterministic_on_same_input():
110
+ """Same input β†’ same semantic IR hash (modulo UUIDs)."""
111
+ from semantic_engine import SemanticPipeline, RawInput
112
+ text = "compute matrix product"
113
+ r1 = RawInput(text)
114
+ r2 = RawInput(text)
115
+ p = SemanticPipeline()
116
+ res1 = p.run(r1)
117
+ res2 = p.run(r2)
118
+ # Both must succeed and produce same intent
119
+ assert res1.ir.intent == res2.ir.intent
120
+ assert res1.ir.root.id == res2.ir.root.id # node ID is content-addressed
121
+
122
+
123
+ def test_pipeline_two_output_modes():
124
+ """Same IR must be able to produce both NL and code artifacts."""
125
+ from semantic_engine import SemanticPipeline, RawInput, OutputMode
126
+ r = RawInput("build a validation service")
127
+ p = SemanticPipeline()
128
+ nl = p.run(r, output_mode=OutputMode.NATURAL_LANGUAGE)
129
+ code = p.run(RawInput("build a validation service"), output_mode=OutputMode.CODE)
130
+ assert nl.artifact is not None
131
+ assert code.artifact is not None
132
+ assert nl.artifact.type.value != code.artifact.type.value
133
+
134
+
135
+ def test_pipeline_rejects_empty_input():
136
+ from semantic_engine import SemanticPipeline, RawInput
137
+ r = RawInput("")
138
+ result = SemanticPipeline().run(r)
139
+ # May complete but should not crash
140
+ assert result is not None
tests/test_subleq.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Tests: Phases 12-13 β€” SUBLEQ IR and VM."""
6
+
7
+ import pytest
8
+ from semantic_engine.subleq import (
9
+ SubleqInstruction, SubleqProgram, SubleqMemory, SubleqVM,
10
+ HALT_ADDRESS, MAX_MEMORY,
11
+ )
12
+ from semantic_engine.types import PipelineError
13
+
14
+
15
+ def halt_prog() -> SubleqProgram:
16
+ prog = SubleqProgram()
17
+ prog.data[0] = 0 # zero cell
18
+ prog.add(SubleqInstruction(A=0, B=0, C=HALT_ADDRESS, comment="HALT"))
19
+ return prog
20
+
21
+
22
+ def test_instruction_repr():
23
+ instr = SubleqInstruction(A=1, B=2, C=3)
24
+ r = repr(instr)
25
+ assert "SUBLEQ" in r
26
+
27
+
28
+ def test_instruction_halt_repr():
29
+ instr = SubleqInstruction(A=0, B=0, C=HALT_ADDRESS)
30
+ assert "HALT" in repr(instr)
31
+
32
+
33
+ def test_program_add_and_label():
34
+ prog = SubleqProgram()
35
+ prog.add(SubleqInstruction(A=0, B=0, C=HALT_ADDRESS, label="start"))
36
+ assert "start" in prog.labels
37
+ assert prog.labels["start"] == 0
38
+
39
+
40
+ def test_program_resolve_label():
41
+ prog = SubleqProgram()
42
+ prog.add(SubleqInstruction(A=0, B=0, C=HALT_ADDRESS, label="end"))
43
+ assert prog.resolve_label("end") == 0
44
+
45
+
46
+ def test_program_resolve_missing_label():
47
+ prog = SubleqProgram()
48
+ with pytest.raises(PipelineError):
49
+ prog.resolve_label("missing")
50
+
51
+
52
+ def test_assemble_disassemble_roundtrip():
53
+ prog = SubleqProgram()
54
+ prog.add(SubleqInstruction(A=1, B=2, C=3))
55
+ prog.add(SubleqInstruction(A=4, B=5, C=HALT_ADDRESS))
56
+ binary = prog.assemble()
57
+ recovered = SubleqProgram.disassemble(binary)
58
+ assert len(recovered.instructions) == 2
59
+ assert recovered.instructions[0].A == 1
60
+ assert recovered.instructions[0].B == 2
61
+ assert recovered.instructions[1].C == HALT_ADDRESS
62
+
63
+
64
+ def test_assemble_bad_alignment():
65
+ with pytest.raises(PipelineError):
66
+ SubleqProgram.disassemble(b"\x00\x01\x02\x03")
67
+
68
+
69
+ def test_memory_read_write():
70
+ mem = SubleqMemory()
71
+ mem.write(100, 42)
72
+ assert mem.read(100) == 42
73
+
74
+
75
+ def test_memory_out_of_bounds():
76
+ mem = SubleqMemory()
77
+ with pytest.raises(PipelineError):
78
+ mem.read(MAX_MEMORY + 1)
79
+ with pytest.raises(PipelineError):
80
+ mem.write(-1, 0)
81
+
82
+
83
+ def test_vm_halts():
84
+ prog = halt_prog()
85
+ vm = SubleqVM()
86
+ vm.load(prog)
87
+ vm.execute()
88
+ assert vm.halted
89
+
90
+
91
+ def test_vm_step_returns_false_on_halt():
92
+ prog = halt_prog()
93
+ vm = SubleqVM()
94
+ vm.load(prog)
95
+ while not vm.halted:
96
+ result = vm.step()
97
+ assert not result
98
+
99
+
100
+ def test_vm_trace_recorded():
101
+ prog = halt_prog()
102
+ vm = SubleqVM()
103
+ vm.load(prog)
104
+ trace = vm.execute()
105
+ assert len(trace) >= 1
106
+ assert trace[0].pc >= 0 # PC starts at base offset
107
+
108
+
109
+ def test_vm_max_steps_exceeded():
110
+ prog = SubleqProgram()
111
+ # Infinite loop: SUBLEQ 0 0 0
112
+ prog.data[0] = 0
113
+ prog.add(SubleqInstruction(A=0, B=0, C=0))
114
+ vm = SubleqVM(max_steps=10)
115
+ vm.load(prog)
116
+ with pytest.raises(PipelineError) as exc:
117
+ vm.execute()
118
+ assert "max" in str(exc.value).lower() or "steps" in str(exc.value).lower()
119
+
120
+
121
+ def test_verify_halts():
122
+ prog = halt_prog()
123
+ vm = SubleqVM()
124
+ assert vm.verify_halts(prog)
125
+
126
+
127
+ def test_subleq_arithmetic():
128
+ """SUBLEQ(A, B, C): mem[B] -= mem[A]; branch if <=0"""
129
+ prog = SubleqProgram()
130
+ # Use addresses 10, 11 for data (well below base=100 for instructions)
131
+ prog.data[10] = 5
132
+ prog.data[11] = 3
133
+ prog.add(SubleqInstruction(A=10, B=11, C=HALT_ADDRESS))
134
+ vm = SubleqVM()
135
+ vm.load(prog)
136
+ vm.execute()
137
+ assert vm.memory.read(11) == -2
tests/test_tokens.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Tests: Phase 8 β€” Boolean token engine."""
6
+
7
+ import pytest
8
+ from semantic_engine.boolean_engine import AND, OR, NOT, var, lit, BooleanLiteral, BooleanVar
9
+ from semantic_engine.token_engine import (
10
+ BoolEncoder, BoolDecoder, BooleanTokenStream,
11
+ encode_binary, decode_binary, encode, decode, round_trip,
12
+ TTYPE_AND, TTYPE_OR, TTYPE_NOT, TTYPE_TRUE, TTYPE_FALSE,
13
+ TTYPE_VAR, TTYPE_VAR_NEG, TTYPE_OPEN, TTYPE_CLOSE,
14
+ )
15
+ from semantic_engine.types import FSMState, TokenError
16
+
17
+
18
+ def encode_and_check(expr):
19
+ stream = BoolEncoder().encode_expr(expr)
20
+ binary = encode_binary(stream)
21
+ decoded = decode_binary(binary)
22
+ return stream, binary, decoded
23
+
24
+
25
+ def test_encode_literal_true():
26
+ stream, binary, decoded = encode_and_check(BooleanLiteral(True))
27
+ assert len(decoded.tokens) == 1
28
+ assert decoded.tokens[0].token_type == TTYPE_TRUE
29
+
30
+
31
+ def test_encode_literal_false():
32
+ stream, binary, decoded = encode_and_check(BooleanLiteral(False))
33
+ assert decoded.tokens[0].token_type == TTYPE_FALSE
34
+
35
+
36
+ def test_encode_var():
37
+ stream, binary, decoded = encode_and_check(BooleanVar("x"))
38
+ assert decoded.tokens[0].token_type == TTYPE_VAR
39
+ assert decoded.tokens[0].payload == b"x"
40
+
41
+
42
+ def test_encode_var_negated():
43
+ stream, binary, decoded = encode_and_check(BooleanVar("x", negated=True))
44
+ assert decoded.tokens[0].token_type == TTYPE_VAR_NEG
45
+ assert decoded.tokens[0].payload == b"x"
46
+
47
+
48
+ def test_encode_and():
49
+ expr = AND(var("a"), var("b"))
50
+ stream = BoolEncoder().encode_expr(expr)
51
+ types = [t.token_type for t in stream]
52
+ assert TTYPE_AND in types
53
+ assert TTYPE_OPEN in types
54
+ assert TTYPE_CLOSE in types
55
+
56
+
57
+ def test_encode_not():
58
+ expr = NOT(var("x"))
59
+ stream = BoolEncoder().encode_expr(expr)
60
+ types = [t.token_type for t in stream]
61
+ assert TTYPE_NOT in types
62
+
63
+
64
+ def test_round_trip_literal():
65
+ expr = BooleanLiteral(True)
66
+ result = round_trip(expr)
67
+ assert isinstance(result, BooleanLiteral)
68
+ assert result.value is True
69
+
70
+
71
+ def test_round_trip_var():
72
+ expr = BooleanVar("myvar")
73
+ result = round_trip(expr)
74
+ assert isinstance(result, BooleanVar)
75
+ assert result.name == "myvar"
76
+ assert not result.negated
77
+
78
+
79
+ def test_round_trip_complex():
80
+ expr = AND(var("a"), OR(var("b"), NOT(var("c"))))
81
+ result = round_trip(expr)
82
+ # repr should match after round-trip
83
+ assert repr(result) == repr(expr)
84
+
85
+
86
+ def test_binary_bad_magic():
87
+ with pytest.raises(TokenError):
88
+ decode_binary(b"XXXX\x01\x00\x01")
89
+
90
+
91
+ def test_binary_too_short():
92
+ with pytest.raises(TokenError):
93
+ decode_binary(b"BO")
94
+
95
+
96
+ def test_token_positions():
97
+ expr = AND(var("a"), var("b"))
98
+ stream = BoolEncoder().encode_expr(expr)
99
+ for i, tok in enumerate(stream):
100
+ assert tok.position == i
101
+
102
+
103
+ def test_token_ids_unique():
104
+ expr = AND(var("a"), OR(var("b"), var("c")))
105
+ stream = BoolEncoder().encode_expr(expr)
106
+ ids = [t.token_id for t in stream]
107
+ assert len(ids) == len(set(ids))
tests/test_types.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Tests: Phase 1 β€” Core types."""
6
+
7
+ import pytest
8
+ from semantic_engine.types import (
9
+ RawInput, NormalizedInput, SemanticCandidate,
10
+ FSMState, VerificationState, ArtifactType, BooleanOperator,
11
+ IntentClass, Provenance, FSMTransition, PipelineError,
12
+ )
13
+
14
+
15
+ def test_raw_input_immutable():
16
+ r = RawInput("hello world")
17
+ with pytest.raises(AttributeError):
18
+ r.text = "changed"
19
+
20
+
21
+ def test_raw_input_hash_deterministic():
22
+ r1 = RawInput("hello")
23
+ r2 = RawInput("hello")
24
+ assert r1.hash == r2.hash
25
+
26
+
27
+ def test_raw_input_unique_id():
28
+ r1 = RawInput("hello")
29
+ r2 = RawInput("hello")
30
+ assert r1.input_id != r2.input_id
31
+
32
+
33
+ def test_semantic_candidate_confidence_range():
34
+ with pytest.raises(ValueError):
35
+ SemanticCandidate("id", IntentClass.BUILD, [], [], [], [], 1.5, False)
36
+ with pytest.raises(ValueError):
37
+ SemanticCandidate("id", IntentClass.BUILD, [], [], [], [], -0.1, False)
38
+
39
+
40
+ def test_semantic_candidate_valid():
41
+ c = SemanticCandidate("id", IntentClass.BUILD, ["matrix"], ["build"], [], [], 0.8, False)
42
+ assert c.confidence == 0.8
43
+
44
+
45
+ def test_fsm_states_unique():
46
+ values = [s.value for s in FSMState]
47
+ assert len(values) == len(set(values))
48
+
49
+
50
+ def test_provenance_chain_hash():
51
+ p = Provenance("input-1", "test", transform_hash="abc")
52
+ h1 = p.chain_hash("data")
53
+ h2 = p.chain_hash("data")
54
+ assert h1 == h2
55
+
56
+
57
+ def test_fsm_transition_validate():
58
+ t = FSMTransition(
59
+ FSMState.INPUT, "raw_input", "normalize",
60
+ ("text_not_empty",), FSMState.NORMALIZE, "input preserved",
61
+ )
62
+ assert t.validate(FSMState.INPUT, "raw_input")
63
+ assert not t.validate(FSMState.COMPLETE, "raw_input")
64
+ assert not t.validate(FSMState.INPUT, "wrong_class")
65
+
66
+
67
+ def test_pipeline_error_message():
68
+ e = PipelineError(FSMState.TOKENIZATION, "token broken")
69
+ assert "TOKENIZATION" in str(e)
70
+ assert "token broken" in str(e)
tests/test_unknown.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """Tests: ADR-Ξ©-0001 β€” The Uninterpreted Operator and WORM Seal Engine."""
6
+
7
+ import pytest
8
+ from semantic_engine.unknown import (
9
+ InvariantState, ObservationRecord, UnknownClassification,
10
+ WORMSealChain, WORMSealEntry, UnknownNode, UnknownOperatorError,
11
+ observe_and_seal, _seal_hash, _canonical,
12
+ )
13
+
14
+
15
+ # ── InvariantState ────────────────────────────────────────────────────────────
16
+
17
+ def test_invariant_hash_deterministic():
18
+ omega = InvariantState(tau=0, system_hash="abc123", properties=("typed", "deterministic"))
19
+ assert omega.hash() == omega.hash()
20
+
21
+
22
+ def test_invariant_hash_changes_with_tau():
23
+ o1 = InvariantState(tau=0, system_hash="abc")
24
+ o2 = InvariantState(tau=1, system_hash="abc")
25
+ assert o1.hash() != o2.hash()
26
+
27
+
28
+ # ── ObservationRecord ─────────────────────────────────────────────────────────
29
+
30
+ def make_omega(tau=0):
31
+ return InvariantState(tau=tau, system_hash="test_hash")
32
+
33
+
34
+ def test_observation_record_hash_valid():
35
+ omega = make_omega()
36
+ rec = ObservationRecord.observe("input_A", "output_B", omega)
37
+ assert rec.verify()
38
+
39
+
40
+ def test_observation_record_fabricated_hash_fails():
41
+ omega = make_omega()
42
+ rec = ObservationRecord.observe("input_A", "output_B", omega)
43
+ # Tamper with provenance hash
44
+ tampered = ObservationRecord(
45
+ input_state=rec.input_state,
46
+ output_state=rec.output_state,
47
+ provenance_hash="deadbeef" * 8,
48
+ timestamp=rec.timestamp,
49
+ invariant=rec.invariant,
50
+ classification=rec.classification,
51
+ )
52
+ assert not tampered.verify()
53
+
54
+
55
+ def test_observation_starts_as_observed():
56
+ rec = ObservationRecord.observe("A", "B", make_omega())
57
+ assert rec.classification == UnknownClassification.OBSERVED
58
+
59
+
60
+ def test_classification_pipeline_valid():
61
+ rec = ObservationRecord.observe("A", "B", make_omega())
62
+ rec = rec.advance(UnknownClassification.CAPTURED)
63
+ rec = rec.advance(UnknownClassification.HASHED)
64
+ rec = rec.advance(UnknownClassification.COMPARED)
65
+ rec = rec.advance(UnknownClassification.TESTED)
66
+ rec = rec.advance(UnknownClassification.CLASSIFIED)
67
+ assert rec.classification == UnknownClassification.CLASSIFIED
68
+
69
+
70
+ def test_classification_skip_not_allowed():
71
+ """UNKNOWN must not become UNDERSTOOD by skipping stages."""
72
+ rec = ObservationRecord.observe("A", "B", make_omega())
73
+ with pytest.raises(UnknownOperatorError):
74
+ rec.advance(UnknownClassification.CLASSIFIED) # skip CAPTURED, HASHED, etc.
75
+
76
+
77
+ def test_classification_backward_not_allowed():
78
+ rec = ObservationRecord.observe("A", "B", make_omega())
79
+ rec = rec.advance(UnknownClassification.CAPTURED)
80
+ with pytest.raises(UnknownOperatorError):
81
+ rec.advance(UnknownClassification.OBSERVED) # go backward
82
+
83
+
84
+ def test_unknown_is_not_true_or_false():
85
+ """Fundamental property: UNKNOWN β‰  FALSE, UNKNOWN β‰  TRUE."""
86
+ rec = ObservationRecord.observe("A", "B", make_omega())
87
+ node = UnknownNode(node_id="test", observation=rec)
88
+ assert not node.is_understood
89
+ # The node never claims to know what ? means
90
+ assert "?" in repr(node)
91
+
92
+
93
+ # ── Seal hash ─────────────────────────────────────────────────────────────────
94
+
95
+ def test_seal_hash_deterministic():
96
+ omega = make_omega()
97
+ h1 = _seal_hash("A", "B", omega)
98
+ h2 = _seal_hash("A", "B", omega)
99
+ assert h1 == h2
100
+
101
+
102
+ def test_seal_hash_changes_with_input():
103
+ omega = make_omega()
104
+ h1 = _seal_hash("A", "B", omega)
105
+ h2 = _seal_hash("X", "B", omega)
106
+ assert h1 != h2
107
+
108
+
109
+ def test_seal_hash_changes_with_output():
110
+ omega = make_omega()
111
+ h1 = _seal_hash("A", "B", omega)
112
+ h2 = _seal_hash("A", "Z", omega)
113
+ assert h1 != h2
114
+
115
+
116
+ def test_seal_hash_includes_omega():
117
+ o1 = make_omega(tau=0)
118
+ o2 = make_omega(tau=1)
119
+ h1 = _seal_hash("A", "B", o1)
120
+ h2 = _seal_hash("A", "B", o2)
121
+ assert h1 != h2
122
+
123
+
124
+ # ── WORM Seal Chain ───────────────────────────────────────────────────────────
125
+
126
+ def test_worm_chain_starts_empty():
127
+ chain = WORMSealChain()
128
+ assert len(chain) == 0
129
+ assert chain.head() is None
130
+
131
+
132
+ def test_worm_seal_appends():
133
+ chain = WORMSealChain()
134
+ rec = ObservationRecord.observe("A", "B", make_omega())
135
+ entry = chain.seal(rec)
136
+ assert len(chain) == 1
137
+ assert entry.index == 0
138
+ assert entry.previous_hash == "0" * 64
139
+
140
+
141
+ def test_worm_chain_links():
142
+ chain = WORMSealChain()
143
+ r1 = ObservationRecord.observe("A", "B", make_omega(0))
144
+ r2 = ObservationRecord.observe("B", "C", make_omega(1))
145
+ e1 = chain.seal(r1)
146
+ e2 = chain.seal(r2)
147
+ assert e2.previous_hash == e1.entry_hash
148
+ assert e2.index == 1
149
+
150
+
151
+ def test_worm_chain_verify_intact():
152
+ chain = WORMSealChain()
153
+ for i in range(5):
154
+ rec = ObservationRecord.observe(f"input_{i}", f"output_{i}", make_omega(i))
155
+ chain.seal(rec)
156
+ ok, errors = chain.verify_chain()
157
+ assert ok
158
+ assert errors == []
159
+
160
+
161
+ def test_worm_chain_audit_returns_all():
162
+ chain = WORMSealChain()
163
+ for i in range(3):
164
+ rec = ObservationRecord.observe(f"A{i}", f"B{i}", make_omega(i))
165
+ chain.seal(rec)
166
+ audit = chain.audit()
167
+ assert len(audit) == 3
168
+ for entry in audit:
169
+ assert "entry_hash" in entry
170
+ assert "provenance_hash" in entry
171
+
172
+
173
+ def test_worm_seal_not_mutable():
174
+ """Once sealed, the entry is frozen (frozen dataclass)."""
175
+ chain = WORMSealChain()
176
+ rec = ObservationRecord.observe("A", "B", make_omega())
177
+ entry = chain.seal(rec)
178
+ with pytest.raises((AttributeError, TypeError)):
179
+ entry.entry_hash = "tampered"
180
+
181
+
182
+ def test_worm_export_import_jsonl(tmp_path):
183
+ chain = WORMSealChain()
184
+ for i in range(3):
185
+ rec = ObservationRecord.observe(f"A{i}", f"B{i}", make_omega(i))
186
+ chain.seal(rec)
187
+ p = tmp_path / "chain.jsonl"
188
+ chain.export_jsonl(p)
189
+ rows = WORMSealChain.import_jsonl(p)
190
+ assert len(rows) == 3
191
+ assert rows[0]["index"] == 0
192
+ assert rows[2]["index"] == 2
193
+
194
+
195
+ # ── observe_and_seal ──────────────────────────────────────────────────────────
196
+
197
+ def test_observe_and_seal_full_pipeline():
198
+ chain = WORMSealChain()
199
+ omega = make_omega(tau=5)
200
+ node = observe_and_seal("my_input", "my_output", omega, chain)
201
+ assert node.is_sealed
202
+ assert not node.is_understood
203
+ assert node.observation.classification == UnknownClassification.CLASSIFIED
204
+ assert len(chain) == 1
205
+
206
+
207
+ def test_observe_and_seal_chain_verifies():
208
+ chain = WORMSealChain()
209
+ omega = make_omega()
210
+ observe_and_seal("A", "B", omega, chain)
211
+ observe_and_seal("B", "C", omega, chain)
212
+ ok, errors = chain.verify_chain()
213
+ assert ok
214
+
215
+
216
+ def test_observe_and_seal_repr():
217
+ chain = WORMSealChain()
218
+ node = observe_and_seal("x", "y", make_omega(), chain)
219
+ r = repr(node)
220
+ assert "?" in r
221
+ assert "CLASSIFIED" in r
222
+ assert "SEALED" in r
223
+
224
+
225
+ # ── Algebra invariants ────────────────────────────────────────────────────────
226
+
227
+ def test_unknown_not_equal_to_understood():
228
+ """
229
+ The fundamental ADR invariant:
230
+ UNKNOWN β‰  FALSE
231
+ UNKNOWN β‰  TRUE
232
+ """
233
+ rec = ObservationRecord.observe("A", "B", make_omega())
234
+ node = UnknownNode(node_id="abc", observation=rec)
235
+ assert node.is_understood is False
236
+ # Trying to assert it's True or False is a bug β€” the node never claims either
237
+ assert isinstance(node.is_understood, bool)
238
+ assert node.is_understood is not True
239
+
240
+
241
+ def test_classification_terminal_at_classified():
242
+ """Once CLASSIFIED, no further advancement is defined."""
243
+ rec = ObservationRecord.observe("A", "B", make_omega())
244
+ for step in [
245
+ UnknownClassification.CAPTURED,
246
+ UnknownClassification.HASHED,
247
+ UnknownClassification.COMPARED,
248
+ UnknownClassification.TESTED,
249
+ UnknownClassification.CLASSIFIED,
250
+ ]:
251
+ rec = rec.advance(step)
252
+
253
+ # No valid next step exists
254
+ with pytest.raises(UnknownOperatorError):
255
+ rec.advance(UnknownClassification.OBSERVED)