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
arxo_codegen/check.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Run the existing backend harness against a verified exported fixture kit.
|
|
2
|
+
|
|
3
|
+
The harness is transitional: its source remains in law-dsl until ownership is
|
|
4
|
+
switched. Only fixtures exported from a trusted core checkout should be used.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import hashlib
|
|
10
|
+
import importlib.util
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
HERE = Path(__file__).resolve().parent
|
|
17
|
+
sys.path.insert(0, str(HERE))
|
|
18
|
+
|
|
19
|
+
import runtime_cert
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_fixtures(root: Path) -> dict:
|
|
23
|
+
root = root.resolve()
|
|
24
|
+
manifest = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
|
|
25
|
+
if manifest.get("format") != "law.codegen-fixtures/0.1":
|
|
26
|
+
raise ValueError("unsupported codegen fixture format")
|
|
27
|
+
seen = set()
|
|
28
|
+
for row in manifest["files"]:
|
|
29
|
+
rel = row["path"]
|
|
30
|
+
path = root / rel
|
|
31
|
+
if (not isinstance(rel, str) or Path(rel).is_absolute() or ".." in Path(rel).parts
|
|
32
|
+
or rel in seen or path.is_symlink() or not path.resolve().is_relative_to(root)):
|
|
33
|
+
raise ValueError("invalid codegen fixture path")
|
|
34
|
+
seen.add(rel)
|
|
35
|
+
data = path.read_bytes()
|
|
36
|
+
if row["sha256"] != "sha256:" + hashlib.sha256(data).hexdigest() or row["bytes"] != len(data):
|
|
37
|
+
raise ValueError(f"codegen fixture hash mismatch: {rel}")
|
|
38
|
+
required = {"verify/ci/gates/backends/check_backends.py", "verify/ci/gates/backends/l1_fixtures.py",
|
|
39
|
+
"runtime/runtime-cert.json", "data/backends-vectors.json"}
|
|
40
|
+
if not required <= seen:
|
|
41
|
+
raise ValueError("incomplete codegen fixture kit")
|
|
42
|
+
actual = {p.relative_to(root).as_posix() for p in root.rglob("*") if p.is_file()}
|
|
43
|
+
if actual != seen | {"manifest.json"}:
|
|
44
|
+
raise ValueError("unlisted codegen fixture files")
|
|
45
|
+
return manifest
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_harness(root: Path):
|
|
49
|
+
validate_fixtures(root)
|
|
50
|
+
import core_support
|
|
51
|
+
import deliver
|
|
52
|
+
import plan
|
|
53
|
+
owned_harness = HERE / "harness/verify/ci/gates/backends"
|
|
54
|
+
# Once extracted, harness code is owned/versioned with codegen. During
|
|
55
|
+
# transition the exported copy keeps source-checkout invocation working.
|
|
56
|
+
gate_dir = owned_harness if owned_harness.is_dir() else root / "verify/ci/gates/backends"
|
|
57
|
+
old_path = sys.path[:]
|
|
58
|
+
old_bytecode = sys.dont_write_bytecode
|
|
59
|
+
old_fx = sys.modules.pop("l1_fixtures", None)
|
|
60
|
+
try:
|
|
61
|
+
sys.dont_write_bytecode = True
|
|
62
|
+
sys.path.insert(0, str(gate_dir))
|
|
63
|
+
spec = importlib.util.spec_from_file_location("_codegen_fixture_gate", gate_dir / "check_backends.py")
|
|
64
|
+
gate = importlib.util.module_from_spec(spec)
|
|
65
|
+
spec.loader.exec_module(gate)
|
|
66
|
+
finally:
|
|
67
|
+
sys.path[:] = old_path
|
|
68
|
+
sys.dont_write_bytecode = old_bytecode
|
|
69
|
+
sys.modules.pop("l1_fixtures", None)
|
|
70
|
+
if old_fx is not None:
|
|
71
|
+
sys.modules["l1_fixtures"] = old_fx
|
|
72
|
+
gate.ROOT = root
|
|
73
|
+
gate.BACKENDS = HERE
|
|
74
|
+
gate.VECTORS = root / "vectors"
|
|
75
|
+
gate.CLIR = root / "corpus/clir"
|
|
76
|
+
gate.CERT = root / "runtime/runtime-cert.json"
|
|
77
|
+
gate.MANIFEST = root / "data/backends-vectors.json"
|
|
78
|
+
gate.P = plan
|
|
79
|
+
gate.D = deliver
|
|
80
|
+
gate.canon_value = lambda value: core_support.canonical_bytes(value, document=False).decode("utf-8")
|
|
81
|
+
_install_profiled_certifier(gate)
|
|
82
|
+
return gate
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _install_profiled_certifier(gate) -> None:
|
|
86
|
+
"""Apply the installable runtime contract without modifying the archived gate.
|
|
87
|
+
|
|
88
|
+
The source-tree gate deliberately keeps its historical all-fixture
|
|
89
|
+
certification during the transition. The packaged wrapper instead gives
|
|
90
|
+
each target the fixture subset declared in :mod:`runtime_cert`; returning
|
|
91
|
+
the same ``list[str]`` shape keeps the gate's normal failure path intact.
|
|
92
|
+
"""
|
|
93
|
+
def certify(emit: dict, work: Path, active: list[str]) -> list[str]:
|
|
94
|
+
errors, reports = [], {}
|
|
95
|
+
for lang in active:
|
|
96
|
+
try:
|
|
97
|
+
report, target_errors = runtime_cert.certify_target(
|
|
98
|
+
lang, emit[lang], work, gate.CERT, gate.D.emit_project,
|
|
99
|
+
gate.build_projects, gate.run_target,
|
|
100
|
+
)
|
|
101
|
+
reports[lang] = report
|
|
102
|
+
errors.extend(target_errors)
|
|
103
|
+
except runtime_cert.FixtureError as exc:
|
|
104
|
+
errors.append(f"сертификация {lang}: неверный capability-контракт: {exc}")
|
|
105
|
+
gate.RUNTIME_CERT_REPORTS = reports
|
|
106
|
+
summary = []
|
|
107
|
+
for lang in active:
|
|
108
|
+
report = reports.get(lang)
|
|
109
|
+
if report is None:
|
|
110
|
+
continue
|
|
111
|
+
skipped_ops = ",".join(report["unsupportedOperations"]) or "—"
|
|
112
|
+
skipped_literals = ",".join(report["unsupportedLiteralTags"]) or "—"
|
|
113
|
+
summary.append(f"{lang}: {len(report['checkedFixtureIds'])} cases; "
|
|
114
|
+
f"ops вне профиля: {skipped_ops}; literals вне профиля: {skipped_literals}")
|
|
115
|
+
if summary:
|
|
116
|
+
print("check_backends: installed runtime profiles — " + " | ".join(summary))
|
|
117
|
+
return errors
|
|
118
|
+
|
|
119
|
+
gate.certify = certify
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def main(argv: list[str] | None = None) -> int:
|
|
123
|
+
ap = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
|
|
124
|
+
ap.add_argument("--fixtures", required=True, type=Path)
|
|
125
|
+
ap.add_argument("--core-kit", required=True, type=Path)
|
|
126
|
+
ap.add_argument("--active-canaries", type=Path)
|
|
127
|
+
ap.add_argument("--active-canary-targets")
|
|
128
|
+
args, remaining = ap.parse_known_args(argv)
|
|
129
|
+
import core_support
|
|
130
|
+
core_support.configure_core_kit(args.core_kit)
|
|
131
|
+
core_support.validate_core_kit(args.core_kit)
|
|
132
|
+
gate = load_harness(args.fixtures.resolve())
|
|
133
|
+
if args.active_canaries is not None:
|
|
134
|
+
def active_value_canaries(_emit, _work):
|
|
135
|
+
import check_active_canaries
|
|
136
|
+
capable = tuple(lang for lang in ("py", "ts", "go") if lang in _emit)
|
|
137
|
+
if args.active_canary_targets is not None:
|
|
138
|
+
requested = tuple(x for x in args.active_canary_targets.split(',') if x)
|
|
139
|
+
if set(capable) != set(requested) or len(requested) != len(capable):
|
|
140
|
+
return ["active value canaries must cover all active primary targets: " + ",".join(capable)], []
|
|
141
|
+
if not capable:
|
|
142
|
+
return [], ["active value canaries: no primary targets in this profile"]
|
|
143
|
+
try:
|
|
144
|
+
lines = check_active_canaries.verify(args.active_canaries, core_kit=args.core_kit, targets=capable)
|
|
145
|
+
except check_active_canaries.CanaryError as exc:
|
|
146
|
+
return ["active value canaries: " + str(exc)], []
|
|
147
|
+
return [], ["active value canaries: " + line for line in lines]
|
|
148
|
+
gate.value_canaries = active_value_canaries
|
|
149
|
+
old_argv = sys.argv[:]
|
|
150
|
+
try:
|
|
151
|
+
sys.argv = ["arxo-codegen-check", *remaining]
|
|
152
|
+
return gate.main()
|
|
153
|
+
finally:
|
|
154
|
+
sys.argv[:] = old_argv
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""Standalone verifier for a published active-canary bundle.
|
|
2
|
+
|
|
3
|
+
The bundle contains frozen lawref outcomes produced by the publisher. This
|
|
4
|
+
consumer never imports lawref or the monorepo CI gate: it verifies the bundle
|
|
5
|
+
inventory, rebuilds each supported plan with the installed code generator, and
|
|
6
|
+
executes the Python baseline and named mutation against the frozen oracle.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import copy
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
import tempfile
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
HERE = Path(__file__).resolve().parent
|
|
21
|
+
if str(HERE) not in sys.path:
|
|
22
|
+
sys.path.insert(0, str(HERE))
|
|
23
|
+
# Keep one flat core_support module with plan/deliver, both in a checkout and
|
|
24
|
+
# in the installed package layout (deliver deliberately uses this convention).
|
|
25
|
+
import core_support # noqa: E402
|
|
26
|
+
import deliver as D # noqa: E402
|
|
27
|
+
import plan as P # noqa: E402
|
|
28
|
+
import target_runner as TR # noqa: E402
|
|
29
|
+
|
|
30
|
+
FORMAT = "law.codegen-active-canaries/0.1"
|
|
31
|
+
SEMANTIC = "0.2.11"
|
|
32
|
+
REQUIRED = {
|
|
33
|
+
"LAY-L0-RAT-DYN-DECIMAL-KEPT": "kindChecks",
|
|
34
|
+
"LAY-L0-RAT-GUARD-DYN-MIX": "err",
|
|
35
|
+
"LAY-L0-RAT-HEAD-DIV-ZERO": "headFallible",
|
|
36
|
+
"LAY-L0-RAT-DYN-RATIONAL-KEPT": "lk",
|
|
37
|
+
"LAY-L0-QTY-GUARD-DIMENSION": "err",
|
|
38
|
+
"LAY-L0-QTY-ADD-DIMENSION": "headFallible",
|
|
39
|
+
}
|
|
40
|
+
EXPECTED_REFUSALS = {"LAY-L0-RAT-GUARD-DYN-MIX": "NUMBER_UNAVAILABLE"}
|
|
41
|
+
SOURCE_FILES = {"source.law", "source.ir.json", "source.case.json", "source.query.json"}
|
|
42
|
+
DERIVED_FILES = {"ir.json", "case.json", "query.json", "oracle.outcome.json",
|
|
43
|
+
"backend.baseline.json", "backend.mutated.json"}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class CanaryError(RuntimeError):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _sha(data: bytes) -> str:
|
|
51
|
+
return "sha256:" + hashlib.sha256(data).hexdigest()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _read_json(path: Path) -> dict:
|
|
55
|
+
try:
|
|
56
|
+
obj = json.loads(path.read_bytes())
|
|
57
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
58
|
+
raise CanaryError(f"invalid JSON: {path.name}") from exc
|
|
59
|
+
if not isinstance(obj, dict):
|
|
60
|
+
raise CanaryError(f"JSON object required: {path.name}")
|
|
61
|
+
return obj
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _safe_relative(value: object) -> Path:
|
|
65
|
+
if not isinstance(value, str):
|
|
66
|
+
raise CanaryError("invalid manifest path")
|
|
67
|
+
path = Path(value)
|
|
68
|
+
if path.is_absolute() or not value or ".." in path.parts:
|
|
69
|
+
raise CanaryError("unsafe manifest path")
|
|
70
|
+
return path
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _canary_id(value: object) -> str:
|
|
74
|
+
if not isinstance(value, str) or not value or Path(value).name != value or not value.isascii() \
|
|
75
|
+
or any(char not in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-" for char in value):
|
|
76
|
+
raise CanaryError("unsafe canary id")
|
|
77
|
+
return value
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _outcome(document: dict) -> dict:
|
|
81
|
+
if "refused" in document:
|
|
82
|
+
return {"refused": document["refused"]}
|
|
83
|
+
issues = document.get("issues", [])
|
|
84
|
+
if not isinstance(issues, list):
|
|
85
|
+
raise CanaryError("backend outcome has invalid issues")
|
|
86
|
+
codes = sorted(item.get("code") if isinstance(item, dict) else str(item)
|
|
87
|
+
for item in issues if isinstance(item, str) or isinstance(item, dict) and item.get("code"))
|
|
88
|
+
result = (document.get("results") or [{}])[0]
|
|
89
|
+
if not isinstance(result, dict):
|
|
90
|
+
raise CanaryError("oracle result is invalid")
|
|
91
|
+
return {"truthStatus": result.get("truthStatus", document.get("truthStatus")),
|
|
92
|
+
"evaluationStatus": result.get("evaluationStatus", document.get("evaluationStatus")),
|
|
93
|
+
"issues": codes}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _mutate(plan: dict, field: str) -> dict:
|
|
97
|
+
out = copy.deepcopy(plan)
|
|
98
|
+
for rule in out.get("rules", []):
|
|
99
|
+
if field in ("kindChecks", "headFallible"):
|
|
100
|
+
rule.pop(field, None)
|
|
101
|
+
elif field == "err":
|
|
102
|
+
for guard in rule.get("guards", []):
|
|
103
|
+
guard.pop("err", None)
|
|
104
|
+
if field == "lk":
|
|
105
|
+
stack = [out]
|
|
106
|
+
while stack:
|
|
107
|
+
item = stack.pop()
|
|
108
|
+
if isinstance(item, dict):
|
|
109
|
+
if item.get("k") == "bin" and item.get("op") == "div" and "lk" in item:
|
|
110
|
+
item["lk"] = item["rk"] = "d"
|
|
111
|
+
stack.extend(value for value in item.values() if isinstance(value, (dict, list)))
|
|
112
|
+
elif isinstance(item, list):
|
|
113
|
+
stack.extend(item)
|
|
114
|
+
return out
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _run(plan: dict, case: Path, query: Path, target: str = "py", env=None) -> dict:
|
|
118
|
+
with tempfile.TemporaryDirectory(prefix="codegen-canary-") as temp:
|
|
119
|
+
project = Path(temp) / target
|
|
120
|
+
D.emit_project(plan, target, project)
|
|
121
|
+
emit={target:D.emitter(target)}
|
|
122
|
+
failures=TR.build_projects(emit,{target:[project]},Path(temp),env)
|
|
123
|
+
if failures: raise CanaryError(failures[0])
|
|
124
|
+
kind,response=TR.run_target(emit[target],project,"runner",[str(case),str(query)],env)
|
|
125
|
+
if kind == "refused": return _outcome({"refused": response})
|
|
126
|
+
if kind != "answer": raise CanaryError(f"generated {target} runner {kind}: {response}")
|
|
127
|
+
return _outcome(response)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def load(bundle: Path) -> tuple[dict, list[dict]]:
|
|
131
|
+
bundle = Path(bundle).resolve()
|
|
132
|
+
if bundle.is_symlink() or not bundle.is_dir():
|
|
133
|
+
raise CanaryError("canary bundle is not a directory")
|
|
134
|
+
if any(path.is_symlink() for path in bundle.rglob("*")):
|
|
135
|
+
raise CanaryError("canary bundle contains a symlink")
|
|
136
|
+
manifest = _read_json(bundle / "manifest.json")
|
|
137
|
+
if manifest.get("format") != FORMAT or manifest.get("semanticVersion") != SEMANTIC:
|
|
138
|
+
raise CanaryError("unsupported canary bundle format or semantic version")
|
|
139
|
+
rows = manifest.get("canaries")
|
|
140
|
+
unsupported = manifest.get("unsupported")
|
|
141
|
+
if not isinstance(rows, list) or not isinstance(unsupported, list):
|
|
142
|
+
raise CanaryError("canary bundle has invalid rows")
|
|
143
|
+
known = {"manifest.json"}
|
|
144
|
+
seen = set()
|
|
145
|
+
all_rows: dict[str, dict] = {}
|
|
146
|
+
for row in rows:
|
|
147
|
+
if not isinstance(row, dict):
|
|
148
|
+
raise CanaryError("invalid canary row")
|
|
149
|
+
ident = _canary_id(row.get("id"))
|
|
150
|
+
if ident in seen:
|
|
151
|
+
raise CanaryError("duplicate canary id")
|
|
152
|
+
seen.add(ident); all_rows[ident] = row
|
|
153
|
+
prefix = Path("canaries") / ident
|
|
154
|
+
known.update((prefix / name).as_posix() for name in (
|
|
155
|
+
"source.law", "source.ir.json", "source.case.json", "source.query.json",
|
|
156
|
+
"ir.json", "case.json", "query.json", "oracle.outcome.json",
|
|
157
|
+
"backend.baseline.json", "backend.mutated.json"))
|
|
158
|
+
if row.get("semanticVersion") != SEMANTIC or row.get("mutation") != REQUIRED.get(ident):
|
|
159
|
+
raise CanaryError("invalid canary semantic provenance")
|
|
160
|
+
if row.get("expectedRefusal") != EXPECTED_REFUSALS.get(ident):
|
|
161
|
+
raise CanaryError("invalid current refusal qualification")
|
|
162
|
+
for key, required in (("sourceFiles", SOURCE_FILES), ("files", DERIVED_FILES)):
|
|
163
|
+
inventory = row.get(key)
|
|
164
|
+
if not isinstance(inventory, dict) or set(inventory) != required:
|
|
165
|
+
raise CanaryError("canary row lacks file provenance")
|
|
166
|
+
for name, digest in inventory.items():
|
|
167
|
+
path = prefix / _safe_relative(name)
|
|
168
|
+
if digest != _sha((bundle / path).read_bytes()):
|
|
169
|
+
raise CanaryError("canary file hash mismatch: " + path.as_posix())
|
|
170
|
+
for row in unsupported:
|
|
171
|
+
if not isinstance(row, dict):
|
|
172
|
+
raise CanaryError("invalid unsupported canary row")
|
|
173
|
+
ident = _canary_id(row.get("id"))
|
|
174
|
+
if ident in all_rows or row.get("mutation") != REQUIRED.get(ident) or not isinstance(row.get("reason"), str):
|
|
175
|
+
raise CanaryError("invalid unsupported canary provenance")
|
|
176
|
+
all_rows[ident] = row
|
|
177
|
+
if set(all_rows) != set(REQUIRED):
|
|
178
|
+
raise CanaryError("required canary inventory is incomplete")
|
|
179
|
+
actual = {path.relative_to(bundle).as_posix() for path in bundle.rglob("*") if path.is_file()}
|
|
180
|
+
if actual != known:
|
|
181
|
+
raise CanaryError("canary bundle inventory differs from manifest")
|
|
182
|
+
return manifest, rows
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def verify(bundle: Path, *, allow_partial: bool = False, core_kit: Path | None = None,
|
|
186
|
+
targets: tuple[str,...] = ("py",)) -> list[str]:
|
|
187
|
+
if core_kit is None:
|
|
188
|
+
raise CanaryError("--core-kit is required for standalone canary verification")
|
|
189
|
+
previous = core_support._configured_kit
|
|
190
|
+
core_support.configure_core_kit(core_kit)
|
|
191
|
+
try:
|
|
192
|
+
if not targets or len(set(targets)) != len(targets) or any(t not in ("py","ts","go") for t in targets): raise CanaryError("unknown active canary target")
|
|
193
|
+
core_support.validate_core_kit(core_kit)
|
|
194
|
+
bundle = Path(bundle).resolve()
|
|
195
|
+
manifest, rows = load(bundle)
|
|
196
|
+
messages: list[str] = []
|
|
197
|
+
cache_directory = tempfile.TemporaryDirectory(prefix="codegen-active-env-")
|
|
198
|
+
env = dict(os.environ, GOCACHE=str(Path(cache_directory.name) / "gocache"),
|
|
199
|
+
GOMODCACHE=str(Path(cache_directory.name) / "gomodcache"))
|
|
200
|
+
for row in rows:
|
|
201
|
+
base = bundle / "canaries" / row["id"]
|
|
202
|
+
ir, case, query = (_read_json(base / name) for name in ("ir.json", "case.json", "query.json"))
|
|
203
|
+
if row.get("expectedRefusal"):
|
|
204
|
+
try:
|
|
205
|
+
P.build_plan(ir, resources={})
|
|
206
|
+
except P.Refused as exc:
|
|
207
|
+
if not str(exc).startswith(row["expectedRefusal"] + ":"):
|
|
208
|
+
raise CanaryError("wrong qualified canary refusal") from exc
|
|
209
|
+
for name in ("backend.baseline.json", "backend.mutated.json"):
|
|
210
|
+
if _read_json(base / name) != {"refused": str(exc)}:
|
|
211
|
+
raise CanaryError("qualified refusal differs from frozen evidence")
|
|
212
|
+
else:
|
|
213
|
+
raise CanaryError("qualified canary unexpectedly admitted")
|
|
214
|
+
messages.append(f"EXPECTED_REFUSAL {row['id']}: {row['expectedRefusal']}")
|
|
215
|
+
continue
|
|
216
|
+
plan = P.build_plan(ir, resources={})
|
|
217
|
+
mutation = _mutate(plan, row["mutation"])
|
|
218
|
+
if mutation == plan:
|
|
219
|
+
raise CanaryError(f"{row['id']}: mutation does not change rebuilt plan")
|
|
220
|
+
expected = _read_json(base / "oracle.outcome.json")
|
|
221
|
+
frozen_baseline = _read_json(base / "backend.baseline.json")
|
|
222
|
+
frozen_mutated = _read_json(base / "backend.mutated.json")
|
|
223
|
+
for target in targets:
|
|
224
|
+
baseline = _run(plan, base / "case.json", base / "query.json",target,env)
|
|
225
|
+
changed = _run(mutation, base / "case.json", base / "query.json",target,env)
|
|
226
|
+
if baseline != expected or baseline != frozen_baseline:
|
|
227
|
+
raise CanaryError(f"{row['id']} {target}: baseline differs from frozen evidence")
|
|
228
|
+
if changed != frozen_mutated or changed == baseline:
|
|
229
|
+
raise CanaryError(f"{row['id']} {target}: mutation witness did not fail")
|
|
230
|
+
messages.append(f"PASS {row['id']} ({','.join(targets)})")
|
|
231
|
+
unsupported = manifest["unsupported"]
|
|
232
|
+
if unsupported:
|
|
233
|
+
names = ", ".join(str(row["id"]) for row in unsupported)
|
|
234
|
+
if not allow_partial:
|
|
235
|
+
raise CanaryError("unsupported required canaries: " + names)
|
|
236
|
+
messages.append("PARTIAL unsupported required canaries: " + names)
|
|
237
|
+
return messages
|
|
238
|
+
finally:
|
|
239
|
+
if "cache_directory" in locals():
|
|
240
|
+
cache_directory.cleanup()
|
|
241
|
+
core_support.configure_core_kit(previous)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def main(argv: list[str] | None = None) -> int:
|
|
245
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
246
|
+
parser.add_argument("--bundle", type=Path, required=True)
|
|
247
|
+
parser.add_argument("--core-kit", type=Path)
|
|
248
|
+
parser.add_argument("--allow-partial", action="store_true", help="report unsupported rows as PARTIAL, never PASS")
|
|
249
|
+
parser.add_argument("--targets", default="py", help="comma-separated py,ts,go")
|
|
250
|
+
args = parser.parse_args(argv)
|
|
251
|
+
try:
|
|
252
|
+
for line in verify(args.bundle, allow_partial=args.allow_partial, core_kit=args.core_kit,
|
|
253
|
+
targets=tuple(x for x in args.targets.split(',') if x)):
|
|
254
|
+
print(line)
|
|
255
|
+
except (CanaryError, core_support.CoreKitError, P.Refused) as exc:
|
|
256
|
+
print(f"FAIL: {exc}", file=sys.stderr)
|
|
257
|
+
return 1
|
|
258
|
+
return 0
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if __name__ == "__main__":
|
|
262
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""API порождённого модуля против раннера на проверенном комплекте (DECISION-0423 §2.6).
|
|
2
|
+
|
|
3
|
+
Шаг приёмки: для API-пакетов комплекта (`corpus/clir`) печатаются проекты
|
|
4
|
+
целей с API, и каждое задание — положительное, с отрицательным фактом L1, с
|
|
5
|
+
литералом чужого типа (§189), с синтетическим конфликтом ключа §45.1 —
|
|
6
|
+
исполняется раннером и харнессом API; исходы обязаны совпасть у API и раннера
|
|
7
|
+
каждой цели и у целей между собой. Затем пакет заданий с повтором
|
|
8
|
+
исполняется одним процессом последовательно и параллельно, и канарейка
|
|
9
|
+
портит общую часть API Python — расхождение обязано найтись.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
import sys
|
|
17
|
+
import tempfile
|
|
18
|
+
|
|
19
|
+
HERE = Path(__file__).resolve().parent
|
|
20
|
+
sys.path.insert(0, str(HERE))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main(argv: list[str] | None = None) -> int:
|
|
24
|
+
ap = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
|
|
25
|
+
ap.add_argument("--fixtures", required=True, type=Path)
|
|
26
|
+
ap.add_argument("--core-kit", required=True, type=Path)
|
|
27
|
+
ap.add_argument("--targets", default="py,ts,go", help="цели с API через запятую")
|
|
28
|
+
ap.add_argument("--require", default="", help="цели, отсутствие тулчейна которых — отказ")
|
|
29
|
+
ap.add_argument("--keep", type=Path, help="сохранить проекты")
|
|
30
|
+
ap.add_argument("--report", type=Path)
|
|
31
|
+
args = ap.parse_args(argv)
|
|
32
|
+
import core_support
|
|
33
|
+
core_support.configure_core_kit(args.core_kit)
|
|
34
|
+
core_support.validate_core_kit(args.core_kit)
|
|
35
|
+
import check
|
|
36
|
+
gate = check.load_harness(args.fixtures.resolve())
|
|
37
|
+
wanted = [x for x in args.targets.split(",") if x]
|
|
38
|
+
emit = {}
|
|
39
|
+
for lang in wanted:
|
|
40
|
+
em = gate.D.emitter(lang)
|
|
41
|
+
if not (getattr(em, "API", None) and hasattr(em, "emit_api")):
|
|
42
|
+
print(f"check_api: у цели {lang} нет API")
|
|
43
|
+
return 1
|
|
44
|
+
emit[lang] = em
|
|
45
|
+
tools = {lang: emit[lang].available() for lang in wanted}
|
|
46
|
+
active = [lang for lang in wanted if tools[lang][0]]
|
|
47
|
+
for lang in wanted:
|
|
48
|
+
if not tools[lang][0]:
|
|
49
|
+
print(f"check_api: цель {lang} ПРОПУЩЕНА — {tools[lang][1]}")
|
|
50
|
+
missing = [x for x in args.require.split(",") if x and x not in active]
|
|
51
|
+
if missing:
|
|
52
|
+
print(f"check_api: обязательные цели без тулчейна: {', '.join(missing)}")
|
|
53
|
+
return 1
|
|
54
|
+
if not active:
|
|
55
|
+
print("check_api: нет ни одной цели с тулчейном")
|
|
56
|
+
return 1
|
|
57
|
+
emit = {lang: emit[lang] for lang in active}
|
|
58
|
+
if args.keep:
|
|
59
|
+
work = args.keep.resolve()
|
|
60
|
+
work.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
problems, checked = gate.api_check(emit, work, active)
|
|
62
|
+
else:
|
|
63
|
+
with tempfile.TemporaryDirectory(prefix="codegen-api-") as tmp:
|
|
64
|
+
problems, checked = gate.api_check(emit, Path(tmp), active)
|
|
65
|
+
for line in problems:
|
|
66
|
+
print("check_api: " + line)
|
|
67
|
+
print(f"check_api: цели {', '.join(active)}; сверено заданий {checked}; проблем {len(problems)}")
|
|
68
|
+
if args.report:
|
|
69
|
+
args.report.write_text(json.dumps({"format": "law.codegen-api-check/0.1", "targets": active,
|
|
70
|
+
"checked": checked, "problems": problems},
|
|
71
|
+
ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
|
|
72
|
+
return 1 if problems or not checked else 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
raise SystemExit(main())
|