masterwork 0.0.1__tar.gz → 0.0.3__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 (39) hide show
  1. {masterwork-0.0.1/masterwork.egg-info → masterwork-0.0.3}/PKG-INFO +27 -18
  2. {masterwork-0.0.1 → masterwork-0.0.3}/README.md +11 -2
  3. {masterwork-0.0.1 → masterwork-0.0.3}/README.pypi.md +26 -17
  4. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/__init__.py +1 -1
  5. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/ceremony.py +13 -4
  6. {masterwork-0.0.1 → masterwork-0.0.3/masterwork.egg-info}/PKG-INFO +27 -18
  7. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork.egg-info/SOURCES.txt +1 -0
  8. {masterwork-0.0.1 → masterwork-0.0.3}/pyproject.toml +1 -1
  9. masterwork-0.0.3/tests/test_badges.py +75 -0
  10. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_ceremony.py +17 -5
  11. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_packaging.py +19 -5
  12. {masterwork-0.0.1 → masterwork-0.0.3}/LICENSE +0 -0
  13. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/__main__.py +0 -0
  14. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/blind_label.py +0 -0
  15. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/campaign.py +0 -0
  16. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/cells.py +0 -0
  17. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/gate.py +0 -0
  18. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/line.py +0 -0
  19. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/measure.py +0 -0
  20. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/pairs.py +0 -0
  21. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/retain.py +0 -0
  22. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/rubric-example.json +0 -0
  23. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork/seal.py +0 -0
  24. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork.egg-info/dependency_links.txt +0 -0
  25. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork.egg-info/entry_points.txt +0 -0
  26. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork.egg-info/requires.txt +0 -0
  27. {masterwork-0.0.1 → masterwork-0.0.3}/masterwork.egg-info/top_level.txt +0 -0
  28. {masterwork-0.0.1 → masterwork-0.0.3}/setup.cfg +0 -0
  29. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_blind_label.py +0 -0
  30. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_campaign.py +0 -0
  31. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_cells.py +0 -0
  32. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_cli.py +0 -0
  33. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_examples.py +0 -0
  34. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_gate.py +0 -0
  35. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_line.py +0 -0
  36. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_measure.py +0 -0
  37. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_pairs.py +0 -0
  38. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_retain.py +0 -0
  39. {masterwork-0.0.1 → masterwork-0.0.3}/tests/test_seal.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: masterwork
3
- Version: 0.0.1
3
+ Version: 0.0.3
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
@@ -25,11 +25,11 @@ License-File: LICENSE
25
25
  Requires-Dist: journeyman-bench>=0.2.1
26
26
  Dynamic: license-file
27
27
 
28
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.1/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.3/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.1/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.1/CONTRIBUTING.md#ground-rules) [![status](https://img.shields.io/badge/status-not%20published-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.3/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-1-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.3/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,6 +61,8 @@ 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.3/docs/first-run.md).
65
+
64
66
  Or from a clone, with nothing installed at all:
65
67
 
66
68
  ```bash
@@ -115,7 +117,7 @@ record, and exits in a way that says *waiting*, not *broken*.
115
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 |
116
118
  | `HELD_AT_SEAL` | the piece is unreproducible, or the deployed copy differs | fix the seal or the deployment — never run "just to see" |
117
119
  | `HELD_AT_COMPLETENESS` | the grid is short | find the missing cells; if proceeding anyway, declare it |
118
- | `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.1/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.3/masterwork/blind_label.py); the line is waiting, not broken |
119
121
  | `HELD_AT_MEASUREMENT` | the benchmark says the score is not comparable | judge from a separate endpoint, or declare and keep the stamp |
120
122
  | `HELD_AT_GATE` | a threshold sits inside its own noise | the gate is void; measure the band, write a new gate |
121
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 |
@@ -173,7 +175,7 @@ deleted, because deciding what to remove is not the line's call.
173
175
 
174
176
  ## Making a piece
