October 2026 main drop: mirror from GitHub
Browse files- .gitignore +6 -0
- LICENSE +374 -0
- README.md +17 -0
- cli/main.py +252 -0
- examples/matrix_multiply.txt +1 -0
- examples/parse_binary_format.txt +1 -0
- examples/validate_packets.txt +1 -0
- lexicon/latin_vocabulary.json +33 -0
- mappings/rosetta_table.json +29 -0
- pyproject.toml +24 -0
- src/semantic_engine/__init__.py +10 -0
- src/semantic_engine/agent_harness.py +102 -0
- src/semantic_engine/artifacts.py +237 -0
- src/semantic_engine/boolean_engine.py +261 -0
- src/semantic_engine/fsm.py +128 -0
- src/semantic_engine/geometry.py +114 -0
- src/semantic_engine/ir.py +255 -0
- src/semantic_engine/latin.py +151 -0
- src/semantic_engine/lexer.py +144 -0
- src/semantic_engine/matrix.py +172 -0
- src/semantic_engine/nlp.py +177 -0
- src/semantic_engine/pipeline.py +243 -0
- src/semantic_engine/rosetta.py +97 -0
- src/semantic_engine/subleq.py +289 -0
- src/semantic_engine/temporal.py +86 -0
- src/semantic_engine/token_engine.py +270 -0
- src/semantic_engine/types.py +199 -0
- src/semantic_engine/unknown.py +329 -0
- src/semantic_engine/verifier.py +211 -0
- templates/backend.go.tmpl +33 -0
- templates/document.mustache +25 -0
- templates/prompt.mustache +30 -0
- tests/conftest.py +8 -0
- tests/test_boolean.py +108 -0
- tests/test_fsm.py +69 -0
- tests/test_ir.py +106 -0
- tests/test_roundtrip.py +140 -0
- tests/test_subleq.py +137 -0
- tests/test_tokens.py +107 -0
- tests/test_types.py +70 -0
- tests/test_unknown.py +255 -0
.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)
|