Reorganize paperspec commands and pin dependency versions #2
No files matched your search
@@ -18,4 +18,4 @@ jobs:
|
||||
- name: Check the repository
|
||||
run: pixi run check
|
||||
- name: Stress the reference implementation
|
||||
run: pixi run python tools/stress.py
|
||||
run: pixi run stress
|
||||
+5
-3
@@ -22,10 +22,12 @@ The first release.
|
||||
|
||||
### 0.1.1
|
||||
|
||||
Packaging and lazy imports only; the formats stay at 0.1.0, and no computed value, vector, fixture, schema or SQL file changed.
|
||||
Packaging, commands and lazy imports only; the formats stay at 0.1.0, and no computed value, vector, fixture, schema or SQL file changed.
|
||||
|
||||
- The oracle moves from `tools/oracle/` to `src/paperspec/` and installs as the Python package `paperspec` (`pyproject.toml`, setuptools), with `schemas/`, `sql/` and `vectors/` as the data subpackages `paperspec.schemas`, `paperspec.sql` and `paperspec.vectors`, so other repositories import the reference implementation instead of reimplementing it.
|
||||
- The package version comes from git tags through setuptools-scm.
|
||||
- The base package has no dependencies and requires Python 3.14, the first with the Unicode 16.0 character data the specification pins; the extras `paperspec[schema]` (`jsonschema`, `referencing`), `paperspec[crypto]` (`cryptography`) and `paperspec[tools]` (both) cover `schema` and the HKDF and Ed25519 functions of `crypto`, which now import their packages on first use.
|
||||
- The tools import `paperspec` from `src/`, and their Python 3.14 and package check moves to `tools/__init__.py`, so the package itself checks nothing on import.
|
||||
- The base package has no dependencies and requires Python 3.14, the only version with the Unicode 16.0 character data the specification pins, and refuses to import under any other Unicode version; the extras `paperspec[schema]` (`jsonschema`, `referencing`), `paperspec[crypto]` (`cryptography`) and `paperspec[tools]` (both) cover `schema` and the HKDF and Ed25519 functions of `crypto`, which now import their packages on first use.
|
||||
- `paperspec.resources.path` finds the schemas, SQL and vectors in an installed wheel and in an editable install alike.
|
||||
- The programs in `tools/` become the package's commands in `src/paperspec/cli/`: `paperspec-check`, `paperspec-build`, `paperspec-validate`, `paperspec-load`, `paperspec-stress` and `paperspec-site`, which the pixi tasks and the workflow run.
|
||||
- `src/` is Apache-2.0, as `tools/` was.
|
||||
- The pixi workspace installs the package editable, and a `dev` environment builds the wheel with `pixi run -e dev wheel`.
|
||||
+5
-5
@@ -13,11 +13,11 @@ The schemas, SQL, registries, vectors and fixtures then follow the chapter ([spe
|
||||
2. **Change the schemas, SQL and registries** that the rule shapes.
|
||||
A new refusal gets a rule ID in [registry/rules.json](registry/rules.json).
|
||||
3. **Compute, never type, the expected values.**
|
||||
Change the oracle in [src/paperspec/](src/paperspec/) to apply the rule, and add the case that shows it to [tools/build.py](tools/build.py): a vector, a fixture or a broken package in `fixtures/invalid/`.
|
||||
Change the oracle in [src/paperspec/](src/paperspec/) to apply the rule, and add the case that shows it to [src/paperspec/cli/build.py](src/paperspec/cli/build.py): a vector, a fixture or a broken package in `fixtures/invalid/`.
|
||||
Then run `pixi run build`, which rewrites `fixtures/` and `vectors/`.
|
||||
4. **Run the checks.**
|
||||
`pixi run check` must end with `All checks pass.`
|
||||
Without pixi, run `python3 tools/check.py`, with Python 3.14 and two packages, `jsonschema` and `cryptography` installed.
|
||||
Without pixi, install the checkout with `pip install -e ".[tools]"` on Python 3.14, then run `paperspec-check`.
|
||||
5. **Record the change** in [CHANGELOG.md](CHANGELOG.md).
|
||||
|
||||
## Versions
|
||||
@@ -38,8 +38,8 @@ A release is a tag on `main` whose version is the top heading of [CHANGELOG.md](
|
||||
Before tagging:
|
||||
|
||||
1. `pixi run check` passes on Linux and macOS, which the workflow in `.gitea/workflows/` runs on every push.
|
||||
The check also confirms that every count the prose states (formats, schemas, vectors, broken packages) matches the repository, and that `pixi.toml`, the registries and `tools/build.py` state the same version.
|
||||
2. `python3 tools/stress.py` passes with its default seed, and with one or two others.
|
||||
The check also confirms that every count the prose states (formats, schemas, vectors, broken packages) matches the repository, and that `pixi.toml`, the registries and `src/paperspec/cli/build.py` state the same version.
|
||||
2. `paperspec-stress` passes with its default seed, and with one or two others.
|
||||
3. The website is published from the release ([deploy/](deploy/README.md)), and the JSON-LD context at `https://paperspec.scient.ing/context/scienting-1.jsonld` and a schema at its `$id` both answer with their media types.
|
||||
Layers claim to be W3C Web Annotations, and a JSON-LD processor that cannot fetch the context cannot read them.
|
||||
4. The decisions that freeze on release are made: enumeration values and format identifiers never change meaning afterwards ([spec/22 §22.5](spec/22-conformance.md#225-stability)), so a name worth changing is changed now.
|
||||
@@ -56,4 +56,4 @@ The keys in `fixtures/keys/` are public test keys and must never sign anything e
|
||||
|
||||
## Licenses
|
||||
|
||||
Each path keeps the license that [LICENSE.md](LICENSE.md) gives it: prose CC-BY-4.0, the normative data CC0-1.0, and `tools/` Apache-2.0.
|
||||
Each path keeps the license that [LICENSE.md](LICENSE.md) gives it: prose CC-BY-4.0, the normative data CC0-1.0, and the code in `src/` and `tools/` Apache-2.0.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
| --- | --- | --- |
|
||||
| `spec/`, `docs/`, `README.md`, `CONTRIBUTING.md`, `CHANGELOG.md` and the other prose | Creative Commons Attribution 4.0 International | `CC-BY-4.0` |
|
||||
| `schemas/`, `sql/`, `context/`, `registry/`, `vectors/`, `fixtures/` | Creative Commons Zero 1.0 Universal | `CC0-1.0` |
|
||||
| `tools/` | Apache License 2.0 | `Apache-2.0` |
|
||||
| `src/` (the `paperspec` package and its commands), `tools/` | Apache License 2.0 | `Apache-2.0` |
|
||||
|
||||
The legal texts are at <https://creativecommons.org/licenses/by/4.0/legalcode>, <https://creativecommons.org/publicdomain/zero/1.0/legalcode> and <https://www.apache.org/licenses/LICENSE-2.0>.
|
||||
|
||||
|
||||
@@ -61,8 +61,8 @@ The address grammar is in [spec/06 §6.1](spec/06-anchors.md#61-addresses), and
|
||||
| [registry/](registry/README.md) | The list of formats, the rule IDs validators report, download-stamp rules, and the default rights each processing stage requires. |
|
||||
| [vectors/](vectors/README.md) | Test vectors: inputs and the exact outputs every implementation must compute. |
|
||||
| [fixtures/](fixtures/README.md) | Example packages and records, deliberately broken packages, and the test keys that signed them. |
|
||||
| [tools/](tools/README.md) | The programs that build and check everything above. |
|
||||
| [src/paperspec/](src/paperspec/) | The reference implementation of every rule, installable as the Python package `paperspec`. |
|
||||
| [tools/](tools/README.md) | How to run the commands that build and check everything above. |
|
||||
| [src/paperspec/](src/paperspec/) | The reference implementation of every rule and those commands, installable as the Python package `paperspec`. |
|
||||
| [docs/](docs/README.md) | Guides for people getting to know the project. |
|
||||
| [deploy/](deploy/README.md) | How the website at <https://paperspec.scient.ing> is built and served. |
|
||||
|
||||
@@ -81,10 +81,10 @@ Last, it loads every package into SQLite and back, and checks the prose and the
|
||||
Pixi lints the Markdown first.
|
||||
The run ends with `All checks pass.`
|
||||
`pixi task list` shows the other tasks: `quick`, `build`, `lint`, `load`, `validate` and `stress`.
|
||||
`pixi run python tools/site.py` builds the website into `_site/` ([deploy/](deploy/README.md)).
|
||||
`pixi run site` builds the website into `_site/` ([deploy/](deploy/README.md)).
|
||||
|
||||
The tools need Python 3.14 and two packages, `jsonschema` and `cryptography`, which pixi installs along with markdownlint for the Markdown.
|
||||
Without pixi, install those two with pip and run `python3 tools/check.py`.
|
||||
Without pixi, install the checkout as the package with `pip install -e ".[tools]"` on Python 3.14, then run `paperspec-check`.
|
||||
|
||||
To explore a worked package, load the example paper (a synthetic chemistry paper with 12 corrections) into SQLite and query it; [docs/examples.md](docs/examples.md) has queries to start from.
|
||||
|
||||
@@ -132,5 +132,5 @@ Table columns and figure-text items have IDs and are not nodes ([spec/04 §4.2](
|
||||
|
||||
The specification and other prose are CC BY 4.0, so anyone may implement the formats.
|
||||
Schemas, SQL, the JSON-LD context, the registries, the vectors and the fixtures are CC0 1.0.
|
||||
The tools are Apache-2.0.
|
||||
The code, the `paperspec` package in `src/` with its commands, is Apache-2.0.
|
||||
See [LICENSE.md](LICENSE.md).
|
||||
+1
-1
@@ -96,7 +96,7 @@ Three of the nine fixture annotations have only an `AnchorSelector` (the section
|
||||
- Reader: Follow [chapter 6](../spec/06-anchors.md) to resolve an anchor.
|
||||
The file `vectors/resolve.jsonl` resolves seventeen anchors against `fixtures/packages/hale2026harmonic-damb` and gives every outcome.
|
||||
- JSON-LD processor: Use a JSON-LD 1.1 processor; the context relies on `@json` and `@version` 1.1.
|
||||
- Checker: `schemas/annotation.schema.json` and `schemas/layer.schema.json` validate the files. `tools/check.py` also checks that each annotation's ID derives from its content and that the receipt at `fixtures/records/layer/receipt.json` covers each `confirmed` annotation.
|
||||
- Checker: `schemas/annotation.schema.json` and `schemas/layer.schema.json` validate the files. `paperspec-check` also checks that each annotation's ID derives from its content and that the receipt at `fixtures/records/layer/receipt.json` covers each `confirmed` annotation.
|
||||
|
||||
The traps:
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
# Deploying the website
|
||||
|
||||
This folder holds what it takes to serve the paperspec website at <https://paperspec.scient.ing>.
|
||||
The site is static: `tools/site.py` builds it into `_site/`, and any web server can serve that folder.
|
||||
The site is static: `paperspec-site` builds it into `_site/`, and any web server can serve that folder.
|
||||
|
||||
## What the site serves
|
||||
|
||||
@@ -21,8 +21,8 @@ The pages need no server-side code; search runs in the browser over a small inde
|
||||
## Build it
|
||||
|
||||
```sh
|
||||
pixi run python tools/site.py # writes _site/
|
||||
pixi run python tools/site.py --out /tmp/paperspec-site
|
||||
pixi run site # writes _site/
|
||||
pixi run site --out /tmp/paperspec-site
|
||||
```
|
||||
|
||||
`pixi run check` builds the site into a temporary folder and checks that every Markdown file has a page, that every internal link and fragment resolves, that no Markdown is left unrendered, and that each schema's `$id`, the context's address and the vocabulary's IRIs are files of the site.
|
||||
|
||||
+1
-1
@@ -428,7 +428,7 @@ Keep using it as a reading and distribution format.
|
||||
*Status.*
|
||||
The specification is version 0.1.0.
|
||||
`registry/formats.json` lists 32 formats: 19 are `candidate` (complete, with vectors and fixtures, ready to implement) and 13 are `provisional` (complete; expected to change as implementations gain experience): project, bundle, dataset, digitization, view, snapshot, draft-ids, exercise, submission, grade, checklist, log-entry and checkpoint ([spec/00 §0.4](../spec/00-introduction.md#04-status)).
|
||||
The repository holds 24 chapters, 42 JSON Schemas, 25 vector files, five valid fixture packages, one of them also zipped, and 31 invalid ones, each naming the rule it breaks, and a reference checker, `tools/check.py`.
|
||||
The repository holds 24 chapters, 42 JSON Schemas, 25 vector files, five valid fixture packages, one of them also zipped, and 31 invalid ones, each naming the rule it breaks, and a reference checker, `paperspec-check`.
|
||||
Licenses: prose CC BY 4.0; schemas, SQL, the JSON-LD context, registries, vectors and fixtures CC0 1.0; tools Apache-2.0 ([README.md](../README.md#licenses)).
|
||||
|
||||
*No adoption claim.*
|
||||
|
||||
+5
-5
@@ -19,10 +19,10 @@ This shows how to confirm that the repository's specification, fixtures and test
|
||||
pixi run check
|
||||
```
|
||||
|
||||
`pixi run check` lints the Markdown, then runs `python tools/check.py`, the repository's reference checker.
|
||||
`pixi run check` lints the Markdown, then runs `paperspec-check`, the repository's reference checker.
|
||||
Each section prints how many checks it ran, and the last line is `All checks pass.`
|
||||
`pixi run quick`, or `python3 tools/check.py --quick`, skips the slowest part, which rebuilds everything and compares it byte for byte.
|
||||
Without pixi, the Markdown lint is a separate step (`markdownlint-cli2`); `tools/check.py` does not run it.
|
||||
`pixi run quick`, or `paperspec-check --quick`, skips the slowest part, which rebuilds everything and compares it byte for byte.
|
||||
Without pixi, the Markdown lint is a separate step (`markdownlint-cli2`); `paperspec-check` does not run it.
|
||||
|
||||
## Recompute a hash by hand
|
||||
|
||||
@@ -146,7 +146,7 @@ This shows that the same packages can live in a database and be queried with SQL
|
||||
pixi run load --sqlite lab.db fixtures/packages/hale2026harmonic-damb fixtures/packages/okafor2025solvent-acth fixtures/packages/writer2027fixed-ynr7.v3
|
||||
```
|
||||
|
||||
Without pixi, run `python3 tools/load.py` with the same arguments.
|
||||
Without pixi, run `paperspec-load` with the same arguments.
|
||||
|
||||
```text
|
||||
hale2026harmonic-damb: 75 nodes
|
||||
@@ -154,7 +154,7 @@ okafor2025solvent-acth: 15 nodes
|
||||
writer2027fixed-ynr7.v3: 15 nodes
|
||||
```
|
||||
|
||||
The tables are those of [sql/doc.sql](../sql/doc.sql) and [sql/store.sql](../sql/store.sql), which run unchanged on Dolt and PostgreSQL as well ([chapter 13](../spec/13-database.md)); `tools/check.py` exercises them on SQLite only, and this repository has not yet run them on Dolt or PostgreSQL.
|
||||
The tables are those of [sql/doc.sql](../sql/doc.sql) and [sql/store.sql](../sql/store.sql), which run unchanged on Dolt and PostgreSQL as well ([chapter 13](../spec/13-database.md)); `paperspec-check` exercises them on SQLite only, and this repository has not yet run them on Dolt or PostgreSQL.
|
||||
Here are four queries, in Python's `sqlite3` so that nothing else is needed.
|
||||
|
||||
```python
|
||||
|
||||
+2
-2
@@ -107,7 +107,7 @@ A `candidate` format is complete, with vectors and fixtures, and ready to implem
|
||||
A `provisional` one is complete and expected to change as implementations gain experience.
|
||||
The provisional formats are dataset, digitization, view, project, bundle, snapshot, draft-ids, exercise, submission, grade, checklist, log-entry and checkpoint.
|
||||
|
||||
The repository holds a reference checker (`tools/check.py`), 25 vector files, five valid packages, one of them also as a zip, and 31 deliberately broken packages ([spec/00 §0.4](../spec/00-introduction.md#04-status), [spec/22](../spec/22-conformance.md)).
|
||||
The repository holds a reference checker (`paperspec-check`), 25 vector files, five valid packages, one of them also as a zip, and 31 deliberately broken packages ([spec/00 §0.4](../spec/00-introduction.md#04-status), [spec/22](../spec/22-conformance.md)).
|
||||
|
||||
## What does it not do?
|
||||
|
||||
@@ -126,6 +126,6 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
## What are the files in `tools/` for?
|
||||
|
||||
`build.py` builds the fixtures and vectors. `check.py` recomputes and checks every one, and `load.py` loads packages into SQLite.
|
||||
`paperspec-build` builds the fixtures and vectors. `paperspec-check` recomputes and checks every one, and `paperspec-load` loads packages into SQLite.
|
||||
The oracle's modules in `src/paperspec/` compute every expected value, so that none is typed by hand.
|
||||
The other repositories of the project install them as the Python package `paperspec`; independent implementations follow the specification ([tools/README.md](../tools/README.md)).
|
||||
+3
-3
@@ -54,7 +54,7 @@ It appears inside hashed data and test vectors, and it is separate from the repo
|
||||
| [spec/](../spec/README.md) | The specification, in 24 chapters (00 to 23). Normative. |
|
||||
| [schemas/](../schemas/README.md) | 42 JSON (plain-text data format) Schemas for the files and records. |
|
||||
| [registry/](../registry/README.md) | The list of formats, the rule IDs a checker reports, download-stamp rules, and default rights requirements. |
|
||||
| [tools/](../tools/README.md) | Reference programs: `check.py` checks everything, `build.py` rebuilds the fixtures and vectors, and `load.py` loads packages into SQLite. |
|
||||
| [tools/](../tools/README.md) | How to run the reference commands: `paperspec-check` checks everything, `paperspec-build` rebuilds the fixtures and vectors, and `paperspec-load` loads packages into SQLite. |
|
||||
| [src/paperspec/](../src/paperspec/) | The oracle, a compact implementation of every rule, which the other repositories of the project install as the Python package `paperspec`. |
|
||||
| [vectors/](../vectors/README.md) | 25 files of test vectors: inputs and the exact outputs every implementation must compute. |
|
||||
| [fixtures/](../fixtures/README.md) | Five valid example packages, one of them also as a zip, 31 deliberately broken ones that each break one named rule, example records, and the test keys that signed them. All text is synthetic. |
|
||||
@@ -73,8 +73,8 @@ The repository ships reference tools only for the checker and store roles.
|
||||
| Reader | Opens a package, resolves addresses and anchors, and shows text, annotations and each passage's fidelity level. |
|
||||
| Writer | Produces files in these formats: nodes, corrections, receipts, annotations, datasets. |
|
||||
| Converter | Turns a witness (a PDF, JATS XML, the article format publishers use, a LaTeX source archive, an HTML page, an EPUB or a Typst project) into a package and raises doubtful readings as suspects (flagged disagreements awaiting review). |
|
||||
| Checker | Recomputes hashes, validates files against their schemas and reports broken rules by rule ID. `tools/check.py` does this for the repository. |
|
||||
| Store | Keeps packages and records in a database a team can clone. `tools/load.py` loads packages into SQLite. |
|
||||
| Checker | Recomputes hashes, validates files against their schemas and reports broken rules by rule ID. `paperspec-check` does this for the repository. |
|
||||
| Store | Keeps packages and records in a database a team can clone. `paperspec-load` loads packages into SQLite. |
|
||||
|
||||
The [specification's reading paths](../spec/README.md) say which chapters each role needs.
|
||||
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ It has nothing to do with the repository's name.
|
||||
|
||||
Open [fixtures/packages/hale2026harmonic-damb/](../fixtures/packages/hale2026harmonic-damb/) beside this page, because every ID below is in it.
|
||||
A package is a folder of files, mostly JSON Lines (`.jsonl`: one JSON record per line).
|
||||
The hand-written source of the first reading is [fixtures/src/hale2026harmonic.json](../fixtures/src/hale2026harmonic.json); no converter ran on it, and [`tools/build.py`](../tools/build.py) expands it into the package.
|
||||
The hand-written source of the first reading is [fixtures/src/hale2026harmonic.json](../fixtures/src/hale2026harmonic.json); no converter ran on it, and [`paperspec-build`](../src/paperspec/cli/build.py) expands it into the package.
|
||||
Each step says what a converter, a person or a checker does.
|
||||
|
||||
## 1. A PDF arrives
|
||||
@@ -166,7 +166,7 @@ It is a [Frictionless Data Package](https://datapackage.org/standard/data-packag
|
||||
Its CSV keeps every digit as printed (`1.526` and `1.5260` stay different), and every value has an anchor to the cell that prints it, so a claim that rests on a value can show the cell.
|
||||
The descriptor types the fields as numbers; how a generic Data Package tool treats `1.5260` was not verified.
|
||||
All twelve value anchors resolve as `current`.
|
||||
The reference checker, [`tools/check.py`](../tools/check.py), confirms each CSV value equals its cell's text.
|
||||
The reference checker, [`paperspec-check`](../src/paperspec/cli/check.py), confirms each CSV value equals its cell's text.
|
||||
|
||||
## 8. Sharing without the text
|
||||
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ A conforming reader accepts each one and recomputes every hash it contains.
|
||||
## Records
|
||||
|
||||
`records/` holds a valid example of every other format that is a JSON file of its own.
|
||||
Three formats have their example elsewhere: the stage requirements (`scienting-stages/1`) and the stamp rules (`scienting-stamps/1`) are in [registry/](../registry/README.md), and a bundle (`scienting-bundle/1`) is a SQLite file that `tools/load.py --bundle` builds from the packages.
|
||||
Three formats have their example elsewhere: the stage requirements (`scienting-stages/1`) and the stamp rules (`scienting-stamps/1`) are in [registry/](../registry/README.md), and a bundle (`scienting-bundle/1`) is a SQLite file that `paperspec-load --bundle` builds from the packages.
|
||||
Each record is a single file, or a folder where a format needs several files.
|
||||
|
||||
| Files | What they are for |
|
||||
@@ -109,4 +109,4 @@ Use them for nothing else.
|
||||
## Sources
|
||||
|
||||
`src/` holds the hand-written descriptions of the three main documents, as first read; see [src/README.md](src/README.md).
|
||||
Everything else here is generated by `python3 tools/build.py`, so change the sources or the builder, never the generated files.
|
||||
Everything else here is generated by `paperspec-build`, so change the sources or the builder, never the generated files.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Fixture sources
|
||||
|
||||
Each source describes a document as a converter first read it, mistakes included, in a shorthand that `tools/build.py` expands.
|
||||
Each source describes a document as a converter first read it, mistakes included, in a shorthand that `paperspec-build` expands.
|
||||
The build mints IDs, applies the listed corrections, lays out synthetic geometry, signs the listed receipts and writes the package.
|
||||
The shorthand is tooling, not a format.
|
||||
|
||||
|
||||
@@ -39,13 +39,13 @@ default = { solve-group = "default" }
|
||||
dev = { features = ["dev"], solve-group = "default" }
|
||||
|
||||
[tasks]
|
||||
check = { cmd = "python tools/check.py", depends-on = [
|
||||
check = { cmd = "paperspec-check", depends-on = [
|
||||
"lint",
|
||||
], description = "Check schemas, records, packages, vectors, a byte-for-byte rebuild, the SQL round trip, prose and links" }
|
||||
quick = { cmd = "python tools/check.py --quick", description = "Check everything except the byte-for-byte rebuild" }
|
||||
build = { cmd = "python tools/build.py", description = "Rebuild fixtures/ and vectors/ from fixtures/src/ and the oracle" }
|
||||
quick = { cmd = "paperspec-check --quick", description = "Check everything except the byte-for-byte rebuild" }
|
||||
build = { cmd = "paperspec-build", description = "Rebuild fixtures/ and vectors/ from fixtures/src/ and the oracle" }
|
||||
lint = { cmd = "markdownlint-cli2", description = "Lint every Markdown file with markdownlint" }
|
||||
load = { cmd = "python tools/load.py", description = "Load packages into SQLite: pixi run load --sqlite lab.db PACKAGE..." }
|
||||
validate = { cmd = "python tools/validate.py", description = "Validate packages, or run proposed corrections through the gate" }
|
||||
stress = { cmd = "python tools/stress.py", description = "Stress the oracle with random inputs: pixi run stress -- --seed N" }
|
||||
site = { cmd = "python tools/site.py", description = "Build the website into _site/" }
|
||||
load = { cmd = "paperspec-load", description = "Load packages into SQLite: pixi run load --sqlite lab.db PACKAGE..." }
|
||||
validate = { cmd = "paperspec-validate", description = "Validate packages, or run proposed corrections through the gate" }
|
||||
stress = { cmd = "paperspec-stress", description = "Stress the oracle with random inputs: pixi run stress --seed N" }
|
||||
site = { cmd = "paperspec-site", description = "Build the website into _site/" }
|
||||
+13
-3
@@ -10,8 +10,9 @@ readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE.md"]
|
||||
authors = [{ name = "Scientific Computing Studio", email = "maintainer@scient.ing" }]
|
||||
# Python 3.14 is the first with the Unicode 16.0 character data the specification pins (spec/01 §1.4).
|
||||
requires-python = ">=3.14"
|
||||
# Python 3.14 is the only version with the Unicode 16.0 character data the specification pins (spec/01 §1.4);
|
||||
# 3.13 has 15.1 and 3.15 has 17.0, and the package refuses to import on either.
|
||||
requires-python = ">=3.14,<3.15"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Science/Research",
|
||||
@@ -28,6 +29,15 @@ schema = ["jsonschema>=4.26", "referencing>=0.35"]
|
||||
crypto = ["cryptography>=46"]
|
||||
tools = ["paperspec[crypto,schema]"]
|
||||
|
||||
# validate and load work wherever the package is installed with [tools]; the other four work on a checkout.
|
||||
[project.scripts]
|
||||
paperspec-build = "paperspec.cli.build:cli"
|
||||
paperspec-check = "paperspec.cli.check:main"
|
||||
paperspec-load = "paperspec.cli.load:main"
|
||||
paperspec-site = "paperspec.cli.site:main"
|
||||
paperspec-stress = "paperspec.cli.stress:main"
|
||||
paperspec-validate = "paperspec.cli.validate:main"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://paperspec.scient.ing"
|
||||
Repository = "https://git.scient.ing/infra/paperspec"
|
||||
@@ -38,7 +48,7 @@ Documentation = "https://paperspec.scient.ing/docs/"
|
||||
# package as data subpackages, so a consumer reads them with importlib.resources. That mapping is why the
|
||||
# packages are listed rather than found.
|
||||
[tool.setuptools]
|
||||
packages = ["paperspec", "paperspec.schemas", "paperspec.sql", "paperspec.vectors"]
|
||||
packages = ["paperspec", "paperspec.cli", "paperspec.schemas", "paperspec.sql", "paperspec.vectors"]
|
||||
|
||||
[tool.setuptools.package-dir]
|
||||
"" = "src"
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ A rule's meaning never changes, and a retired rule's ID is never reused, so tool
|
||||
A tool can match on the `rule` field, whose meaning is looked up in `rules.json`, and treat the message as text for people.
|
||||
[Chapter 22, section 22.4](../spec/22-conformance.md#224-rules) lists the ID prefixes.
|
||||
Each directory in `fixtures/invalid/` has an `expect.json` that names the rule it breaks.
|
||||
The reference checker (`tools/check.py`) fails if that rule is not in `rules.json`.
|
||||
The reference checker (`paperspec-check`) fails if that rule is not in `rules.json`.
|
||||
|
||||
## Adding stamp rules
|
||||
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ The `Registry` maps that name to the file's contents, so nothing is fetched over
|
||||
Never load a schema alone.
|
||||
|
||||
Any draft 2020-12 validator works the same way: give it every schema under its `$id`, then validate against the one you need.
|
||||
`python3 tools/check.py` validates every fixture with `jsonschema` this way, and checks that every schema keeps to the keyword subset.
|
||||
`paperspec-check` validates every fixture with `jsonschema` this way, and checks that every schema keeps to the keyword subset.
|
||||
|
||||
## What a schema does not check
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ A correction is refused, naming the rule ([22](22-conformance.md)), when:
|
||||
11. the result breaks a rule of [3](03-nodes.md).
|
||||
|
||||
The gate needs only the document's current state, so a tool can run it on a proposal before applying anything, and get back the record the correction would append or the rule it breaks.
|
||||
`tools/validate.py --correction` does this against a package.
|
||||
`paperspec-validate --correction` does this against a package.
|
||||
|
||||
Digits are characters of General Category Nd in the plain projection.
|
||||
A package's corrections are also checked as a whole: indexes run from 1 without gaps, each `id` derives from its record, `rev` equals their number, every retired ID is in `ids.jsonl`, and the last correction of each leaf ends at its current `node_hash`.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
| --- | --- | --- |
|
||||
| `vectors/*.jsonl` | Inputs and expected outputs, one case per line. | Computes every expected output from its input. |
|
||||
| `fixtures/packages/` | Valid packages: a converted chemistry paper with corrections, receipts, a multi-level-header table and a dataset; its checks-only export; an open-access paper with two witnesses; a Typst draft; a tiny document, as a folder and as the zip of [12.2](12-package.md#122-paths). | Accepts each, and recomputes every value it contains. |
|
||||
| `fixtures/records/` | A valid record of every other format that is a JSON file of its own; the stage requirements and stamp rules are in `registry/`, and a bundle is built by `tools/load.py --bundle`. | Accepts each. |
|
||||
| `fixtures/records/` | A valid record of every other format that is a JSON file of its own; the stage requirements and stamp rules are in `registry/`, and a bundle is built by `paperspec-load --bundle`. | Accepts each. |
|
||||
| `fixtures/invalid/` | Invalid packages, each with `expect.json` naming the rule it breaks. | Refuses each, and SHOULD name that rule. |
|
||||
| `fixtures/keys/` | The test keys that signed the fixtures and the team secret of the checks-only export. | Never uses them for anything else. |
|
||||
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ dolt sql < sql/doc.sql && dolt sql < sql/store.sql
|
||||
The PostgreSQL database `lab` and the Dolt repository must already exist.
|
||||
The `sqlite3` line runs as shown; the `psql` and `dolt` lines follow the portability rules of [chapter 13](../spec/13-database.md#131-portability) but have not been run on those engines in this repository.
|
||||
|
||||
To load packages into SQLite without pixi, run `python3 tools/load.py --sqlite lab.db PACKAGE…` with Python 3.14 and two packages, `jsonschema` and `cryptography` installed.
|
||||
To load packages into SQLite without pixi, install the package with its `tools` extra on Python 3.14, then run `paperspec-load --sqlite lab.db PACKAGE…`.
|
||||
The file [docs/examples.md](../docs/examples.md#load-packages-into-sqlite) has queries to try.
|
||||
The loader copies files that have no table (assets, dataset files, derived files) by SHA-256 into `lab.db.assets/`.
|
||||
|
||||
@@ -57,7 +57,7 @@ On `fixtures/packages/hale2026harmonic-damb` it returns one row, `hca6dc5`: the
|
||||
- Names: no table, column or index name is reserved in SQLite, PostgreSQL or MySQL (Dolt is MySQL-compatible).
|
||||
The formats' `key`, `index`, `at`, `by`, `before` and `after` become `doc_key`, `idx`, `made_at`, `signer`, `before_hash` and `after_hash`.
|
||||
- Round trip: each row keeps every property of its record, so unloading restores the same bytes and the same manifest hash.
|
||||
Unloading copies the files with no table from the asset store, which must travel with the database. `tools/check.py` tests this for every fixture package.
|
||||
Unloading copies the files with no table from the asset store, which must travel with the database. `paperspec-check` tests this for every fixture package.
|
||||
- Derived tables: `citations`, `receipt_nodes` and `annotation_targets` are built on load for queries and are not part of the package.
|
||||
|
||||
## Status
|
||||
|
||||
@@ -2,13 +2,26 @@
|
||||
in this repository; see tools/README.md.
|
||||
|
||||
The modules other programs use (`keys`, `text`, `jcs`, `package`, `db`, `rights`, ...) need only the
|
||||
standard library and Python 3.14, the first with the Unicode 16.0 character data the specification pins.
|
||||
standard library.
|
||||
`schema` needs `paperspec[schema]` and the HKDF and Ed25519 functions of `crypto` need `paperspec[crypto]`;
|
||||
both import their packages on first use.
|
||||
The schemas, SQL and vectors travel as package data: `importlib.resources.files("paperspec") / "sql"`.
|
||||
The schemas, SQL and vectors travel with the package: `paperspec.resources.path("sql")`.
|
||||
The commands are in `paperspec.cli`.
|
||||
|
||||
The specification pins the Unicode character data that normalization and the character classes come from
|
||||
(spec/01 §1.4), so the package refuses to import on a Python with any other version: a different version
|
||||
would compute different values for text with the characters it adds or changes.
|
||||
"""
|
||||
|
||||
import unicodedata
|
||||
|
||||
UNICODE_VERSION = "16.0.0"
|
||||
|
||||
if unicodedata.unidata_version != UNICODE_VERSION:
|
||||
raise ImportError("paperspec needs the Unicode %s character data the specification pins, which Python 3.14 "
|
||||
"ships; this Python has Unicode %s." % (UNICODE_VERSION, unicodedata.unidata_version))
|
||||
|
||||
try:
|
||||
from ._version import __version__
|
||||
except ImportError: # a checkout that was never built or installed, as the repository's tools use it
|
||||
except ImportError: # a checkout that was never built or installed, as `python -m` from src/ runs it
|
||||
__version__ = "0+unknown"
|
||||
@@ -0,0 +1,42 @@
|
||||
"""The paperspec commands, installed as console scripts; see tools/README.md.
|
||||
|
||||
`paperspec-validate` and `paperspec-load` work anywhere the package is installed with `paperspec[tools]`.
|
||||
`paperspec-check`, `paperspec-build`, `paperspec-stress` and `paperspec-site` work on a checkout of the
|
||||
paperspec repository: the one an editable install points at, or else the one around the current folder.
|
||||
Each is also a module, so `python -m paperspec.cli.check` runs the same program.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def require(*modules):
|
||||
"""Exit with a message naming the extra to install when a package a command needs is missing."""
|
||||
missing = [m for m in modules if importlib.util.find_spec(m) is None]
|
||||
if missing:
|
||||
sys.exit("This command needs %s %s; install paperspec[tools], or run it through pixi."
|
||||
% ("the package" if len(missing) == 1 else "the packages", " and ".join(missing)))
|
||||
|
||||
|
||||
def _is_checkout(d):
|
||||
return all(os.path.exists(os.path.join(d, m)) for m in ("pyproject.toml", "spec", "registry", "fixtures/src"))
|
||||
|
||||
|
||||
def checkout():
|
||||
"""The root of the paperspec checkout a repository command works on, or exit with a message.
|
||||
|
||||
An editable install (and `python -m` from `src/`) runs the checkout this file is in, so the code and the
|
||||
files it checks always match; an installed wheel uses the nearest checkout above the current folder.
|
||||
"""
|
||||
own = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
if _is_checkout(own):
|
||||
return own
|
||||
d = os.getcwd()
|
||||
while True:
|
||||
if _is_checkout(d):
|
||||
return d
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
sys.exit("This command works on a checkout of the paperspec repository; run it inside one.")
|
||||
d = parent
|
||||
@@ -2,8 +2,8 @@
|
||||
"""Build every fixture and test vector from fixtures/src and the cases below.
|
||||
|
||||
Non-normative tooling.
|
||||
The build is deterministic: run it twice and the output is byte-identical; `tools/check.py` verifies that.
|
||||
Usage: python3 tools/build.py [--out DIR] (default: the repository itself)
|
||||
The build is deterministic: run it twice and the output is byte-identical; `paperspec-check` verifies that.
|
||||
Usage: paperspec-build [--out DIR] (default: the repository itself)
|
||||
"""
|
||||
|
||||
import base64
|
||||
@@ -18,9 +18,10 @@ import zipfile
|
||||
import zlib
|
||||
from fractions import Fraction
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, ROOT)
|
||||
import tools # noqa: E402,F401 checks the runtime and puts src/ first on the path, before paperspec is imported
|
||||
from paperspec.cli import checkout, require
|
||||
|
||||
require("cryptography", "jsonschema")
|
||||
ROOT = checkout()
|
||||
|
||||
from paperspec import align as AL # noqa: E402
|
||||
from paperspec import anchors as AN # noqa: E402
|
||||
@@ -545,6 +546,8 @@ def package_files(src, key, names, side, final, records, receipts=None, roster_r
|
||||
if roster_rec:
|
||||
files["roster.json"] = (jcs.dumps(roster_rec) + "\n").encode()
|
||||
files["witnesses.json"] = (jcs.dumps({"witnesses": witnesses}) + "\n").encode()
|
||||
# The tool name is the label the fixtures were built and signed under, not this file's path; changing it
|
||||
# would change every fixture's provenance, manifest hash and receipts.
|
||||
files["provenance.json"] = (jcs.dumps({
|
||||
"tools": [{"name": "paperspec tools/build.py", "version": VERSION}],
|
||||
"stages": [{"stage": "hand", "processor": "local", "tool": "paperspec tools/build.py"}],
|
||||
@@ -1854,7 +1857,7 @@ def cli():
|
||||
import argparse
|
||||
ap = argparse.ArgumentParser(
|
||||
description="Build every fixture and test vector from fixtures/src and the oracle. The build is "
|
||||
"deterministic, and tools/check.py rebuilds into a temporary folder to prove it.")
|
||||
"deterministic, and paperspec-check rebuilds into a temporary folder to prove it.")
|
||||
ap.add_argument("--out", metavar="DIR", default=ROOT,
|
||||
help="build into DIR instead of rewriting fixtures/ and vectors/ in this repository")
|
||||
out = os.path.abspath(ap.parse_args().out)
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check the whole repository. Exit status 0 means every check passed.
|
||||
|
||||
Non-normative tooling; it needs Python 3.14 and the packages pixi installs (`jsonschema`, `cryptography`).
|
||||
Usage: python3 tools/check.py [--quick] (--quick skips the determinism rebuild)
|
||||
Non-normative tooling; it needs the packages `jsonschema` and `cryptography`, which pixi and paperspec[tools] install.
|
||||
Usage: paperspec-check [--quick] (--quick skips the determinism rebuild)
|
||||
"""
|
||||
|
||||
import base64
|
||||
@@ -18,9 +18,10 @@ import tempfile
|
||||
import zipfile
|
||||
from fractions import Fraction
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, ROOT)
|
||||
import tools # noqa: E402,F401 checks the runtime and puts src/ first on the path, before paperspec is imported
|
||||
from paperspec.cli import checkout, require
|
||||
|
||||
require("cryptography", "jsonschema")
|
||||
ROOT = checkout()
|
||||
|
||||
from paperspec import align as AL # noqa: E402
|
||||
from paperspec import anchors as AN # noqa: E402
|
||||
@@ -69,7 +70,7 @@ STORE = Store(p("schemas"))
|
||||
def fixture_key(stem):
|
||||
"""The key the rule of spec/02 §2.4 mints for a fixture source, which names its package folder."""
|
||||
if stem == "tiny2026example":
|
||||
from tools.build import TINY
|
||||
from paperspec.cli.build import TINY
|
||||
d = TINY["document"]
|
||||
else:
|
||||
d = js(p("fixtures", "src", stem + ".json"))["document"]
|
||||
@@ -432,7 +433,7 @@ def reference_values(doc, hasher=hashing.PLAIN):
|
||||
|
||||
def check_incremental():
|
||||
"""After every correction of every fixture source, the incrementally kept values equal the slow ones."""
|
||||
from tools.build import Names, build_document
|
||||
from paperspec.cli.build import Names, build_document
|
||||
team = hashing.Hasher(crypto.team_key(bytes.fromhex(
|
||||
open(p("fixtures", "keys", "smithlab-team-secret.hex")).read().strip())))
|
||||
Names.registry.clear()
|
||||
@@ -510,7 +511,7 @@ ROSTER_BY_ID_CHECK = {k["id"]: k for k in js(p("fixtures", "records", "roster.js
|
||||
|
||||
|
||||
def check_vectors():
|
||||
from tools.build import parse_doubles, projection_case, sentence_doc
|
||||
from paperspec.cli.build import parse_doubles, projection_case, sentence_doc
|
||||
for c in V("canonical-json.jsonl"):
|
||||
ok("vectors", jcs.dumps(parse_doubles(c["input_json"])) == c["output"], "canonical-json " + c["id"])
|
||||
for c in V("normalize.jsonl"):
|
||||
@@ -694,7 +695,7 @@ def check_vectors():
|
||||
ok("vectors", any(c["id"] == "rfc9381-example-16" and c["proof"].startswith("8657106690b5526245a92b003bb079cc")
|
||||
and c["beta"].startswith("90cf1df3b703cce59e2a35b925d41116") for c in V("vrf.jsonl")),
|
||||
"the RFC 9381 example is not reproduced")
|
||||
from tools.build import secret
|
||||
from paperspec.cli.build import secret
|
||||
for c in V("checkpoint.jsonl"):
|
||||
ok("vectors", LG.root(c["entries"]) == c["root"], "checkpoint %s: root" % c["id"])
|
||||
signers = [(n, secret(n)) for n in c["signers"]]
|
||||
@@ -709,7 +710,7 @@ def check_vectors():
|
||||
|
||||
def check_determinism():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
subprocess.run([sys.executable, p("tools", "build.py"), "--out", tmp], check=True)
|
||||
subprocess.run([sys.executable, "-m", "paperspec.cli.build", "--out", tmp], check=True)
|
||||
for sub in ("fixtures", "vectors"):
|
||||
# Files the repository ignores, such as macOS's .DS_Store, are not part of the build.
|
||||
cmp = filecmp.dircmp(p(sub), os.path.join(tmp, sub), ignore=filecmp.DEFAULT_IGNORES + [".DS_Store"])
|
||||
@@ -982,7 +983,7 @@ def check_counts():
|
||||
}
|
||||
top = re.search(r"^## (\S+)", open(p("CHANGELOG.md"), encoding="utf-8").read(), re.M).group(1)
|
||||
pixi = re.search(r'^version = "([^"]+)"', open(p("pixi.toml"), encoding="utf-8").read(), re.M).group(1)
|
||||
from tools.build import VERSION
|
||||
from paperspec.cli.build import VERSION
|
||||
ok("counts", top == pixi == formats["version"] == rules["version"] == VERSION,
|
||||
"versions differ: CHANGELOG %s, pixi.toml %s, formats %s, rules %s, build.py %s"
|
||||
% (top, pixi, formats["version"], rules["version"], VERSION))
|
||||
@@ -1038,7 +1039,7 @@ def check_log():
|
||||
ok("log", LG.accept_checkpoint(note, roster, settings) == (True, []), "the project does not accept its checkpoint")
|
||||
ok("log", not LG.accept_checkpoint(note, roster, dict(settings, threshold=2))[0], "two witnesses were not required")
|
||||
ok("log", not LG.accept_checkpoint(note, roster, dict(settings, origin="other"))[0], "another origin was accepted")
|
||||
alone = LG.sign_checkpoint(settings["origin"], entries, [("wit", __import__("tools.build", fromlist=["secret"]).secret("wit"))])
|
||||
alone = LG.sign_checkpoint(settings["origin"], entries, [("wit", __import__("paperspec.cli.build", fromlist=["secret"]).secret("wit"))])
|
||||
ok("log", not LG.accept_checkpoint(alone, roster, settings)[0], "a checkpoint without the team's key was accepted")
|
||||
# The ML-DSA-65 receipt verifies under dana's key, and fails under a tampered payload.
|
||||
rec = js(os.path.join(R, "receipt-ml-dsa.json"))
|
||||
@@ -1087,7 +1088,7 @@ def check_site():
|
||||
"""The website builds, every Markdown file has a page, every internal link and fragment resolves, no Markdown
|
||||
is left unrendered, and the namespace addresses the formats name are files the site serves."""
|
||||
from html.parser import HTMLParser
|
||||
from tools import site
|
||||
from paperspec.cli import site
|
||||
|
||||
class Page(HTMLParser):
|
||||
def __init__(self):
|
||||
@@ -2,8 +2,8 @@
|
||||
"""Load packages into a SQLite database with the tables of sql/doc.sql and sql/store.sql.
|
||||
|
||||
Non-normative tooling.
|
||||
Usage: python3 tools/load.py --sqlite lab.db PACKAGE [PACKAGE ...]
|
||||
python3 tools/load.py --sqlite lab.db --bundle --built-from REV PACKAGE [PACKAGE ...]
|
||||
Usage: paperspec-load --sqlite lab.db PACKAGE [PACKAGE ...]
|
||||
paperspec-load --sqlite lab.db --bundle --built-from REV PACKAGE [PACKAGE ...]
|
||||
A package is a folder, or a zipped package (`.sct.zip`), unpacked under the rules of spec/12 §12.2 first.
|
||||
With --bundle the new file is a scienting-bundle/1: it also gets the tables of sql/bundle-sqlite.sql,
|
||||
the full-text search index and bundle_meta (spec/13 §13.5).
|
||||
@@ -18,11 +18,11 @@ import sqlite3
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, ROOT)
|
||||
import tools # noqa: E402,F401 checks the runtime and puts src/ first on the path, before paperspec is imported
|
||||
from paperspec.cli import require
|
||||
|
||||
from paperspec import db, package # noqa: E402
|
||||
require("cryptography", "jsonschema")
|
||||
|
||||
from paperspec import db, package, resources # noqa: E402
|
||||
from paperspec.schema import Store # noqa: E402
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ def main():
|
||||
a = ap.parse_args()
|
||||
if a.bundle and (os.path.exists(a.sqlite) or not a.built_from):
|
||||
ap.error("--bundle needs --built-from and a database file that does not exist yet")
|
||||
store, bad = Store(os.path.join(ROOT, "schemas")), False
|
||||
store, bad = Store(resources.path("schemas")), False
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
folders = []
|
||||
for i, p in enumerate(a.packages):
|
||||
@@ -59,9 +59,9 @@ def main():
|
||||
new = not os.path.exists(a.sqlite)
|
||||
conn = sqlite3.connect(a.sqlite)
|
||||
if a.bundle:
|
||||
db.create_bundle(conn, os.path.join(ROOT, "sql"))
|
||||
db.create_bundle(conn, resources.path("sql"))
|
||||
elif new:
|
||||
db.create(conn, os.path.join(ROOT, "sql"))
|
||||
db.create(conn, resources.path("sql"))
|
||||
for p in folders:
|
||||
key = db.load(conn, p, a.sqlite + ".assets")
|
||||
n = conn.execute("SELECT COUNT(*) FROM nodes WHERE doc_key = ?", (key,)).fetchone()[0]
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Build the paperspec website: every file of the repository, and its Markdown rendered as documentation.
|
||||
|
||||
Non-normative tooling, standard library only.
|
||||
Usage: python3 tools/site.py [--out DIR] [--base-url URL]
|
||||
Usage: paperspec-site [--out DIR] [--base-url URL]
|
||||
The output folder (default `_site/`) mirrors the repository, so each file is served at its own path: a schema at
|
||||
the URL in its `$id`, the JSON-LD context at the address layers name, and every vector, fixture and table as it is.
|
||||
Beside each Markdown file the builder writes a page, `README.md` becoming its folder's `index.html` and `X.md`
|
||||
@@ -19,7 +19,9 @@ import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
from paperspec.cli import checkout
|
||||
|
||||
ROOT = checkout()
|
||||
SKIP_DIRS = {".git", ".pixi", ".claude", "_site", "__pycache__", "node_modules", ".gitea", ".vscode", ".idea"}
|
||||
SKIP_FILES = {".DS_Store"}
|
||||
NAME = "paperspec"
|
||||
@@ -36,7 +38,7 @@ def version():
|
||||
# --- inline Markdown ------------------------------------------------------------------------------------------
|
||||
|
||||
def slug(heading):
|
||||
"""A heading's anchor as GitHub, Gitea and Forgejo compute it (the same rule tools/check.py uses)."""
|
||||
"""A heading's anchor as GitHub, Gitea and Forgejo compute it (the same rule paperspec-check uses)."""
|
||||
t = re.sub(r"[`*]", "", heading.strip().lower())
|
||||
t = "".join(ch for ch in t if ch.isalnum() or ch in " -_")
|
||||
return t.replace(" ", "-")
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Stress the reference implementation with random inputs. Exit status 0 means every property held.
|
||||
|
||||
Non-normative tooling; it needs Python 3.14 and the packages pixi installs (`jsonschema`, `cryptography`).
|
||||
Usage: python3 tools/stress.py [--seed N] [--rounds N]
|
||||
Non-normative tooling; it needs the packages `jsonschema` and `cryptography`, which pixi and paperspec[tools] install.
|
||||
Usage: paperspec-stress [--seed N] [--rounds N]
|
||||
The vectors pin values; this program checks the invariants the chapters state, over inputs drawn from a seeded
|
||||
random generator, so a failure is reproducible from the seed it prints. Canonical JSON round-trips and sorts keys
|
||||
by UTF-16 units; normalization is idempotent; notation folding never raises; random corrections keep the five
|
||||
@@ -27,9 +27,10 @@ import zipfile
|
||||
from fractions import Fraction
|
||||
from math import comb
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, ROOT)
|
||||
import tools # noqa: E402,F401 checks the runtime and puts src/ first on the path, before paperspec is imported
|
||||
from paperspec.cli import checkout, require
|
||||
|
||||
require("cryptography", "jsonschema")
|
||||
ROOT = checkout()
|
||||
|
||||
from paperspec import align as AL # noqa: E402
|
||||
from paperspec import anchors as AN # noqa: E402
|
||||
@@ -41,8 +42,8 @@ from paperspec.model import LABELLED, LEAVES, Doc # noqa: E402
|
||||
from paperspec.notation import fold_math # noqa: E402
|
||||
from paperspec.text import NODE_ID_RE, RECORD_ID_RE, letter_first, norm, record_id, sha256_hex, is_ws # noqa: E402
|
||||
from paperspec.tree import Refusal, validate_structure # noqa: E402
|
||||
from tools.build import ROSTER_BY_ID, SCHEMAS, secret, sentence_doc, tiny_package # noqa: E402
|
||||
from tools.check import HALE, STORE, TINY_KEY, reference_values # noqa: E402
|
||||
from paperspec.cli.build import ROSTER_BY_ID, SCHEMAS, secret, sentence_doc, tiny_package # noqa: E402
|
||||
from paperspec.cli.check import HALE, STORE, TINY_KEY, reference_values # noqa: E402
|
||||
|
||||
FAILS = []
|
||||
COUNTS = {}
|
||||
@@ -2,8 +2,8 @@
|
||||
"""Validate packages, or run the correction gate on proposals against a package.
|
||||
|
||||
Non-normative tooling.
|
||||
Usage: python3 tools/validate.py PACKAGE [PACKAGE ...]
|
||||
python3 tools/validate.py --correction PROPOSALS PACKAGE
|
||||
Usage: paperspec-validate PACKAGE [PACKAGE ...]
|
||||
paperspec-validate --correction PROPOSALS PACKAGE
|
||||
A package is a folder, or a zipped package (`.sct.zip`), which is unpacked under the rules of spec/12 §12.2
|
||||
before it is checked. Each is checked as spec/22 §22.2 describes, and each failure printed as
|
||||
`package: where: rule: message`.
|
||||
@@ -19,11 +19,11 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, ROOT)
|
||||
import tools # noqa: E402,F401 checks the runtime and puts src/ first on the path, before paperspec is imported
|
||||
from paperspec.cli import require
|
||||
|
||||
from paperspec import corrections, jcs, package # noqa: E402
|
||||
require("cryptography", "jsonschema")
|
||||
|
||||
from paperspec import corrections, jcs, package, resources # noqa: E402
|
||||
from paperspec.schema import Store # noqa: E402
|
||||
from paperspec.tree import Refusal # noqa: E402
|
||||
|
||||
@@ -56,7 +56,7 @@ def main():
|
||||
a = ap.parse_args()
|
||||
if a.correction and len(a.packages) != 1:
|
||||
ap.error("--correction checks proposals against exactly one package")
|
||||
store, bad = Store(os.path.join(ROOT, "schemas")), False
|
||||
store, bad = Store(resources.path("schemas")), False
|
||||
for p in a.packages:
|
||||
for e in validate_one(p, store, a.max_size).errors:
|
||||
print("%s: %s: %s: %s" % (p, e["where"], e["rule"], e["message"]), file=sys.stderr)
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Where the schemas, SQL and vectors are, as filesystem paths.
|
||||
|
||||
An installed wheel carries them inside the package (`paperspec/schemas`, `paperspec/sql`, `paperspec/vectors`).
|
||||
An editable install or a checkout keeps them at the repository root, where the specification links to them,
|
||||
so `importlib.resources.files("paperspec") / "sql"` finds nothing there; `path("sql")` finds both.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
FOLDERS = ("schemas", "sql", "vectors")
|
||||
_PACKAGE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def path(name):
|
||||
"""The folder `schemas`, `sql` or `vectors`."""
|
||||
if name not in FOLDERS:
|
||||
raise ValueError("no data folder %r; there are %s" % (name, ", ".join(FOLDERS)))
|
||||
for d in (os.path.join(_PACKAGE, name), os.path.join(os.path.dirname(os.path.dirname(_PACKAGE)), name)):
|
||||
if os.path.isdir(d):
|
||||
return d
|
||||
raise FileNotFoundError("the %s folder is neither in the package nor at the root of its checkout" % name)
|
||||
+32
-25
@@ -4,51 +4,57 @@ This page is for anyone who wants to run the repository's checks, rebuild its ex
|
||||
Newcomers get each term defined where it first appears.
|
||||
Implementers get the commands and the traps.
|
||||
|
||||
These programs are the repository's reference implementation.
|
||||
These commands are the repository's reference implementation.
|
||||
They apply every rule of the specification, so the rest of the repository can be checked against them.
|
||||
Two build and check the repository, one validates packages and proposed corrections, and one loads packages into SQLite, a database stored in a single file.
|
||||
They are console scripts of the Python package `paperspec`, whose code is in [src/paperspec/](../src/paperspec/), with the commands in `src/paperspec/cli/`.
|
||||
They need Python 3.14 and two packages, `jsonschema` and `cryptography`, and are licensed Apache-2.0.
|
||||
|
||||
A package is the folder of files that holds one document, such as `fixtures/packages/hale2026harmonic`.
|
||||
A package is the folder of files that holds one document, such as `fixtures/packages/hale2026harmonic-damb`.
|
||||
A vector is a test case: an input and the exact output every implementation must compute (see [the vectors](../vectors/README.md)).
|
||||
A fixture is an example file or package under `fixtures/`.
|
||||
|
||||
| Program | Does |
|
||||
| Command | Does |
|
||||
| --- | --- |
|
||||
| `check.py` | Checks everything: schemas, records, packages and the zipped package, the broken packages, every vector, a byte-for-byte rebuild, the SQL (a database query language) round trip, the counts the prose states, and the prose. `--quick` skips the rebuild. |
|
||||
| `build.py` | Builds `fixtures/` and `vectors/` from the hand-written sources in `fixtures/src/` and the cases it defines. `--out DIR` builds into another folder. |
|
||||
| `validate.py` | Validates packages, as folders or as zipped packages (`.sct.zip`, unpacked under the rules of [spec/12 §12.2](../spec/12-package.md#122-paths)): `python3 tools/validate.py PACKAGE [PACKAGE ...]`. With `--correction PROPOSALS` it runs proposed corrections through the gate against one package's current state, and prints the record each would append or the rule it breaks, without writing anything ([spec/11 §11.4](../spec/11-corrections.md#114-the-gate)). |
|
||||
| `load.py` | Validates packages, folders or zipped, then loads them into a SQLite database with the tables of `sql/`: `python3 tools/load.py --sqlite lab.db PACKAGE [PACKAGE ...]`. With `--bundle --built-from REV` it builds a new bundle instead, with the full-text search index ([spec/13 §13.5](../spec/13-database.md#135-the-bundle)). |
|
||||
| `site.py` | Builds the website into `_site/`: every file of the repository at its own path, and each Markdown file rendered as a page beside it, with a vocabulary page, folder listings and a search index. It uses only the standard library. `pixi run check` builds it into a temporary folder and checks every link ([deploy/](../deploy/README.md)). |
|
||||
| `stress.py` | Stresses the oracle with random inputs from a seeded generator: `python3 tools/stress.py [--seed N] [--rounds N]`. It checks the invariants the chapters state rather than the vectors' values, such as canonical JSON round trips, corrections keeping the five values consistent with a full recomputation, anchors always resolving to one of the five outcomes, the zip reader never writing outside its folder, and a mutated package always being refused. |
|
||||
| [src/paperspec/](../src/paperspec/) | The oracle: a compact implementation of every rule, and the Python package `paperspec`. `build.py` uses it to compute expected values, and `check.py` uses it to recompute them. The programs put `src/` first on the path, so they run this checkout's code with or without pixi. |
|
||||
| `paperspec-check` | Checks everything: schemas, records, packages and the zipped package, the broken packages, every vector, a byte-for-byte rebuild, the SQL (a database query language) round trip, the counts the prose states, and the prose. `--quick` skips the rebuild. |
|
||||
| `paperspec-build` | Builds `fixtures/` and `vectors/` from the hand-written sources in `fixtures/src/` and the cases it defines. `--out DIR` builds into another folder. |
|
||||
| `paperspec-validate` | Validates packages, as folders or as zipped packages (`.sct.zip`, unpacked under the rules of [spec/12 §12.2](../spec/12-package.md#122-paths)): `paperspec-validate PACKAGE [PACKAGE ...]`. With `--correction PROPOSALS` it runs proposed corrections through the gate against one package's current state, and prints the record each would append or the rule it breaks, without writing anything ([spec/11 §11.4](../spec/11-corrections.md#114-the-gate)). |
|
||||
| `paperspec-load` | Validates packages, folders or zipped, then loads them into a SQLite database with the tables of `sql/`: `paperspec-load --sqlite lab.db PACKAGE [PACKAGE ...]`. With `--bundle --built-from REV` it builds a new bundle instead, with the full-text search index ([spec/13 §13.5](../spec/13-database.md#135-the-bundle)). |
|
||||
| `paperspec-site` | Builds the website into `_site/`: every file of the repository at its own path, and each Markdown file rendered as a page beside it, with a vocabulary page, folder listings and a search index. It uses only the standard library. `pixi run check` builds it into a temporary folder and checks every link ([deploy/](../deploy/README.md)). |
|
||||
| `paperspec-stress` | Stresses the oracle with random inputs from a seeded generator: `paperspec-stress [--seed N] [--rounds N]`. It checks the invariants the chapters state rather than the vectors' values, such as canonical JSON round trips, corrections keeping the five values consistent with a full recomputation, anchors always resolving to one of the five outcomes, the zip reader never writing outside its folder, and a mutated package always being refused. |
|
||||
|
||||
`paperspec-validate` and `paperspec-load` work anywhere the package is installed, with the schemas and SQL it carries.
|
||||
The other four work on a checkout of this repository: the one an editable install points at, or else the one around the current folder.
|
||||
|
||||
## When to run what
|
||||
|
||||
Each program is also a pixi task.
|
||||
Pixi sets up a pinned Python and the other packages the repository uses.
|
||||
Each command is also a pixi task.
|
||||
Pixi sets up a pinned Python and the other packages the repository uses, and installs this checkout as the package, editable, so a change to the code takes effect at once.
|
||||
The commands `pixi run check`, `pixi run quick`, `pixi run build` and `pixi run load` then work from anywhere in the repository.
|
||||
`pixi run check` lints the Markdown first.
|
||||
|
||||
Run the programs directly from the repository root if you do not use pixi:
|
||||
If you do not use pixi, install the checkout editable with its extras on Python 3.14, then run the commands from the repository root:
|
||||
|
||||
```sh
|
||||
python3 tools/check.py # everything
|
||||
python3 tools/check.py --quick # everything except the rebuild
|
||||
python3 tools/build.py --out /tmp/rebuilt
|
||||
python3 tools/validate.py fixtures/packages/hale2026harmonic
|
||||
python3 tools/load.py --sqlite lab.db fixtures/packages/hale2026harmonic
|
||||
pip install -e ".[tools]"
|
||||
paperspec-check # everything
|
||||
paperspec-check --quick # everything except the rebuild
|
||||
paperspec-build --out /tmp/rebuilt
|
||||
paperspec-validate fixtures/packages/hale2026harmonic-damb
|
||||
paperspec-load --sqlite lab.db fixtures/packages/hale2026harmonic-damb
|
||||
```
|
||||
|
||||
Each command is also a module, so `python -m paperspec.cli.check` runs the same program.
|
||||
|
||||
A passing check ends with `All checks pass.`
|
||||
The load command prints `hale2026harmonic: 75 nodes`, one line per package, and writes `lab.db` with a folder `lab.db.assets/` beside it for files that have no table of their own.
|
||||
The load command prints `hale2026harmonic-damb: 75 nodes`, one line per package, and writes `lab.db` with a folder `lab.db.assets/` beside it for files that have no table of their own.
|
||||
It validates every package first and loads nothing if one fails, printing each failure as `package: where: rule: message`.
|
||||
The full and checks-only forms of one document share its key, so they go into separate databases.
|
||||
Each program prints its options with `--help` and refuses options it does not know.
|
||||
`build.py` rebuilds `fixtures/` and `vectors/` in place unless you pass `--out`.
|
||||
Each command prints its options with `--help` and refuses options it does not know.
|
||||
`paperspec-build` rebuilds `fixtures/` and `vectors/` in place unless you pass `--out`.
|
||||
|
||||
Run `pixi run check` before every commit.
|
||||
Run `pixi run build` after you change a fixture source, a rule in `src/paperspec/` or a case in `build.py`, and commit the regenerated files with the change.
|
||||
Run `pixi run build` after you change a fixture source, a rule in `src/paperspec/` or a case in `src/paperspec/cli/build.py`, and commit the regenerated files with the change.
|
||||
The check rebuilds into a temporary folder and compares every file byte for byte, so it fails on out-of-date files.
|
||||
|
||||
## About the oracle
|
||||
@@ -65,11 +71,12 @@ Canonical JSON goes through Python's built-in JSON encoder, and falls back to an
|
||||
|
||||
Hashes are kept incrementally.
|
||||
A node's five values depend only on its own subtree, so after a correction the oracle rehashes only the nodes that changed and their ancestors, and a container's projections are its children's cached projections joined.
|
||||
`check.py` compares the result after every correction with a slow computation that projects each node from its whole subtree.
|
||||
`paperspec-check` compares the result after every correction with a slow computation that projects each node from its whole subtree.
|
||||
|
||||
Other repositories of the project do not reimplement the specification: they install this repository as the Python package `paperspec` (`pip install .`, or a git pin) and import the oracle from it, so that every key, identifier, canonical JSON string, package check and database form comes from one piece of code.
|
||||
The library surface they use is `keys`, `text`, `jcs`, `package`, `db`, `schema` and `rights`, with the schemas, SQL and vectors as package data (`importlib.resources.files("paperspec") / "sql" / "doc.sql"`).
|
||||
Those modules need Python 3.14, the first with the Unicode 16.0 character data the specification pins, and otherwise only the standard library, except that `schema` needs `paperspec[schema]` (`jsonschema` and `referencing`) and the HKDF and Ed25519 functions of `crypto` need `paperspec[crypto]`; `paperspec[tools]` installs both.
|
||||
The library surface they use is `keys`, `text`, `jcs`, `package`, `db`, `schema` and `rights`, with the schemas, SQL and vectors as package data: `paperspec.resources.path("sql")` finds a folder in an installed wheel and in an editable install alike.
|
||||
The package needs Python 3.14, the only version with the Unicode 16.0 character data the specification pins, and refuses to import on any other.
|
||||
Otherwise it needs only the standard library, except that `schema` needs `paperspec[schema]` (`jsonschema` and `referencing`) and the HKDF and Ed25519 functions of `crypto` need `paperspec[crypto]`; `paperspec[tools]` installs both.
|
||||
The package version comes from git tags through setuptools-scm, and `pixi run -e dev wheel` builds the wheel into `dist/`.
|
||||
When `scienting-core` exists it takes this role behind the same vectors, and the import changes without any computed value changing.
|
||||
Independent implementations follow the [specification](../spec/README.md) and test themselves against the [vectors](../vectors/README.md).
|
||||
@@ -1,24 +0,0 @@
|
||||
"""The repository's own programs; see tools/README.md.
|
||||
|
||||
Importing this package checks that the tools can run here, and puts `src/` first on the path, so that
|
||||
`import paperspec` gets this checkout's reference implementation rather than an installed copy.
|
||||
The tools need Python 3.14 and the packages `cryptography` and `jsonschema`; the `paperspec` package
|
||||
itself needs Python 3.14 but neither package, and other programs install it on its own.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 14):
|
||||
sys.exit("The paperspec tools need Python 3.14 or later; this is Python %d.%d." % sys.version_info[:2])
|
||||
|
||||
_missing = [m for m in ("cryptography", "jsonschema") if importlib.util.find_spec(m) is None]
|
||||
if _missing:
|
||||
sys.exit("The paperspec tools need %s %s; run them through pixi, or install %s with pip."
|
||||
% ("the package" if len(_missing) == 1 else "the packages", " and ".join(_missing),
|
||||
"it" if len(_missing) == 1 else "them"))
|
||||
|
||||
SRC = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "src")
|
||||
if SRC not in sys.path:
|
||||
sys.path.insert(0, SRC)
|
||||
+1
-1
@@ -81,7 +81,7 @@ Several files also mix more than one kind of case, told apart by their fields.
|
||||
The reference checker in [tools/](../tools/README.md) runs all twenty-five files, together with the rest of the repository's checks:
|
||||
|
||||
```sh
|
||||
python3 tools/check.py --quick
|
||||
paperspec-check --quick
|
||||
```
|
||||
|
||||
It needs Python 3.14 and two packages, `jsonschema` and `cryptography`, and has no option to run one file.
|
||||
|
||||
Reference in new issue
Block a user