feat: validator, bundle builder and release checks for 0.1.0
- Add tools/validate.py: validates packages, and with --correction runs proposed corrections through the gate against a package without writing anything (spec/11 §11.4). The gate now refuses a proposal that fails the correction schema, rule correction.schema. - tools/load.py validates every package before loading, and with --bundle --built-from builds a scienting-bundle/1 with the FTS5 search index and bundle_meta (spec/13 §13.5). Search rows are defined per unit (sentence, cell, equation outside a sentence, figure-text) with the parent kind as context. - Roster keys carry a role, and a key with the role owner may revoke any receipt (spec/08 §8.7, spec/20); roster schema, store table and the fixture roster updated. The checker signs a trial revocation to prove the rule both ways. - A qa: receipt raises the nodes it sampled except those in its errors (spec/08 §8.2); the example paper's fidelity counts become 27 F3, 13 F2, 4 F1 and 1 F0. - The destination chooses the keyed scheme, not the text policy: hmac-team between the team's machines, hkdf-pages outside the team (spec/05 §5.8, spec/12 §12.5). - A TextQuoteSelector must repeat the anchor's plain quote and is absent for hashed or keyed anchors (spec/18 §18.3). - Add fixtures/records/suspect.json, a scienting-suspect/1 record. - Oracle: hashes are kept incrementally and checked after every correction against a full recomputation; load and write refuse unsafe manifest paths; Ed25519, HKDF and schema validation come from the cryptography and jsonschema packages; canonical JSON uses the built-in encoder with an exact RFC 8785 fallback. - The tools need Python 3.14 with cryptography and jsonschema; the py310 CI environment is dropped and the workflow runs the default only. - Resolve every [CHECK] note in the docs, READMEs and context; add CONTRIBUTING.md; every tool answers --help and refuses unknown options. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
89 files changed
+1141
-513
No files matched your search
@@ -1,6 +1,7 @@
|
||||
# Forgejo and Gitea Actions both read .github/workflows when their own folder is absent.
|
||||
# On Forgejo, if the runner's default action mirror lacks setup-pixi, write it as
|
||||
# https://github.com/prefix-dev/setup-pixi@v0.9.4.
|
||||
# The tools need Python 3.14, which pixi.toml pins, so one environment covers every supported Python.
|
||||
name: check
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
@@ -9,13 +10,11 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
environment: [default, py310]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: prefix-dev/setup-pixi@v0.9.4
|
||||
with:
|
||||
pixi-version: v0.69.0
|
||||
environments: ${{ matrix.environment }}
|
||||
- name: Check the repository
|
||||
run: pixi run --environment ${{ matrix.environment }} check
|
||||
run: pixi run check
|
||||
+2
-2
@@ -13,5 +13,5 @@ The first release.
|
||||
- Test vectors for every computed value, from canonical JSON to anchor resolution and grading.
|
||||
- Example packages (a converted chemistry paper with corrections and receipts, its checks-only export, an open-access paper, a Typst draft and a tiny document), a record of every other format, and seventeen broken packages.
|
||||
- Guides for newcomers: an overview, the concepts, a walkthrough of the example paper, runnable examples, the rationale, a FAQ and a glossary.
|
||||
- Tools that build and check everything, and one that loads packages into SQLite.
|
||||
- A pixi workspace with tasks for checking, building, linting and loading, and an environment for the oldest supported Python.
|
||||
- Tools that build and check everything, validate packages and proposed corrections, and load packages into SQLite or a bundle.
|
||||
- A pixi workspace pinned to Python 3.14, with tasks for checking, building, linting and loading.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Contributing
|
||||
|
||||
This page is for anyone who wants to fix the specification, change a format or add an example.
|
||||
Propose a change as a pull request on the repository at <https://git.scient.ing/infra/manuspec>.
|
||||
|
||||
## What a change needs
|
||||
|
||||
A change starts in the chapter of [the specification](spec/README.md) that defines the rule, because the chapters are normative.
|
||||
The schemas, SQL, registries, vectors and fixtures then follow the chapter ([spec/00 §0.5](spec/00-introduction.md#05-how-the-repository-is-arranged)).
|
||||
|
||||
1. **Change the chapter.**
|
||||
Say what is new in the chapter's own terms, and link the sections it touches.
|
||||
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 [tools/oracle/](tools/oracle/) 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/`.
|
||||
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.
|
||||
5. **Record the change** in [CHANGELOG.md](CHANGELOG.md).
|
||||
|
||||
## Versions
|
||||
|
||||
A change that makes a valid file invalid, or that changes a computed value, is a breaking change and needs a new major version of the format, with a migration.
|
||||
Adding an optional property, a node kind, a run kind or an enumeration value is a minor change ([spec/00 §0.3](spec/00-introduction.md#03-format-identifiers-and-versions)).
|
||||
A rebuild that changes any file under `vectors/` or any hash in `fixtures/` is a computed value changing, so say in the pull request why it must.
|
||||
|
||||
## New node kinds and run kinds
|
||||
|
||||
Propose a kind with the vector or fixture that shows it.
|
||||
A node kind needs its row in [chapter 3](spec/03-nodes.md), the node schema, and its entry in the `CHILDREN` table of `tools/oracle/model.py`.
|
||||
A run kind needs its row in [section 3.5](spec/03-nodes.md#35-inline-runs) and the node schema, and its text in the plain projection ([3.6](spec/03-nodes.md#36-the-plain-projection)) and in the strict and lossy projections ([5.3](spec/05-hashes.md#53-the-strict-projection), [5.5](spec/05-hashes.md#55-the-lossy-projection)).
|
||||
|
||||
## Writing
|
||||
|
||||
The checker enforces two rules on every Markdown file: one sentence per line, and US spelling.
|
||||
`pixi run lint` runs markdownlint with the repository's settings.
|
||||
Define each term where it first appears, because the guides and READMEs are written for newcomers as well as implementers.
|
||||
|
||||
All fixture text is synthetic, written for the fixtures.
|
||||
The keys in `fixtures/keys/` are public test keys and must never sign anything else.
|
||||
|
||||
## 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.
|
||||
@@ -79,8 +79,8 @@ Pixi lints the Markdown first.
|
||||
The run ends with `All checks pass.`
|
||||
`pixi task list` shows the other tasks: `quick`, `build`, `lint` and `load`.
|
||||
|
||||
The tools need nothing but the Python standard library, so without pixi the same check is `python3 tools/check.py` [CHECK: "Python 3.10 or later" is stated in README.md and docs/examples.md, but I could only run it on 3.13; pixi.toml pins 3.14].
|
||||
Pixi adds the `jsonschema` package as a second schema validator and markdownlint for the Markdown.
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
+7
-5
@@ -19,7 +19,8 @@ Two tools that read the same context agree on what `target` or `status` means, e
|
||||
`scienting-1.jsonld` is the context that makes every annotation layer valid JSON-LD, together with the W3C Web Annotation context.
|
||||
The Web Annotation context supplies `Annotation`, `body`, `target` and `selector`.
|
||||
This one supplies the terms the formats add.
|
||||
It is published at `https://scient.ing/ns/scienting/1.jsonld` [CHECK: the comparison source could not fetch this URL; confirm that it resolves].
|
||||
Its address is `https://scient.ing/ns/scienting/1.jsonld`.
|
||||
That address did not resolve when checked on 2026-10-14, so until it does, a JSON-LD processor needs the copy in this folder loaded in its place.
|
||||
Every layer and every annotation lists both contexts, Web Annotation first:
|
||||
|
||||
```json
|
||||
@@ -65,7 +66,7 @@ A processor reads `layer` as an IRI, and it always reads `docs` as a set, so a o
|
||||
The file declares `anchor` and `exercise` as JSON literals (`"@type": "@json"`, a JSON-LD 1.1 feature, which is why the file sets `"@version": 1.1`).
|
||||
A JSON-LD processor then keeps a `scienting-anchor/1` record intact instead of expanding it.
|
||||
An anchor holds hashes (fixed-length fingerprints of text) and a `span`, a pair of character offsets whose order carries meaning.
|
||||
Expansion would drop the anchor's unmapped field names and treat its arrays as unordered sets [CHECK: needs citation, JSON-LD 1.1 expansion rules].
|
||||
Expansion would drop the anchor's unmapped field names ([JSON-LD 1.1 API, expansion step 13.3](https://www.w3.org/TR/json-ld11-api/#alg-expand-each-key-value)) and would give its arrays no order ([JSON-LD 1.1 §4.3](https://www.w3.org/TR/json-ld11/#sets-and-lists)).
|
||||
|
||||
## The anchor selector and its fallback
|
||||
|
||||
@@ -83,7 +84,7 @@ A tool that does not know `AnchorSelector` cannot use it.
|
||||
An annotation's `selector` list puts the `AnchorSelector` first and, where the anchor has a quote, a standard `TextQuoteSelector` after it, for tools that do not know ours ([spec/18 §18.3](../spec/18-layers.md#183-the-annotation)).
|
||||
The formats use no `TextPositionSelector` and no `FragmentSelector`.
|
||||
Such a tool still reads the annotation's body and its `TextQuoteSelector`; the context matters only to tools that process the data as linked data.
|
||||
That is the repository's statement; no Web Annotation client has been tested against these files [CHECK: confirm that no client test exists; the comparison source marks this unverified].
|
||||
That is the repository's statement; no Web Annotation client has been tested against these files.
|
||||
|
||||
The fallback is not always there.
|
||||
Three of the nine fixture annotations have only an `AnchorSelector` (the section-level question, the `generated` annotation on a figure and the exercise on a table), because the anchor points at a container and has no quote.
|
||||
@@ -104,10 +105,11 @@ The traps:
|
||||
- `@context` is part of an annotation's content hash (the hash covers everything except `status`, `receipt` and `modified`).
|
||||
Rewriting or reordering the array changes the hash that a receipt covers.
|
||||
- `format` is one term for two jobs: the layer's `format` (`scienting-layer/1`) and a text body's `format` (`text/markdown`).
|
||||
The scienting context maps it to `sct:format` [CHECK: the Web Annotation context defines its own `format`; confirm that listing this context second overrides it].
|
||||
The scienting context maps it to `sct:format`.
|
||||
The Web Annotation context maps `format` to `dc:format` and protects none of its terms, and in an array of contexts the later definition wins ([JSON-LD 1.1 §4.1](https://www.w3.org/TR/json-ld11/#advanced-context-usage)), so listing this context second makes `format` mean `sct:format`.
|
||||
- A consumer that does not use a field must still preserve it when it rewrites a file.
|
||||
A tool that rewrites layers must not drop the scienting terms ([spec/00 §0.4](../spec/00-introduction.md)).
|
||||
- The spec does not say what the `TextQuoteSelector` holds when the anchor's quote is hashed (over 200 code points) or keyed ([spec/06 §6.3](../spec/06-anchors.md#63-keyed-anchors)) [CHECK: spec/18 is silent; a plain fallback quote would put text in a public layer that the keyed anchor withholds].
|
||||
- The `TextQuoteSelector` repeats the anchor's plain quotation, and is left out when the quote is hashed (over 200 code points) or keyed ([spec/18 §18.3](../spec/18-layers.md#183-the-annotation)), so a public layer never shows text that a keyed anchor withholds.
|
||||
|
||||
## Status and comparison
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ The guides say what each format is for, with examples drawn from the fixtures.
|
||||
| [Walkthrough](walkthrough.md) | One paper, from a flawed first reading to a corrected, reviewed and signed document. |
|
||||
| [Examples](examples.md) | Things to run: checking hashes by hand, reading a package with a few lines of Python, querying a database. |
|
||||
| [Rationale](rationale.md) | Why the formats look the way they do, and the questions they leave open. |
|
||||
| [Comparison](comparison.md) | How manuspec compares with JATS, TEI, W3C Web Annotation and other schemas. [CHECK: docs/comparison.md is not in the repository yet; confirm the page exists and covers these standards] |
|
||||
| [Comparison](comparison.md) | How manuspec compares with JATS, TEI, W3C Web Annotation and other schemas. |
|
||||
| [*FAQ*](faq.md) | Short answers to the questions people ask first. |
|
||||
| [Glossary](glossary.md) | Every term, in a sentence. |
|
||||
|
||||
|
||||
+4
-4
@@ -260,7 +260,7 @@ Every leaf has a level that says how far its text was checked.
|
||||
|
||||
A level counts only at the node's current hash, so a correction drops a proofread sentence back until someone signs for it again.
|
||||
The pipeline records F0, F1 or F2; F3 comes only from a valid receipt.
|
||||
The example reports 28 leaves at F3, 12 at F2, 4 at F1 and 1 at F0, which is its 45 leaves.
|
||||
The example reports 27 leaves at F3, 13 at F2, 4 at F1 and 1 at F0, which is its 45 leaves.
|
||||
See [chapter 8](../spec/08-fidelity.md).
|
||||
|
||||
## Receipts and spot-checks
|
||||
@@ -307,9 +307,9 @@ A keyed hash (an HMAC) needs a secret key to compute, so guessing does not work
|
||||
|
||||
Values are plain SHA-256 inside the team.
|
||||
Exported to another of the team's machines, they are HMACs under the team's key.
|
||||
In a public layer, the key is derived from the paper's page fingerprints (see the previous section), so only someone holding the paper can check them. [fixtures/packages/hale2026harmonic.checks-only](../fixtures/packages/hale2026harmonic.checks-only) is the example paper exported this way, under the team key.
|
||||
|
||||
[CHECK: spec/05 §5.8 lists the page-derived key for "any export on a work whose text is withheld", but spec/12 §12.5, fixtures/README.md and the checks-only fixture's document.json use the team key (hmac-team); confirm which the spec intends before stage 03]
|
||||
In a public layer, or in an export outside the team, the key is derived from the paper's page fingerprints (see the previous section), so only someone holding the paper can check them.
|
||||
Where the values go chooses the key, not whether the text is withheld.
|
||||
[fixtures/packages/hale2026harmonic.checks-only](../fixtures/packages/hale2026harmonic.checks-only) is the example paper exported to another of the team's machines, so it uses the team key.
|
||||
|
||||
A checks-only package also has no corrections, suspects or datasets.
|
||||
Pinned by [vectors/keyed.jsonl](../vectors/keyed.jsonl).
|
||||
|
||||
+3
-4
@@ -9,7 +9,7 @@ The formats are version 0.1.0, and each is `candidate` or `provisional` in `regi
|
||||
|
||||
Run every command from the root of the repository, <https://git.scient.ing/infra/manuspec>.
|
||||
With [pixi](https://pixi.sh), `pixi run <task>` sets up the environment first.
|
||||
Every command also works with plain Python 3.10 or later and standard command-line tools (the `sed` command in the last section is the GNU version of `sed`).
|
||||
Every command also works without pixi, with Python 3.14 and two packages, `jsonschema` and `cryptography` installed and standard command-line tools (the `sed` command in the last section is the GNU version of `sed`).
|
||||
|
||||
## Run the checks
|
||||
|
||||
@@ -154,7 +154,7 @@ okafor2025open: 15 nodes
|
||||
smithlab2027charges: 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 [CHECK: confirm that no run on Dolt or PostgreSQL is claimed anywhere].
|
||||
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.
|
||||
Here are four queries, in Python's `sqlite3` so that nothing else is needed.
|
||||
|
||||
```python
|
||||
@@ -210,8 +210,7 @@ The first query reads the level the converter recorded, not the final one.
|
||||
A receipt is a signed record that a reviewer checked certain nodes against the page.
|
||||
Receipts by `ana` raise `e4fkseg` and `r1gacps` to F3; `eajq93t` stays at F0 because the only receipt that covered it, by `cara`, was revoked.
|
||||
To compute the final level, check each receipt's signature, revocations and spot-checks, as [chapter 8](../spec/08-fidelity.md#82-levels) describes.
|
||||
The `scope` column of the third query shows the value `targeted:lode`, which is data in the fixture, copied here exactly as the query prints it [CHECK: house rule 2 bans naming programs that use the formats; this value is real fixture data in fixtures/src/hale2026harmonic.json and the package receipts, so either keep the output exact or change the fixture].
|
||||
|
||||
The `scope` column of the third query shows the value `targeted:lode`, which is data in the fixture, copied here exactly as the query prints it.
|
||||
|
||||
## Test your implementation against a vector
|
||||
|
||||
|
||||
+17
-17
@@ -5,12 +5,12 @@ Newcomers, implementers of a reader, writer, converter or checker, and experts w
|
||||
Each answer gives the plain statement first, then the precise one, then the chapter that defines it.
|
||||
The repository is <https://git.scient.ing/infra/manuspec>, and the formats are at version 0.1.0.
|
||||
|
||||
### Can I read these files without special software?
|
||||
## Can I read these files without special software?
|
||||
|
||||
Yes.
|
||||
Every file is JSON or JSON Lines (one JSON value per line), and [spec/](../spec/README.md) specifies every format. [The examples](examples.md) read a package with a few lines of Python.
|
||||
|
||||
### Why not keep using PDFs and page numbers?
|
||||
## Why not keep using PDFs and page numbers?
|
||||
|
||||
A page number names a region, not a statement, and it changes between a preprint and the version of record.
|
||||
An address names a sentence or a cell by its ID, such as `hale2026harmonic#j06tfhn`.
|
||||
@@ -19,14 +19,14 @@ It can add a pin (the first six hex digits of the node's hash) and character off
|
||||
An ID encodes no page, position or kind.
|
||||
The system mints it once from the document key, kind, text hash and occurrence, then freezes it ([spec/04 §4.2](../spec/04-identity.md#42-minting-node-ids)).
|
||||
|
||||
### Why are numbers stored as strings?
|
||||
## Why are numbers stored as strings?
|
||||
|
||||
Databases and JSON libraries rewrite numbers: `310.0` becomes `310` and `1.5260` becomes `1.526`, and the printed digits are part of what a paper says.
|
||||
Canonical JSON, the byte-exact form used for hashing, writes numbers as ECMAScript writes a double, so it would erase the same difference.
|
||||
A decimal that carries meaning is a string.
|
||||
JSON numbers are for counts, indexes, offsets, sizes and geometry ([spec/01 §1.2](../spec/01-conventions.md#12-numbers)).
|
||||
|
||||
### What happens when a better converter comes along?
|
||||
## What happens when a better converter comes along?
|
||||
|
||||
The system aligns the new reading with the established document, sentence by sentence.
|
||||
A sentence the alignment pairs keeps its ID even where the readings disagree, which catches a lost superscript instead of accepting it.
|
||||
@@ -35,7 +35,7 @@ Nothing changes until a correction passes the gate, the rules that refuse, for e
|
||||
|
||||
Converting the same input again with the same converter mints the same IDs ([spec/05 §5.6](../spec/05-hashes.md#56-alignment), [spec/11 §11.4](../spec/11-corrections.md#114-the-gate), [spec/04 §4.2](../spec/04-identity.md#42-minting-node-ids)).
|
||||
|
||||
### Do I need receipts, rights records and spot-checks for a personal library?
|
||||
## Do I need receipts, rights records and spot-checks for a personal library?
|
||||
|
||||
You need no receipts and no spot-checks.
|
||||
A receipt is a signed record that a named person compared nodes with the page image, and a spot-check is a second reviewer's sample of that work.
|
||||
@@ -44,12 +44,12 @@ One person can leave both out.
|
||||
Each source file still carries a rights record, though the record can say little: the license as printed, a `basis` of `unknown`, and no uses, each of which then counts as unknown ([spec/14 §14.1](../spec/14-rights.md)).
|
||||
The fields are defined so that a personal library loads into a team's store later without conversion ([spec/00 §0.4](../spec/00-introduction.md#04-status)).
|
||||
|
||||
### Is a model allowed to correct a document?
|
||||
## Is a model allowed to correct a document?
|
||||
|
||||
A model may propose corrections and answer suspects, under the same gate as a person, and its corrections record the model and the run as author.
|
||||
Only a named person can sign a receipt, and a model's annotation stays `proposed` until a person confirms it ([spec/11 §11.2](../spec/11-corrections.md#112-every-correction-records), [spec/18 §18.3](../spec/18-layers.md#183-the-annotation)).
|
||||
|
||||
### Why are annotations W3C Web Annotations?
|
||||
## Why are annotations W3C Web Annotations?
|
||||
|
||||
Tools that follow the W3C Web Annotation model can read the standard parts of these annotations.
|
||||
A layer, the file that groups annotations, follows the [W3C Web Annotation Data Model](https://www.w3.org/TR/annotation-model/), a W3C Recommendation of 2017.
|
||||
@@ -59,17 +59,17 @@ The formats use no `TextPositionSelector` or `FragmentSelector`.
|
||||
|
||||
The anchor adds what the standard selectors lack: the node's ID, its hashes, and a defined outcome (`current`, `changed-below`, `moved`, `reattached` or `stale`) when the text has changed.
|
||||
The Web Annotation model alone is the better-established choice where an annotation needs none of that.
|
||||
Whether a generic Web Annotation client displays an `AnchorSelector` is untested [CHECK: comparison.md says this was not verified].
|
||||
Whether a generic Web Annotation client displays an `AnchorSelector` is untested.
|
||||
See [the comparison](comparison.md).
|
||||
|
||||
### What is the difference between a claim and an annotation?
|
||||
## What is the difference between a claim and an annotation?
|
||||
|
||||
A statement true independently of the paper, such as "K_r is the bond force constant of the AMBER energy", is a claim.
|
||||
A claim has to be checked against evidence, so it belongs in a store of checked claims outside these formats, which define no claim semantics.
|
||||
|
||||
A statement about reading the paper, such as "the authors pass quickly over the scaling here", is an annotation in a layer, attributed and unchecked ([spec/18 §18.1](../spec/18-layers.md#181-one-rule-decides-what-is-a-claim-and-what-is-a-layer)).
|
||||
|
||||
### Can a team share notes on a paywalled paper?
|
||||
## Can a team share notes on a paywalled paper?
|
||||
|
||||
Yes, without sharing the paper.
|
||||
A checks-only package withholds all text and keeps IDs, labels, pages, boxes and keyed hashes.
|
||||
@@ -78,13 +78,13 @@ The hash must be keyed because anyone can recover a plain hash of a short text s
|
||||
|
||||
Whether a team may share notes on a paper this way is a question for its own counsel; nothing here is legal advice.
|
||||
|
||||
### How does this differ from JATS or TEI?
|
||||
## How does this differ from JATS or TEI?
|
||||
|
||||
They overlap in subject, not in job.
|
||||
|
||||
JATS, the [Journal Article Tag Suite](https://www.niso.org/publications/z3996-2024-jats) (ANSI/NISO Z39.96-2024), is an XML format for publishers and archives to exchange journal content. manuspec does not replace it.
|
||||
A JATS file is one kind of witness (a source file), an input from which a document is established.
|
||||
The JATS 1.4 tag library lists no sentence-level element [CHECK: comparison.md found none on the element list page it read; confirm against the full tag library].
|
||||
None of the 312 elements in the [JATS 1.4 Archiving tag library](https://jats.nlm.nih.gov/archiving/tag-library/1.4/) marks a sentence; its smallest text unit is the paragraph, `<p>`.
|
||||
|
||||
TEI, the Text Encoding Initiative, has a sentence-like [`<s>` element](https://tei-c.org/release/doc/tei-p5-doc/en/html/ref-s.html) and keeps both readings of a corrected passage with [`<corr>` and `<sic>`](https://tei-c.org/release/doc/tei-p5-doc/en/html/ref-corr.html).
|
||||
The encoder assigns its `xml:id`, and the guidelines [recommend it for durability](https://tei-c.org/release/doc/tei-p5-doc/en/html/SA.html).
|
||||
@@ -92,9 +92,9 @@ manuspec mints and retires IDs by rule, keeps corrections as separate records wi
|
||||
|
||||
Keep JATS for publisher and archive exchange and TEI for general scholarly text encoding.
|
||||
A TEI file is a witness of kind `xml` ([spec/02 §2.3](../spec/02-model.md)).
|
||||
The full comparison is [docs/comparison.md](comparison.md) [CHECK: that page is written elsewhere and does not yet exist in the repository; confirm the path].
|
||||
The full comparison is [docs/comparison.md](comparison.md)
|
||||
|
||||
### What is its status and maturity?
|
||||
## What is its status and maturity?
|
||||
|
||||
Version 0.1.0 has 27 formats: 20 are `candidate` and 7 are `provisional`, as [registry/formats.json](../registry/formats.json) lists.
|
||||
A `candidate` format is complete, with vectors and fixtures, and ready to implement.
|
||||
@@ -103,7 +103,7 @@ The provisional formats are dataset, digitization, view, project, bundle, snapsh
|
||||
|
||||
The repository holds a reference checker (`tools/check.py`), 20 vector files, and 22 example packages, of which 17 are deliberately broken ([spec/00 §0.4](../spec/00-introduction.md#04-status), [spec/22](../spec/22-conformance.md)).
|
||||
|
||||
### What does it not do?
|
||||
## What does it not do?
|
||||
|
||||
It is built for a team-scale corpus of hundreds of documents, not hundreds of millions of papers.
|
||||
It does not replace publisher interchange formats; it reads JATS as a witness.
|
||||
@@ -111,14 +111,14 @@ It does not fix what a converter reads wrongly.
|
||||
It records the reading and raises disagreements as suspects, and a correction is checkable.
|
||||
It is not a search engine, a citation index or a recommendation feed ([docs/overview.md](overview.md)).
|
||||
|
||||
### How do I add a node kind or a run kind?
|
||||
## How do I add a node kind or a run kind?
|
||||
|
||||
A node kind is a type of document part, such as `sentence`; a run kind is a type of inline content within a sentence, such as `math`.
|
||||
Propose either with the vector or fixture that shows it.
|
||||
A new kind is a minor version ([spec/00 §0.3](../spec/00-introduction.md#03-format-identifiers-and-versions)).
|
||||
See [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
### What are the files in `tools/` for?
|
||||
## 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.
|
||||
The `oracle/` modules compute every expected value, so that none is typed by hand.
|
||||
|
||||
+6
-2
@@ -62,7 +62,8 @@ Layer
|
||||
: A named set of annotations with one purpose, audience, visibility and license. ([18.2](../spec/18-layers.md#182-the-layer))
|
||||
|
||||
Leaf
|
||||
: A node that holds text itself: a sentence, cell, reference, author line, keyword line, equation, equation line, code or algorithm [CHECK: spec/03 §3.6 says an `equation-line` is never a text leaf, while spec/06 §6.4 lists it as a leaf anchor kind; confirm the intended list]. ([3.6](../spec/03-nodes.md#36-the-plain-projection))
|
||||
: A node that holds text itself: a sentence, cell, reference, author line, keyword line, equation, equation line, code or algorithm.
|
||||
A leaf anchor points at one whole. ([6.4](../spec/06-anchors.md#64-four-kinds-of-anchor))
|
||||
|
||||
Lossy projection
|
||||
: A node's text with scripts flattened, citation marks, whitespace and dashes removed, and case folded; used for proposing alignments. ([5.5](../spec/05-hashes.md#55-the-lossy-projection))
|
||||
@@ -112,7 +113,7 @@ Rights record
|
||||
: A witness's license, the basis on which the team holds it, and the uses that basis allows. ([14.1](../spec/14-rights.md#141-the-rights-record))
|
||||
|
||||
Roster
|
||||
: The list of keys that may sign, with their owners and purposes. ([8.7](../spec/08-fidelity.md#87-revocations-and-the-roster))
|
||||
: The list of keys that may sign, with the person who holds each, that person's role in the team, and what the key may sign. ([8.7](../spec/08-fidelity.md#87-revocations-and-the-roster))
|
||||
|
||||
Selector
|
||||
: Outside term.
|
||||
@@ -136,6 +137,9 @@ Targeted F3
|
||||
: Proofreading the nodes a team's claims, quotations and datasets rest on, rather than every node.
|
||||
The target set also includes every display equation and, under the project's digit rule (`all`, `anchored-sections` or `none`), sentences with a digit in a text or math run (an inline stretch of plain text, or of TeX or Typst math, within the sentence; [3.5](../spec/03-nodes.md#35-inline-runs)). ([8.10](../spec/08-fidelity.md#810-targeted-f3))
|
||||
|
||||
Text leaf
|
||||
: A leaf whose text a container's projections are made of: any leaf except an equation line, or an equation inside a sentence, whose text is already part of its equation or its sentence. ([3.6](../spec/03-nodes.md#36-the-plain-projection))
|
||||
|
||||
Text policy
|
||||
: Whether a package carries its text (`full`) or withholds it (`checks-only`). ([12.5](../spec/12-package.md#125-text-policies))
|
||||
|
||||
|
||||
+2
-2
@@ -99,7 +99,7 @@ Only the three text values, `text_hash`, `match_hash` and `align_key`, normalize
|
||||
|
||||
### Hashes that leave the team are keyed
|
||||
|
||||
A plain hash of "310.0" is recovered by hashing guesses, and overlapping anchors can rebuild passages [CHECK: the overlapping-anchors claim is the author's; spec/05 §5.8 gives only the guessing argument].
|
||||
A plain hash of "310.0" is recovered by hashing guesses, and overlapping anchors can rebuild passages.
|
||||
Inside a team, plain hashes are fine.
|
||||
|
||||
An export to another machine of the team uses an HMAC (hash-based message authentication code), a hash that needs a secret key, under the team key.
|
||||
@@ -124,7 +124,7 @@ Deciding whether a merged paragraph still holds "at least half" of what was anch
|
||||
|
||||
### Long quotations become a hash and a length
|
||||
|
||||
Quotations over 200 code points are replaced by a hash and a length, which keeps anchors small and limits how much text an anchor carries [CHECK: reason for the cap is the author's; spec/06 §6.2 gives the number but not the reason].
|
||||
Quotations over 200 code points are replaced by a hash and a length, which keeps anchors small and limits how much text an anchor carries.
|
||||
A hashed window can still be found again in the node.
|
||||
|
||||
### Prefix and suffix pick between repeated quotes
|
||||
|
||||
+8
-5
@@ -134,7 +134,7 @@ A checker of exact words accepts only `current`, `moved` and `reattached`.
|
||||
|
||||
The [W3C Web Annotation Data Model](https://www.w3.org/TR/annotation-model/) is the general W3C Recommendation for annotating web resources. manuspec's annotation layers build on it, with a custom `AnchorSelector` followed by a standard `TextQuoteSelector` as a fallback.
|
||||
It has no permanent IDs for sentences inside the source, no hashes of the target text, and no results such as `moved` or drift.
|
||||
See [comparison.md](comparison.md) [CHECK: docs/comparison.md is absent from the repository snapshot; confirm the link target].
|
||||
See [comparison.md](comparison.md).
|
||||
|
||||
## 6. Review and signatures
|
||||
|
||||
@@ -154,8 +154,10 @@ His signature, made with his separate spot-check key `ben-spot`, picks 10 nodes
|
||||
Those sentences fall back to their earlier levels, which is why `eajq93t` is still F0.
|
||||
- `ana` signs `gena76nkb6cnc` for the figure's three caption sentences. `ben`'s spot-check `tejzs01eqn2ym` finds a missing period in `nmm4md0`, which suspends the receipt until she reviews the sitting again and signs a new one.
|
||||
|
||||
The document's report, in [`document.json`](../fixtures/packages/hale2026harmonic/document.json), counts the result over 45 leaves: 28 at F3, 12 at F2, 4 at F1 and 1 at F0.
|
||||
The 28 are the 23 nodes of `eqgbbpwb6725v`, the 2 of `zaxky8c1y12tc`, and the 3 caption sentences, which still count because Ben's own valid receipt lists them at F3 [CHECK: confirm this is intended; tools/oracle/receipts.py computes it this way].
|
||||
The document's report, in [`document.json`](../fixtures/packages/hale2026harmonic/document.json), counts the result over 45 leaves: 27 at F3, 13 at F2, 4 at F1 and 1 at F0.
|
||||
The 27 are the 23 nodes of `eqgbbpwb6725v`, the 2 of `zaxky8c1y12tc`, and 2 of the 3 caption sentences.
|
||||
Those two still count because Ben's spot-check is itself a valid receipt, and he compared them with the page and found them right.
|
||||
The third, `nmm4md0`, stays at F2: a spot-check never raises a node it found in error ([spec/08 §8.2](../spec/08-fidelity.md#82-levels)).
|
||||
|
||||
## 7. Data out of the table
|
||||
|
||||
@@ -168,14 +170,15 @@ The reference checker, [`tools/check.py`](../tools/check.py), confirms each CSV
|
||||
|
||||
## 8. Sharing without the text
|
||||
|
||||
The team wants to share its notes with another machine and keep the paper to itself.
|
||||
The team wants to move its notes to another of its own machines without copying the paper there.
|
||||
[`hale2026harmonic.checks-only`](../fixtures/packages/hale2026harmonic.checks-only/) is the same document with every leaf's text withheld and every hash replaced by an HMAC under the team's key ([spec/12 §12.5](../spec/12-package.md), [spec/05 §5.8](../spec/05-hashes.md)).
|
||||
An HMAC is a hash mixed with a secret key.
|
||||
Anyone can recover a plain hash of a short text such as "310.0" by hashing guesses; a keyed hash resists that.
|
||||
Corrections, receipts, the roster and the dataset are left out too.
|
||||
IDs, labels, pages and boxes survive, so annotations still land in the right place.
|
||||
Only someone holding the key and the text can check the values.
|
||||
[CHECK: the author wrote "a collaborator's machine"; the fixture uses the team-key scheme (`hmac-team`), which spec/05 §5.8 assigns to exports between the team's own machines].
|
||||
The team key suits an export between the team's own machines.
|
||||
An export to a collaborator outside the team would use a key derived from the pages of the paper instead, so that only someone holding the paper can check it.
|
||||
|
||||
## 9. Recompute, print and query the package
|
||||
|
||||
|
||||
+4
-3
@@ -33,8 +33,8 @@ A conforming reader accepts each one and recomputes every hash it contains.
|
||||
|
||||
## Records
|
||||
|
||||
`records/` holds a valid example of most of the other formats.
|
||||
[CHECK: spec/22 section 22.1 says "a valid record of every other format", but `scienting-stages/1`, `scienting-stamps/1`, `scienting-suspect/1` and `scienting-bundle/1` have no standalone file here; confirm the wording the author wants]
|
||||
`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.
|
||||
Each record is a single file, or a folder where a format needs several files.
|
||||
|
||||
| Files | What they are for |
|
||||
@@ -45,9 +45,10 @@ Each record is a single file, or a folder where a format needs several files.
|
||||
| `policy.json` | A signed team decision that settles a use the rights record leaves unknown. |
|
||||
| `backend-docling.json`, `backend-examplevendor.json` | The code and weight licenses of a conversion tool and the conditions attached to them. |
|
||||
| `profile-acme.json`, `profile-smithlab.json` | What those conditions ask about a team: its purpose, its funding and whom it competes with. |
|
||||
| `roster.json` | The keys that may sign, and for what. |
|
||||
| `roster.json` | The keys that may sign, the role of each key's holder in the team, and what each key may sign. Ana is the owner, so she may revoke anyone's receipt. |
|
||||
| `layer/` | A layer of annotations on the main example (`layer.json`, `annotations.jsonl`) and the receipt that confirms them (`receipt.json`). |
|
||||
| `submissions/`, `grades/` | A student's answers to exercises in the layer, and the grader's result for each. |
|
||||
| `suspect.json` | A doubtful reading raised for review: the converter's reading of an exponent in the main example, before the first correction answered it. |
|
||||
| `qa-report.json` | Reviewer error bounds for a delivery that covers two of the main example's receipts. |
|
||||
| `project.json`, `digitization.json`, `digitization-confirmation.json`, `datasets/`, `view.vl.json`, `snapshot.json`, `draft-ids.jsonl` | A team project; values read off the main example's figure by hand, the receipt that confirms them, and the dataset they yield; a Vega-Lite chart pinned to that dataset; what the draft cites, pinned; and the sentence IDs of the draft's Typst project. These formats are `provisional`. |
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"ids.jsonl","sha256":"1eb53589cd394214dd4ee15be98a3dfbacec5e585a1e655fe2dfeff71a07e19e","size":61},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"ids.jsonl","sha256":"1eb53589cd394214dd4ee15be98a3dfbacec5e585a1e655fe2dfeff71a07e19e","size":61},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"7f531bc7549887e8d18a0b31a91752912f2c20b26521cf3d8d97180019773324","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"7f531bc7549887e8d18a0b31a91752912f2c20b26521cf3d8d97180019773324","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"8b74d834e39d7180321ca103af1ed778954ddf6c8ed1dd29e5c7c41e551f46d4","size":562},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"8b74d834e39d7180321ca103af1ed778954ddf6c8ed1dd29e5c7c41e551f46d4","size":562},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"f685b39051b28677fc0829e7811fdcf65ef4a5801e3af23d55178a8af30aed48","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"f685b39051b28677fc0829e7811fdcf65ef4a5801e3af23d55178a8af30aed48","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"suspects.jsonl","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","size":0},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"suspects.jsonl","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","size":0},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"d9b0514d8cc6988ad7dadfc1992dbca4c19843079dd8ca6b7edbe520635d2929","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"d9b0514d8cc6988ad7dadfc1992dbca4c19843079dd8ca6b7edbe520635d2929","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"3145ecabdd0b6d2014e200616bbac7b8f3904fbea53d3665e9dba27714422223","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"3145ecabdd0b6d2014e200616bbac7b8f3904fbea53d3665e9dba27714422223","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"locations.jsonl","sha256":"5c893b791f364ff5b4f7388bd2c8f0e7162bd6d8118914e10d752b703ef01037","size":130},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"locations.jsonl","sha256":"5c893b791f364ff5b4f7388bd2c8f0e7162bd6d8118914e10d752b703ef01037","size":130},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"9ed0d41facc80ee7213d3d29b17756c9f26780eb3c154680c6f7d7101435f3d8","size":11312},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"9ed0d41facc80ee7213d3d29b17756c9f26780eb3c154680c6f7d7101435f3d8","size":11312},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"781da9a1c774b789aae28c3eab9187925619ea5a397a238b1a19f416b5c4fd88","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"781da9a1c774b789aae28c3eab9187925619ea5a397a238b1a19f416b5c4fd88","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"8d1a52600c06b988080c7b3f5f0fa9eddb348c923d529d9d5470ff165ef8cc69","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"8d1a52600c06b988080c7b3f5f0fa9eddb348c923d529d9d5470ff165ef8cc69","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"a829a88ea1c7f269d76fea3e7b3f97e63a836c4c92bbb95ec78f14b2c19e29e6","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"a829a88ea1c7f269d76fea3e7b3f97e63a836c4c92bbb95ec78f14b2c19e29e6","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"5309752c863a0827a5d41e6cd6b766e3fc01ad70b76d19a8e3444c05442c4f71","size":11317},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"5309752c863a0827a5d41e6cd6b766e3fc01ad70b76d19a8e3444c05442c4f71","size":11317},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"00f14e40fce80930e91d1ac01e97c5ec793f477f7dc93e65b49cda8e54bc547d","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"00f14e40fce80930e91d1ac01e97c5ec793f477f7dc93e65b49cda8e54bc547d","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"797937baef9d68ae3cafe26d9d99412c53bf4ebdfdc6b2c6ccbbf2ce752fad30","size":11291},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"797937baef9d68ae3cafe26d9d99412c53bf4ebdfdc6b2c6ccbbf2ce752fad30","size":11291},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"assets/586f92512b294a1c325bfc16207771fb9acc7bb67bb206f25a429a055134056f.png","present":false,"sha256":"586f92512b294a1c325bfc16207771fb9acc7bb67bb206f25a429a055134056f","size":18213},{"path":"assets/a18e1eb7f434c85ee180399dc55d2ee78c5ceb915c4a004c5dd0e490399d4c75.png","sha256":"a18e1eb7f434c85ee180399dc55d2ee78c5ceb915c4a004c5dd0e490399d4c75","size":73},{"path":"document.json","sha256":"e5f0fa4a71c865d7a8ced771073f4ea8a7d55f83ca6ddbde12f339bbc1e5bc72","size":1018},{"path":"ids.jsonl","sha256":"665b127abf3d7ad20631aa2281d66cea0076cd68e77fc8a5921bc310d295a489","size":852},{"path":"locations.jsonl","sha256":"2c085435b753f9f586d29790d65cc9e61470f0ae661591480f528193dcd91a04","size":7396},{"path":"nodes.jsonl","sha256":"f3464c19215b521c44660d38cd1d9270c740d485eb96af8162922c3e9c41b858","size":41653},{"path":"pages.jsonl","sha256":"948177c6b7e5e361ac1da0ecff6b52406d3513f14d58cd27cd84f17e6f1c054e","size":438},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"references.jsonl","sha256":"59c7120991928517607521a43a76482370b984c48cf1e74550209dbf607707ef","size":148},{"path":"witnesses.json","sha256":"99ae654df4fb461a424bb51edd476a89041182c7df9c98653a81b78031f92ada","size":1006}],"format":"scienting-package/1","key":"hale2026harmonic"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"assets/586f92512b294a1c325bfc16207771fb9acc7bb67bb206f25a429a055134056f.png","present":false,"sha256":"586f92512b294a1c325bfc16207771fb9acc7bb67bb206f25a429a055134056f","size":18213},{"path":"assets/a18e1eb7f434c85ee180399dc55d2ee78c5ceb915c4a004c5dd0e490399d4c75.png","sha256":"a18e1eb7f434c85ee180399dc55d2ee78c5ceb915c4a004c5dd0e490399d4c75","size":73},{"path":"document.json","sha256":"158e72b3c95617b49b4e0297f01067d8841913349cbe8fc3ec0c35d878b39f4d","size":1018},{"path":"ids.jsonl","sha256":"665b127abf3d7ad20631aa2281d66cea0076cd68e77fc8a5921bc310d295a489","size":852},{"path":"locations.jsonl","sha256":"2c085435b753f9f586d29790d65cc9e61470f0ae661591480f528193dcd91a04","size":7396},{"path":"nodes.jsonl","sha256":"f3464c19215b521c44660d38cd1d9270c740d485eb96af8162922c3e9c41b858","size":41653},{"path":"pages.jsonl","sha256":"948177c6b7e5e361ac1da0ecff6b52406d3513f14d58cd27cd84f17e6f1c054e","size":438},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"references.jsonl","sha256":"59c7120991928517607521a43a76482370b984c48cf1e74550209dbf607707ef","size":148},{"path":"witnesses.json","sha256":"99ae654df4fb461a424bb51edd476a89041182c7df9c98653a81b78031f92ada","size":1006}],"format":"scienting-package/1","key":"hale2026harmonic"}
|
||||
@@ -1 +1 @@
|
||||
{"counts":{"leaves":45,"nodes":75},"fidelity":{"counts":{"F0":1,"F1":4,"F2":12,"F3":28},"lowest":"F0"},"format":"scienting-doc/1","hashing":{"key_id":"ba87f5b02064d529","scheme":"hmac-team"},"key":"hale2026harmonic","kind":"article","language":"en","license":{"text":"All rights reserved by the publisher (fictional)."},"meta":{"DOI":"10.5555/example.hale.2026","author":[{"family":"Hale","given":"R."},{"family":"Osei","given":"M."}],"container-title":"Journal of Example Chemistry","issued":{"date-parts":[[2026,3,2]]},"page":"101-109","title":"Harmonic bond parameters for small organic molecules from a two-step fit","type":"article-journal","volume":"14"},"meta_hash":"831ac4c47c87df2bc0d761e169f3aefb3b2e64404a2d2b157a692262a956301f","policy":"checks-only","relations":[],"rev":12,"root":"1fd39f356fbbdbe306b68ed01d71ce1acf9a46310658f430b0e6c5581f5fce6b","store":"smithlab","version":{"date":"2026-03-02","type":"version-of-record"},"work":{"key":"hale2026harmonic","same_as":["doi:10.5555/example.hale.2026"]}}
|
||||
{"counts":{"leaves":45,"nodes":75},"fidelity":{"counts":{"F0":1,"F1":4,"F2":13,"F3":27},"lowest":"F0"},"format":"scienting-doc/1","hashing":{"key_id":"ba87f5b02064d529","scheme":"hmac-team"},"key":"hale2026harmonic","kind":"article","language":"en","license":{"text":"All rights reserved by the publisher (fictional)."},"meta":{"DOI":"10.5555/example.hale.2026","author":[{"family":"Hale","given":"R."},{"family":"Osei","given":"M."}],"container-title":"Journal of Example Chemistry","issued":{"date-parts":[[2026,3,2]]},"page":"101-109","title":"Harmonic bond parameters for small organic molecules from a two-step fit","type":"article-journal","volume":"14"},"meta_hash":"831ac4c47c87df2bc0d761e169f3aefb3b2e64404a2d2b157a692262a956301f","policy":"checks-only","relations":[],"rev":12,"root":"1fd39f356fbbdbe306b68ed01d71ce1acf9a46310658f430b0e6c5581f5fce6b","store":"smithlab","version":{"date":"2026-03-02","type":"version-of-record"},"work":{"key":"hale2026harmonic","same_as":["doi:10.5555/example.hale.2026"]}}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"assets/586f92512b294a1c325bfc16207771fb9acc7bb67bb206f25a429a055134056f.png","present":false,"sha256":"586f92512b294a1c325bfc16207771fb9acc7bb67bb206f25a429a055134056f","size":18213},{"path":"assets/a18e1eb7f434c85ee180399dc55d2ee78c5ceb915c4a004c5dd0e490399d4c75.png","sha256":"a18e1eb7f434c85ee180399dc55d2ee78c5ceb915c4a004c5dd0e490399d4c75","size":73},{"path":"corrections.jsonl","sha256":"6d72d6629c7da952c17af944a90e266f5be1d0540f2c82c207fea57244aa3bee","size":8310},{"path":"datasets/ds-hale-bond-params/anchors.jsonl","sha256":"f45d4dd993aeaa741d7d6a12eb1817e5c975ccec2a1527da13457b4fec0bf1cc","size":5986},{"path":"datasets/ds-hale-bond-params/data.csv","sha256":"dbde72e4526e469e1535b87272eb50683160b2fe7c40b7f064e6592341f754a8","size":206},{"path":"datasets/ds-hale-bond-params/datapackage.json","sha256":"d8bbf8bd7c1b0207ada817d10580e835bda3a8691ad3e14fc1910519f4341974","size":574},{"path":"derived/mathml.jsonl","sha256":"a1aa1bad2eeea181f85670ad12aff89f95b703b6d28c092e2aad4c74e3d3b597","size":348},{"path":"document.json","sha256":"3e18fac5264a12b7981230b23b28fba400e28a6e9ba5f1f9c319027d64f4300e","size":979},{"path":"ids.jsonl","sha256":"665b127abf3d7ad20631aa2281d66cea0076cd68e77fc8a5921bc310d295a489","size":852},{"path":"locations.jsonl","sha256":"2c085435b753f9f586d29790d65cc9e61470f0ae661591480f528193dcd91a04","size":7396},{"path":"nodes.jsonl","sha256":"26f94d985bf65825c33cef53a3952b4177de203f6829073e76eea19ee3d0d37c","size":45649},{"path":"pages.jsonl","sha256":"948177c6b7e5e361ac1da0ecff6b52406d3513f14d58cd27cd84f17e6f1c054e","size":438},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"2d4e11512d055abbd6953a803f6498ffd3a0bfdde3fad8f9e97e700f4c6d8ef4","size":13075},{"path":"references.jsonl","sha256":"09875f7f7205036b2cf4fb5a889dcecc686faf4b1dc2c4e129b7017abbc9ce3d","size":567},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"99ae654df4fb461a424bb51edd476a89041182c7df9c98653a81b78031f92ada","size":1006}],"format":"scienting-package/1","key":"hale2026harmonic"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"assets/586f92512b294a1c325bfc16207771fb9acc7bb67bb206f25a429a055134056f.png","present":false,"sha256":"586f92512b294a1c325bfc16207771fb9acc7bb67bb206f25a429a055134056f","size":18213},{"path":"assets/a18e1eb7f434c85ee180399dc55d2ee78c5ceb915c4a004c5dd0e490399d4c75.png","sha256":"a18e1eb7f434c85ee180399dc55d2ee78c5ceb915c4a004c5dd0e490399d4c75","size":73},{"path":"corrections.jsonl","sha256":"6d72d6629c7da952c17af944a90e266f5be1d0540f2c82c207fea57244aa3bee","size":8310},{"path":"datasets/ds-hale-bond-params/anchors.jsonl","sha256":"f45d4dd993aeaa741d7d6a12eb1817e5c975ccec2a1527da13457b4fec0bf1cc","size":5986},{"path":"datasets/ds-hale-bond-params/data.csv","sha256":"dbde72e4526e469e1535b87272eb50683160b2fe7c40b7f064e6592341f754a8","size":206},{"path":"datasets/ds-hale-bond-params/datapackage.json","sha256":"d8bbf8bd7c1b0207ada817d10580e835bda3a8691ad3e14fc1910519f4341974","size":574},{"path":"derived/mathml.jsonl","sha256":"a1aa1bad2eeea181f85670ad12aff89f95b703b6d28c092e2aad4c74e3d3b597","size":348},{"path":"document.json","sha256":"db825f861803d1c475e49978c95e98aa11a8085e47371348e9e88969f612e025","size":979},{"path":"ids.jsonl","sha256":"665b127abf3d7ad20631aa2281d66cea0076cd68e77fc8a5921bc310d295a489","size":852},{"path":"locations.jsonl","sha256":"2c085435b753f9f586d29790d65cc9e61470f0ae661591480f528193dcd91a04","size":7396},{"path":"nodes.jsonl","sha256":"26f94d985bf65825c33cef53a3952b4177de203f6829073e76eea19ee3d0d37c","size":45649},{"path":"pages.jsonl","sha256":"948177c6b7e5e361ac1da0ecff6b52406d3513f14d58cd27cd84f17e6f1c054e","size":438},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"2d4e11512d055abbd6953a803f6498ffd3a0bfdde3fad8f9e97e700f4c6d8ef4","size":13075},{"path":"references.jsonl","sha256":"09875f7f7205036b2cf4fb5a889dcecc686faf4b1dc2c4e129b7017abbc9ce3d","size":567},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"99ae654df4fb461a424bb51edd476a89041182c7df9c98653a81b78031f92ada","size":1006}],"format":"scienting-package/1","key":"hale2026harmonic"}
|
||||
@@ -1 +1 @@
|
||||
{"counts":{"leaves":45,"nodes":75},"fidelity":{"counts":{"F0":1,"F1":4,"F2":12,"F3":28},"lowest":"F0"},"format":"scienting-doc/1","hashing":{"scheme":"plain"},"key":"hale2026harmonic","kind":"article","language":"en","license":{"text":"All rights reserved by the publisher (fictional)."},"meta":{"DOI":"10.5555/example.hale.2026","author":[{"family":"Hale","given":"R."},{"family":"Osei","given":"M."}],"container-title":"Journal of Example Chemistry","issued":{"date-parts":[[2026,3,2]]},"page":"101-109","title":"Harmonic bond parameters for small organic molecules from a two-step fit","type":"article-journal","volume":"14"},"meta_hash":"776546e619e3fc9f1832153fb0585272c0a7015a0708994245cb09b1cfd1fb87","policy":"full","relations":[],"rev":12,"root":"9c00efa3d1f8fe5c93ae26868926f6af6c193a5cf8cecd32ba650842aff71e01","store":"smithlab","version":{"date":"2026-03-02","type":"version-of-record"},"work":{"key":"hale2026harmonic","same_as":["doi:10.5555/example.hale.2026"]}}
|
||||
{"counts":{"leaves":45,"nodes":75},"fidelity":{"counts":{"F0":1,"F1":4,"F2":13,"F3":27},"lowest":"F0"},"format":"scienting-doc/1","hashing":{"scheme":"plain"},"key":"hale2026harmonic","kind":"article","language":"en","license":{"text":"All rights reserved by the publisher (fictional)."},"meta":{"DOI":"10.5555/example.hale.2026","author":[{"family":"Hale","given":"R."},{"family":"Osei","given":"M."}],"container-title":"Journal of Example Chemistry","issued":{"date-parts":[[2026,3,2]]},"page":"101-109","title":"Harmonic bond parameters for small organic molecules from a two-step fit","type":"article-journal","volume":"14"},"meta_hash":"776546e619e3fc9f1832153fb0585272c0a7015a0708994245cb09b1cfd1fb87","policy":"full","relations":[],"rev":12,"root":"9c00efa3d1f8fe5c93ae26868926f6af6c193a5cf8cecd32ba650842aff71e01","store":"smithlab","version":{"date":"2026-03-02","type":"version-of-record"},"work":{"key":"hale2026harmonic","same_as":["doi:10.5555/example.hale.2026"]}}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"d5c14bada3b9b61e71c91f2f9f8dbd7eeb84b3e0b4256748e655821aaba326bd","size":861},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
{"doc":"scienting-doc/1","files":[{"path":"document.json","sha256":"20a7d6ba83bf42a87e44f3b3f96bfb16ff545b199d82e54d3c17989e465281df","size":555},{"path":"nodes.jsonl","sha256":"ffeffbf15cc9fe8182a31ef0bac32c1aef8c7a119706ac1e8a55aad218d0622c","size":11295},{"path":"provenance.json","sha256":"b1af404b3a07d4b102f0a45237285315dc1ff60f243636c22aef3e1c20b46912","size":224},{"path":"receipts.jsonl","sha256":"e6be2c6ff4d1444e2faf75c8c4d03482ceaf49746ea390592928b2b5e3b22237","size":2180},{"path":"references.jsonl","sha256":"bb5445037c28bf5fde215b4b831c29327dec6dd88259e2d53aabb995f2fdb98f","size":60},{"path":"roster.json","sha256":"7ee0c38fb4fdfc15d4ffa8139a239c23082dc69f8147c5920a39e8361cc7bbeb","size":924},{"path":"witnesses.json","sha256":"83ac4411c2efe65e0f7fc4c9336abbef6977cd32b0f90de029a7f8516afc3e97","size":750}],"format":"scienting-package/1","key":"tiny2026example"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -1 +1 @@
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"scheme":"ed25519"}],"store":"smithlab"}
|
||||
{"format":"scienting-roster/1","keys":[{"id":"ana","person":{"name":"Ana Example"},"public":"KaptcVSRRjQ1EaRm7sWY+ExKeNtrQ5Xny7G0sOQq1Rc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"owner","scheme":"ed25519"},{"id":"ben","person":{"name":"Ben Example"},"public":"FTgWcu94E4B07ky4Sd2W6VcMzamQgx8G7v++3Y/jvfY=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"ben-spot","person":{"name":"Ben Example"},"public":"Z9Kcdd6RARODR/eUTI0yTXQkue0/K6ehrInEtAfGrzI=","purposes":["spot-check"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"editor","scheme":"ed25519"},{"id":"cara","person":{"name":"Cara Example"},"public":"J8HPVPYtxYbKQZLMnlvND2FeGrhHj/RN8fLQ0MeyKCc=","purposes":["sign"],"registered_at":"2026-09-01T00:00:00Z","revoked_at":null,"role":"reader","scheme":"ed25519"}],"store":"smithlab"}
|
||||
@@ -0,0 +1 @@
|
||||
{"a":"10-3","b":"10−3","crop":"ab6ae30528212e54c2a74d8ed67631ff65f74055505dbe8af44d92a29bde0840","format":"scienting-suspect/1","id":"fm59v7mr46prc","kind":"number","node":"e4fkseg","note":"The text layer reads a hyphen on the line; the page image shows a raised minus three."}
|
||||
@@ -30,6 +30,8 @@ environments:
|
||||
linux-64:
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.1.1-py314h8d76f0c_3.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-50.0.2-py311hc91d8b8_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_103.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.5-hd2095e1_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda
|
||||
@@ -52,11 +54,15 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-2026.9.1-py314h7e8cd81_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.8-py314hd8ed1ab_102.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_1.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/markdownlint-cli2-0.23.3-h5ac6406_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.11-pyh5ded981_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.8-h4df99d1_102.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-9_cp314.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda
|
||||
@@ -64,6 +70,8 @@ environments:
|
||||
linux-aarch64:
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_10.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.1.1-py314h65cb5ac_3.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cryptography-50.0.2-py311h9269228_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.46.1-default_h1979696_103.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.8.5-h4154aff_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.7.0-hdaad0be_1.conda
|
||||
@@ -86,26 +94,36 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/rpds-py-2026.9.1-py314h231d840_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_hf03c496_4.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h9d15635_7.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.8-py314hd8ed1ab_102.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_1.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/markdownlint-cli2-0.23.3-h5ac6406_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.11-pyh5ded981_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.8-h4df99d1_102.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-9_cp314.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda
|
||||
osx-64:
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.8-py314hd8ed1ab_102.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_1.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/markdownlint-cli2-0.23.3-h5ac6406_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.11-pyh5ded981_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.8-h4df99d1_102.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-9_cp314.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h374f1ed_10.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.1.1-py314h0656537_3.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/cryptography-50.0.2-py311hc027f23_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-23.1.3-h19cb2f5_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.8.5-h6e061ac_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.7.0-h6b3a05b_1.conda
|
||||
@@ -123,17 +141,23 @@ environments:
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda
|
||||
osx-arm64:
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.8-py314hd8ed1ab_102.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_1.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/markdownlint-cli2-0.23.3-h5ac6406_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.11-pyh5ded981_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.8-h4df99d1_102.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-9_cp314.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h74c22ad_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.1.1-py314h618e29d_3.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-50.0.2-py311hc28ac56_0.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260526.0-cxx17_h4c27e2a_3.conda
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-h1dcdb26_4.conda
|
||||
@@ -188,6 +212,39 @@ packages:
|
||||
- bzip2 >=1.0.8,<2.0a0
|
||||
size: 257808
|
||||
timestamp: 1785906269155
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.1.1-py314h8d76f0c_3.conda
|
||||
sha256: 15e38176fa2173e063a19dad3d55746ac4c757b028813380d7f22c42781af4c4
|
||||
md5: ee66d46f7693fdcbaa1761fb8a4f6c3d
|
||||
depends:
|
||||
- __glibc >=2.17,<3.0.a0
|
||||
- libffi >=3.7.0,<3.8.0a0
|
||||
- libgcc >=15
|
||||
- pycparser
|
||||
- python >=3.14,<3.15.0a0
|
||||
- python_abi 3.14.* *_cp314
|
||||
license: MIT
|
||||
license_family: MIT
|
||||
run_exports: {}
|
||||
size: 307262
|
||||
timestamp: 1788485305536
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-50.0.2-py311hc91d8b8_0.conda
|
||||
noarch: python
|
||||
sha256: d1956b37648b45d54e884d765223974fe6e26d4e7ea248cd08c625ba8cd823ab
|
||||
md5: e9af843b1d7656fd6bdbe9aa69e95579
|
||||
depends:
|
||||
- python
|
||||
- cffi >=2.0
|
||||
- libgcc >=15
|
||||
- __glibc >=2.17,<3.0.a0
|
||||
- _python_abi3_support 1.*
|
||||
- cpython >=3.11
|
||||
- openssl >=3.5.8,<4.0a0
|
||||
constrains:
|
||||
- __glibc >=2.17
|
||||
license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT
|
||||
run_exports: {}
|
||||
size: 1830947
|
||||
timestamp: 1790790516832
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_103.conda
|
||||
sha256: 901649b1edbe841fd4ee7e2d491a05184ed540eb01cdae506872f344babc1d28
|
||||
md5: 2362c70e7e79827c32db35547d9c179b
|
||||
@@ -526,6 +583,37 @@ packages:
|
||||
- bzip2 >=1.0.8,<2.0a0
|
||||
size: 194694
|
||||
timestamp: 1785906301397
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-2.1.1-py314h65cb5ac_3.conda
|
||||
sha256: cc2a7c807182bd18e07a6e255c98dc8a8feec2310ad2e801d296adebb8f2d9ea
|
||||
md5: 30b36a0c4239d65f98209664afb3485b
|
||||
depends:
|
||||
- libffi >=3.7.0,<3.8.0a0
|
||||
- libgcc >=15
|
||||
- pycparser
|
||||
- python >=3.14,<3.15.0a0
|
||||
- python_abi 3.14.* *_cp314
|
||||
license: MIT
|
||||
license_family: MIT
|
||||
run_exports: {}
|
||||
size: 329121
|
||||
timestamp: 1788485263944
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cryptography-50.0.2-py311h9269228_0.conda
|
||||
noarch: python
|
||||
sha256: 3636b61361abb09454348da5c32e6e3bbe62248547cd4255ce471469d671883a
|
||||
md5: 98a1febddc9e9d0fae8341302bde57a2
|
||||
depends:
|
||||
- python
|
||||
- cffi >=2.0
|
||||
- libgcc >=15
|
||||
- _python_abi3_support 1.*
|
||||
- cpython >=3.11
|
||||
- openssl >=3.5.8,<4.0a0
|
||||
constrains:
|
||||
- __glibc >=2.17
|
||||
license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT
|
||||
run_exports: {}
|
||||
size: 1795185
|
||||
timestamp: 1790790512228
|
||||
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.46.1-default_h1979696_103.conda
|
||||
sha256: 516808982ad26ebdc4a9d8f71ee86ea55d0bc95382b7bca80620e0ac220971f3
|
||||
md5: ca781d8b499c39ddfe10e55a0347d6d9
|
||||
@@ -816,6 +904,17 @@ packages:
|
||||
- zstd >=1.5.7,<1.6.0a0
|
||||
size: 615477
|
||||
timestamp: 1786599613561
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda
|
||||
sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2
|
||||
md5: 3845f3d75991bae0fb90884662f4327c
|
||||
depends:
|
||||
- cpython
|
||||
- python-gil
|
||||
license: MIT
|
||||
license_family: MIT
|
||||
run_exports: {}
|
||||
size: 8144
|
||||
timestamp: 1784221492234
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda
|
||||
sha256: 1b6124230bb4e571b1b9401537ecff575b7b109cc3a21ee019f65e083b8399ab
|
||||
md5: c6b0543676ecb1fb2d7643941fe375f2
|
||||
@@ -836,6 +935,17 @@ packages:
|
||||
run_exports: {}
|
||||
size: 131780
|
||||
timestamp: 1784754889428
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.8-py314hd8ed1ab_102.conda
|
||||
noarch: generic
|
||||
sha256: bf062db3f461ebb91b77ab4cea6841049dbe9d13c513a9ca2a101fa8ddea76a2
|
||||
md5: 54ecbe8d03ac833af969b3543bf116ef
|
||||
depends:
|
||||
- python >=3.14,<3.15.0a0
|
||||
- python_abi * *_cp314
|
||||
license: Python-2.0
|
||||
run_exports: {}
|
||||
size: 51745
|
||||
timestamp: 1791826571351
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_1.conda
|
||||
sha256: 328756a4555941d57af4a5f553e5d8598e9f3b37db028f557e30f9f0b3086db6
|
||||
md5: 204b5ca91eba7738790ecc333facbbbe
|
||||
@@ -873,6 +983,27 @@ packages:
|
||||
run_exports: {}
|
||||
size: 2246247
|
||||
timestamp: 1789995027396
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.11-pyh5ded981_0.conda
|
||||
sha256: 99af7c6061d7aa73cfcef615500ddf3e9f4a58c844646b7d7ce58147fb5758d7
|
||||
md5: 3cd164745122ab557cc30879390089e8
|
||||
depends:
|
||||
- python >=3.11
|
||||
- python
|
||||
license: BSD-3-Clause
|
||||
license_family: BSD
|
||||
run_exports: {}
|
||||
size: 58624
|
||||
timestamp: 1791592610066
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.8-h4df99d1_102.conda
|
||||
sha256: 96b092a54bc4187149726b121febef3142d21f3c9e69387f6029ee5f4e57ff85
|
||||
md5: 556734568e49dfd7cab8c0341ca1e67b
|
||||
depends:
|
||||
- cpython 3.14.8.*
|
||||
- python_abi * *_cp314
|
||||
license: Python-2.0
|
||||
run_exports: {}
|
||||
size: 51714
|
||||
timestamp: 1791826718684
|
||||
- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-9_cp314.conda
|
||||
build_number: 9
|
||||
sha256: e7f8e965bbfb98e08feb2365cacdad8bb218fa5b5a0a2752f747287f425f213c
|
||||
@@ -928,6 +1059,37 @@ packages:
|
||||
- bzip2 >=1.0.8,<2.0a0
|
||||
size: 133271
|
||||
timestamp: 1785906721507
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.1.1-py314h0656537_3.conda
|
||||
sha256: 493eb10ddcd00cf42151f1a6303b5b08a3641172861583fe2e128e9bbc1db3e2
|
||||
md5: 5502c80a2761fbc926dc5847b69b7f53
|
||||
depends:
|
||||
- __osx >=11.0
|
||||
- libffi >=3.7.0,<3.8.0a0
|
||||
- pycparser
|
||||
- python >=3.14,<3.15.0a0
|
||||
- python_abi 3.14.* *_cp314
|
||||
license: MIT
|
||||
license_family: MIT
|
||||
run_exports: {}
|
||||
size: 292318
|
||||
timestamp: 1788485600231
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/cryptography-50.0.2-py311hc027f23_0.conda
|
||||
noarch: python
|
||||
sha256: 99c82adba610df83bf0f47491adf6ced7ea7eafc703011a230ddfcfc89118081
|
||||
md5: 1d4e7183556210fc49be37acdca44076
|
||||
depends:
|
||||
- python
|
||||
- cffi >=2.0
|
||||
- __osx >=11.0
|
||||
- _python_abi3_support 1.*
|
||||
- cpython >=3.11
|
||||
- openssl >=3.5.8,<4.0a0
|
||||
constrains:
|
||||
- __osx >=11.0
|
||||
license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT
|
||||
run_exports: {}
|
||||
size: 1819278
|
||||
timestamp: 1790790581781
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-23.1.3-h19cb2f5_0.conda
|
||||
sha256: 19ec3adf903c04aac6f31b180d283f0beb6d0393c5c1603f1d57355eb3a8fd66
|
||||
md5: 55664c771bbae09ac41bd7c940c3a7e1
|
||||
@@ -1168,6 +1330,37 @@ packages:
|
||||
- c-ares >=1.34.8,<2.0a0
|
||||
size: 197819
|
||||
timestamp: 1787169996553
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.1.1-py314h618e29d_3.conda
|
||||
sha256: 172fcb9649244b38083d3242550841e9b24fafeb5530ea125b44f364172f4e9d
|
||||
md5: 1177738698334efc54d4e9a2f108c469
|
||||
depends:
|
||||
- __osx >=11.0
|
||||
- libffi >=3.7.0,<3.8.0a0
|
||||
- pycparser
|
||||
- python >=3.14,<3.15.0a0
|
||||
- python_abi 3.14.* *_cp314
|
||||
license: MIT
|
||||
license_family: MIT
|
||||
run_exports: {}
|
||||
size: 292373
|
||||
timestamp: 1788485305495
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-50.0.2-py311hc28ac56_0.conda
|
||||
noarch: python
|
||||
sha256: 0c681f12351d4942decede4ef431f4d4f27720da76c49f0a43f351275eca0bca
|
||||
md5: db8c959446b5085b7ae417e0d34ddc38
|
||||
depends:
|
||||
- python
|
||||
- cffi >=2.0
|
||||
- __osx >=11.0
|
||||
- openssl >=3.5.8,<4.0a0
|
||||
- _python_abi3_support 1.*
|
||||
- cpython >=3.11
|
||||
constrains:
|
||||
- __osx >=11.0
|
||||
license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT
|
||||
run_exports: {}
|
||||
size: 1770710
|
||||
timestamp: 1790790523277
|
||||
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda
|
||||
sha256: 6cdb5dee54c72e56ab189fb3ad33cb28533553d42590e7e831160248f4416a43
|
||||
md5: a5efc0b42bb8b42e97d0a29ae3e3c187
|
||||
|
||||
@@ -17,6 +17,7 @@ requires-pixi = ">=0.40"
|
||||
python = ">=3.14,<3.15"
|
||||
jsonschema = ">=4.26.0"
|
||||
markdownlint-cli2 = ">=0.23.3"
|
||||
cryptography = ">=46"
|
||||
|
||||
[tasks]
|
||||
check = { cmd = "python tools/check.py", depends-on = [
|
||||
@@ -26,3 +27,4 @@ quick = { cmd = "python tools/check.py --quick", description = "Check everything
|
||||
build = { cmd = "python tools/build.py", 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" }
|
||||
+1
-1
@@ -74,6 +74,6 @@ The stage requirements are configuration, not code, because what a team may do d
|
||||
|
||||
A team copies `scienting-stages-default.json`, adjusts it with its counsel and points its project's settings at the copy.
|
||||
The file itself calls its contents "proposed defaults from the design, for counsel to set".
|
||||
In a project, the `settings.stages` field names the stage file in use ([Chapter 20](../spec/20-projects.md)) [CHECK: schema says the value is a key; confirm it is the stage file's `id`, so a copy needs its own `id`].
|
||||
In a project, the `settings.stages` field holds the `id` of the stage file in use ([Chapter 20](../spec/20-projects.md)), so a team's copy needs an `id` of its own.
|
||||
The project format is `provisional`.
|
||||
[Chapter 14](../spec/14-rights.md) explains how stages are checked.
|
||||
@@ -53,6 +53,11 @@
|
||||
"description": "Runs replace text outside a text run, or include an equation run.",
|
||||
"chapter": "spec/22-conformance.md"
|
||||
},
|
||||
{
|
||||
"id": "correction.schema",
|
||||
"description": "A proposed correction does not match the correction schema.",
|
||||
"chapter": "spec/22-conformance.md"
|
||||
},
|
||||
{
|
||||
"id": "correction.set",
|
||||
"description": "The attribute cannot be set on the kind.",
|
||||
|
||||
+1
-1
@@ -63,7 +63,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 the repository's own validator and, when installed, with `jsonschema` too.
|
||||
`python3 tools/check.py` validates every fixture with `jsonschema` this way, and checks that every schema keeps to the keyword subset.
|
||||
|
||||
## What a schema does not check
|
||||
|
||||
|
||||
@@ -40,6 +40,15 @@
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"role": {
|
||||
"description": "The holder's role in the team that keeps the roster; a key with the role owner may revoke any receipt (spec/08 §8.7).",
|
||||
"enum": [
|
||||
"owner",
|
||||
"editor",
|
||||
"reader",
|
||||
"guest"
|
||||
]
|
||||
},
|
||||
"scheme": {
|
||||
"enum": [
|
||||
"ed25519",
|
||||
@@ -81,6 +90,7 @@
|
||||
"required": [
|
||||
"id",
|
||||
"person",
|
||||
"role",
|
||||
"scheme",
|
||||
"public",
|
||||
"purposes",
|
||||
|
||||
+5
-2
@@ -170,8 +170,11 @@ So every content value that leaves a team's store is keyed.
|
||||
| Where the value goes | `hashing` | Key |
|
||||
| --- | --- | --- |
|
||||
| The team's own store and bundles | `{"scheme": "plain"}` | None. |
|
||||
| An export to another machine of the team | `{"scheme": "hmac-team", "key_id": …}` | The team key. |
|
||||
| A public layer, or any export on a work whose text is withheld | `{"scheme": "hkdf-pages", "context": …, "pages": […]}` | A key derived from the witness's page fingerprints. |
|
||||
| An export to another machine of the team, whatever its text policy | `{"scheme": "hmac-team", "key_id": …}` | The team key. |
|
||||
| A public layer, or an export outside the team on a work whose text is withheld | `{"scheme": "hkdf-pages", "context": …, "pages": […]}` | A key derived from the witness's page fingerprints. |
|
||||
|
||||
The destination chooses the scheme, not the text policy.
|
||||
A checks-only package ([12.5](12-package.md#125-text-policies)) sent between the team's own machines is keyed with the team key, as `hale2026harmonic.checks-only` is; sent to anyone outside the team, it is keyed with the page key.
|
||||
|
||||
A keyed scheme replaces SHA-256 with HMAC-SHA-256 under its key in every content value, including the Merkle construction of `node_hash`, and in `meta_hash`.
|
||||
|
||||
|
||||
+5
-2
@@ -17,6 +17,7 @@ A correction that changes a leaf drops it back: the gate's crop evidence makes i
|
||||
|
||||
A leaf's level is the highest of its recorded `fidelity` and the levels of every valid receipt ([8.6](#86-receipt-status)) that lists it at its current `node_hash`.
|
||||
A receipt at a lower level never lowers a node; only a revocation, a failed spot-check or a correction does.
|
||||
A spot-check's `qa:` receipt ([8.8](#88-spot-checks)) raises the nodes it sampled, except those its `errors` name.
|
||||
A document reports, in `document.json`, the lowest level among its leaves and the count at each level.
|
||||
|
||||
## 8.3 Receipts
|
||||
@@ -88,9 +89,11 @@ A suspended receipt stays suspended; its reviewer reviews the sitting again and
|
||||
## 8.7 Revocations and the roster
|
||||
|
||||
A revocation, `scienting-revocation/1`, names one `receipt`, or a `signer` and a `period` `{from, to}`, with a `reason`, and is signed like a receipt.
|
||||
It is valid when its signature verifies and its signer is the revoked receipt's signer, the signer it names, or an owner of the project.
|
||||
It is valid when its signature verifies and its signer is the revoked receipt's signer, the signer it names, or a key whose `role` in the roster is `owner`.
|
||||
|
||||
The roster, `scienting-roster/1`, lists the keys that may sign: each with an `id`, a `person`, a `scheme`, its `public` key (Base64 for Ed25519), its `purposes` (`sign`, `spot-check`, `export`), `registered_at` and `revoked_at`.
|
||||
The roster, `scienting-roster/1`, lists the keys that may sign: each with an `id`, a `person`, a `role`, a `scheme`, its `public` key (Base64 for Ed25519), its `purposes` (`sign`, `spot-check`, `export`), `registered_at` and `revoked_at`.
|
||||
The `role` is the holder's role in the team that keeps the roster, `owner`, `editor`, `reader` or `guest`, as in a project ([20](20-projects.md)).
|
||||
Because the roster carries it, a package that carries its roster carries everything needed to judge its revocations.
|
||||
A package MAY carry the roster its receipts need as `roster.json`, so its receipts verify without the store.
|
||||
The roster is meant to be the one a store verifies against; see the [rationale](../docs/rationale.md#known-limits).
|
||||
|
||||
|
||||
+14
-10
@@ -54,16 +54,20 @@ Drift ([6.6](06-anchors.md#66-drift)) is computed from `path`, `to_path` and `to
|
||||
|
||||
A correction is refused, naming the rule ([22](22-conformance.md)), when:
|
||||
|
||||
1. a target, parent or sibling is not live, or the root would be deleted, moved, merged or reclassified;
|
||||
2. `find` is empty or does not occur exactly once;
|
||||
3. an `insert` has no crop evidence;
|
||||
4. a digit changes without crop evidence: in a `replace` whose `find` and replacement have different digits, a `delete` of leaves that hold digits, or a `relabel` whose labels have different digits;
|
||||
5. any leaf outside `touched` has different digits afterwards;
|
||||
6. a `split` falls outside the sentence, inside a run other than text, or leaves a part empty;
|
||||
7. a `merge` joins nodes that differ in kind, are not mergeable, or are not adjacent siblings;
|
||||
8. a `move` would put a node inside itself;
|
||||
9. a `reclassify` changes the content shape, or a `relabel` or `set` targets a kind that does not take it; or
|
||||
10. the result breaks a rule of [3](03-nodes.md).
|
||||
1. it does not match the correction schema, `schemas/correction.schema.json`;
|
||||
2. a target, parent or sibling is not live, or the root would be deleted, moved, merged or reclassified;
|
||||
3. `find` is empty or does not occur exactly once;
|
||||
4. an `insert` has no crop evidence, or names a run or offset its target does not have;
|
||||
5. a digit changes without crop evidence: in a `replace` whose `find` and replacement have different digits, a `delete` of leaves that hold digits, or a `relabel` whose labels have different digits;
|
||||
6. any leaf outside `touched` has different digits afterwards;
|
||||
7. a `split` falls outside the sentence, inside a run other than text, or leaves a part empty;
|
||||
8. a `merge` joins nodes that differ in kind, are not mergeable, or are not adjacent siblings;
|
||||
9. a `move` would put a node inside itself;
|
||||
10. a `reclassify` changes the content shape, or a `relabel` or `set` targets a kind that does not take it; or
|
||||
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.
|
||||
|
||||
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`.
|
||||
|
||||
+2
-2
@@ -48,11 +48,11 @@ Every stage that uses a model or an outside service records where it ran ([14](1
|
||||
| Policy | Means |
|
||||
| --- | --- |
|
||||
| `full` | Every leaf keeps its content. |
|
||||
| `checks-only` | Every leaf has `withheld: true` and no `content`; panels drop their figure text; references keep their resolution but no CSL; there are no corrections, suspects or datasets; and the hashing scheme is keyed. |
|
||||
| `checks-only` | Every leaf has `withheld: true` and no `content`; panels drop their figure text; references keep their resolution but no CSL; there are no corrections, suspects or datasets; and the hashing scheme is keyed: `hmac-team` between the team's machines and `hkdf-pages` outside the team ([5.8](05-hashes.md#58-keyed-hashes)). |
|
||||
|
||||
A checks-only package is how a team shares its annotations on a paywalled paper without sharing the paper.
|
||||
Its text lives in the team's private asset store, and only key holders can check its values.
|
||||
`fixtures/packages/hale2026harmonic.checks-only` is such an export of `hale2026harmonic` under the team key in `fixtures/keys/`.
|
||||
`fixtures/packages/hale2026harmonic.checks-only` is such an export of `hale2026harmonic` to another machine of the team, under the team key in `fixtures/keys/`.
|
||||
|
||||
## 12.6 Assets
|
||||
|
||||
|
||||
+6
-1
@@ -49,9 +49,14 @@ It holds the tables of `doc.sql` and `store.sql`, then those of [sql/bundle-sqli
|
||||
| Table | Holds |
|
||||
| --- | --- |
|
||||
| `bundle_meta` | `name` and `value` pairs: at least `format` (`scienting-bundle/1`), `store`, `built_from` (the store revision) and `spec` (the version of these specifications). |
|
||||
| `search` | An FTS5 index with one row per sentence, cell, display equation and figure-text item: the document, the node, the unit, the context (the kind of a sentence's container) and the normalized plain projection. Withheld text is never indexed. |
|
||||
| `search` | An FTS5 index with one row per sentence, cell, equation outside a sentence and figure-text item, in document order: the document, the node, the unit, the context and the text. Withheld text is never indexed. |
|
||||
| `embeddings_meta` | Reserved for embeddings with `sqlite-vec`: the model, its dimensions, and the rights use they were made under. |
|
||||
|
||||
A search row's `unit` is `sentence`, `cell`, `equation` or `figure-text`.
|
||||
For a node, `context` is the kind of its parent, such as `paragraph`, `heading`, `caption` or `row`, and `text` is its normalized plain projection ([3.6](03-nodes.md#36-the-plain-projection)).
|
||||
An equation inside a sentence is searched as part of that sentence, so it has no row of its own.
|
||||
A figure-text item names its panel as the node, its `role` as the context and its normalized `text` as the text.
|
||||
|
||||
At team scale this is all the search a team needs.
|
||||
A team can download its bundle and use it offline.
|
||||
Because a bundle holds text, it is served only under the rights of [14](14-rights.md), checked again whenever text is served.
|
||||
@@ -31,6 +31,9 @@ Each line of `annotations.jsonl` is a W3C `Annotation`:
|
||||
| `status` | `proposed`, `confirmed`, `rejected` or `withdrawn`. A model's annotation is `proposed` until a person confirms it. |
|
||||
| `receipt` | For a confirmed annotation, the receipt that covers it. |
|
||||
|
||||
The `TextQuoteSelector`, when present, repeats the anchor quote's `exact`, `prefix` and `suffix`, with an empty string for a `prefix` or `suffix` the quote lacks.
|
||||
It is present only when the anchor's quote is plain text; an anchor whose quote is hashed or keyed ([6.3](06-anchors.md#63-keyed-anchors)) has none, so the fallback never shows text the anchor withholds.
|
||||
|
||||
An annotation's content hash is SHA-256 of the canonical JSON of the annotation without `status`, `receipt` and `modified`.
|
||||
A confirmed annotation is covered by its confirmer's receipt, which lists `["annotation", <record id>, <content hash>]`.
|
||||
Replies are annotations that target annotations, so discussions are threads anchored to sentences.
|
||||
|
||||
+3
-2
@@ -10,7 +10,8 @@ A project, `scienting-project/1`, is the unit a team works in.
|
||||
| `paths` | Reading paths: ordered steps through documents, nodes, claims and layers, for onboarding a student or preparing a reading group. |
|
||||
| `layers`, `datasets`, `views`, `drafts` | What the project holds. |
|
||||
| `claims` | The claim namespace or bundle it uses, as the `lode` setting. |
|
||||
| `settings` | The digit rule for targeted F3 ([8.10](08-fidelity.md#810-targeted-f3)), the drift threshold ([6.6](06-anchors.md#66-drift)), the spot-check rate and minimum ([8.8](08-fidelity.md#88-spot-checks)), what happens when a right is unknown (`refuse`, the default, or `warn`), and the stage requirements in use. |
|
||||
| `settings` | The digit rule for targeted F3 ([8.10](08-fidelity.md#810-targeted-f3)), the drift threshold ([6.6](06-anchors.md#66-drift)), the spot-check rate and minimum ([8.8](08-fidelity.md#88-spot-checks)), what happens when a right is unknown (`refuse`, the default, or `warn`), and the stage requirements in use, as the `id` of their stage file ([14](14-rights.md)). |
|
||||
|
||||
Projects are small on purpose: everything about them is designed for hundreds of documents, not millions.
|
||||
Owners sign policies ([14.2](14-rights.md#142-effective-uses)) and may revoke receipts ([8.7](08-fidelity.md#87-revocations-and-the-roster)).
|
||||
Owners sign policies ([14.2](14-rights.md#142-effective-uses)).
|
||||
Who may revoke receipts follows the roster instead: a key whose `role` in the roster is `owner` ([8.7](08-fidelity.md#87-revocations-and-the-roster)), so a project's owners hold keys with that role.
|
||||
@@ -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. | Accepts each, and recomputes every value it contains. |
|
||||
| `fixtures/records/` | A valid record of every other format. | 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 `tools/load.py --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. |
|
||||
|
||||
|
||||
+3
-2
@@ -28,9 +28,10 @@ psql -d lab -f sql/doc.sql && psql -d lab -f sql/store.sql
|
||||
dolt sql < sql/doc.sql && dolt sql < sql/store.sql
|
||||
```
|
||||
|
||||
[CHECK: the sqlite3, psql and dolt commands were not run here; the PostgreSQL database `lab` and the Dolt repository must already exist.]
|
||||
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 with nothing installed but Python 3, run `python3 tools/load.py --sqlite lab.db PACKAGE…`.
|
||||
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.
|
||||
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/`.
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@ CREATE TABLE bundle_meta (
|
||||
PRIMARY KEY (name)
|
||||
);
|
||||
|
||||
-- One row per searchable unit: a sentence, an equation outside a sentence, a cell, or a figure-text item.
|
||||
-- `context` is the kind of the sentence's container: paragraph, heading, caption, footnote and so on.
|
||||
-- `text` is the normalized plain projection; withheld text is never indexed.
|
||||
-- One row per searchable unit: a sentence, a cell, an equation outside a sentence, or a figure-text item.
|
||||
-- `unit` is sentence, cell, equation or figure-text. `context` is the kind of the node's parent:
|
||||
-- paragraph, heading, caption, footnote, row and so on. A figure-text item names its panel as `node`
|
||||
-- and its role as `context`. `text` is the normalized plain projection; withheld text is never indexed.
|
||||
CREATE VIRTUAL TABLE search USING fts5(
|
||||
doc_key UNINDEXED,
|
||||
node UNINDEXED,
|
||||
|
||||
@@ -7,6 +7,7 @@ CREATE TABLE roster_keys (
|
||||
store VARCHAR(128) NOT NULL,
|
||||
id VARCHAR(128) NOT NULL,
|
||||
person TEXT NOT NULL,
|
||||
role VARCHAR(16) NOT NULL CHECK (role IN ('owner', 'editor', 'reader', 'guest')),
|
||||
scheme VARCHAR(16) NOT NULL CHECK (scheme IN ('ed25519', 'ssh', 'gpg', 'sigstore')),
|
||||
public_key TEXT NOT NULL,
|
||||
purposes TEXT NOT NULL,
|
||||
|
||||
+19
-8
@@ -6,8 +6,8 @@ Implementers get the commands and the traps.
|
||||
|
||||
These programs 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, and one loads packages into SQLite, a database stored in a single file.
|
||||
They use only the Python standard library (Python 3.10 or later [CHECK: stated in README.md; pixi.toml pins 3.14 and I could only run on 3.13]) and are licensed Apache-2.0.
|
||||
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 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 vector is a test case: an input and the exact output every implementation must compute (see [the vectors](../vectors/README.md)).
|
||||
@@ -17,14 +17,16 @@ A fixture is an example file or package under `fixtures/`.
|
||||
| --- | --- |
|
||||
| `check.py` | Checks everything: schemas, records, packages, the broken packages, every vector, a byte-for-byte rebuild, the SQL (a database query language) round trip 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. |
|
||||
| `load.py` | Loads packages into a SQLite database with the tables of `sql/`: `python3 tools/load.py --sqlite lab.db PACKAGE [PACKAGE ...]`. |
|
||||
| `validate.py` | Validates packages: `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, 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)). |
|
||||
| `oracle/` | A compact implementation of every rule. `build.py` uses it to compute expected values, and `check.py` uses it to recompute them. |
|
||||
|
||||
## When to run what
|
||||
|
||||
Each program is also a pixi task.
|
||||
Pixi sets up a pinned Python and the other packages the repository uses.
|
||||
The commands `pixi run check`, `pixi run quick`, `pixi run build` and `pixi run load` then work from anywhere in the repository [CHECK: pixi is not installed here, so the pixi forms were not run]. `pixi run check` lints the Markdown first.
|
||||
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:
|
||||
|
||||
@@ -32,12 +34,16 @@ Run the programs directly from the repository root if you do not use pixi:
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
`build.py` has no help option and ignores flags it does not know, so it rebuilds `fixtures/` and `vectors/` in place unless you pass `--out`.
|
||||
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`.
|
||||
|
||||
Run `pixi run check` before every commit.
|
||||
Run `pixi run build` after you change a fixture source, a rule in `oracle/` or a case in `build.py`, and commit the regenerated files with the change.
|
||||
@@ -49,9 +55,14 @@ An oracle is a reference computation: a slow, plain program whose answers count
|
||||
It is not a production reader or writer.
|
||||
The oracle exists so that no expected value in this repository is typed by hand.
|
||||
|
||||
It favors clarity over speed.
|
||||
Its Ed25519 (a digital signature scheme) is the slow reference code of RFC 8032.
|
||||
Its validator for JSON Schema (a format that describes the shape of JSON data) supports exactly the keyword subset the schemas use and refuses any schema that uses more.
|
||||
It favors clarity, and leaves the cryptography and schema validation to established packages.
|
||||
Ed25519 (a digital signature scheme) and HKDF come from `cryptography`, and JSON Schema (a format that describes the shape of JSON data) validation from `jsonschema`.
|
||||
The oracle still refuses any schema that uses a keyword outside the subset the schemas are restricted to.
|
||||
Canonical JSON goes through Python's built-in JSON encoder, and falls back to an exact RFC 8785 serializer for the values that encoder writes differently: floating-point numbers, integers beyond 2^53 and some non-ASCII member names.
|
||||
|
||||
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.
|
||||
|
||||
Do not build on it as a library.
|
||||
Implementations follow the [specification](../spec/README.md) and test themselves against the [vectors](../vectors/README.md).
|
||||
+33
-21
@@ -43,10 +43,10 @@ def secret(name):
|
||||
|
||||
|
||||
KEYS = {
|
||||
"ana": {"name": "Ana Example", "purposes": ["sign"]},
|
||||
"ben": {"name": "Ben Example", "purposes": ["sign"]},
|
||||
"ben-spot": {"name": "Ben Example", "purposes": ["spot-check"]},
|
||||
"cara": {"name": "Cara Example", "purposes": ["sign"]},
|
||||
"ana": {"name": "Ana Example", "role": "owner", "purposes": ["sign"]},
|
||||
"ben": {"name": "Ben Example", "role": "editor", "purposes": ["sign"]},
|
||||
"ben-spot": {"name": "Ben Example", "role": "editor", "purposes": ["spot-check"]},
|
||||
"cara": {"name": "Cara Example", "role": "reader", "purposes": ["sign"]},
|
||||
}
|
||||
TEAM_SECRET = hashlib.sha256(b"manuspec fixture team secret: smithlab").digest()
|
||||
|
||||
@@ -54,7 +54,7 @@ TEAM_SECRET = hashlib.sha256(b"manuspec fixture team secret: smithlab").digest()
|
||||
def roster():
|
||||
keys = []
|
||||
for kid, k in KEYS.items():
|
||||
keys.append({"id": kid, "person": {"name": k["name"]}, "scheme": "ed25519",
|
||||
keys.append({"id": kid, "person": {"name": k["name"]}, "role": k["role"], "scheme": "ed25519",
|
||||
"public": base64.b64encode(crypto.ed25519_public(secret(kid))).decode(),
|
||||
"purposes": k["purposes"], "registered_at": "2026-09-01T00:00:00Z", "revoked_at": None})
|
||||
return {"format": "scienting-roster/1", "store": STORE, "keys": keys}
|
||||
@@ -118,9 +118,9 @@ def _math_node(kind, e):
|
||||
|
||||
|
||||
def expand(s, page=None):
|
||||
page = s.get("page", page)
|
||||
if isinstance(s, str):
|
||||
s = {"s": [s]}
|
||||
page = s.get("page", page)
|
||||
if "s" in s:
|
||||
kids = []
|
||||
n = {"kind": "sentence", "content": runs_of(s["s"], kids), "children": kids}
|
||||
@@ -565,7 +565,6 @@ def build_hale(out):
|
||||
anchors = []
|
||||
for rk, bond, pre in rows:
|
||||
for st, (kc, rc) in (("A", ("_ak", "_ar")), ("B", ("_bk", "_br"))):
|
||||
kname = pre + kc.replace("_a", "_a").replace("_b", "_b")
|
||||
kid, rid = names.ids[pre + kc], names.ids[pre + rc]
|
||||
kv, rv = norm(hashing.plain(final_doc, kid)), norm(hashing.plain(final_doc, rid))
|
||||
row_key = rk + "-" + st.lower()
|
||||
@@ -1007,6 +1006,8 @@ def corrections_vectors():
|
||||
("insert-text", {"op": "insert", "target": nm["@d"], "run": 0, "offset": 8, "text": "only ", "evidence": crop}),
|
||||
("insert-text-no-crop", {"op": "insert", "target": nm["@d"], "run": 0, "offset": 8, "text": "only ",
|
||||
"evidence": person}),
|
||||
("insert-text-bad-run", {"op": "insert", "target": nm["@d"], "run": 7, "offset": 0, "text": "x",
|
||||
"evidence": crop}),
|
||||
("insert-node", {"op": "insert", "parent": nm["@p2"], "place_after": nm["@c"],
|
||||
"node": {"kind": "sentence", "content": [{"kind": "text", "text": "A dropped sentence."}]},
|
||||
"evidence": crop}),
|
||||
@@ -1050,7 +1051,6 @@ def resolve_vectors(hale):
|
||||
nid = names.ids[a["node"]]
|
||||
if a.get("container_of"):
|
||||
nid = d0.nodes[nid]["parent"]
|
||||
cid = cid
|
||||
span = None
|
||||
if "quote" in a:
|
||||
t = hashing.plain(d0, nid)
|
||||
@@ -1134,6 +1134,12 @@ def records(out, hale):
|
||||
return AN.make_anchor(doc, vals, nid, rev, span=span, store=STORE)
|
||||
|
||||
wr(out, "roster.json", ROSTER)
|
||||
# The doubtful reading behind the first correction, as the converter raised it before anyone answered it.
|
||||
sus = {"format": "scienting-suspect/1", "kind": "number", "node": I["@s4"], "a": "10-3", "b": "10\u22123",
|
||||
"crop": crop_sha(I["@s4"]),
|
||||
"note": "The text layer reads a hyphen on the line; the page image shows a raised minus three."}
|
||||
sus = dict(sus, id=record_id(sha256_hex(jcs.dumpb(sus))))
|
||||
wr(out, "suspect.json", sus)
|
||||
wr(out, "anchor-span.json", anchor("@s3", "first test case"))
|
||||
wr(out, "anchor-cell.json", anchor("@cc_ak"))
|
||||
wr(out, "anchor-paragraph.json", AN.make_anchor(doc, vals, doc.nodes[I["@s3"]]["parent"], rev, store=STORE))
|
||||
@@ -1393,9 +1399,8 @@ def records(out, hale):
|
||||
# --- invalid packages ---------------------------------------------------------------
|
||||
|
||||
|
||||
def invalid(out):
|
||||
def invalid(out, tiny):
|
||||
base = os.path.join(out, "fixtures", "invalid")
|
||||
tiny = tiny_package(None)
|
||||
files = tiny["files"]
|
||||
nodes = [json.loads(x) for x in files["nodes.jsonl"].decode().splitlines()]
|
||||
nm = tiny["names"].ids
|
||||
@@ -1418,7 +1423,6 @@ def invalid(out):
|
||||
with open(os.path.join(base, name, "expect.json"), "wb") as fh:
|
||||
fh.write((jcs.dumps({"rule": rule, "note": note}) + "\n").encode())
|
||||
|
||||
write(os.path.join(out, "fixtures", "packages", "tiny2026example"), files)
|
||||
case("hash-mismatch", "doc.hash.node-hash", {"nodes.jsonl": edit_nodes(lambda ns: by(ns, "@a").update(node_hash="0" * 64))},
|
||||
"A node_hash that does not recompute.")
|
||||
case("text-edited", "doc.hash.text-hash", {"nodes.jsonl": edit_nodes(
|
||||
@@ -1531,16 +1535,24 @@ def main(out):
|
||||
tiny = tiny_package(os.path.join(out, "fixtures", "packages", "tiny2026example"))
|
||||
records(out, hale)
|
||||
vectors(out, hale, tiny)
|
||||
invalid(out)
|
||||
invalid(out, tiny)
|
||||
|
||||
|
||||
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.")
|
||||
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)
|
||||
if out != ROOT:
|
||||
for sub in ("registry", "fixtures/src"):
|
||||
dst = os.path.join(out, sub)
|
||||
if not os.path.exists(dst):
|
||||
shutil.copytree(os.path.join(ROOT, sub), dst)
|
||||
main(out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
out = ROOT
|
||||
if "--out" in sys.argv:
|
||||
out = sys.argv[sys.argv.index("--out") + 1]
|
||||
if out != ROOT:
|
||||
for sub in ("registry", "fixtures/src"):
|
||||
dst = os.path.join(out, sub)
|
||||
if not os.path.exists(dst):
|
||||
shutil.copytree(os.path.join(ROOT, sub), dst)
|
||||
main(out)
|
||||
cli()
|
||||
+237
-49
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check the whole repository. Exit status 0 means every check passed.
|
||||
|
||||
Non-normative tooling; standard library only (it uses `jsonschema` as a second validator when installed).
|
||||
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)
|
||||
"""
|
||||
|
||||
@@ -13,6 +13,7 @@ import re
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
from fractions import Fraction
|
||||
|
||||
@@ -27,6 +28,8 @@ from tools.oracle import package as PK # noqa: E402
|
||||
from tools.oracle.model import Doc # noqa: E402
|
||||
from tools.oracle.notation import fold_math # noqa: E402
|
||||
from tools.oracle.schema import Store, validate as sv # noqa: E402
|
||||
from jsonschema import Draft202012Validator # noqa: E402
|
||||
from jsonschema.exceptions import SchemaError # noqa: E402
|
||||
from tools.oracle.text import cp_to_utf16, letter_first, mint_node_id, norm, record_id, sha256_hex # noqa: E402
|
||||
from tools.oracle.tree import Refusal # noqa: E402
|
||||
|
||||
@@ -69,35 +72,20 @@ def check_schemas():
|
||||
for f in SCHEMA_FILES:
|
||||
s = STORE.load(f)
|
||||
ok("schemas", s.get("$id", "").endswith("/" + f), "%s: $id must end with its file name" % f)
|
||||
try:
|
||||
Draft202012Validator.check_schema(s)
|
||||
meta = True
|
||||
except SchemaError:
|
||||
meta = False
|
||||
ok("schemas", meta, "%s is not a valid draft 2020-12 schema" % f)
|
||||
for f in js(p("registry", "formats.json"))["formats"]:
|
||||
ok("schemas", os.path.exists(p("schemas", f["schema"])) or os.path.exists(p(f["schema"])),
|
||||
"%s names a missing schema %s" % (f["format"], f["schema"]))
|
||||
try:
|
||||
import jsonschema # noqa: F401
|
||||
from referencing import Registry, Resource
|
||||
reg = Registry().with_resources(
|
||||
[(STORE.load(f)["$id"], Resource.from_contents(STORE.load(f))) for f in SCHEMA_FILES])
|
||||
COUNTS["second-validator"] = 0
|
||||
globals()["SECOND"] = (jsonschema, reg)
|
||||
except ImportError:
|
||||
globals()["SECOND"] = None
|
||||
|
||||
|
||||
def second(name, rec):
|
||||
if not SECOND:
|
||||
return
|
||||
jsonschema, reg = SECOND
|
||||
v = jsonschema.Draft202012Validator(STORE.load(name), registry=reg)
|
||||
errs = list(v.iter_errors(rec))
|
||||
COUNTS["second-validator"] += 1
|
||||
if errs:
|
||||
FAILS.append("second-validator: %s: %s" % (name, errs[0].message))
|
||||
|
||||
|
||||
def valid(section, name, rec, where):
|
||||
e = schema_errors(name, rec)
|
||||
ok(section, not e, "%s: %s" % (where, e[:3]))
|
||||
second(name, rec)
|
||||
|
||||
|
||||
def check_registry():
|
||||
@@ -106,6 +94,13 @@ def check_registry():
|
||||
for rule in js(p("registry", "stamps", "scienting-stamps-1.json"))["rules"]:
|
||||
re.compile(rule["pattern"])
|
||||
rules = {r["id"] for r in js(p("registry", "rules.json"))["rules"]}
|
||||
for f in sorted(os.listdir(p("tools", "oracle"))):
|
||||
if f.endswith(".py"):
|
||||
for rule in re.findall(r'(?:Refusal|rep\.add)\(\s*"([a-z][a-z0-9.-]+)"', open(p("tools", "oracle", f)).read()):
|
||||
if not rule.endswith("."):
|
||||
ok("registry", rule in rules, "tools/oracle/%s raises %s, which is not in rules.json" % (f, rule))
|
||||
for v in ("text-hash", "match-hash", "align-key", "own-hash", "node-hash"):
|
||||
ok("registry", "doc.hash." + v in rules, "doc.hash.%s is not in rules.json" % v)
|
||||
for d in sorted(os.listdir(p("fixtures", "invalid"))):
|
||||
ok("registry", js(p("fixtures", "invalid", d, "expect.json"))["rule"] in rules, "%s: rule is not in rules.json" % d)
|
||||
|
||||
@@ -133,13 +128,28 @@ def check_records():
|
||||
else:
|
||||
sch = FORMAT_SCHEMA[rec["format"]]
|
||||
valid("records", sch, rec, rel)
|
||||
if rec.get("format") == "scienting-suspect/1":
|
||||
ok("records", rec["id"] == record_id(sha256_hex(jcs.dumpb({k: v for k, v in rec.items() if k != "id"}))),
|
||||
"%s: id does not derive" % rel)
|
||||
if rec.get("signature"):
|
||||
ok("records", RC.verify_signature(rec, roster), "%s: signature does not verify" % rel)
|
||||
ok("records", rec["id"] == record_id(RC.record_hash(rec)), "%s: id does not derive" % rel)
|
||||
# A project's settings.stages holds the id of a stage file.
|
||||
stage_ids = {js(p("registry", "stages", f))["id"] for f in os.listdir(p("registry", "stages")) if f.endswith(".json")}
|
||||
stages = js(os.path.join(R, "project.json")).get("settings", {}).get("stages")
|
||||
ok("records", stages is None or stages in stage_ids, "project settings.stages names no stage file: %s" % stages)
|
||||
# Confirmed annotations are covered by the layer receipt at their content hash.
|
||||
lr = js(os.path.join(R, "layer", "receipt.json"))
|
||||
items = {(k, i): h for k, i, h in lr["records"]}
|
||||
for a in jl(os.path.join(R, "layer", "annotations.jsonl")):
|
||||
sel = a["target"].get("selector", [])
|
||||
anchors = [x["anchor"] for x in sel if x.get("type") == "AnchorSelector"]
|
||||
for x in sel:
|
||||
if x.get("type") == "TextQuoteSelector":
|
||||
q = anchors[0].get("quote", {}) if anchors else {}
|
||||
same = all(x.get(k, "") == q.get(k, "") for k in ("exact", "prefix", "suffix"))
|
||||
ok("records", "exact" in q and same and set(x) <= {"type", "exact", "prefix", "suffix"},
|
||||
"a TextQuoteSelector must repeat the anchor's plain quote: " + a["id"])
|
||||
body = {k: v for k, v in a.items() if k not in ("status", "receipt", "modified")}
|
||||
ok("records", a["id"].rsplit("/", 1)[1] == record_id(sha256_hex(jcs.dumpb(
|
||||
{k: v for k, v in body.items() if k != "id"}))), "annotation id does not derive: " + a["id"])
|
||||
@@ -150,12 +160,7 @@ def check_records():
|
||||
|
||||
|
||||
def state_of(pkg):
|
||||
nodes = jl(os.path.join(pkg, "nodes.jsonl"))
|
||||
key = js(os.path.join(pkg, "document.json"))["key"]
|
||||
ids = jl(os.path.join(pkg, "ids.jsonl")) if os.path.exists(os.path.join(pkg, "ids.jsonl")) else []
|
||||
cors = jl(os.path.join(pkg, "corrections.jsonl")) if os.path.exists(os.path.join(pkg, "corrections.jsonl")) else []
|
||||
strip = ("text_hash", "match_hash", "align_key", "own_hash", "node_hash")
|
||||
return CO.State(key, [{k: v for k, v in n.items() if k not in strip} for n in nodes], ids, cors)
|
||||
return PK.read_state(pkg)
|
||||
|
||||
|
||||
def check_packages():
|
||||
@@ -192,6 +197,142 @@ def check_packages():
|
||||
valid("packages", "mathml.schema.json", m, "derived/mathml.jsonl")
|
||||
|
||||
|
||||
def check_owner_revocation():
|
||||
"""A key the roster makes an owner may revoke anyone's receipt (spec/08 §8.7); any other key may not.
|
||||
|
||||
No fixture has such a revocation, so this signs one: Ben revokes a receipt of Ana's, once with the
|
||||
roster role owner and once as the editor he is.
|
||||
"""
|
||||
hp = p("fixtures", "packages", "hale2026harmonic")
|
||||
revoker = "ben"
|
||||
secret = bytes.fromhex(open(p("fixtures", "keys", revoker + ".ed25519.hex")).read().strip())
|
||||
roster_rec = js(os.path.join(hp, "roster.json"))
|
||||
as_owner = dict(roster_rec, keys=[dict(k, role="owner") if k["id"] == revoker else k for k in roster_rec["keys"]])
|
||||
owner_roster = {k["id"]: k for k in as_owner["keys"]}
|
||||
nodes = jl(os.path.join(hp, "nodes.jsonl"))
|
||||
vals = {n["id"]: n for n in nodes}
|
||||
document = js(os.path.join(hp, "document.json"))
|
||||
recs, fidelity = jl(os.path.join(hp, "receipts.jsonl")), None
|
||||
for target in recs:
|
||||
if target["format"] != "scienting-receipt/1" or target["by"] == revoker:
|
||||
continue
|
||||
rev = RC.seal({"format": "scienting-revocation/1", "receipt": target["id"], "reason": "Owner check.",
|
||||
"by": revoker, "at": "2026-10-13T00:00:00Z"}, secret, revoker)
|
||||
trial = sorted(recs + [rev], key=lambda r: (r["at"], r["id"]))
|
||||
fidelity = RC.distribution(RC.effective_levels(nodes, vals, trial, owner_roster))
|
||||
if fidelity != document["fidelity"]:
|
||||
recs = trial
|
||||
break
|
||||
ok("packages", fidelity != document["fidelity"], "no receipt of the hale package matters to its levels")
|
||||
document["fidelity"] = fidelity
|
||||
manifest = js(os.path.join(hp, "MANIFEST.json"))
|
||||
files, absent = {}, [f for f in manifest["files"] if f.get("present", True) is False]
|
||||
for f in manifest["files"]:
|
||||
if f.get("present", True):
|
||||
with open(os.path.join(hp, f["path"]), "rb") as fh:
|
||||
files[f["path"]] = fh.read()
|
||||
files["receipts.jsonl"] = PK.jsonl(recs)
|
||||
files["document.json"] = PK.line(document).encode("utf-8")
|
||||
for roster, passes in ((as_owner, True), (roster_rec, False)):
|
||||
files["roster.json"] = PK.line(roster).encode("utf-8")
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
PK.write(tmp, files, absent)
|
||||
rules = {e["rule"] for e in PK.validate(tmp, STORE).errors}
|
||||
role = next(k["role"] for k in roster["keys"] if k["id"] == revoker)
|
||||
ok("packages", ("doc.fidelity.report" not in rules) == passes and rules <= {"doc.fidelity.report"},
|
||||
"a revocation by a key with the role %s: %s" % (role, sorted(rules)))
|
||||
|
||||
|
||||
def check_unsafe_paths():
|
||||
"""Loading and writing refuse manifest paths that leave the package, even without a prior validation."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
pkg = os.path.join(tmp, "pkg")
|
||||
shutil.copytree(p("fixtures", "packages", "tiny2026example"), pkg)
|
||||
manifest = js(os.path.join(pkg, "MANIFEST.json"))
|
||||
for bad in ("../outside", "/etc/outside", "assets/../../outside"):
|
||||
m = dict(manifest, files=[{"path": bad, "sha256": "0" * 64, "size": 1}] + manifest["files"])
|
||||
with open(os.path.join(pkg, "MANIFEST.json"), "w", encoding="utf-8") as f:
|
||||
f.write(PK.line(m))
|
||||
conn = sqlite3.connect(":memory:")
|
||||
db.create(conn, p("sql"))
|
||||
try:
|
||||
db.load(conn, pkg, os.path.join(tmp, "assets"))
|
||||
rule = None
|
||||
except Refusal as e:
|
||||
rule = e.rule
|
||||
ok("packages", rule == "package.path.unsafe", "load accepted the manifest path %r" % bad)
|
||||
try:
|
||||
PK.write(os.path.join(tmp, "out"), {bad: b"x", "document.json": b"{}"})
|
||||
rule = None
|
||||
except Refusal as e:
|
||||
rule = e.rule
|
||||
ok("packages", rule == "package.path.unsafe", "write accepted the path %r" % bad)
|
||||
ok("packages", not os.path.exists(os.path.join(tmp, "outside")), "a file was written outside the package")
|
||||
|
||||
|
||||
def reference_values(doc, hasher=hashing.PLAIN):
|
||||
"""The five values the slow way: every node projected from its whole subtree, as spec/05 §5.1 reads.
|
||||
|
||||
The oracle's hashing.Index joins its children's cached projections and rehashes only what a correction
|
||||
changed; this independent computation must always agree with it.
|
||||
"""
|
||||
values = {}
|
||||
for nid in reversed(doc.preorder()):
|
||||
st, _ = hashing.strict(doc, nid)
|
||||
own = hasher(jcs.dumpb(hashing.own_content(doc, nid, lambda x: values[x]["text_hash"])))
|
||||
values[nid] = {"text_hash": hasher(norm(hashing.plain(doc, nid))), "match_hash": hasher(st),
|
||||
"align_key": hasher(hashing.lossy_from_strict(st)), "own_hash": own,
|
||||
"node_hash": hasher(jcs.dumpb({"own": own, "children": [
|
||||
values[c]["node_hash"] for c in doc.kids(nid)]}))}
|
||||
return values
|
||||
|
||||
|
||||
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
|
||||
team = hashing.Hasher(crypto.team_key(bytes.fromhex(
|
||||
open(p("fixtures", "keys", "smithlab-team-secret.hex")).read().strip())))
|
||||
Names.registry.clear()
|
||||
# Sorted, the main example comes first, so the draft's references to its sentences resolve.
|
||||
for f in sorted(os.listdir(p("fixtures", "src"))):
|
||||
if not f.endswith(".json"):
|
||||
continue
|
||||
_, _, _, _, states, _ = build_document(js(p("fixtures", "src", f)))
|
||||
keyed = None
|
||||
for rev in sorted(states):
|
||||
st = states[rev]
|
||||
ok("packages", st.index().values == reference_values(st.doc()),
|
||||
"%s rev %d: incremental values differ from the full computation" % (f, rev))
|
||||
# The keyed values, carried forward from revision 0 by updates alone.
|
||||
keyed = hashing.Index.build(st.doc(), team) if keyed is None else keyed.update(st.doc())
|
||||
ok("packages", keyed.values == reference_values(st.doc(), team),
|
||||
"%s rev %d: incremental keyed values differ from the full computation" % (f, rev))
|
||||
Names.registry.clear()
|
||||
|
||||
|
||||
def check_gate():
|
||||
"""The gate runs on a proposal against a package's current state and leaves that state unchanged."""
|
||||
st = state_of(p("fixtures", "packages", "hale2026harmonic"))
|
||||
before = json.dumps(st.nodes, sort_keys=True)
|
||||
target = next(n for n in st.nodes if n["kind"] == "sentence" and "Each bond" in hashing.plain(st.doc(), n["id"]))
|
||||
base = {"format": "scienting-correction/1", "evidence": [{"kind": "person", "by": "ana"}], "author": {"key": "ana"},
|
||||
"reason": "Gate check.", "at": "2026-10-14T09:00:00Z", "status": "proposed"}
|
||||
st1, rec = CO.gate(st, dict(base, op="replace", target=target["id"], find="Each bond", replace="Every bond"), STORE)
|
||||
ok("packages", rec["index"] == st.rev + 1 and st1.rev == st.rev + 1, "the gate's record has the next index")
|
||||
ok("packages", json.dumps(st.nodes, sort_keys=True) == before, "the gate changed the state it was given")
|
||||
valid("packages", "correction.schema.json", rec, "gate record")
|
||||
for bad, rule in (({"op": "replace", "target": target["id"]}, "correction.schema"),
|
||||
(dict(base, op="replace", target=target["id"], find="0.01", replace="0.02"), "correction.digits"),
|
||||
(dict(base, op="insert", target=target["id"], run=9, offset=0, text="x",
|
||||
evidence=[{"kind": "crop", "sha256": "0" * 64}]), "correction.insert")):
|
||||
try:
|
||||
CO.gate(st, bad, STORE)
|
||||
got = None
|
||||
except Refusal as e:
|
||||
got = e.rule
|
||||
ok("packages", got == rule, "the gate gave %s for a proposal that breaks %s" % (got, rule))
|
||||
|
||||
|
||||
def check_invalid():
|
||||
base = p("fixtures", "invalid")
|
||||
for d in sorted(os.listdir(base)):
|
||||
@@ -299,6 +440,8 @@ def check_vectors():
|
||||
try:
|
||||
st1, rec = CO.apply(st, c["correction"])
|
||||
v1 = hashing.compute(st1.doc())
|
||||
ok("vectors", st1.index().values == reference_values(st1.doc()),
|
||||
"correction %s: incremental values differ from the full computation" % c["id"])
|
||||
got = {"record": rec, "nodes": [dict(n, **v1[n["id"]]) for n in st1.nodes], "ids": st1.ids}
|
||||
except Refusal as e:
|
||||
got = {"refusal": e.rule}
|
||||
@@ -330,7 +473,8 @@ def check_determinism():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
subprocess.run([sys.executable, p("tools", "build.py"), "--out", tmp], check=True)
|
||||
for sub in ("fixtures", "vectors"):
|
||||
cmp = filecmp.dircmp(p(sub), os.path.join(tmp, sub))
|
||||
# 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"])
|
||||
diffs = []
|
||||
|
||||
def walk(c, prefix):
|
||||
@@ -384,8 +528,7 @@ def check_sql():
|
||||
ok("sql", "FOREIGN KEY" not in ddl and "REFERENCES" not in ddl, "no foreign keys")
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
conn = sqlite3.connect(os.path.join(tmp, "bundle.db"))
|
||||
db.create(conn, p("sql"))
|
||||
conn.executescript(open(p("sql", "bundle-sqlite.sql"), encoding="utf-8").read())
|
||||
db.create_bundle(conn, p("sql"))
|
||||
assets = os.path.join(tmp, "assets")
|
||||
pk = p("fixtures", "packages")
|
||||
for d in sorted(os.listdir(pk)):
|
||||
@@ -393,29 +536,18 @@ def check_sql():
|
||||
if key is None:
|
||||
# A second load of the same key goes to its own database.
|
||||
c2 = sqlite3.connect(":memory:")
|
||||
db.create(c2, p("sql"))
|
||||
db.create_bundle(c2, p("sql"))
|
||||
key = db.load(c2, os.path.join(pk, d), assets)
|
||||
ok("sql", db.index(c2, key) == 0, "%s: withheld text was indexed" % d)
|
||||
h = db.unload(c2, key, os.path.join(tmp, "out", d), assets)
|
||||
else:
|
||||
db.index(conn, key)
|
||||
h = db.unload(conn, key, os.path.join(tmp, "out", d), assets)
|
||||
cmp = filecmp.dircmp(os.path.join(pk, d), os.path.join(tmp, "out", d))
|
||||
same = _same_tree(os.path.join(pk, d), os.path.join(tmp, "out", d))
|
||||
ok("sql", same, "%s: unload is not byte-identical" % d)
|
||||
with open(os.path.join(pk, d, "MANIFEST.json"), "rb") as f:
|
||||
ok("sql", sha256_hex(f.read()) == h, "%s: package hash differs after unload" % d)
|
||||
# The search index over sentences, captions, headings and cells.
|
||||
for doc_key, nid, kind, parent in conn.execute(
|
||||
"SELECT n.doc_key, n.id, n.kind, p.kind FROM nodes n JOIN nodes p ON p.doc_key = n.doc_key AND p.id = n.parent "
|
||||
"WHERE n.kind IN ('sentence', 'cell', 'equation') AND n.withheld IS NULL").fetchall():
|
||||
st = None
|
||||
rows = conn.execute("SELECT doc_key, id, kind, content FROM nodes WHERE kind IN ('sentence','cell')").fetchall()
|
||||
for doc_key, nid, kind, content in rows:
|
||||
runs = json.loads(content)
|
||||
text = "".join(r.get("text", "") if r["kind"] != "math" else "$" + r["source"] + "$" for r in runs
|
||||
if r["kind"] != "equation")
|
||||
conn.execute("INSERT INTO search VALUES (?,?,?,?,?)", (doc_key, nid, kind, "", norm(text)))
|
||||
hits = conn.execute("SELECT node FROM search WHERE search MATCH 'scanned'").fetchall()
|
||||
ok("sql", len(hits) == 1, "full-text search finds the merged sentence")
|
||||
check_bundle(conn)
|
||||
# Store records satisfy the store tables' CHECK constraints.
|
||||
R = p("fixtures", "records")
|
||||
lay = js(os.path.join(R, "layer", "layer.json"))
|
||||
@@ -433,8 +565,8 @@ def check_sql():
|
||||
for m in prj["members"]:
|
||||
conn.execute("INSERT INTO members VALUES (?,?,?,?)", (prj["key"], m["person"], m["role"], jcs.dumps(m["keys"])))
|
||||
for k in js(os.path.join(R, "roster.json"))["keys"]:
|
||||
conn.execute("INSERT INTO roster_keys VALUES (?,?,?,?,?,?,?,?)",
|
||||
("smithlab", k["id"], jcs.dumps(k["person"]), k["scheme"], k["public"],
|
||||
conn.execute("INSERT INTO roster_keys VALUES (?,?,?,?,?,?,?,?,?)",
|
||||
("smithlab", k["id"], jcs.dumps(k["person"]), k["role"], k["scheme"], k["public"],
|
||||
jcs.dumps(k["purposes"]), k["registered_at"], k["revoked_at"]))
|
||||
dg = js(os.path.join(R, "digitization.json"))
|
||||
conn.execute("INSERT INTO digitizations VALUES (?,?,?,?,?,?,?,?)",
|
||||
@@ -444,6 +576,51 @@ def check_sql():
|
||||
ok("sql", True, "store records loaded")
|
||||
|
||||
|
||||
def check_bundle(conn):
|
||||
"""The bundle's search index and metadata (spec/13 §13.5), built by the oracle's db.index and db.write_meta."""
|
||||
units = dict(conn.execute("SELECT unit, COUNT(*) FROM search GROUP BY unit").fetchall())
|
||||
for u in ("sentence", "cell", "figure-text"):
|
||||
ok("sql", units.get(u, 0) > 0, "the search index has no %s rows" % u)
|
||||
# No fixture has an equation outside a sentence, so a small document shows both kinds of equation.
|
||||
nodes = [{"id": "d", "kind": "document", "parent": None, "ord": 0},
|
||||
{"id": "p", "kind": "paragraph", "parent": "d", "ord": 0},
|
||||
{"id": "s", "kind": "sentence", "parent": "p", "ord": 0,
|
||||
"content": [{"kind": "text", "text": "Then "}, {"kind": "equation", "node": "e1"}]},
|
||||
{"id": "e1", "kind": "equation", "parent": "s", "ord": 0, "content": {"notation": "tex", "source": "a=b"}},
|
||||
{"id": "e2", "kind": "equation", "parent": "p", "ord": 1, "content": {"notation": "tex", "source": "c=d"}}]
|
||||
got = [(n, u, c) for _, n, u, c, _ in db.search_rows("k", nodes)]
|
||||
ok("sql", got == [("s", "sentence", "paragraph"), ("e2", "equation", "paragraph")],
|
||||
"search rows for a sentence with an inline equation and a display equation: %s" % got)
|
||||
ok("sql", set(units) <= {"sentence", "cell", "equation", "figure-text"}, "unknown search units %s" % sorted(units))
|
||||
expect = conn.execute(
|
||||
"SELECT COUNT(*) FROM nodes n LEFT JOIN nodes q ON q.doc_key = n.doc_key AND q.id = n.parent "
|
||||
"WHERE n.withheld IS NULL AND (n.kind IN ('sentence', 'cell') OR (n.kind = 'equation' AND q.kind <> 'sentence'))"
|
||||
).fetchone()[0]
|
||||
ok("sql", sum(v for u, v in units.items() if u != "figure-text") == expect,
|
||||
"the search index does not have one row per sentence, cell and equation outside a sentence")
|
||||
inner = conn.execute(
|
||||
"SELECT COUNT(*) FROM search s JOIN nodes n ON n.doc_key = s.doc_key AND n.id = s.node "
|
||||
"JOIN nodes q ON q.doc_key = n.doc_key AND q.id = n.parent WHERE n.kind = 'equation' AND q.kind = 'sentence'"
|
||||
).fetchone()[0]
|
||||
ok("sql", inner == 0, "an equation inside a sentence is indexed apart from its sentence")
|
||||
bad = conn.execute(
|
||||
"SELECT COUNT(*) FROM search s JOIN nodes n ON n.doc_key = s.doc_key AND n.id = s.node "
|
||||
"LEFT JOIN nodes q ON q.doc_key = n.doc_key AND q.id = n.parent "
|
||||
"WHERE s.unit <> 'figure-text' AND s.context IS NOT q.kind").fetchone()[0]
|
||||
ok("sql", bad == 0, "a search row's context is not the kind of its node's parent")
|
||||
hits = conn.execute("SELECT node FROM search WHERE search MATCH 'scanned'").fetchall()
|
||||
ok("sql", len(hits) == 1, "full-text search finds the merged sentence")
|
||||
hits = conn.execute("SELECT context FROM search WHERE unit = 'sentence' AND search MATCH 'Methods'").fetchall()
|
||||
ok("sql", ("heading",) in hits, "a heading sentence is indexed with the context heading")
|
||||
stores = [r[0] for r in conn.execute("SELECT DISTINCT store FROM documents WHERE store IS NOT NULL")]
|
||||
db.write_meta(conn, stores[0] if len(stores) == 1 else "", "fixtures/packages")
|
||||
meta = dict(conn.execute("SELECT name, value FROM bundle_meta").fetchall())
|
||||
ok("sql", {"format", "store", "built_from", "spec"} <= set(meta), "bundle_meta lacks a required name")
|
||||
ok("sql", meta["format"] in FORMAT_SCHEMA, "bundle_meta names an unregistered format")
|
||||
top = re.search(r"^## (\S+)", open(p("CHANGELOG.md"), encoding="utf-8").read(), re.M).group(1)
|
||||
ok("sql", meta["spec"] == top, "bundle_meta spec %s is not the changelog's latest version %s" % (meta["spec"], top))
|
||||
|
||||
|
||||
def _same_tree(a, b):
|
||||
fa = sorted(os.path.relpath(os.path.join(d, f), a) for d, _, fs in os.walk(a) for f in fs)
|
||||
fb = sorted(os.path.relpath(os.path.join(d, f), b) for d, _, fs in os.walk(b) for f in fs)
|
||||
@@ -525,13 +702,24 @@ def check_links():
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
ap = argparse.ArgumentParser(
|
||||
description="Check the whole repository: schemas, records, packages, broken packages, vectors, a "
|
||||
"byte-for-byte rebuild, the SQL round trip and the bundle, prose and links. "
|
||||
"Exit status 0 means every check passed.")
|
||||
ap.add_argument("--quick", action="store_true", help="skip the byte-for-byte rebuild, the slowest check")
|
||||
args = ap.parse_args()
|
||||
check_schemas()
|
||||
check_registry()
|
||||
check_records()
|
||||
check_packages()
|
||||
check_owner_revocation()
|
||||
check_unsafe_paths()
|
||||
check_incremental()
|
||||
check_gate()
|
||||
check_invalid()
|
||||
check_vectors()
|
||||
if "--quick" not in sys.argv:
|
||||
if not args.quick:
|
||||
check_determinism()
|
||||
check_sql()
|
||||
check_prose()
|
||||
|
||||
+32
-4
@@ -1,8 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Load packages into a SQLite database with the tables of sql/doc.sql and sql/store.sql.
|
||||
|
||||
Non-normative tooling, standard library only.
|
||||
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 ...]
|
||||
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).
|
||||
Each package is validated first, and nothing is loaded if any package fails.
|
||||
Files without tables of their own (assets, datasets, derived files) are copied by SHA-256 into an
|
||||
asset store folder beside the database, named <database>.assets/.
|
||||
"""
|
||||
@@ -15,22 +19,46 @@ import sys
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from tools.oracle import db # noqa: E402
|
||||
from tools.oracle import db, package # noqa: E402
|
||||
from tools.oracle.schema import Store # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
ap.add_argument("--sqlite", required=True, help="the database file; created with the tables if new")
|
||||
ap.add_argument("--bundle", action="store_true", help="build a new scienting-bundle/1 file with search")
|
||||
ap.add_argument("--built-from", help="with --bundle: the store revision the packages come from")
|
||||
ap.add_argument("--store", help="with --bundle: the store's name (default: the packages' common store)")
|
||||
ap.add_argument("packages", nargs="+", help="package folders")
|
||||
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
|
||||
for p in a.packages:
|
||||
for e in package.validate(p, store).errors:
|
||||
print("%s: %s: %s: %s" % (p, e["where"], e["rule"], e["message"]), file=sys.stderr)
|
||||
bad = True
|
||||
if bad:
|
||||
sys.exit("Nothing was loaded.")
|
||||
new = not os.path.exists(a.sqlite)
|
||||
conn = sqlite3.connect(a.sqlite)
|
||||
if new:
|
||||
if a.bundle:
|
||||
db.create_bundle(conn, os.path.join(ROOT, "sql"))
|
||||
elif new:
|
||||
db.create(conn, os.path.join(ROOT, "sql"))
|
||||
for p in a.packages:
|
||||
key = db.load(conn, p, a.sqlite + ".assets")
|
||||
n = conn.execute("SELECT COUNT(*) FROM nodes WHERE doc_key = ?", (key,)).fetchone()[0]
|
||||
print("%s: %d nodes" % (key, n))
|
||||
if a.bundle:
|
||||
print("%s: %d nodes, %d search rows" % (key, n, db.index(conn, key)))
|
||||
else:
|
||||
print("%s: %d nodes" % (key, n))
|
||||
if a.bundle:
|
||||
stores = {r[0] for r in conn.execute("SELECT DISTINCT store FROM documents WHERE store IS NOT NULL")}
|
||||
store = a.store or (stores.pop() if len(stores) == 1 else None)
|
||||
if store is None:
|
||||
sys.exit("The packages name no single store; give one with --store.")
|
||||
db.write_meta(conn, store, a.built_from)
|
||||
conn.commit()
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
"""The reference computations behind every expected value in this repository; see tools/README.md."""
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 14):
|
||||
sys.exit("The manuspec 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 manuspec 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"))
|
||||
+42
-13
@@ -9,6 +9,7 @@ from . import hashing
|
||||
from .model import LABELLED, LEAVES, MATH_KINDS, RUN_KINDS, VERBATIM_KINDS, Doc, CHILDREN
|
||||
from .tree import Refusal, canonical_runs, mint_ids, validate_structure, _flatten
|
||||
from . import jcs
|
||||
from .schema import validate as schema_validate
|
||||
from .text import digits, record_id, sha256_hex
|
||||
|
||||
MERGEABLE = RUN_KINDS | {"paragraph", "list", "item", "quote", "caption", "figure-note", "table-note"}
|
||||
@@ -27,11 +28,12 @@ EDITABLE_RUN_FIELD = {"text": "text", "link": "text", "cite": "text", "xref": "t
|
||||
class State:
|
||||
"""The current state of a document: nodes, the ID table and the corrections applied."""
|
||||
|
||||
def __init__(self, key, nodes, ids=None, corrections=None):
|
||||
def __init__(self, key, nodes, ids=None, corrections=None, indexes=None):
|
||||
self.key = key
|
||||
self.nodes = [dict(n) for n in nodes]
|
||||
self.ids = [dict(e) for e in (ids or [])]
|
||||
self.corrections = [dict(c) for c in (corrections or [])]
|
||||
self._indexes = dict(indexes or {})
|
||||
|
||||
@property
|
||||
def rev(self):
|
||||
@@ -40,6 +42,12 @@ class State:
|
||||
def doc(self):
|
||||
return Doc(self.key, self.nodes)
|
||||
|
||||
def index(self, hasher=hashing.PLAIN):
|
||||
"""The node values under `hasher`, kept with the state so the next correction rehashes only what changes."""
|
||||
if hasher.key not in self._indexes:
|
||||
self._indexes[hasher.key] = hashing.Index.build(self.doc(), hasher)
|
||||
return self._indexes[hasher.key]
|
||||
|
||||
def used_ids(self):
|
||||
return {n["id"] for n in self.nodes} | {e["id"] for e in self.ids}
|
||||
|
||||
@@ -120,11 +128,28 @@ def _place(nodes, nid, parent, after):
|
||||
by_id[nid]["ord"] = pos
|
||||
|
||||
|
||||
def gate(state, proposal, store, hasher=hashing.PLAIN):
|
||||
"""Run the gate (spec/11 §11.4) on a proposed correction, leaving `state` unchanged.
|
||||
|
||||
Returns the state the correction would produce and the record it would append to corrections.jsonl, as apply
|
||||
does. Raises Refusal naming the rule broken. `store` is the schema store that holds correction.schema.json.
|
||||
"""
|
||||
errors = schema_validate(store, "correction.schema.json", proposal)
|
||||
if errors:
|
||||
raise Refusal("correction.schema", "; ".join(errors[:3]))
|
||||
return apply(state, proposal, hasher)
|
||||
|
||||
|
||||
def _is_index(v):
|
||||
return isinstance(v, int) and not isinstance(v, bool) and v >= 0
|
||||
|
||||
|
||||
def apply(state, c, hasher=hashing.PLAIN):
|
||||
"""Apply correction `c` to `state` and return the recorded correction. Raises Refusal."""
|
||||
c = copy.deepcopy(c)
|
||||
doc = state.doc()
|
||||
before_vals = hashing.compute(doc, hasher)
|
||||
before_ix = state.index(hasher)
|
||||
before_vals = before_ix.values
|
||||
nodes = [copy.deepcopy(n) for n in state.nodes]
|
||||
by_id = {n["id"]: n for n in nodes}
|
||||
op = c["op"]
|
||||
@@ -194,11 +219,14 @@ def apply(state, c, hasher=hashing.PLAIN):
|
||||
node = by_id[target]
|
||||
if node["kind"] not in RUN_KINDS:
|
||||
raise Refusal("correction.insert", "text is inserted into runs", target)
|
||||
r = node["content"][c["run"]]
|
||||
f = EDITABLE_RUN_FIELD.get(r["kind"])
|
||||
if not f or not (0 <= c["offset"] <= len(r[f])):
|
||||
run, offset = c.get("run"), c.get("offset")
|
||||
if not _is_index(run) or run >= len(node["content"]) or not _is_index(offset):
|
||||
raise Refusal("correction.insert", "bad run or offset", target)
|
||||
node["content"][c["run"]] = dict(r, **{f: r[f][: c["offset"]] + c["text"] + r[f][c["offset"]:]})
|
||||
r = node["content"][run]
|
||||
f = EDITABLE_RUN_FIELD.get(r["kind"])
|
||||
if not f or offset > len(r[f]) or not isinstance(c.get("text"), str):
|
||||
raise Refusal("correction.insert", "bad run or offset", target)
|
||||
node["content"][run] = dict(r, **{f: r[f][:offset] + c["text"] + r[f][offset:]})
|
||||
node["content"] = canonical_runs(node["content"])
|
||||
touched = [target]
|
||||
|
||||
@@ -212,10 +240,10 @@ def apply(state, c, hasher=hashing.PLAIN):
|
||||
node = by_id[target]
|
||||
if node["kind"] != "sentence":
|
||||
raise Refusal("correction.split", "only sentences are split", target)
|
||||
k = c["offset"]
|
||||
k = c.get("offset")
|
||||
first, second, pos = [], [], 0
|
||||
total = len(hashing.plain_runs(node["content"], doc))
|
||||
if not 0 < k < total:
|
||||
if not _is_index(k) or not 0 < k < total:
|
||||
raise Refusal("correction.split", "offset must fall inside the sentence", target)
|
||||
for r in node["content"]:
|
||||
ln = len(hashing.plain_runs([r], doc))
|
||||
@@ -325,7 +353,8 @@ def apply(state, c, hasher=hashing.PLAIN):
|
||||
|
||||
after_doc = Doc(state.key, nodes)
|
||||
validate_structure(after_doc)
|
||||
after_vals = hashing.compute(after_doc, hasher)
|
||||
after_ix = before_ix.update(after_doc)
|
||||
after_vals = after_ix.values
|
||||
|
||||
# Digits: a change to a digit needs crop evidence, and no leaf outside `touched` may change its digits.
|
||||
needs_crop = False
|
||||
@@ -333,14 +362,14 @@ def apply(state, c, hasher=hashing.PLAIN):
|
||||
rep = c["replace"] if isinstance(c["replace"], str) else hashing.plain_runs(c["replace"], doc)
|
||||
needs_crop = digits(c["find"]) != digits(rep)
|
||||
elif op == "delete":
|
||||
needs_crop = any(digits(hashing.plain(doc, x)) for x in touched)
|
||||
needs_crop = any(digits(before_ix.plain(x)) for x in touched)
|
||||
elif op == "relabel":
|
||||
needs_crop = digits(record.get("from") or "") != digits(c.get("label") or "")
|
||||
if needs_crop and not _has_crop(c):
|
||||
raise Refusal("correction.digits", "a digit changed without crop evidence", target)
|
||||
for nid in doc.leaves(doc.root):
|
||||
if nid in after_doc.nodes and nid not in touched:
|
||||
if digits(hashing.plain(doc, nid)) != digits(hashing.plain(after_doc, nid)):
|
||||
if nid in after_ix.entries and nid not in touched:
|
||||
if digits(before_ix.plain(nid)) != digits(after_ix.plain(nid)):
|
||||
raise Refusal("correction.digits", "an undeclared digit change", nid)
|
||||
|
||||
for k in ("op", "target", "find", "replace", "run", "offset", "text", "parent", "place_after", "node", "with",
|
||||
@@ -365,7 +394,7 @@ def apply(state, c, hasher=hashing.PLAIN):
|
||||
new_ids.append({"id": rid, "kind": "retired", "successor": succ, "node_kind": old_kinds[rid],
|
||||
"rev": record["index"]})
|
||||
new_state = State(state.key, [after_doc.nodes[x] for x in after_doc.preorder()], new_ids,
|
||||
state.corrections + [record])
|
||||
state.corrections + [record], {hasher.key: after_ix})
|
||||
return new_state, record
|
||||
|
||||
|
||||
|
||||
+15
-116
@@ -1,24 +1,22 @@
|
||||
"""Keyed hashing, key derivation, Merkle roots and Ed25519.
|
||||
|
||||
Non-normative; see spec/05-hashes.md and spec/08-fidelity.md.
|
||||
Ed25519 follows the reference code of RFC 8032, section 6; it is slow and is meant only for test vectors.
|
||||
HKDF and Ed25519 come from the `cryptography` package.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
|
||||
from . import jcs
|
||||
|
||||
|
||||
def hkdf(ikm, salt, info, length=32):
|
||||
"""HKDF-SHA-256, RFC 5869."""
|
||||
prk = hmac.new(salt, ikm, hashlib.sha256).digest()
|
||||
out, t, i = b"", b"", 1
|
||||
while len(out) < length:
|
||||
t = hmac.new(prk, t + info + bytes([i]), hashlib.sha256).digest()
|
||||
out += t
|
||||
i += 1
|
||||
return out[:length]
|
||||
return HKDF(algorithm=hashes.SHA256(), length=length, salt=salt, info=info).derive(ikm)
|
||||
|
||||
|
||||
def team_key(secret):
|
||||
@@ -74,121 +72,22 @@ def receipt_items(receipt):
|
||||
|
||||
# --- Ed25519 (RFC 8032) -------------------------------------------------------
|
||||
|
||||
_p = 2**255 - 19
|
||||
_d = -121665 * pow(121666, _p - 2, _p) % _p
|
||||
_q = 2**252 + 27742317777372353535851937790883648493
|
||||
|
||||
|
||||
def _sha512(b):
|
||||
return hashlib.sha512(b).digest()
|
||||
|
||||
|
||||
def _inv(x):
|
||||
return pow(x, _p - 2, _p)
|
||||
|
||||
|
||||
def _add(P, Q):
|
||||
A = (P[1] - P[0]) * (Q[1] - Q[0]) % _p
|
||||
B = (P[1] + P[0]) * (Q[1] + Q[0]) % _p
|
||||
C = 2 * P[3] * Q[3] * _d % _p
|
||||
D = 2 * P[2] * Q[2] % _p
|
||||
E, F, G, H = B - A, D - C, D + C, B + A
|
||||
return (E * F % _p, G * H % _p, F * G % _p, E * H % _p)
|
||||
|
||||
|
||||
def _mul(s, P):
|
||||
Q = (0, 1, 1, 0)
|
||||
while s > 0:
|
||||
if s & 1:
|
||||
Q = _add(Q, P)
|
||||
P = _add(P, P)
|
||||
s >>= 1
|
||||
return Q
|
||||
|
||||
|
||||
def _equal(P, Q):
|
||||
if (P[0] * Q[2] - Q[0] * P[2]) % _p != 0:
|
||||
return False
|
||||
return (P[1] * Q[2] - Q[1] * P[2]) % _p == 0
|
||||
|
||||
|
||||
_modp_sqrt_m1 = pow(2, (_p - 1) // 4, _p)
|
||||
|
||||
|
||||
def _recover_x(y, sign):
|
||||
if y >= _p:
|
||||
return None
|
||||
x2 = (y * y - 1) * _inv(_d * y * y + 1)
|
||||
if x2 == 0:
|
||||
return None if sign else 0
|
||||
x = pow(x2, (_p + 3) // 8, _p)
|
||||
if (x * x - x2) % _p != 0:
|
||||
x = x * _modp_sqrt_m1 % _p
|
||||
if (x * x - x2) % _p != 0:
|
||||
return None
|
||||
if (x & 1) != sign:
|
||||
x = _p - x
|
||||
return x
|
||||
|
||||
|
||||
_gy = 4 * _inv(5) % _p
|
||||
_gx = _recover_x(_gy, 0)
|
||||
_G = (_gx, _gy, 1, _gx * _gy % _p)
|
||||
|
||||
|
||||
def _compress(P):
|
||||
zinv = _inv(P[2])
|
||||
x = P[0] * zinv % _p
|
||||
y = P[1] * zinv % _p
|
||||
return int.to_bytes(y | ((x & 1) << 255), 32, "little")
|
||||
|
||||
|
||||
def _decompress(s):
|
||||
if len(s) != 32:
|
||||
return None
|
||||
y = int.from_bytes(s, "little")
|
||||
sign = y >> 255
|
||||
y &= (1 << 255) - 1
|
||||
x = _recover_x(y, sign)
|
||||
if x is None:
|
||||
return None
|
||||
return (x, y, 1, x * y % _p)
|
||||
|
||||
|
||||
def _secret_expand(secret):
|
||||
h = _sha512(secret)
|
||||
a = int.from_bytes(h[:32], "little")
|
||||
a &= (1 << 254) - 8
|
||||
a |= 1 << 254
|
||||
return a, h[32:]
|
||||
|
||||
|
||||
def ed25519_public(secret):
|
||||
a, _ = _secret_expand(secret)
|
||||
return _compress(_mul(a, _G))
|
||||
"""The 32-byte public key of a 32-byte Ed25519 secret."""
|
||||
return Ed25519PrivateKey.from_private_bytes(secret).public_key().public_bytes_raw()
|
||||
|
||||
|
||||
def ed25519_sign(secret, msg):
|
||||
a, prefix = _secret_expand(secret)
|
||||
A = _compress(_mul(a, _G))
|
||||
r = int.from_bytes(_sha512(prefix + msg), "little") % _q
|
||||
R = _compress(_mul(r, _G))
|
||||
h = int.from_bytes(_sha512(R + A + msg), "little") % _q
|
||||
s = (r + h * a) % _q
|
||||
return R + int.to_bytes(s, 32, "little")
|
||||
return Ed25519PrivateKey.from_private_bytes(secret).sign(msg)
|
||||
|
||||
|
||||
def ed25519_verify(public, msg, signature):
|
||||
"""True when `signature` is a valid Ed25519 signature of `msg` under `public`; never raises."""
|
||||
if len(public) != 32 or len(signature) != 64:
|
||||
return False
|
||||
A = _decompress(public)
|
||||
if not A:
|
||||
try:
|
||||
Ed25519PublicKey.from_public_bytes(public).verify(signature, msg)
|
||||
except (InvalidSignature, ValueError):
|
||||
return False
|
||||
R = _decompress(signature[:32])
|
||||
if not R:
|
||||
return False
|
||||
s = int.from_bytes(signature[32:], "little")
|
||||
if s >= _q:
|
||||
return False
|
||||
h = int.from_bytes(_sha512(signature[:32] + public + msg), "little") % _q
|
||||
return _equal(_mul(s, _G), _add(R, _mul(h, A)))
|
||||
return True
|
||||
+72
-4
@@ -8,9 +8,14 @@ import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from . import jcs
|
||||
from .package import read_json, read_jsonl, write
|
||||
from .text import sha256_hex
|
||||
from . import hashing, jcs
|
||||
from .model import Doc
|
||||
from .package import SHA256_RE, read_json, read_jsonl, safe_path, write
|
||||
from .text import norm, sha256_hex
|
||||
from .tree import Refusal
|
||||
|
||||
SPEC_VERSION = "0.1.0"
|
||||
BUNDLE_FORMAT = "scienting-bundle/1"
|
||||
|
||||
STRUCTURED = {"MANIFEST.json", "document.json", "witnesses.json", "provenance.json", "roster.json", "nodes.jsonl",
|
||||
"pages.jsonl", "locations.jsonl", "references.jsonl", "corrections.jsonl", "receipts.jsonl",
|
||||
@@ -44,10 +49,71 @@ def create(conn, sql_dir):
|
||||
conn.executescript(f.read())
|
||||
|
||||
|
||||
def create_bundle(conn, sql_dir):
|
||||
"""The tables of a bundle: those of create(), then sql/bundle-sqlite.sql (spec/13 §13.5)."""
|
||||
create(conn, sql_dir)
|
||||
with open(os.path.join(sql_dir, "bundle-sqlite.sql"), encoding="utf-8") as f:
|
||||
conn.executescript(f.read())
|
||||
|
||||
|
||||
def search_rows(key, nodes):
|
||||
"""The bundle's search rows for one document, in document order (spec/13 §13.5).
|
||||
|
||||
One row per sentence, cell, equation outside a sentence and figure-text item, as
|
||||
(doc_key, node, unit, context, text). For a node, `context` is the kind of its parent; a figure-text item
|
||||
takes its panel as `node` and its `role` as `context`. Withheld nodes are skipped.
|
||||
"""
|
||||
doc = Doc(key, nodes)
|
||||
out = []
|
||||
for nid in doc.order:
|
||||
n = doc.nodes[nid]
|
||||
kind = n["kind"]
|
||||
parent = doc.nodes[n["parent"]]["kind"] if n.get("parent") else None
|
||||
if n.get("withheld"):
|
||||
continue
|
||||
if kind in ("sentence", "cell") or (kind == "equation" and parent != "sentence"):
|
||||
out.append((key, nid, kind, parent, norm(hashing.plain(doc, nid))))
|
||||
elif kind == "panel":
|
||||
for item in (n.get("attrs") or {}).get("figure_text", []):
|
||||
out.append((key, nid, "figure-text", item["role"], norm(item["text"])))
|
||||
return out
|
||||
|
||||
|
||||
def index(conn, key):
|
||||
"""Fill the bundle's `search` table for the loaded document `key`."""
|
||||
nodes = []
|
||||
for n in _q(conn, "SELECT id, kind, parent, ord, attrs, content, withheld FROM nodes WHERE doc_key = ? ORDER BY seq",
|
||||
(key,)):
|
||||
rec = {"id": n["id"], "kind": n["kind"], "parent": n["parent"], "ord": n["ord"]}
|
||||
_opt(rec, "attrs", P(n["attrs"]))
|
||||
_opt(rec, "content", P(n["content"]))
|
||||
if n["withheld"]:
|
||||
rec["withheld"] = True
|
||||
nodes.append(rec)
|
||||
rows = search_rows(key, nodes)
|
||||
conn.executemany("INSERT INTO search VALUES (?, ?, ?, ?, ?)", rows)
|
||||
return len(rows)
|
||||
|
||||
|
||||
def write_meta(conn, store, built_from, extra=None):
|
||||
"""Write `bundle_meta`: format, store, built_from and spec, then any `extra` pairs."""
|
||||
pairs = {"format": BUNDLE_FORMAT, "store": store, "built_from": built_from, "spec": SPEC_VERSION}
|
||||
pairs.update(extra or {})
|
||||
conn.executemany("INSERT INTO bundle_meta VALUES (?, ?)", sorted(pairs.items()))
|
||||
|
||||
|
||||
def load(conn, root, asset_store):
|
||||
"""Load the package at `root`. Files without tables go to `asset_store`, by SHA-256."""
|
||||
"""Load the package at `root`. Files without tables go to `asset_store`, by SHA-256.
|
||||
|
||||
Validate the package first (package.validate); this only refuses paths and hashes that are unsafe to use.
|
||||
"""
|
||||
manifest = read_json(os.path.join(root, "MANIFEST.json"))
|
||||
listed = {f["path"]: f for f in manifest["files"]}
|
||||
for path, f in listed.items():
|
||||
if not safe_path(path):
|
||||
raise Refusal("package.path.unsafe", "unsafe path %r" % path)
|
||||
if not isinstance(f.get("sha256"), str) or not SHA256_RE.fullmatch(f["sha256"]):
|
||||
raise Refusal("package.manifest.schema", "%s: sha256 is not 64 lowercase hex digits" % path)
|
||||
|
||||
def jl(name):
|
||||
return read_jsonl(os.path.join(root, name)) if name in listed else []
|
||||
@@ -241,6 +307,8 @@ def unload(conn, key, out, asset_store):
|
||||
absent = []
|
||||
for f in _q(conn, "SELECT * FROM package_files WHERE doc_key = ?", (key,)):
|
||||
if f["present"]:
|
||||
if not SHA256_RE.fullmatch(f["sha256"]):
|
||||
raise Refusal("package.manifest.schema", "%s: sha256 is not 64 lowercase hex digits" % f["path"])
|
||||
with open(os.path.join(asset_store, f["sha256"]), "rb") as fh:
|
||||
files[f["path"]] = fh.read()
|
||||
else:
|
||||
|
||||
+95
-19
@@ -214,26 +214,102 @@ def own_content(doc, nid, text_hash_of=None):
|
||||
return o
|
||||
|
||||
|
||||
class _Entry:
|
||||
"""One node's cached work: its five values, and the projections its parent joins."""
|
||||
|
||||
__slots__ = ("sig", "values", "plain", "joined_plain", "joined_strict", "complexes")
|
||||
|
||||
def __init__(self, sig, values, plain, joined_plain, joined_strict, complexes):
|
||||
self.sig = sig
|
||||
self.values = values
|
||||
self.plain = plain
|
||||
# What this node gives a container's projections: its text leaves joined, or None when it has none.
|
||||
self.joined_plain = joined_plain
|
||||
self.joined_strict = joined_strict
|
||||
self.complexes = complexes
|
||||
|
||||
|
||||
def _sig(doc, nid):
|
||||
"""Everything a node's values depend on besides its children's values."""
|
||||
n = doc.nodes[nid]
|
||||
return (n["kind"], n.get("label"), n.get("attrs"), n.get("content"), tuple(doc.kids(nid)))
|
||||
|
||||
|
||||
class Index:
|
||||
"""The five values of every node of one document, kept so that a change rehashes only what it touched.
|
||||
|
||||
A node's values depend only on its own subtree, so after a change only the nodes whose own record or
|
||||
children changed, and their ancestors, are rehashed. A container's projections are its children's
|
||||
cached projections joined, so no container walks its subtree again.
|
||||
"""
|
||||
|
||||
def __init__(self, hasher=PLAIN):
|
||||
self.hasher = hasher
|
||||
self.entries = {}
|
||||
self.values = {}
|
||||
|
||||
@classmethod
|
||||
def build(cls, doc, hasher=PLAIN):
|
||||
ix = cls(hasher)
|
||||
ix._rehash(doc, set(doc.nodes))
|
||||
return ix
|
||||
|
||||
def update(self, doc):
|
||||
"""A new Index for `doc`, a later state of this one's document; this Index is left unchanged."""
|
||||
ix = Index(self.hasher)
|
||||
ix.entries = {nid: e for nid, e in self.entries.items() if nid in doc.nodes}
|
||||
ix.values = {nid: v for nid, v in self.values.items() if nid in doc.nodes}
|
||||
dirty = {nid for nid in doc.nodes if nid not in ix.entries or ix.entries[nid].sig != _sig(doc, nid)}
|
||||
for nid in list(dirty):
|
||||
p = doc.nodes[nid].get("parent")
|
||||
while p is not None and p not in dirty:
|
||||
dirty.add(p)
|
||||
p = doc.nodes[p].get("parent")
|
||||
ix._rehash(doc, dirty)
|
||||
return ix
|
||||
|
||||
def plain(self, nid):
|
||||
"""The node's plain projection."""
|
||||
return self.entries[nid].plain
|
||||
|
||||
def _rehash(self, doc, dirty):
|
||||
h = self.hasher
|
||||
for nid in reversed(doc.preorder()):
|
||||
if nid not in dirty:
|
||||
continue
|
||||
n = doc.nodes[nid]
|
||||
k = n["kind"]
|
||||
kids = doc.kids(nid)
|
||||
if k in RUN_KINDS or k in MATH_KINDS or k in VERBATIM_KINDS:
|
||||
p = plain(doc, nid)
|
||||
st, cx = strict(doc, nid)
|
||||
# An equation line is never a text leaf; a sentence's equations are part of its own runs.
|
||||
joined = (None, None, []) if k == "equation-line" else (p, st, cx)
|
||||
else:
|
||||
ps, ss, cx = [], [], []
|
||||
for c in kids:
|
||||
e = self.entries[c]
|
||||
if e.joined_plain is not None:
|
||||
ps.append(e.joined_plain)
|
||||
ss.append(e.joined_strict)
|
||||
cx.extend(e.complexes)
|
||||
p = " ".join(ps)
|
||||
st = _finish_strict(" ".join(ss))
|
||||
joined = (p, " ".join(ss), cx) if ps else (None, None, [])
|
||||
own = h(jcs.dumpb(own_content(doc, nid, lambda x: self.values[x]["text_hash"])))
|
||||
self.values[nid] = {
|
||||
"text_hash": h(norm(p)),
|
||||
"match_hash": h(st),
|
||||
"align_key": h(lossy_from_strict(st)),
|
||||
"own_hash": own,
|
||||
"node_hash": h(jcs.dumpb({"own": own, "children": [self.values[c]["node_hash"] for c in kids]})),
|
||||
}
|
||||
self.entries[nid] = _Entry(_sig(doc, nid), self.values[nid], p, *joined)
|
||||
|
||||
|
||||
def compute(doc, hasher=PLAIN):
|
||||
"""Compute the five content values of every node, bottom up. Returns {id: {...}}."""
|
||||
values = {}
|
||||
|
||||
def text_hash(nid):
|
||||
return values[nid]["text_hash"] if nid in values else hasher(norm(plain(doc, nid)))
|
||||
|
||||
for nid in reversed(doc.preorder()):
|
||||
s, _ = strict(doc, nid)
|
||||
th = hasher(norm(plain(doc, nid)))
|
||||
own = hasher(jcs.dumpb(own_content(doc, nid, text_hash)))
|
||||
node = hasher(jcs.dumpb({"own": own, "children": [values[c]["node_hash"] for c in doc.kids(nid)]}))
|
||||
values[nid] = {
|
||||
"text_hash": th,
|
||||
"match_hash": hasher(s),
|
||||
"align_key": hasher(lossy_from_strict(s)),
|
||||
"own_hash": own,
|
||||
"node_hash": node,
|
||||
}
|
||||
return values
|
||||
"""The five content values of every node, bottom up. Returns {id: {...}}."""
|
||||
return Index.build(doc, hasher).values
|
||||
|
||||
|
||||
def meta_object(document):
|
||||
|
||||
+37
-1
@@ -1,11 +1,18 @@
|
||||
"""Canonical JSON, RFC 8785 (JSON Canonicalization Scheme).
|
||||
|
||||
Non-normative.
|
||||
The normative rule is spec/01-conventions.md; this module exists so the vectors can be generated and checked.
|
||||
The normative rule is spec/01-conventions.md.
|
||||
Most values serialize through the standard library's C encoder, which escapes strings exactly as RFC 8785 does.
|
||||
Values it would write differently take the exact path below: floats, integers beyond 2^53, and member names
|
||||
outside the Basic Multilingual Plane, which RFC 8785 sorts by UTF-16 code units rather than by code points.
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
|
||||
_C = json.JSONEncoder(ensure_ascii=False, separators=(",", ":"), sort_keys=True, allow_nan=False)
|
||||
_EXACT_INT = 2**53
|
||||
|
||||
_ESC = {
|
||||
0x08: "\\b",
|
||||
0x09: "\\t",
|
||||
@@ -82,8 +89,37 @@ def _key(s):
|
||||
return s.encode("utf-16-be")
|
||||
|
||||
|
||||
def _fast(v):
|
||||
"""True when the C encoder writes `v` exactly as RFC 8785 does."""
|
||||
t = type(v)
|
||||
if t is str or v is None or t is bool:
|
||||
return True
|
||||
if t is int:
|
||||
return -_EXACT_INT <= v <= _EXACT_INT
|
||||
if t is dict:
|
||||
for k, x in v.items():
|
||||
if type(k) is not str or (not k.isascii() and max(k) > "\uffff") or not _fast(x):
|
||||
return False
|
||||
return True
|
||||
if t is list or t is tuple:
|
||||
return all(_fast(x) for x in v)
|
||||
return False
|
||||
|
||||
|
||||
def dumps(value):
|
||||
"""Serialize `value` to canonical JSON text."""
|
||||
if _fast(value):
|
||||
text = _C.encode(value)
|
||||
if not text.isascii():
|
||||
try:
|
||||
text.encode("utf-8")
|
||||
except UnicodeEncodeError:
|
||||
raise ValueError("lone surrogate in string") from None
|
||||
return text
|
||||
return _exact(value)
|
||||
|
||||
|
||||
def _exact(value):
|
||||
if value is None:
|
||||
return "null"
|
||||
if value is True:
|
||||
|
||||
+23
-3
@@ -7,6 +7,7 @@ import base64
|
||||
import json
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
|
||||
from . import crypto, hashing, jcs, receipts as R
|
||||
from .anchors import ADDRESS_RE
|
||||
@@ -46,8 +47,11 @@ def witness_id(sha):
|
||||
return letter_first(bytes.fromhex(sha), 7)
|
||||
|
||||
|
||||
SHA256_RE = re.compile(r"[0-9a-f]{64}")
|
||||
|
||||
|
||||
def safe_path(p):
|
||||
if not p or p.startswith("/") or "\\" in p or "\x00" in p:
|
||||
if not isinstance(p, str) or not p or p.startswith("/") or "\\" in p or "\x00" in p:
|
||||
return False
|
||||
parts = p.split("/")
|
||||
return all(x not in ("", ".", "..") for x in parts) and posixpath.normpath(p) == p
|
||||
@@ -58,6 +62,9 @@ def write(root, files, absent=()):
|
||||
|
||||
Returns the package hash.
|
||||
"""
|
||||
for path in list(files) + [a["path"] for a in absent]:
|
||||
if not safe_path(path) or path == "MANIFEST.json":
|
||||
raise Refusal("package.path.unsafe", "unsafe path %r" % path)
|
||||
os.makedirs(root, exist_ok=True)
|
||||
entries = [dict(a) for a in absent]
|
||||
for path in sorted(files, key=lambda p: p.encode("utf-8")):
|
||||
@@ -374,10 +381,23 @@ def _validate(root, store, key, rep):
|
||||
if qa["sample"] != R.sample(orig, seed, qa["rate"], qa["minimum"]):
|
||||
rep.add("doc.qa.sample", r["id"], "the sample does not follow from the seed")
|
||||
if roster and not withheld:
|
||||
lv = R.effective_levels(nodes, vals, recs, roster)
|
||||
if R.distribution(lv) != document["fidelity"]:
|
||||
if R.distribution(R.effective_levels(nodes, vals, recs, roster)) != document["fidelity"]:
|
||||
rep.add("doc.fidelity.report", "document.json", "the fidelity distribution does not recompute")
|
||||
|
||||
|
||||
def read_state(root):
|
||||
"""The current state of the package at `root`, for the gate (spec/11 §11.4): nodes without their values,
|
||||
the ID table and the corrections applied so far. Validate the package first."""
|
||||
from .corrections import State
|
||||
present = {f["path"] for f in read_json(os.path.join(root, "MANIFEST.json"))["files"]}
|
||||
|
||||
def jl(name):
|
||||
return read_jsonl(os.path.join(root, name)) if name in present else []
|
||||
|
||||
values = ("text_hash", "match_hash", "align_key", "own_hash", "node_hash")
|
||||
nodes = [{k: v for k, v in n.items() if k not in values} for n in jl("nodes.jsonl")]
|
||||
return State(read_json(os.path.join(root, "document.json"))["key"], nodes, jl("ids.jsonl"), jl("corrections.jsonl"))
|
||||
|
||||
|
||||
def n_root_hash(nodes, doc):
|
||||
return {n["id"]: n for n in nodes}[doc.root]["node_hash"]
|
||||
@@ -123,7 +123,9 @@ def revoked(receipt, revocations):
|
||||
return False
|
||||
|
||||
|
||||
def valid_revocations(revocations, receipts_by_id, roster, owners):
|
||||
def valid_revocations(revocations, receipts_by_id, roster):
|
||||
"""The revocations signed by the receipt's signer, the signer they name, or a key the roster makes an owner."""
|
||||
owners = {k for k, v in roster.items() if v.get("role") == "owner"}
|
||||
out = []
|
||||
for r in revocations:
|
||||
if not verify_signature(r, roster):
|
||||
@@ -139,7 +141,7 @@ def valid_revocations(revocations, receipts_by_id, roster, owners):
|
||||
return out
|
||||
|
||||
|
||||
def receipt_status(rec, records, roster, owners=()):
|
||||
def receipt_status(rec, records, roster):
|
||||
"""`valid`, `revoked`, `suspended`, or `invalid` for one receipt."""
|
||||
receipts = [x for x in records if x["format"] == "scienting-receipt/1"]
|
||||
revs = [x for x in records if x["format"] == "scienting-revocation/1"]
|
||||
@@ -151,26 +153,30 @@ def receipt_status(rec, records, roster, owners=()):
|
||||
key = roster.get(rec["by"])
|
||||
if not key or not key_valid_at(key, rec["at"]) or rec["signature"]["key"] != rec["by"]:
|
||||
return "invalid"
|
||||
if revoked(rec, valid_revocations(revs, by_id, roster, owners)):
|
||||
if revoked(rec, valid_revocations(revs, by_id, roster)):
|
||||
return "revoked"
|
||||
for q in receipts:
|
||||
qa = q.get("qa")
|
||||
if qa and qa.get("of") == rec["id"] and qa.get("errors"):
|
||||
if receipt_status(q, records, roster, owners) == "valid":
|
||||
if receipt_status(q, records, roster) == "valid":
|
||||
return "suspended"
|
||||
return "valid"
|
||||
|
||||
|
||||
def effective_levels(nodes, values, records, roster, owners=()):
|
||||
"""Every leaf's level: its recorded level, raised by valid receipts that list it at its current hash."""
|
||||
def effective_levels(nodes, values, records, roster):
|
||||
"""Every leaf's level: its recorded level, raised by valid receipts that list it at its current hash.
|
||||
|
||||
A spot-check's `qa:` receipt does not raise the nodes it found in error (spec/08 §8.2).
|
||||
"""
|
||||
level = {n["id"]: n.get("fidelity") for n in nodes if "fidelity" in n}
|
||||
for rec in records:
|
||||
if rec["format"] != "scienting-receipt/1":
|
||||
continue
|
||||
if receipt_status(rec, records, roster, owners) != "valid":
|
||||
if receipt_status(rec, records, roster) != "valid":
|
||||
continue
|
||||
wrong = {e["node"] for e in (rec.get("qa") or {}).get("errors", [])}
|
||||
for nid, h in rec.get("nodes", []):
|
||||
if nid in level and values[nid]["node_hash"] == h:
|
||||
if nid in level and nid not in wrong and values[nid]["node_hash"] == h:
|
||||
if LEVELS.index(rec["level"]) > LEVELS.index(level[nid]):
|
||||
level[nid] = rec["level"]
|
||||
return level
|
||||
|
||||
+25
-146
@@ -1,13 +1,15 @@
|
||||
"""A JSON Schema (draft 2020-12) validator for the subset of keywords the spec's schemas use.
|
||||
"""JSON Schema (draft 2020-12) validation with the `jsonschema` package, and the lint of the keyword subset.
|
||||
|
||||
Non-normative.
|
||||
The spec's schemas are restricted to these keywords (spec/22-conformance.md) so that any 2020-12 validator, and
|
||||
this one, agree; CI also runs the `jsonschema` package when it is installed.
|
||||
The spec's schemas are restricted to the keywords below (spec/22-conformance.md) so that every 2020-12
|
||||
validator reads them alike; `lint` refuses any schema that leaves the subset.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
from jsonschema import Draft202012Validator
|
||||
from referencing import Registry, Resource
|
||||
|
||||
KEYWORDS = {
|
||||
"$schema", "$id", "$ref", "$defs", "$comment", "title", "description", "examples", "default", "deprecated",
|
||||
@@ -24,9 +26,25 @@ class SchemaError(Exception):
|
||||
|
||||
|
||||
class Store:
|
||||
"""The schemas of one folder, linted on load, with a compiled validator per schema."""
|
||||
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.docs = {}
|
||||
self._registry = None
|
||||
self._validators = {}
|
||||
|
||||
def registry(self):
|
||||
if self._registry is None:
|
||||
names = sorted(f for f in os.listdir(self.root) if f.endswith(".schema.json"))
|
||||
self._registry = Registry().with_resources(
|
||||
[(self.load(f)["$id"], Resource.from_contents(self.load(f))) for f in names])
|
||||
return self._registry
|
||||
|
||||
def validator(self, name):
|
||||
if name not in self._validators:
|
||||
self._validators[name] = Draft202012Validator(self.load(name), registry=self.registry())
|
||||
return self._validators[name]
|
||||
|
||||
def load(self, name):
|
||||
if name not in self.docs:
|
||||
@@ -65,146 +83,7 @@ def lint(schema, where):
|
||||
lint(s, "%s/%s/%d" % (where, k, i))
|
||||
|
||||
|
||||
def _type_ok(t, x):
|
||||
if t == "null":
|
||||
return x is None
|
||||
if t == "boolean":
|
||||
return isinstance(x, bool)
|
||||
if t == "integer":
|
||||
return isinstance(x, int) and not isinstance(x, bool)
|
||||
if t == "number":
|
||||
return isinstance(x, (int, float)) and not isinstance(x, bool)
|
||||
if t == "string":
|
||||
return isinstance(x, str)
|
||||
if t == "array":
|
||||
return isinstance(x, list)
|
||||
if t == "object":
|
||||
return isinstance(x, dict)
|
||||
raise SchemaError("unknown type " + t)
|
||||
|
||||
|
||||
def _equal(a, b):
|
||||
if isinstance(a, bool) or isinstance(b, bool):
|
||||
return type(a) is type(b) and a == b
|
||||
return a == b
|
||||
|
||||
|
||||
def validate(store, name, instance):
|
||||
"""Return a list of error strings; empty when the instance is valid."""
|
||||
schema = store.load(name)
|
||||
errors = []
|
||||
_v(store, name, schema, instance, "", errors)
|
||||
return errors
|
||||
|
||||
|
||||
def _v(store, base, s, x, path, errors):
|
||||
if s is True:
|
||||
return
|
||||
if s is False:
|
||||
errors.append(path + ": not allowed")
|
||||
return
|
||||
if "$ref" in s:
|
||||
b2, target = store.deref(base, s["$ref"])
|
||||
_v(store, b2, target, x, path, errors)
|
||||
if "type" in s:
|
||||
ts = s["type"] if isinstance(s["type"], list) else [s["type"]]
|
||||
if not any(_type_ok(t, x) for t in ts):
|
||||
errors.append("%s: expected %s" % (path or "/", "/".join(ts)))
|
||||
return
|
||||
if "const" in s and not _equal(s["const"], x):
|
||||
errors.append("%s: expected %r" % (path or "/", s["const"]))
|
||||
if "enum" in s and not any(_equal(e, x) for e in s["enum"]):
|
||||
errors.append("%s: %r is not one of %r" % (path or "/", x, s["enum"]))
|
||||
if isinstance(x, str):
|
||||
if "minLength" in s and len(x) < s["minLength"]:
|
||||
errors.append("%s: shorter than %d" % (path, s["minLength"]))
|
||||
if "maxLength" in s and len(x) > s["maxLength"]:
|
||||
errors.append("%s: longer than %d" % (path, s["maxLength"]))
|
||||
if "pattern" in s and not re.search(s["pattern"], x):
|
||||
errors.append("%s: %r does not match %s" % (path, x, s["pattern"]))
|
||||
if isinstance(x, (int, float)) and not isinstance(x, bool):
|
||||
if "minimum" in s and x < s["minimum"]:
|
||||
errors.append("%s: below %s" % (path, s["minimum"]))
|
||||
if "maximum" in s and x > s["maximum"]:
|
||||
errors.append("%s: above %s" % (path, s["maximum"]))
|
||||
if "exclusiveMinimum" in s and x <= s["exclusiveMinimum"]:
|
||||
errors.append("%s: not above %s" % (path, s["exclusiveMinimum"]))
|
||||
if "exclusiveMaximum" in s and x >= s["exclusiveMaximum"]:
|
||||
errors.append("%s: not below %s" % (path, s["exclusiveMaximum"]))
|
||||
if isinstance(x, list):
|
||||
if "minItems" in s and len(x) < s["minItems"]:
|
||||
errors.append("%s: fewer than %d items" % (path, s["minItems"]))
|
||||
if "maxItems" in s and len(x) > s["maxItems"]:
|
||||
errors.append("%s: more than %d items" % (path, s["maxItems"]))
|
||||
if s.get("uniqueItems"):
|
||||
seen = [json.dumps(i, sort_keys=True) for i in x]
|
||||
if len(seen) != len(set(seen)):
|
||||
errors.append("%s: items are not unique" % path)
|
||||
pre = s.get("prefixItems", [])
|
||||
for i, item in enumerate(x):
|
||||
if i < len(pre):
|
||||
_v(store, base, pre[i], item, "%s/%d" % (path, i), errors)
|
||||
elif "items" in s:
|
||||
_v(store, base, s["items"], item, "%s/%d" % (path, i), errors)
|
||||
if "contains" in s:
|
||||
if not any(not _sub(store, base, s["contains"], i) for i in x):
|
||||
errors.append("%s: no item matches `contains`" % path)
|
||||
if isinstance(x, dict):
|
||||
if "minProperties" in s and len(x) < s["minProperties"]:
|
||||
errors.append("%s: fewer than %d properties" % (path, s["minProperties"]))
|
||||
if "maxProperties" in s and len(x) > s["maxProperties"]:
|
||||
errors.append("%s: more than %d properties" % (path, s["maxProperties"]))
|
||||
for r in s.get("required", []):
|
||||
if r not in x:
|
||||
errors.append("%s: missing %s" % (path or "/", r))
|
||||
for k, deps in s.get("dependentRequired", {}).items():
|
||||
if k in x:
|
||||
for d in deps:
|
||||
if d not in x:
|
||||
errors.append("%s: %s requires %s" % (path or "/", k, d))
|
||||
props = s.get("properties", {})
|
||||
pprops = s.get("patternProperties", {})
|
||||
for k, v in x.items():
|
||||
matched = False
|
||||
if k in props:
|
||||
matched = True
|
||||
_v(store, base, props[k], v, path + "/" + k, errors)
|
||||
for pat, ps in pprops.items():
|
||||
if re.search(pat, k):
|
||||
matched = True
|
||||
_v(store, base, ps, v, path + "/" + k, errors)
|
||||
if not matched and "additionalProperties" in s:
|
||||
ap = s["additionalProperties"]
|
||||
if ap is False:
|
||||
errors.append("%s: unexpected property %s" % (path or "/", k))
|
||||
else:
|
||||
_v(store, base, ap, v, path + "/" + k, errors)
|
||||
if "propertyNames" in s:
|
||||
_v(store, base, s["propertyNames"], k, path + "/" + k, errors)
|
||||
for sub in s.get("allOf", []):
|
||||
_v(store, base, sub, x, path, errors)
|
||||
if "anyOf" in s:
|
||||
if all(_sub(store, base, sub, x) for sub in s["anyOf"]):
|
||||
errors.append("%s: matches none of anyOf" % (path or "/"))
|
||||
if "oneOf" in s:
|
||||
ok = [sub for sub in s["oneOf"] if not _sub(store, base, sub, x)]
|
||||
if len(ok) != 1:
|
||||
detail = ""
|
||||
if not ok:
|
||||
best = min((_sub(store, base, sub, x) for sub in s["oneOf"]), key=len)
|
||||
detail = " (closest: %s)" % "; ".join(best[:3])
|
||||
errors.append("%s: matches %d of oneOf%s" % (path or "/", len(ok), detail))
|
||||
if "not" in s and not _sub(store, base, s["not"], x):
|
||||
errors.append("%s: matches `not`" % (path or "/"))
|
||||
if "if" in s:
|
||||
if not _sub(store, base, s["if"], x):
|
||||
if "then" in s:
|
||||
_v(store, base, s["then"], x, path, errors)
|
||||
elif "else" in s:
|
||||
_v(store, base, s["else"], x, path, errors)
|
||||
|
||||
|
||||
def _sub(store, base, s, x):
|
||||
e = []
|
||||
_v(store, base, s, x, "", e)
|
||||
return e
|
||||
"""Return a list of error strings, `path: message`, in path order; empty when the instance is valid."""
|
||||
errors = sorted(store.validator(name).iter_errors(instance), key=lambda e: [str(x) for x in e.absolute_path])
|
||||
return ["%s: %s" % ("/" + "/".join(str(x) for x in e.absolute_path), e.message) for e in errors]
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""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
|
||||
A package is checked as spec/22 §22.2 describes, and each failure printed as `package: where: rule: message`.
|
||||
With --correction, PROPOSALS is a JSON file holding one proposed correction, or a JSON Lines file of several
|
||||
applied in order. Each runs through the gate (spec/11 §11.4) against the package's current state, and the record
|
||||
it would append to corrections.jsonl is printed, until one is refused. Nothing is written.
|
||||
Exit status: 0 when everything is valid, 1 when something is refused, 2 for a usage error.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from tools.oracle import corrections, jcs, package # noqa: E402
|
||||
from tools.oracle.schema import Store # noqa: E402
|
||||
from tools.oracle.tree import Refusal # noqa: E402
|
||||
|
||||
|
||||
def read_proposals(path):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
try:
|
||||
return [json.loads(text)]
|
||||
except json.JSONDecodeError:
|
||||
return [json.loads(ln) for ln in text.splitlines() if ln.strip()]
|
||||
|
||||
|
||||
def main():
|
||||
lines = __doc__.splitlines()
|
||||
ap = argparse.ArgumentParser(description=lines[0], epilog="\n".join(lines[5:]),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("--correction", metavar="PROPOSALS",
|
||||
help="a JSON or JSON Lines file of proposed corrections to run through the gate")
|
||||
ap.add_argument("packages", nargs="+", metavar="PACKAGE", help="package folders")
|
||||
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
|
||||
for p in a.packages:
|
||||
for e in package.validate(p, store).errors:
|
||||
print("%s: %s: %s: %s" % (p, e["where"], e["rule"], e["message"]), file=sys.stderr)
|
||||
bad = True
|
||||
if bad:
|
||||
return 1
|
||||
if not a.correction:
|
||||
for p in a.packages:
|
||||
print("%s: valid" % p)
|
||||
return 0
|
||||
pkg = a.packages[0]
|
||||
state = package.read_state(pkg)
|
||||
if any(n.get("withheld") for n in state.nodes):
|
||||
print("%s: doc.policy.withheld: a package that withholds its text takes no corrections" % pkg,
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
for i, proposal in enumerate(read_proposals(a.correction), 1):
|
||||
try:
|
||||
state, record = corrections.gate(state, proposal, store)
|
||||
except Refusal as e:
|
||||
where = "%s:%d" % (a.correction, i) + (" " + e.node if e.node else "")
|
||||
print("%s: %s: %s" % (where, e.rule, e.message), file=sys.stderr)
|
||||
return 1
|
||||
print(jcs.dumps(record))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+2
-2
@@ -84,7 +84,7 @@ The reference checker in [tools/](../tools/README.md) runs all twenty files, tog
|
||||
python3 tools/check.py --quick
|
||||
```
|
||||
|
||||
It needs Python 3.10 or later and has no option to run one file.
|
||||
It needs Python 3.14 and two packages, `jsonschema` and `cryptography`, and has no option to run one file.
|
||||
It tests the reference implementation in `tools/oracle/` against the vectors.
|
||||
It does not test yours.
|
||||
|
||||
@@ -161,4 +161,4 @@ Canonical JSON is RFC 8785 with added rules.
|
||||
Receipts use a Merkle tree that follows RFC 6962 ([8.5](../spec/08-fidelity.md#85-merkle-roots)); RFC 9162 replaces RFC 6962 and defines the same tree.
|
||||
The `node_hash` of a node is a different construction: a hash of the canonical JSON of `{own, children}`.
|
||||
Here `own` is the node's own hash and `children` holds the `node_hash` of each child ([5.1](../spec/05-hashes.md#51-the-five-values)), and `hashes.jsonl` pins it.
|
||||
The comparison page covers how these formats differ from JATS (Journal Article Tag Suite), TEI (Text Encoding Initiative), W3C (World Wide Web Consortium) Web Annotation and the other neighbors [CHECK: docs/comparison.md does not exist in the reference repository; link it once it does].
|
||||
The [comparison page](../docs/comparison.md) covers how these formats differ from JATS (Journal Article Tag Suite), TEI (Text Encoding Initiative), W3C (World Wide Web Consortium) Web Annotation and the other neighbors.
|
||||
@@ -5,6 +5,7 @@
|
||||
{"before":{"ids":[],"nodes":[{"align_key":"b3108c22ec836d883e5116f7ed8546fa75390eb4aa960d78f2936115f69b6826","id":"ayan7gn","kind":"document","match_hash":"b6a4e12f700a6214bf02c9a437559e82fccdbecea7b575fa438a7d2e8c33071a","node_hash":"5bca682ef032725a940cf6745051126022a1ad6bd465bb3c051dae35ddfbbd17","ord":0,"own_hash":"34444fe0e6bdbf3927a579fe3286859d204ea0882a381b7769ffd4905551d24f","parent":null,"text_hash":"bd2abc746d51cf52d57a18632383245d585e29e265426967894eed99bc937561"},{"align_key":"caf6aee3882b3890996037e35d0ffdfe30b5e66653d45321d7ea0fa9135f1ee9","attrs":{"level":1},"id":"mzkksmc","kind":"section","label":"1","match_hash":"5a96c01f314c4d8a22fd5e105bebef45df4301886cccbc2538ecefddfcb1d405","node_hash":"83b33c64f1aab4df2f6bc32ee36aa4e5f77410681e2dea8289f64d569fdafbcb","ord":0,"own_hash":"65da097b48f8237e8d171bfbd0053ff0e3795c0532d5522d23a92b53430956f4","parent":"ayan7gn","text_hash":"7a97628fe9643c0fca3614a077e2e631c3fee81d3733ba1564292df65a294573"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","id":"gtjh3x4","kind":"heading","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"a3c82ab1e44bf4cffa1a59507f8bddc39a1ad191321ee803d0d60d96609a44c7","ord":0,"own_hash":"e813ff86a8920cef741e548eb5df545774b520d2fadbdda6e2b872a6729b9d26","parent":"mzkksmc","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","content":[{"kind":"text","text":"Results"}],"fidelity":"F2","id":"fvafy61","kind":"sentence","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"293fc03ff4e39d7532467e4417ffcdb15f470f0bd3658dc1c021645ad53c9bef","ord":0,"own_hash":"c55f401232b19ff1133d77eac6c72868975064f177fc2d31ab8a181303c30599","parent":"gtjh3x4","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"610c235321e38145abff21e31eaab303d85a6ce0d1b9340a599dc821a04922f6","id":"gj83ag8","kind":"paragraph","match_hash":"bcb4902ec69f2a7a724384f561ee9aa0e214c824c5882c1ad92777bee680aad4","node_hash":"a5b991f3f9d3119d4b9f667004147da0c216eec55b8e15c1e90f4916486fd345","ord":1,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"f589ee914c46c4aea7ce2de49d925c433c914121f576b81a3eab692f17f179a3"},{"align_key":"8f21603c39df123eb8680362184274aac9bdf1dbd485b3bba230960179071c95","content":[{"kind":"text","text":"The value is 42.0 at 300 K."}],"fidelity":"F2","id":"v43b2r1","kind":"sentence","match_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3","node_hash":"f5761f338b2878dff39a6ee724a75b13626dd625a0df96cf1cc6f9e8258cb7cc","ord":0,"own_hash":"7f0b9190d076cf01b51a8be863a51e9a8eeaab5d3ec8656202ff4d41578d4d82","parent":"gj83ag8","text_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3"},{"align_key":"8e7fe271cd1bab88d86c8900fe3ffae0ce95c55af6659df1fca278d359296e2d","content":[{"kind":"text","text":"See "},{"kind":"xref","target":"r12vgb0","text":"Table 1"},{"kind":"text","text":" and the note."},{"kind":"cite","refs":["w1dkdqd"],"text":"1"}],"fidelity":"F2","id":"jss11hp","kind":"sentence","match_hash":"9894a48790a8561966e68672fa13da20541d25a5178653720648f62cbc9ad36b","node_hash":"ec5d51bd0027283d3a6f32dce37228ac9290eba1eae32b12c0d148ae247fbb78","ord":1,"own_hash":"d9635148479076021e01e1eb9185f1745a457bb2d238a4f9ed0b402ce254f91a","parent":"gj83ag8","text_hash":"5b1de19c9eb1399e54bde7e96f051cac8b337be1f307fac320292b8db7e39da7"},{"align_key":"282eea00b316a6950fb7179f6289a8b98359019e11cb23a3ff426cdecad90165","id":"pevazrk","kind":"paragraph","match_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1","node_hash":"062dd26bfb226ca9811d0dfb013df211cb201dfe7b4b25f9aba000f3212298d5","ord":2,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1"},{"align_key":"b8074a8bc013aa007023c4cce7334f1e1f22384d57a526ca22fd2f5589d5ab17","content":[{"kind":"text","text":"A second paragraph."}],"fidelity":"F2","id":"pd32xxr","kind":"sentence","match_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e","node_hash":"443539440dca73149ca75276492334ce9a5a6c50070137e8dc7bf414b75a27ab","ord":0,"own_hash":"acee4b6b1399bfde1dc67dea8d8c86b0db961d2af516ae4aaee26f294261456f","parent":"pevazrk","text_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e"},{"align_key":"437c101749b0481e615fc7eed8dcacb536b65400c7e0695ad4cbb2bf2dae488c","content":[{"kind":"text","text":"It ends here."}],"fidelity":"F2","id":"whq15cf","kind":"sentence","match_hash":"2888dbfa0c08fd65c58c923b920860c76e13913d9fa93b03eccbbf7bdf53b882","node_hash":"caeb3ae57d0821b8ac961c9507debbcda2b118b16cdc010b83a143a782058d57","ord":1,"own_hash":"705f77e8520c8c5ce3a7a495a20d8a9c55baa9801e49a55c341f5de79eeeLine truncated
|
||||
{"before":{"ids":[],"nodes":[{"align_key":"b3108c22ec836d883e5116f7ed8546fa75390eb4aa960d78f2936115f69b6826","id":"ayan7gn","kind":"document","match_hash":"b6a4e12f700a6214bf02c9a437559e82fccdbecea7b575fa438a7d2e8c33071a","node_hash":"5bca682ef032725a940cf6745051126022a1ad6bd465bb3c051dae35ddfbbd17","ord":0,"own_hash":"34444fe0e6bdbf3927a579fe3286859d204ea0882a381b7769ffd4905551d24f","parent":null,"text_hash":"bd2abc746d51cf52d57a18632383245d585e29e265426967894eed99bc937561"},{"align_key":"caf6aee3882b3890996037e35d0ffdfe30b5e66653d45321d7ea0fa9135f1ee9","attrs":{"level":1},"id":"mzkksmc","kind":"section","label":"1","match_hash":"5a96c01f314c4d8a22fd5e105bebef45df4301886cccbc2538ecefddfcb1d405","node_hash":"83b33c64f1aab4df2f6bc32ee36aa4e5f77410681e2dea8289f64d569fdafbcb","ord":0,"own_hash":"65da097b48f8237e8d171bfbd0053ff0e3795c0532d5522d23a92b53430956f4","parent":"ayan7gn","text_hash":"7a97628fe9643c0fca3614a077e2e631c3fee81d3733ba1564292df65a294573"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","id":"gtjh3x4","kind":"heading","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"a3c82ab1e44bf4cffa1a59507f8bddc39a1ad191321ee803d0d60d96609a44c7","ord":0,"own_hash":"e813ff86a8920cef741e548eb5df545774b520d2fadbdda6e2b872a6729b9d26","parent":"mzkksmc","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","content":[{"kind":"text","text":"Results"}],"fidelity":"F2","id":"fvafy61","kind":"sentence","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"293fc03ff4e39d7532467e4417ffcdb15f470f0bd3658dc1c021645ad53c9bef","ord":0,"own_hash":"c55f401232b19ff1133d77eac6c72868975064f177fc2d31ab8a181303c30599","parent":"gtjh3x4","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"610c235321e38145abff21e31eaab303d85a6ce0d1b9340a599dc821a04922f6","id":"gj83ag8","kind":"paragraph","match_hash":"bcb4902ec69f2a7a724384f561ee9aa0e214c824c5882c1ad92777bee680aad4","node_hash":"a5b991f3f9d3119d4b9f667004147da0c216eec55b8e15c1e90f4916486fd345","ord":1,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"f589ee914c46c4aea7ce2de49d925c433c914121f576b81a3eab692f17f179a3"},{"align_key":"8f21603c39df123eb8680362184274aac9bdf1dbd485b3bba230960179071c95","content":[{"kind":"text","text":"The value is 42.0 at 300 K."}],"fidelity":"F2","id":"v43b2r1","kind":"sentence","match_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3","node_hash":"f5761f338b2878dff39a6ee724a75b13626dd625a0df96cf1cc6f9e8258cb7cc","ord":0,"own_hash":"7f0b9190d076cf01b51a8be863a51e9a8eeaab5d3ec8656202ff4d41578d4d82","parent":"gj83ag8","text_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3"},{"align_key":"8e7fe271cd1bab88d86c8900fe3ffae0ce95c55af6659df1fca278d359296e2d","content":[{"kind":"text","text":"See "},{"kind":"xref","target":"r12vgb0","text":"Table 1"},{"kind":"text","text":" and the note."},{"kind":"cite","refs":["w1dkdqd"],"text":"1"}],"fidelity":"F2","id":"jss11hp","kind":"sentence","match_hash":"9894a48790a8561966e68672fa13da20541d25a5178653720648f62cbc9ad36b","node_hash":"ec5d51bd0027283d3a6f32dce37228ac9290eba1eae32b12c0d148ae247fbb78","ord":1,"own_hash":"d9635148479076021e01e1eb9185f1745a457bb2d238a4f9ed0b402ce254f91a","parent":"gj83ag8","text_hash":"5b1de19c9eb1399e54bde7e96f051cac8b337be1f307fac320292b8db7e39da7"},{"align_key":"282eea00b316a6950fb7179f6289a8b98359019e11cb23a3ff426cdecad90165","id":"pevazrk","kind":"paragraph","match_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1","node_hash":"062dd26bfb226ca9811d0dfb013df211cb201dfe7b4b25f9aba000f3212298d5","ord":2,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1"},{"align_key":"b8074a8bc013aa007023c4cce7334f1e1f22384d57a526ca22fd2f5589d5ab17","content":[{"kind":"text","text":"A second paragraph."}],"fidelity":"F2","id":"pd32xxr","kind":"sentence","match_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e","node_hash":"443539440dca73149ca75276492334ce9a5a6c50070137e8dc7bf414b75a27ab","ord":0,"own_hash":"acee4b6b1399bfde1dc67dea8d8c86b0db961d2af516ae4aaee26f294261456f","parent":"pevazrk","text_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e"},{"align_key":"437c101749b0481e615fc7eed8dcacb536b65400c7e0695ad4cbb2bf2dae488c","content":[{"kind":"text","text":"It ends here."}],"fidelity":"F2","id":"whq15cf","kind":"sentence","match_hash":"2888dbfa0c08fd65c58c923b920860c76e13913d9fa93b03eccbbf7bdf53b882","node_hash":"caeb3ae57d0821b8ac961c9507debbcda2b118b16cdc010b83a143a782058d57","ord":1,"own_hash":"705f77e8520c8c5ce3a7a495a20d8a9c55baa9801e49a55c341f5de79eeeLine truncated
|
||||
{"before":{"ids":[],"nodes":[{"align_key":"b3108c22ec836d883e5116f7ed8546fa75390eb4aa960d78f2936115f69b6826","id":"ayan7gn","kind":"document","match_hash":"b6a4e12f700a6214bf02c9a437559e82fccdbecea7b575fa438a7d2e8c33071a","node_hash":"5bca682ef032725a940cf6745051126022a1ad6bd465bb3c051dae35ddfbbd17","ord":0,"own_hash":"34444fe0e6bdbf3927a579fe3286859d204ea0882a381b7769ffd4905551d24f","parent":null,"text_hash":"bd2abc746d51cf52d57a18632383245d585e29e265426967894eed99bc937561"},{"align_key":"caf6aee3882b3890996037e35d0ffdfe30b5e66653d45321d7ea0fa9135f1ee9","attrs":{"level":1},"id":"mzkksmc","kind":"section","label":"1","match_hash":"5a96c01f314c4d8a22fd5e105bebef45df4301886cccbc2538ecefddfcb1d405","node_hash":"83b33c64f1aab4df2f6bc32ee36aa4e5f77410681e2dea8289f64d569fdafbcb","ord":0,"own_hash":"65da097b48f8237e8d171bfbd0053ff0e3795c0532d5522d23a92b53430956f4","parent":"ayan7gn","text_hash":"7a97628fe9643c0fca3614a077e2e631c3fee81d3733ba1564292df65a294573"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","id":"gtjh3x4","kind":"heading","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"a3c82ab1e44bf4cffa1a59507f8bddc39a1ad191321ee803d0d60d96609a44c7","ord":0,"own_hash":"e813ff86a8920cef741e548eb5df545774b520d2fadbdda6e2b872a6729b9d26","parent":"mzkksmc","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","content":[{"kind":"text","text":"Results"}],"fidelity":"F2","id":"fvafy61","kind":"sentence","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"293fc03ff4e39d7532467e4417ffcdb15f470f0bd3658dc1c021645ad53c9bef","ord":0,"own_hash":"c55f401232b19ff1133d77eac6c72868975064f177fc2d31ab8a181303c30599","parent":"gtjh3x4","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"610c235321e38145abff21e31eaab303d85a6ce0d1b9340a599dc821a04922f6","id":"gj83ag8","kind":"paragraph","match_hash":"bcb4902ec69f2a7a724384f561ee9aa0e214c824c5882c1ad92777bee680aad4","node_hash":"a5b991f3f9d3119d4b9f667004147da0c216eec55b8e15c1e90f4916486fd345","ord":1,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"f589ee914c46c4aea7ce2de49d925c433c914121f576b81a3eab692f17f179a3"},{"align_key":"8f21603c39df123eb8680362184274aac9bdf1dbd485b3bba230960179071c95","content":[{"kind":"text","text":"The value is 42.0 at 300 K."}],"fidelity":"F2","id":"v43b2r1","kind":"sentence","match_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3","node_hash":"f5761f338b2878dff39a6ee724a75b13626dd625a0df96cf1cc6f9e8258cb7cc","ord":0,"own_hash":"7f0b9190d076cf01b51a8be863a51e9a8eeaab5d3ec8656202ff4d41578d4d82","parent":"gj83ag8","text_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3"},{"align_key":"8e7fe271cd1bab88d86c8900fe3ffae0ce95c55af6659df1fca278d359296e2d","content":[{"kind":"text","text":"See "},{"kind":"xref","target":"r12vgb0","text":"Table 1"},{"kind":"text","text":" and the note."},{"kind":"cite","refs":["w1dkdqd"],"text":"1"}],"fidelity":"F2","id":"jss11hp","kind":"sentence","match_hash":"9894a48790a8561966e68672fa13da20541d25a5178653720648f62cbc9ad36b","node_hash":"ec5d51bd0027283d3a6f32dce37228ac9290eba1eae32b12c0d148ae247fbb78","ord":1,"own_hash":"d9635148479076021e01e1eb9185f1745a457bb2d238a4f9ed0b402ce254f91a","parent":"gj83ag8","text_hash":"5b1de19c9eb1399e54bde7e96f051cac8b337be1f307fac320292b8db7e39da7"},{"align_key":"282eea00b316a6950fb7179f6289a8b98359019e11cb23a3ff426cdecad90165","id":"pevazrk","kind":"paragraph","match_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1","node_hash":"062dd26bfb226ca9811d0dfb013df211cb201dfe7b4b25f9aba000f3212298d5","ord":2,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1"},{"align_key":"b8074a8bc013aa007023c4cce7334f1e1f22384d57a526ca22fd2f5589d5ab17","content":[{"kind":"text","text":"A second paragraph."}],"fidelity":"F2","id":"pd32xxr","kind":"sentence","match_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e","node_hash":"443539440dca73149ca75276492334ce9a5a6c50070137e8dc7bf414b75a27ab","ord":0,"own_hash":"acee4b6b1399bfde1dc67dea8d8c86b0db961d2af516ae4aaee26f294261456f","parent":"pevazrk","text_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e"},{"align_key":"437c101749b0481e615fc7eed8dcacb536b65400c7e0695ad4cbb2bf2dae488c","content":[{"kind":"text","text":"It ends here."}],"fidelity":"F2","id":"whq15cf","kind":"sentence","match_hash":"2888dbfa0c08fd65c58c923b920860c76e13913d9fa93b03eccbbf7bdf53b882","node_hash":"caeb3ae57d0821b8ac961c9507debbcda2b118b16cdc010b83a143a782058d57","ord":1,"own_hash":"705f77e8520c8c5ce3a7a495a20d8a9c55baa9801e49a55c341f5de79eeeLine truncated
|
||||
{"before":{"ids":[],"nodes":[{"align_key":"b3108c22ec836d883e5116f7ed8546fa75390eb4aa960d78f2936115f69b6826","id":"ayan7gn","kind":"document","match_hash":"b6a4e12f700a6214bf02c9a437559e82fccdbecea7b575fa438a7d2e8c33071a","node_hash":"5bca682ef032725a940cf6745051126022a1ad6bd465bb3c051dae35ddfbbd17","ord":0,"own_hash":"34444fe0e6bdbf3927a579fe3286859d204ea0882a381b7769ffd4905551d24f","parent":null,"text_hash":"bd2abc746d51cf52d57a18632383245d585e29e265426967894eed99bc937561"},{"align_key":"caf6aee3882b3890996037e35d0ffdfe30b5e66653d45321d7ea0fa9135f1ee9","attrs":{"level":1},"id":"mzkksmc","kind":"section","label":"1","match_hash":"5a96c01f314c4d8a22fd5e105bebef45df4301886cccbc2538ecefddfcb1d405","node_hash":"83b33c64f1aab4df2f6bc32ee36aa4e5f77410681e2dea8289f64d569fdafbcb","ord":0,"own_hash":"65da097b48f8237e8d171bfbd0053ff0e3795c0532d5522d23a92b53430956f4","parent":"ayan7gn","text_hash":"7a97628fe9643c0fca3614a077e2e631c3fee81d3733ba1564292df65a294573"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","id":"gtjh3x4","kind":"heading","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"a3c82ab1e44bf4cffa1a59507f8bddc39a1ad191321ee803d0d60d96609a44c7","ord":0,"own_hash":"e813ff86a8920cef741e548eb5df545774b520d2fadbdda6e2b872a6729b9d26","parent":"mzkksmc","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","content":[{"kind":"text","text":"Results"}],"fidelity":"F2","id":"fvafy61","kind":"sentence","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"293fc03ff4e39d7532467e4417ffcdb15f470f0bd3658dc1c021645ad53c9bef","ord":0,"own_hash":"c55f401232b19ff1133d77eac6c72868975064f177fc2d31ab8a181303c30599","parent":"gtjh3x4","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"610c235321e38145abff21e31eaab303d85a6ce0d1b9340a599dc821a04922f6","id":"gj83ag8","kind":"paragraph","match_hash":"bcb4902ec69f2a7a724384f561ee9aa0e214c824c5882c1ad92777bee680aad4","node_hash":"a5b991f3f9d3119d4b9f667004147da0c216eec55b8e15c1e90f4916486fd345","ord":1,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"f589ee914c46c4aea7ce2de49d925c433c914121f576b81a3eab692f17f179a3"},{"align_key":"8f21603c39df123eb8680362184274aac9bdf1dbd485b3bba230960179071c95","content":[{"kind":"text","text":"The value is 42.0 at 300 K."}],"fidelity":"F2","id":"v43b2r1","kind":"sentence","match_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3","node_hash":"f5761f338b2878dff39a6ee724a75b13626dd625a0df96cf1cc6f9e8258cb7cc","ord":0,"own_hash":"7f0b9190d076cf01b51a8be863a51e9a8eeaab5d3ec8656202ff4d41578d4d82","parent":"gj83ag8","text_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3"},{"align_key":"8e7fe271cd1bab88d86c8900fe3ffae0ce95c55af6659df1fca278d359296e2d","content":[{"kind":"text","text":"See "},{"kind":"xref","target":"r12vgb0","text":"Table 1"},{"kind":"text","text":" and the note."},{"kind":"cite","refs":["w1dkdqd"],"text":"1"}],"fidelity":"F2","id":"jss11hp","kind":"sentence","match_hash":"9894a48790a8561966e68672fa13da20541d25a5178653720648f62cbc9ad36b","node_hash":"ec5d51bd0027283d3a6f32dce37228ac9290eba1eae32b12c0d148ae247fbb78","ord":1,"own_hash":"d9635148479076021e01e1eb9185f1745a457bb2d238a4f9ed0b402ce254f91a","parent":"gj83ag8","text_hash":"5b1de19c9eb1399e54bde7e96f051cac8b337be1f307fac320292b8db7e39da7"},{"align_key":"282eea00b316a6950fb7179f6289a8b98359019e11cb23a3ff426cdecad90165","id":"pevazrk","kind":"paragraph","match_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1","node_hash":"062dd26bfb226ca9811d0dfb013df211cb201dfe7b4b25f9aba000f3212298d5","ord":2,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1"},{"align_key":"b8074a8bc013aa007023c4cce7334f1e1f22384d57a526ca22fd2f5589d5ab17","content":[{"kind":"text","text":"A second paragraph."}],"fidelity":"F2","id":"pd32xxr","kind":"sentence","match_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e","node_hash":"443539440dca73149ca75276492334ce9a5a6c50070137e8dc7bf414b75a27ab","ord":0,"own_hash":"acee4b6b1399bfde1dc67dea8d8c86b0db961d2af516ae4aaee26f294261456f","parent":"pevazrk","text_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e"},{"align_key":"437c101749b0481e615fc7eed8dcacb536b65400c7e0695ad4cbb2bf2dae488c","content":[{"kind":"text","text":"It ends here."}],"fidelity":"F2","id":"whq15cf","kind":"sentence","match_hash":"2888dbfa0c08fd65c58c923b920860c76e13913d9fa93b03eccbbf7bdf53b882","node_hash":"caeb3ae57d0821b8ac961c9507debbcda2b118b16cdc010b83a143a782058d57","ord":1,"own_hash":"705f77e8520c8c5ce3a7a495a20d8a9c55baa9801e49a55c341f5de79eeeLine truncated
|
||||
{"before":{"ids":[],"nodes":[{"align_key":"b3108c22ec836d883e5116f7ed8546fa75390eb4aa960d78f2936115f69b6826","id":"ayan7gn","kind":"document","match_hash":"b6a4e12f700a6214bf02c9a437559e82fccdbecea7b575fa438a7d2e8c33071a","node_hash":"5bca682ef032725a940cf6745051126022a1ad6bd465bb3c051dae35ddfbbd17","ord":0,"own_hash":"34444fe0e6bdbf3927a579fe3286859d204ea0882a381b7769ffd4905551d24f","parent":null,"text_hash":"bd2abc746d51cf52d57a18632383245d585e29e265426967894eed99bc937561"},{"align_key":"caf6aee3882b3890996037e35d0ffdfe30b5e66653d45321d7ea0fa9135f1ee9","attrs":{"level":1},"id":"mzkksmc","kind":"section","label":"1","match_hash":"5a96c01f314c4d8a22fd5e105bebef45df4301886cccbc2538ecefddfcb1d405","node_hash":"83b33c64f1aab4df2f6bc32ee36aa4e5f77410681e2dea8289f64d569fdafbcb","ord":0,"own_hash":"65da097b48f8237e8d171bfbd0053ff0e3795c0532d5522d23a92b53430956f4","parent":"ayan7gn","text_hash":"7a97628fe9643c0fca3614a077e2e631c3fee81d3733ba1564292df65a294573"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","id":"gtjh3x4","kind":"heading","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"a3c82ab1e44bf4cffa1a59507f8bddc39a1ad191321ee803d0d60d96609a44c7","ord":0,"own_hash":"e813ff86a8920cef741e548eb5df545774b520d2fadbdda6e2b872a6729b9d26","parent":"mzkksmc","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","content":[{"kind":"text","text":"Results"}],"fidelity":"F2","id":"fvafy61","kind":"sentence","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"293fc03ff4e39d7532467e4417ffcdb15f470f0bd3658dc1c021645ad53c9bef","ord":0,"own_hash":"c55f401232b19ff1133d77eac6c72868975064f177fc2d31ab8a181303c30599","parent":"gtjh3x4","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"610c235321e38145abff21e31eaab303d85a6ce0d1b9340a599dc821a04922f6","id":"gj83ag8","kind":"paragraph","match_hash":"bcb4902ec69f2a7a724384f561ee9aa0e214c824c5882c1ad92777bee680aad4","node_hash":"a5b991f3f9d3119d4b9f667004147da0c216eec55b8e15c1e90f4916486fd345","ord":1,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"f589ee914c46c4aea7ce2de49d925c433c914121f576b81a3eab692f17f179a3"},{"align_key":"8f21603c39df123eb8680362184274aac9bdf1dbd485b3bba230960179071c95","content":[{"kind":"text","text":"The value is 42.0 at 300 K."}],"fidelity":"F2","id":"v43b2r1","kind":"sentence","match_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3","node_hash":"f5761f338b2878dff39a6ee724a75b13626dd625a0df96cf1cc6f9e8258cb7cc","ord":0,"own_hash":"7f0b9190d076cf01b51a8be863a51e9a8eeaab5d3ec8656202ff4d41578d4d82","parent":"gj83ag8","text_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3"},{"align_key":"8e7fe271cd1bab88d86c8900fe3ffae0ce95c55af6659df1fca278d359296e2d","content":[{"kind":"text","text":"See "},{"kind":"xref","target":"r12vgb0","text":"Table 1"},{"kind":"text","text":" and the note."},{"kind":"cite","refs":["w1dkdqd"],"text":"1"}],"fidelity":"F2","id":"jss11hp","kind":"sentence","match_hash":"9894a48790a8561966e68672fa13da20541d25a5178653720648f62cbc9ad36b","node_hash":"ec5d51bd0027283d3a6f32dce37228ac9290eba1eae32b12c0d148ae247fbb78","ord":1,"own_hash":"d9635148479076021e01e1eb9185f1745a457bb2d238a4f9ed0b402ce254f91a","parent":"gj83ag8","text_hash":"5b1de19c9eb1399e54bde7e96f051cac8b337be1f307fac320292b8db7e39da7"},{"align_key":"282eea00b316a6950fb7179f6289a8b98359019e11cb23a3ff426cdecad90165","id":"pevazrk","kind":"paragraph","match_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1","node_hash":"062dd26bfb226ca9811d0dfb013df211cb201dfe7b4b25f9aba000f3212298d5","ord":2,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1"},{"align_key":"b8074a8bc013aa007023c4cce7334f1e1f22384d57a526ca22fd2f5589d5ab17","content":[{"kind":"text","text":"A second paragraph."}],"fidelity":"F2","id":"pd32xxr","kind":"sentence","match_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e","node_hash":"443539440dca73149ca75276492334ce9a5a6c50070137e8dc7bf414b75a27ab","ord":0,"own_hash":"acee4b6b1399bfde1dc67dea8d8c86b0db961d2af516ae4aaee26f294261456f","parent":"pevazrk","text_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e"},{"align_key":"437c101749b0481e615fc7eed8dcacb536b65400c7e0695ad4cbb2bf2dae488c","content":[{"kind":"text","text":"It ends here."}],"fidelity":"F2","id":"whq15cf","kind":"sentence","match_hash":"2888dbfa0c08fd65c58c923b920860c76e13913d9fa93b03eccbbf7bdf53b882","node_hash":"caeb3ae57d0821b8ac961c9507debbcda2b118b16cdc010b83a143a782058d57","ord":1,"own_hash":"705f77e8520c8c5ce3a7a495a20d8a9c55baa9801e49a55c341f5de79eeeLine truncated
|
||||
{"before":{"ids":[],"nodes":[{"align_key":"b3108c22ec836d883e5116f7ed8546fa75390eb4aa960d78f2936115f69b6826","id":"ayan7gn","kind":"document","match_hash":"b6a4e12f700a6214bf02c9a437559e82fccdbecea7b575fa438a7d2e8c33071a","node_hash":"5bca682ef032725a940cf6745051126022a1ad6bd465bb3c051dae35ddfbbd17","ord":0,"own_hash":"34444fe0e6bdbf3927a579fe3286859d204ea0882a381b7769ffd4905551d24f","parent":null,"text_hash":"bd2abc746d51cf52d57a18632383245d585e29e265426967894eed99bc937561"},{"align_key":"caf6aee3882b3890996037e35d0ffdfe30b5e66653d45321d7ea0fa9135f1ee9","attrs":{"level":1},"id":"mzkksmc","kind":"section","label":"1","match_hash":"5a96c01f314c4d8a22fd5e105bebef45df4301886cccbc2538ecefddfcb1d405","node_hash":"83b33c64f1aab4df2f6bc32ee36aa4e5f77410681e2dea8289f64d569fdafbcb","ord":0,"own_hash":"65da097b48f8237e8d171bfbd0053ff0e3795c0532d5522d23a92b53430956f4","parent":"ayan7gn","text_hash":"7a97628fe9643c0fca3614a077e2e631c3fee81d3733ba1564292df65a294573"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","id":"gtjh3x4","kind":"heading","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"a3c82ab1e44bf4cffa1a59507f8bddc39a1ad191321ee803d0d60d96609a44c7","ord":0,"own_hash":"e813ff86a8920cef741e548eb5df545774b520d2fadbdda6e2b872a6729b9d26","parent":"mzkksmc","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","content":[{"kind":"text","text":"Results"}],"fidelity":"F2","id":"fvafy61","kind":"sentence","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"293fc03ff4e39d7532467e4417ffcdb15f470f0bd3658dc1c021645ad53c9bef","ord":0,"own_hash":"c55f401232b19ff1133d77eac6c72868975064f177fc2d31ab8a181303c30599","parent":"gtjh3x4","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"610c235321e38145abff21e31eaab303d85a6ce0d1b9340a599dc821a04922f6","id":"gj83ag8","kind":"paragraph","match_hash":"bcb4902ec69f2a7a724384f561ee9aa0e214c824c5882c1ad92777bee680aad4","node_hash":"a5b991f3f9d3119d4b9f667004147da0c216eec55b8e15c1e90f4916486fd345","ord":1,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"f589ee914c46c4aea7ce2de49d925c433c914121f576b81a3eab692f17f179a3"},{"align_key":"8f21603c39df123eb8680362184274aac9bdf1dbd485b3bba230960179071c95","content":[{"kind":"text","text":"The value is 42.0 at 300 K."}],"fidelity":"F2","id":"v43b2r1","kind":"sentence","match_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3","node_hash":"f5761f338b2878dff39a6ee724a75b13626dd625a0df96cf1cc6f9e8258cb7cc","ord":0,"own_hash":"7f0b9190d076cf01b51a8be863a51e9a8eeaab5d3ec8656202ff4d41578d4d82","parent":"gj83ag8","text_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3"},{"align_key":"8e7fe271cd1bab88d86c8900fe3ffae0ce95c55af6659df1fca278d359296e2d","content":[{"kind":"text","text":"See "},{"kind":"xref","target":"r12vgb0","text":"Table 1"},{"kind":"text","text":" and the note."},{"kind":"cite","refs":["w1dkdqd"],"text":"1"}],"fidelity":"F2","id":"jss11hp","kind":"sentence","match_hash":"9894a48790a8561966e68672fa13da20541d25a5178653720648f62cbc9ad36b","node_hash":"ec5d51bd0027283d3a6f32dce37228ac9290eba1eae32b12c0d148ae247fbb78","ord":1,"own_hash":"d9635148479076021e01e1eb9185f1745a457bb2d238a4f9ed0b402ce254f91a","parent":"gj83ag8","text_hash":"5b1de19c9eb1399e54bde7e96f051cac8b337be1f307fac320292b8db7e39da7"},{"align_key":"282eea00b316a6950fb7179f6289a8b98359019e11cb23a3ff426cdecad90165","id":"pevazrk","kind":"paragraph","match_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1","node_hash":"062dd26bfb226ca9811d0dfb013df211cb201dfe7b4b25f9aba000f3212298d5","ord":2,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1"},{"align_key":"b8074a8bc013aa007023c4cce7334f1e1f22384d57a526ca22fd2f5589d5ab17","content":[{"kind":"text","text":"A second paragraph."}],"fidelity":"F2","id":"pd32xxr","kind":"sentence","match_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e","node_hash":"443539440dca73149ca75276492334ce9a5a6c50070137e8dc7bf414b75a27ab","ord":0,"own_hash":"acee4b6b1399bfde1dc67dea8d8c86b0db961d2af516ae4aaee26f294261456f","parent":"pevazrk","text_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e"},{"align_key":"437c101749b0481e615fc7eed8dcacb536b65400c7e0695ad4cbb2bf2dae488c","content":[{"kind":"text","text":"It ends here."}],"fidelity":"F2","id":"whq15cf","kind":"sentence","match_hash":"2888dbfa0c08fd65c58c923b920860c76e13913d9fa93b03eccbbf7bdf53b882","node_hash":"caeb3ae57d0821b8ac961c9507debbcda2b118b16cdc010b83a143a782058d57","ord":1,"own_hash":"705f77e8520c8c5ce3a7a495a20d8a9c55baa9801e49a55c341f5de79eeeLine truncated
|
||||
{"before":{"ids":[],"nodes":[{"align_key":"b3108c22ec836d883e5116f7ed8546fa75390eb4aa960d78f2936115f69b6826","id":"ayan7gn","kind":"document","match_hash":"b6a4e12f700a6214bf02c9a437559e82fccdbecea7b575fa438a7d2e8c33071a","node_hash":"5bca682ef032725a940cf6745051126022a1ad6bd465bb3c051dae35ddfbbd17","ord":0,"own_hash":"34444fe0e6bdbf3927a579fe3286859d204ea0882a381b7769ffd4905551d24f","parent":null,"text_hash":"bd2abc746d51cf52d57a18632383245d585e29e265426967894eed99bc937561"},{"align_key":"caf6aee3882b3890996037e35d0ffdfe30b5e66653d45321d7ea0fa9135f1ee9","attrs":{"level":1},"id":"mzkksmc","kind":"section","label":"1","match_hash":"5a96c01f314c4d8a22fd5e105bebef45df4301886cccbc2538ecefddfcb1d405","node_hash":"83b33c64f1aab4df2f6bc32ee36aa4e5f77410681e2dea8289f64d569fdafbcb","ord":0,"own_hash":"65da097b48f8237e8d171bfbd0053ff0e3795c0532d5522d23a92b53430956f4","parent":"ayan7gn","text_hash":"7a97628fe9643c0fca3614a077e2e631c3fee81d3733ba1564292df65a294573"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","id":"gtjh3x4","kind":"heading","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"a3c82ab1e44bf4cffa1a59507f8bddc39a1ad191321ee803d0d60d96609a44c7","ord":0,"own_hash":"e813ff86a8920cef741e548eb5df545774b520d2fadbdda6e2b872a6729b9d26","parent":"mzkksmc","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"c099142bc3186ded72786ba27e9ea6d2da240fb9fd3fe79b479ecf8e734b2850","content":[{"kind":"text","text":"Results"}],"fidelity":"F2","id":"fvafy61","kind":"sentence","match_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd","node_hash":"293fc03ff4e39d7532467e4417ffcdb15f470f0bd3658dc1c021645ad53c9bef","ord":0,"own_hash":"c55f401232b19ff1133d77eac6c72868975064f177fc2d31ab8a181303c30599","parent":"gtjh3x4","text_hash":"219c4a6c86a716e99d5dc9611b61e584deb11b57e972d87d4b773387ebc3cadd"},{"align_key":"610c235321e38145abff21e31eaab303d85a6ce0d1b9340a599dc821a04922f6","id":"gj83ag8","kind":"paragraph","match_hash":"bcb4902ec69f2a7a724384f561ee9aa0e214c824c5882c1ad92777bee680aad4","node_hash":"a5b991f3f9d3119d4b9f667004147da0c216eec55b8e15c1e90f4916486fd345","ord":1,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"f589ee914c46c4aea7ce2de49d925c433c914121f576b81a3eab692f17f179a3"},{"align_key":"8f21603c39df123eb8680362184274aac9bdf1dbd485b3bba230960179071c95","content":[{"kind":"text","text":"The value is 42.0 at 300 K."}],"fidelity":"F2","id":"v43b2r1","kind":"sentence","match_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3","node_hash":"f5761f338b2878dff39a6ee724a75b13626dd625a0df96cf1cc6f9e8258cb7cc","ord":0,"own_hash":"7f0b9190d076cf01b51a8be863a51e9a8eeaab5d3ec8656202ff4d41578d4d82","parent":"gj83ag8","text_hash":"3bcf9fa8412dca28be91d6423460233ddedd7add6c324d8a0f2b26038567d3d3"},{"align_key":"8e7fe271cd1bab88d86c8900fe3ffae0ce95c55af6659df1fca278d359296e2d","content":[{"kind":"text","text":"See "},{"kind":"xref","target":"r12vgb0","text":"Table 1"},{"kind":"text","text":" and the note."},{"kind":"cite","refs":["w1dkdqd"],"text":"1"}],"fidelity":"F2","id":"jss11hp","kind":"sentence","match_hash":"9894a48790a8561966e68672fa13da20541d25a5178653720648f62cbc9ad36b","node_hash":"ec5d51bd0027283d3a6f32dce37228ac9290eba1eae32b12c0d148ae247fbb78","ord":1,"own_hash":"d9635148479076021e01e1eb9185f1745a457bb2d238a4f9ed0b402ce254f91a","parent":"gj83ag8","text_hash":"5b1de19c9eb1399e54bde7e96f051cac8b337be1f307fac320292b8db7e39da7"},{"align_key":"282eea00b316a6950fb7179f6289a8b98359019e11cb23a3ff426cdecad90165","id":"pevazrk","kind":"paragraph","match_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1","node_hash":"062dd26bfb226ca9811d0dfb013df211cb201dfe7b4b25f9aba000f3212298d5","ord":2,"own_hash":"494e069be36a18ecaabf5a63be7c31617ade7a5460218ca83a2de3d8d8f964e0","parent":"mzkksmc","text_hash":"da88f35d24faf0099610c720953c4919647a5dfb9003889a15c464cd9c540fb1"},{"align_key":"b8074a8bc013aa007023c4cce7334f1e1f22384d57a526ca22fd2f5589d5ab17","content":[{"kind":"text","text":"A second paragraph."}],"fidelity":"F2","id":"pd32xxr","kind":"sentence","match_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e","node_hash":"443539440dca73149ca75276492334ce9a5a6c50070137e8dc7bf414b75a27ab","ord":0,"own_hash":"acee4b6b1399bfde1dc67dea8d8c86b0db961d2af516ae4aaee26f294261456f","parent":"pevazrk","text_hash":"2e8313b033de5e17878ae778d6293bbd6e197a072f36acf7fe0840a690a7f31e"},{"align_key":"437c101749b0481e615fc7eed8dcacb536b65400c7e0695ad4cbb2bf2dae488c","content":[{"kind":"text","text":"It ends here."}],"fidelity":"F2","id":"whq15cf","kind":"sentence","match_hash":"2888dbfa0c08fd65c58c923b920860c76e13913d9fa93b03eccbbf7bdf53b882","node_hash":"caeb3ae57d0821b8ac961c9507debbcda2b118b16cdc010b83a143a782058d57","ord":1,"own_hash":"705f77e8520c8c5ce3a7a495a20d8a9c55baa9801e49a55c341f5de79eeeLine truncated
|
||||
|
||||
Reference in new issue
Block a user