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.
- arxo_codegen/__init__.py +5 -0
- arxo_codegen/__main__.py +7 -0
- arxo_codegen/active-outcomes.json +6837 -0
- arxo_codegen/check.py +158 -0
- arxo_codegen/check_active_canaries.py +262 -0
- arxo_codegen/check_api.py +76 -0
- arxo_codegen/check_conformance.py +414 -0
- arxo_codegen/check_matrix.py +131 -0
- arxo_codegen/check_packages.py +281 -0
- arxo_codegen/check_semantic_probes.py +293 -0
- arxo_codegen/check_xlsx.py +320 -0
- arxo_codegen/consumer_runner.py +316 -0
- arxo_codegen/core_support.py +203 -0
- arxo_codegen/coverage.py +341 -0
- arxo_codegen/deliver.py +180 -0
- arxo_codegen/examples/xlsx/pricing.lawir.json +409 -0
- arxo_codegen/examples/xlsx/pricing.xlsx.json +52 -0
- arxo_codegen/excel_plan.py +602 -0
- arxo_codegen/harness/verify/ci/gates/backends/check_backends.py +1248 -0
- arxo_codegen/harness/verify/ci/gates/backends/l1_fixtures.py +463 -0
- arxo_codegen/inert-nodes.json +62 -0
- arxo_codegen/inert.py +91 -0
- arxo_codegen/inputs.py +59 -0
- arxo_codegen/package.py +565 -0
- arxo_codegen/plan.py +4053 -0
- arxo_codegen/recertify_registry.py +100 -0
- arxo_codegen/runtime-cert.json +5259 -0
- arxo_codegen/runtime_cert.py +233 -0
- arxo_codegen/survey.py +272 -0
- arxo_codegen/target_runner.py +101 -0
- arxo_codegen/targets/bsl/cert.bsl +113 -0
- arxo_codegen/targets/bsl/emit.py +356 -0
- arxo_codegen/targets/bsl/json.bsl +243 -0
- arxo_codegen/targets/bsl/runner.bsl +210 -0
- arxo_codegen/targets/bsl/runtime.bsl +858 -0
- arxo_codegen/targets/cs/Cert.cs +90 -0
- arxo_codegen/targets/cs/Runner.cs +154 -0
- arxo_codegen/targets/cs/Runtime.cs +243 -0
- arxo_codegen/targets/cs/emit.py +223 -0
- arxo_codegen/targets/dart/cert.dart +99 -0
- arxo_codegen/targets/dart/emit.py +206 -0
- arxo_codegen/targets/dart/json.dart +166 -0
- arxo_codegen/targets/dart/runner.dart +154 -0
- arxo_codegen/targets/dart/runtime.dart +293 -0
- arxo_codegen/targets/erl/cert.erl +64 -0
- arxo_codegen/targets/erl/emit.py +216 -0
- arxo_codegen/targets/erl/rt.erl +238 -0
- arxo_codegen/targets/erl/runner.erl +130 -0
- arxo_codegen/targets/ex/cert.exs +62 -0
- arxo_codegen/targets/ex/emit.py +204 -0
- arxo_codegen/targets/ex/runner.exs +124 -0
- arxo_codegen/targets/ex/runtime.ex +227 -0
- arxo_codegen/targets/go/apiBase.go +483 -0
- arxo_codegen/targets/go/apicheck.go +97 -0
- arxo_codegen/targets/go/cert.go +218 -0
- arxo_codegen/targets/go/emit.py +1806 -0
- arxo_codegen/targets/go/evaluate.go +1826 -0
- arxo_codegen/targets/go/runner.go +38 -0
- arxo_codegen/targets/go/runtime.go +2612 -0
- arxo_codegen/targets/java/Cert.java +91 -0
- arxo_codegen/targets/java/Json.java +148 -0
- arxo_codegen/targets/java/Runner.java +151 -0
- arxo_codegen/targets/java/Runtime.java +243 -0
- arxo_codegen/targets/java/emit.py +198 -0
- arxo_codegen/targets/kt/Cert.kt +67 -0
- arxo_codegen/targets/kt/Json.kt +103 -0
- arxo_codegen/targets/kt/Runner.kt +126 -0
- arxo_codegen/targets/kt/Runtime.kt +196 -0
- arxo_codegen/targets/kt/emit.py +195 -0
- arxo_codegen/targets/lua/cert.lua +82 -0
- arxo_codegen/targets/lua/emit.py +250 -0
- arxo_codegen/targets/lua/json.lua +133 -0
- arxo_codegen/targets/lua/runner.lua +167 -0
- arxo_codegen/targets/lua/runtime.lua +541 -0
- arxo_codegen/targets/php/cert.php +84 -0
- arxo_codegen/targets/php/emit.py +229 -0
- arxo_codegen/targets/php/json.php +144 -0
- arxo_codegen/targets/php/runner.php +189 -0
- arxo_codegen/targets/php/runtime.php +660 -0
- arxo_codegen/targets/py/api_base.py +334 -0
- arxo_codegen/targets/py/apicheck.py +51 -0
- arxo_codegen/targets/py/cert.py +140 -0
- arxo_codegen/targets/py/emit.py +1140 -0
- arxo_codegen/targets/py/evaluate.py +980 -0
- arxo_codegen/targets/py/runner.py +38 -0
- arxo_codegen/targets/py/runtime.py +1634 -0
- arxo_codegen/targets/rb/cert.rb +73 -0
- arxo_codegen/targets/rb/emit.py +203 -0
- arxo_codegen/targets/rb/runner.rb +130 -0
- arxo_codegen/targets/rb/runtime.rb +255 -0
- arxo_codegen/targets/rs/Cargo.lock +142 -0
- arxo_codegen/targets/rs/Cargo.toml +16 -0
- arxo_codegen/targets/rs/cert.rs +106 -0
- arxo_codegen/targets/rs/emit.py +233 -0
- arxo_codegen/targets/rs/prefetch.rs +1 -0
- arxo_codegen/targets/rs/runner.rs +181 -0
- arxo_codegen/targets/rs/runtime.rs +448 -0
- arxo_codegen/targets/scala/Cert.scala +77 -0
- arxo_codegen/targets/scala/Json.scala +140 -0
- arxo_codegen/targets/scala/Runner.scala +121 -0
- arxo_codegen/targets/scala/Runtime.scala +269 -0
- arxo_codegen/targets/scala/emit.py +193 -0
- arxo_codegen/targets/swift/cert.swift +105 -0
- arxo_codegen/targets/swift/emit.py +231 -0
- arxo_codegen/targets/swift/json.swift +208 -0
- arxo_codegen/targets/swift/runner.swift +133 -0
- arxo_codegen/targets/swift/runtime.swift +528 -0
- arxo_codegen/targets/ts/apiBase.ts +250 -0
- arxo_codegen/targets/ts/apicheck.ts +37 -0
- arxo_codegen/targets/ts/cert.ts +96 -0
- arxo_codegen/targets/ts/emit.py +1214 -0
- arxo_codegen/targets/ts/evaluate.ts +894 -0
- arxo_codegen/targets/ts/package-lock.json +45 -0
- arxo_codegen/targets/ts/package.json +9 -0
- arxo_codegen/targets/ts/runner.ts +31 -0
- arxo_codegen/targets/ts/runtime.ts +1369 -0
- arxo_codegen/targets/xlsx/README.ru.md +176 -0
- arxo_codegen/targets/xlsx/emit.py +198 -0
- arxo_codegen/temporal.py +59 -0
- arxo_codegen/test_alias_types.py +171 -0
- arxo_codegen/test_api_contract.py +89 -0
- arxo_codegen/test_calendar_policy.py +48 -0
- arxo_codegen/test_check.py +49 -0
- arxo_codegen/test_check_active_canaries.py +96 -0
- arxo_codegen/test_check_conformance.py +132 -0
- arxo_codegen/test_check_semantic_probes.py +85 -0
- arxo_codegen/test_check_xlsx.py +52 -0
- arxo_codegen/test_consumer_runner.py +175 -0
- arxo_codegen/test_core_support.py +80 -0
- arxo_codegen/test_e0212_decision_time.py +63 -0
- arxo_codegen/test_head_aggregate.py +71 -0
- arxo_codegen/test_inert.py +143 -0
- arxo_codegen/test_instant_types.py +113 -0
- arxo_codegen/test_key_conflict_scope.py +92 -0
- arxo_codegen/test_key_conflict_targets.py +17 -0
- arxo_codegen/test_package_layout.py +163 -0
- arxo_codegen/test_plan_language_version.py +44 -0
- arxo_codegen/test_positions_refusal.py +43 -0
- arxo_codegen/test_recertify_registry.py +77 -0
- arxo_codegen/test_ruby_target.py +41 -0
- arxo_codegen/test_runtime_cert.py +83 -0
- arxo_codegen/test_secondary_key_conflict_scope.py +17 -0
- arxo_codegen/test_stage_r.py +108 -0
- arxo_codegen/test_standalone_inputs.py +71 -0
- arxo_codegen/test_support.py +150 -0
- arxo_codegen/test_survey_inputs.py +115 -0
- arxo_codegen-0.4.0.dist-info/LICENSE +202 -0
- arxo_codegen-0.4.0.dist-info/METADATA +1673 -0
- arxo_codegen-0.4.0.dist-info/RECORD +152 -0
- arxo_codegen-0.4.0.dist-info/WHEEL +5 -0
- arxo_codegen-0.4.0.dist-info/entry_points.txt +13 -0
- 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()
|