masterwork 0.0.4__tar.gz → 0.0.5__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 (42) hide show
  1. {masterwork-0.0.4/masterwork.egg-info → masterwork-0.0.5}/PKG-INFO +33 -32
  2. {masterwork-0.0.4 → masterwork-0.0.5}/README.md +9 -8
  3. {masterwork-0.0.4 → masterwork-0.0.5}/README.pypi.md +31 -30
  4. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/__init__.py +1 -1
  5. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/measure.py +17 -0
  6. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/seal.py +77 -7
  7. {masterwork-0.0.4 → masterwork-0.0.5/masterwork.egg-info}/PKG-INFO +33 -32
  8. masterwork-0.0.5/masterwork.egg-info/requires.txt +1 -0
  9. {masterwork-0.0.4 → masterwork-0.0.5}/pyproject.toml +6 -2
  10. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_line.py +1 -1
  11. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_measure.py +18 -2
  12. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_seal.py +81 -0
  13. masterwork-0.0.4/masterwork.egg-info/requires.txt +0 -1
  14. {masterwork-0.0.4 → masterwork-0.0.5}/LICENSE +0 -0
  15. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/__main__.py +0 -0
  16. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/blind_label.py +0 -0
  17. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/campaign.py +0 -0
  18. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/cells.py +0 -0
  19. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/ceremony.py +0 -0
  20. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/gate.py +0 -0
  21. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/line.py +0 -0
  22. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/pairs.py +0 -0
  23. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/retain.py +0 -0
  24. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/rubric-example.json +0 -0
  25. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/starter/script.json +0 -0
  26. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork/starter/tales.md +0 -0
  27. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork.egg-info/SOURCES.txt +0 -0
  28. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork.egg-info/dependency_links.txt +0 -0
  29. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork.egg-info/entry_points.txt +0 -0
  30. {masterwork-0.0.4 → masterwork-0.0.5}/masterwork.egg-info/top_level.txt +0 -0
  31. {masterwork-0.0.4 → masterwork-0.0.5}/setup.cfg +0 -0
  32. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_badges.py +0 -0
  33. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_blind_label.py +0 -0
  34. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_campaign.py +0 -0
  35. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_cells.py +0 -0
  36. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_ceremony.py +0 -0
  37. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_cli.py +0 -0
  38. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_examples.py +0 -0
  39. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_gate.py +0 -0
  40. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_packaging.py +0 -0
  41. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_pairs.py +0 -0
  42. {masterwork-0.0.4 → masterwork-0.0.5}/tests/test_retain.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: masterwork
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: A production line for agent character: a sitting the model speaks for itself in, sealed candidates, frozen gates, and a verdict it is not allowed to give itself.
5
5
  Author: Codechu
6
6
  License-Expression: Apache-2.0
@@ -22,14 +22,14 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
22
  Requires-Python: >=3.10
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: journeyman-bench>=0.2.1
25
+ Requires-Dist: journeyman-bench<0.5.0,>=0.2.1
26
26
  Dynamic: license-file
27
27
 
28
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.4/assets/banner.jpg" alt="A craftsman slides a sealed folio across his bench onto the guild's empty judging table; earlier pieces stand turned to the wall behind him." width="100%"></p>
28
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/banner.jpg" alt="A craftsman slides a sealed folio across his bench onto the guild's empty judging table; earlier pieces stand turned to the wall behind him." width="100%"></p>
29
29
 
30
30
  # Masterwork
31
31
 
