arxo-codegen 0.4.0__py3-none-any.whl

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.
Files changed (152) hide show
  1. arxo_codegen/__init__.py +5 -0
  2. arxo_codegen/__main__.py +7 -0
  3. arxo_codegen/active-outcomes.json +6837 -0
  4. arxo_codegen/check.py +158 -0
  5. arxo_codegen/check_active_canaries.py +262 -0
  6. arxo_codegen/check_api.py +76 -0
  7. arxo_codegen/check_conformance.py +414 -0
  8. arxo_codegen/check_matrix.py +131 -0
  9. arxo_codegen/check_packages.py +281 -0
  10. arxo_codegen/check_semantic_probes.py +293 -0
  11. arxo_codegen/check_xlsx.py +320 -0
  12. arxo_codegen/consumer_runner.py +316 -0
  13. arxo_codegen/core_support.py +203 -0
  14. arxo_codegen/coverage.py +341 -0
  15. arxo_codegen/deliver.py +180 -0
  16. arxo_codegen/examples/xlsx/pricing.lawir.json +409 -0
  17. arxo_codegen/examples/xlsx/pricing.xlsx.json +52 -0
  18. arxo_codegen/excel_plan.py +602 -0
  19. arxo_codegen/harness/verify/ci/gates/backends/check_backends.py +1248 -0
  20. arxo_codegen/harness/verify/ci/gates/backends/l1_fixtures.py +463 -0
  21. arxo_codegen/inert-nodes.json +62 -0
  22. arxo_codegen/inert.py +91 -0
  23. arxo_codegen/inputs.py +59 -0
  24. arxo_codegen/package.py +565 -0
  25. arxo_codegen/plan.py +4053 -0
  26. arxo_codegen/recertify_registry.py +100 -0
  27. arxo_codegen/runtime-cert.json +5259 -0
  28. arxo_codegen/runtime_cert.py +233 -0
  29. arxo_codegen/survey.py +272 -0
  30. arxo_codegen/target_runner.py +101 -0
  31. arxo_codegen/targets/bsl/cert.bsl +113 -0
  32. arxo_codegen/targets/bsl/emit.py +356 -0
  33. arxo_codegen/targets/bsl/json.bsl +243 -0
  34. arxo_codegen/targets/bsl/runner.bsl +210 -0
  35. arxo_codegen/targets/bsl/runtime.bsl +858 -0
  36. arxo_codegen/targets/cs/Cert.cs +90 -0
  37. arxo_codegen/targets/cs/Runner.cs +154 -0
  38. arxo_codegen/targets/cs/Runtime.cs +243 -0
  39. arxo_codegen/targets/cs/emit.py +223 -0
  40. arxo_codegen/targets/dart/cert.dart +99 -0
  41. arxo_codegen/targets/dart/emit.py +206 -0
  42. arxo_codegen/targets/dart/json.dart +166 -0
  43. arxo_codegen/targets/dart/runner.dart +154 -0
  44. arxo_codegen/targets/dart/runtime.dart +293 -0
  45. arxo_codegen/targets/erl/cert.erl +64 -0
  46. arxo_codegen/targets/erl/emit.py +216 -0
  47. arxo_codegen/targets/erl/rt.erl +238 -0
  48. arxo_codegen/targets/erl/runner.erl +130 -0
  49. arxo_codegen/targets/ex/cert.exs +62 -0
  50. arxo_codegen/targets/ex/emit.py +204 -0
  51. arxo_codegen/targets/ex/runner.exs +124 -0
  52. arxo_codegen/targets/ex/runtime.ex +227 -0
  53. arxo_codegen/targets/go/apiBase.go +483 -0
  54. arxo_codegen/targets/go/apicheck.go +97 -0
  55. arxo_codegen/targets/go/cert.go +218 -0
  56. arxo_codegen/targets/go/emit.py +1806 -0
  57. arxo_codegen/targets/go/evaluate.go +1826 -0
  58. arxo_codegen/targets/go/runner.go +38 -0
  59. arxo_codegen/targets/go/runtime.go +2612 -0
  60. arxo_codegen/targets/java/Cert.java +91 -0
  61. arxo_codegen/targets/java/Json.java +148 -0
  62. arxo_codegen/targets/java/Runner.java +151 -0
  63. arxo_codegen/targets/java/Runtime.java +243 -0
  64. arxo_codegen/targets/java/emit.py +198 -0
  65. arxo_codegen/targets/kt/Cert.kt +67 -0
  66. arxo_codegen/targets/kt/Json.kt +103 -0
  67. arxo_codegen/targets/kt/Runner.kt +126 -0
  68. arxo_codegen/targets/kt/Runtime.kt +196 -0
  69. arxo_codegen/targets/kt/emit.py +195 -0
  70. arxo_codegen/targets/lua/cert.lua +82 -0
  71. arxo_codegen/targets/lua/emit.py +250 -0
  72. arxo_codegen/targets/lua/json.lua +133 -0
  73. arxo_codegen/targets/lua/runner.lua +167 -0
  74. arxo_codegen/targets/lua/runtime.lua +541 -0
  75. arxo_codegen/targets/php/cert.php +84 -0
  76. arxo_codegen/targets/php/emit.py +229 -0
  77. arxo_codegen/targets/php/json.php +144 -0
  78. arxo_codegen/targets/php/runner.php +189 -0
  79. arxo_codegen/targets/php/runtime.php +660 -0
  80. arxo_codegen/targets/py/api_base.py +334 -0
  81. arxo_codegen/targets/py/apicheck.py +51 -0
  82. arxo_codegen/targets/py/cert.py +140 -0
  83. arxo_codegen/targets/py/emit.py +1140 -0
  84. arxo_codegen/targets/py/evaluate.py +980 -0
  85. arxo_codegen/targets/py/runner.py +38 -0
  86. arxo_codegen/targets/py/runtime.py +1634 -0
  87. arxo_codegen/targets/rb/cert.rb +73 -0
  88. arxo_codegen/targets/rb/emit.py +203 -0
  89. arxo_codegen/targets/rb/runner.rb +130 -0
  90. arxo_codegen/targets/rb/runtime.rb +255 -0
  91. arxo_codegen/targets/rs/Cargo.lock +142 -0
  92. arxo_codegen/targets/rs/Cargo.toml +16 -0
  93. arxo_codegen/targets/rs/cert.rs +106 -0
  94. arxo_codegen/targets/rs/emit.py +233 -0
  95. arxo_codegen/targets/rs/prefetch.rs +1 -0
  96. arxo_codegen/targets/rs/runner.rs +181 -0
  97. arxo_codegen/targets/rs/runtime.rs +448 -0
  98. arxo_codegen/targets/scala/Cert.scala +77 -0
  99. arxo_codegen/targets/scala/Json.scala +140 -0
  100. arxo_codegen/targets/scala/Runner.scala +121 -0
  101. arxo_codegen/targets/scala/Runtime.scala +269 -0
  102. arxo_codegen/targets/scala/emit.py +193 -0
  103. arxo_codegen/targets/swift/cert.swift +105 -0
  104. arxo_codegen/targets/swift/emit.py +231 -0
  105. arxo_codegen/targets/swift/json.swift +208 -0
  106. arxo_codegen/targets/swift/runner.swift +133 -0
  107. arxo_codegen/targets/swift/runtime.swift +528 -0
  108. arxo_codegen/targets/ts/apiBase.ts +250 -0
  109. arxo_codegen/targets/ts/apicheck.ts +37 -0
  110. arxo_codegen/targets/ts/cert.ts +96 -0
  111. arxo_codegen/targets/ts/emit.py +1214 -0
  112. arxo_codegen/targets/ts/evaluate.ts +894 -0
  113. arxo_codegen/targets/ts/package-lock.json +45 -0
  114. arxo_codegen/targets/ts/package.json +9 -0
  115. arxo_codegen/targets/ts/runner.ts +31 -0
  116. arxo_codegen/targets/ts/runtime.ts +1369 -0
  117. arxo_codegen/targets/xlsx/README.ru.md +176 -0
  118. arxo_codegen/targets/xlsx/emit.py +198 -0
  119. arxo_codegen/temporal.py +59 -0
  120. arxo_codegen/test_alias_types.py +171 -0
  121. arxo_codegen/test_api_contract.py +89 -0
  122. arxo_codegen/test_calendar_policy.py +48 -0
  123. arxo_codegen/test_check.py +49 -0
  124. arxo_codegen/test_check_active_canaries.py +96 -0
  125. arxo_codegen/test_check_conformance.py +132 -0
  126. arxo_codegen/test_check_semantic_probes.py +85 -0
  127. arxo_codegen/test_check_xlsx.py +52 -0
  128. arxo_codegen/test_consumer_runner.py +175 -0
  129. arxo_codegen/test_core_support.py +80 -0
  130. arxo_codegen/test_e0212_decision_time.py +63 -0
  131. arxo_codegen/test_head_aggregate.py +71 -0
  132. arxo_codegen/test_inert.py +143 -0
  133. arxo_codegen/test_instant_types.py +113 -0
  134. arxo_codegen/test_key_conflict_scope.py +92 -0
  135. arxo_codegen/test_key_conflict_targets.py +17 -0
  136. arxo_codegen/test_package_layout.py +163 -0
  137. arxo_codegen/test_plan_language_version.py +44 -0
  138. arxo_codegen/test_positions_refusal.py +43 -0
  139. arxo_codegen/test_recertify_registry.py +77 -0
  140. arxo_codegen/test_ruby_target.py +41 -0
  141. arxo_codegen/test_runtime_cert.py +83 -0
  142. arxo_codegen/test_secondary_key_conflict_scope.py +17 -0
  143. arxo_codegen/test_stage_r.py +108 -0
  144. arxo_codegen/test_standalone_inputs.py +71 -0
  145. arxo_codegen/test_support.py +150 -0
  146. arxo_codegen/test_survey_inputs.py +115 -0
  147. arxo_codegen-0.4.0.dist-info/LICENSE +202 -0
  148. arxo_codegen-0.4.0.dist-info/METADATA +1673 -0
  149. arxo_codegen-0.4.0.dist-info/RECORD +152 -0
  150. arxo_codegen-0.4.0.dist-info/WHEEL +5 -0
  151. arxo_codegen-0.4.0.dist-info/entry_points.txt +13 -0
  152. arxo_codegen-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,175 @@
