archkeep-rule-sdk 0.13.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.
- archkeep_rule_sdk/__init__.py +60 -0
- archkeep_rule_sdk/build.py +336 -0
- archkeep_rule_sdk/carrier/Cargo.toml.template +70 -0
- archkeep_rule_sdk/carrier/lib.rs.template +737 -0
- archkeep_rule_sdk/runtime.py +561 -0
- archkeep_rule_sdk-0.13.0.dist-info/METADATA +306 -0
- archkeep_rule_sdk-0.13.0.dist-info/RECORD +9 -0
- archkeep_rule_sdk-0.13.0.dist-info/WHEEL +5 -0
- archkeep_rule_sdk-0.13.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Author a Archkeep custom architecture rule in Python.
|
|
2
|
+
|
|
3
|
+
`import archkeep_rule_sdk` reaches the same names in both places a rule ever
|
|
4
|
+
runs: here, under the author's own CPython, and inside the rule's `.wasm`
|
|
5
|
+
carrier, where the build tool registers `runtime.py`'s namespace under this
|
|
6
|
+
name in an embedded interpreter that has no filesystem to import from.
|
|
7
|
+
|
|
8
|
+
Which is why this file re-exports rather than adds: every public name is
|
|
9
|
+
`runtime`'s, so the module an author imports is the module the artifact runs.
|
|
10
|
+
A helper defined HERE would exist in the tests and be missing in the artifact,
|
|
11
|
+
and the difference would surface as an `unknown` verdict in a consumer's CI
|
|
12
|
+
rather than as a red test — the silent direction, one import away
|
|
13
|
+
(`../../../../AGENTS.md`).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .runtime import (
|
|
17
|
+
EVIDENCE_KINDS,
|
|
18
|
+
VERDICTS,
|
|
19
|
+
Edge,
|
|
20
|
+
Evidence,
|
|
21
|
+
Finding,
|
|
22
|
+
ImportRecord,
|
|
23
|
+
Policy,
|
|
24
|
+
Project,
|
|
25
|
+
Resolved,
|
|
26
|
+
RuleError,
|
|
27
|
+
RuleInstance,
|
|
28
|
+
Spelling,
|
|
29
|
+
UndeclaredEvidence,
|
|
30
|
+
Verdict,
|
|
31
|
+
drive,
|
|
32
|
+
failed,
|
|
33
|
+
from_findings,
|
|
34
|
+
not_applicable,
|
|
35
|
+
passed,
|
|
36
|
+
unknown,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"EVIDENCE_KINDS",
|
|
41
|
+
"VERDICTS",
|
|
42
|
+
"Edge",
|
|
43
|
+
"Evidence",
|
|
44
|
+
"Finding",
|
|
45
|
+
"ImportRecord",
|
|
46
|
+
"Policy",
|
|
47
|
+
"Project",
|
|
48
|
+
"Resolved",
|
|
49
|
+
"RuleError",
|
|
50
|
+
"RuleInstance",
|
|
51
|
+
"Spelling",
|
|
52
|
+
"UndeclaredEvidence",
|
|
53
|
+
"Verdict",
|
|
54
|
+
"drive",
|
|
55
|
+
"failed",
|
|
56
|
+
"from_findings",
|
|
57
|
+
"not_applicable",
|
|
58
|
+
"passed",
|
|
59
|
+
"unknown",
|
|
60
|
+
]
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""Compile one Python rule into the core-WebAssembly artifact a policy pins.
|
|
2
|
+
|
|
3
|
+
python -m archkeep_rule_sdk.build rule.py --out rule.wasm
|
|
4
|
+
|
|
5
|
+
What it does, in order: reads the rule's declaration out of `rule.py`, writes a
|
|
6
|
+
carrier crate into a build directory, runs `cargo build` for
|
|
7
|
+
`wasm32-unknown-unknown`, and copies the resulting module out beside a `.sha256`
|
|
8
|
+
holding its digest — the exact string a `customRules` row's `sha256` field
|
|
9
|
+
takes.
|
|
10
|
+
|
|
11
|
+
## Why there is a Rust toolchain in a Python SDK's build story
|
|
12
|
+
|
|
13
|
+
There is no Python toolchain that emits a core-wasm module with an empty import
|
|
14
|
+
section. Pyodide targets a JavaScript host; componentize-py emits a
|
|
15
|
+
component-model binary over WASI. The engine's host speaks neither and refuses
|
|
16
|
+
a module that declares any import at all, so the Python is CARRIED by a Rust
|
|
17
|
+
crate that embeds an interpreter — `carrier/lib.rs.template` argues that in
|
|
18
|
+
full. `cargo` and the `wasm32-unknown-unknown` target are therefore an
|
|
19
|
+
author-side build requirement, once, and nothing a workspace RUNNING the rule
|
|
20
|
+
needs: a consumer's `check` reads bytes.
|
|
21
|
+
|
|
22
|
+
## What this tool deliberately does not check
|
|
23
|
+
|
|
24
|
+
The rule's declaration — the name's grammar, the finding ids, the catalogue's
|
|
25
|
+
shape — is validated by the carrier crate at COMPILE time, by the SDK's
|
|
26
|
+
`archkeep_rule!` macro. This file checks only that the pieces are there
|
|
27
|
+
(`ARCHKEEP_RULE` exists and states three keys, `evaluate` is callable), because
|
|
28
|
+
the grammar already has an owner on the Rust side and a second copy here would
|
|
29
|
+
be a third statement of one rule (`../../../../AGENTS.md`). What this file adds
|
|
30
|
+
is the FRAME: a cargo failure that came from the declaration is reported as a
|
|
31
|
+
declaration failure naming `rule.py`, not as compiler noise.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import argparse
|
|
35
|
+
import hashlib
|
|
36
|
+
import importlib.util
|
|
37
|
+
import os
|
|
38
|
+
import shutil
|
|
39
|
+
import subprocess
|
|
40
|
+
import sys
|
|
41
|
+
import tempfile
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
#: The build directory's crate name. Fixed rather than derived from the rule
|
|
45
|
+
#: name, because a Cargo package name has its own grammar and mapping one onto
|
|
46
|
+
#: the other would be a second naming rule for a crate nobody publishes.
|
|
47
|
+
CRATE_NAME = "archkeep_rule_carrier"
|
|
48
|
+
|
|
49
|
+
#: The target the artifact is built for. Not `wasm32-wasip1`: WASI is a set of
|
|
50
|
+
#: imports, and the host refuses a module that declares any.
|
|
51
|
+
WASM_TARGET = "wasm32-unknown-unknown"
|
|
52
|
+
|
|
53
|
+
#: `getrandom` has no backend on `wasm32-unknown-unknown` unless one is named.
|
|
54
|
+
#: The carrier defines `__getrandom_v03_custom`; this is what makes the crate
|
|
55
|
+
#: look for it. Passed through `RUSTFLAGS` because a `cfg` is a compiler flag
|
|
56
|
+
#: and cannot be stated in a manifest.
|
|
57
|
+
GETRANDOM_BACKEND_FLAG = '--cfg getrandom_backend="custom"'
|
|
58
|
+
|
|
59
|
+
_CARRIER = Path(__file__).resolve().parent / "carrier"
|
|
60
|
+
|
|
61
|
+
# Reading a rule's declaration means importing the author's `rule.py`, and an
|
|
62
|
+
# import writes a `__pycache__` beside it. A build tool that leaves untracked
|
|
63
|
+
# directories in the tree it was pointed at is a build tool whose output nobody
|
|
64
|
+
# can `git add -A` safely, so this process writes none.
|
|
65
|
+
sys.dont_write_bytecode = True
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class BuildError(Exception):
|
|
69
|
+
"""A build that could not produce an artifact, with the reason named."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def read_declaration(rule_path: Path):
|
|
73
|
+
"""The rule's `ARCHKEEP_RULE` declaration and its `evaluate`, from its source.
|
|
74
|
+
|
|
75
|
+
Loaded by importing the module rather than by parsing it, and the reason is
|
|
76
|
+
that this is the author's own file on the author's own machine: `cargo` is
|
|
77
|
+
about to compile it into a binary either way, so refusing to execute it here
|
|
78
|
+
would buy no safety while costing the only check that catches a rule whose
|
|
79
|
+
module body raises.
|
|
80
|
+
"""
|
|
81
|
+
if not rule_path.is_file():
|
|
82
|
+
raise BuildError(f"{rule_path} is not a file")
|
|
83
|
+
|
|
84
|
+
spec = importlib.util.spec_from_file_location("archkeep_rule_under_build", rule_path)
|
|
85
|
+
if spec is None or spec.loader is None:
|
|
86
|
+
raise BuildError(f"{rule_path} could not be loaded as a Python module")
|
|
87
|
+
module = importlib.util.module_from_spec(spec)
|
|
88
|
+
try:
|
|
89
|
+
spec.loader.exec_module(module)
|
|
90
|
+
# Broad on purpose: this is the author's own module body, which may raise
|
|
91
|
+
# anything at all, and what a caller needs is the file and the reason.
|
|
92
|
+
except Exception as error:
|
|
93
|
+
raise BuildError(f"{rule_path} raised while loading: {type(error).__name__}: {error}")
|
|
94
|
+
|
|
95
|
+
declaration = getattr(module, "ARCHKEEP_RULE", None)
|
|
96
|
+
if not isinstance(declaration, dict):
|
|
97
|
+
raise BuildError(
|
|
98
|
+
f"{rule_path} states no ARCHKEEP_RULE mapping — a rule declares its name, the "
|
|
99
|
+
f"evidence kinds it needs, and its findings catalogue beside the function that "
|
|
100
|
+
f"reads them"
|
|
101
|
+
)
|
|
102
|
+
missing = [key for key in ("name", "needs", "findings") if key not in declaration]
|
|
103
|
+
if missing:
|
|
104
|
+
raise BuildError(
|
|
105
|
+
f"{rule_path}'s ARCHKEEP_RULE states no {', '.join(missing)} — the three keys are "
|
|
106
|
+
f"what the artifact answers archkeep_describe with"
|
|
107
|
+
)
|
|
108
|
+
if not callable(getattr(module, "evaluate", None)):
|
|
109
|
+
raise BuildError(
|
|
110
|
+
f"{rule_path} defines no evaluate(evidence) function — the carrier reads it by name"
|
|
111
|
+
)
|
|
112
|
+
return declaration
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def render_carrier(declaration, rule_path: Path, crate_dir: Path, sdk_dependency: str) -> None:
|
|
116
|
+
"""Writes the carrier crate for one rule into `crate_dir`."""
|
|
117
|
+
source_dir = crate_dir / "src"
|
|
118
|
+
source_dir.mkdir(parents=True, exist_ok=True)
|
|
119
|
+
|
|
120
|
+
manifest = (_CARRIER / "Cargo.toml.template").read_text(encoding="utf-8")
|
|
121
|
+
manifest = manifest.replace("{{CRATE_NAME}}", CRATE_NAME)
|
|
122
|
+
manifest = manifest.replace("{{SDK_DEPENDENCY}}", sdk_dependency)
|
|
123
|
+
(crate_dir / "Cargo.toml").write_text(manifest, encoding="utf-8")
|
|
124
|
+
|
|
125
|
+
lib = (_CARRIER / "lib.rs.template").read_text(encoding="utf-8")
|
|
126
|
+
lib = lib.replace("{{RULE_NAME}}", str(declaration["name"]))
|
|
127
|
+
needs = [str(kind) for kind in declaration["needs"]]
|
|
128
|
+
# Twice, in two spellings. `{{NEEDS}}` is the bare-word list the
|
|
129
|
+
# `archkeep_rule!` macro takes; `{{NEEDS_WIRE}}` is the same kinds as Rust
|
|
130
|
+
# string literals, which the carrier hands the Python runtime so both
|
|
131
|
+
# runners agree on what the rule may read. One list, rendered for the two
|
|
132
|
+
# places that need it — never two lists to keep in step.
|
|
133
|
+
lib = lib.replace("{{NEEDS_WIRE}}", ", ".join(_rust_string(kind) for kind in needs))
|
|
134
|
+
lib = lib.replace("{{NEEDS}}", ", ".join(needs))
|
|
135
|
+
lib = lib.replace("{{FINDINGS}}", _findings_literal(declaration["findings"]))
|
|
136
|
+
(source_dir / "lib.rs").write_text(lib, encoding="utf-8")
|
|
137
|
+
|
|
138
|
+
# The runtime the artifact runs is the file the author's own tests import,
|
|
139
|
+
# copied rather than re-rendered: a template of it would be a second
|
|
140
|
+
# version of the runtime, and the two would agree only until one was edited.
|
|
141
|
+
shutil.copyfile(Path(__file__).resolve().parent / "runtime.py", source_dir / "archkeep_rule_sdk.py")
|
|
142
|
+
shutil.copyfile(rule_path, source_dir / "rule.py")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _findings_literal(findings) -> str:
|
|
146
|
+
"""The catalogue as the Rust literal list `archkeep_rule!` takes.
|
|
147
|
+
|
|
148
|
+
The entries are written as Rust string literals through `json.dumps`-shaped
|
|
149
|
+
escaping done by `repr`-free means: a catalogue message is the author's
|
|
150
|
+
prose and may hold quotes, backslashes or newlines, and a naive f-string
|
|
151
|
+
would produce a crate that does not compile — with the error pointing at
|
|
152
|
+
generated code rather than at the message that caused it.
|
|
153
|
+
"""
|
|
154
|
+
entries = []
|
|
155
|
+
for index, entry in enumerate(findings):
|
|
156
|
+
try:
|
|
157
|
+
id, message = entry["id"], entry["message"]
|
|
158
|
+
except (TypeError, KeyError):
|
|
159
|
+
raise BuildError(
|
|
160
|
+
f"ARCHKEEP_RULE['findings'][{index}] is {entry!r}, and a catalogue entry states "
|
|
161
|
+
f"an id and a message"
|
|
162
|
+
)
|
|
163
|
+
entries.append(f"({_rust_string(id)}, {_rust_string(message)})")
|
|
164
|
+
return ", ".join(entries)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _rust_string(value) -> str:
|
|
168
|
+
"""One Python string as a Rust string literal."""
|
|
169
|
+
if not isinstance(value, str):
|
|
170
|
+
raise BuildError(f"{value!r} is not a string, and a catalogue entry holds two of them")
|
|
171
|
+
escaped = (
|
|
172
|
+
value.replace("\\", "\\\\")
|
|
173
|
+
.replace('"', '\\"')
|
|
174
|
+
.replace("\n", "\\n")
|
|
175
|
+
.replace("\r", "\\r")
|
|
176
|
+
.replace("\t", "\\t")
|
|
177
|
+
)
|
|
178
|
+
return f'"{escaped}"'
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def run_cargo(crate_dir: Path, offline: bool) -> Path:
|
|
182
|
+
"""Builds the carrier and answers the path of the module cargo produced."""
|
|
183
|
+
environment = dict(os.environ)
|
|
184
|
+
flags = environment.get("RUSTFLAGS", "")
|
|
185
|
+
if GETRANDOM_BACKEND_FLAG not in flags:
|
|
186
|
+
environment["RUSTFLAGS"] = f"{flags} {GETRANDOM_BACKEND_FLAG}".strip()
|
|
187
|
+
|
|
188
|
+
command = ["cargo", "build", "--release", "--target", WASM_TARGET]
|
|
189
|
+
if offline:
|
|
190
|
+
command.append("--offline")
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
# An argument list, never a built string: every value in play here comes
|
|
194
|
+
# from a path the author passed in, and a directory named `a;rm -rf .`
|
|
195
|
+
# stops being a name and becomes two commands
|
|
196
|
+
# (`../../../../SECURITY.md`).
|
|
197
|
+
completed = subprocess.run(command, cwd=crate_dir, env=environment, check=False)
|
|
198
|
+
except FileNotFoundError:
|
|
199
|
+
raise BuildError(
|
|
200
|
+
"cargo is not on PATH. Building a Python rule needs the Rust toolchain and the "
|
|
201
|
+
f"{WASM_TARGET} target — `rustup target add {WASM_TARGET}` — because there is no "
|
|
202
|
+
"Python toolchain that emits a core-wasm module with no imports. Running a built "
|
|
203
|
+
"rule needs neither."
|
|
204
|
+
)
|
|
205
|
+
if completed.returncode != 0:
|
|
206
|
+
raise BuildError(
|
|
207
|
+
f"cargo build failed (exit {completed.returncode}). When the failure names "
|
|
208
|
+
"ARCHKEEP_RULE, assert_valid or a const evaluation, it is the rule's declaration "
|
|
209
|
+
"that is wrong — the name's grammar, a duplicate finding id, or an empty "
|
|
210
|
+
"catalogue — and the carrier is where that is checked."
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
artifact = crate_dir / "target" / WASM_TARGET / "release" / f"{CRATE_NAME}.wasm"
|
|
214
|
+
if not artifact.is_file():
|
|
215
|
+
raise BuildError(
|
|
216
|
+
f"cargo reported success and wrote no {artifact.name} — nothing was produced, and "
|
|
217
|
+
"an absent artifact is never read as a built one"
|
|
218
|
+
)
|
|
219
|
+
return artifact
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def build(rule_path: Path, out_path: Path, sdk_dependency: str, keep: Path | None, offline: bool) -> None:
|
|
223
|
+
"""The whole build, from a `rule.py` to a `.wasm` and its digest."""
|
|
224
|
+
declaration = read_declaration(rule_path)
|
|
225
|
+
|
|
226
|
+
if keep is not None:
|
|
227
|
+
keep.mkdir(parents=True, exist_ok=True)
|
|
228
|
+
_build_in(declaration, rule_path, keep, out_path, sdk_dependency, offline)
|
|
229
|
+
return
|
|
230
|
+
with tempfile.TemporaryDirectory(prefix="archkeep-rule-carrier-") as scratch:
|
|
231
|
+
_build_in(declaration, rule_path, Path(scratch), out_path, sdk_dependency, offline)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _build_in(declaration, rule_path, crate_dir, out_path, sdk_dependency, offline) -> None:
|
|
235
|
+
render_carrier(declaration, rule_path, crate_dir, sdk_dependency)
|
|
236
|
+
artifact = run_cargo(crate_dir, offline)
|
|
237
|
+
|
|
238
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
239
|
+
shutil.copyfile(artifact, out_path)
|
|
240
|
+
digest = hashlib.sha256(out_path.read_bytes()).hexdigest()
|
|
241
|
+
# Bare lowercase hex and a newline, nothing else: this string is pasted
|
|
242
|
+
# verbatim into a `customRules` row's `sha256` field, which is 64 hex
|
|
243
|
+
# characters with no filename beside it.
|
|
244
|
+
out_path.with_suffix(out_path.suffix + ".sha256").write_text(digest + "\n", encoding="utf-8")
|
|
245
|
+
|
|
246
|
+
print(
|
|
247
|
+
f"built {out_path} ({out_path.stat().st_size} bytes) for rule "
|
|
248
|
+
f"\"{declaration['name']}\", digest {digest}"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def main(argv=None) -> int:
|
|
253
|
+
parser = argparse.ArgumentParser(
|
|
254
|
+
prog="python -m archkeep_rule_sdk.build",
|
|
255
|
+
description="Compile a Python Archkeep rule into a core-WebAssembly artifact.",
|
|
256
|
+
)
|
|
257
|
+
parser.add_argument("rule", type=Path, help="the rule's Python source")
|
|
258
|
+
parser.add_argument(
|
|
259
|
+
"--out",
|
|
260
|
+
type=Path,
|
|
261
|
+
required=True,
|
|
262
|
+
help="where to write the .wasm; a .wasm.sha256 is written beside it",
|
|
263
|
+
)
|
|
264
|
+
parser.add_argument(
|
|
265
|
+
"--sdk-path",
|
|
266
|
+
type=Path,
|
|
267
|
+
default=None,
|
|
268
|
+
help=(
|
|
269
|
+
"build against a local checkout of the Rust SDK crate instead of the published "
|
|
270
|
+
"one — how this repository builds its own reference artifact"
|
|
271
|
+
),
|
|
272
|
+
)
|
|
273
|
+
parser.add_argument(
|
|
274
|
+
"--keep-crate",
|
|
275
|
+
type=Path,
|
|
276
|
+
default=None,
|
|
277
|
+
help=(
|
|
278
|
+
"write the generated carrier crate here and leave it behind, so cargo's "
|
|
279
|
+
"incremental cache survives between builds"
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
parser.add_argument(
|
|
283
|
+
"--offline",
|
|
284
|
+
action="store_true",
|
|
285
|
+
help="pass --offline to cargo, for a build with no registry access",
|
|
286
|
+
)
|
|
287
|
+
arguments = parser.parse_args(argv)
|
|
288
|
+
|
|
289
|
+
if arguments.sdk_path is None:
|
|
290
|
+
# The published crate, at this package's own version: the SDKs and the
|
|
291
|
+
# engine share one version chain (`../../../../docs/skills/versioning.md`),
|
|
292
|
+
# so a rule built by this tool is built against the binding that shipped
|
|
293
|
+
# with it.
|
|
294
|
+
sdk_dependency = f'"{_own_version()}"'
|
|
295
|
+
else:
|
|
296
|
+
sdk_dependency = f'{{ path = "{arguments.sdk_path.resolve().as_posix()}" }}'
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
build(
|
|
300
|
+
arguments.rule.resolve(),
|
|
301
|
+
arguments.out.resolve(),
|
|
302
|
+
sdk_dependency,
|
|
303
|
+
arguments.keep_crate.resolve() if arguments.keep_crate else None,
|
|
304
|
+
arguments.offline,
|
|
305
|
+
)
|
|
306
|
+
except BuildError as error:
|
|
307
|
+
print(f"archkeep: {error}", file=sys.stderr)
|
|
308
|
+
return 1
|
|
309
|
+
return 0
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _own_version() -> str:
|
|
313
|
+
"""This package's version, read from the metadata that installed it.
|
|
314
|
+
|
|
315
|
+
Falls back loudly rather than to a literal: a version guessed here would
|
|
316
|
+
pin a rule's binding to a crate that may not exist, and the failure would
|
|
317
|
+
arrive as an unresolvable dependency with no clue where the number came
|
|
318
|
+
from.
|
|
319
|
+
"""
|
|
320
|
+
try:
|
|
321
|
+
from importlib.metadata import version
|
|
322
|
+
|
|
323
|
+
return version("archkeep-rule-sdk")
|
|
324
|
+
# Broad on purpose: importlib.metadata raises several unrelated types when
|
|
325
|
+
# a distribution is not installed, and every one of them means the same
|
|
326
|
+
# thing here — the version to build against is not knowable.
|
|
327
|
+
except Exception as error:
|
|
328
|
+
raise BuildError(
|
|
329
|
+
"this package's own version could not be read from its installed metadata "
|
|
330
|
+
f"({type(error).__name__}: {error}), so the crate version to build against is "
|
|
331
|
+
"unknown — pass --sdk-path to build against a checkout instead"
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
if __name__ == "__main__":
|
|
336
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# GENERATED by `python -m archkeep_rule_sdk.build` — do not edit.
|
|
2
|
+
#
|
|
3
|
+
# The carrier crate for one Python rule. It is not published and not depended
|
|
4
|
+
# on: it exists for the length of one `cargo build`, and its output is the
|
|
5
|
+
# `.wasm` a workspace pins by digest.
|
|
6
|
+
#
|
|
7
|
+
# Three dependencies, and each is load-bearing:
|
|
8
|
+
#
|
|
9
|
+
# archkeep-rule-sdk the contract binding — the typed evidence model, the four
|
|
10
|
+
# verdict constructors, and the `archkeep_rule!` macro that
|
|
11
|
+
# writes the ABI exports. The Python carrier is a client of
|
|
12
|
+
# the Rust one rather than a second implementation of it,
|
|
13
|
+
# so the two SDKs cannot disagree about the wire.
|
|
14
|
+
# rustpython-vm the interpreter, with `default-features = false`. The
|
|
15
|
+
# default set includes `wasmbind`, which pulls
|
|
16
|
+
# `wasm-bindgen` and would put JavaScript imports in the
|
|
17
|
+
# module's import section — the one thing the host refuses
|
|
18
|
+
# outright. `compiler` is kept because the carrier compiles
|
|
19
|
+
# the author's source at startup; it is what makes the
|
|
20
|
+
# artifact reproducible from the `.py` in the tree rather
|
|
21
|
+
# than from bytecode nobody can read.
|
|
22
|
+
# getrandom named explicitly so the custom backend in `src/lib.rs`
|
|
23
|
+
# resolves. The build passes
|
|
24
|
+
# `--cfg getrandom_backend="custom"`; without it the crate
|
|
25
|
+
# has no backend on `wasm32-unknown-unknown` and fails to
|
|
26
|
+
# compile with a message about enabling one.
|
|
27
|
+
#
|
|
28
|
+
# `serde_json` is here because the carrier converts between JSON values and
|
|
29
|
+
# Python objects in both directions. It is the same version the SDK crate
|
|
30
|
+
# resolves, so the `Value` in one is the `Value` in the other.
|
|
31
|
+
[package]
|
|
32
|
+
name = "{{CRATE_NAME}}"
|
|
33
|
+
version = "0.0.0"
|
|
34
|
+
edition = "2024"
|
|
35
|
+
# The floor the SDK crate sets, restated because a carrier is compiled by the
|
|
36
|
+
# author's own toolchain and the failure is clearer here than inside a
|
|
37
|
+
# dependency.
|
|
38
|
+
rust-version = "1.85"
|
|
39
|
+
publish = false
|
|
40
|
+
|
|
41
|
+
[dependencies]
|
|
42
|
+
archkeep-rule-sdk = {{SDK_DEPENDENCY}}
|
|
43
|
+
rustpython-vm = { version = "0.5.0", default-features = false, features = ["compiler"] }
|
|
44
|
+
getrandom = "0.3"
|
|
45
|
+
serde_json = "1"
|
|
46
|
+
|
|
47
|
+
[lib]
|
|
48
|
+
# A cdylib, which is what makes rustc's `wasm32-unknown-unknown` linkage export
|
|
49
|
+
# the module's linear memory under the name `memory` — the fourth ABI export,
|
|
50
|
+
# the one no macro writes.
|
|
51
|
+
crate-type = ["cdylib"]
|
|
52
|
+
|
|
53
|
+
# The profile the artifact is built with, stated here rather than in the build
|
|
54
|
+
# command so that a rebuild by hand produces what the build tool produces.
|
|
55
|
+
#
|
|
56
|
+
# `panic = "abort"` is a contract requirement rather than a size tweak:
|
|
57
|
+
# unwinding across the ABI's `extern "C"` boundary is undefined behaviour, so a
|
|
58
|
+
# panic must trap instead — and a trap is a failure the host NAMES rather than a
|
|
59
|
+
# verdict it invents.
|
|
60
|
+
#
|
|
61
|
+
# The size options matter more here than they do for a Rust rule, and for the
|
|
62
|
+
# same reason: the artifact is committed to the workspace that declares it and
|
|
63
|
+
# pinned by a sha256 in the policy row. An interpreter is large no matter what
|
|
64
|
+
# is done to it; these options are what keep "large" at single-digit megabytes.
|
|
65
|
+
[profile.release]
|
|
66
|
+
panic = "abort"
|
|
67
|
+
opt-level = "z"
|
|
68
|
+
lto = true
|
|
69
|
+
codegen-units = 1
|
|
70
|
+
strip = true
|