32
- [![python](https://img.shields.io/badge/python-3.10%2B-blue)](#run-it) [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/codechu/masterwork/blob/v0.0.4/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-1-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.4/CONTRIBUTING.md#ground-rules) [![status](https://img.shields.io/badge/status-unproven-lightgrey)](#status)
32
+ [![python](https://img.shields.io/badge/python-3.10%2B-blue)](#run-it) [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-1-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md#ground-rules) [![status](https://img.shields.io/badge/status-unproven-lightgrey)](#status)
33
33
 
34
34
  *Makes a character for a model. Does not score it.*
35
35
 
@@ -61,7 +61,7 @@ masterwork # what it is, and its stages
61
61
  masterwork gate gates/ # check the gate templates
62
62
  ```
63
63
 
64
- Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/first-run.md).
64
+ Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md).
65
65
 
66
66
  Or from a clone, with nothing installed at all:
67
67
 
@@ -117,7 +117,7 @@ record, and exits in a way that says *waiting*, not *broken*.
117
117
  | `HELD_WITHOUT_PURPOSE` | the spec does not say what the run decides | write `purpose`; if you cannot say what changes on each outcome, the run is not ready |
118
118
  | `HELD_AT_SEAL` | the piece is unreproducible, or the deployed copy differs | fix the seal or the deployment — never run "just to see" |
119
119
  | `HELD_AT_COMPLETENESS` | the grid is short | find the missing cells; if proceeding anyway, declare it |
120
- | `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.4/masterwork/blind_label.py); the line is waiting, not broken |
120
+ | `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/blind_label.py); the line is waiting, not broken |
121
121
  | `HELD_AT_MEASUREMENT` | the benchmark says the score is not comparable | judge from a separate endpoint, or declare and keep the stamp |
122
122
  | `HELD_AT_GATE` | a threshold sits inside its own noise | the gate is void; measure the band, write a new gate |
123
123
  | `HELD_WITHOUT_MEASUREMENT` | a rule names an axis and nothing measured it | judge with the benchmark, or declare the run diagnostic — a gate that was checked is not a gate that was applied |
@@ -169,15 +169,15 @@ live under `runs/`, which version control ignores, and the seal names
169
169
  everything needed to re-derive them. The run record and the benchmark's report
170
170
  are archived on purpose — both are kilobytes, and between them they carry the
171
171
  seal, the grid, the axes, the gate and the verdict. That is what a later
172
- disagreement is settled with. `pipeline/retain.py archive <run-dir> <dest>`
172
+ disagreement is settled with. `masterwork/retain.py archive <run-dir> <dest>`
173
173
  copies out exactly those two; oversized run directories are reported, never
174
174
  deleted, because deciding what to remove is not the line's call.
175
175
 
176
176
  ## Making a piece
177
177
 
178
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.4/assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
178
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
179
179
 
180
- `pipeline/ceremony.py` runs the sitting. Four properties are the recipe rather
180
+ `masterwork/ceremony.py` runs the sitting. Four properties are the recipe rather
181
181
  than implementation detail, and each was paid for:
182
182
 
183
183
  - **Anchorless.** No earlier commitment or name is anywhere in the context.
@@ -222,10 +222,10 @@ You get the line and its gates, the shape of the sitting, and the method
222
222
  written down: how a tale differs from an instruction, where a tale comes from,
223
223
  what a question has to do, why instructions come last, and how to tag a tale
224
224
  whose effect cannot be separated. That is
225
- [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.4/corpus/GROWING.md), and it is the part that transfers.
225
+ [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md), and it is the part that transfers.
226
226
 
227
227
  You do not get a corpus. Three pairs come with the repository in
228
- [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.4/masterwork/starter/) so the machinery runs end to end and the
228
+ [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) so the machinery runs end to end and the
229
229
  form is unambiguous; they are a beginning and they are meant to be outgrown.
230
230
  The corpus is the work, and it has to be yours: a tale is written from
231
231
  something **you** measured and got wrong, and a corpus assembled from someone
@@ -281,38 +281,39 @@ the position — a masterwork is submitted, and it can be refused.
281
281
 
282
282
  | document | when you need it |
283
283
  |---|---|
284
- | [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/first-run.md) | **start here** — nothing to a sealed candidate, one command at a time |
285
- | [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.4/AGENTS.md) | before running anything — the refusals, the vocabulary, and the four things not to do. Read it whether or not you are an agent |
286
- | [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/ceremony.md) | making a piece: the four properties of the sitting, and the script that drives it |
287
- | [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/seal.md) | the five fields a candidate carries, and what to do when the gate refuses one |
288
- | [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/run-spec.md) | writing the JSON for one run, field by field |
289
- | [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
290
- | [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
291
- | [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.4/gates/succession.md) |
292
- | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.4/corpus/GROWING.md) | before writing a single tale — the shape is in [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.4/masterwork/starter/) |
293
- | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.4/CHANGELOG.md) | every entry names the incident that caused it |
294
- | [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.4/assets/README.md) | you need a fifth image that matches the four |
284
+ | [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md) | **start here** — nothing to a sealed candidate, one command at a time |
285
+ | [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.5/AGENTS.md) | before running anything — the refusals, the vocabulary, and the four things not to do. Read it whether or not you are an agent |
286
+ | [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/ceremony.md) | making a piece: the four properties of the sitting, and the script that drives it |
287
+ | [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/seal.md) | the five fields a candidate carries, and what to do when the gate refuses one |
288
+ | [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/run-spec.md) | writing the JSON for one run, field by field |
289
+ | [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
290
+ | [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
291
+ | [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.5/gates/succession.md) |
292
+ | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md) | before writing a single tale — the shape is in [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) |
293
+ | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) | every entry names the incident that caused it |
294
+ | [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.5/assets/README.md) | you need a fifth image that matches the four |
295
295
 
296
296
  <details>
297
297
  <summary>Layout</summary>
298
298
 
299
299
  ```text
300
- pipeline/ the line: one run, all gates, no manual steps
301
- tools/ instruments the line calls (blind labelling, gate checks)
302
- gates/ frozen gate templates — example.md is the smallest one
303
- examples/ runnable fixtures; the trace above comes from here
304
- corpus/ the method, and a starter of three tale-and-question pairs
305
- runs/ local run directories (ignored — heavy and re-derivable)
300
+ masterwork/ the line and every stage it walks
301
+ masterwork/starter/ three tale-and-question pairs, shipped so you can begin
302
+ gates/ frozen gate templates — example.md is the smallest one
303
+ examples/ runnable fixtures; the trace above comes from here
304
+ corpus/ how a corpus grows, and what an invariant costs
305
+ docs/ reference, and one page that is a path
306
+ runs/ local run directories (ignored — heavy and re-derivable)
306
307
  ```
307
308
 
308
309
  </details>
309
310
 
310
311
  ---
311
312
 
312
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.4/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
313
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
313
314
 
314
- Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.4/CONTRIBUTING.md) · Changelog:
315
- [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.4/CHANGELOG.md) · Licensed under the
316
- [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.4/LICENSE).
315
+ Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md) · Changelog:
316
+ [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) · Licensed under the
317
+ [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE).
317
318
 
318
319
  Part of [Codechu](https://github.com/codechu).
@@ -142,7 +142,7 @@ live under `runs/`, which version control ignores, and the seal names
142
142
  everything needed to re-derive them. The run record and the benchmark's report
143
143
  are archived on purpose — both are kilobytes, and between them they carry the
144
144
  seal, the grid, the axes, the gate and the verdict. That is what a later
145
- disagreement is settled with. `pipeline/retain.py archive <run-dir> <dest>`
145
+ disagreement is settled with. `masterwork/retain.py archive <run-dir> <dest>`
146
146
  copies out exactly those two; oversized run directories are reported, never
147
147
  deleted, because deciding what to remove is not the line's call.
148
148
 
@@ -150,7 +150,7 @@ deleted, because deciding what to remove is not the line's call.
150
150
 
151
151
  <p align="center"><img src="assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
152
152
 
153
- `pipeline/ceremony.py` runs the sitting. Four properties are the recipe rather
153
+ `masterwork/ceremony.py` runs the sitting. Four properties are the recipe rather
154
154
  than implementation detail, and each was paid for:
155
155
 
156
156
  - **Anchorless.** No earlier commitment or name is anywhere in the context.
@@ -270,12 +270,13 @@ the position — a masterwork is submitted, and it can be refused.
270
270
  <summary>Layout</summary>
271
271
 
272
272
  ```text
273
- pipeline/ the line: one run, all gates, no manual steps
274
- tools/ instruments the line calls (blind labelling, gate checks)
275
- gates/ frozen gate templates — example.md is the smallest one
276
- examples/ runnable fixtures; the trace above comes from here
277
- corpus/ the method, and a starter of three tale-and-question pairs
278
- runs/ local run directories (ignored — heavy and re-derivable)
273
+ masterwork/ the line and every stage it walks
274
+ masterwork/starter/ three tale-and-question pairs, shipped so you can begin
275
+ gates/ frozen gate templates — example.md is the smallest one
276
+ examples/ runnable fixtures; the trace above comes from here
277
+ corpus/ how a corpus grows, and what an invariant costs
278
+ docs/ reference, and one page that is a path
279
+ runs/ local run directories (ignored — heavy and re-derivable)
279
280
  ```
280
281
 
281
282
  </details>
@@ -1,8 +1,8 @@
1
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.4/assets/banner.jpg" alt="A craftsman slides a sealed folio across his bench onto the guild's empty judging table; earlier pieces stand turned to the wall behind him." width="100%"></p>
1
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/banner.jpg" alt="A craftsman slides a sealed folio across his bench onto the guild's empty judging table; earlier pieces stand turned to the wall behind him." width="100%"></p>
2
2
 
3
3
  # Masterwork
4
4
 
5
- [![python](https://img.shields.io/badge/python-3.10%2B-blue)](#run-it) [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/codechu/masterwork/blob/v0.0.4/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-1-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.4/CONTRIBUTING.md#ground-rules) [![status](https://img.shields.io/badge/status-unproven-lightgrey)](#status)
5
+ [![python](https://img.shields.io/badge/python-3.10%2B-blue)](#run-it) [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-1-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md#ground-rules) [![status](https://img.shields.io/badge/status-unproven-lightgrey)](#status)
6
6
 
7
7
  *Makes a character for a model. Does not score it.*
8
8
 
@@ -34,7 +34,7 @@ masterwork # what it is, and its stages
34
34
  masterwork gate gates/ # check the gate templates
35
35
  ```
36
36
 
37
- Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/first-run.md).
37
+ Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md).
38
38
 
39
39
  Or from a clone, with nothing installed at all:
40
40
 
@@ -90,7 +90,7 @@ record, and exits in a way that says *waiting*, not *broken*.
90
90
  | `HELD_WITHOUT_PURPOSE` | the spec does not say what the run decides | write `purpose`; if you cannot say what changes on each outcome, the run is not ready |
91
91
  | `HELD_AT_SEAL` | the piece is unreproducible, or the deployed copy differs | fix the seal or the deployment — never run "just to see" |
92
92
  | `HELD_AT_COMPLETENESS` | the grid is short | find the missing cells; if proceeding anyway, declare it |
93
- | `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.4/masterwork/blind_label.py); the line is waiting, not broken |
93
+ | `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/blind_label.py); the line is waiting, not broken |
94
94
  | `HELD_AT_MEASUREMENT` | the benchmark says the score is not comparable | judge from a separate endpoint, or declare and keep the stamp |
