boundver 0.9.0__tar.gz

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.
boundver-0.9.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 boundver contributors
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.
@@ -0,0 +1,441 @@
1
+ Metadata-Version: 2.4
2
+ Name: boundver
3
+ Version: 0.9.0
4
+ Summary: Automated change-type classification for components that lack static verification
5
+ Author: boundver contributors
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Provides-Extra: schema
11
+ Requires-Dist: jsonschema>=4; extra == "schema"
12
+ Provides-Extra: yaml
13
+ Requires-Dist: PyYAML>=6; extra == "yaml"
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest<9,>=7; extra == "dev"
16
+ Requires-Dist: jsonschema>=4; extra == "dev"
17
+ Requires-Dist: build>=1.2; extra == "dev"
18
+ Requires-Dist: PyYAML>=6; extra == "dev"
19
+ Dynamic: license-file
20
+
21
+ # boundver
22
+
23
+ [![PyPI](https://img.shields.io/pypi/v/boundver)](https://pypi.org/project/boundver/)
24
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue)](https://pypi.org/project/boundver/)
25
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
26
+ [![No runtime dependencies](https://img.shields.io/badge/dependencies-none-brightgreen)](pyproject.toml)
27
+
28
+ **Automated change-type classification for components that lack static verification.**
29
+
30
+ boundver answers four questions per component — *did anything change?*, *did the behavioral contract change?*, *did the declared boundary change?*, *is it still compatible?* — using content-addressed fingerprints derived from Git state and declared boundary files. No external dependencies. No build system required.
31
+
32
+ ## Why
33
+
34
+ When a component has consumers but no compiler or type system verifying its interface — services exposing OpenAPI specs, Python libraries, config-driven systems, internal platforms — there's no machine that tells you whether a change is internal, boundary-affecting, or breaking.
35
+
36
+ boundver fills that gap. It lets you **declare** what constitutes your component's boundary, then **automatically classifies every change** into one of four categories:
37
+
38
+ - **Implementation-only** — internals changed, boundary stable, consumers unaffected.
39
+ - **Behavioral contract change** — defaults/config/migrations changed, API shape stable, consumers may need to re-verify.
40
+ - **Boundary change** — the declared contract changed, consumers should re-verify.
41
+ - **Compatibility break** — the compatibility family changed, deployment coordination required.
42
+
43
+ This is the information that CI, consumers, and operators each need — derived deterministically from repo state, not from human discipline or commit-message conventions.
44
+
45
+ ### When to use boundver
46
+
47
+ boundver is for any component whose boundary has consumers but **no static verification** — no compiler checking signatures, no type system enforcing contracts. That includes most services, most Python/Go libraries, most YAML/JSON-defined APIs, and most internal platforms.
48
+
49
+ | Tool | Sweet spot | Skip if… |
50
+ |---|---|---|
51
+ | **Nx / Turborepo** | JS/TS monorepos with task graphs and caching | You have a polyglot repo or can't adopt a full task runner |
52
+ | **Bazel / Pants** | Large-scale build + dependency graph orchestration | Adoption cost exceeds value for your team size |
53
+ | **TypeScript / Rust compiler** | Statically verified API contracts within a single language | Your entire stack is one statically-typed language |
54
+ | **boundver** | Any language — automated change classification where no static verifier exists | You already have affected-graph + cache-key tooling that satisfies all four questions |
55
+
56
+ For full tool-selection guidance, see [docs/WHY_BOUNDVER.md](docs/WHY_BOUNDVER.md).
57
+
58
+ ## How it works
59
+
60
+ Each component gets four fingerprints forming a strict containment hierarchy (`exact ⊇ behavior ⊇ boundary`):
61
+
62
+ | Fingerprint | Question it answers | What it hashes |
63
+ |---|---|---|
64
+ | `exact` | Did anything change? | All tracked files in the component path |
65
+ | `behavior` | Did the behavioral contract change? | Declared contract files: boundary + config + migrations + contract tests |
66
+ | `boundary` | Did the API surface shape change? | Only the declared boundary files (e.g. `openapi.yaml`, `__init__.py`) |
67
+ | `compat` | Is it still in the same compatibility family? | Derived from SemVer major version |
68
+
69
+ This gives you four distinct change classifications:
70
+
71
+ | What changed | Meaning |
72
+ |---|---|
73
+ | Only `exact` | Pure internal refactor — consumers unaffected |
74
+ | `exact` + `behavior` | Behavioral contract changed (defaults, config, migrations) — API shape stable but consumers may be affected |
75
+ | `exact` + `behavior` + `boundary` | API surface changed — consumers must re-verify |
76
+ | All four | Breaking change — compatibility family changed |
77
+
78
+ Components are grouped into **slices** — named subsets with their own stable fingerprints. Adding an unrelated component changes the full-project hash but leaves existing slice fingerprints untouched.
79
+
80
+ > **Note:** `boundary` and `behavior` are **declared-file fingerprints**, not semantic analysis. They detect changes in files you declare as contract-relevant. The `openapi-canonical` and `json-canonical` providers go further — they strip non-contract content (descriptions, comments, formatting) so only structural changes trigger the fingerprint.
81
+
82
+ Each component also reports `boundary_status` in lock output:
83
+ - `ok`: boundary paths were declared and hashed successfully
84
+ - `partial`: boundary provider is `implicit` and no boundary paths are declared (API fingerprint is `null`)
85
+ - `error`: explicit boundary provider has no paths, or declared paths produced no API digest
86
+
87
+ ## Quick start
88
+
89
+ ```bash
90
+ # Install
91
+ pip install boundver
92
+
93
+ # Create a starter config
94
+ boundver init
95
+ # Or auto-discover components from common manifests
96
+ boundver init --discover
97
+ # Custom path / overwrite existing
98
+ boundver init --out boundary.config.json --force
99
+
100
+ # Or create manually (see Config Reference below)
101
+ cat > boundary.config.json << 'EOF'
102
+ {
103
+ "project": "my-project",
104
+ "components": {
105
+ "auth-service": {
106
+ "path": "services/auth",
107
+ "version_source": { "file": "package.json", "field": "version" },
108
+ "boundary": {
109
+ "provider": "openapi",
110
+ "paths": ["openapi.yaml"]
111
+ },
112
+ "behavior": {
113
+ "paths": ["openapi.yaml", "config/defaults.json"]
114
+ }
115
+ }
116
+ },
117
+ "slices": {
118
+ "auth-api": {
119
+ "description": "Auth service public API",
120
+ "mode": "boundary",
121
+ "components": ["auth-service"]
122
+ }
123
+ }
124
+ }
125
+ EOF
126
+
127
+ # Generate the lockfile
128
+ boundver generate
129
+
130
+ # Regenerate only selected components (and affected slices)
131
+ boundver generate --components auth-service,billing-service
132
+
133
+ # Preview generation without writing boundary.lock.json
134
+ boundver generate --dry-run
135
+
136
+ # Check current status
137
+ boundver status
138
+
139
+ # Verify lockfile matches repo state
140
+ boundver verify
141
+
142
+ # Verify only selected components
143
+ boundver verify --components auth-service,billing-service
144
+
145
+ # Verify only components changed since main
146
+ boundver verify --changed-from origin/main
147
+
148
+ # JSON output for automation
149
+ boundver verify --format json
150
+
151
+ # Logging controls
152
+ boundver --quiet status
153
+ boundver --verbose verify
154
+
155
+ # Diff two lockfiles
156
+ boundver diff old.lock.json boundary.lock.json
157
+
158
+ # Inspect a specific slice
159
+ boundver slice auth-api
160
+
161
+ # Preview discovered components
162
+ boundver discover --format json
163
+ ```
164
+
165
+ ## Behavior matrix
166
+
167
+ | Event | exact | behavior | boundary | compat |
168
+ |---|---|---|---|---|
169
+ | Bug fix (no API change) | ✓ changes | unchanged | unchanged | unchanged |
170
+ | Config/default/migration change | ✓ changes | ✓ changes | unchanged | unchanged |
171
+ | New API endpoint added | ✓ changes | ✓ changes | ✓ changes | unchanged |
172
+ | Breaking change + major bump | ✓ changes | ✓ changes | ✓ changes | ✓ changes |
173
+ | Internal refactor | ✓ changes | unchanged | unchanged | unchanged |
174
+ | New unrelated component added | slice unchanged | slice unchanged | slice unchanged | n/a |
175
+
176
+ ## Config reference
177
+
178
+ ### `boundary.config.json`
179
+
180
+ Schema file: `boundary.config.schema.json` (Draft 2020-12).
181
+
182
+ ```json
183
+ {
184
+ "$schema": "https://raw.githubusercontent.com/yzm1/boundver/main/boundary.config.schema.json",
185
+ "project": "my-project",
186
+ "defaults": {
187
+ "compat_mode": "major"
188
+ },
189
+ "components": {
190
+ "component-name": {
191
+ "path": "relative/path/from/repo/root",
192
+ "ecosystem": "python | typescript | cloudformation",
193
+ "version_source": {
194
+ "file": "package.json",
195
+ "field": "version"
196
+ },
197
+ "boundary": {
198
+ "provider": "openapi | python-exports | typescript-exports | leaf | implicit",
199
+ "paths": ["openapi.yaml"],
200
+ "note": "optional explanation"
201
+ },
202
+ "behavior": {
203
+ "paths": ["openapi.yaml", "config/defaults.json"]
204
+ },
205
+ "vendored_copies": ["path/to/vendored/copy/"]
206
+ }
207
+ },
208
+ "slices": {
209
+ "slice-name": {
210
+ "description": "Human-readable purpose",
211
+ "mode": "exact | behavior | boundary | compat",
212
+ "components": ["component-a", "component-b"]
213
+ }
214
+ }
215
+ }
216
+ ```
217
+
218
+ ### Version source options
219
+
220
+ ```json
221
+ // From a JSON/TOML/YAML file field:
222
+ "version_source": { "file": "pyproject.toml", "field": "project.version" }
223
+
224
+ // From git tags:
225
+ "version_source": { "git_tag_prefix": "auth-service-v" }
226
+
227
+ // No version tracking:
228
+ "version_source": null
229
+ ```
230
+
231
+ ### Boundary providers
232
+
233
+ | Provider | Meaning |
234
+ |---|---|
235
+ | `openapi` | OpenAPI/Swagger spec defines the API surface |
236
+ | `python-exports` | `__init__.py` or `__all__` exports define the boundary |
237
+ | `typescript-exports` | `.d.ts` or `index.ts` exports define the boundary |
238
+ | `json-file` | Generic JSON boundary artifact defines the contract |
239
+ | `custom.example.service-definition.v1` | Example custom provider namespace |
240
+ | `leaf` | No downstream consumers — boundary is the component itself |
241
+ | `implicit` | No explicit boundary artifact yet (`boundary` fingerprint will be `null`) |
242
+
243
+ ### Provider capability matrix
244
+
245
+ | Provider | Semantic parser? | Requires `paths` | Empty `paths` allowed | Output |
246
+ |---|---:|---:|---:|---|
247
+ | `openapi` | No (raw file digest) | Yes | No | Raw boundary digest |
248
+ | `python-exports` | No (raw file digest) | Yes | No | Raw boundary digest |
249
+ | `typescript-exports` | No (raw file digest) | Yes | No | Raw boundary digest |
250
+ | `json-file` | No (raw file digest) | Yes | No | Raw boundary digest |
251
+ | `leaf` | n/a | No | Yes | No boundary digest required |
252
+ | `implicit` | n/a | No | Yes | `boundary_status=partial` |
253
+ | `custom.*` | Depends on implementation | Usually | Depends | Raw digest by default |
254
+
255
+ > Built-in providers are currently raw-boundary artifact hashers, not semantic API diff engines.
256
+
257
+
258
+ ## Near-term implementation focus
259
+
260
+ boundver remains a public, language-agnostic tool. Near-term work is focused on:
261
+
262
+ - strict config validation and no silent fingerprint fallback
263
+ - explicit source mode behavior (`head`, `index`, `working-tree`)
264
+ - portability for external users (no implicit dependency on internal/proprietary boundary artifacts)
265
+
266
+ Short term deliverables: `validate-config`, strict digest selection, explicit source modes, and public examples that avoid proprietary dependencies.
267
+
268
+ ## CI integration
269
+
270
+ For lockfile merge conflict handling, see [docs/LOCKFILE_MERGE.md](docs/LOCKFILE_MERGE.md).
271
+
272
+ ### GitHub Actions — PR verification
273
+
274
+ For a full set of patterns (conditional builds, cache keys, GitLab, pre-commit), see [docs/ci-cookbook.md](docs/ci-cookbook.md).
275
+
276
+ #### Option A: use bundled composite action
277
+
278
+ ```yaml
279
+ name: Boundary check
280
+ on: [pull_request]
281
+ jobs:
282
+ verify:
283
+ runs-on: ubuntu-latest
284
+ steps:
285
+ - uses: actions/checkout@v4
286
+ with:
287
+ fetch-depth: 0
288
+ - uses: ./.github/actions/boundver
289
+ with:
290
+ config: boundary.config.json
291
+ lock: boundary.lock.json
292
+ source: head
293
+ show-diff-on-failure: "true"
294
+ ```
295
+
296
+ #### Option B: explicit steps
297
+
298
+ ```yaml
299
+ name: Boundary check
300
+ on: [pull_request]
301
+ jobs:
302
+ verify:
303
+ runs-on: ubuntu-latest
304
+ steps:
305
+ - uses: actions/checkout@v4
306
+ with:
307
+ fetch-depth: 0
308
+ - uses: actions/setup-python@v5
309
+ with:
310
+ python-version: '3.11'
311
+ - run: boundver verify
312
+ - name: Show diff on failure
313
+ if: failure()
314
+ run: |
315
+ boundver generate --out boundary.lock.new.json
316
+ boundver diff boundary.lock.json boundary.lock.new.json
317
+ ```
318
+
319
+ ### Conditional builds using slice fingerprints
320
+
321
+ ```bash
322
+ # Only rebuild if the API slice actually changed
323
+ NEW_FP=$(python -c "
324
+ import json
325
+ lock = json.load(open('boundary.lock.json'))
326
+ print(lock['slices']['my-api']['fingerprint'][:12])
327
+ ")
328
+
329
+ if [ "$NEW_FP" != "$CACHED_FP" ]; then
330
+ echo "API changed — rebuilding consumers"
331
+ # ... trigger downstream builds
332
+ fi
333
+ ```
334
+
335
+ ### Shell verifier (portability proof)
336
+
337
+ ```bash
338
+ # Verifies exact/boundary fingerprints against HEAD using git + jq + sha256sum
339
+ scripts/boundver-verify.sh boundary.config.json boundary.lock.json
340
+ ```
341
+
342
+ ## Exit codes
343
+
344
+ `boundver verify` uses structured exit codes for reliable CI scripting:
345
+
346
+ | Code | Meaning |
347
+ |------|---------|
348
+ | `0` | Lockfile matches current repo state |
349
+ | `1` | Lockfile is out of date (fingerprint mismatches found) |
350
+ | `2` | Usage error (unknown component, config missing, etc.) |
351
+
352
+ `validate-config` exits `0` on success, `1` on validation errors.
353
+ `generate` exits `0` on success, `1` on config/generation error.
354
+
355
+ ## Design decisions
356
+
357
+ - **No external dependencies.** Only Git and Python stdlib. Runs anywhere Python 3.8+ and Git are available.
358
+ - **Deterministic output.** Canonical JSON (sorted keys, compact separators) ensures two machines produce identical hashes from identical repo state.
359
+ - **Canonical exact hashing across source modes.** `exact` uses one canonical SHA-256 file-content digest model for `head`, `index`, and `working-tree`, enabling direct cross-source comparison.
360
+ - **Config/lockfile split.** Config is human-maintained (what exists). Lockfile is machine-generated (current state). Mirrors `package.json` / `package-lock.json`.
361
+ - **Language-agnostic boundaries.** Instead of parsing ASTs, you declare which files constitute the public boundary. Works with any language or artifact format.
362
+
363
+ ## Examples
364
+
365
+ - `examples/openapi/`
366
+ - `examples/json-file/`
367
+ - `examples/implicit-and-leaf/`
368
+ - `examples/python-package/`
369
+ - `examples/typescript-package/`
370
+
371
+ ## Documentation
372
+
373
+ - [Getting started](docs/getting-started.md) — install, first config, first lockfile, CI setup
374
+ - [Gradual adoption guide](docs/gradual-adoption.md) — incremental adoption from one component to full coverage
375
+ - [CI cookbook](docs/ci-cookbook.md) — GitHub Actions, cache keys, GitLab, pre-commit
376
+ - [Why boundver?](docs/WHY_BOUNDVER.md) — tool comparison and positioning
377
+ - [Custom vs public providers](docs/public-vs-custom-providers.md) — when to use `custom.*`
378
+ - [Lockfile merge handling](docs/LOCKFILE_MERGE.md) — resolving merge conflicts
379
+
380
+ ## Validation dependencies
381
+
382
+ - **Runtime dependencies:** none (stdlib + git only).
383
+ - **Optional enhanced schema validation:** install `jsonschema` for stricter JSON Schema engine checks in `validate-config`.
384
+ - **Optional enhanced YAML extraction:** install `PyYAML` for robust YAML parsing in version extraction.
385
+
386
+ ```bash
387
+ pip install "boundver[schema]"
388
+ pip install "boundver[yaml]"
389
+ ```
390
+
391
+ Without `jsonschema`, boundver still runs and applies built-in semantic validation checks.
392
+
393
+ ## Release
394
+
395
+ - PyPI publish workflow: `.github/workflows/publish.yml`
396
+ - Trigger: push a version tag matching `v*` (for example `v0.3.0`)
397
+
398
+ ## Source modes
399
+
400
+ | Mode | File list | Content read from | Default for |
401
+ |------|-----------|-------------------|-------------|
402
+ | `head` | `git ls-tree HEAD` | committed git blobs | `generate`, `verify`, `status`, `why` |
403
+ | `index` | `git ls-files --cached` | staged blobs | — |
404
+ | `working-tree` | `git ls-files` (tracked) | disk bytes (CRLF→LF) | `explain` |
405
+
406
+ ### Important: `working-tree` only sees tracked files
407
+
408
+ `--source=working-tree` hashes the **on-disk content** of files that are already tracked by git.
409
+ It does **not** include untracked files. If you just created a new file but haven't run
410
+ `git add`, that file will not appear in any fingerprint until it is tracked.
411
+
412
+ This matters most during:
413
+
414
+ - **Initial setup** — run `git add .` before `boundver generate --source working-tree`.
415
+ - **Adding new boundary files** — a new `openapi.yaml` won't affect digests until tracked.
416
+ - **CI with uncommitted generated files** — prefer `--source head` (the default) in CI.
417
+
418
+ ### Ignore behavior
419
+
420
+ `--source=working-tree` prefers Git-backed tracked-file enumeration (`git ls-files`) when available.
421
+ In non-git fallback contexts, local file traversal is used.
422
+ Symlinks are hashed as link-target text (not dereferenced bytes) for cross-source consistency.
423
+
424
+ ## Requirements
425
+
426
+ - Python 3.8+
427
+ - Git
428
+ - No pip packages needed
429
+
430
+ ## Hash guardrails
431
+
432
+ To avoid pathological repository scans, hashing enforces built-in guardrails:
433
+
434
+ - maximum files hashed per digest: `50,000`
435
+ - maximum size per hashed file: `50 MiB`
436
+
437
+ If exceeded, boundver records explicit digest errors on affected components.
438
+
439
+ ## License
440
+
441
+ MIT