imlx-gate 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- imlx_gate-0.1.0/LICENSE +21 -0
- imlx_gate-0.1.0/PKG-INFO +71 -0
- imlx_gate-0.1.0/README.md +52 -0
- imlx_gate-0.1.0/pyproject.toml +30 -0
- imlx_gate-0.1.0/setup.cfg +4 -0
- imlx_gate-0.1.0/src/imlx/__init__.py +30 -0
- imlx_gate-0.1.0/src/imlx/charset.py +165 -0
- imlx_gate-0.1.0/src/imlx/cli.py +109 -0
- imlx_gate-0.1.0/src/imlx/declarations.py +167 -0
- imlx_gate-0.1.0/src/imlx/executor.py +213 -0
- imlx_gate-0.1.0/src/imlx/gate.py +101 -0
- imlx_gate-0.1.0/src/imlx/layer1.py +563 -0
- imlx_gate-0.1.0/src/imlx/layer2.py +434 -0
- imlx_gate-0.1.0/src/imlx/trace.py +94 -0
- imlx_gate-0.1.0/src/imlx_gate.egg-info/PKG-INFO +71 -0
- imlx_gate-0.1.0/src/imlx_gate.egg-info/SOURCES.txt +18 -0
- imlx_gate-0.1.0/src/imlx_gate.egg-info/dependency_links.txt +1 -0
- imlx_gate-0.1.0/src/imlx_gate.egg-info/entry_points.txt +2 -0
- imlx_gate-0.1.0/src/imlx_gate.egg-info/top_level.txt +1 -0
- imlx_gate-0.1.0/tests/test_conformance.py +181 -0
imlx_gate-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Passert Professional Education Corporation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
imlx_gate-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: imlx-gate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: IMLX: Invariant Markup Language (eXtended). Deterministic input contracts: a binary conformance gate, a total 21-opcode executor, and byte-identical execution traces.
|
|
5
|
+
Author: Passert
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/passert-ai/imlx
|
|
8
|
+
Keywords: determinism,markup,validation,gate,llm,pipeline
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# imlx
|
|
21
|
+
|
|
22
|
+
Reference implementation of **IMLX: Invariant Markup Language (eXtended)** —
|
|
23
|
+
a plain-text format so constrained that a small deterministic program can
|
|
24
|
+
verify conformance completely and answer with a single bit.
|
|
25
|
+
|
|
26
|
+
The model produces; the gate decides.
|
|
27
|
+
|
|
28
|
+
- **Binary gate** — PASS or FAIL, per layer. No warnings, no partial
|
|
29
|
+
acceptance, no quirks mode.
|
|
30
|
+
- **Closed alphabet** — anything not enumerated is forbidden by
|
|
31
|
+
construction. Validation is membership testing.
|
|
32
|
+
- **21 opcodes, zero judgment** — every opcode is a computable function or
|
|
33
|
+
a decidable predicate. Semantic production happens inside a SLOT, outside
|
|
34
|
+
the language boundary, and the GATE decides what comes back.
|
|
35
|
+
- **Total execution** — every program provably halts before it runs.
|
|
36
|
+
- **Byte-identical traces** — a run's complete event log is itself a gated
|
|
37
|
+
IMLX document, byte-identical across runs and implementations in
|
|
38
|
+
skeleton mode. Proofs you can diff, animate, and audit.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
pip install imlx-gate
|
|
43
|
+
|
|
44
|
+
Zero runtime dependencies. The distribution is `imlx-gate`; the import
|
|
45
|
+
and the command are both `imlx`:
|
|
46
|
+
|
|
47
|
+
import imlx
|
|
48
|
+
|
|
49
|
+
## Use
|
|
50
|
+
|
|
51
|
+
imlx gate artifact.imlx --decls decls.imlx # verdict; exit code is the bit
|
|
52
|
+
imlx run artifact.imlx --trace out.trace.imlx # gate, execute, emit the trace
|
|
53
|
+
imlx run artifact.imlx --trace-json out.json # 1:1 JSON projection
|
|
54
|
+
|
|
55
|
+
Python API:
|
|
56
|
+
|
|
57
|
+
from imlx import gate_file, run_file
|
|
58
|
+
result = gate_file("artifact.imlx", "decls.imlx")
|
|
59
|
+
result.verdict # "PASS" | "FAIL" | "L1-PASS"
|
|
60
|
+
_, run_result = run_file("artifact.imlx", "decls.imlx")
|
|
61
|
+
run_result.trace.render_imlx()
|
|
62
|
+
|
|
63
|
+
## Conformance
|
|
64
|
+
|
|
65
|
+
The spec is normative; no implementation is. `tests/conformance` and
|
|
66
|
+
`tests/adversarial` carry the fixture corpus: required verdicts, required
|
|
67
|
+
reason codes, and pinned byte-exact traces. Matching the corpus is what
|
|
68
|
+
conformance means. Divergence is a bug in at least one implementation —
|
|
69
|
+
falsify this.
|
|
70
|
+
|
|
71
|
+
Spec: SPEC.md v0.1 — github.com/passert-ai/imlx. License: MIT.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# imlx
|
|
2
|
+
|
|
3
|
+
Reference implementation of **IMLX: Invariant Markup Language (eXtended)** —
|
|
4
|
+
a plain-text format so constrained that a small deterministic program can
|
|
5
|
+
verify conformance completely and answer with a single bit.
|
|
6
|
+
|
|
7
|
+
The model produces; the gate decides.
|
|
8
|
+
|
|
9
|
+
- **Binary gate** — PASS or FAIL, per layer. No warnings, no partial
|
|
10
|
+
acceptance, no quirks mode.
|
|
11
|
+
- **Closed alphabet** — anything not enumerated is forbidden by
|
|
12
|
+
construction. Validation is membership testing.
|
|
13
|
+
- **21 opcodes, zero judgment** — every opcode is a computable function or
|
|
14
|
+
a decidable predicate. Semantic production happens inside a SLOT, outside
|
|
15
|
+
the language boundary, and the GATE decides what comes back.
|
|
16
|
+
- **Total execution** — every program provably halts before it runs.
|
|
17
|
+
- **Byte-identical traces** — a run's complete event log is itself a gated
|
|
18
|
+
IMLX document, byte-identical across runs and implementations in
|
|
19
|
+
skeleton mode. Proofs you can diff, animate, and audit.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
pip install imlx-gate
|
|
24
|
+
|
|
25
|
+
Zero runtime dependencies. The distribution is `imlx-gate`; the import
|
|
26
|
+
and the command are both `imlx`:
|
|
27
|
+
|
|
28
|
+
import imlx
|
|
29
|
+
|
|
30
|
+
## Use
|
|
31
|
+
|
|
32
|
+
imlx gate artifact.imlx --decls decls.imlx # verdict; exit code is the bit
|
|
33
|
+
imlx run artifact.imlx --trace out.trace.imlx # gate, execute, emit the trace
|
|
34
|
+
imlx run artifact.imlx --trace-json out.json # 1:1 JSON projection
|
|
35
|
+
|
|
36
|
+
Python API:
|
|
37
|
+
|
|
38
|
+
from imlx import gate_file, run_file
|
|
39
|
+
result = gate_file("artifact.imlx", "decls.imlx")
|
|
40
|
+
result.verdict # "PASS" | "FAIL" | "L1-PASS"
|
|
41
|
+
_, run_result = run_file("artifact.imlx", "decls.imlx")
|
|
42
|
+
run_result.trace.render_imlx()
|
|
43
|
+
|
|
44
|
+
## Conformance
|
|
45
|
+
|
|
46
|
+
The spec is normative; no implementation is. `tests/conformance` and
|
|
47
|
+
`tests/adversarial` carry the fixture corpus: required verdicts, required
|
|
48
|
+
reason codes, and pinned byte-exact traces. Matching the corpus is what
|
|
49
|
+
conformance means. Divergence is a bug in at least one implementation —
|
|
50
|
+
falsify this.
|
|
51
|
+
|
|
52
|
+
Spec: SPEC.md v0.1 — github.com/passert-ai/imlx. License: MIT.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "imlx-gate"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "IMLX: Invariant Markup Language (eXtended). Deterministic input contracts: a binary conformance gate, a total 21-opcode executor, and byte-identical execution traces."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Passert" }]
|
|
13
|
+
keywords = ["determinism", "markup", "validation", "gate", "llm", "pipeline"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Text Processing :: Markup",
|
|
20
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
imlx = "imlx.cli:main"
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Repository = "https://github.com/passert-ai/imlx"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["src"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
imlx
|
|
3
|
+
====
|
|
4
|
+
Reference implementation of IMLX: Invariant Markup Language (eXtended).
|
|
5
|
+
|
|
6
|
+
Binary determinism: a deterministic pass/fail gate at input, a
|
|
7
|
+
deterministic, auditable pass/fail at output. The model produces; the
|
|
8
|
+
gate decides.
|
|
9
|
+
|
|
10
|
+
Spec: SPEC.md v0.1.0-rc.1 (github.com/passert-ai/imlx). Foundation:
|
|
11
|
+
the recovered iml-cli v1.0.0 validator core's result-object and CLI
|
|
12
|
+
patterns, rebuilt spec-facing against SPEC v0.1. Zero runtime
|
|
13
|
+
dependencies.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
from .gate import GateResult, gate_bytes, gate_file, run_file
|
|
19
|
+
from .layer1 import Verdict, Reason, gate_layer1_bytes
|
|
20
|
+
from .layer2 import gate_layer2, OPCODES
|
|
21
|
+
from .declarations import Registries, parse_declarations
|
|
22
|
+
from .executor import RunResult, FailureRecord, run
|
|
23
|
+
from .trace import Trace, TraceEvent
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"GateResult", "gate_bytes", "gate_file", "run_file",
|
|
27
|
+
"Verdict", "Reason", "gate_layer1_bytes", "gate_layer2",
|
|
28
|
+
"Registries", "parse_declarations", "RunResult", "FailureRecord",
|
|
29
|
+
"run", "Trace", "TraceEvent", "OPCODES", "__version__",
|
|
30
|
+
]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
imlx.charset
|
|
3
|
+
============
|
|
4
|
+
The closed alphabet (SPEC Section 6) and Layer 1 character law.
|
|
5
|
+
|
|
6
|
+
Binary determinism: every check is a membership test over an enumerated set.
|
|
7
|
+
Absence from the allowlist is what forbids (Law 1); the forbidden list in
|
|
8
|
+
SPEC 6.3 exists only so this module can emit useful reason codes.
|
|
9
|
+
|
|
10
|
+
Spec basis (SPEC.md v0.1.0-rc.1):
|
|
11
|
+
- 5.1 encoding and line discipline
|
|
12
|
+
- 6.1 allowed characters (content space)
|
|
13
|
+
- 6.2 allowed constructs
|
|
14
|
+
- 6.4 straight quote codification
|
|
15
|
+
- 9 the @ reference namespace (program/declaration space only)
|
|
16
|
+
|
|
17
|
+
IMPLEMENTATION NOTES (ratified in SPEC v0.1.0-rc.2):
|
|
18
|
+
- PROGRAM_SPACE_EXTRA: characters permitted inside program-space and
|
|
19
|
+
declaration-space pipe-table cells beyond the content alphabet. `@` and
|
|
20
|
+
`=` are attested by SPEC Appendix B/D examples; `< > +` are included to
|
|
21
|
+
make SELECT predicates and COMPUTE operators expressible per 11.2
|
|
22
|
+
("closed operator set"). Interpretation, not verbatim spec text.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__version__ = "0.1.0"
|
|
26
|
+
|
|
27
|
+
import re
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Character sets (SPEC 6.1, 6.4)
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
_LETTERS = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
|
34
|
+
_DIGITS = set("0123456789")
|
|
35
|
+
|
|
36
|
+
# Punctuation allowed in content space, subject to positional rules
|
|
37
|
+
# (SPEC 6.1). Straight double quote and apostrophe per 6.4.
|
|
38
|
+
_CONTENT_PUNCT = set(". , : ; - ' \" ( ) [ ] | * # $ %".split()) | {"/", "?"}
|
|
39
|
+
|
|
40
|
+
#: Every character legal anywhere in content space (positional rules are
|
|
41
|
+
#: enforced in layer1, not here). LF is handled at the line level.
|
|
42
|
+
CONTENT_CHARS = _LETTERS | _DIGITS | {" "} | _CONTENT_PUNCT
|
|
43
|
+
|
|
44
|
+
#: Characters that MUST appear only inside $-fences in content space
|
|
45
|
+
#: (SPEC 6.2 math fencing). `%` bare is legal only within %%PAGEBREAK%%.
|
|
46
|
+
MATH_ONLY_CHARS = set("<>=+")
|
|
47
|
+
|
|
48
|
+
#: Extra characters legal inside program-space / declaration-space /
|
|
49
|
+
#: trace-space pipe-table cells (see module docstring).
|
|
50
|
+
PROGRAM_SPACE_EXTRA = {"@", "=", "<", ">", "+", "_"}
|
|
51
|
+
|
|
52
|
+
#: Full alphabet for program/declaration/trace table cells.
|
|
53
|
+
PROGRAM_CELL_CHARS = CONTENT_CHARS | PROGRAM_SPACE_EXTRA
|
|
54
|
+
|
|
55
|
+
#: Characters legal on an envelope opening line, beyond content letters and
|
|
56
|
+
#: digits (SPEC 7.1): ::: { } = " - and space are all consumed by the exact
|
|
57
|
+
#: line regex in layer1; no free-form check applies to envelope lines.
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Exact tokens and line-form regexes
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
PAGEBREAK_TOKEN = "%%PAGEBREAK%%"
|
|
64
|
+
|
|
65
|
+
#: SPEC 5.2 header line 1.
|
|
66
|
+
HEADER_IMLX_RE = re.compile(r"^IMLX: (\d+\.\d+)$")
|
|
67
|
+
|
|
68
|
+
#: SPEC 5.2 header line 2: external pairing or the literal INLINE.
|
|
69
|
+
HEADER_DECL_RE = re.compile(
|
|
70
|
+
r"^DECLARATIONS: (?:(INLINE)|([A-Za-z0-9._-]+\.imlx); (\d+\.\d+))$"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
#: SPEC 10.3 declaration-file third header line.
|
|
74
|
+
HEADER_DECLVER_RE = re.compile(r"^DECL_VERSION: (\d+\.\d+)$")
|
|
75
|
+
|
|
76
|
+
#: SPEC 7.1 envelope opening line, exact form.
|
|
77
|
+
ENVELOPE_OPEN_RE = re.compile(r'^::: \{custom-style="([A-Za-z][A-Za-z0-9_]*)"\}$')
|
|
78
|
+
|
|
79
|
+
#: SPEC 7.1 envelope closing line.
|
|
80
|
+
ENVELOPE_CLOSE = ":::"
|
|
81
|
+
|
|
82
|
+
#: Headings: #, ##, ### + one space (SPEC 6.2). Deeper levels are illegal.
|
|
83
|
+
HEADING_RE = re.compile(r"^(#{1,3}) (\S.*)$")
|
|
84
|
+
HEADING_TOO_DEEP_RE = re.compile(r"^#{4,}")
|
|
85
|
+
|
|
86
|
+
#: Bullets: `* ` and one indented sublevel `* * ` (SPEC 6.2).
|
|
87
|
+
BULLET_RE = re.compile(r"^\* (?:\* )?(\S.*)$")
|
|
88
|
+
|
|
89
|
+
#: Legal-numbered list line (SPEC 6.2): 1.1, 1.2 ... at line start.
|
|
90
|
+
LEGAL_LIST_RE = re.compile(r"^\d+\.\d+ \S")
|
|
91
|
+
|
|
92
|
+
#: Step numbering (SPEC 6.2): Step 1:, Step 2:, ... at line start.
|
|
93
|
+
STEP_LINE_RE = re.compile(r"^Step \d+: \S")
|
|
94
|
+
|
|
95
|
+
#: Pipe-table row: starts and ends with |, at least two pipes.
|
|
96
|
+
TABLE_ROW_RE = re.compile(r"^\|.*\|$")
|
|
97
|
+
|
|
98
|
+
#: Pipe-table separator row (SPEC 6.2): | :--- | :--- |
|
|
99
|
+
TABLE_SEP_CELL = ":---"
|
|
100
|
+
|
|
101
|
+
#: Reference name (SPEC 9): letters, digits, _, beginning with a letter.
|
|
102
|
+
REFERENCE_RE = re.compile(r"^@([A-Za-z][A-Za-z0-9_]*)$")
|
|
103
|
+
REFERENCE_SCAN_RE = re.compile(r"@([A-Za-z][A-Za-z0-9_]*)")
|
|
104
|
+
|
|
105
|
+
#: Program-table header cells, exact (SPEC 11.1).
|
|
106
|
+
PROGRAM_HEADER = ["step", "opcode", "operand", "bind", "style"]
|
|
107
|
+
|
|
108
|
+
#: Trace-table header cells, exact (SPEC 13.6).
|
|
109
|
+
TRACE_HEADER = ["seq", "event", "step", "opcode", "subject", "outcome", "digest"]
|
|
110
|
+
|
|
111
|
+
#: DECLARE section heading (SPEC 10.4): `# DECLARE <kind>`.
|
|
112
|
+
DECLARE_HEADING_RE = re.compile(r"^# DECLARE ([A-Z]+)$")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# Character-law checks
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
def illegal_content_chars(text: str) -> list[str]:
|
|
120
|
+
"""Characters in ``text`` outside the content-space alphabet, fence-aware.
|
|
121
|
+
|
|
122
|
+
Outside $-fences the alphabet is CONTENT_CHARS; inside a fence the
|
|
123
|
+
mathematical symbols (SPEC 6.2) are additionally legal. Positional
|
|
124
|
+
rules remain layer1's job; this is membership.
|
|
125
|
+
"""
|
|
126
|
+
outside, inside, _balanced = split_math_fences(text)
|
|
127
|
+
bad = {c for c in outside if c not in CONTENT_CHARS}
|
|
128
|
+
bad |= {c for c in inside if c not in CONTENT_CHARS | MATH_ONLY_CHARS}
|
|
129
|
+
return sorted(bad)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def illegal_program_cell_chars(text: str) -> list[str]:
|
|
133
|
+
"""Characters in a program/declaration/trace cell outside its alphabet."""
|
|
134
|
+
return sorted({c for c in text if c not in PROGRAM_CELL_CHARS})
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def split_math_fences(line: str):
|
|
138
|
+
"""Split a line into (outside, inside) character streams by $-fences.
|
|
139
|
+
|
|
140
|
+
Returns (outside_text, inside_text, balanced) where ``balanced`` is
|
|
141
|
+
False if the line ends inside an unclosed fence (fences MUST close
|
|
142
|
+
within the line; multi-line blocks are hard-broken lines, SPEC 6.2).
|
|
143
|
+
"""
|
|
144
|
+
outside: list[str] = []
|
|
145
|
+
inside: list[str] = []
|
|
146
|
+
in_fence = False
|
|
147
|
+
for ch in line:
|
|
148
|
+
if ch == "$":
|
|
149
|
+
in_fence = not in_fence
|
|
150
|
+
continue
|
|
151
|
+
(inside if in_fence else outside).append(ch)
|
|
152
|
+
return "".join(outside), "".join(inside), not in_fence
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def bare_math_chars_outside_fences(line: str) -> list[str]:
|
|
156
|
+
"""Math-only characters appearing outside $-fences on a content line.
|
|
157
|
+
|
|
158
|
+
``%`` is excluded when the line is exactly %%PAGEBREAK%% (caller
|
|
159
|
+
handles that line form before reaching here).
|
|
160
|
+
"""
|
|
161
|
+
outside, _inside, _balanced = split_math_fences(line)
|
|
162
|
+
hits = [c for c in outside if c in MATH_ONLY_CHARS]
|
|
163
|
+
if "%" in outside:
|
|
164
|
+
hits.append("%")
|
|
165
|
+
return sorted(set(hits))
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
imlx.cli
|
|
3
|
+
========
|
|
4
|
+
Command-line interface. Zero dependencies (argparse), mirroring the
|
|
5
|
+
recovered iml-cli's verdict-first ergonomics.
|
|
6
|
+
|
|
7
|
+
imlx gate ARTIFACT [--decls FILE] [--json]
|
|
8
|
+
imlx run ARTIFACT [--decls FILE] [--trace OUT] [--trace-json OUT] [--json]
|
|
9
|
+
imlx version
|
|
10
|
+
|
|
11
|
+
Exit code IS the verdict: 0 = PASS, 1 = FAIL, 2 = usage error.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from . import __version__ as pkg_version
|
|
22
|
+
from .gate import gate_file, run_file
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _print_gate(result, as_json: bool) -> None:
|
|
26
|
+
if as_json:
|
|
27
|
+
print(json.dumps(result.to_dict(), indent=2))
|
|
28
|
+
return
|
|
29
|
+
print(f"Layer 1: {result.layer1.bit}")
|
|
30
|
+
for r in result.layer1.reasons:
|
|
31
|
+
print(f" {r.code} line {r.line}: {r.message}")
|
|
32
|
+
if result.layer2 is None:
|
|
33
|
+
if not result.layer1.passed:
|
|
34
|
+
print("Layer 2: not attempted (Layer 1 failed; nothing runs, SPEC 13.5)")
|
|
35
|
+
else:
|
|
36
|
+
print("Layer 2: not attempted (no declaration source; artifact remains "
|
|
37
|
+
"fully Layer 1-validatable alone, SPEC 5.2)")
|
|
38
|
+
else:
|
|
39
|
+
print(f"Layer 2: {result.layer2.bit}")
|
|
40
|
+
for r in result.layer2.reasons:
|
|
41
|
+
print(f" {r.code} line {r.line}: {r.message}")
|
|
42
|
+
print(f"VERDICT: {result.verdict}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main(argv=None) -> int:
|
|
46
|
+
parser = argparse.ArgumentParser(prog="imlx",
|
|
47
|
+
description="IMLX gate and executor. One bit.")
|
|
48
|
+
sub = parser.add_subparsers(dest="cmd")
|
|
49
|
+
|
|
50
|
+
p_gate = sub.add_parser("gate", help="render the conformance verdict")
|
|
51
|
+
p_gate.add_argument("artifact")
|
|
52
|
+
p_gate.add_argument("--decls", help="external declaration file")
|
|
53
|
+
p_gate.add_argument("--json", action="store_true")
|
|
54
|
+
|
|
55
|
+
p_run = sub.add_parser("run", help="gate, then execute (skeleton mode)")
|
|
56
|
+
p_run.add_argument("artifact")
|
|
57
|
+
p_run.add_argument("--decls")
|
|
58
|
+
p_run.add_argument("--trace", help="write canonical IMLX trace to this path")
|
|
59
|
+
p_run.add_argument("--trace-json", help="write JSON trace projection to this path")
|
|
60
|
+
p_run.add_argument("--json", action="store_true")
|
|
61
|
+
|
|
62
|
+
sub.add_parser("version", help="print version")
|
|
63
|
+
|
|
64
|
+
args = parser.parse_args(argv)
|
|
65
|
+
if args.cmd == "version":
|
|
66
|
+
print(f"imlx {pkg_version} (SPEC v0.1)")
|
|
67
|
+
return 0
|
|
68
|
+
if args.cmd is None:
|
|
69
|
+
parser.print_help()
|
|
70
|
+
return 2
|
|
71
|
+
|
|
72
|
+
if not Path(args.artifact).is_file():
|
|
73
|
+
print(f"error: no such file: {args.artifact}", file=sys.stderr)
|
|
74
|
+
return 2
|
|
75
|
+
|
|
76
|
+
if args.cmd == "gate":
|
|
77
|
+
result = gate_file(args.artifact, args.decls)
|
|
78
|
+
_print_gate(result, args.json)
|
|
79
|
+
return 0 if result.verdict in ("PASS", "L1-PASS") else 1
|
|
80
|
+
|
|
81
|
+
if args.cmd == "run":
|
|
82
|
+
result, run_result = run_file(args.artifact, args.decls)
|
|
83
|
+
if run_result is None:
|
|
84
|
+
_print_gate(result, args.json)
|
|
85
|
+
return 1
|
|
86
|
+
if args.trace:
|
|
87
|
+
Path(args.trace).write_text(run_result.trace.render_imlx(), encoding="utf-8")
|
|
88
|
+
if args.trace_json:
|
|
89
|
+
Path(args.trace_json).write_text(run_result.trace.render_json(), encoding="utf-8")
|
|
90
|
+
if args.json:
|
|
91
|
+
out = {"verdict": run_result.verdict,
|
|
92
|
+
"events": [e.to_dict() for e in run_result.trace.events]}
|
|
93
|
+
if run_result.failure:
|
|
94
|
+
out["failure"] = run_result.failure.to_dict()
|
|
95
|
+
print(json.dumps(out, indent=2))
|
|
96
|
+
else:
|
|
97
|
+
print(run_result.trace.render_imlx(), end="")
|
|
98
|
+
if run_result.failure:
|
|
99
|
+
f = run_result.failure
|
|
100
|
+
print(f"FAILURE RECORD: step {f.step} {f.opcode} {f.reason_code} "
|
|
101
|
+
f"[{f.contract}]")
|
|
102
|
+
print(f"VERDICT: {run_result.verdict}")
|
|
103
|
+
return 0 if run_result.verdict == "PASS" else 1
|
|
104
|
+
|
|
105
|
+
return 2
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
sys.exit(main())
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
imlx.declarations
|
|
3
|
+
=================
|
|
4
|
+
The declaration resolver (SPEC Section 10). Populates the registries Layer 2
|
|
5
|
+
validates against: block types (with capabilities), procedures, converters,
|
|
6
|
+
registers, symbols, policy data.
|
|
7
|
+
|
|
8
|
+
- Exactly one declaration source per artifact, named by the header pairing
|
|
9
|
+
line (10.2). External is canonical; INLINE is permitted.
|
|
10
|
+
- A declaration file is itself an IMLX artifact (10.3): same parser, no
|
|
11
|
+
second format. It MUST pass Layer 1.
|
|
12
|
+
- Duplicates fail the gate: L2-DUP01. No precedence, no overlay, no merge.
|
|
13
|
+
|
|
14
|
+
L2-DEC01 ("malformed declaration table": unknown DECLARE kind or wrong
|
|
15
|
+
column set) is ratified in SPEC Appendix C as of v0.1.0-rc.2 and ships
|
|
16
|
+
here with its fixtures.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
|
|
23
|
+
from . import charset as cs
|
|
24
|
+
from .layer1 import Document, Reason, Table, Verdict, gate_layer1_bytes
|
|
25
|
+
|
|
26
|
+
#: SPEC 10.4: registry kinds and their REQUIRED columns.
|
|
27
|
+
REQUIRED_COLUMNS: dict[str, list[str]] = {
|
|
28
|
+
"TYPE": ["name", "capabilities"],
|
|
29
|
+
"PROCEDURE": ["name", "arity", "operand_schema"],
|
|
30
|
+
"CONVERTER": ["name", "from_type", "to_type"],
|
|
31
|
+
"REGISTER": ["name", "type"],
|
|
32
|
+
"SYMBOL": ["sigil_name", "target"],
|
|
33
|
+
"POLICY": ["name", "kind", "payload_ref"],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#: Capabilities defined by SPEC 8. The only sanctioned relaxation mechanism.
|
|
37
|
+
KNOWN_CAPABILITIES = {"legal_lists", "-"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class Registries:
|
|
42
|
+
"""The declaration space in force for one artifact."""
|
|
43
|
+
types: dict[str, dict] = field(default_factory=dict) # name -> {capabilities}
|
|
44
|
+
procedures: dict[str, dict] = field(default_factory=dict) # name -> {arity, operand_schema}
|
|
45
|
+
converters: dict[str, dict] = field(default_factory=dict) # name -> {from_type, to_type}
|
|
46
|
+
registers: dict[str, dict] = field(default_factory=dict) # name -> {type}
|
|
47
|
+
symbols: dict[str, str] = field(default_factory=dict) # sigil_name -> target
|
|
48
|
+
policies: dict[str, dict] = field(default_factory=dict) # name -> {kind, payload_ref}
|
|
49
|
+
source_name: str = ""
|
|
50
|
+
source_version: str = ""
|
|
51
|
+
|
|
52
|
+
_KIND_MAP = {
|
|
53
|
+
"TYPE": ("types", "name"),
|
|
54
|
+
"PROCEDURE": ("procedures", "name"),
|
|
55
|
+
"CONVERTER": ("converters", "name"),
|
|
56
|
+
"REGISTER": ("registers", "name"),
|
|
57
|
+
"SYMBOL": ("symbols", "sigil_name"),
|
|
58
|
+
"POLICY": ("policies", "name"),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def type_capabilities(self, type_name: str) -> set[str]:
|
|
62
|
+
entry = self.types.get(type_name)
|
|
63
|
+
if not entry:
|
|
64
|
+
return set()
|
|
65
|
+
caps = entry.get("capabilities", "-")
|
|
66
|
+
return set() if caps == "-" else {c.strip() for c in caps.split(";")}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def parse_declarations(doc: Document, reasons: list[Reason]) -> Registries:
|
|
70
|
+
"""Build registries from a document's # DECLARE sections (SPEC 10.4).
|
|
71
|
+
|
|
72
|
+
Works identically for a declaration file and for an INLINE artifact's
|
|
73
|
+
own declaration section: one parser, no second format (SPEC 10.3).
|
|
74
|
+
"""
|
|
75
|
+
regs = Registries()
|
|
76
|
+
for tbl in doc.declaration_tables:
|
|
77
|
+
_ingest_table(tbl, regs, reasons)
|
|
78
|
+
return regs
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _ingest_table(tbl: Table, regs: Registries, reasons: list[Reason]) -> None:
|
|
82
|
+
kind = tbl.declare_kind or ""
|
|
83
|
+
required = REQUIRED_COLUMNS.get(kind)
|
|
84
|
+
if required is None:
|
|
85
|
+
reasons.append(Reason("L2-DEC01", tbl.start_line,
|
|
86
|
+
f"unknown DECLARE kind '{kind}' (SPEC 10.4 enumerates "
|
|
87
|
+
f"{sorted(REQUIRED_COLUMNS)})"))
|
|
88
|
+
return
|
|
89
|
+
if tbl.header_cells != required:
|
|
90
|
+
reasons.append(Reason("L2-DEC01", tbl.start_line,
|
|
91
|
+
f"DECLARE {kind} requires columns {required}, "
|
|
92
|
+
f"found {tbl.header_cells} (SPEC 10.4)"))
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
attr, key_col = Registries._KIND_MAP[kind]
|
|
96
|
+
store = getattr(regs, attr)
|
|
97
|
+
key_idx = required.index(key_col)
|
|
98
|
+
|
|
99
|
+
for cells, ln in zip(tbl.rows, tbl.row_lines):
|
|
100
|
+
name = cells[key_idx]
|
|
101
|
+
if name in store:
|
|
102
|
+
reasons.append(Reason("L2-DUP01", ln,
|
|
103
|
+
f"duplicate {kind} declaration '{name}' "
|
|
104
|
+
f"(SPEC 10.2: no precedence, no overlay, no merge)"))
|
|
105
|
+
continue
|
|
106
|
+
if kind == "SYMBOL":
|
|
107
|
+
store[name] = cells[required.index("target")]
|
|
108
|
+
else:
|
|
109
|
+
store[name] = {col: cells[idx] for idx, col in enumerate(required) if idx != key_idx}
|
|
110
|
+
# entry-shape checks decidable without other registries
|
|
111
|
+
if kind == "TYPE":
|
|
112
|
+
caps = store[name]["capabilities"]
|
|
113
|
+
unknown = {c.strip() for c in caps.split(";")} - KNOWN_CAPABILITIES
|
|
114
|
+
if caps != "-" and unknown:
|
|
115
|
+
reasons.append(Reason("L2-DEC01", ln,
|
|
116
|
+
f"unknown capability {sorted(unknown)} "
|
|
117
|
+
f"(SPEC 8 defines: legal_lists)"))
|
|
118
|
+
if kind == "PROCEDURE" and not store[name]["arity"].isdigit():
|
|
119
|
+
reasons.append(Reason("L2-DEC01", ln, "PROCEDURE arity must be a non-negative integer"))
|
|
120
|
+
if kind == "SYMBOL" and not cs.REFERENCE_RE.match("@" + name):
|
|
121
|
+
reasons.append(Reason("L2-DEC01", ln,
|
|
122
|
+
"sigil_name must be letters, digits, _, beginning "
|
|
123
|
+
"with a letter (SPEC 9)"))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def load_external_source(path_bytes: bytes, file_name: str,
|
|
127
|
+
expected_name: str, expected_version: str,
|
|
128
|
+
reasons: list[Reason]) -> Registries | None:
|
|
129
|
+
"""Load and verify an external declaration file (SPEC 5.2, 10.3).
|
|
130
|
+
|
|
131
|
+
The Layer 2 gate MUST verify that the declaration source it was given
|
|
132
|
+
matches the name and version the artifact demands (L2-PAIR01), and the
|
|
133
|
+
file itself MUST pass Layer 1.
|
|
134
|
+
"""
|
|
135
|
+
verdict, decl_doc = gate_layer1_bytes(path_bytes, file_name)
|
|
136
|
+
if not verdict.passed:
|
|
137
|
+
reasons.append(Reason("L2-PAIR01", 0,
|
|
138
|
+
f"declaration file '{file_name}' fails Layer 1 "
|
|
139
|
+
f"({verdict.reasons[0].code} at its line "
|
|
140
|
+
f"{verdict.reasons[0].line}); a declaration file MUST "
|
|
141
|
+
f"pass Layer 1 like any artifact (SPEC 10.3)"))
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
if file_name != expected_name:
|
|
145
|
+
reasons.append(Reason("L2-PAIR01", 2,
|
|
146
|
+
f"artifact header pairs '{expected_name}' but was given "
|
|
147
|
+
f"'{file_name}' (SPEC 5.2)"))
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
if decl_doc.header.decl_mode != "INLINE" or decl_doc.header.file_decl_version is None:
|
|
151
|
+
reasons.append(Reason("L2-PAIR01", 0,
|
|
152
|
+
f"'{file_name}' is not a declaration file: its DECLARATIONS "
|
|
153
|
+
f"line must read INLINE and it must carry DECL_VERSION "
|
|
154
|
+
f"(SPEC 10.3)"))
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
if decl_doc.header.file_decl_version != expected_version:
|
|
158
|
+
reasons.append(Reason("L2-PAIR01", 2,
|
|
159
|
+
f"artifact header demands version {expected_version}; "
|
|
160
|
+
f"'{file_name}' declares DECL_VERSION "
|
|
161
|
+
f"{decl_doc.header.file_decl_version} (SPEC 5.2)"))
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
regs = parse_declarations(decl_doc, reasons)
|
|
165
|
+
regs.source_name = file_name
|
|
166
|
+
regs.source_version = decl_doc.header.file_decl_version
|
|
167
|
+
return regs
|