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.
@@ -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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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