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,89 @@
1
+ """DECISION-0423: контракт API порождённой библиотеки Python.
2
+
3
+ Один ответ `Answer` у L0 и L1, значения входа — литерал-термы §53, сверка §189
4
+ в общем входе `evaluate` (API отвечает так же, как раннер), ошибка формы вызова —
5
+ `InputError`, состояние — на вызов (потоки не делят таблиц модели)."""
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from pathlib import Path
10
+ import subprocess
11
+ import sys
12
+ import tempfile
13
+ import unittest
14
+
15
+ HERE = Path(__file__).resolve().parent
16
+ sys.path.insert(0, str(HERE))
17
+ import deliver # noqa: E402
18
+ import plan # noqa: E402
19
+ from test_support import fixture_root # noqa: E402
20
+
21
+ PROBE = r'''
22
+ import json, threading, api
23
+ from decimal import Decimal
24
+ out = {}
25
+ base = [api.fact_ransom_offered_to("старик"), api.fact_released_without_ransom("старик")]
26
+ wish = lambda w: [api.fact_wished("старуха", w), api.fact_conveyed_to_fish("старик", w)]
27
+ a = api.ask_obliged_to_grant(base + wish("корыто"), "2026-09-28", "корыто")
28
+ out["answer"] = [a.result_kind, a.truth_status, a.evaluation_status, list(a.issues)]
29
+ d = api.decimal("10")
30
+ bad = api.ask_obliged_to_grant([api.fact_ransom_offered_to(d), api.fact_released_without_ransom(d),
31
+ api.fact_wished("старуха", "корыто"), api.fact_conveyed_to_fish(d, "корыто")],
32
+ "2026-09-28", "корыто")
33
+ out["wrong_type"] = [bad.truth_status, bad.evaluation_status, list(bad.issues)]
34
+ r = api.rows_obliged_to_grant(base + wish("корыто"), "2026-09-28")
35
+ out["rows"] = [r.result_kind, [[t.kind, t.id] for row in r.rows for t in row]]
36
+ try:
37
+ api.fact_wished("старуха", Decimal("1"))
38
+ out["input_error"] = None
39
+ except api.InputError as exc:
40
+ out["input_error"] = type(exc).__name__
41
+ try:
42
+ api.ask_obliged_to_grant([], "28.09.2026", "корыто")
43
+ out["bad_on"] = None
44
+ except api.InputError:
45
+ out["bad_on"] = "InputError"
46
+ res = {}
47
+ def work(n):
48
+ w = "корыто" if n % 2 else "изба"
49
+ res[n] = api.ask_obliged_to_grant(base + wish(w), "2026-09-28", "корыто").truth_status
50
+ ts = [threading.Thread(target=work, args=(n,)) for n in range(24)]
51
+ [t.start() for t in ts]; [t.join() for t in ts]
52
+ out["threads"] = sorted({(n % 2, v) for n, v in res.items()})
53
+ print(json.dumps(out, ensure_ascii=False))
54
+ '''
55
+
56
+
57
+ def emit(name: str, root: Path) -> Path:
58
+ ir = json.loads((fixture_root() / "corpus/clir" / f"{name}.lawir.json").read_text(encoding="utf-8"))
59
+ project = root / name
60
+ deliver.emit_project(plan.build_plan(ir), "py", project)
61
+ return project
62
+
63
+
64
+ class ApiContractTests(unittest.TestCase):
65
+ def test_l1_api_answers_like_the_runner_and_isolates_calls(self):
66
+ with tempfile.TemporaryDirectory(prefix="api-contract-") as raw:
67
+ project = emit("codegen-api-defeasible", Path(raw))
68
+ res = subprocess.run([sys.executable, "-c", PROBE], cwd=project, capture_output=True, text=True, check=True)
69
+ out = json.loads(res.stdout)
70
+ self.assertEqual(out["answer"], ["PROPOSITION", "TRUE_ONLY", "COMPUTED", []])
71
+ # §189: Decimal в слоте сущности — утверждения не приняты, как у раннера и оракула.
72
+ self.assertEqual(out["wrong_type"], ["NEITHER", "TYPE_ERROR", ["TYPE_ERROR"] * 3])
73
+ self.assertEqual(out["rows"], ["COLLECTION", [["entity_ref", "корыто"]]])
74
+ self.assertEqual(out["input_error"], "InputError")
75
+ self.assertEqual(out["bad_on"], "InputError")
76
+ self.assertEqual(out["threads"], [[0, "NEITHER"], [1, "TRUE_ONLY"]])
77
+
78
+ def test_l0_and_l1_share_one_answer_shape(self):
79
+ with tempfile.TemporaryDirectory(prefix="api-contract-l0-") as raw:
80
+ project = emit("codegen-api-strict", Path(raw))
81
+ script = ("import json, api\n"
82
+ "a = api.ask_derived_value([api.fact_input_value('s1')], '2026-09-28', 's1')\n"
83
+ "print(json.dumps([type(a).__name__, a.result_kind, a.truth_status, a.evaluation_status, list(a.issues)]))\n")
84
+ res = subprocess.run([sys.executable, "-c", script], cwd=project, capture_output=True, text=True, check=True)
85
+ self.assertEqual(json.loads(res.stdout), ["Answer", "PROPOSITION", "TRUE_ONLY", "COMPUTED", []])
86
+
87
+
88
+ if __name__ == "__main__":
89
+ unittest.main()
@@ -0,0 +1,48 @@
1
+ """E-0187 changes the month_end read set, not the rest of the policy."""
2
+ from __future__ import annotations
3
+
4
+ import importlib.util
5
+ from pathlib import Path
6
+ import sys
7
+ import unittest
8
+
9
+ path = Path(__file__).resolve().parent / "targets/py/runtime.py"
10
+ spec = importlib.util.spec_from_file_location("_calendar_policy_runtime", path)
11
+ RT = importlib.util.module_from_spec(spec)
12
+ sys.modules[spec.name] = RT
13
+ spec.loader.exec_module(RT)
14
+
15
+
16
+ class CalendarPolicyTests(unittest.TestCase):
17
+ def setUp(self):
18
+ self.policy = {"start_count": "same_day", "include_end": True,
19
+ "roll": "no_roll", "cutoff": None, "month_end": "last_day_of_month"}
20
+ self.addCleanup(RT.set_calendar, None, None)
21
+
22
+ def test_mixed_day_and_year_share_one_policy(self):
23
+ RT.set_calendar(self.policy, None)
24
+ start = RT.Date("2024-02-29")
25
+ self.assertEqual("2025-02-28", RT.add_calendar_period(start, 1, "calendar_year").v)
26
+ self.assertEqual("2024-12-05", RT.add_calendar_period(start, 280, "calendar_day").v)
27
+
28
+ def test_day_step_does_not_silence_invalid_policy(self):
29
+ policies = [None, dict(self.policy, roll="next_working_day"),
30
+ dict(self.policy, cutoff="12:00:00"), dict(self.policy, include_end=False),
31
+ dict(self.policy, month_end="unknown"), dict(self.policy, unknown=True)]
32
+ for policy in policies:
33
+ with self.subTest(policy=policy), self.assertRaises(RT.TermError):
34
+ RT.set_calendar(policy, None)
35
+ RT.add_calendar_period(RT.Date("2024-02-29"), 280, "calendar_day")
36
+
37
+ def test_month_end_is_required_only_for_month_and_year(self):
38
+ policy = dict(self.policy)
39
+ policy.pop("month_end")
40
+ RT.set_calendar(policy, None)
41
+ self.assertEqual("2024-03-01", RT.add_calendar_period(RT.Date("2024-02-29"), 1, "calendar_day").v)
42
+ for unit in ("calendar_month", "calendar_year"):
43
+ with self.subTest(unit=unit), self.assertRaises(RT.TermError):
44
+ RT.add_calendar_period(RT.Date("2024-02-29"), 1, unit)
45
+
46
+
47
+ if __name__ == "__main__":
48
+ unittest.main()
@@ -0,0 +1,49 @@
1
+ """Installed check wrapper keeps runtime certification target-profiled."""
2
+ from __future__ import annotations
3
+
4
+ import io
5
+ from pathlib import Path
6
+ import sys
7
+ from types import SimpleNamespace
8
+ import unittest
9
+ from unittest.mock import patch
10
+
11
+
12
+ HERE = Path(__file__).resolve().parent
13
+ sys.path.insert(0, str(HERE))
14
+ import check # noqa: E402
15
+
16
+
17
+ class CheckWrapperTests(unittest.TestCase):
18
+ def test_profiled_certifier_preserves_gate_error_list_and_reports_selection(self):
19
+ gate = SimpleNamespace(
20
+ CERT=Path("fixture.json"),
21
+ D=SimpleNamespace(emit_project=object()),
22
+ build_projects=object(),
23
+ run_target=object(),
24
+ )
25
+ calls = []
26
+
27
+ def certify_target(lang, descriptor, work, fixture, emit_project, build_projects, run_target):
28
+ calls.append((lang, descriptor, work, fixture, emit_project, build_projects, run_target))
29
+ return ({"checkedFixtureIds": ["a", "b"] if lang == "py" else ["a"],
30
+ "unsupportedOperations": ["rat"] if lang == "lua" else [],
31
+ "unsupportedLiteralTags": ["rat"] if lang == "lua" else []},
32
+ ["сертификация lua: CRASH"] if lang == "lua" else [])
33
+
34
+ check._install_profiled_certifier(gate)
35
+ output = io.StringIO()
36
+ with patch.object(check.runtime_cert, "certify_target", side_effect=certify_target), \
37
+ patch("sys.stdout", output):
38
+ errors = gate.certify({"py": "python", "lua": "lua"}, Path("work"), ["py", "lua"])
39
+
40
+ self.assertEqual(errors, ["сертификация lua: CRASH"])
41
+ self.assertEqual([call[0] for call in calls], ["py", "lua"])
42
+ self.assertTrue(all(call[3] == gate.CERT for call in calls))
43
+ self.assertEqual(gate.RUNTIME_CERT_REPORTS["py"]["checkedFixtureIds"], ["a", "b"])
44
+ self.assertIn("py: 2 cases", output.getvalue())
45
+ self.assertIn("lua: 1 cases; ops вне профиля: rat", output.getvalue())
46
+
47
+
48
+ if __name__ == "__main__":
49
+ unittest.main()
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import subprocess
5
+ import sys
6
+ import tempfile
7
+ import unittest
8
+ from pathlib import Path
9
+ import shutil
10
+
11
+ HERE = Path(__file__).resolve().parent
12
+ sys.path.insert(0, str(HERE))
13
+ import check_active_canaries
14
+ from test_support import materialize_canaries, materialize_core_kit
15
+
16
+
17
+ class ActiveCanaryCheckerTests(unittest.TestCase):
18
+ def bundle_and_kit(self, base: Path) -> tuple[Path, Path]:
19
+ bundle, kit = base / "bundle", base / "core-kit"
20
+ materialize_canaries(bundle)
21
+ materialize_core_kit(kit, source_commit="2" * 40)
22
+ return bundle, kit
23
+
24
+ def test_validates_full_export(self) -> None:
25
+ with tempfile.TemporaryDirectory() as name:
26
+ bundle, kit = self.bundle_and_kit(Path(name))
27
+ lines = check_active_canaries.verify(bundle, core_kit=kit)
28
+ self.assertEqual(6, len(lines))
29
+ self.assertEqual(5, sum(line.startswith("PASS ") for line in lines))
30
+ self.assertEqual(1, sum(line.startswith("EXPECTED_REFUSAL ") for line in lines))
31
+
32
+ def test_refuses_tampered_bundle(self) -> None:
33
+ with tempfile.TemporaryDirectory() as name:
34
+ bundle, kit = self.bundle_and_kit(Path(name))
35
+ target = next((bundle / "canaries").rglob("oracle.outcome.json"))
36
+ target.write_bytes(target.read_bytes() + b"x")
37
+ with self.assertRaisesRegex(check_active_canaries.CanaryError, "hash"):
38
+ check_active_canaries.verify(bundle, core_kit=kit, allow_partial=True)
39
+
40
+ def test_refuses_missing_inventory_hash_and_escaped_id(self) -> None:
41
+ with tempfile.TemporaryDirectory() as name:
42
+ bundle, _ = self.bundle_and_kit(Path(name))
43
+ manifest_path = bundle / "manifest.json"
44
+ manifest = json.loads(manifest_path.read_text())
45
+ manifest["canaries"][0]["files"].pop("query.json")
46
+ manifest_path.write_text(json.dumps(manifest))
47
+ with self.assertRaisesRegex(check_active_canaries.CanaryError, "provenance"):
48
+ check_active_canaries.load(bundle)
49
+ with tempfile.TemporaryDirectory() as name:
50
+ bundle, _ = self.bundle_and_kit(Path(name))
51
+ manifest_path = bundle / "manifest.json"
52
+ manifest = json.loads(manifest_path.read_text())
53
+ manifest["canaries"][0]["id"] = "../escape"
54
+ manifest_path.write_text(json.dumps(manifest))
55
+ with self.assertRaisesRegex(check_active_canaries.CanaryError, "unsafe canary id"):
56
+ check_active_canaries.load(bundle)
57
+
58
+ def test_refuses_empty_required_inventory(self) -> None:
59
+ with tempfile.TemporaryDirectory() as name:
60
+ bundle = Path(name) / "empty"; bundle.mkdir()
61
+ bundle.joinpath("manifest.json").write_text(
62
+ '{"format":"law.codegen-active-canaries/0.1","semanticVersion":"0.2.11","canaries":[],"unsupported":[]}'
63
+ )
64
+ with self.assertRaisesRegex(check_active_canaries.CanaryError, "required canary inventory"):
65
+ check_active_canaries.load(bundle)
66
+
67
+ def test_named_unsupported_copy_is_partial_only(self) -> None:
68
+ with tempfile.TemporaryDirectory() as name:
69
+ bundle, kit = self.bundle_and_kit(Path(name))
70
+ manifest_path = bundle / "manifest.json"
71
+ manifest = json.loads(manifest_path.read_text())
72
+ self.assertEqual([], manifest["unsupported"])
73
+ row = manifest["canaries"].pop()
74
+ shutil.rmtree(bundle / "canaries" / row["id"])
75
+ manifest["unsupported"] = [{"id": row["id"], "mutation": row["mutation"],
76
+ "reason": "synthetic unsupported target surface", "source": row["id"]}]
77
+ manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
78
+ with self.assertRaisesRegex(check_active_canaries.CanaryError, "unsupported required"):
79
+ check_active_canaries.verify(bundle, core_kit=kit)
80
+ lines = check_active_canaries.verify(bundle, core_kit=kit, allow_partial=True)
81
+ self.assertTrue(lines[-1].startswith("PARTIAL unsupported required canaries: " + row["id"]))
82
+
83
+ def test_direct_cli_and_console_entry_are_wired(self) -> None:
84
+ with tempfile.TemporaryDirectory() as name:
85
+ bundle, kit = self.bundle_and_kit(Path(name))
86
+ command = [sys.executable, str(HERE / "check_active_canaries.py"), "--bundle", str(bundle),
87
+ "--core-kit", str(kit)]
88
+ run = subprocess.run(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
89
+ self.assertEqual(0, run.returncode, run.stderr)
90
+ self.assertIn("PASS LAY-L0-RAT-DYN-DECIMAL-KEPT", run.stdout)
91
+ project = (HERE / "pyproject.toml").read_text(encoding="utf-8")
92
+ self.assertIn('arxo-codegen-check-canaries = "arxo_codegen.check_active_canaries:main"', project)
93
+
94
+
95
+ if __name__ == "__main__":
96
+ unittest.main()
@@ -0,0 +1,132 @@
1
+ """The active registry (format 0.3) pins each vector by the digest of its own
2
+ inputs: it must not hide missing coverage or regressions, and a change to one
3
+ vector must not invalidate the pins of the others."""
4
+ import copy
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import sys
9
+ import tempfile
10
+ import unittest
11
+ from unittest.mock import patch
12
+
13
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
14
+ import check_conformance as C
15
+
16
+
17
+ def _manifest() -> dict:
18
+ return {"format": "law.codegen-fixtures/0.1",
19
+ "vectors": [{"id": "example", "path": "vectors/lay/example"},
20
+ {"id": "other", "path": "vectors/lay/other"}],
21
+ "files": [{"path": "vectors/lay/example/ir.json", "sha256": "a", "bytes": 1, "source": "x"},
22
+ {"path": "vectors/lay/other/ir.json", "sha256": "b", "bytes": 2, "source": "y"},
23
+ {"path": "data/backends-vectors.json", "sha256": "c", "bytes": 3, "source": "z"}]}
24
+
25
+
26
+ class RegistryTests(unittest.TestCase):
27
+ def setUp(self):
28
+ self.fixture = _manifest()
29
+ digests = C.vector_input_digests(self.fixture)
30
+ self.registry = {"format": C.REGISTRY_FORMAT,
31
+ "semanticVersions": sorted(C.ACTIVE_SEMANTICS),
32
+ "targets": ["py", "ts", "go"],
33
+ "vectors": {"example": {"outcome": "match", "inputsSha256": digests["example"]}}}
34
+
35
+ def test_complete_registry(self):
36
+ C.validate_outcomes(self.registry, ("py", "ts", "go"))
37
+
38
+ def test_digest_is_per_vector_and_ignores_provenance(self):
39
+ before = C.vector_input_digests(self.fixture)
40
+ updated = copy.deepcopy(self.fixture)
41
+ updated["provenance"] = {"revision": "new source commit"}
42
+ updated["files"][0]["source"] = "different publisher cache"
43
+ updated["files"][1]["sha256"] = "changed" # другой вектор
44
+ updated["files"][2]["sha256"] = "changed harness" # не вектор
45
+ after = C.vector_input_digests(updated)
46
+ self.assertEqual(before["example"], after["example"])
47
+ self.assertNotEqual(before["other"], after["other"])
48
+
49
+ def test_regression_and_malformed_pins_are_rejected(self):
50
+ digest = self.registry["vectors"]["example"]["inputsSha256"]
51
+ for vectors in ({"example": {"outcome": "divergence", "inputsSha256": digest}},
52
+ {"example": {"outcome": "crash", "inputsSha256": digest}},
53
+ {"example": "match"},
54
+ {"example": {"outcome": "match"}}):
55
+ with self.subTest(vectors=vectors), self.assertRaises(ValueError):
56
+ C.validate_outcomes(dict(self.registry, vectors=vectors), ("py",))
57
+
58
+ def test_scope_cannot_drift(self):
59
+ for key, value in (("format", "law.codegen-active-outcomes/0.2"),
60
+ ("semanticVersions", ["0.1.1"]), ("targets", ["py"])):
61
+ with self.subTest(key=key), self.assertRaises(ValueError):
62
+ C.validate_outcomes(dict(self.registry, **{key: value}), ("py",))
63
+
64
+ def test_pin_outcome_reads_both_formats(self):
65
+ self.assertEqual(C.pin_outcome("match"), "match")
66
+ self.assertEqual(C.pin_outcome({"outcome": "plan: x", "inputsSha256": "sha256:0"}), "plan: x")
67
+ self.assertIsNone(C.pin_outcome(None))
68
+
69
+
70
+ class VectorSelectionTests(unittest.TestCase):
71
+ def test_no_selector_keeps_the_full_inventory(self):
72
+ self.assertEqual(C.select_vectors({"A", "B"}), {"A", "B"})
73
+
74
+ def test_exact_ids_and_patterns_form_a_deduplicated_union(self):
75
+ self.assertEqual(C.select_vectors({"INST-A", "INST-B", "OTHER"},
76
+ ("OTHER", "INST-A"), ("INST-*",)),
77
+ {"INST-A", "INST-B", "OTHER"})
78
+
79
+ def test_each_unknown_id_and_unmatched_pattern_is_an_error(self):
80
+ for ids, patterns in ((("TYPO",), ()), (("A",), ("MISSING*",)), (("",), ())):
81
+ with self.subTest(ids=ids, patterns=patterns), self.assertRaises(ValueError):
82
+ C.select_vectors({"A"}, ids, patterns)
83
+
84
+ def test_inertness_selection_adds_only_the_relevant_pair(self):
85
+ manifest = {"format": "test", "constructs": {
86
+ "node:a": {"witness": "A", "bare": "A-BARE"},
87
+ "node:b": {"witness": "B", "bare": "B-BARE"}}}
88
+ reduced, selected = C.selected_inert_witnesses(manifest, {"A-BARE", "OTHER"})
89
+ self.assertEqual(selected, {"A", "A-BARE", "OTHER"})
90
+ self.assertEqual(set(reduced["constructs"]), {"node:a"})
91
+ self.assertEqual(len(manifest["constructs"]), 2)
92
+
93
+
94
+ @unittest.skipUnless(os.environ.get("LAW_CODEGEN_TEST_FIXTURES") and
95
+ os.environ.get("LAW_CODEGEN_TEST_CORE_KIT"), "requires exported current inputs")
96
+ class SelectedConformanceTests(unittest.TestCase):
97
+ def test_partial_development_run_does_not_enforce_the_retired_legacy_baseline(self):
98
+ fixtures = Path(os.environ["LAW_CODEGEN_TEST_FIXTURES"])
99
+ core = Path(os.environ["LAW_CODEGEN_TEST_CORE_KIT"])
100
+ ident = "LAY-L0-INST-EQ-MOMENT"
101
+ gate = C.check.load_harness(fixtures)
102
+ with tempfile.TemporaryDirectory(prefix="selected-legacy-baseline-") as tmp:
103
+ path = Path(tmp) / "legacy.json"
104
+ path.write_text(json.dumps({"format": gate.MANIFEST_FORMAT,
105
+ "vectors": {ident: "plan: retired release refusal"}}))
106
+ gate.MANIFEST = path
107
+ with patch.object(C.check, "load_harness", return_value=gate):
108
+ report, status = C.run(fixtures, core, timeout=10, targets=("py",), vectors=(ident,))
109
+ self.assertEqual(report["summary"]["outcomes"], {"match": 1})
110
+ self.assertEqual(report["summary"]["legacyMismatches"], [ident])
111
+ self.assertEqual(status, 0)
112
+
113
+ def test_partial_run_still_rejects_a_stale_explicit_pin(self):
114
+ fixtures = Path(os.environ["LAW_CODEGEN_TEST_FIXTURES"])
115
+ core = Path(os.environ["LAW_CODEGEN_TEST_CORE_KIT"])
116
+ ident = "LAY-L0-INST-EQ-MOMENT"
117
+ registry = {"format": C.REGISTRY_FORMAT, "semanticVersions": ["0.2.11"],
118
+ "targets": ["py", "ts", "go"], "vectors": {
119
+ ident: {"outcome": "match", "inputsSha256": "sha256:" + "0" * 64}}}
120
+ with tempfile.TemporaryDirectory(prefix="selected-stale-pin-") as tmp:
121
+ path = Path(tmp) / "outcomes.json"
122
+ path.write_text(json.dumps(registry))
123
+ report, status = C.run(fixtures, core, timeout=10, targets=("py",),
124
+ outcomes=path, vectors=(ident,))
125
+ self.assertEqual(report["summary"]["outcomes"], {"match": 1})
126
+ self.assertEqual(report["scope"]["mode"], "partial")
127
+ self.assertEqual(report["summary"]["stalePins"], [ident])
128
+ self.assertEqual(status, 1)
129
+
130
+
131
+ if __name__ == "__main__":
132
+ unittest.main()
@@ -0,0 +1,85 @@
1
+ """Portable verification tests for the published semantic-probe bundle."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ from pathlib import Path
7
+ import tempfile
8
+ import unittest
9
+ from unittest.mock import patch
10
+
11
+ HERE = Path(__file__).resolve().parent
12
+ import sys
13
+ if str(HERE) not in sys.path:
14
+ sys.path.insert(0, str(HERE))
15
+
16
+ import check_semantic_probes as C
17
+ import test_support as S
18
+
19
+
20
+ class SemanticProbeTests(unittest.TestCase):
21
+ def _bundle(self, root: Path) -> Path:
22
+ return S.materialize_semantic_probes(root / "semantic")
23
+
24
+ @staticmethod
25
+ def _rewrite_hash(bundle: Path, ident: str, name: str) -> None:
26
+ manifest_path = bundle / "manifest.json"
27
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
28
+ row = next(row for row in manifest["probes"] if row["id"] == ident)
29
+ data = (bundle / "probes" / ident / name).read_bytes()
30
+ row["files"][name] = "sha256:" + hashlib.sha256(data).hexdigest()
31
+ manifest_path.write_text(json.dumps(manifest, ensure_ascii=False), encoding="utf-8")
32
+
33
+ def test_bundle_executes_with_exported_core_kit(self) -> None:
34
+ with tempfile.TemporaryDirectory() as temporary:
35
+ root = Path(temporary)
36
+ bundle = self._bundle(root)
37
+ kit = S.materialize_core_kit(root / "core", source_commit="3" * 40)
38
+ self.assertEqual(8, len(C.verify(bundle, root / "core")))
39
+ self.assertEqual("law.codegen-core-kit/0.1", kit["format"])
40
+
41
+ def test_targets_require_unique_available_runner_descriptors(self) -> None:
42
+ with patch.object(C.D, "targets", return_value=["py", "ts"]), \
43
+ patch.object(C.D, "emitter") as emitter:
44
+ emitter.return_value.available.return_value = (True, "available")
45
+ self.assertEqual(("py", "ts"), C._targets(("py", "ts")))
46
+ with self.assertRaisesRegex(C.CanaryError, "unique known"):
47
+ C._targets(("py", "py"))
48
+ emitter.return_value.available.return_value = (False, "not installed")
49
+ with self.assertRaisesRegex(C.CanaryError, "unavailable"):
50
+ C._targets(("py",))
51
+
52
+ def test_duplicate_id_is_rejected(self) -> None:
53
+ with tempfile.TemporaryDirectory() as temporary:
54
+ bundle = self._bundle(Path(temporary))
55
+ manifest_path = bundle / "manifest.json"
56
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
57
+ manifest["probes"][-1]["id"] = manifest["probes"][0]["id"]
58
+ manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
59
+ with self.assertRaisesRegex(C.CanaryError, "duplicate"):
60
+ C.load(bundle)
61
+
62
+ def test_symlink_escape_is_rejected(self) -> None:
63
+ with tempfile.TemporaryDirectory() as temporary:
64
+ bundle = self._bundle(Path(temporary))
65
+ outside = Path(temporary) / "outside.json"
66
+ outside.write_text("{}", encoding="utf-8")
67
+ link = bundle / "probes" / "i64-max" / "escape.json"
68
+ link.symlink_to(outside)
69
+ with self.assertRaisesRegex(C.CanaryError, "symlink"):
70
+ C.load(bundle)
71
+
72
+ def test_rehashed_i64_mutation_is_rejected_by_contract(self) -> None:
73
+ with tempfile.TemporaryDirectory() as temporary:
74
+ bundle = self._bundle(Path(temporary))
75
+ query_path = bundle / "probes" / "i64-max" / "query.json"
76
+ query = json.loads(query_path.read_text(encoding="utf-8"))
77
+ query["term"]["value"] = 1
78
+ query_path.write_text(json.dumps(query), encoding="utf-8")
79
+ self._rewrite_hash(bundle, "i64-max", "query.json")
80
+ with self.assertRaisesRegex(C.CanaryError, "canonical number"):
81
+ C.load(bundle)
82
+
83
+
84
+ if __name__ == "__main__":
85
+ unittest.main()
@@ -0,0 +1,52 @@
1
+ import sys
2
+ import tempfile
3
+ import unittest
4
+ from decimal import Decimal
5
+ import json
6
+ from pathlib import Path
7
+
8
+ HERE = Path(__file__).resolve().parent
9
+ sys.path.insert(0, str(HERE))
10
+ import check_xlsx
11
+ from test_support import materialize_core_kit, materialize_xlsx
12
+
13
+
14
+ class CheckXlsxTests(unittest.TestCase):
15
+ def fixture(self, base: Path) -> Path:
16
+ out = base / "fixtures"; return materialize_xlsx(out)
17
+
18
+ def test_accepts_exported_active_fixture_without_oracle_import(self):
19
+ with tempfile.TemporaryDirectory() as name:
20
+ fixture = self.fixture(Path(name))
21
+ _, expected = check_xlsx.load(fixture)
22
+ check_xlsx.structural(fixture, expected)
23
+
24
+ def test_refuses_tampered_export(self):
25
+ with tempfile.TemporaryDirectory() as name:
26
+ fixture = self.fixture(Path(name))
27
+ model = fixture / "pricing/program.lawir.json"
28
+ model.write_bytes(model.read_bytes() + b"tampered")
29
+ with self.assertRaisesRegex(check_xlsx.FixtureError, "hash"):
30
+ check_xlsx.load(fixture)
31
+
32
+ def test_regenerates_with_explicit_core_kit_and_empty_resources(self):
33
+ with tempfile.TemporaryDirectory() as name:
34
+ base = Path(name); fixture = self.fixture(base)
35
+ core = base / "core"; materialize_core_kit(core, source_commit="1" * 40)
36
+ _, expected = check_xlsx.load(fixture)
37
+ regenerated = base / "regenerated"
38
+ check_xlsx.regenerate(fixture, expected, core, regenerated)
39
+ self.assertTrue((regenerated / "pricing/program.lawir.json").is_file())
40
+ self.assertFalse((regenerated / "pricing/model.xlsx").exists())
41
+
42
+ def test_math_mutation_preserves_numeric_cell_type(self):
43
+ with tempfile.TemporaryDirectory() as name:
44
+ fixture = self.fixture(Path(name))
45
+ mapping = {"inputs": {"urn:law:example:excel#operands": [{"args": ["Inputs!A1", "Inputs!B1"]}]}}
46
+ values = check_xlsx.changes_for("arithmetic", {"changes": {"operands": ["1.25", "2"]}}, mapping,
47
+ "urn:law:example:excel#")
48
+ self.assertEqual(set(values.values()), {Decimal("1.25"), Decimal("2")})
49
+
50
+
51
+ if __name__ == "__main__":
52
+ unittest.main()