codecalc 0.1.0__py3-none-win_amd64.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.
- codecalc/SKILL.md +87 -0
- codecalc/__init__.py +3 -0
- codecalc/__main__.py +19 -0
- codecalc/_worker_bootstrap.py +211 -0
- codecalc/bin/codecalc-exec.exe +0 -0
- codecalc/complexity.py +162 -0
- codecalc/contract.py +624 -0
- codecalc/doctor.py +428 -0
- codecalc/errors.py +214 -0
- codecalc/exact.py +969 -0
- codecalc/execution_service.py +302 -0
- codecalc/executor.py +1324 -0
- codecalc/guarded.py +300 -0
- codecalc/landlock.py +231 -0
- codecalc/logic.py +500 -0
- codecalc/mcp_middleware.py +135 -0
- codecalc/optimization.py +296 -0
- codecalc/optional.py +107 -0
- codecalc/packages.py +360 -0
- codecalc/parsing.py +266 -0
- codecalc/provider_adapters/__init__.py +1 -0
- codecalc/provider_adapters/piston.py +34 -0
- codecalc/providers.py +802 -0
- codecalc/py.typed +0 -0
- codecalc/registry.py +254 -0
- codecalc/run_supervisor.py +178 -0
- codecalc/runtimes.py +542 -0
- codecalc/safe_expr.py +346 -0
- codecalc/server.py +1103 -0
- codecalc/sessions.py +1060 -0
- codecalc/strict_runtime.py +232 -0
- codecalc/tools.py +326 -0
- codecalc/translation.py +286 -0
- codecalc/units.py +410 -0
- codecalc-0.1.0.dist-info/METADATA +838 -0
- codecalc-0.1.0.dist-info/RECORD +39 -0
- codecalc-0.1.0.dist-info/WHEEL +4 -0
- codecalc-0.1.0.dist-info/entry_points.txt +2 -0
- codecalc-0.1.0.dist-info/licenses/LICENSE +201 -0
codecalc/contract.py
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
"""The published result contract: one version number, one schema (THE-781).
|
|
2
|
+
|
|
3
|
+
Slices 1 and 2 built the contract. Slice 1 (#120) made the two backends agree
|
|
4
|
+
on a key set and gated it. Slice 2 (#121) replaced 121 free-form error strings
|
|
5
|
+
with eight stable codes. Neither of them published anything: a caller could
|
|
6
|
+
observe the shape by running the thing, and had no document to program against
|
|
7
|
+
and no way to tell which shape a given server speaks.
|
|
8
|
+
|
|
9
|
+
This module is that document, kept next to the code that produces the shape so
|
|
10
|
+
the two cannot drift silently — `scripts/check_contract.py` regenerates
|
|
11
|
+
`docs/contract/result-v1.schema.json` from here and fails on a diff.
|
|
12
|
+
|
|
13
|
+
WHAT IS MEASURED, NOT ASSUMED
|
|
14
|
+
Every vocabulary below was read out of the two backends rather than designed:
|
|
15
|
+
|
|
16
|
+
* `VERDICTS` is the union of `verdict()` in executor/src/main.rs:840 and
|
|
17
|
+
`_fallback_verdict()` in executor.py:617. They are NOT the same set. The
|
|
18
|
+
fallback cannot emit MLE, and says so in its own docstring: the Rust path
|
|
19
|
+
infers MLE from a signal plus an RSS reading near the cap, and the fallback
|
|
20
|
+
has neither, so an OOM there lands in RTE. That asymmetry is part of the
|
|
21
|
+
contract and is written down rather than smoothed over.
|
|
22
|
+
* `PHASES` is `compile` and `run`, the only two values either backend emits.
|
|
23
|
+
* The envelope's required keys are the 19 that `check_parity` proves both
|
|
24
|
+
backends return, measured by running both.
|
|
25
|
+
|
|
26
|
+
TWO SHAPES, BECAUSE THERE ARE TWO SHAPES
|
|
27
|
+
A request that reaches a runtime comes back as the full envelope. A request
|
|
28
|
+
rejected BEFORE execution — an unknown language, for instance — comes back from
|
|
29
|
+
the Rust binary as `{"ok": false, "error": ...}` and nothing else. Measured:
|
|
30
|
+
|
|
31
|
+
executor.execute("nosuchlang", "x") -> keys ['backend', 'error', 'ok']
|
|
32
|
+
|
|
33
|
+
Writing a schema that requires all 19 keys everywhere would have been a
|
|
34
|
+
document that the product fails, and the honest fix is to say there are two
|
|
35
|
+
shapes and when each applies — not to pad the short one with nulls so a
|
|
36
|
+
validator stops complaining. A caller distinguishes them the same way the
|
|
37
|
+
schema does: `verdict` is present exactly when the code ran.
|
|
38
|
+
|
|
39
|
+
WHY THE VERSION IS EMITTED AND NOT JUST DOCUMENTED
|
|
40
|
+
A version nobody can read is not a version. `stamp()` puts `contract_version`
|
|
41
|
+
on every result at the single point both backends pass through, so a client can
|
|
42
|
+
branch on the contract it is actually being served rather than on the contract
|
|
43
|
+
it hoped was deployed.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
from __future__ import annotations
|
|
47
|
+
|
|
48
|
+
from . import errors
|
|
49
|
+
|
|
50
|
+
#: The contract's own version, semver. See docs/contract/README.md for what
|
|
51
|
+
#: each component is allowed to change — the short form is that MAJOR is the
|
|
52
|
+
#: only one that may break a reader, and it carries a twelve-month deprecation
|
|
53
|
+
#: window before anything is removed.
|
|
54
|
+
CONTRACT_VERSION = "1.0.0"
|
|
55
|
+
|
|
56
|
+
# THE `$schema` AND `$id` URIs ARE NOT HERE ON PURPOSE.
|
|
57
|
+
#
|
|
58
|
+
# `tests/test_offline.py` bans every public URL literal from this package. That
|
|
59
|
+
# ban is why codecalc has no LLM client and no docs fetch, and the two URIs a
|
|
60
|
+
# published JSON Schema wants — the 2020-12 dialect and the document's own `$id`
|
|
61
|
+
# — would be the first exceptions to it.
|
|
62
|
+
#
|
|
63
|
+
# They are identifiers, not endpoints: nothing here fetches either one, and MCP
|
|
64
|
+
# 2026-07-28 tells implementations they MUST NOT auto-dereference external `$ref`
|
|
65
|
+
# URIs. But "it is only an identifier" is exactly how a fetch arrives a year
|
|
66
|
+
# later, and the gate is worth more than the convenience of holding two strings
|
|
67
|
+
# in the package that ships.
|
|
68
|
+
#
|
|
69
|
+
# So `scripts/check_contract.py` owns them and injects them when it generates
|
|
70
|
+
# the document. The wheel contains no URL at all; the published file is
|
|
71
|
+
# complete. Omitting `$schema` from the in-package dict is also correct rather
|
|
72
|
+
# than merely tolerable: MCP defaults `outputSchema` to 2020-12 when no
|
|
73
|
+
# `$schema` is present, so this dict remains a valid output schema as-is.
|
|
74
|
+
|
|
75
|
+
#: Run classification. The union of both backends; see the module docstring.
|
|
76
|
+
VERDICTS = ("OK", "TLE", "OLE", "MLE", "RTE")
|
|
77
|
+
|
|
78
|
+
#: Verdicts only the native executor can produce. Not a defect and not a gap to
|
|
79
|
+
#: close: the fallback declines to guess MLE rather than reporting a ceiling it
|
|
80
|
+
#: did not measure, which is the same rule `unenforced` exists to serve.
|
|
81
|
+
NATIVE_ONLY_VERDICTS = ("MLE",)
|
|
82
|
+
|
|
83
|
+
#: Which step produced the result.
|
|
84
|
+
PHASES = ("compile", "run")
|
|
85
|
+
|
|
86
|
+
#: Which executor answered a full-envelope result.
|
|
87
|
+
BACKENDS = ("rust", "python")
|
|
88
|
+
|
|
89
|
+
#: Sessions are a THIRD backend and were missed by the first version of this
|
|
90
|
+
#: module, which asserted `executor.execute` was the one place every execution
|
|
91
|
+
#: result passes through. It is not: `execute_code(session_id=...)` routes to a
|
|
92
|
+
#: warm worker in sessions.py, never touches `executor.execute`, and returns ten
|
|
93
|
+
#: keys with `backend="session-worker"` — a value the first published enum did
|
|
94
|
+
#: not contain, so every session result would have failed validation against the
|
|
95
|
+
#: document that claimed to describe it. Found by cross-vendor review.
|
|
96
|
+
SESSION_BACKEND = "session-worker"
|
|
97
|
+
|
|
98
|
+
#: The keys `check_parity.py` proves both backends return. Adding one is a MINOR
|
|
99
|
+
#: change; removing one is MAJOR.
|
|
100
|
+
ENVELOPE_KEYS = (
|
|
101
|
+
"ok", "language", "phase", "backend", "platform",
|
|
102
|
+
"stdout", "stderr", "exit_code", "timed_out",
|
|
103
|
+
"verdict", "output_truncated", "output_error",
|
|
104
|
+
"duration_ms", "compile_ms", "total_ms", "cpu_ms", "peak_memory_kb",
|
|
105
|
+
"unenforced", "workdir",
|
|
106
|
+
"stdout_bytes", "stderr_bytes",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _error_properties() -> dict:
|
|
111
|
+
"""The error half. `code`'s enum is generated from errors.ALL_CODES.
|
|
112
|
+
|
|
113
|
+
Generated rather than transcribed: a hand-copied enum is a second place the
|
|
114
|
+
taxonomy lives, and the two would agree right up until someone added a
|
|
115
|
+
ninth code. `scripts/check_contract.py` re-derives this and diffs it, so a
|
|
116
|
+
new code that never reached the schema is a failing gate rather than a
|
|
117
|
+
published document that quietly lies.
|
|
118
|
+
"""
|
|
119
|
+
return {
|
|
120
|
+
"ok": {"const": False},
|
|
121
|
+
"code": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"enum": sorted(errors.ALL_CODES),
|
|
124
|
+
"description": (
|
|
125
|
+
"Stable machine-readable failure category. A caller MAY branch "
|
|
126
|
+
"on this. An unrecognised value MUST be treated as 'internal' "
|
|
127
|
+
"rather than as a protocol violation — that is what makes "
|
|
128
|
+
"adding a code a MINOR change."
|
|
129
|
+
),
|
|
130
|
+
},
|
|
131
|
+
"error": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"description": (
|
|
134
|
+
"Human-facing explanation. NOT stable and NOT a branch target: "
|
|
135
|
+
"the whole point of `code` is that this text is free to improve."
|
|
136
|
+
),
|
|
137
|
+
},
|
|
138
|
+
"remedy": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"description": "What the caller can do about it, one line.",
|
|
141
|
+
},
|
|
142
|
+
"code_inferred": {
|
|
143
|
+
"type": "boolean",
|
|
144
|
+
"description": (
|
|
145
|
+
"True when the code was derived from the message text rather "
|
|
146
|
+
"than chosen where the failure was raised. A weaker claim, "
|
|
147
|
+
"marked as such. Absent means the code came from the raise site."
|
|
148
|
+
),
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def build_schema(dialect: str | None = None, schema_id: str | None = None) -> dict:
|
|
154
|
+
"""The published schema, as a dict. Single source of truth.
|
|
155
|
+
|
|
156
|
+
`dialect` and `schema_id` are injected by `scripts/check_contract.py` rather
|
|
157
|
+
than defaulted here — see the note above on why no URL literal lives in this
|
|
158
|
+
package. Called with neither, the result is the same document without its
|
|
159
|
+
two identifier keys, which is still a valid 2020-12 output schema.
|
|
160
|
+
|
|
161
|
+
`additionalProperties` is left open deliberately. Forward compatibility is
|
|
162
|
+
the reason: adding a field has to be a MINOR change, and a closed schema
|
|
163
|
+
would make every addition breaking for any client that validates strictly.
|
|
164
|
+
The rule that pairs with it — clients MUST ignore fields they do not know —
|
|
165
|
+
is stated in the docs and in the descriptions below.
|
|
166
|
+
"""
|
|
167
|
+
identifiers = {}
|
|
168
|
+
if dialect:
|
|
169
|
+
identifiers["$schema"] = dialect
|
|
170
|
+
if schema_id:
|
|
171
|
+
identifiers["$id"] = schema_id
|
|
172
|
+
return {
|
|
173
|
+
**identifiers,
|
|
174
|
+
"title": "codecalc execution result",
|
|
175
|
+
"description": (
|
|
176
|
+
f"The result contract for codecalc's execution tools, version "
|
|
177
|
+
f"{CONTRACT_VERSION}. Generated from codecalc/contract.py by "
|
|
178
|
+
f"scripts/check_contract.py — edit the module, not this file."
|
|
179
|
+
),
|
|
180
|
+
"type": "object",
|
|
181
|
+
"required": ["ok", "contract_version"],
|
|
182
|
+
"properties": {
|
|
183
|
+
"contract_version": {
|
|
184
|
+
"type": "string",
|
|
185
|
+
"pattern": r"^\d+\.\d+\.\d+$",
|
|
186
|
+
"description": (
|
|
187
|
+
"Semver of this contract, as served. MAJOR is the only "
|
|
188
|
+
"component that may break a reader."
|
|
189
|
+
),
|
|
190
|
+
},
|
|
191
|
+
"ok": {
|
|
192
|
+
"type": "boolean",
|
|
193
|
+
"description": (
|
|
194
|
+
"True only when the code ran and exited 0. A program that "
|
|
195
|
+
"runs correctly and exits non-zero is ok=false with "
|
|
196
|
+
"exit_code set and verdict=RTE — measured: exit(3) returns "
|
|
197
|
+
"ok=false, exit_code=3, verdict=RTE. So `ok` does NOT "
|
|
198
|
+
"separate 'codecalc worked' from 'your program worked'; "
|
|
199
|
+
"read `verdict` and `exit_code` for that, and `code` to "
|
|
200
|
+
"tell a rejected request from a failed run."
|
|
201
|
+
),
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
# FOUR shapes, discriminated so that exactly one branch can match:
|
|
205
|
+
#
|
|
206
|
+
# envelope verdict present, backend in (rust, python)
|
|
207
|
+
# session backend == "session-worker"
|
|
208
|
+
# compact verdict present, NO backend key
|
|
209
|
+
# rejected no verdict at all
|
|
210
|
+
#
|
|
211
|
+
# The first version of this schema had two branches and asserted that
|
|
212
|
+
# `executor.execute` was the single choke point for every execution
|
|
213
|
+
# result. Cross-vendor review disproved it in three places at once:
|
|
214
|
+
# `compact_result` rebuilds a four-key result, the native streaming path
|
|
215
|
+
# returns raw executor JSON, and sessions bypass the function entirely.
|
|
216
|
+
# All three produced results that NEITHER branch accepted — a published
|
|
217
|
+
# document that the product fails, which is worse than no document.
|
|
218
|
+
"oneOf": [
|
|
219
|
+
{"$ref": "#/$defs/execution_envelope"},
|
|
220
|
+
{"$ref": "#/$defs/session_result"},
|
|
221
|
+
{"$ref": "#/$defs/compact_result"},
|
|
222
|
+
{"$ref": "#/$defs/rejected"},
|
|
223
|
+
],
|
|
224
|
+
"$defs": {
|
|
225
|
+
"execution_envelope": {
|
|
226
|
+
"title": "a request that reached a runtime",
|
|
227
|
+
"description": (
|
|
228
|
+
"Returned when the code was handed to a language runtime, "
|
|
229
|
+
"whether or not it then succeeded. Both backends return "
|
|
230
|
+
"every key listed here; scripts/check_parity.py runs both "
|
|
231
|
+
"and diffs the key sets."
|
|
232
|
+
),
|
|
233
|
+
"type": "object",
|
|
234
|
+
"required": list(ENVELOPE_KEYS),
|
|
235
|
+
"properties": {
|
|
236
|
+
"ok": {"type": "boolean"},
|
|
237
|
+
"language": {"type": "string"},
|
|
238
|
+
"phase": {"type": "string", "enum": list(PHASES)},
|
|
239
|
+
"backend": {
|
|
240
|
+
"type": "string", "enum": list(BACKENDS),
|
|
241
|
+
"description": (
|
|
242
|
+
"Which executor answered. 'python' is the pure-"
|
|
243
|
+
"Python fallback and enforces strictly less; read "
|
|
244
|
+
"`unenforced` for what it could not apply."
|
|
245
|
+
),
|
|
246
|
+
},
|
|
247
|
+
"platform": {"type": "string"},
|
|
248
|
+
"stdout": {"type": "string"},
|
|
249
|
+
"stderr": {"type": "string"},
|
|
250
|
+
"exit_code": {
|
|
251
|
+
"type": ["integer", "null"],
|
|
252
|
+
"description": "null when the process was killed rather than exiting.",
|
|
253
|
+
},
|
|
254
|
+
"timed_out": {"type": "boolean"},
|
|
255
|
+
"verdict": {
|
|
256
|
+
"type": "string", "enum": list(VERDICTS),
|
|
257
|
+
"description": (
|
|
258
|
+
"OK ran and exited 0 · TLE wall-clock kill · OLE "
|
|
259
|
+
"output over cap · MLE memory ceiling · RTE nonzero "
|
|
260
|
+
"exit or signal. MLE is native-only: the fallback "
|
|
261
|
+
"cannot measure it and reports RTE instead of "
|
|
262
|
+
"guessing."
|
|
263
|
+
),
|
|
264
|
+
},
|
|
265
|
+
"output_truncated": {
|
|
266
|
+
"type": "boolean",
|
|
267
|
+
"description": (
|
|
268
|
+
"True when stdout/stderr was cut at the cap. Slice 1 "
|
|
269
|
+
"(#120) exists because the Rust backend computed "
|
|
270
|
+
"this, raised the OLE verdict from it, and did not "
|
|
271
|
+
"emit it, while the fallback did."
|
|
272
|
+
),
|
|
273
|
+
},
|
|
274
|
+
"output_error": {
|
|
275
|
+
"type": ["string", "null"],
|
|
276
|
+
"description": (
|
|
277
|
+
"Why output could not be read, when it could not. "
|
|
278
|
+
"null is 'no problem'; a string here means the "
|
|
279
|
+
"output is unreliable even if stdout looks fine."
|
|
280
|
+
),
|
|
281
|
+
},
|
|
282
|
+
"stdout_bytes": {
|
|
283
|
+
"type": ["integer", "null"],
|
|
284
|
+
"minimum": 0,
|
|
285
|
+
"description": (
|
|
286
|
+
"Bytes of stdout OBSERVED before the response cap "
|
|
287
|
+
"was applied. When `output_truncated` is false this "
|
|
288
|
+
"is exact and both backends agree. When it is true "
|
|
289
|
+
"this is a LOWER BOUND on what the program would "
|
|
290
|
+
"have produced, and the two backends can differ: "
|
|
291
|
+
"the fallback kills the process as soon as its "
|
|
292
|
+
"drain crosses the cap, while the native executor "
|
|
293
|
+
"lets it run on under a separate file-size ceiling. "
|
|
294
|
+
"Measured on one 200 KiB writer at a 1 KiB cap: "
|
|
295
|
+
"native 204800, fallback 65536. Useful for sizing a "
|
|
296
|
+
"retry — raise `max_output_kb` above this — but not "
|
|
297
|
+
"a promise about total output. null means not "
|
|
298
|
+
"measured, never zero; a program that printed "
|
|
299
|
+
"nothing reports 0."
|
|
300
|
+
),
|
|
301
|
+
},
|
|
302
|
+
"stderr_bytes": {
|
|
303
|
+
"type": ["integer", "null"],
|
|
304
|
+
"minimum": 0,
|
|
305
|
+
"description": "As stdout_bytes, for stderr.",
|
|
306
|
+
},
|
|
307
|
+
"duration_ms": {"type": "integer", "minimum": 0},
|
|
308
|
+
"compile_ms": {"type": "integer", "minimum": 0},
|
|
309
|
+
"total_ms": {"type": "integer", "minimum": 0},
|
|
310
|
+
"cpu_ms": {"type": "integer", "minimum": 0},
|
|
311
|
+
"peak_memory_kb": {
|
|
312
|
+
"type": ["integer", "null"],
|
|
313
|
+
"minimum": 0,
|
|
314
|
+
"description": (
|
|
315
|
+
"Peak resident memory, or null when this host could "
|
|
316
|
+
"not attribute it to this run. The pure-Python "
|
|
317
|
+
"fallback always returns null and names the reason "
|
|
318
|
+
"in `unenforced`: ru_maxrss is a high-water mark "
|
|
319
|
+
"for the whole process. null is 'not measured', "
|
|
320
|
+
"never 'measured as zero'."
|
|
321
|
+
),
|
|
322
|
+
},
|
|
323
|
+
"unenforced": {
|
|
324
|
+
"type": "array",
|
|
325
|
+
"items": {"type": "string"},
|
|
326
|
+
"description": (
|
|
327
|
+
"Bounds this host could not apply, named. An empty "
|
|
328
|
+
"array is a claim that everything requested was "
|
|
329
|
+
"enforced; a non-empty one is the sandbox telling "
|
|
330
|
+
"you what it did not do."
|
|
331
|
+
),
|
|
332
|
+
},
|
|
333
|
+
"workdir": {"type": "string"},
|
|
334
|
+
**{k: v for k, v in _error_properties().items() if k != "ok"},
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
"session_result": {
|
|
338
|
+
"title": "a result from a warm session worker",
|
|
339
|
+
"description": (
|
|
340
|
+
"`execute_code(session_id=...)` runs in a persistent worker "
|
|
341
|
+
"rather than a fresh sandbox, so the fields a fresh run "
|
|
342
|
+
"reports about ITS environment — language, platform, "
|
|
343
|
+
"workdir, per-run timings — do not apply and are absent "
|
|
344
|
+
"rather than faked. `unenforced` is still required and is "
|
|
345
|
+
"the field that matters most here: a worker cannot apply "
|
|
346
|
+
"several ceilings a fresh sandbox can, and it says so."
|
|
347
|
+
),
|
|
348
|
+
# `stdout`/`stderr`/`exit_code`/`output_truncated`/`session` are
|
|
349
|
+
# NOT required, because a session call whose worker DIED never
|
|
350
|
+
# ran the code and has none of them: it returns verdict RTE,
|
|
351
|
+
# backend session-worker, `error`, a worker_failure code and the
|
|
352
|
+
# disclosure list, and nothing else.
|
|
353
|
+
#
|
|
354
|
+
# Found by CI, on a runner where the worker could not start,
|
|
355
|
+
# after both a local suite and a cross-vendor review had passed.
|
|
356
|
+
# Neither reached it because both ran where sessions work. What
|
|
357
|
+
# `unenforced` and `verdict` being required buys is that a
|
|
358
|
+
# session result still cannot omit its disclosures.
|
|
359
|
+
"type": "object",
|
|
360
|
+
"required": ["ok", "verdict", "backend", "unenforced"],
|
|
361
|
+
"properties": {
|
|
362
|
+
"ok": {"type": "boolean"},
|
|
363
|
+
"backend": {"const": SESSION_BACKEND},
|
|
364
|
+
# A boolean flag meaning "this ran in a session", NOT the
|
|
365
|
+
# session id. Written as a string first, from the field's
|
|
366
|
+
# name rather than from its value; the validator caught it.
|
|
367
|
+
"session": {"type": "boolean"},
|
|
368
|
+
"confined": {"type": "boolean"},
|
|
369
|
+
"verdict": {"type": "string", "enum": list(VERDICTS)},
|
|
370
|
+
"stdout": {"type": "string"},
|
|
371
|
+
"stderr": {"type": "string"},
|
|
372
|
+
"exit_code": {"type": ["integer", "null"]},
|
|
373
|
+
"output_truncated": {"type": "boolean"},
|
|
374
|
+
"unenforced": {"type": "array", "items": {"type": "string"}},
|
|
375
|
+
**{k: v for k, v in _error_properties().items() if k != "ok"},
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
"compact_result": {
|
|
379
|
+
"title": "a deliberately small result",
|
|
380
|
+
"description": (
|
|
381
|
+
"What `execute_code(compact=True)` returns. Diagnostics a "
|
|
382
|
+
"caller can live without are dropped to save tokens; "
|
|
383
|
+
"`unenforced` and `output_error` are NOT droppable and "
|
|
384
|
+
"appear whenever they say anything, which is the whole "
|
|
385
|
+
"difference between this and the version of compact mode "
|
|
386
|
+
"that was a defect (#117). No `backend` key, which is what "
|
|
387
|
+
"distinguishes this branch from the full envelope."
|
|
388
|
+
),
|
|
389
|
+
"type": "object",
|
|
390
|
+
"required": ["ok", "verdict", "stdout", "exit_code"],
|
|
391
|
+
"not": {"required": ["backend"]},
|
|
392
|
+
"properties": {
|
|
393
|
+
"ok": {"type": "boolean"},
|
|
394
|
+
"verdict": {"type": "string", "enum": list(VERDICTS)},
|
|
395
|
+
"stdout": {"type": "string"},
|
|
396
|
+
"exit_code": {"type": ["integer", "null"]},
|
|
397
|
+
"unenforced": {"type": "array", "items": {"type": "string"}},
|
|
398
|
+
"output_error": {"type": ["string", "null"]},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
"rejected": {
|
|
402
|
+
"title": "a request rejected before execution",
|
|
403
|
+
"description": (
|
|
404
|
+
"Returned when nothing ran — an unknown language, a "
|
|
405
|
+
"malformed request, an executor that could not start. "
|
|
406
|
+
"Measured: executor.execute('nosuchlang', 'x') returns "
|
|
407
|
+
"exactly ok/error/backend. There is no verdict because "
|
|
408
|
+
"there was no run to classify."
|
|
409
|
+
),
|
|
410
|
+
"type": "object",
|
|
411
|
+
"required": ["ok", "error"],
|
|
412
|
+
"properties": _error_properties(),
|
|
413
|
+
"not": {"required": ["verdict"]},
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
#: Built once. The schema is a constant; rebuilding it per call would only add
|
|
420
|
+
#: a way for two callers to see different documents.
|
|
421
|
+
RESULT_SCHEMA = build_schema()
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def build_doctor_schema(dialect: str | None = None, schema_id: str | None = None) -> dict:
|
|
425
|
+
"""The schema for `codecalc doctor --json` (THE-780).
|
|
426
|
+
|
|
427
|
+
Under THIS contract's version and policy rather than a third number of its
|
|
428
|
+
own. The doctor payload is machine-readable output a client programs
|
|
429
|
+
against, which is exactly what the policy in docs/contract/README.md is
|
|
430
|
+
for; a separate `doctor_schema_version` would mean two documents to keep in
|
|
431
|
+
step, two deprecation windows, and two chances to forget one.
|
|
432
|
+
|
|
433
|
+
`runtimes[].status` is a CLOSED enum, so by that policy adding a fifth state
|
|
434
|
+
is a MAJOR change — the same rule `code` and `verdict` follow, for the same
|
|
435
|
+
reason: a strictly validating client rejects an unknown member before any
|
|
436
|
+
application logic can degrade it.
|
|
437
|
+
"""
|
|
438
|
+
from .doctor import RUNTIME_STATES
|
|
439
|
+
|
|
440
|
+
identifiers = {}
|
|
441
|
+
if dialect:
|
|
442
|
+
identifiers["$schema"] = dialect
|
|
443
|
+
if schema_id:
|
|
444
|
+
identifiers["$id"] = schema_id
|
|
445
|
+
return {
|
|
446
|
+
**identifiers,
|
|
447
|
+
"title": "codecalc doctor report",
|
|
448
|
+
"description": (
|
|
449
|
+
f"`codecalc doctor --json`, version {CONTRACT_VERSION} — the same "
|
|
450
|
+
"contract version and policy as the execution result. Generated "
|
|
451
|
+
"from codecalc/contract.py by scripts/check_contract.py."
|
|
452
|
+
),
|
|
453
|
+
"type": "object",
|
|
454
|
+
"required": ["contract_version", "codecalc_version", "healthy", "python",
|
|
455
|
+
"backend", "execution_providers", "install_sandbox", "extras", "grammar_cache",
|
|
456
|
+
"status_basis",
|
|
457
|
+
"runtimes", "runtime_summary", "workspace", "remedies"],
|
|
458
|
+
"properties": {
|
|
459
|
+
"contract_version": {"type": "string", "pattern": r"^\d+\.\d+\.\d+$"},
|
|
460
|
+
"codecalc_version": {"type": "string"},
|
|
461
|
+
"healthy": {
|
|
462
|
+
"type": "boolean",
|
|
463
|
+
"description": (
|
|
464
|
+
"Deliberately NARROW, and the process exit code follows it: "
|
|
465
|
+
"true when a workspace is writable and a backend resolved. A "
|
|
466
|
+
"missing optional extra or an uninstalled runtime is a fact "
|
|
467
|
+
"about the host, not a fault, and must not fail an install "
|
|
468
|
+
"check."
|
|
469
|
+
),
|
|
470
|
+
},
|
|
471
|
+
"python": {
|
|
472
|
+
"type": "object", "required": ["version", "platform"],
|
|
473
|
+
"properties": {"version": {"type": "string"},
|
|
474
|
+
"platform": {"type": "string"}},
|
|
475
|
+
},
|
|
476
|
+
"backend": {
|
|
477
|
+
"type": "object", "required": ["kind", "binary", "detail"],
|
|
478
|
+
"properties": {
|
|
479
|
+
"kind": {"type": "string", "enum": list(BACKENDS)},
|
|
480
|
+
"binary": {"type": ["string", "null"]},
|
|
481
|
+
"detail": {"type": ["string", "null"]},
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
"execution_providers": {
|
|
485
|
+
"type": "array",
|
|
486
|
+
"items": {
|
|
487
|
+
"type": "object",
|
|
488
|
+
"required": ["interface_version", "provider_id",
|
|
489
|
+
"provider_version", "host_class", "strict",
|
|
490
|
+
"capabilities", "ready", "technology", "detail"],
|
|
491
|
+
"properties": {
|
|
492
|
+
"interface_version": {"type": "string"},
|
|
493
|
+
"provider_id": {"type": "string"},
|
|
494
|
+
"provider_version": {"type": "string"},
|
|
495
|
+
"host_class": {"type": "string"},
|
|
496
|
+
"strict": {"type": "boolean"},
|
|
497
|
+
"capabilities": {"type": "object"},
|
|
498
|
+
"ready": {"type": ["boolean", "null"]},
|
|
499
|
+
"technology": {"type": ["string", "null"]},
|
|
500
|
+
"detail": {"type": ["string", "null"]},
|
|
501
|
+
},
|
|
502
|
+
"additionalProperties": False,
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
"install_sandbox": {
|
|
506
|
+
"type": "object", "required": ["landlock_abi", "confined", "detail"],
|
|
507
|
+
"properties": {"landlock_abi": {"type": ["integer", "null"]},
|
|
508
|
+
"confined": {"type": "boolean"},
|
|
509
|
+
"detail": {"type": ["string", "null"]}},
|
|
510
|
+
},
|
|
511
|
+
"grammar_cache": {
|
|
512
|
+
"type": "object",
|
|
513
|
+
"required": ["extra_installed", "cached", "path", "grammars",
|
|
514
|
+
"detail"],
|
|
515
|
+
"description": (
|
|
516
|
+
"tree-sitter grammars are NOT in the wheel: the pack fetches "
|
|
517
|
+
"each one on first use, in-process, into a local cache "
|
|
518
|
+
"(THE-821). Reported so an offline or egress-restricted "
|
|
519
|
+
"install can see it BEFORE analyze_complexity degrades to "
|
|
520
|
+
"regex-fallback. `cached: false` is not a fault."
|
|
521
|
+
),
|
|
522
|
+
"properties": {
|
|
523
|
+
"extra_installed": {"type": "boolean"},
|
|
524
|
+
"cached": {
|
|
525
|
+
"type": "boolean",
|
|
526
|
+
"description": (
|
|
527
|
+
"At least one grammar is on disk. Derived from a "
|
|
528
|
+
"COUNT, not from the directory existing — the pack "
|
|
529
|
+
"creates the directory before downloading anything, "
|
|
530
|
+
"so existence alone reports a warm cache that is not."
|
|
531
|
+
),
|
|
532
|
+
},
|
|
533
|
+
"path": {"type": ["string", "null"]},
|
|
534
|
+
"grammars": {"type": "integer", "minimum": 0},
|
|
535
|
+
"detail": {"type": ["string", "null"]},
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
"extras": {
|
|
539
|
+
"type": "array",
|
|
540
|
+
"items": {
|
|
541
|
+
"type": "object",
|
|
542
|
+
"required": ["name", "installed", "missing", "remedy"],
|
|
543
|
+
"properties": {"name": {"type": "string"},
|
|
544
|
+
"installed": {"type": "boolean"},
|
|
545
|
+
"missing": {"type": "array",
|
|
546
|
+
"items": {"type": "string"}},
|
|
547
|
+
"remedy": {"type": ["string", "null"]}},
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
"status_basis": {
|
|
551
|
+
"type": "string", "enum": ["resolved", "executed"],
|
|
552
|
+
"description": (
|
|
553
|
+
"Which measurement produced the runtime statuses. "
|
|
554
|
+
"'resolved' means the command was found on PATH and NOT "
|
|
555
|
+
"run, so no runtime can be 'available'. 'executed' means "
|
|
556
|
+
"--deep ran them. Without this a reader cannot tell a host "
|
|
557
|
+
"with nothing installed from a report that never looked."
|
|
558
|
+
),
|
|
559
|
+
},
|
|
560
|
+
"runtimes": {
|
|
561
|
+
"type": "array",
|
|
562
|
+
"items": {
|
|
563
|
+
"type": "object",
|
|
564
|
+
"required": ["name", "command", "status", "path", "version"],
|
|
565
|
+
"properties": {
|
|
566
|
+
"name": {"type": "string"},
|
|
567
|
+
"command": {"type": "string"},
|
|
568
|
+
"status": {
|
|
569
|
+
"type": "string", "enum": list(RUNTIME_STATES),
|
|
570
|
+
"description": (
|
|
571
|
+
"supported = codecalc knows it, nothing resolves "
|
|
572
|
+
"here · installed = resolves and is executable, "
|
|
573
|
+
"NOT run · unhealthy = resolves but cannot run, "
|
|
574
|
+
"or was run and failed · available = actually "
|
|
575
|
+
"executed here and answered (--deep only)"
|
|
576
|
+
),
|
|
577
|
+
},
|
|
578
|
+
"path": {"type": ["string", "null"]},
|
|
579
|
+
"version": {
|
|
580
|
+
"type": ["string", "null"],
|
|
581
|
+
"description": (
|
|
582
|
+
"the runtime's own version string, read by "
|
|
583
|
+
"running it with --version. null means NOT "
|
|
584
|
+
"MEASURED — no --deep, no version flag, or an "
|
|
585
|
+
"unreadable answer — never 'no version'."
|
|
586
|
+
),
|
|
587
|
+
},
|
|
588
|
+
"detail": {"type": "string"},
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
"runtime_summary": {
|
|
593
|
+
"type": "object",
|
|
594
|
+
"required": list(RUNTIME_STATES),
|
|
595
|
+
"properties": {s: {"type": "integer", "minimum": 0}
|
|
596
|
+
for s in RUNTIME_STATES},
|
|
597
|
+
},
|
|
598
|
+
"workspace": {
|
|
599
|
+
"type": "object", "required": ["path", "writable", "error"],
|
|
600
|
+
"properties": {"path": {"type": "string"},
|
|
601
|
+
"writable": {"type": "boolean"},
|
|
602
|
+
"error": {"type": ["string", "null"]}},
|
|
603
|
+
},
|
|
604
|
+
"skill_file": {"type": ["string", "null"]},
|
|
605
|
+
"remedies": {"type": "array", "items": {"type": "string"}},
|
|
606
|
+
},
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def stamp(result: dict) -> dict:
|
|
611
|
+
"""Attach `contract_version` to a result. Mutates and returns it.
|
|
612
|
+
|
|
613
|
+
Applied at the single point in `executor.execute` that both backends pass
|
|
614
|
+
through, for the same reason `backend` is attached there: it is the only
|
|
615
|
+
place that can make the two agree without editing either of them.
|
|
616
|
+
|
|
617
|
+
Non-dict results are returned untouched rather than raising. A caller that
|
|
618
|
+
somehow has a non-dict here has a worse problem than a missing version, and
|
|
619
|
+
turning it into an exception inside the executor would replace a readable
|
|
620
|
+
failure with an unreadable one.
|
|
621
|
+
"""
|
|
622
|
+
if isinstance(result, dict):
|
|
623
|
+
result.setdefault("contract_version", CONTRACT_VERSION)
|
|
624
|
+
return result
|