onebudgetspec-cli 0.1.2__py3-none-win_arm64.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.
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.4
2
+ Name: onebudgetspec-cli
3
+ Version: 0.1.2
4
+ License-File: LICENSE
5
+ Summary: The onebudgetspec command line: check, validate and list the budgets registered in budgets.yaml files.
6
+ Author: Nick DeRobertis
7
+ License-Expression: MIT
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
10
+ Project-URL: Repository, https://github.com/nickderobertis/onebudgetspec
11
+
12
+ # onebudgetspec
13
+
14
+ Register measurable budgets in YAML and gate on them. Each budget names a command that
15
+ performs the measurement, a unit, a direction and a threshold; `onebudgetspec check` runs
16
+ every selected command once and reports the actual value, the budget and the headroom.
17
+
18
+ It ships as a Rust SDK (`onebudgetspec-core`) and a command line (`onebudgetspec`), with
19
+ Python and TypeScript SDKs released beside them at the same version (see [SDKs](#sdks)).
20
+
21
+ ## Principles
22
+
23
+ - **Measure once.** Each selected budget's command runs exactly once per check. There is
24
+ no retry, repeat or sampling.
25
+ - **Report actual, budget and headroom.** Every result carries all three, so even a result
26
+ within budget shows how close it came.
27
+ - **Record host conditions beside each result.** The load average, CPU count, available
28
+ memory and any conditions the file declares are recorded with every measurement, so a
29
+ person can judge an extreme circumstance from the result itself.
30
+ - **Over budget is a defect, never re-sampled.** An over-budget result fails the check on
31
+ any host. It is never re-run until it passes.
32
+ - **No baselines.** A budget is the threshold written in the file, not a comparison with an
33
+ earlier run.
34
+
35
+ ## Install
36
+
37
+ ```sh
38
+ pip install onebudgetspec-cli # or: uvx --from onebudgetspec-cli onebudgetspec
39
+ npm install --save-dev @onebudgetspec/cli
40
+ cargo install onebudgetspec
41
+ ```
42
+
43
+ Each installs a native binary for Linux, macOS and Windows, on x86_64 and aarch64.
44
+
45
+ ## The budgets file
46
+
47
+ A file named `budgets.yaml`:
48
+
49
+ ```yaml
50
+ schema_version: 1
51
+ conditions: # optional; recorded beside every result measured from this file
52
+ - name: dispatches # ^[a-z][a-z0-9_]*$, unique, never load1, cpus or mem_available_mib
53
+ command: ["bash", "scripts/count-dispatches.sh"] # run once per file per check; trimmed stdout recorded
54
+ budgets:
55
+ - id: gate-time # ^[a-z][a-z0-9-]*$, unique across every file one discovery finds
56
+ description: "Wall clock of the full gate" # optional
57
+ labels: [ci] # optional free tags, for selecting a subset
58
+ measure: reported # elapsed | reported
59
+ command: ["bash", "scripts/budget-gate-time.sh"] # argv, no shell, run from this file's directory
60
+ unit: seconds # a free label; `elapsed` requires seconds
61
+ direction: max # max: within when actual <= threshold; min: within when actual >= threshold
62
+ threshold: 1800 # finite and non-negative
63
+ timeout_seconds: 3600 # optional; past it the measurement is an error
64
+ ```
65
+
66
+ Unknown keys are refused at every level, naming the key and the file.
67
+
68
+ A `command` is an argv run with no shell, from the directory holding its file; a program
69
+ named by a relative path with more than one component is found from that directory too. On
70
+ Linux and macOS an executable script may be named directly, by its `#!` line. Windows reads
71
+ no `#!` line, and a bare name there resolves only to `<name>.exe`, so a script measured
72
+ on Windows names its interpreter in the command, as in
73
+ `["bash", "scripts/budget-gate-time.sh"]`, `["node", "measure.js"]` or
74
+ `["python", "measure.py"]`; that form runs on every platform. A `timeout_seconds` ends
75
+ everything the command started: its process group on Linux and macOS, its Job Object on
76
+ Windows. A failed command is described by its exit status, which on Windows is the exit code
77
+ (in hexadecimal, such as `0xC0000005`, when it is a crash's NTSTATUS); on Linux and macOS a
78
+ command ended by a signal is described by that signal. `load1` is unknown (`null`) on
79
+ Windows, which has no load average, and `mem_available_mib` is unknown on macOS.
80
+
81
+ **`elapsed`** times the command's wall clock; a non-zero exit is an error. **`reported`**
82
+ sets `ONEBUDGETSPEC_RESULT` to the path of a fresh, empty file, and the command writes one
83
+ JSON object there and exits 0:
84
+
85
+ ```json
86
+ {"value": 1395.0, "detail": "optional text", "conditions": {"runner": "large"}}
87
+ ```
88
+
89
+ `detail` and `conditions` are optional. Returned conditions are recorded in that result's
90
+ host conditions only; a returned name equal to a declared condition, `load1`, `cpus` or
91
+ `mem_available_mib` makes the measurement an error rather than replacing a sampled value.
92
+ A command's own stdout and stderr go to stderr, never into a report, with one exception:
93
+ when a budget's command exits non-zero, is ended by a signal or its timeout, or exits 0
94
+ with a result that cannot be read, its result's `error` states that (for an unreadable
95
+ result, why it could not be read and then the exit status), followed by `; its stderr: `
96
+ and the tail of what the command wrote there. That tail is decoded lossily, each line
97
+ trimmed, blank lines dropped, the rest joined by ` | ` and other control characters made
98
+ spaces, and it is bounded to at most the last 1000 characters, after a leading `…` when
99
+ anything was cut. A command that wrote nothing but whitespace there gets no such suffix;
100
+ a command that succeeds is reported without its stderr. A failing condition command is
101
+ still recorded as `unknown`, and the same reason appears on the line onebudgetspec prints
102
+ to stderr for it.
103
+
104
+ ## The command line
105
+
106
+ ```sh
107
+ onebudgetspec check [PATH]... [--id ID]... [--label LABEL]... [--exclude-label LABEL]... [--recursive]
108
+ onebudgetspec validate [PATH]... [--recursive]
109
+ onebudgetspec list [PATH]... [--id ID]... [--label LABEL]... [--exclude-label LABEL]... [--recursive]
110
+ onebudgetspec schema
111
+ ```
112
+
113
+ With no PATH, `./budgets.yaml` is read. `--recursive` searches a directory for files named
114
+ `budgets.yaml`, honouring `.gitignore`. Every verb takes `--output text|json` (`--json` for
115
+ short). `schema` prints the JSON Schema bundle for the budgets file and both reports.
116
+
117
+ A text result reads:
118
+
119
+ ```text
120
+ budget gate-time: actual 1395 seconds, budget 1800 seconds, headroom 405 seconds (22.5%) — within; host: load=2.1/8 mem_available=5120MiB dispatches=3
121
+ ```
122
+
123
+ | Exit | Meaning |
124
+ | ---- | ------- |
125
+ | 0 | every selected budget is within |
126
+ | 1 | at least one is over, and none errored |
127
+ | 2 | the invocation or a file is invalid; nothing was run (the reason is on stderr) |
128
+ | 3 | at least one measurement errored (its reason is that result's `error`, in the report), or the report could not be written (that reason is on stderr) |
129
+
130
+ The npm launcher adds three of its own, each with the reason on stderr: `64` on a platform
131
+ no carrier is published for, `69` when the carrier package is missing or its binary
132
+ cannot run, and `70` when the binary was ended by a signal.
133
+
134
+ ## SDKs
135
+
136
+ Three SDKs, released at the binary's version. The Rust SDK is the engine itself; the Python
137
+ and TypeScript SDKs run the `onebudgetspec` binary once per call, parse the JSON it prints,
138
+ and return the reports as types generated from `onebudgetspec schema`, so they never drift
139
+ from it.
140
+
141
+ | | Rust | Python | TypeScript |
142
+ | --- | --- | --- | --- |
143
+ | Package | `onebudgetspec-core` (crates.io) | `onebudgetspec-sdk` (PyPI), imported as `onebudgetspec_sdk` | `@onebudgetspec/sdk` (npm) |
144
+ | Install | `cargo add onebudgetspec-core` | `pip install onebudgetspec-sdk`, which installs `onebudgetspec-cli` at the same version | `npm install @onebudgetspec/sdk`, which installs its optional dependency `@onebudgetspec/cli` at the same version |
145
+ | Calls | `load(paths, recursive)`, then `.select(&Selection)` and `.check()` or `.list_report()`, or `.all().list_report()` to validate; `schema_bundle()` | `check(paths=None, ids=None, labels=None, exclude_labels=None, recursive=False, cwd=None) -> CheckReport`, `validate(paths=None, recursive=False, cwd=None) -> ListReport`, `list_budgets(...)` with `check`'s arguments `-> ListReport`, `schema() -> dict` | `check({paths, ids, labels, excludeLabels, recursive, cwd})`, `validate({paths, recursive, cwd})`, `listBudgets({paths, ids, labels, excludeLabels, recursive, cwd})`, `schema()`, each a promise of the generated type |
146
+ | Binary | none: it measures in process | the `binary=` argument, then `ONEBUDGETSPEC_BIN`, then the `onebudgetspec` the `onebudgetspec-cli` wheel installed (`Scripts\onebudgetspec.exe` on Windows), then `onebudgetspec` on `PATH` (`onebudgetspec.exe` on Windows) | the `binary` option, then `ONEBUDGETSPEC_BIN`, then the launcher of the resolved `@onebudgetspec/cli` package |
147
+ | Tests and journeys it owes | `crates/onebudgetspec-core/tests/api.rs`, `crates/onebudgetspec-core/tests/schema.rs`, and every CLI journey and conformance case, which drive it through the binary | `sdks/python/tests/test_conformance.py` (every conformance case through `check`, `list_budgets` and `validate`), `sdks/python/tests/test_binary.py` (resolution order and refusals), `sdks/python/tests/test_generate.py` (the model generator), and the packaging journey `crates/onebudgetspec-packaging-e2e/tests/packaging/sdk_python.rs` | `sdks/typescript/tests/conformance.test.ts`, `sdks/typescript/tests/binary.test.ts`, `sdks/typescript/tests/errors.test.ts` and `sdks/typescript/tests/generator.test.ts` (the same concerns), and the packaging journey `crates/onebudgetspec-packaging-e2e/tests/packaging/sdk_typescript.rs` |
148
+
149
+ In Python and TypeScript, exit statuses `0`, `1` and `3` return the report, since the
150
+ verdicts are in it; status `2` (an invalid invocation or budgets file) raises
151
+ `OnebudgetspecError` (Python) or rejects with it (TypeScript), carrying the CLI's own
152
+ message and `exit_code`/`exitCode` `2`.
153
+
154
+ ```python
155
+ from onebudgetspec_sdk import check
156
+
157
+ report = check(labels=["api"], exclude_labels=["slow"], cwd="services")
158
+ for result in report.results:
159
+ print(result.id, result.verdict, result.actual, result.headroom, result.host.conditions)
160
+ ```
161
+
162
+ ```ts
163
+ import { check } from "@onebudgetspec/sdk";
164
+
165
+ const report = await check({ labels: ["api"], excludeLabels: ["slow"], cwd: "services" });
166
+ for (const result of report.results) {
167
+ console.log(result.id, result.verdict, result.actual, result.headroom, result.host.conditions);
168
+ }
169
+ ```
170
+
171
+ ## Nesting budgets files
172
+
173
+ Each project registers its own budgets beside its code, and only the budgets a change can
174
+ affect run; the budgets that must always be checked go in the root file. With Nx as the
175
+ worked example, a repository lays them out as:
176
+
177
+ ```text
178
+ budgets.yaml # always checked
179
+ services/api/budgets.yaml # checked when services/api is affected
180
+ services/api/project.json
181
+ ```
182
+
183
+ The project's own file:
184
+
185
+ ```yaml title="services/api/budgets.yaml"
186
+ schema_version: 1
187
+ budgets:
188
+ - id: api-cold-start
189
+ description: "Seconds from process start to the first healthy response"
190
+ measure: elapsed
191
+ command: ["bash", "scripts/start-and-probe.sh"]
192
+ unit: seconds
193
+ direction: max
194
+ threshold: 2
195
+ timeout_seconds: 30
196
+ - id: api-requests-per-second
197
+ measure: reported
198
+ command: ["bash", "scripts/load-test.sh"]
199
+ unit: requests/s
200
+ direction: min
201
+ threshold: 500
202
+ ```
203
+
204
+ checked by that project's `budgets` target in `services/api/project.json`:
205
+
206
+ ```json
207
+ {
208
+ "targets": {
209
+ "budgets": {
210
+ "command": "onebudgetspec check services/api/budgets.yaml",
211
+ "inputs": ["{projectRoot}/**/*"]
212
+ }
213
+ }
214
+ }
215
+ ```
216
+
217
+ and run for the affected projects only:
218
+
219
+ ```sh
220
+ nx affected -t budgets
221
+ ```
222
+
223
+ The root file holds what every change must stay within:
224
+
225
+ ```yaml title="budgets.yaml"
226
+ schema_version: 1
227
+ conditions:
228
+ - name: runner
229
+ command: ["node", "-p", "process.arch"]
230
+ budgets:
231
+ - id: gate-time
232
+ description: "Wall clock of the full gate"
233
+ measure: elapsed
234
+ command: ["just", "check"]
235
+ unit: seconds
236
+ direction: max
237
+ threshold: 1800
238
+ timeout_seconds: 3600
239
+ ```
240
+
241
+ checked on every change with `onebudgetspec check budgets.yaml`. To check every file at
242
+ once, `onebudgetspec check --recursive .` discovers both.
243
+
244
+ ## Licence
245
+
246
+ MIT
247
+
@@ -0,0 +1,6 @@
1
+ onebudgetspec_cli-0.1.2.data/scripts/onebudgetspec.exe,sha256=5ntqpolejBEEpQiSXnkMVAROddeaNlOfiySzAPmrcTw,3105280
2
+ onebudgetspec_cli-0.1.2.dist-info/METADATA,sha256=DXwfUEyfLTBvcVvkbdKQT1MQCBBMzzqZZxyTG2KXhEA,12032
3
+ onebudgetspec_cli-0.1.2.dist-info/WHEEL,sha256=sIXBi-p2sWmn7sxKLRLkxCeOb2LZNI_-9m50saSMshs,94
4
+ onebudgetspec_cli-0.1.2.dist-info/licenses/LICENSE,sha256=JeBGdcXIUkoTZEHhkKd-FmKgdgumkH509Ob1QWJomVE,1072
5
+ onebudgetspec_cli-0.1.2.dist-info/sboms/onebudgetspec.cyclonedx.json,sha256=P6OGsL_tdCGyhD0r__p-n4gIUXXMKx6BEm63FvuByaA,77129
6
+ onebudgetspec_cli-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.15.0)
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_arm64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nick DeRobertis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.