95
95
  | `HELD_AT_GATE` | a threshold sits inside its own noise | the gate is void; measure the band, write a new gate |
96
96
  | `HELD_WITHOUT_MEASUREMENT` | a rule names an axis and nothing measured it | judge with the benchmark, or declare the run diagnostic — a gate that was checked is not a gate that was applied |
@@ -142,15 +142,15 @@ live under `runs/`, which version control ignores, and the seal names
142
142
  everything needed to re-derive them. The run record and the benchmark's report
143
143
  are archived on purpose — both are kilobytes, and between them they carry the
144
144
  seal, the grid, the axes, the gate and the verdict. That is what a later
145
- disagreement is settled with. `pipeline/retain.py archive <run-dir> <dest>`
145
+ disagreement is settled with. `masterwork/retain.py archive <run-dir> <dest>`
146
146
  copies out exactly those two; oversized run directories are reported, never
147
147
  deleted, because deciding what to remove is not the line's call.
148
148
 
149
149
  ## Making a piece
150
150
 
151
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.4/assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
151
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
152
152
 
153
- `pipeline/ceremony.py` runs the sitting. Four properties are the recipe rather
153
+ `masterwork/ceremony.py` runs the sitting. Four properties are the recipe rather
154
154
  than implementation detail, and each was paid for:
155
155
 
156
156
  - **Anchorless.** No earlier commitment or name is anywhere in the context.
@@ -195,10 +195,10 @@ You get the line and its gates, the shape of the sitting, and the method
195
195
  written down: how a tale differs from an instruction, where a tale comes from,
196
196
  what a question has to do, why instructions come last, and how to tag a tale
197
197
  whose effect cannot be separated. That is