175
177
 
176
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.1/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.3/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>
177
179
 
178
180
  `pipeline/ceremony.py` runs the sitting. Four properties are the recipe rather
179
181
  than implementation detail, and each was paid for:
@@ -220,10 +222,10 @@ You get the line and its gates, the shape of the sitting, and the method
220
222
  written down: how a tale differs from an instruction, where a tale comes from,
221
223
  what a question has to do, why instructions come last, and how to tag a tale
222
224
  whose effect cannot be separated. That is
223
- [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/GROWING.md), and it is the part that transfers.
225
+ [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/GROWING.md), and it is the part that transfers.
224
226
 
225
227
  You do not get a corpus. Three pairs come with the repository in
226
- [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/starter/) so the machinery runs end to end and the
228
+ [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/starter/) so the machinery runs end to end and the
227
229
  form is unambiguous; they are a beginning and they are meant to be outgrown.
228
230
  The corpus is the work, and it has to be yours: a tale is written from
229
231
  something **you** measured and got wrong, and a corpus assembled from someone
@@ -264,7 +266,7 @@ Not automatable, and this is the honest part:
264
266
 
265
267
  ## Status
266
268
 
267
- Not published, and not proven. Our own corpus has eighteen tales and took
269
+ Published, and not proven. Our own corpus has eighteen tales and took
268
270
  months. It has produced eighteen candidates and none of them is ahead of the
269
271
  agent already running: the closest comparison came back `UNRESOLVED` rather
270
272
  than won — the difference was smaller than the gate could resolve, which is not
@@ -279,12 +281,17 @@ the position — a masterwork is submitted, and it can be refused.
279
281
 
280
282
  | document | when you need it |
281
283
  |---|---|
282
- | [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.1/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 |
283
- | [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.1/docs/run-spec.md) | writing the JSON for one run, field by field |
284
- | [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.1/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
285
- | [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.1/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.1/gates/succession.md) |
286
- | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/GROWING.md) | before writing a single tale — the shape is in [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/starter/) |
287
- | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.1/CHANGELOG.md) | every entry names the incident that caused it |
284
+ | [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.3/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.3/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.3/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.3/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.3/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.3/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.3/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.3/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.3/gates/succession.md) |
292
+ | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/GROWING.md) | before writing a single tale — the shape is in [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/starter/) |
293
+ | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.3/CHANGELOG.md) | every entry names the incident that caused it |
294
+ | [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.3/assets/README.md) | you need a fifth image that matches the four |
288
295
 
289
296
  <details>
290
297
  <summary>Layout</summary>
@@ -302,8 +309,10 @@ runs/ local run directories (ignored — heavy and re-derivable)
302
309
 
303
310
  ---
304
311
 
305
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.1/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
312
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.3/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
313
+
314
+ Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.3/CONTRIBUTING.md) · Changelog:
315
+ [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.3/CHANGELOG.md) · Licensed under the
316
+ [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.3/LICENSE).
306
317
 
307
- Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.1/CONTRIBUTING.md) · Changelog:
308
- [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.1/CHANGELOG.md) · Licensed under the
309
- [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.1/LICENSE).
318
+ Part of [Codechu](https://github.com/codechu).
@@ -2,7 +2,7 @@
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)](LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](CONTRIBUTING.md#ground-rules) [![status](https://img.shields.io/badge/status-not%20published-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)](LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-1-brightgreen)](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,6 +34,8 @@ 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`](docs/first-run.md).
38
+
37
39
  Or from a clone, with nothing installed at all:
38
40
 
39
41
  ```bash
@@ -237,7 +239,7 @@ Not automatable, and this is the honest part:
237
239
 
238
240
  ## Status
239
241
 
240
- Not published, and not proven. Our own corpus has eighteen tales and took
242
+ Published, and not proven. Our own corpus has eighteen tales and took
241
243
  months. It has produced eighteen candidates and none of them is ahead of the
242
244
  agent already running: the closest comparison came back `UNRESOLVED` rather
243
245
  than won — the difference was smaller than the gate could resolve, which is not
@@ -252,12 +254,17 @@ the position — a masterwork is submitted, and it can be refused.
252
254
 
253
255
  | document | when you need it |
254
256
  |---|---|
257
+ | [`docs/first-run.md`](docs/first-run.md) | **start here** — nothing to a sealed candidate, one command at a time |
255
258
  | [`AGENTS.md`](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`](docs/ceremony.md) | making a piece: the four properties of the sitting, and the script that drives it |