1
+ """Contracts for the standalone lowered-consumer bundle reader."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ from pathlib import Path
7
+ import sys
8
+ import tempfile
9
+ import unittest
10
+ from unittest import mock
11
+
12
+ HERE = Path(__file__).resolve().parent
13
+ sys.path.insert(0, str(HERE))
14
+ import consumer_runner as runner # noqa: E402
15
+ from test_support import materialize_core_kit, vector # noqa: E402
16
+
17
+
18
+ def entry(root: Path, relative: str) -> dict:
19
+ data = (root / relative).read_bytes()
20
+ return {"path": relative, "sha256": "sha256:" + hashlib.sha256(data).hexdigest()}
21
+
22
+
23
+ class ConsumerRunnerTests(unittest.TestCase):
24
+ def tearDown(self):
25
+ runner.core_support.configure_core_kit(None)
26
+
27
+ def bundle(self, root: Path) -> None:
28
+ for relative, document in {
29
+ "program.json": {"package": {"name": "example"}, "imports": [], "nodes": []},
30
+ "case.json": {"assertions": []},
31
+ "query.json": {"kind": "truth"},
32
+ }.items():
33
+ (root / relative).write_text(json.dumps(document), encoding="utf-8")
34
+ document = {"format": runner.FORMAT, "program": entry(root, "program.json"), "world": [],
35
+ "group": {"world": [], "concat": [], "resolve": [], "merge": "link"},
36
+ "tests": [{"id": "one", "case": entry(root, "case.json"), "query": entry(root, "query.json"),
37
+ "expectations": [{"field": "truth_status", "op": "eq", "value": "TRUE_ONLY"},
38
+ {"observation": "any_applied", "value": "rule"}]}]}
39
+ (root / "bundle.json").write_text(json.dumps(document), encoding="utf-8")
40
+
41
+ def test_loads_explicit_hashed_lowered_inputs(self):
42
+ with tempfile.TemporaryDirectory(prefix="consumer-runner-") as tmp:
43
+ root = Path(tmp)
44
+ self.bundle(root)
45
+ bundle = runner.load_bundle(root)
46
+ self.assertEqual(bundle["program"].name, "program.json")
47
+ self.assertEqual(bundle["tests"][0]["id"], "one")
48
+ self.assertEqual(bundle["source_skipped"], [])
49
+ fields, skipped, positions = runner.expected(bundle["tests"][0])
50
+ self.assertEqual(fields, {"truth_status": "TRUE_ONLY"})
51
+ self.assertEqual(skipped, ["any_applied"])
52
+ self.assertEqual(positions, [])
53
+
54
+ def test_rejects_changed_or_escaping_inputs(self):
55
+ with tempfile.TemporaryDirectory(prefix="consumer-runner-") as tmp:
56
+ root = Path(tmp)
57
+ self.bundle(root)
58
+ (root / "case.json").write_text("{}", encoding="utf-8")
59
+ with self.assertRaisesRegex(ValueError, "SHA-256"):
60
+ runner.load_bundle(root)
61
+ self.bundle(root)
62
+ doc = json.loads((root / "bundle.json").read_text(encoding="utf-8"))
63
+ doc["program"]["path"] = "../program.json"
64
+ (root / "bundle.json").write_text(json.dumps(doc), encoding="utf-8")
65
+ with self.assertRaisesRegex(ValueError, "outside"):
66
+ runner.load_bundle(root)
67
+
68
+ def test_rejects_unknown_or_query_inappropriate_expectations(self):
69
+ with tempfile.TemporaryDirectory(prefix="consumer-runner-") as tmp:
70
+ root = Path(tmp); self.bundle(root)
71
+ doc = json.loads((root / "bundle.json").read_text(encoding="utf-8"))
72
+ doc["tests"][0]["expectations"] = [{"field": "typo", "op": "eq", "value": "x"}]
73
+ (root / "bundle.json").write_text(json.dumps(doc), encoding="utf-8")
74
+ with self.assertRaisesRegex(ValueError, "unknown expectation field"):
75
+ runner.load_bundle(root)
76
+ self.bundle(root)
77
+ doc = json.loads((root / "bundle.json").read_text(encoding="utf-8"))
78
+ doc["tests"][0]["expectations"] = [{"field": "normative_status", "op": "eq", "value": "SATISFIED"}]
79
+ (root / "bundle.json").write_text(json.dumps(doc), encoding="utf-8")
80
+ with self.assertRaisesRegex(ValueError, "inapplicable"):
81
+ runner.load_bundle(root)
82
+
83
+ def test_compares_truth_and_names_unsupported_query_as_skip(self):
84
+ with tempfile.TemporaryDirectory(prefix="consumer-runner-") as tmp:
85
+ root = Path(tmp)
86
+ self.bundle(root)
87
+ bundle = runner.load_bundle(root)
88
+ test = bundle["tests"][0]
89
+ self.assertEqual(runner.compare({"truthStatus": "TRUE_ONLY", "evaluationStatus": "COMPUTED"}, test), [])
90
+ (root / "query.json").write_text(json.dumps({"kind": "why_not"}), encoding="utf-8")
91
+ # Recreate the entry after changing the input, as a lowering adapter would.
92
+ doc = json.loads((root / "bundle.json").read_text(encoding="utf-8"))
93
+ doc["tests"][0]["query"] = entry(root, "query.json")
94
+ doc["tests"][0]["expectations"] = []
95
+ (root / "bundle.json").write_text(json.dumps(doc), encoding="utf-8")
96
+ test = runner.load_bundle(root)["tests"][0]
97
+ self.assertEqual(runner.compare({}, test), ["пропуск: запрос why_not"])
98
+
99
+ def test_executes_real_lowered_bundle_and_keeps_unchecked_proof_named(self):
100
+ """One active 0.2 vector proves the runner needs neither corpus nor CI."""
101
+ active_vector = vector("t/T199")
102
+ with tempfile.TemporaryDirectory(prefix="consumer-runner-real-") as tmp:
103
+ root = Path(tmp) / "bundle"; root.mkdir()
104
+ ir = json.loads((active_vector / "ir.json").read_text(encoding="utf-8"))
105
+ ir["imports"] = [{"package": {"name": "dependency"}}]
106
+ (root / "ir.json").write_text(json.dumps(ir), encoding="utf-8")
107
+ (root / "world.json").write_text(json.dumps({"semanticVersion": "0.2.11", "schemaVersion": "law.core.ir/0.7", "package": {"name": "dependency"}, "imports": [], "nodes": []}), encoding="utf-8")
108
+ for name in ("case.json", "query.json"):
109
+ (root / name).write_bytes((active_vector / name).read_bytes())
110
+ rows = []
111
+ for identifier, expectations in (
112
+ ("truth", [{"field": "truth_status", "op": "eq", "value": "TRUE_ONLY"},
113
+ {"field": "evaluation_status", "op": "eq", "value": "COMPUTED"}]),
114
+ ("no-status", []),
115
+ ("proof-only", [{"observation": "any_applied", "value": "x"}]),
116
+ ):
117
+ rows.append({"id": identifier, "case": entry(root, "case.json"), "query": entry(root, "query.json"),
118
+ "expectations": expectations})
119
+ (root / "bundle.json").write_text(json.dumps({"format": runner.FORMAT,
120
+ "program": entry(root, "ir.json"), "world": [entry(root, "world.json")],
121
+ "group": {"world": ["dependency"], "concat": [], "resolve": ["dependency"], "merge": "link"}, "tests": rows}), encoding="utf-8")
122
+ kit = Path(tmp) / "kit"; materialize_core_kit(kit, source_commit="a" * 40)
123
+ runner.core_support.configure_core_kit(kit)
124
+ summary, failures = runner.run(runner.load_bundle(root), ["py"], Path(tmp) / "work")
125
+ self.assertEqual(failures, [])
126
+ self.assertEqual(summary["matched"], 1)
127
+ self.assertEqual(summary["unchecked"], 2)
128
+ self.assertEqual(summary["skipped"], {"наблюдение any_applied": 1})
129
+ runner.core_support.configure_core_kit(None)
130
+
131
+ def test_truth_result_kind_and_unsupported_observations_are_not_dropped(self):
132
+ with tempfile.TemporaryDirectory() as tmp:
133
+ root = Path(tmp); self.bundle(root)
134
+ test = runner.load_bundle(root)["tests"][0]
135
+ test["expectations"] = [{"field": "result_kind", "op": "eq", "value": "PROPOSITION"}]
136
+ runner.validate_expectations(test["id"], "truth", test["expectations"])
137
+ self.assertEqual(runner.compare({"truthStatus": "TRUE_ONLY"}, test), [])
138
+ self.assertTrue(runner.compare({}, test))
139
+ runner.validate_expectations("collection", "collect", [{"observation": "collected_count", "value": 0}])
140
+
141
+ def test_source_omissions_require_explicit_opt_in(self):
142
+ with tempfile.TemporaryDirectory() as tmp:
143
+ root = Path(tmp); self.bundle(root)
144
+ doc = json.loads((root / "bundle.json").read_text()); doc["skipped"] = ["external snapshots"]
145
+ (root / "bundle.json").write_text(json.dumps(doc))
146
+ kit = root / "kit"; materialize_core_kit(kit, source_commit="b" * 40)
147
+ with mock.patch.object(runner, "run") as run:
148
+ self.assertEqual(runner.main(["--bundle", str(root), "--core-kit", str(kit)]), 1)
149
+ run.assert_not_called()
150
+
151
+ def test_collection_validates_each_group_and_rejects_changed_manifest(self):
152
+ with tempfile.TemporaryDirectory() as tmp:
153
+ root = Path(tmp)
154
+ for name in ("one", "two"):
155
+ (root / name).mkdir(); self.bundle(root / name)
156
+ doc = {"format": runner.SET_FORMAT, "groups": [entry(root, name + "/bundle.json") for name in ("one", "two")]}
157
+ (root / "bundle.json").write_text(json.dumps(doc))
158
+ loaded = runner.load_bundle(root)
159
+ self.assertEqual(len(loaded["groups"]), 2)
160
+ (root / "two/bundle.json").write_text("{}")
161
+ with self.assertRaisesRegex(ValueError, "SHA-256"):
162
+ runner.load_bundle(root)
163
+
164
+ def test_main_fails_when_all_cases_are_unchecked(self):
165
+ with tempfile.TemporaryDirectory(prefix="consumer-runner-main-") as tmp:
166
+ root = Path(tmp); self.bundle(root)
167
+ kit = root / "kit"; materialize_core_kit(kit, source_commit="b" * 40)
168
+ summary = {"tests": 1, "matched": 0, "unchecked": 1, "refused": {}, "skipped": {},
169
+ "sourceSkipped": [], "unavailable": []}
170
+ with mock.patch.object(runner, "run", return_value=(summary, [])):
171
+ self.assertEqual(runner.main(["--bundle", str(root), "--core-kit", str(kit), "--targets", "py"]), 1)
172
+
173
+
174
+ if __name__ == "__main__":
175
+ unittest.main()
@@ -0,0 +1,80 @@
1
+ import os
2
+ import json
3
+ import sys
4
+ import tempfile
5
+ import unittest
6
+ from pathlib import Path
7
+ import hashlib
8
+
9
+ HERE = Path(__file__).resolve().parent
10
+ sys.path.insert(0, str(HERE))
11
+ import core_support
12
+ from test_support import materialize_core_kit, uses_exported_inputs
13
+
14
+
15
+ class CoreSupportTests(unittest.TestCase):
16
+ def tearDown(self):
17
+ core_support.configure_core_kit(None)
18
+ os.environ.pop("LAW_CODEGEN_CORE_KIT", None)
19
+
20
+ def test_explicit_kit_matches_monorepo_semantic_contract(self):
21
+ ir = {"schemaVersion": "law.core/0.2", "package": "urn:test", "nodes": [],
22
+ "metadata": {"local": True}}
23
+ with tempfile.TemporaryDirectory() as name:
24
+ out = Path(name) / "kit"
25
+ manifest = materialize_core_kit(out, source_commit="d" * 40)
26
+ core_support.configure_core_kit(out)
27
+ self.assertFalse(core_support.using_monorepo_fallback())
28
+ self.assertTrue(core_support.semantic_hash(ir).startswith("sha256:"))
29
+ self.assertTrue(core_support.canonical_bytes({"nodes": [{"id": "b"}, {"id": "a"}]}))
30
+ schema = core_support.calendar_schema()
31
+ self.assertEqual(schema["title"], "Arxo calendar dataset 0.1")
32
+ schema["title"] = "changed"
33
+ self.assertEqual(core_support.calendar_schema()["title"], "Arxo calendar dataset 0.1")
34
+ self.assertFalse((out / "python/lawref/__pycache__").exists())
35
+ self.assertEqual(core_support.validate_core_kit(out), manifest)
36
+ if not uses_exported_inputs():
37
+ self.assertEqual(manifest["sourceCommit"], "d" * 40)
38
+
39
+ def test_environment_kit_is_validated_before_import(self):
40
+ with tempfile.TemporaryDirectory() as name:
41
+ out = Path(name) / "kit"
42
+ materialize_core_kit(out, source_commit="e" * 40)
43
+ (out / "python/lawref/hashing.py").write_text("raise RuntimeError('must not import')")
44
+ os.environ["LAW_CODEGEN_CORE_KIT"] = str(out)
45
+ with self.assertRaisesRegex(core_support.CodegenCoreError, "hash mismatch"):
46
+ core_support.semantic_hash({"nodes": []})
47
+
48
+ def test_refuses_symlinked_explicit_kit(self):
49
+ with tempfile.TemporaryDirectory() as name:
50
+ base = Path(name); kit = base / "kit"; materialize_core_kit(kit, source_commit="f" * 40)
51
+ link = base / "linked-kit"; link.symlink_to(kit, target_is_directory=True)
52
+ core_support.configure_core_kit(link)
53
+ with self.assertRaisesRegex(core_support.CoreKitError, "not a directory"):
54
+ core_support.semantic_hash({"nodes": []})
55
+
56
+ def test_refuses_unsupported_compatibility_before_import(self):
57
+ with tempfile.TemporaryDirectory() as name:
58
+ out = Path(name) / "kit"; materialize_core_kit(out, source_commit="1" * 40)
59
+ lock_name = "codegen-core.lock.json"
60
+ lock = json.loads((out / lock_name).read_bytes())
61
+ lock["compatibility"]["semanticVersions"] = ["0.3"]
62
+ (out / lock_name).write_text(json.dumps(lock, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
63
+ manifest = json.loads((out / "manifest.json").read_bytes())
64
+ for row in manifest["files"]:
65
+ if row["path"] == lock_name:
66
+ data = (out / lock_name).read_bytes()
67
+ row.update(sha256="sha256:" + hashlib.sha256(data).hexdigest(), bytes=len(data))
68
+ (out / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
69
+ core_support.configure_core_kit(out)
70
+ with self.assertRaisesRegex(core_support.CoreKitError, "compatibility"):
71
+ core_support.semantic_hash({"nodes": []})
72
+
73
+ def test_missing_kit_is_named_error(self):
74
+ self.assertFalse(core_support.using_monorepo_fallback())
75
+ with self.assertRaisesRegex(core_support.CoreKitError, "explicit LAW_CODEGEN_CORE_KIT"):
76
+ core_support.semantic_hash({"nodes": []})
77
+
78
+
79
+ if __name__ == "__main__":
80
+ unittest.main()
@@ -0,0 +1,63 @@
1
+ """E-0212: position status uses an explicitly supplied decision time."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ import unittest
10
+
11
+
12
+ HERE = Path(__file__).resolve().parent
13
+ sys.path.insert(0, str(HERE))
14
+ import deliver # noqa: E402
15
+ import plan # noqa: E402
16
+ from test_support import vector # noqa: E402
17
+
18
+
19
+ CASES = {
20
+ "ERR-E0212-ACTIVE-AT-DECISION": "ACTIVE",
21
+ "ERR-E0212-PENDING-AT-EARLIER-DECISION": "PENDING",
22
+ "ERR-E0212-SATISFIED-AT-DECISION": "SATISFIED",
23
+ "ERR-E0212-VIOLATED-AT-DECISION": "VIOLATED",
24
+ }
25
+
26
+
27
+ class DecisionTimeStatusTests(unittest.TestCase):
28
+ def test_normative_models_refuse_before_the_old_status_clock(self):
29
+ for ident in CASES:
30
+ ir = json.loads((vector("errata/" + ident) / "ir.json").read_text())
31
+ with self.subTest(ident=ident), self.assertRaisesRegex(plan.Refused, "NORM_RANKS_UNAVAILABLE"):
32
+ plan.build_plan(ir)
33
+
34
+ def test_go_l0_model_keeps_the_shared_runner_status_hook(self):
35
+ """The Go runner is shared with L0, whose model has no positions."""
36
+ fixture = vector("smoke/LQ6-T170-V02")
37
+ ir = json.loads((fixture / "ir.json").read_text())
38
+ with tempfile.TemporaryDirectory(prefix="e0212-go-l0-") as raw:
39
+ project = Path(raw)
40
+ deliver.emit_project(plan.build_plan(ir), "go", project)
41
+ source = (project / deliver.emitter("go").MODEL).read_text(encoding="utf-8")
42
+ # DECISION-0423 §2.3: хук — замыкание фабрики newModel, подключённое к Model.
43
+ self.assertIn("setStatusTime = func(t string) {}", source)
44
+ self.assertIn("setStatusTime: setStatusTime", source)
45
+
46
+ def test_python_runner_refuses_old_position_ladder_until_w6(self):
47
+ with tempfile.TemporaryDirectory(prefix="e0212-python-") as raw:
48
+ root = Path(raw)
49
+ for vector_id, expected in CASES.items():
50
+ fixture = vector("errata/" + vector_id).resolve()
51
+ ir = json.loads((fixture / "ir.json").read_text())
52
+ project = root / vector_id
53
+ deliver.emit_project(plan.build_plan({"semanticVersion":"0.2.11", "schemaVersion":"law.core.ir/0.7", "nodes":[]}), "py", project)
54
+ result = subprocess.run([sys.executable, str(project / "runner.py"),
55
+ str(fixture / "case.json"), str(fixture / "query.json")],
56
+ capture_output=True, text=True, check=False)
57
+ answer = json.loads(result.stdout)
58
+ self.assertEqual(result.returncode, 3)
59
+ self.assertEqual(answer, {"refused": "POSITIONS_UNAVAILABLE: positions requires current norm ranks (W6)"})
60
+
61
+
62
+ if __name__ == "__main__":
63
+ unittest.main()
@@ -0,0 +1,71 @@
1
+ """W5 (errata E-0265): агрегат `sum` без политики `empty` в голове — план строится,
2
+ правило помечено `headFallible` и `headAgg`: ошибка агрегата снимает только свою
3
+ подстановку, issue одна на (правило, код)."""
4
+ from __future__ import annotations
5
+
6
+ from pathlib import Path
7
+ import sys
8
+ import unittest
9
+
10
+ HERE = Path(__file__).resolve().parent
11
+ sys.path.insert(0, str(HERE))
12
+ import plan as P # noqa: E402
13
+
14
+ NS = "urn:law:test:head-agg"
15
+ TEXT, MONEY = "urn:law:std#Text", "urn:law:std#Money"
16
+ H = "sha256:" + "0" * 64
17
+
18
+
19
+ def _rel(name, kind, params):
20
+ return {"kind": "symbol_decl", "symbolKind": "relation", "id": f"{NS}#{name}", "package": NS, "name": name,
21
+ "relationKind": kind, "contentHash": H,
22
+ "parameters": [{"id": f"{NS}#{name}/arg/{p}", "name": p, "type": {"name": t}} for p, t in params]}
23
+
24
+
25
+ def _lit(pred, args):
26
+ return {"kind": "literal", "polarity": "positive", "predicate": pred, "args": args}
27
+
28
+
29
+ def _est(lit):
30
+ return {"kind": "status", "status": "established", "formula": lit}
31
+
32
+
33
+ def _ir(options=None):
34
+ element = {"kind": "var", "var": "v9"}
35
+ agg = {"kind": "aggregate", "op": "sum",
36
+ "input": {"kind": "comprehension", "distinct": False, "variable": {"id": "v9", "type": {"name": MONEY}},
37
+ "binders": [], "element": element,
38
+ "generator": _est(_lit(f"{NS}#item", [{"kind": "var", "var": "v0"}, element]))}}
39
+ if options is not None:
40
+ agg["options"] = options
41
+ rule = {"kind": "rule", "id": f"{NS}#Total", "package": NS, "strength": "strict",
42
+ "variables": [{"id": "v0", "type": {"name": TEXT}}],
43
+ "body": _est(_lit(f"{NS}#holder", [{"kind": "var", "var": "v0"}])),
44
+ "head": _lit(f"{NS}#total", [{"kind": "var", "var": "v0"}, agg]),
45
+ "scope": {"kind": "boolean", "value": True}, "interpretations": [], "contentHash": H}
46
+ return {"schemaVersion": "law.core.ir/0.7", "semanticVersion": "0.2.11",
47
+ "package": {"name": "test.head_agg", "version": "0.1.0", "namespace": NS, "semanticHash": H},
48
+ "imports": [], "features": ["core-executable"],
49
+ "nodes": [_rel("holder", "empirical", [("e", TEXT)]), _rel("item", "empirical", [("e", TEXT), ("x", MONEY)]),
50
+ _rel("total", "institutional", [("e", TEXT), ("t", MONEY)]), rule],
51
+ "strata": [], "metadata": {}}
52
+
53
+
54
+ class HeadAggregateTests(unittest.TestCase):
55
+ def test_head_aggregate_without_empty_policy_is_planned_per_substitution(self):
56
+ plan = P.build_plan(_ir())
57
+ rules = [r for r in plan.get("rules", []) if r.get("id") == f"{NS}#Total"]
58
+ self.assertEqual(len(rules), 1)
59
+ self.assertTrue(rules[0].get("headFallible"))
60
+ self.assertTrue(rules[0].get("headAgg"))
61
+
62
+ def test_empty_policy_keeps_head_total(self):
63
+ plan = P.build_plan(_ir({"empty": {"kind": "value", "type": {"name": MONEY}, "value": "0",
64
+ "currency": "KZT"}}))
65
+ rules = [r for r in plan.get("rules", []) if r.get("id") == f"{NS}#Total"]
66
+ self.assertEqual(len(rules), 1)
67
+ self.assertFalse(rules[0].get("headAgg"))
68
+
69
+
70
+ if __name__ == "__main__":
71
+ unittest.main()
@@ -0,0 +1,143 @@
1
+ """Инертные конструкции (W1): манифест держится свидетелями, план пропускает
2
+ только перечисленное, ворота роняют строку без свидетеля."""
3
+ from __future__ import annotations
4
+
5
+ import copy
6
+ import json
7
+ from pathlib import Path
8
+ import sys
9
+ import tempfile
10
+ import unittest
11
+
12
+ HERE = Path(__file__).resolve().parent
13
+ sys.path.insert(0, str(HERE))
14
+ import inert # noqa: E402
15
+ import plan as P # noqa: E402
16
+
17
+ NS = "urn:law:test:inert"
18
+ TEXT = "urn:law:std#Text"
19
+
20
+
21
+ def _relation(name: str, kind: str) -> dict:
22
+ return {"kind": "symbol_decl", "symbolKind": "relation", "id": f"{NS}#{name}", "package": NS,
23
+ "name": name, "relationKind": kind, "contentHash": "sha256:" + "0" * 64,
24
+ "parameters": [{"id": f"{NS}#{name}/arg/a", "name": "a", "type": {"name": TEXT}}]}
25
+
26
+
27
+ def _rule(**extra) -> dict:
28
+ node = {"kind": "rule", "id": f"{NS}#Ok", "package": NS, "strength": "strict",
29
+ "variables": [{"id": "v0", "type": {"name": TEXT}}],
30
+ "body": {"kind": "status", "status": "established",
31
+ "formula": {"kind": "literal", "polarity": "positive", "predicate": f"{NS}#flag",
32
+ "args": [{"kind": "var", "var": "v0"}]}},
33
+ "head": {"kind": "literal", "polarity": "positive", "predicate": f"{NS}#ok",
34
+ "args": [{"kind": "var", "var": "v0"}]},
35
+ "scope": {"kind": "boolean", "value": True}, "interpretations": [],
36
+ "contentHash": "sha256:" + "0" * 64}
37
+ node.update(extra)
38
+ return node
39
+
40
+
41
+ def _ir(nodes: list[dict]) -> dict:
42
+ return {"schemaVersion": "law.core.ir/0.7", "semanticVersion": "0.2.11",
43
+ "package": {"name": "test.inert", "version": "0.1.0", "namespace": NS,
44
+ "semanticHash": "sha256:" + "0" * 64},
45
+ "imports": [], "features": ["core-executable"], "nodes": nodes, "strata": [], "metadata": {}}
46
+
47
+
48
+ BASE = [_relation("flag", "empirical"), _relation("ok", "institutional"), _rule()]
49
+
50
+
51
+ class ManifestTests(unittest.TestCase):
52
+ def test_manifest_loads_and_names_pairs(self):
53
+ manifest = inert.load()
54
+ self.assertEqual(manifest["format"], inert.FORMAT)
55
+ for construct, row in manifest["constructs"].items():
56
+ self.assertTrue(construct.startswith(inert.FAMILIES), construct)
57
+ self.assertNotEqual(row["witness"], row["bare"])
58
+ self.assertTrue(row["witness"].startswith("LAY-L0-INERT-"), construct)
59
+
60
+ def test_plan_reads_exactly_the_manifest(self):
61
+ manifest = inert.load()
62
+ self.assertEqual(P.INERT_NODE_KINDS, inert.node_kinds(manifest))
63
+ self.assertEqual(P.INERT_RULE_FIELDS, inert.rule_fields(manifest))
64
+ self.assertNotIn(("source_work", None), P.KNOWN_NODES)
65
+
66
+ def test_malformed_manifest_is_refused(self):
67
+ for broken in ({"format": "x", "constructs": {}},
68
+ {"format": inert.FORMAT, "constructs": {"term:foo": {"witness": "a", "bare": "b"}}},
69
+ {"format": inert.FORMAT, "constructs": {"node:anchor": {"witness": "a"}}},
70
+ {"format": inert.FORMAT, "constructs": {"node:anchor": {"witness": "a", "bare": "a"}}}):
71
+ with tempfile.TemporaryDirectory() as temporary, self.subTest(broken=broken):
72
+ path = Path(temporary) / "m.json"
73
+ path.write_text(json.dumps(broken), encoding="utf-8")
74
+ with self.assertRaises(ValueError):
75
+ inert.load(path)
76
+
77
+
78
+ class PlanTests(unittest.TestCase):
79
+ def test_inert_nodes_are_skipped_and_unknown_nodes_refused(self):
80
+ anchor = {"kind": "anchor", "id": f"{NS}#Ok/anchor/1", "package": NS, "semanticNode": f"{NS}#Ok",
81
+ "sourceFragment": f"{NS}#F1", "relation": "formalizes", "coverage": "exact",
82
+ "contentHash": "sha256:" + "1" * 64}
83
+ P.build_plan(_ir(BASE + [anchor]))
84
+ for kind in sorted(P.INERT_NODE_KINDS):
85
+ with self.subTest(kind=kind):
86
+ P.build_plan(_ir(BASE + [{"kind": kind, "id": f"{NS}#X", "package": NS,
87
+ "contentHash": "sha256:" + "2" * 64}]))
88
+ with self.assertRaisesRegex(P.Refused, "объявление вне среза генератора: interpretation"):
89
+ P.build_plan(_ir(BASE + [{"kind": "interpretation", "id": f"{NS}#I", "package": NS,
90
+ "contentHash": "sha256:" + "3" * 64}]))
91
+ with self.assertRaisesRegex(P.Refused, "объявление вне среза генератора: reference"):
92
+ P.build_plan(_ir(BASE + [{"kind": "reference", "id": f"{NS}#R", "package": NS, "work": f"{NS}#W",
93
+ "resolveAt": "legal_time", "contentHash": "sha256:" + "4" * 64}]))
94
+
95
+ def test_inert_rule_field_is_skipped_and_others_refused(self):
96
+ P.build_plan(_ir(BASE[:2] + [_rule(visibility="public")]))
97
+ with self.assertRaisesRegex(P.Refused, "поле правила вне среза генератора: origin"):
98
+ P.build_plan(_ir(BASE[:2] + [_rule(origin="somewhere")]))
99
+
100
+
101
+ class WitnessTests(unittest.TestCase):
102
+ def _fixtures(self, temporary: Path, pairs: dict[str, tuple[dict, dict]]) -> Path:
103
+ vectors = temporary / "vectors" / "lay"
104
+ for name, (witness, bare) in pairs.items():
105
+ for suffix, doc in (("", witness), ("-BARE", bare)):
106
+ directory = vectors / f"LAY-L0-INERT-{name}{suffix}"
107
+ directory.mkdir(parents=True)
108
+ (directory / "expected.json").write_text(json.dumps(doc), encoding="utf-8")
109
+ return temporary / "vectors"
110
+
111
+ def test_witness_must_match_and_documents_must_agree(self):
112
+ doc = {"results": [{"truthStatus": "TRUE_ONLY"}], "positions": [], "issues": [], "conflicts": [],
113
+ "proofGraph": {"nodes": []}, "manifest": {"artifactHash": "a"}, "resultHash": "x"}
114
+ other = copy.deepcopy(doc)
115
+ other["manifest"]["artifactHash"] = "b"
116
+ other["resultHash"] = "y"
117
+ manifest = {"format": inert.FORMAT, "constructs": {
118
+ "node:anchor": {"witness": "LAY-L0-INERT-ANCHOR", "bare": "LAY-L0-INERT-ANCHOR-BARE"},
119
+ "node:revision": {"witness": "LAY-L0-INERT-REVISION", "bare": "LAY-L0-INERT-REVISION-BARE"},
120
+ "node:ghost": {"witness": "LAY-L0-INERT-GHOST", "bare": "LAY-L0-INERT-GHOST-BARE"}}}
121
+ changed = copy.deepcopy(doc)
122
+ changed["results"] = []
123
+ with tempfile.TemporaryDirectory() as temporary:
124
+ vectors = self._fixtures(Path(temporary), {"ANCHOR": (doc, other), "REVISION": (changed, doc)})
125
+ rows = [{"id": "LAY-L0-INERT-ANCHOR", "actual": "match"},
126
+ {"id": "LAY-L0-INERT-ANCHOR-BARE", "actual": "match"},
127
+ {"id": "LAY-L0-INERT-REVISION", "actual": "plan: объявление вне среза генератора"},
128
+ {"id": "LAY-L0-INERT-REVISION-BARE", "actual": "match"}]
129
+ table, failures = inert.witness_failures(manifest, rows, vectors)
130
+ by_construct = {row["construct"]: row for row in table}
131
+ self.assertTrue(by_construct["node:anchor"]["documentsAgree"])
132
+ self.assertFalse(by_construct["node:revision"]["documentsAgree"])
133
+ self.assertIsNone(by_construct["node:ghost"]["documentsAgree"])
134
+ text = "\n".join(failures)
135
+ self.assertNotIn("node:anchor", text)
136
+ self.assertIn("node:revision: свидетель LAY-L0-INERT-REVISION — «plan: объявление вне среза генератора»", text)
137
+ self.assertIn("node:revision: половины пары расходятся по results", text)
138
+ self.assertIn("node:ghost: свидетель LAY-L0-INERT-GHOST не среди активных векторов", text)
139
+ self.assertIn("node:ghost: у половины пары нет голдена", text)
140
+
141
+
142
+ if __name__ == "__main__":
143
+ unittest.main()
@@ -0,0 +1,113 @@
1
+ """Instant §2.15 в срезе (W2, часть 2): вид значения `inst`, длительности §84.1,
2
+ отказы плана там, где у оракула TYPE_ERROR по виду всегда."""
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ import sys
7
+ import unittest
8
+
9
+ HERE = Path(__file__).resolve().parent
10
+ sys.path.insert(0, str(HERE))
11
+ import plan as P # noqa: E402
12
+
13
+ NS = "urn:law:test:instant"
14
+ INST, INT, QTY, DATE, TEXT = ("urn:law:std#Instant", "urn:law:std#Integer", "urn:law:std#Quantity",
15
+ "urn:law:std#Date", "urn:law:std#Text")
16
+ H = "sha256:" + "0" * 64
17
+
18
+
19
+ def _rel(name, kind, params):
20
+ return {"kind": "symbol_decl", "symbolKind": "relation", "id": f"{NS}#{name}", "package": NS, "name": name,
21
+ "relationKind": kind, "contentHash": H,
22
+ "parameters": [{"id": f"{NS}#{name}/arg/{p}", "name": p, "type": {"name": t}} for p, t in params]}
23
+
24
+
25
+ def _lit(pred, args):
26
+ return {"kind": "literal", "polarity": "positive", "predicate": pred, "args": args}
27
+
28
+
29
+ def _var(i):
30
+ return {"kind": "var", "var": f"v{i}"}
31
+
32
+
33
+ def _val(t, v, unit=None):
34
+ node = {"kind": "value", "type": {"name": t}, "value": v}
35
+ if unit:
36
+ node["unit"] = unit
37
+ return node
38
+
39
+
40
+ def _call(fn, *args):
41
+ return {"kind": "call", "function": f"urn:law:std#{fn}", "args": list(args)}
42
+
43
+
44
+ def _rule(name, variables, body_items, head):
45
+ body = body_items[0] if len(body_items) == 1 else {"kind": "and", "items": body_items}
46
+ return {"kind": "rule", "id": f"{NS}#{name}", "package": NS, "strength": "strict",
47
+ "variables": [{"id": f"v{i}", "type": {"name": t}} for i, t in variables],
48
+ "body": body, "head": head, "scope": {"kind": "boolean", "value": True},
49
+ "interpretations": [], "contentHash": H}
50
+
51
+
52
+ def _est(lit):
53
+ return {"kind": "status", "status": "established", "formula": lit}
54
+
55
+
56
+ def _ir(nodes):
57
+ return {"schemaVersion": "law.core.ir/0.7", "semanticVersion": "0.2.11",
58
+ "package": {"name": "test.instant", "version": "0.1.0", "namespace": NS, "semanticHash": H},
59
+ "imports": [], "features": ["core-executable"], "nodes": nodes, "strata": [], "metadata": {}}
60
+
61
+
62
+ def _plan(nodes):
63
+ return P.build_plan(_ir(nodes))
64
+
65
+
66
+ class InstantTests(unittest.TestCase):
67
+ def test_instant_slot_guard_and_capability(self):
68
+ nodes = [_rel("filed", "empirical", [("a", TEXT), ("t", INST)]),
69
+ _rel("late", "institutional", [("a", TEXT)]),
70
+ _rule("Late", [(0, TEXT), (1, INST)],
71
+ [{"kind": "comparison", "op": "gt", "left": _var(1), "right": _val(INST, "2026-08-26T12:00:00Z")},
72
+ _est(_lit(f"{NS}#filed", [_var(0), _var(1)]))],
73
+ _lit(f"{NS}#late", [_var(0)]))]
74
+ plan = _plan(nodes)
75
+ self.assertIn("value:instant", plan["capabilities"])
76
+ self.assertEqual(P.TYPES[INST], "inst")
77
+
78
+ def test_duration_functions_static_types(self):
79
+ nodes = [_rel("window", "empirical", [("a", TEXT), ("s", INST), ("e", INST)]),
80
+ _rel("two_days", "institutional", [("a", TEXT)]),
81
+ _rel("deadline", "institutional", [("a", TEXT), ("d", INST)]),
82
+ _rule("TwoDays", [(0, TEXT), (1, INST), (2, INST)],
83
+ [{"kind": "comparison", "op": "ge", "left": _call("hours_between", _var(1), _var(2)),
84
+ "right": _val(INT, 48)},
85
+ _est(_lit(f"{NS}#window", [_var(0), _var(1), _var(2)]))],
86
+ _lit(f"{NS}#two_days", [_var(0)])),
87
+ _rule("Deadline", [(0, TEXT), (1, INST), (2, INST)],
88
+ [_est(_lit(f"{NS}#window", [_var(0), _var(1), _var(2)]))],
89
+ _lit(f"{NS}#deadline", [_var(0), _call("add_duration", _var(1), _val(QTY, "24", "hour"))]))]
90
+ plan = _plan(nodes)
91
+ self.assertIn("value:instant", plan["capabilities"])
92
+ # Литерал Quantity длительности сворачивается в (число, единица), как у календарных функций.
93
+ self.assertNotIn("value:quantity", plan["capabilities"])
94
+
95
+ def test_refusals_match_oracle_type_errors(self):
96
+ base = [_rel("filed", "empirical", [("a", TEXT), ("t", INST)]), _rel("late", "institutional", [("a", TEXT)])]
97
+ with self.assertRaisesRegex(P.Refused, "Instant с иным видом"):
98
+ _plan(base + [_rule("Late", [(0, TEXT), (1, INST)],
99
+ [{"kind": "comparison", "op": "gt", "left": _var(1), "right": _val(DATE, "2026-08-26")},
100
+ _est(_lit(f"{NS}#filed", [_var(0), _var(1)]))], _lit(f"{NS}#late", [_var(0)]))])
101
+ with self.assertRaisesRegex(P.Refused, "hours_between: аргумент не Instant"):
102
+ _plan(base + [_rule("Late", [(0, TEXT), (1, INST)],
103
+ [{"kind": "comparison", "op": "gt",
104
+ "left": _call("hours_between", _var(1), _val(DATE, "2026-08-26")), "right": _val(INT, 1)},
105
+ _est(_lit(f"{NS}#filed", [_var(0), _var(1)]))], _lit(f"{NS}#late", [_var(0)]))])
106
+ with self.assertRaisesRegex(P.Refused, "add_duration: единица week"):
107
+ _plan(base + [_rel("due", "institutional", [("a", TEXT), ("d", INST)]),
108
+ _rule("Due", [(0, TEXT), (1, INST)], [_est(_lit(f"{NS}#filed", [_var(0), _var(1)]))],
109
+ _lit(f"{NS}#due", [_var(0), _call("add_duration", _var(1), _val(QTY, "1", "week"))]))])
110
+
111
+
112
+ if __name__ == "__main__":
113
+ unittest.main()