198
- [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.4/corpus/GROWING.md), and it is the part that transfers.
198
+ [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md), and it is the part that transfers.
199
199
 
200
200
  You do not get a corpus. Three pairs come with the repository in
201
- [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.4/masterwork/starter/) so the machinery runs end to end and the
201
+ [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) so the machinery runs end to end and the
202
202
  form is unambiguous; they are a beginning and they are meant to be outgrown.
203
203
  The corpus is the work, and it has to be yours: a tale is written from
204
204
  something **you** measured and got wrong, and a corpus assembled from someone
@@ -254,38 +254,39 @@ the position — a masterwork is submitted, and it can be refused.
254
254
 
255
255
  | document | when you need it |
256
256
  |---|---|
257
- | [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/first-run.md) | **start here** — nothing to a sealed candidate, one command at a time |
258
- | [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.4/AGENTS.md) | before running anything — the refusals, the vocabulary, and the four things not to do. Read it whether or not you are an agent |
259
- | [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/ceremony.md) | making a piece: the four properties of the sitting, and the script that drives it |
260
- | [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/seal.md) | the five fields a candidate carries, and what to do when the gate refuses one |
261
- | [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/run-spec.md) | writing the JSON for one run, field by field |
262
- | [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
263
- | [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
264
- | [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.4/gates/succession.md) |
265
- | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.4/corpus/GROWING.md) | before writing a single tale — the shape is in [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.4/masterwork/starter/) |
266
- | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.4/CHANGELOG.md) | every entry names the incident that caused it |
267
- | [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.4/assets/README.md) | you need a fifth image that matches the four |
257
+ | [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md) | **start here** — nothing to a sealed candidate, one command at a time |
258
+ | [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.5/AGENTS.md) | before running anything — the refusals, the vocabulary, and the four things not to do. Read it whether or not you are an agent |
259
+ | [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/ceremony.md) | making a piece: the four properties of the sitting, and the script that drives it |
260
+ | [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/seal.md) | the five fields a candidate carries, and what to do when the gate refuses one |
261
+ | [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/run-spec.md) | writing the JSON for one run, field by field |
262
+ | [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
263
+ | [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
264
+ | [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.5/gates/succession.md) |
265
+ | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md) | before writing a single tale — the shape is in [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) |
266
+ | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) | every entry names the incident that caused it |
267
+ | [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.5/assets/README.md) | you need a fifth image that matches the four |
268
268
 
269
269
  <details>
270
270
  <summary>Layout</summary>
271
271
 
272
272
  ```text
273
- pipeline/ the line: one run, all gates, no manual steps
274
- tools/ instruments the line calls (blind labelling, gate checks)
275
- gates/ frozen gate templates — example.md is the smallest one
276
- examples/ runnable fixtures; the trace above comes from here
277
- corpus/ the method, and a starter of three tale-and-question pairs
278
- runs/ local run directories (ignored — heavy and re-derivable)
273
+ masterwork/ the line and every stage it walks
274
+ masterwork/starter/ three tale-and-question pairs, shipped so you can begin
275
+ gates/ frozen gate templates — example.md is the smallest one
276
+ examples/ runnable fixtures; the trace above comes from here
277
+ corpus/ how a corpus grows, and what an invariant costs
278
+ docs/ reference, and one page that is a path
279
+ runs/ local run directories (ignored — heavy and re-derivable)
279
280
  ```
280
281
 
281
282
  </details>
282
283
 
283
284
  ---
284
285
 
285
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.4/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
286
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
286
287
 
287
- Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.4/CONTRIBUTING.md) · Changelog:
288
- [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.4/CHANGELOG.md) · Licensed under the
289
- [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.4/LICENSE).
288
+ Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md) · Changelog:
289
+ [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) · Licensed under the
290
+ [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE).
290
291
 
