onebudgetspec-cli 0.1.2__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.
- onebudgetspec_cli-0.1.2.data/scripts/onebudgetspec.exe +0 -0
- onebudgetspec_cli-0.1.2.dist-info/METADATA +247 -0
- onebudgetspec_cli-0.1.2.dist-info/RECORD +6 -0
- onebudgetspec_cli-0.1.2.dist-info/WHEEL +4 -0
- onebudgetspec_cli-0.1.2.dist-info/licenses/LICENSE +21 -0
- onebudgetspec_cli-0.1.2.dist-info/sboms/onebudgetspec.cyclonedx.json +2467 -0
|
Binary file
|
|
@@ -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=8HqhMvrjT71CXb8KlkPVOomM680AsUL-y9YnfhMJUDg,3619840
|
|
2
|
+
onebudgetspec_cli-0.1.2.dist-info/METADATA,sha256=DXwfUEyfLTBvcVvkbdKQT1MQCBBMzzqZZxyTG2KXhEA,12032
|
|
3
|
+
onebudgetspec_cli-0.1.2.dist-info/WHEEL,sha256=8Aej0W0a6Cz6apA3IzJrTnxLRVLAt-w0Oh8SA3Con_c,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=RSTV3ZtmleJCsB4LDKSF0-Ku4dx07Vk77IX_cllYcqs,77129
|
|
6
|
+
onebudgetspec_cli-0.1.2.dist-info/RECORD,,
|
|
@@ -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.
|