260
+ | [`docs/seal.md`](docs/seal.md) | the five fields a candidate carries, and what to do when the gate refuses one |
256
261
  | [`docs/run-spec.md`](docs/run-spec.md) | writing the JSON for one run, field by field |
262
+ | [`docs/pairs.md`](docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
257
263
  | [`docs/campaigns.md`](docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
258
264
  | [`docs/gates.md`](docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](gates/succession.md) |
259
265
  | [`corpus/GROWING.md`](corpus/GROWING.md) | before writing a single tale — the shape is in [`corpus/starter/`](corpus/starter/) |
260
266
  | [`CHANGELOG.md`](CHANGELOG.md) | every entry names the incident that caused it |
267
+ | [assets/README.md](assets/README.md) | you need a fifth image that matches the four |
261
268
 
262
269
  <details>
263
270
  <summary>Layout</summary>
@@ -280,3 +287,5 @@ runs/ local run directories (ignored — heavy and re-derivable)
280
287
  Contributing: [CONTRIBUTING.md](CONTRIBUTING.md) · Changelog:
281
288
  [CHANGELOG.md](CHANGELOG.md) · Licensed under the
282
289
  [Apache License 2.0](LICENSE).
290
+
291
+ Part of [Codechu](https://github.com/codechu).
@@ -1,8 +1,8 @@
1
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.1/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.3/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.1/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.1/CONTRIBUTING.md#ground-rules) [![status](https://img.shields.io/badge/status-not%20published-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.3/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-1-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.3/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,6 +34,8 @@ 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.3/docs/first-run.md).
38
+
37
39
  Or from a clone, with nothing installed at all:
38
40
 
39
41
  ```bash
@@ -88,7 +90,7 @@ record, and exits in a way that says *waiting*, not *broken*.
88
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 |
89
91
  | `HELD_AT_SEAL` | the piece is unreproducible, or the deployed copy differs | fix the seal or the deployment — never run "just to see" |
90
92
  | `HELD_AT_COMPLETENESS` | the grid is short | find the missing cells; if proceeding anyway, declare it |
91
- | `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.1/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.3/masterwork/blind_label.py); the line is waiting, not broken |
92
94
  | `HELD_AT_MEASUREMENT` | the benchmark says the score is not comparable | judge from a separate endpoint, or declare and keep the stamp |
93
95
  | `HELD_AT_GATE` | a threshold sits inside its own noise | the gate is void; measure the band, write a new gate |
94
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 |
@@ -146,7 +148,7 @@ deleted, because deciding what to remove is not the line's call.
146
148
 
147
149
  ## Making a piece
148
150
 
149
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.1/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.3/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>
150
152
 
151
153
  `pipeline/ceremony.py` runs the sitting. Four properties are the recipe rather
152
154
  than implementation detail, and each was paid for:
@@ -193,10 +195,10 @@ You get the line and its gates, the shape of the sitting, and the method
193
195
  written down: how a tale differs from an instruction, where a tale comes from,
194
196
  what a question has to do, why instructions come last, and how to tag a tale
195
197
  whose effect cannot be separated. That is
196
- [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/GROWING.md), and it is the part that transfers.
198
+ [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/GROWING.md), and it is the part that transfers.
197
199
 
198
200
  You do not get a corpus. Three pairs come with the repository in
199
- [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/starter/) so the machinery runs end to end and the
201
+ [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/starter/) so the machinery runs end to end and the
200
202
  form is unambiguous; they are a beginning and they are meant to be outgrown.
201
203
  The corpus is the work, and it has to be yours: a tale is written from
202
204
  something **you** measured and got wrong, and a corpus assembled from someone
@@ -237,7 +239,7 @@ Not automatable, and this is the honest part:
237
239
 
238
240
  ## Status
239
241
 
240
- Not published, and not proven. Our own corpus has eighteen tales and took
242
+ Published, and not proven. Our own corpus has eighteen tales and took
241
243
  months. It has produced eighteen candidates and none of them is ahead of the
242
244
  agent already running: the closest comparison came back `UNRESOLVED` rather
243
245
  than won — the difference was smaller than the gate could resolve, which is not
@@ -252,12 +254,17 @@ the position — a masterwork is submitted, and it can be refused.
252
254
 
253
255
  | document | when you need it |
254
256
  |---|---|
255
- | [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.1/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 |
256
- | [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.1/docs/run-spec.md) | writing the JSON for one run, field by field |
257
- | [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.1/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
258
- | [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.1/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.1/gates/succession.md) |
259
- | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/GROWING.md) | before writing a single tale — the shape is in [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/starter/) |
260
- | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.1/CHANGELOG.md) | every entry names the incident that caused it |
257
+ | [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.3/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.3/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.3/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.3/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.3/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.3/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.3/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.3/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.3/gates/succession.md) |
265
+ | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/GROWING.md) | before writing a single tale — the shape is in [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/starter/) |
266
+ | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.3/CHANGELOG.md) | every entry names the incident that caused it |
267
+ | [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.3/assets/README.md) | you need a fifth image that matches the four |
261
268
 