291
292
  Part of [Codechu](https://github.com/codechu).
@@ -4,4 +4,4 @@ The version lives here and nowhere else. It is pre-release: nothing has
4
4
  been published, and the number exists so the tool can answer for itself
5
5
  rather than so a package index can order it.
6
6
  """
7
- __version__ = "0.0.4"
7
+ __version__ = "0.0.5"
@@ -138,8 +138,25 @@ def newest_report(runs_dir: str) -> str | None:
138
138
  return max(found)[1] if found else None
139
139
 
140
140
 
141
+ # The shape of report.json this line knows how to read. The benchmark
142
+ # bumps this integer only when the shape changes in a way that can break a
143
+ # reader, and its own guidance to integrators is to pin here rather than on
144
+ # the release number — the package moves for reasons that never touch the
145
+ # report. On a value we do not know we stop, because a reader that guesses
146
+ # turns a changed shape into a wrong number instead of an error.
147
+ KNOWN_SCHEMA = 1
148
+
149
+
141
150
  def read_report(path: str) -> dict:
142
151
  d = json.load(open(path, encoding="utf-8"))
152
+ got = d.get("schema_version")
153
+ if got != KNOWN_SCHEMA:
154
+ raise SystemExit(
155
+ f"{path} carries schema_version {got!r}; this line reads "
156
+ f"{KNOWN_SCHEMA}. A report written to a shape we do not know is "
157
+ f"not a report we may score. Upgrade masterwork, or re-render "
158
+ f"the report from its cells with the benchmark that wrote them."
159
+ )
143
160
  axes = d.get("axes") or {}
144
161
  return {
145
162
  "report": path,
@@ -102,17 +102,87 @@ def raw_header(path: str) -> str:
102
102
  return "".join(lines)
103
103
 
104
104
 
105
+ # Shapes, in the notation a person writing a profile already knows. A
106
+ # profile is written by whoever keeps a workshop's seals, not necessarily by
107
+ # someone who writes regular expressions — and in JSON a regex arrives
108
+ # doubly escaped, which is how `(\\d{4}-\\d{2}-\\d{2})` ends up in a config
109
+ # file as the illustration of a date. `pattern` is still there for a shape
110
+ # this cannot express; it is the exception, not the way in.
111
+ # Longest first: `MMMM` before `MMM` before `MM`, or the shorter token eats
112
+ # the front of the longer one and leaves a literal behind.
113
+ #
114
+ # A written month is "a word where the month goes", not a list of English
115
+ # month names. The dialect mechanism exists so a workshop can keep its own
116
+ # headers, and half the point of that is that they are not in English — a
117
+ # list would read `29 August 2026` and refuse `29 Ağustos 2026`. A shape
118
+ # locates a value; it does not validate one, here or anywhere else.
119
+ FORMAT_TOKENS = [("yyyy", r"\d{4}"), ("yy", r"\d{2}"),
120
+ ("MMMM", r"[^\W\d_]+"), ("MMM", r"[^\W\d_]+"),
121
+ ("MM", r"\d{2}"),
122
+ ("dd", r"\d{2}"), ("HH", r"\d{2}"), ("mm", r"\d{2}"),
123
+ ("ss", r"\d{2}")]
124
+
125
+
126
+ # Letters that mean something in a shape. One of these left over after
127
+ # tokenising is a shape the writer meant and this cannot read — `MMM dd,
128
+ # yyyy` consumes `MM` and leaves a literal `M`, producing a pattern that
129
+ # matches nothing and reports the field as missing, with nothing to say
130
+ # why. Refusing names the mistake where it was made.
131
+ # Case included: `YYYY-MM-DD` is a common way to write this and would
132
+ # otherwise compile to a pattern matching the literal text "YYYY".
133
+ TOKEN_LETTERS = set("yYMDdHhmsS")
134
+
135
+
136
+ def format_to_regex(fmt: str) -> str:
137
+ r"""`yyyy-MM-dd` -> `(\d{4}-\d{2}-\d{2})`. Everything else is literal."""
138
+ out, i = [], 0
139
+ while i < len(fmt):
140
+ for token, rx in FORMAT_TOKENS:
141
+ if fmt.startswith(token, i):
142
+ out.append(rx)
143
+ i += len(token)
144
+ break
145
+ else:
146
+ if fmt[i] in TOKEN_LETTERS:
147
+ supported = ", ".join(t for t, _ in FORMAT_TOKENS)
148
+ raise ValueError(
149
+ f"format {fmt!r}: {fmt[i]!r} at position {i} is left over "
150
+ f"from a token this cannot read. Supported: {supported} "
151
+ f"— a written month is MMM or MMMM.")
152
+ out.append(re.escape(fmt[i]))
153
+ i += 1
154
+ return "(" + "".join(out) + ")"
155
+
156
+
157
+ def _extract(value: str, spec) -> str:
158
+ """Pull the shape out of a value that carries more than the value.
159
+
160
+ Applied to whatever an alias found, not only as a rescue when none did:
161
+ a header that has the key but writes `sealed on 2026-08-29` around the
162
+ value could not be cleaned when this ran as a fallback.
163
+ """
164
+ if not isinstance(spec, dict):
165
+ return value
166
+ rx = spec.get("pattern")
167
+ if not rx and spec.get("format"):
168
+ rx = format_to_regex(spec["format"])
169
+ if not rx:
170
+ return value
171
+ m = re.search(rx, value)
172
+ if not m:
173
+ return value
174
+ return (m.group(1) if m.groups() else m.group(0)).strip()
175
+
176
+
105
177
  def _lookup(canonical: str, spec, header: dict[str, str], raw: str):
106
- """A profile entry is a list of aliases, or a dict that may add a pattern."""
178
+ """A profile entry is a list of aliases, or a dict that may add a shape."""
107
179
  aliases = spec if isinstance(spec, list) else (spec or {}).get("aliases", [])
108
180
  for alias in aliases:
109
181
  if alias.lower() in header:
110
- return header[alias.lower()]
111
- pattern = (spec or {}).get("pattern") if isinstance(spec, dict) else None
112
- if pattern:
113
- m = re.search(pattern, raw)
114
- if m:
115
- return (m.group(1) if m.groups() else m.group(0)).strip()
182
+ return _extract(header[alias.lower()], spec)
183
+ if isinstance(spec, dict) and (spec.get("pattern") or spec.get("format")):
184
+ found = _extract(raw, spec)
185
+ return found if found is not raw else None
116
186
  return None
117
187
 
118
188
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: masterwork
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: A production line for agent character: a sitting the model speaks for itself in, sealed candidates, frozen gates, and a verdict it is not allowed to give itself.
5
5
  Author: Codechu
6
6
  License-Expression: Apache-2.0
@@ -22,14 +22,14 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
22
  Requires-Python: >=3.10
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: journeyman-bench>=0.2.1
25
+ Requires-Dist: journeyman-bench<0.5.0,>=0.2.1
26
26
  Dynamic: license-file
27
27
 
28
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.4/assets/banner.jpg" alt="A craftsman slides a sealed folio across his bench onto the guild's empty judging table; earlier pieces stand turned to the wall behind him." width="100%"></p>
28
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/banner.jpg" alt="A craftsman slides a sealed folio across his bench onto the guild's empty judging table; earlier pieces stand turned to the wall behind him." width="100%"></p>
29
29
 
30
30
  # Masterwork
31
31
 
32
- [![python](https://img.shields.io/badge/python-3.10%2B-blue)](#run-it) [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/codechu/masterwork/blob/v0.0.4/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-1-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.4/CONTRIBUTING.md#ground-rules) [![status](https://img.shields.io/badge/status-unproven-lightgrey)](#status)
32
+ [![python](https://img.shields.io/badge/python-3.10%2B-blue)](#run-it) [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-1-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md#ground-rules) [![status](https://img.shields.io/badge/status-unproven-lightgrey)](#status)
33
33
 
34
34
  *Makes a character for a model. Does not score it.*
35
35
 
@@ -61,7 +61,7 @@ masterwork # what it is, and its stages
61
61
  masterwork gate gates/ # check the gate templates
62
62
  ```
63
63
 
64
- Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/first-run.md).
64
+ Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md).
65
65
 
66
66
  Or from a clone, with nothing installed at all:
67
67
 
@@ -117,7 +117,7 @@ record, and exits in a way that says *waiting*, not *broken*.
117
117
  | `HELD_WITHOUT_PURPOSE` | the spec does not say what the run decides | write `purpose`; if you cannot say what changes on each outcome, the run is not ready |
118
118
  | `HELD_AT_SEAL` | the piece is unreproducible, or the deployed copy differs | fix the seal or the deployment — never run "just to see" |
119
119
  | `HELD_AT_COMPLETENESS` | the grid is short | find the missing cells; if proceeding anyway, declare it |
120
- | `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.4/masterwork/blind_label.py); the line is waiting, not broken |
120
+ | `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/blind_label.py); the line is waiting, not broken |
121
121
  | `HELD_AT_MEASUREMENT` | the benchmark says the score is not comparable | judge from a separate endpoint, or declare and keep the stamp |
122
122
  | `HELD_AT_GATE` | a threshold sits inside its own noise | the gate is void; measure the band, write a new gate |
123
123
  | `HELD_WITHOUT_MEASUREMENT` | a rule names an axis and nothing measured it | judge with the benchmark, or declare the run diagnostic — a gate that was checked is not a gate that was applied |
@@ -169,15 +169,15 @@ live under `runs/`, which version control ignores, and the seal names
169
169
  everything needed to re-derive them. The run record and the benchmark's report
170
170
  are archived on purpose — both are kilobytes, and between them they carry the
171
171
  seal, the grid, the axes, the gate and the verdict. That is what a later
172
- disagreement is settled with. `pipeline/retain.py archive <run-dir> <dest>`
172
+ disagreement is settled with. `masterwork/retain.py archive <run-dir> <dest>`
173
173
  copies out exactly those two; oversized run directories are reported, never
174
174
  deleted, because deciding what to remove is not the line's call.
175
175
 
176
176
  ## Making a piece
177
177
 
178
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.4/assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
178
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
179
179
 
180
- `pipeline/ceremony.py` runs the sitting. Four properties are the recipe rather
180
+ `masterwork/ceremony.py` runs the sitting. Four properties are the recipe rather
181
181
  than implementation detail, and each was paid for:
182
182
 
183
183
  - **Anchorless.** No earlier commitment or name is anywhere in the context.
@@ -222,10 +222,10 @@ You get the line and its gates, the shape of the sitting, and the method
222
222
  written down: how a tale differs from an instruction, where a tale comes from,
223
223
  what a question has to do, why instructions come last, and how to tag a tale
224
224
  whose effect cannot be separated. That is
225
- [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.4/corpus/GROWING.md), and it is the part that transfers.
225
+ [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md), and it is the part that transfers.
226
226
 
227
227
  You do not get a corpus. Three pairs come with the repository in
228
- [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.4/masterwork/starter/) so the machinery runs end to end and the
228
+ [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) so the machinery runs end to end and the
229
229
  form is unambiguous; they are a beginning and they are meant to be outgrown.
230
230
  The corpus is the work, and it has to be yours: a tale is written from
231
231
  something **you** measured and got wrong, and a corpus assembled from someone
@@ -281,38 +281,39 @@ the position — a masterwork is submitted, and it can be refused.
281
281
 
282
282
  | document | when you need it |
283
283
  |---|---|
284
- | [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/first-run.md) | **start here** — nothing to a sealed candidate, one command at a time |
285
- | [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.4/AGENTS.md) | before running anything — the refusals, the vocabulary, and the four things not to do. Read it whether or not you are an agent |
286
- | [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/ceremony.md) | making a piece: the four properties of the sitting, and the script that drives it |
287
- | [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/seal.md) | the five fields a candidate carries, and what to do when the gate refuses one |
288
- | [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/run-spec.md) | writing the JSON for one run, field by field |
289
- | [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
290
- | [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
291
- | [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.4/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.4/gates/succession.md) |
292
- | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.4/corpus/GROWING.md) | before writing a single tale — the shape is in [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.4/masterwork/starter/) |
293
- | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.4/CHANGELOG.md) | every entry names the incident that caused it |
294
- | [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.4/assets/README.md) | you need a fifth image that matches the four |
284
+ | [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md) | **start here** — nothing to a sealed candidate, one command at a time |
285
+ | [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.5/AGENTS.md) | before running anything — the refusals, the vocabulary, and the four things not to do. Read it whether or not you are an agent |
286
+ | [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/ceremony.md) | making a piece: the four properties of the sitting, and the script that drives it |
287
+ | [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/seal.md) | the five fields a candidate carries, and what to do when the gate refuses one |
288
+ | [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/run-spec.md) | writing the JSON for one run, field by field |
289
+ | [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
290
+ | [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
291
+ | [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.5/gates/succession.md) |
292
+ | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md) | before writing a single tale — the shape is in [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) |
293
+ | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) | every entry names the incident that caused it |
294
+ | [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.5/assets/README.md) | you need a fifth image that matches the four |
295
295
 
296
296
  <details>
297
297
  <summary>Layout</summary>
298
298
 
299
299
  ```text
300
- pipeline/ the line: one run, all gates, no manual steps
301
- tools/ instruments the line calls (blind labelling, gate checks)
302
- gates/ frozen gate templates — example.md is the smallest one
303
- examples/ runnable fixtures; the trace above comes from here
304
- corpus/ the method, and a starter of three tale-and-question pairs
305
- runs/ local run directories (ignored — heavy and re-derivable)
300
+ masterwork/ the line and every stage it walks
301
+ masterwork/starter/ three tale-and-question pairs, shipped so you can begin
302
+ gates/ frozen gate templates — example.md is the smallest one
303
+ examples/ runnable fixtures; the trace above comes from here
304
+ corpus/ how a corpus grows, and what an invariant costs
305
+ docs/ reference, and one page that is a path
306
+ runs/ local run directories (ignored — heavy and re-derivable)
306
307
  ```
307
308
 
308
309
  </details>
309
310
 
310
311
  ---
311
312
 
312
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.4/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
313
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
313
314
 
314
- Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.4/CONTRIBUTING.md) · Changelog:
315
- [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.4/CHANGELOG.md) · Licensed under the
316
- [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.4/LICENSE).
315
+ Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md) · Changelog:
316
+ [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) · Licensed under the
317
+ [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE).
317
318
 
318
319
  Part of [Codechu](https://github.com/codechu).
@@ -0,0 +1 @@
1
+ journeyman-bench<0.5.0,>=0.2.1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "masterwork"
7
- version = "0.0.4"
7
+ version = "0.0.5"
8
8
  description = "A production line for agent character: a sitting the model speaks for itself in, sealed candidates, frozen gates, and a verdict it is not allowed to give itself."
9
9
  readme = "README.pypi.md"
10
10
  requires-python = ">=3.10"
@@ -33,7 +33,11 @@ classifiers = [
33
33
  # the verdict, through the CLI rather than an import, so the stamp travels
34
34
  # with the claim. The dependency is declared because a line without a judge
35
35
  # cannot finish, and the judge must be able to name its own version.
36
- dependencies = ["journeyman-bench>=0.2.1"]
36
+ # Upper bound, not open-ended: the benchmark documents that pre-1.0 the
37
+ # MINOR digit is where a breaking change goes, so ">=x" alone invites in
38
+ # exactly the releases allowed to break this. Verified read-compatible
39
+ # through 0.4.x (report.json keys unchanged since 0.2.0).
40
+ dependencies = ["journeyman-bench>=0.2.1,<0.5.0"]
37
41
 
38
42
  [project.scripts]
39
43
  masterwork = "masterwork.__main__:main"
@@ -131,7 +131,7 @@ def test_self_judged_measurement_never_reaches_the_gate():
131
131
  spec = build(tmp)
132
132
  report = os.path.join(tmp, "report.json")
133
133
  json.dump({"axes": {"grounding": {"score": 1.0, "n": 1}},
134
- "self_judged": True}, open(report, "w"))
134
+ "self_judged": True, "schema_version": 1}, open(report, "w"))
135
135
  spec["judge"] = {"journeyman": {"endpoint": "http://x/v1",
136
136
  "executable": "true", "report": report,
137
137
  "skip_preflight": True}}
@@ -35,7 +35,9 @@ def test_empty_axes_is_not_a_pass():
35
35
  def test_reads_the_report_contract():
36
36
  with tempfile.TemporaryDirectory() as tmp:
37
37
  p = os.path.join(tmp, "report.json")
38
- json.dump({"axes": {"grounding": {"score": 1.0, "n": 2},
38
+ # shape: journeyman/report.py:178 (schema_version written on every report)
39
+ json.dump({"schema_version": 1,
40
+ "axes": {"grounding": {"score": 1.0, "n": 2},
39
41
  "wall-pricing": {"score": 0.5, "n": 2}},
40
42
  "self_judged": False, "seal": {"bench": "0.1.0"}}, open(p, "w"))
41
43
  s = measure.read_report(p)
@@ -48,7 +50,7 @@ def test_newest_report_is_found_under_a_runs_dir():
48
50
  d = os.path.join(tmp, "run-a")
49
51
  os.makedirs(d)
50
52
  p = os.path.join(d, "report.json")
51
- json.dump({"axes": {}}, open(p, "w"))
53
+ json.dump({"schema_version": 1, "axes": {}}, open(p, "w"))
52
54
  assert measure.newest_report(tmp) == p
53
55
 
54
56
 
@@ -99,3 +101,17 @@ def test_different_piece_is_caught():
99
101
 
100
102
  def test_unreachable_endpoint_is_reported_before_the_run():
101
103
  assert measure.reachable("http://127.0.0.1:1/v1", timeout=1)
104
+
105
+
106
+ def test_a_report_shape_we_do_not_know_is_refused():
107
+ """The benchmark's own guidance to integrators: pin on schema_version,
108
+ and on a value you do not know, stop rather than guess. A reader that
109
+ guesses turns a changed shape into a wrong number instead of an error."""
110
+ import pytest
111
+ with tempfile.TemporaryDirectory() as tmp:
112
+ p = os.path.join(tmp, "report.json")
113
+ json.dump({"schema_version": 2, "axes": {"grounding": {"score": 1.0}}},
114
+ open(p, "w"))
115
+ with pytest.raises(SystemExit) as e:
116
+ measure.read_report(p)
117
+ assert "schema_version 2" in str(e.value)
@@ -109,3 +109,84 @@ def test_a_candidate_that_is_not_there_is_a_problem_not_a_traceback():
109
109
  """The line calls verify() directly, so the check belongs here."""
110
110
  assert seal.verify("/nowhere/candidate.txt") == [
111
111
  "no candidate at /nowhere/candidate.txt — nothing to verify"]
112
+
113
+
114
+ def test_a_shape_is_written_the_way_a_person_writes_dates(tmp_path):
115
+ """`yyyy-MM-dd`, not a doubly-escaped regex in a JSON file.
116
+
117
+ A seal profile is written by whoever keeps a workshop's seals. Asking
118
+ for `(\\d{4}-\\d{2}-\\d{2})` there asks them to know our dialect and
119
+ JSON's escaping at once, to say a thing they can already say.
120
+ """
121
+ from masterwork.seal import format_to_regex, read_seal
122
+ assert format_to_regex("yyyy-MM-dd") == r"(\d{4}\-\d{2}\-\d{2})"
123
+
124
+ piece = tmp_path / "candidate.txt"
125
+ piece.write_text(
126
+ "# corpus hash: a\n# script hash: b\n# question seed: 1\n"
127
+ "# sampling seed: 2\n# sealed on 2026-08-29 by the workshop\n\ntext\n")
128
+ profile = {"corpus_hash": ["corpus hash"], "script_hash": ["script hash"],
129
+ "question_seed": ["question seed"], "sampling_seed": ["sampling seed"],
130
+ "date": {"aliases": [], "format": "yyyy-MM-dd"}}
131
+ assert read_seal(str(piece), profile).fields["date"] == "2026-08-29"
132
+
133
+
134
+ def test_a_shape_also_cleans_a_value_an_alias_found(tmp_path):
135
+ """It used to run only when no alias matched.
136
+
137
+ A header with the key but a value wrapped in prose could not be
138
+ cleaned: the alias matched, returned the whole string, and the shape
139
+ never ran. That is the common case, not the rare one.
140
+ """
141
+ from masterwork.seal import read_seal
142
+ piece = tmp_path / "candidate.txt"
143
+ piece.write_text(
144
+ "# corpus hash: a\n# script hash: b\n# question seed: 1\n"
145
+ "# sampling seed: 2\n# date: sealed on 2026-08-29, unpriced\n\ntext\n")
146
+ profile = {"corpus_hash": ["corpus hash"], "script_hash": ["script hash"],
147
+ "question_seed": ["question seed"], "sampling_seed": ["sampling seed"],
148
+ "date": {"aliases": ["date"], "format": "yyyy-MM-dd"}}
149
+ assert read_seal(str(piece), profile).fields["date"] == "2026-08-29"
150
+
151
+
152
+ def test_a_shape_it_cannot_read_is_refused_rather_than_compiled(tmp_path):
153
+ """`MMM dd, yyyy` consumes `MM` and leaves a literal `M`.
154
+
155
+ That compiles to a pattern matching nothing, so the field comes back
156
+ missing and the writer is told only that — with no way to see that
157
+ their shape was the problem. Same for `YYYY-MM-DD`, which is a common
158
+ way to write it and would look for the literal text "YYYY".
159
+ """
160
+ import pytest
161
+ from masterwork.seal import format_to_regex
162
+ for good in ("yyyy-MM-dd", "MM-dd-yyyy", "dd.MM.yyyy",
163
+ "yyyy-MM-ddTHH:mm:ss"):
164
+ assert format_to_regex(good).startswith("(")
165
+ for bad in ("YYYY-MM-DD", "yyyy-M-d", "DD.MM.YYYY"):
166
+ with pytest.raises(ValueError) as e:
167
+ format_to_regex(bad)
168
+ assert "Supported" in str(e.value)
169
+
170
+
171
+ def test_a_written_month_is_a_word_not_an_english_month_list(tmp_path):
172
+ """`dd MMMM yyyy` must read 29 Ağustos 2026 as readily as 29 August 2026.
173
+
174
+ The dialect mechanism exists so a workshop can keep its own headers,
175
+ and half the point of that is that they are not in English. A list of
176
+ month names would have read one and refused the other. A shape locates
177
+ a value; it does not validate one.
178
+ """
179
+ from masterwork.seal import read_seal
180
+ base = ("# corpus hash: a\n# script hash: b\n# question seed: 1\n"
181
+ "# sampling seed: 2\n# date: {}\n\ntext\n")
182
+ prof = {"corpus_hash": ["corpus hash"], "script_hash": ["script hash"],
183
+ "question_seed": ["question seed"], "sampling_seed": ["sampling seed"]}
184
+ for fmt, written, want in (
185
+ ("dd MMM yyyy", "sealed 29 Aug 2026", "29 Aug 2026"),
186
+ ("dd MMMM yyyy", "29 August 2026", "29 August 2026"),
187
+ ("dd MMMM yyyy", "muhurlendi 29 Ağustos 2026", "29 Ağustos 2026"),
188
+ ("dd MMM yyyy", "29 août 2026", "29 août 2026")):
189
+ piece = tmp_path / f"c-{abs(hash((fmt, written)))}.txt"
190
+ piece.write_text(base.format(written), encoding="utf-8")
191
+ pr = dict(prof, date={"aliases": ["date"], "format": fmt})
192
+ assert read_seal(str(piece), pr).fields["date"] == want
@@ -1 +0,0 @@
1
- journeyman-bench>=0.2.1
File without changes
File without changes
File without changes