softschema 0.2.0__tar.gz → 0.2.2__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.
Files changed (58) hide show
  1. {softschema-0.2.0 → softschema-0.2.2}/AGENTS.md +1 -1
  2. {softschema-0.2.0 → softschema-0.2.2}/PKG-INFO +12 -13
  3. {softschema-0.2.0 → softschema-0.2.2}/README.md +11 -12
  4. {softschema-0.2.0 → softschema-0.2.2}/docs/development.md +10 -12
  5. {softschema-0.2.0 → softschema-0.2.2}/docs/e2e-testing.runbook.md +31 -13
  6. {softschema-0.2.0 → softschema-0.2.2}/docs/installation.md +8 -8
  7. {softschema-0.2.0 → softschema-0.2.2}/docs/publishing.md +25 -10
  8. {softschema-0.2.0 → softschema-0.2.2}/docs/softschema-guide.md +11 -12
  9. {softschema-0.2.0 → softschema-0.2.2}/docs/softschema-python-design.md +4 -5
  10. {softschema-0.2.0 → softschema-0.2.2}/docs/softschema-spec.md +22 -10
  11. {softschema-0.2.0 → softschema-0.2.2}/docs/softschema-typescript-design.md +18 -0
  12. softschema-0.2.2/packages/python/README.md +48 -0
  13. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/cli.py +51 -19
  14. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/errors.py +52 -2
  15. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/models.py +1 -1
  16. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/validate.py +1 -1
  17. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_cli.py +35 -9
  18. softschema-0.2.2/packages/python/tests/test_errors.py +88 -0
  19. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_movie_page_example.py +6 -3
  20. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_soft_field.py +4 -9
  21. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_warning_codes.py +0 -5
  22. {softschema-0.2.0 → softschema-0.2.2}/pyproject.toml +9 -6
  23. {softschema-0.2.0 → softschema-0.2.2}/tests/golden/README.md +21 -15
  24. softschema-0.2.0/packages/python/README.md +0 -31
  25. softschema-0.2.0/packages/python/tests/test_errors.py +0 -51
  26. {softschema-0.2.0 → softschema-0.2.2}/.gitignore +0 -0
  27. {softschema-0.2.0 → softschema-0.2.2}/LICENSE +0 -0
  28. {softschema-0.2.0 → softschema-0.2.2}/docs/publishing-npm.md +0 -0
  29. {softschema-0.2.0 → softschema-0.2.2}/examples/__init__.py +0 -0
  30. {softschema-0.2.0 → softschema-0.2.2}/examples/movie_page/README.md +0 -0
  31. {softschema-0.2.0 → softschema-0.2.2}/examples/movie_page/__init__.py +0 -0
  32. {softschema-0.2.0 → softschema-0.2.2}/examples/movie_page/host_integration.py +0 -0
  33. {softschema-0.2.0 → softschema-0.2.2}/examples/movie_page/model.py +0 -0
  34. {softschema-0.2.0 → softschema-0.2.2}/examples/movie_page/movie-page.schema.yaml +0 -0
  35. {softschema-0.2.0 → softschema-0.2.2}/examples/movie_page/spirited-away.md +0 -0
  36. {softschema-0.2.0 → softschema-0.2.2}/examples/parity/__init__.py +0 -0
  37. {softschema-0.2.0 → softschema-0.2.2}/examples/parity/model.py +0 -0
  38. {softschema-0.2.0 → softschema-0.2.2}/examples/parity/parity.schema.yaml +0 -0
  39. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/__init__.py +0 -0
  40. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/canonicalize.py +0 -0
  41. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/compile.py +0 -0
  42. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/generate.py +0 -0
  43. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/py.typed +0 -0
  44. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/registry.py +0 -0
  45. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/schema_view.py +0 -0
  46. {softschema-0.2.0 → softschema-0.2.2}/packages/python/src/softschema/soft_field.py +0 -0
  47. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_canonicalize.py +0 -0
  48. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_core.py +0 -0
  49. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_doc_topics_resolve.py +0 -0
  50. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_enforced_extras.py +0 -0
  51. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_generate.py +0 -0
  52. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_metadata_rules.py +0 -0
  53. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_parity.py +0 -0
  54. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_schema_view.py +0 -0
  55. {softschema-0.2.0 → softschema-0.2.2}/packages/python/tests/test_skill_mirror_drift.py +0 -0
  56. {softschema-0.2.0 → softschema-0.2.2}/packages/typescript/LICENSE +0 -0
  57. {softschema-0.2.0 → softschema-0.2.2}/packages/typescript/README.md +0 -0
  58. {softschema-0.2.0 → softschema-0.2.2}/skills/softschema/SKILL.md +0 -0
@@ -7,7 +7,7 @@ Start here:
7
7
  - [softschema Guide](docs/softschema-guide.md): standalone concept and adoption guide
8
8
  for humans and agents.
9
9
  - [softschema Spec](docs/softschema-spec.md): exact language-neutral artifact format.
10
- - [Movie page example](examples/movie_page/README.md): complete Python-backed example.
10
+ - [Movie Page Example](examples/movie_page/README.md): complete Python-backed example.
11
11
 
12
12
  softschema ships two interchangeable implementations with the same CLI and library
13
13
  surface: Python/Pydantic (`softschema`, `softschema-py`) and TypeScript/Zod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: softschema
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Soft schema conventions and validation tools for Markdown/YAML artifacts
5
5
  Project-URL: Homepage, https://github.com/jlevy/softschema