262
269
  <details>
263
270
  <summary>Layout</summary>
@@ -275,8 +282,10 @@ runs/ local run directories (ignored — heavy and re-derivable)
275
282
 
276
283
  ---
277
284
 
278
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.1/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
285
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.3/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
286
+
287
+ Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.3/CONTRIBUTING.md) · Changelog:
288
+ [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.3/CHANGELOG.md) · Licensed under the
289
+ [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.3/LICENSE).
279
290
 
280
- Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.1/CONTRIBUTING.md) · Changelog:
281
- [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.1/CHANGELOG.md) · Licensed under the
282
- [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.1/LICENSE).
291
+ 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.1"
7
+ __version__ = "0.0.3"
@@ -79,7 +79,7 @@ def _md5(text: str) -> str:
79
79
  return hashlib.md5(text.encode()).hexdigest()
80
80
 
81
81
 
82
- def hold(corpus: str, script: dict, endpoint: str, model: str | None = None,
82
+ def hold(corpus: str, script: dict, script_hash: str, endpoint: str, model: str | None = None,
83
83
  params: dict | None = None, order_seed: int = 0,
84
84
  sampling_seed: int | None = None, max_tokens: int = 8000,
85
85
  report=lambda *_: None) -> dict:
@@ -135,7 +135,14 @@ def hold(corpus: str, script: dict, endpoint: str, model: str | None = None,
135
135
  "order_seed": order_seed,
136
136
  "sampling_seed": sampling_seed,
137
137
  "corpus_hash": _md5(corpus),
138
- "script_hash": _md5(json.dumps(script, sort_keys=True, ensure_ascii=False)),
138
+ # The file's bytes, not a re-serialisation of the parsed object.
139
+ # The seal gate hashes what is on disk, so a hash taken from
140
+ # `json.dumps` could never match it — `--script` verification
141
+ # was unpassable for every piece this ceremony had ever sealed,
142
+ # and a guard that always fires teaches the operator to stop
143
+ # passing the flag. It must also be reproducible with `md5sum`
144
+ # by someone who does not run this code.
145
+ "script_hash": script_hash,
139
146
  "questions_asked": [q["label"] for q in questions],
140
147
  "date": time.strftime("%Y-%m-%d"),
141
148
  }
@@ -182,12 +189,14 @@ def main(argv=None) -> int:
182
189
  a = ap.parse_args(argv)
183
190
 
184
191
  corpus = open(a.corpus, encoding="utf-8").read()
185
- script = json.load(open(a.script, encoding="utf-8"))
192
+ script_bytes = open(a.script, "rb").read()
193
+ script = json.loads(script_bytes)
194
+ script_hash = hashlib.md5(script_bytes).hexdigest()
186
195
  params = json.load(open(a.params_file, encoding="utf-8")) if a.params_file else None
187
196
 
188
197
  print(f"sitting: {len(script['questions'])} questions, order seed "
189
198
  f"{a.order_seed}, sampling seed {a.sampling_seed}", file=sys.stderr)
190
- transcript = hold(corpus, script, a.endpoint, a.model, params,
199
+ transcript = hold(corpus, script, script_hash, a.endpoint, a.model, params,
191
200
  a.order_seed, a.sampling_seed, a.max_tokens,
192
201
  report=lambda m: print(m, file=sys.stderr, flush=True))
193
202
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: masterwork
3
- Version: 0.0.1
3
+ Version: 0.0.3
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
@@ -25,11 +25,11 @@ License-File: LICENSE
25
25
  Requires-Dist: journeyman-bench>=0.2.1
26
26
  Dynamic: license-file
27
27
 
28
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.1/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.3/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.1/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.1/CONTRIBUTING.md#ground-rules) [![status](https://img.shields.io/badge/status-not%20published-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.3/LICENSE) [![dependencies](https://img.shields.io/badge/dependencies-1-brightgreen)](https://github.com/codechu/masterwork/blob/v0.0.3/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,6 +61,8 @@ 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.3/docs/first-run.md).
65
+
64
66
  Or from a clone, with nothing installed at all:
65
67
 
66
68
  ```bash
@@ -115,7 +117,7 @@ record, and exits in a way that says *waiting*, not *broken*.
115
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 |
116
118
  | `HELD_AT_SEAL` | the piece is unreproducible, or the deployed copy differs | fix the seal or the deployment — never run "just to see" |
117
119
  | `HELD_AT_COMPLETENESS` | the grid is short | find the missing cells; if proceeding anyway, declare it |
118
- | `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.1/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.3/masterwork/blind_label.py); the line is waiting, not broken |
119
121
  | `HELD_AT_MEASUREMENT` | the benchmark says the score is not comparable | judge from a separate endpoint, or declare and keep the stamp |
120
122
  | `HELD_AT_GATE` | a threshold sits inside its own noise | the gate is void; measure the band, write a new gate |
121
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 |
@@ -173,7 +175,7 @@ deleted, because deciding what to remove is not the line's call.
173
175
 
174
176
  ## Making a piece
175
177
 
176
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.1/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.3/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>
177
179
 
178
180
  `pipeline/ceremony.py` runs the sitting. Four properties are the recipe rather
179
181
  than implementation detail, and each was paid for:
@@ -220,10 +222,10 @@ You get the line and its gates, the shape of the sitting, and the method
220
222
  written down: how a tale differs from an instruction, where a tale comes from,
221
223
  what a question has to do, why instructions come last, and how to tag a tale
222
224
  whose effect cannot be separated. That is
223
- [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/GROWING.md), and it is the part that transfers.
225
+ [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/GROWING.md), and it is the part that transfers.
224
226
 
225
227
  You do not get a corpus. Three pairs come with the repository in
226
- [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/starter/) so the machinery runs end to end and the
228
+ [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/starter/) so the machinery runs end to end and the
227
229
  form is unambiguous; they are a beginning and they are meant to be outgrown.
228
230
  The corpus is the work, and it has to be yours: a tale is written from
229
231
  something **you** measured and got wrong, and a corpus assembled from someone
@@ -264,7 +266,7 @@ Not automatable, and this is the honest part:
264
266
 
265
267
  ## Status
266
268
 
267
- Not published, and not proven. Our own corpus has eighteen tales and took
269
+ Published, and not proven. Our own corpus has eighteen tales and took
268
270
  months. It has produced eighteen candidates and none of them is ahead of the
269
271
  agent already running: the closest comparison came back `UNRESOLVED` rather
270
272
  than won — the difference was smaller than the gate could resolve, which is not
@@ -279,12 +281,17 @@ the position — a masterwork is submitted, and it can be refused.
279
281
 
280
282
  | document | when you need it |
281
283
  |---|---|
282
- | [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.1/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 |
283
- | [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.1/docs/run-spec.md) | writing the JSON for one run, field by field |
284
- | [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.1/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
285
- | [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.1/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.1/gates/succession.md) |
286
- | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/GROWING.md) | before writing a single tale — the shape is in [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.1/corpus/starter/) |
287
- | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.1/CHANGELOG.md) | every entry names the incident that caused it |
284
+ | [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.3/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.3/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.3/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.3/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.3/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.3/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.3/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.3/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.3/gates/succession.md) |
292
+ | [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/GROWING.md) | before writing a single tale — the shape is in [`corpus/starter/`](https://github.com/codechu/masterwork/blob/v0.0.3/corpus/starter/) |
293
+ | [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.3/CHANGELOG.md) | every entry names the incident that caused it |
294
+ | [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.3/assets/README.md) | you need a fifth image that matches the four |
288
295
 
289
296
  <details>
290
297
  <summary>Layout</summary>
@@ -302,8 +309,10 @@ runs/ local run directories (ignored — heavy and re-derivable)
302
309
 
303
310
  ---
304
311
 
305
- <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.1/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
312
+ <p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.3/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
313
+
314
+ Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.3/CONTRIBUTING.md) · Changelog:
315
+ [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.3/CHANGELOG.md) · Licensed under the
316
+ [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.3/LICENSE).
306
317
 
307
- Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.1/CONTRIBUTING.md) · Changelog:
308
- [CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.1/CHANGELOG.md) · Licensed under the
309
- [Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.1/LICENSE).
318
+ Part of [Codechu](https://github.com/codechu).
@@ -21,6 +21,7 @@ masterwork.egg-info/dependency_links.txt
21
21
  masterwork.egg-info/entry_points.txt
22
22
  masterwork.egg-info/requires.txt
23
23
  masterwork.egg-info/top_level.txt
24
+ tests/test_badges.py
24
25
  tests/test_blind_label.py
25
26
  tests/test_campaign.py
26
27
  tests/test_cells.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "masterwork"
7
- version = "0.0.1"
7
+ version = "0.0.3"
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"
@@ -0,0 +1,75 @@
1
+ """The badges that state a fact are checked against the fact.
2
+
3
+ Three of them assert rather than report: the Python floor, the dependency
4
+ count, and the licence. Nothing read them, and two went stale the day the
5
+ package shipped — the status badge still said "not published" after it was
6
+ on PyPI, and the dependency badge said zero while `journeyman-bench` was a
7
+ declared requirement.
8
+
9
+ The repository already holds that a badge pointing nowhere is decoration
10
+ wearing the costume of a signal. A badge asserting an unchecked fact is the
11
+ same costume, and it is worse, because a stale number reads as a measurement.
12
+ """
13
+ import os
14
+ import re
15
+
16
+ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17
+
18
+
19
+ def read(name):
20
+ with open(os.path.join(ROOT, name), encoding="utf-8") as fh:
21
+ return fh.read()
22
+
23
+
24
+ def project():
25
+ text = read("pyproject.toml")
26
+
27
+ def field(name):
28
+ m = re.search(rf'(?m)^{name} = "([^"]+)"', text)
29
+ return m.group(1) if m else None
30
+
31
+ m = re.search(r"(?ms)^dependencies = \[(.*?)\]", text)
32
+ deps = re.findall(r'"([^"]+)"', m.group(1)) if m else []
33
+ return {"requires-python": field("requires-python"),
34
+ "license": field("license"), "dependencies": deps}
35
+
36
+
37
+ def test_python_floor_matches_requires_python():
38
+ m = re.search(r"badge/python-([\d.]+)%2B-", read("README.md"))
39
+ assert m, "the Python badge changed shape; update this test"
40
+ assert project()["requires-python"] == ">=" + m.group(1)
41
+
42
+
43
+ def test_dependency_count_matches():
44
+ m = re.search(r"badge/dependencies-(\d+)-", read("README.md"))
45
+ assert m, "the dependency badge changed shape"
46
+ declared = project()["dependencies"]
47
+ assert int(m.group(1)) == len(declared), (
48
+ f"badge says {m.group(1)}, pyproject declares {declared}")
49
+
50
+
51
+ def test_licence_matches():
52
+ # shields escapes a literal "-" as "--" and the last segment is the
53
+ # colour. Two ways to get this wrong, both met: a non-greedy match
54
+ # reads "Apache" out of "Apache--2.0", and a greedy `.+` swallows the
55
+ # rest of the row when every badge sits on one line, as they do here.
56
+ m = re.search(r"badge/license-([^()]+)-[a-z]+\)", read("README.md"))
57
+ assert m, "the licence badge changed shape"
58
+ assert m.group(1).replace("--", "-") == project()["license"]
59
+
60
+
61
+ def test_no_badge_claims_it_is_unpublished():
62
+ """It is on PyPI. A badge saying otherwise is a stale claim, not modesty."""
63
+ row = read("README.md")
64
+ assert "not%20published" not in row, (
65
+ "the package is published; say unproven if that is what is meant")
66
+
67
+
68
+ def test_every_badge_is_a_link():
69
+ row = [l for l in read("README.md").splitlines() if l.startswith("[![")]
70
+ assert row, "no badge row found"
71
+ for line in row:
72
+ for badge in re.findall(r"\[!\[[^\]]*\]\([^)]*\)\]\(([^)]*)\)", line):
73
+ assert badge, f"badge with an empty target: {line}"
74
+ assert re.fullmatch(r"(\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)\s*)+", line), (
75
+ f"a badge in this row is not a link: {line}")
@@ -1,4 +1,5 @@
1
1
  """The sitting. The tests are about its shape, which is the recipe."""
2
+ import hashlib
2
3
  import json
3
4
  import os
4
5
  import sys
@@ -45,6 +46,10 @@ class Stub(BaseHTTPRequestHandler):
45
46
  pass
46
47
 
47
48
 
49
+ SCRIPT_BYTES = json.dumps(SCRIPT, indent=2).encode()
50
+ SCRIPT_HASH = hashlib.md5(SCRIPT_BYTES).hexdigest()
51
+
52
+
48
53
  def serve():
49
54
  s = HTTPServer(("127.0.0.1", 0), Stub)
50
55
  threading.Thread(target=s.serve_forever, daemon=True).start()
@@ -57,7 +62,7 @@ def test_the_sitting_is_one_session_and_nothing_is_anchored():
57
62
  Stub.seen = []
58
63
  s, url = serve()
59
64
  try:
60
- t = ceremony.hold("THE TEACHINGS", SCRIPT, url, order_seed=1, sampling_seed=7)
65
+ t = ceremony.hold("THE TEACHINGS", SCRIPT, SCRIPT_HASH, url, order_seed=1, sampling_seed=7)
61
66
  finally:
62
67
  s.shutdown()
63
68
  assert len(t["rounds"]) == 3
@@ -77,7 +82,7 @@ def test_order_is_shuffled_by_the_seed():
77
82
  Stub.seen = []
78
83
  s, url = serve()
79
84
  try:
80
- orders.append(tuple(ceremony.hold("t", SCRIPT, url,
85
+ orders.append(tuple(ceremony.hold("t", SCRIPT, SCRIPT_HASH, url,
81
86
  order_seed=seed)["questions_asked"]))
82
87
  finally:
83
88
  s.shutdown()
@@ -91,7 +96,7 @@ def test_an_empty_answer_is_retried_not_recorded_as_silence():
91
96
  Stub.empties = 1
92
97
  s, url = serve()
93
98
  try:
94
- t = ceremony.hold("t", SCRIPT, url)
99
+ t = ceremony.hold("t", SCRIPT, SCRIPT_HASH, url)
95
100
  finally:
96
101
  s.shutdown()
97
102
  Stub.empties = 0
@@ -104,7 +109,7 @@ def test_the_sealed_piece_passes_the_seal_gate(tmp_path=None):
104
109
  Stub.seen = []
105
110
  s, url = serve()
106
111
  try:
107
- t = ceremony.hold("THE TEACHINGS", SCRIPT, url, order_seed=2, sampling_seed=11)
112
+ t = ceremony.hold("THE TEACHINGS", SCRIPT, SCRIPT_HASH, url, order_seed=2, sampling_seed=11)
108
113
  finally:
109
114
  s.shutdown()
110
115
  with tempfile.TemporaryDirectory() as tmp:
@@ -112,8 +117,15 @@ def test_the_sealed_piece_passes_the_seal_gate(tmp_path=None):
112
117
  open(piece, "w").write(ceremony.seal_text(t))
113
118
  corpus = os.path.join(tmp, "corpus.md")
114
119
  open(corpus, "w").write("THE TEACHINGS")
120
+ script = os.path.join(tmp, "script.json")
121
+ open(script, "wb").write(SCRIPT_BYTES)
115
122
  assert seal.read_seal(piece).complete
116
- assert not seal.verify(piece, corpus=corpus)
123
+ # Both files, not just the corpus. The script was the axis this test
124
+ # did not check, and it was the one that was broken: the sitting
125
+ # hashed a re-serialisation of the parsed script while the gate
126
+ # hashed the file, so `--script` could never pass for any piece the
127
+ # ceremony had ever sealed.
128
+ assert not seal.verify(piece, corpus=corpus, script=script)
117
129
 
118
130
 
119
131
  def test_the_sitting_will_not_seal_a_piece_it_cannot_make_again():
@@ -39,11 +39,25 @@ def test_the_judge_is_a_declared_dependency():
39
39
 
40
40
 
41
41
  def test_versions_agree():
42
- packaged = re.search(r'(?m)^version = "([^"]+)"',
43
- read("pyproject.toml")).group(1)
44
- in_code = re.search(r'(?m)^__version__ = "([^"]+)"',
45
- read("masterwork/__init__.py")).group(1)
46
- assert packaged == in_code, f"pyproject {packaged} vs code {in_code}"
42
+ """Four records now, and a release moves all of them or none.
43
+
44
+ Next door a release bumped one of four and left three behind, so the
45
+ package announced one version while every artefact it produced was
46
+ stamped with another. The archived record is the worst of them: a DOI
47
+ citing a version that did not produce the work cannot be corrected
48
+ after the fact.
49
+ """
50
+ found = {
51
+ "pyproject.toml": re.search(r'(?m)^version = "([^"]+)"',
52
+ read("pyproject.toml")).group(1),
53
+ "masterwork/__init__.py": re.search(r'(?m)^__version__ = "([^"]+)"',
54
+ read("masterwork/__init__.py")).group(1),
55
+ "CITATION.cff": re.search(r"(?m)^version: (\S+)",
56
+ read("CITATION.cff")).group(1),
57
+ ".zenodo.json": re.search(r'"version": "([^"]+)"',
58
+ read(".zenodo.json")).group(1),
59
+ }
60
+ assert len(set(found.values())) == 1, f"version drift: {found}"
47
61
 
48
62
 
49
63
  def test_pypi_readme_is_current():
File without changes
File without changes
File without changes