6
6
  Project-URL: Repository, https://github.com/jlevy/softschema
@@ -29,12 +29,12 @@ Description-Content-Type: text/markdown
29
29
  # softschema
30
30
 
31
31
  Soft schemas: gradual, practical validation for Markdown/YAML artifacts that mix prose
32
- and structured data built for humans and coding agents.
32
+ and structured data—built for humans and coding agents.
33
33
 
34
34
  ## Quick Start
35
35
 
36
36
  Try it anywhere, with nothing installed but [uv](https://docs.astral.sh/uv/) or Node.
37
- Print the bundled example artifact and its compiled schema, then validate the artifact
37
+ Print the bundled example artifact and its compiled schema, then validate—the artifact
38
38
  is fully self-describing, so no flags are needed:
39
39
 
40
40
  ```bash
@@ -63,7 +63,7 @@ flexible document context and machine-readable values.
63
63
  The idea is quite simple, but I’ve found it non-obvious enough that coding agents do not
64
64
  come up with this approach themselves.
65
65
 
66
- However if given the information and tools in this repo, soft schemas are unreasonably
66
+ However, if given the information and tools in this repo, soft schemas are unreasonably
67
67
  effective. Agents become far better at designing and building complex workflows that mix
68
68
  structured and unstructured data, such as document processing, data extraction,
69
69
  scientific or financial analyses, and many similar applications.
@@ -203,8 +203,8 @@ flags.
203
203
 
204
204
  Every key after `contract` is optional; a minimal artifact carries `contract` alone and
205
205
  binds its schema some other way (a `--schema` flag, or a host registry in library use).
206
- Contract IDs follow an enforced shape, `[namespace:]Name[/version]` — for example
207
- `example.movies:MoviePage/v1` or `com.acme.docs:IncidentReview/1.0` — naming a payload
206
+ Contract IDs follow an enforced shape, `[namespace:]Name[/version]`—for example
207
+ `example.movies:MoviePage/v1` or `com.acme.docs:IncidentReview/1.0`—naming a payload
208
208
  contract, not a class or import path.
209
209
 
210
210
  ## Validate
@@ -220,7 +220,7 @@ softschema validate doc.md --envelope incident # designate the payload
220
220
 
221
221
  `validate` reports structural (JSON Schema) and semantic (Pydantic/Zod model) results
222
222
  separately as deterministic JSON. Semantic validation loads a model with
223
- `--model module:Class` (Python) or `--model path:export` (Zod) note that `--model`
223
+ `--model module:Class` (Python) or `--model path:export` (Zod)—note that `--model`
224
224
  imports and executes local code, so use it only with trusted models; a compiled schema
225
225
  via `--schema` executes nothing and is the safe path for untrusted input.
226
226
 
@@ -232,8 +232,8 @@ Two supported ways to consume softschema; pick by use:
232
232
  fast, offline, and the only way to `import` it):
233
233
 
234
234
  ```bash
235
- uv add --dev softschema==0.2.0 # Python
236
- npm install -D softschema@0.2.0 # Node (or: bun add -d)
235
+ uv add --dev softschema==0.2.2 # Python
236
+ npm install -D softschema@0.2.2 # Node (or: bun add -d)
237
237
  ```
238
238
 
239
239
  - **Zero-install** for one-off checks and agent bootstrap:
@@ -243,10 +243,9 @@ Two supported ways to consume softschema; pick by use:
243
243
  npx softschema@latest --help
244
244
  ```
245
245
 
246
- The rule of thumb: if softschema runs more than once, or in CI, or you import it pin
247
- it. For a quick check or an agent bootstrapping with nothing installed, use a
248
- zero-install runner.
249
- See [Installation](docs/installation.md) for details, including the supply-chain
246
+ The rule of thumb: if softschema runs more than once, or in CI, or you import it—pin it.
247
+ For a quick check or an agent bootstrapping with nothing installed, use a zero-install
248
+ runner. See [Installation](docs/installation.md) for details, including the supply-chain
250
249
  cool-off that makes `@latest` safe to recommend.
251
250
 
252
251
  ## Use as a Library
@@ -1,12 +1,12 @@
1
1
  # softschema
2
2
 
3
3
  Soft schemas: gradual, practical validation for Markdown/YAML artifacts that mix prose
4
- and structured data built for humans and coding agents.
4
+ and structured data—built for humans and coding agents.
5
5
 
6
6
  ## Quick Start
7
7
 
8
8
  Try it anywhere, with nothing installed but [uv](https://docs.astral.sh/uv/) or Node.
9
- Print the bundled example artifact and its compiled schema, then validate the artifact
9
+ Print the bundled example artifact and its compiled schema, then validate—the artifact
10
10
  is fully self-describing, so no flags are needed:
11
11
 
12
12
  ```bash
@@ -35,7 +35,7 @@ flexible document context and machine-readable values.
35
35
  The idea is quite simple, but I’ve found it non-obvious enough that coding agents do not
36
36
  come up with this approach themselves.
37
37
 
38
- However if given the information and tools in this repo, soft schemas are unreasonably
38
+ However, if given the information and tools in this repo, soft schemas are unreasonably
39
39
  effective. Agents become far better at designing and building complex workflows that mix
40
40
  structured and unstructured data, such as document processing, data extraction,
41
41
  scientific or financial analyses, and many similar applications.
@@ -175,8 +175,8 @@ flags.
175
175
 
176
176
  Every key after `contract` is optional; a minimal artifact carries `contract` alone and
177
177
  binds its schema some other way (a `--schema` flag, or a host registry in library use).
178
- Contract IDs follow an enforced shape, `[namespace:]Name[/version]` — for example
179
- `example.movies:MoviePage/v1` or `com.acme.docs:IncidentReview/1.0` — naming a payload
178
+ Contract IDs follow an enforced shape, `[namespace:]Name[/version]`—for example
179
+ `example.movies:MoviePage/v1` or `com.acme.docs:IncidentReview/1.0`—naming a payload
180
180
  contract, not a class or import path.
181
181
 
182
182
  ## Validate
@@ -192,7 +192,7 @@ softschema validate doc.md --envelope incident # designate the payload
192
192
 
193
193
  `validate` reports structural (JSON Schema) and semantic (Pydantic/Zod model) results
194
194
  separately as deterministic JSON. Semantic validation loads a model with
195
- `--model module:Class` (Python) or `--model path:export` (Zod) note that `--model`
195
+ `--model module:Class` (Python) or `--model path:export` (Zod)—note that `--model`
196
196
  imports and executes local code, so use it only with trusted models; a compiled schema
197
197
  via `--schema` executes nothing and is the safe path for untrusted input.
198
198
 
@@ -204,8 +204,8 @@ Two supported ways to consume softschema; pick by use:
204
204
  fast, offline, and the only way to `import` it):
205
205
 
206
206
  ```bash
207
- uv add --dev softschema==0.2.0 # Python
208
- npm install -D softschema@0.2.0 # Node (or: bun add -d)
207
+ uv add --dev softschema==0.2.2 # Python
208
+ npm install -D softschema@0.2.2 # Node (or: bun add -d)
209
209
  ```
210
210
 
211
211
  - **Zero-install** for one-off checks and agent bootstrap:
@@ -215,10 +215,9 @@ Two supported ways to consume softschema; pick by use:
215
215
  npx softschema@latest --help
216
216
  ```
217
217
 
218
- The rule of thumb: if softschema runs more than once, or in CI, or you import it pin
219
- it. For a quick check or an agent bootstrapping with nothing installed, use a
220
- zero-install runner.
221
- See [Installation](docs/installation.md) for details, including the supply-chain
218
+ The rule of thumb: if softschema runs more than once, or in CI, or you import it—pin it.
219
+ For a quick check or an agent bootstrapping with nothing installed, use a zero-install
220
+ runner. See [Installation](docs/installation.md) for details, including the supply-chain
222
221
  cool-off that makes `@latest` safe to recommend.
223
222
 
224
223
  ## Use as a Library
@@ -1,10 +1,9 @@
1
1
  # Development
2
2
 
3
3
  First-time setup of `uv` and Python is covered in [Installation](installation.md).
4
- Release workflow and PyPI steps are covered in
5
- [Publishing](https://github.com/jlevy/softschema/blob/main/docs/publishing.md).
6
- The full validation passthe automated sweep run locally plus the manual
7
- clean-environment checks CI cannot run — is codified in the
4
+ Release workflow and PyPI steps are covered in [Publishing](publishing.md).
5
+ The full validation pass—the automated sweep run locally plus the manual
6
+ clean-environment checks CI cannot runis codified in the
8
7
  [end-to-end testing runbook](e2e-testing.runbook.md).
9
8
 
10
9
  Set up the repo (Python deps, Node tooling for hooks, and the git hooks themselves):
@@ -39,7 +38,7 @@ uv build
39
38
 
40
39
  The Python package is built from `packages/python/src/softschema`.
41
40
 
42
- ### Git hooks (this repo)
41
+ ### Git Hooks (This Repo)
43
42
 
44
43
  Hooks are managed by [lefthook](https://lefthook.dev) (`lefthook.yml`), installed with
45
44
  `make hooks-install`. The `pre-commit` hook formats staged changes so commits stay
@@ -69,8 +68,7 @@ bun run publint # lint the publishable package layout (run after build)
69
68
 
70
69
  It publishes to npm as `softschema` (the same name as the PyPI package) and exposes the
71
70
  CLI as both `softschema` and `softschema-ts`. The two packages **release together under
72
- one version number**; see
73
- [Publishing](https://github.com/jlevy/softschema/blob/main/docs/publishing.md).
71
+ one version number**; see [Publishing](publishing.md).
74
72
 
75
73
  Documentation changes should follow `common-doc-guidelines.md`
76
74
  (github.com/jlevy/practical-prose).
@@ -81,7 +79,7 @@ exact format rules in `docs/softschema-spec.md`.
81
79
 
82
80
  Two softschema checks belong in CI for any project that depends on the package.
83
81
 
84
- ### Compiled schema drift
82
+ ### Compiled Schema Drift
85
83
 
86
84
  A committed `.schema.yaml` file is *generated, but committed*. Run
87
85
  `softschema compile ... --check` to fail the build when the committed compiled schema
@@ -96,10 +94,10 @@ uv run softschema compile examples.movie_page.model:MoviePage \
96
94
  Fix on drift: re-run the same command without `--check` and commit the regenerated
97
95
  compiled schema.
98
96
 
99
- ### Generated-section drift
97
+ ### Generated-Section Drift
100
98
 
101
99
  If any Markdown file contains `softschema:generated` markers (see the guide’s “Keep
102
- Schema Tables In Sync With Generated Sections” playbook), run the re-renderer in
100
+ Schema Tables in Sync with Generated Sections” playbook), run the re-renderer in
103
101
  `--check` mode so CI fails when the committed section lags behind the schema:
104
102
 
105
103
  ```bash
@@ -108,7 +106,7 @@ uv run softschema generate examples/movie_page/README.md --check
108
106
 
109
107
  Fix on drift: re-run without `--check` and commit the regenerated section.
110
108
 
111
- ### Artifact validation
109
+ ### Artifact Validation
112
110
 
113
111
  Run `softschema validate` against every artifact under version control whose contract is
114
112
  fully defined:
@@ -154,7 +152,7 @@ jobs:
154
152
  --schema examples/movie_page/movie-page.schema.yaml
155
153
  ```
156
154
 
157
- ### Pre-commit hook
155
+ ### Pre-Commit Hook
158
156
 
159
157
  For local runs before push, a `pre-commit` config that calls the same drift check:
160
158
 
@@ -38,7 +38,7 @@ The automated half of the story runs on every push and PR
38
38
  | Python-vs-TypeScript byte parity | `cross-impl` job |
39
39
  | Tag ↔ `package.json` version match | `publish.yml` guard |
40
40
 
41
- **Not** covered by CI the reason this runbook exists:
41
+ **Not** covered by CI—the reason this runbook exists:
42
42
 
43
43
  - Installing the built wheel and npm tarball into clean environments and running them
44
44
  (entry points, bin shebang, bundled docs/skill resources).
@@ -86,7 +86,7 @@ This phase proves the **packaged artifacts** from a real install with no repo on
86
86
  the console entry points resolve, the bin shebang runs under plain Node, and the bundled
87
87
  docs/skill resources load from inside the installed package.
88
88
 
89
- ### Python wheel in a fresh venv
89
+ ### Python Wheel in a Fresh Venv
90
90
 
91
91
  ```bash
92
92
  uv build
@@ -103,10 +103,10 @@ venv/bin/softschema validate spirited-away.md # exit 0, structural ok, zero fl
103
103
  cd - && rm -rf "$tmp"
104
104
  ```
105
105
 
106
- ### npm tarball under plain Node
106
+ ### npm Tarball under Plain Node
107
107
 
108
- `npm pack` runs `prepublishOnly` (build + publint), so the tarball is freshly rebuilt
109
- from source the same artifact `npm publish` would upload.
108
+ `npm pack` runs `prepublishOnly` (build and publint), so the tarball is freshly rebuilt
109
+ from source—the same artifact `npm publish` would upload.
110
110
  Run it under `node` (>= 22.12), the runtime npm users get, not under bun:
111
111
 
112
112
  ```bash
@@ -153,7 +153,7 @@ diff movie-page.schema.yaml ts-schema.yaml # byte-identical
153
153
  One trap to know about: the artifact names its own schema
154
154
  (`schema: movie-page.schema.yaml`), so the schema **must** be saved under that exact
155
155
  filename next to the artifact.
156
- Redirecting it anywhere else makes `validate` fail with `schema_missing` — correct
156
+ Redirecting it anywhere else makes `validate` fail with `schema_missing`—correct
157
157
  resolution behavior, broken quickstart.
158
158
  If the README quickstart ever changes, re-verify the new text the same way before
159
159
  merging it.
@@ -177,21 +177,39 @@ present on disk.
177
177
 
178
178
  The tagging and release mechanics live in [publishing.md](publishing.md); this phase is
179
179
  what to run **after** `publish.yml` reports success for both registries.
180
- A just-published version sits inside the 14-day supply-chain cool-off, so pin the
181
- version and override the cutoff for the Python side:
180
+
181
+ A just-published version sits inside the 14-day supply-chain cool-off, so the Python
182
+ smoke test must override the cutoff.
183
+ Three details make the difference between a real check and a false “no version” failure:
184
+
185
+ - **Override the cutoff to *now*, not midnight.** `--exclude-newer-package` excludes
186
+ releases newer than the given instant.
187
+ A date-only value (`$(date +%F)`) is midnight UTC, which is *before* a version
188
+ published later the same day—so it excludes the very release you are verifying.
189
+ Use a full timestamp at the current moment (`$(date -u +%Y-%m-%dT%H:%M:%SZ)`); “now”
190
+ is always after the publish.
191
+ - **Run from outside the repo.** From the project tree, uv reads its `[tool.uv]`
192
+ settings and applies the project’s own cool-off; a neutral directory (`cd /tmp`)
193
+ avoids that.
194
+ - **`--refresh`** bypasses uv’s cached package index, which may not yet list a release
195
+ published seconds ago.
182
196
 
183
197
  ```bash
184
- uvx --exclude-newer-package "softschema=$(date +%F)" softschema@X.Y.Z --help
185
- npx softschema@X.Y.Z --help
198
+ cd /tmp
199
+ ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
200
+ uvx --refresh --exclude-newer-package "softschema=$ts" softschema@X.Y.Z --version
201
+ npx -y softschema@X.Y.Z --version
186
202
  ```
187
203
 
188
204
  Then run the quickstart literally against the published artifacts:
189
205
 
190
206
  ```bash
191
207
  cd "$(mktemp -d)"
192
- uvx --exclude-newer-package "softschema=$(date +%F)" softschema@X.Y.Z docs example-artifact > spirited-away.md
193
- uvx --exclude-newer-package "softschema=$(date +%F)" softschema@X.Y.Z docs example-schema > movie-page.schema.yaml
194
- uvx --exclude-newer-package "softschema=$(date +%F)" softschema@X.Y.Z validate spirited-away.md
208
+ ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
209
+ uvx() { command uvx --refresh --exclude-newer-package "softschema=$ts" "$@"; }
210
+ uvx softschema@X.Y.Z docs example-artifact > spirited-away.md
211
+ uvx softschema@X.Y.Z docs example-schema > movie-page.schema.yaml
212
+ uvx softschema@X.Y.Z validate spirited-away.md
195
213
  ```
196
214
 
197
215
  Once the release ages past the cool-off, the unpinned `@latest` forms in the README
@@ -9,11 +9,11 @@ Pick the runtime you already have; both validate against the same canonical sche
9
9
  | | Pin as a dependency | Zero-install (`uvx` / `npx`) |
10
10
  | --- | --- | --- |
11
11
  | **For** | Projects, CI gates, library use | One-off checks, agent bootstrap |
12
- | **Reproducible** | Yes the version is locked in `uv.lock` / `package-lock.json` | Only if you pin the runner (`uvx softschema@0.2.0`) |
13
- | **Fast / offline** | Yes the binary is already on disk | Cold-start fetch; needs the network |
14
- | **Library import** | Yes the only way | No |
12
+ | **Reproducible** | Yes—the version is locked in `uv.lock` / `package-lock.json` | Only if you pin the runner (`uvx softschema@0.2.2`) |
13
+ | **Fast / offline** | Yes—the binary is already on disk | Cold-start fetch; needs the network |
14
+ | **Library import** | Yes—the only way | No |
15
15
 
16
- The rule of thumb: **if softschema runs more than once, or in CI, or you import it pin
16
+ The rule of thumb: **if softschema runs more than once, or in CI, or you import it—pin
17
17
  it as a dependency.
18
18
  For a quick one-off or an agent bootstrapping with nothing installed,
19
19
  use a zero-install runner**, pinned where the result must be repeatable.
@@ -23,14 +23,14 @@ use a zero-install runner**, pinned where the result must be repeatable.
23
23
  Python (a dev dependency, or a persistent user tool):
24
24
 
25
25
  ```bash
26
- uv add --dev softschema==0.2.0 # project dev dependency; run via `uv run softschema`
26
+ uv add --dev softschema==0.2.2 # project dev dependency; run via `uv run softschema`
27
27
  uv tool install softschema # persistent CLI on your PATH
28
28
  ```
29
29
 
30
30
  Node (>= 22.12):
31
31
 
32
32
  ```bash
33
- npm install -D softschema@0.2.0 # or: pnpm add -D / bun add -d
33
+ npm install -D softschema@0.2.2 # or: pnpm add -D / bun add -d
34
34
  npx softschema --help # resolves the local pinned copy
35
35
  ```
36
36
 
@@ -41,7 +41,7 @@ uvx softschema@latest --help # Python implementation, ephemeral
41
41
  npx softschema@latest --help # Node implementation, ephemeral
42
42
  ```
43
43
 
44
- Use `uvx softschema@0.2.0` / `npx -y softschema@0.2.0` when a repeated ad-hoc run must
44
+ Use `uvx softschema@0.2.2` / `npx -y softschema@0.2.2` when a repeated ad-hoc run must
45
45
  resolve the same version every time.
46
46
 
47
47
  ## Quick Start for Agents
@@ -79,7 +79,7 @@ A gate such as npm’s `--before` / `NPM_CONFIG_BEFORE`, pnpm’s `minimumReleas
79
79
  uv’s `--exclude-newer` resolves `@latest` to the newest release old enough to pass, so
80
80
  you get the freshest vetted version without pinning.
81
81
  A just-published version installs only once it ages past the cutoff.
82
- Consumer projects should still pin their own dependency a project’s reproducibility is
82
+ Consumer projects should still pin their own dependency—a project’s reproducibility is
83
83
  the project’s responsibility, not the publisher’s cool-off.
84
84
  See [supply-chain-hardening](https://github.com/jlevy/supply-chain-hardening) for the
85
85
  rationale.
@@ -102,16 +102,25 @@ For each release of version `X.Y.Z`:
102
102
  git push origin vX.Y.Z
103
103
  ```
104
104
 
105
+ If the environment blocks pushing tags over git (some hosted or proxied checkouts
106
+ allow branch pushes but reject tag refs with a `403`), skip the `git push` and let
107
+ the next step create the tag: passing `--target` with the merge-commit SHA to
108
+ `gh release create` creates the tag at that commit as part of publishing the release.
109
+
105
110
  6. **Create the GitHub release** (this triggers the publish workflow, which publishes to
106
111
  both PyPI and npm over OIDC in one run):
107
112
 
108
113
  ```bash
109
- gh release create vX.Y.Z --title "softschema X.Y.Z" --notes "..."
114
+ gh release create vX.Y.Z --title "softschema X.Y.Z" --notes-file notes.md
115
+ # when the tag was not pushed in step 5, create it here at the merge commit:
116
+ gh release create vX.Y.Z --target MERGE_SHA --title "softschema X.Y.Z" --notes-file notes.md
110
117
  ```
111
118
 
112
- Write the notes from the release’s behavior/breaking changes (see prior releases for
113
- the shape: a one-line summary, then “Behavior changes”, “Fixes and hardening”, and
114
- “Testing and release safety” sections).
119
+ Write the notes from the release’s behavior and breaking changes (see prior releases
120
+ for the shape: a one-line summary, then sections for breaking changes, new features,
121
+ fixes and hardening, and testing and release safety).
122
+ Use a notes file for multi-section notes; the inline `--notes` flag is fine only for
123
+ a one-liner.
115
124
 
116
125
  7. **Watch the workflow** until both `Publish to PyPI` and `Publish to npm` report
117
126
  success:
@@ -124,14 +133,20 @@ For each release of version `X.Y.Z`:
124
133
  artifacts (end-to-end runbook Phase 5):
125
134
 
126
135
  ```bash
127
- uvx --exclude-newer-package "softschema=$(date +%F)" softschema@X.Y.Z --help
128
- npx softschema@X.Y.Z --help
136
+ cd /tmp # outside the repo: do not inherit its [tool.uv] cool-off
137
+ ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" # cutoff = now, so a same-day publish is included
138
+ uvx --refresh --exclude-newer-package "softschema=$ts" softschema@X.Y.Z --version
139
+ npx -y softschema@X.Y.Z --version
129
140
  ```
130
141
 
131
- (The `--exclude-newer-package` override is needed because the publish workflow sets
132
- `UV_EXCLUDE_NEWER` to a 14-day cool-off cutoff for supply-chain hygiene.
133
- Anyone consuming the freshly published version with that policy sees the same
134
- friction for ~14 days; this is intentional.)
142
+ The `--exclude-newer-package` override is needed because a freshly published version
143
+ sits inside the 14-day supply-chain cool-off; anyone consuming it under that policy
144
+ sees the same friction for ~14 days, which is intentional.
145
+ Three details matter for the smoke test: use a full timestamp at the current moment
146
+ (a date-only `$(date +%F)` is midnight UTC and excludes a version published later the
147
+ same day), run from outside the repo so uv does not apply the project’s own cool-off,
148
+ and pass `--refresh` so uv does not serve a cached index that predates the publish
149
+ (see runbook Phase 5).
135
150
 
136
151
  <!-- This document follows common-doc-guidelines.md.
137
152
  See github.com/jlevy/practical-prose and review guidelines before editing.
@@ -50,7 +50,7 @@ against a named contract.
50
50
  The practice is language neutral; another project could implement it with any of JSON
51
51
  Schema, database records, or hand-written validators.
52
52
 
53
- ## When To Use It
53
+ ## When to Use It
54
54
 
55
55
  Reach for softschema when all three of these hold:
56
56
 
@@ -178,7 +178,7 @@ Picking a version:
178
178
  - Additive, optional fields usually don’t need a version bump.
179
179
  - Keep versions short (`v1`, `v2`, `1.0`).
180
180
 
181
- ## Playbook: Adopt softschema For An Existing Markdown Artifact
181
+ ## Playbook: Adopt softschema for an Existing Markdown Artifact
182
182
 
183
183
  Start with one document type, not a whole repository:
184
184
 
@@ -234,7 +234,7 @@ The body stays unchanged.
234
234
  A consumer that aggregates incidents now reads `incident.affected_service` from YAML
235
235
  instead of trying to grep the body.
236
236
 
237
- ## Playbook: Choose Which Values Belong In YAML
237
+ ## Playbook: Choose Which Values Belong in YAML
238
238
 
239
239
  The hardest call in adoption is “what goes in YAML, what stays prose?”
240
240
  Use the promotion path step by step:
@@ -270,7 +270,7 @@ file. The contract ID stays; only the shell changes.
270
270
  A field is ready to promote when: a consumer extracts it, the value type is stable, and
271
271
  emitting it consistently is easier than parsing it from prose.
272
272
 
273
- ## Playbook: Inline Frontmatter Vs Companion Data
273
+ ## Playbook: Inline Frontmatter vs. Companion Data
274
274
 
275
275
  The rule of thumb is **inline-small, companion-large**:
276
276
 
@@ -397,7 +397,7 @@ The `result` object reports `structural` (JSON Schema) and `semantic` (Pydantic)
397
397
  separately, so callers can distinguish “shape was wrong” from “cross-field invariant
398
398
  failed” without parsing error strings.
399
399
 
400
- ## Playbook: Annotate Fields With SoftField
400
+ ## Playbook: Annotate Fields with SoftField
401
401
 
402
402
  `SoftField` is an optional wrapper over Pydantic’s `Field` that records per-field
403
403
  authoring metadata (`group`, `owner`, `tier`, `instruction`, `examples`, `aliases`,
@@ -435,7 +435,7 @@ controlled-vocabulary case.
435
435
  The recognized keys and the full call shape are documented in
436
436
  [Python Package Design](softschema-python-design.md).
437
437
 
438
- ## Playbook: Keep Schema Tables In Sync With Generated Sections
438
+ ## Playbook: Keep Schema Tables in Sync with Generated Sections
439
439
 
440
440
  When a controlled vocabulary or field list appears in two places (a schema and a runbook
441
441
  table), it will drift.
@@ -478,7 +478,7 @@ A worked example lives in
478
478
  [examples/movie_page/README.md](../examples/movie_page/README.md); the “Schema Enums”
479
479
  section is regenerated from the movie schema.
480
480
 
481
- ## Playbook: Validate In CI
481
+ ## Playbook: Validate in CI
482
482
 
483
483
  Pin softschema as a dev dependency so CI uses a known version:
484
484
 
@@ -521,7 +521,7 @@ Two checks belong in CI:
521
521
  For a full GitHub Actions snippet and a `pre-commit` hook example, see the “Continuous
522
522
  integration” section of [docs/development.md](development.md).
523
523
 
524
- ## Playbook: Migrate An Existing Artifact
524
+ ## Playbook: Migrate an Existing Artifact
525
525
 
526
526
  Take an artifact that doesn’t fit the canonical shape and bring it in line.
527
527
 
@@ -631,7 +631,7 @@ The consumer reads YAML now.
631
631
  For each migration, set `status: soft` or `permissive` initially.
632
632
  Tighten only after existing instances validate cleanly.
633
633
 
634
- ## Playbook: Use softschema With Agents
634
+ ## Playbook: Use softschema with Agents
635
635
 
636
636
  softschema is built for documents that humans and coding agents both write.
637
637
  A few patterns help agents do the right thing:
@@ -686,7 +686,7 @@ A few patterns help agents do the right thing:
686
686
  - **Promoting prose that no consumer reads.** Leave background, analysis, and caveats as
687
687
  prose. Promote a value only when a code path, QA check, or aggregation reads it.
688
688
 
689
- ## Relationship To The Packages
689
+ ## Relationship to the Packages
690
690
 
691
691
  Two interchangeable packages implement the language-neutral pattern at exact behavioral
692
692
  parity, Python/Pydantic and TypeScript/Zod.
@@ -759,8 +759,7 @@ For Python-specific module layout, public API decisions, and dependency boundary
759
759
  - [Movie Page Example](../examples/movie_page/README.md): the complete public example
760
760
  backing the snippets above.
761
761
  - [Installation](installation.md), [Development](development.md), and
762
- [Publishing](https://github.com/jlevy/softschema/blob/main/docs/publishing.md):
763
- workflow docs.
762
+ [Publishing](publishing.md): workflow docs.
764
763
 
765
764
  <!-- This document follows common-doc-guidelines.md.
766
765
  See github.com/jlevy/practical-prose and review guidelines before editing.
@@ -48,8 +48,7 @@ It should orient a new visitor, show the main example, and point to the guide an
48
48
  rather than repeating their content.
49
49
 
50
50
  Template workflow docs keep template names: [development.md](development.md),
51
- [installation.md](installation.md), and
52
- [publishing.md](https://github.com/jlevy/softschema/blob/main/docs/publishing.md).
51
+ [installation.md](installation.md), and [publishing.md](publishing.md).
53
52
  Package design details live in this document unless they grow large enough to justify a
54
53
  separate reference.
55
54
 
@@ -183,7 +182,7 @@ A relative `schema_path` is resolved against only the document directory and the
183
182
  working directory, so resolution is predictable and never binds to an unrelated compiled
184
183
  schema in a parent directory.
185
184
 
186
- ### Engine-neutral structural errors
185
+ ### Engine-Neutral Structural Errors
187
186
 
188
187
  Structural validation runs through `jsonschema`, but the error records it returns are
189
188
  synthesized by softschema, not passed through from the library.
@@ -204,7 +203,7 @@ to stderr; the package version comes from `importlib.metadata` via
204
203
  `softschema-py` (the latter pairs with a future `softschema-ts` for the shared golden
205
204
  corpus).
206
205
 
207
- ### Skill resource and mirrors
206
+ ### Skill Resource and Mirrors
208
207
 
209
208
  `skills/softschema/SKILL.md` is the source skill.
210
209
  `softschema skill --install` writes full generated copies to
@@ -242,7 +241,7 @@ if any(w.code.startswith("document-") for w in result.warnings):
242
241
  A regression test (`tests/test_warning_codes.py`) holds the table to the enum: any new
243
242
  emitted code that isn’t a `WarningCode` member fails CI.
244
243
 
245
- ### Structural error kinds
244
+ ### Structural Error Kinds
246
245
 
247
246
  `StructuralResult.errors[*].kind` uses a separate `snake_case` namespace because errors
248
247
  are blocking, not advisory.
@@ -41,7 +41,7 @@ These terms are used throughout; each is defined here before it appears in a rul
41
41
  | **metadata block** | The `softschema:` mapping in the frontmatter (or at the document root for pure YAML). It holds softschema’s own keys, not payload data. |
42
42
  | **payload** | The structured values a consumer reads, validated against a contract. |
43
43
  | **envelope** | The single top-level key whose value is the payload; the **envelope key** is its name (for example `movie:`). |
44
- | **contract** | The named payload contract — *what* the payload is. |
44
+ | **contract** | The named payload contract—*what* the payload is. |
45
45
  | **contract ID** | The string that names the contract (for example `example.movies:MoviePage/v1`). |
46
46
  | **model** | A Pydantic class or Zod schema: a language-specific *source* for a contract. |
47
47
  | **compiled schema** | The JSON Schema (written as YAML or JSON) that a model compiles to; the language-neutral form of the contract. |
@@ -212,7 +212,7 @@ An implementation must:
212
212
 
213
213
  For example, the movie artifact above carries both `title:` and `movie:`. With two
214
214
  non-`softschema` keys, inference does not apply, so the artifact declares
215
- `envelope: movie` in its metadata block — `title:` stays an uninterpreted host key, and
215
+ `envelope: movie` in its metadata block—`title:` stays an uninterpreted host key, and
216
216
  the artifact validates with no flags.
217
217
  A caller can still override with `--envelope` on a given run.
218
218
 
@@ -242,7 +242,7 @@ product-tag `namespace`, and short versions (`v1`, `1.0`). Examples:
242
242
  - `com.acme.docs:IncidentReview/1.0`
243
243
 
244
244
  A contract ID may correspond to a Pydantic class, a Zod export, a precompiled JSON
245
- Schema, a database record, or a hand-authored validator all equally valid.
245
+ Schema, a database record, or a hand-authored validator—all equally valid.
246
246
  It is not required to be an import path or a class name.
247
247
 
248
248
  ## Status Values
@@ -282,7 +282,7 @@ Markdown body prose and tables are reader-facing and never authoritative.
282
282
 
283
283
  A compiled schema is a generated validation contract, usually JSON Schema written as
284
284
  YAML. It is the language-neutral form of a contract: a Pydantic class or Zod schema
285
- compiles to it (provably identically the conformance machinery guarantees an equal
285
+ compiles to it (provably identically—the conformance machinery guarantees an equal
286
286
  `schema_sha256`), and any language can validate against it.
287
287
 
288
288
  An artifact may bind to its compiled schema with the optional `softschema.schema` key.
@@ -292,7 +292,7 @@ The compiled schema a validator uses is resolved in this precedence (highest fir
292
292
  library call.
293
293
  2. A host registry binding (a registered contract’s schema path; library path only).
294
294
  3. The `softschema.schema` document metadata.
295
- 4. None a metadata-only check (contract/status/envelope rules, no schema).
295
+ 4. None—a metadata-only check (contract/status/envelope rules, no schema).
296
296
 
297
297
  Host-controlled configuration outranks document metadata on purpose: a document must not
298
298
  silently redirect a host’s validation to a schema the host did not choose.
@@ -302,8 +302,8 @@ none, which is what lets a self-describing artifact validate with no flags.
302
302
  Resolution of a `softschema.schema` value is, by convention, relative to the document
303
303
  that carries it; this spec requires only that the value be a non-empty string and leaves
304
304
  the exact resolution to the host, because file layout is situational.
305
- (The reference CLIs accept only relative values in metadata an absolute path must use
306
- `--schema` — resolve them from the document’s directory, and reject a path whose
305
+ (The reference CLIs accept only relative values in metadata—an absolute path must use
306
+ `--schema`—resolve them from the document’s directory, and reject a path whose
307
307
  normalized result escapes both the document directory and the working directory.)
308
308
 
309
309
  A compiled schema is not a per-document companion data file.
@@ -335,6 +335,18 @@ A validator must reject:
335
335
  - undeclared payload fields rejected by the `enforced` strictness rule (see Status
336
336
  Values)
337
337
 
338
+ Validation output is deterministic across conforming implementations: structural error
339
+ records share an engine-neutral shape and message wording, and numbers — in an error
340
+ record’s `value`/`validator_value`, in a synthesized message, and in an echoed payload —
341
+ render in a canonical form, where a whole-valued number carries no trailing fraction
342
+ (`2`, not `2.0`).
343
+ Output is byte-identical for every number an implementation can represent exactly:
344
+ integers and whole-valued numbers within the IEEE-754 safe-integer range (`abs < 2^53`),
345
+ and ordinary floats.
346
+ A whole-valued magnitude at or beyond 2^53 is out of scope, because an arbitrary-precision
347
+ integer runtime and a double-only runtime cannot always render it identically; validated
348
+ payloads should avoid such literals.
349
+
338
350
  ## Generated Sections
339
351
 
340
352
  A conforming implementation may regenerate Markdown sections from a compiled schema
@@ -366,8 +378,8 @@ Recognized attributes:
366
378
  file path. This is a 0.2.0 change; a marker that still uses `contract="...path..."` is
367
379
  rejected with a message pointing at the rename.)
368
380
 
369
- The output is normative equal inputs produce byte-equal output, and an implementation
370
- is checked against this spec, not the other way around:
381
+ The output is normative—equal inputs produce byte-equal output, and an implementation is
382
+ checked against this spec, not the other way around:
371
383
 
372
384
  - **`enum_table`**: a GFM table with header row `| Field | Allowed values |`; one row
373
385
  per string-enum property of the schema, in the schema’s property order; the field name
@@ -377,7 +389,7 @@ is checked against this spec, not the other way around:
377
389
  branch is rendered); any other enum shape is skipped.
378
390
  With no enum-valued properties the single row is `| _(no enum fields)_ | _(none)_ |`.
379
391
  - **`field_list`**: one bullet per top-level property, in schema order:
380
- `- `name` (type, required): description` — the JSON type label, `required` or
392
+ `- `name` (type, required): description`—the JSON type label, `required` or
381
393
  `optional`, then `: description` only when the property has one.
382
394
  Nested properties are not listed (they appear through their parent’s type).
383
395
  With no properties the single bullet is `- _(no fields)_`.