Files
paperspec/tools/build.py
T
alexm 002e2c3552
check / check (default, macos-latest) (push) Canceled after 0s
check / check (default, ubuntu-latest) (push) Canceled after 0s
check / check (py310, macos-latest) (push) Canceled after 0s
check / check (py310, ubuntu-latest) (push) Canceled after 0s
feat: rewrite docs
2026-10-12 07:55:22 -04:00

1547 lines
85 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Build every fixture and test vector from fixtures/src and the cases below.
Non-normative tooling.
The build is deterministic: run it twice and the output is byte-identical; `tools/check.py` verifies that.
Usage: python3 tools/build.py [--out DIR] (default: the repository itself)
"""
import base64
import copy
import hashlib
import json
import os
import shutil
import struct
import sys
import zlib
from fractions import Fraction
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT)
from tools.oracle import align as AL # noqa: E402
from tools.oracle import anchors as AN # noqa: E402
from tools.oracle import corrections as CO # noqa: E402
from tools.oracle import crypto, fingerprint as FP, grade as GR, hashing, jcs, receipts as RC, rights as RI # noqa: E402
from tools.oracle.model import LEAVES, MATH_KINDS, RUN_KINDS, VERBATIM_KINDS, Doc # noqa: E402
from tools.oracle.notation import fold_math # noqa: E402
from tools.oracle.package import jsonl, line, witness_id, write # noqa: E402
from tools.oracle.text import letter_first, mint_node_id, norm, record_id, sha256_hex # noqa: E402
from tools.oracle.tree import Refusal, _flatten, mint_ids, validate_structure # noqa: E402
VERSION = "0.1.0"
STORE = "smithlab"
def H(s):
return sha256_hex(s)
def secret(name):
return hashlib.sha256(("manuspec fixture key: " + name).encode()).digest()
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"]},
}
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",
"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}
ROSTER = roster()
ROSTER_BY_ID = {k["id"]: k for k in ROSTER["keys"]}
def png(w, h, rgb):
"""A tiny valid PNG, so a fixture can carry a real image asset."""
raw = b"".join(b"\x00" + bytes(rgb) * w for _ in range(h))
def chunk(t, d):
return struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xFFFFFFFF)
return (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0))
+ chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b""))
# --- the shorthand of fixtures/src ------------------------------------------------
def runs_of(parts, kids):
out = []
for p in parts:
if isinstance(p, str):
out.append({"kind": "text", "text": p})
elif "sup" in p or "sub" in p or "em" in p:
st = next(k for k in ("sup", "sub", "em") if k in p)
out.append({"kind": "text", "text": p[st], "style": [st]})
elif "m" in p:
out.append({"kind": "math", "notation": "tex", "source": p["m"]})
elif "t" in p:
out.append({"kind": "math", "notation": "typst", "source": p["t"]})
elif "cite" in p:
out.append({"kind": "cite", "refs": p["cite"], "text": p["text"]})
elif "xref" in p:
out.append({"kind": "xref", "target": p["xref"], "text": p["text"]})
elif "fn" in p:
out.append({"kind": "fnmark", "footnote": p["fn"], "text": p["text"]})
elif "link" in p:
out.append({"kind": "link", "url": p["link"], "text": p["text"]})
elif "eq" in p:
e = p["eq"]
kids.append(_math_node("equation", e))
out.append({"kind": "equation", "child": len(kids) - 1})
else:
raise ValueError(p)
return out
def _math_node(kind, e):
n = {"kind": kind, "content": {"notation": "typst" if "typst" in e else "tex", "source": e.get("tex", e.get("typst"))}}
if "label" in e:
n["label"] = e["label"]
for k in ("name", "fidelity"):
if k in e:
n["_" + k if k == "name" else k] = e[k]
return n
def expand(s, page=None):
page = s.get("page", page)
if isinstance(s, str):
s = {"s": [s]}
if "s" in s:
kids = []
n = {"kind": "sentence", "content": runs_of(s["s"], kids), "children": kids}
elif "p" in s:
n = {"kind": "paragraph", "children": [expand(x, page) for x in s["p"]]}
elif "row" in s:
n = {"kind": "row", "children": [expand(x, page) for x in s["row"]]}
if "header_path" in s:
n["attrs"] = {"header_path": s["header_path"]}
elif "c" in s:
n = {"kind": "cell", "content": runs_of(s["c"], []),
"attrs": {"column": s["col"], "rowspan": s.get("rowspan", 1), "colspan": s.get("colspan", 1),
"role": s.get("role", "data")}}
n["fidelity"] = s.get("fidelity", "F1")
else:
n = {"kind": s["kind"]}
if "label" in s:
n["label"] = s["label"]
kids = []
if s["kind"] == "section":
n["attrs"] = {"level": s["level"]}
if "title" in s:
kids.append({"kind": "heading", "children": [expand({"s": [s["title"]], "fidelity": "F2"}, page)]})
if s["kind"] == "table":
n["attrs"] = {"columns": [{"id": c["name"], "header_path": c["header_path"]} for c in s["columns"]]}
if s["kind"] == "panel":
n["attrs"] = {}
if "figure_text" in s:
n["attrs"]["figure_text"] = [dict(t) for t in s["figure_text"]]
if s["kind"] in ("reference", "authors", "keywords"):
n["content"] = runs_of(s["runs"], [])
if s["kind"] in MATH_KINDS:
n = _math_node(s["kind"], s)
for k in ("csl", "resolution"):
if k in s:
n["_" + k] = s[k]
kids += [expand(x, page) for x in s.get("children", [])]
n["children"] = kids
if "name" in s:
n["_name"] = s["name"]
if "fidelity" in s and n["kind"] in LEAVES:
n["fidelity"] = s["fidelity"]
n["_page"] = page
for c in n.get("children", []):
if c.get("_page") is None:
c["_page"] = page
return n
class Names:
"""Local names (`@x`) and the IDs minted for them, per document."""
registry = {}
def __init__(self, key):
self.key = key
self.ids = {}
Names.registry[key] = self
def resolve(self, v):
if isinstance(v, str) and v.startswith("ext:"):
_, doc, name = v.split(":", 2)
return doc + "#" + Names.registry[doc].ids[name]
if isinstance(v, str) and v.startswith("@"):
return self.ids[v]
if isinstance(v, list):
return [self.resolve(x) for x in v]
return v
def resolve_refs(nodes, names):
for n in nodes:
if n["kind"] in RUN_KINDS:
out = []
for r in n["content"]:
r = dict(r)
if r["kind"] == "cite":
r["refs"] = names.resolve(r["refs"])
elif r["kind"] == "xref":
r["target"] = names.resolve(r["target"])
elif r["kind"] == "fnmark":
r["footnote"] = names.resolve(r["footnote"])
out.append(r)
n["content"] = out
a = n.get("attrs")
if a:
if "header_path" in a:
a["header_path"] = names.resolve(a["header_path"])
if "column" in a:
a["column"] = names.resolve(a["column"])
if "columns" in a:
a["columns"] = [{"id": names.resolve(c["id"]), "header_path": names.resolve(c["header_path"])}
for c in a["columns"]]
def mint_extras(key, nodes, names, used):
"""Columns and figure-text items get IDs from the same minting rule, kinds `column` and `figure-text`."""
doc = Doc(key, nodes)
seen = {}
def mint(kind, th):
occ = seen.get((kind, th), 0)
seen[(kind, th)] = occ + 1
nid = mint_node_id(key, kind, th, occ)
while nid in used:
occ += 1
nid = mint_node_id(key, kind, th, occ)
used.add(nid)
return nid
for n in nodes:
a = n.get("attrs", {})
for c in a.get("columns", []):
if c["id"].startswith("@"):
th = H(norm(" ".join(hashing.plain(doc, names.ids[h]) for h in c["header_path"])))
names.ids[c["id"]] = mint("column", th)
for t in a.get("figure_text", []):
t["id"] = mint("figure-text", H(norm(t["text"])))
if "name" in t:
names.ids[t.pop("name")] = t["id"]
def flatten_spec(spec):
flat = []
_flatten(copy.deepcopy(spec), None, 0, flat)
return flat
def establish(key, spec, names, used):
flat = flatten_spec(spec)
nodes = mint_ids(key, flat, used)
side = {}
for f, n in zip(flat, nodes):
if "_name" in f:
names.ids[f["_name"]] = n["id"]
side[n["id"]] = {k: v for k, v in f.items() if k.startswith("_")}
mint_extras(key, nodes, names, used)
resolve_refs(nodes, names)
return nodes, side
# --- geometry -----------------------------------------------------------------
def page_layout(doc, side, src, wit_id, fps_override=None):
"""Synthetic but consistent geometry: lines per page, fingerprints and boxes."""
P = src["pages"]
size = P["size"]
npages = len(P["labels"])
stamps = json.load(open(os.path.join(ROOT, "registry", "stamps", "scienting-stamps-1.json")))["rules"]
lines = {p: [] for p in range(1, npages + 1)}
boxes = {}
y = {p: 72.0 for p in lines}
for p, rh in P.get("running_head", {}).items():
lines[int(p)].append({"text": rh, "box": [72.0, 40.0, 400.0, 50.0]})
for nid in doc.preorder():
n = doc.nodes[nid]
pg = side.get(nid, {}).get("_page") or 1
if n["kind"] in ("sentence", "cell", "reference", "authors", "keywords") or (
n["kind"] == "equation" and doc.nodes[n["parent"]]["kind"] != "sentence"):
text = norm(hashing.plain(doc, nid))
lines[pg].append({"text": text})
bx = []
rest = len(text)
while True:
w = min(rest, 80)
bx.append([72.0, y[pg], round(72.0 + 5.85 * w, 2), round(y[pg] + 11.8, 2)])
y[pg] = round(y[pg] + 13.95, 2)
rest -= 80
if rest <= 0:
break
boxes[nid] = (pg, bx)
elif n["kind"] == "panel":
boxes[nid] = (pg, [[72.0, y[pg], 300.0, round(y[pg] + 150.0, 2)]])
y[pg] = round(y[pg] + 160.0, 2)
pages = []
images = {}
for nid, s in side.items():
if "_image" in s:
images.setdefault(boxes[nid][0], []).append(s["_image"])
for p in range(1, npages + 1):
ls = list(lines[p])
if "stamp" in P:
ls.append({"text": P["stamp"], "box": [8.0, 200.0, 20.0, 600.0]})
ls.append({"text": P["stamp2"], "box": [580.0, 200.0, 592.0, 600.0]})
page_in = {"size": size, "lines": ls, "images": images.get(p, [])}
fp, removed = FP.fingerprint(page_in, stamps)
rec = {"witness": wit_id, "page": p, "page_fp": fp, "size": size, "page_label": P["labels"][p - 1],
"text_layer": True}
if removed:
rec["stamps_removed"] = removed
pages.append((rec, page_in))
return pages, boxes
# --- packages -------------------------------------------------------------------
def rights_record(kind):
base = {"format": "scienting-rights/1", "at": "2026-09-15T12:00:00Z"}
if kind == "open":
base.update({"license": {"spdx": "CC-BY-4.0", "url": "https://creativecommons.org/licenses/by/4.0/"},
"basis": "open-license",
"uses": {"read": True, "tdm": True, "redistribute": True, "quote-public": True,
"accessible-copy": True, "ai": {p: True for p in RI.AI_PAIRS}},
"asserted_by": {"by": "crossref", "evidence": [
{"kind": "crossref-license", "doi": "10.5555/example.okafor.2025",
"url": "https://creativecommons.org/licenses/by/4.0/"}]},
"valid_until": None})
elif kind == "own":
base.update({"license": {"spdx": "CC-BY-4.0"}, "basis": "own-work",
"uses": {"read": True, "tdm": True, "redistribute": True, "quote-public": True,
"accessible-copy": True, "ai": {p: True for p in RI.AI_PAIRS}},
"asserted_by": {"by": "ana", "evidence": [{"kind": "statement", "note": "The team's own draft."}]},
"valid_until": None})
else:
base.update({"license": {"text": "Publisher's subscription terms (fictional)."}, "basis": "subscription",
"uses": {"read": True, "tdm": True, "redistribute": False, "quote-public": False,
"accessible-copy": "unknown",
"ai": {"internal/local": True, "internal/tenancy": "unknown",
"internal/outside-no-retention": "unknown", "internal/outside": False,
"external/local": False, "external/tenancy": False,
"external/outside-no-retention": False, "external/outside": False}},
"asserted_by": {"by": "ana", "evidence": [
{"kind": "statement", "note": "The library's subscription terms, as the team read them (fictional)."}]},
"valid_until": "2027-08-31T23:59:59Z"})
return base
def crop_sha(name):
return H("crop of " + name + " at 300 dpi (synthetic)")
def node_record(n, values):
rec = {k: n[k] for k in ("id", "kind", "parent", "ord")}
for k in ("label", "attrs", "content", "withheld", "fidelity", "prov"):
if k in n and n[k] not in (None, {}, []):
rec[k] = n[k]
rec.update(values[n["id"]])
return rec
def seal_receipts(spec, names, state_by_rev, final_state, key, wit_sha):
"""Build and sign the receipts and revocations a source lists."""
out = []
by_name = {}
final_doc = final_state.doc()
final_vals = hashing.compute(final_doc)
for r in spec:
if "revoke" in r:
rec = {"format": "scienting-revocation/1", "receipt": by_name[r["revoke"]]["id"], "reason": r["reason"],
"by": r["by"], "at": r["at"]}
rec = RC.seal(rec, secret(r["by"]), r["by"])
elif "qa_of" in r:
orig = by_name[r["qa_of"]]
seed = RC.make_seed(orig, secret(r["key"]))
sample = RC.sample(orig, seed)
hashes = dict((nid, h) for nid, h in orig["nodes"])
errs = [{"node": names.ids[e["node"]], **{k: v for k, v in e.items() if k != "node"}} for e in r["errors"]]
rec = {"format": "scienting-receipt/1", "doc": key, "rev": orig["rev"], "level": orig["level"],
"scope": "qa:" + orig["id"], "nodes": [[nid, hashes[nid]] for nid in sample],
"evidence": [{"kind": "crop", "node": nid, "sha256": crop_sha(nid)} for nid in sample],
"by": r["by"], "at": r["at"],
"qa": {"of": orig["id"], "key": r["key"], "seed": base64.b64encode(seed).decode(), "rate": "0.05",
"minimum": 10, "sample": sample, "errors": errs}}
rec = RC.seal(rec, secret(r["by"]), r["by"])
else:
rev = r.get("rev", final_state.rev)
st = state_by_rev[rev]
vals = hashing.compute(st.doc()) if rev != final_state.rev else final_vals
ids = sorted(names.ids[x] for x in r["nodes"])
scope = r["scope"]
if scope.startswith("section:@"):
scope = "section:" + names.ids[scope[8:]]
rec = {"format": "scienting-receipt/1", "doc": key, "rev": rev, "level": r["level"], "scope": scope,
"nodes": [[i, vals[i]["node_hash"]] for i in ids],
"evidence": [{"kind": "crop", "node": i, "sha256": crop_sha(i)} for i in ids]
+ [{"kind": "witness", "sha256": wit_sha}],
"by": r["by"], "at": r["at"]}
rec = RC.seal(rec, secret(r["by"]), r["by"])
by_name[r["name"]] = rec
out.append(rec)
out.sort(key=lambda x: (x["at"], x["id"]))
return out, by_name
def build_document(src, used=None, extra_files=None):
key = src["document"]["key"]
names = Names(key)
used = used if used is not None else set()
spec = expand(src["tree"])
nodes, side = establish(key, spec, names, used)
aliases = [{"id": a, "kind": "alias", "successor": names.ids[t]}
for a, t in sorted(src.get("legacy_aliases", {}).items())]
state = CO.State(key, nodes, aliases)
states = {0: state}
records = []
for c in src.get("corrections", []):
c = copy.deepcopy(c)
crop = c.pop("crop", None)
new_name = c.pop("new_name", None)
before = c.pop("before", None)
c["evidence"] = [{"kind": "crop", "sha256": crop_sha(names.ids.get(crop, crop))}] if crop else [
{"kind": "person", "by": "ana", "note": "Read against the page."}]
c["author"] = {"key": "ana"}
for k in ("target", "parent", "place_after", "with"):
if k in c:
c[k] = names.resolve(c[k])
if "node" in c:
nspec = expand(c["node"])
flat = flatten_spec(nspec)
pre = mint_ids(key, flat, state.used_ids())
for f, n in zip(flat, pre):
if "_name" in f:
names.ids[f["_name"]] = n["id"]
c["node"] = _strip_private(nspec, names)
if before is not None:
d = state.doc()
c["offset"] = hashing.plain(d, c["target"]).index(before)
state, rec = CO.apply(state, c)
if new_name:
names.ids[new_name] = rec["created"][0]
records.append(rec)
states[state.rev] = state
# A corrected leaf was checked against the page image: it is F1 at most until corroborated again.
final = state
touched = set()
for rec in records:
if any(e["kind"] == "crop" for e in rec["evidence"]):
touched |= set(rec["touched"]) | set(rec.get("created", []))
for n in final.nodes:
if n["id"] in touched and n["kind"] in LEAVES:
n["fidelity"] = "F1"
for n in final.nodes:
if n["kind"] in LEAVES and "fidelity" not in n:
n["fidelity"] = "F1"
return key, names, side, final, states, records
def _strip_private(spec, names):
out = {k: v for k, v in spec.items() if not k.startswith("_") and k != "children"}
if out["kind"] == "row" and "attrs" in out:
out["attrs"] = {"header_path": names.resolve(out["attrs"]["header_path"])}
if out["kind"] == "cell":
out["attrs"] = dict(out["attrs"], column=names.resolve(out["attrs"]["column"]))
if spec.get("children"):
out["children"] = [_strip_private(c, names) for c in spec["children"]]
return out
def package_files(src, key, names, side, final, records, receipts=None, roster_rec=None, witnesses=None,
pages=None, boxes=None, extra=None, absent=()):
doc = final.doc()
vals = hashing.compute(doc)
nodes = [node_record(doc.nodes[nid], vals) for nid in doc.preorder()]
files = {}
files["nodes.jsonl"] = jsonl(nodes)
if pages:
files["pages.jsonl"] = jsonl([p for p, _ in sorted(pages, key=lambda x: (x[0]["witness"], x[0]["page"]))])
locs = []
fps = {p["page"]: p for p, _ in pages}
pos = {nid: i for i, nid in enumerate(doc.preorder())}
for nid, (pg, bx) in boxes.items():
if nid in doc.nodes:
locs.append({"node": nid, "page_fp": fps[pg]["page_fp"], "page": pg,
"page_label": fps[pg]["page_label"], "boxes": bx})
locs.sort(key=lambda l: (pos[l["node"]], l["page"], l["page_fp"]))
if locs:
files["locations.jsonl"] = jsonl(locs)
refs = []
for nid in doc.preorder():
s = side.get(nid, {})
if doc.nodes[nid]["kind"] == "reference":
r = {"node": nid, "label": doc.nodes[nid].get("label")}
if "_csl" in s:
r["csl"] = s["_csl"]
res = s.get("_resolution", {"type": "none"})
r["resolution"] = res
refs.append({k: v for k, v in r.items() if v is not None})
if refs:
files["references.jsonl"] = jsonl(refs)
if records:
files["corrections.jsonl"] = jsonl(records)
ids = sorted(final.ids, key=lambda e: (0 if e["kind"] == "alias" else 1, e.get("rev", 0), e["id"]))
if ids:
files["ids.jsonl"] = jsonl(ids)
if receipts:
files["receipts.jsonl"] = jsonl(receipts)
if roster_rec:
files["roster.json"] = (jcs.dumps(roster_rec) + "\n").encode()
files["witnesses.json"] = (jcs.dumps({"witnesses": witnesses}) + "\n").encode()
files["provenance.json"] = (jcs.dumps({
"tools": [{"name": "manuspec tools/build.py", "version": VERSION}],
"stages": [{"stage": "hand", "processor": "local", "tool": "manuspec tools/build.py"}],
"note": "Built by hand for the conformance suite; every reading is synthetic."}) + "\n").encode()
levels = RC.effective_levels(nodes, vals, receipts or [], ROSTER_BY_ID if roster_rec else {})
d = dict(src["document"])
d = {"format": "scienting-doc/1", **d}
d.update({"hashing": {"scheme": "plain"}, "root": vals[doc.root]["node_hash"], "rev": final.rev,
"counts": {"nodes": len(nodes), "leaves": sum(1 for n in nodes if n["kind"] in LEAVES)},
"fidelity": RC.distribution(levels)})
d["meta_hash"] = hashing.meta_hash(d)
files["document.json"] = (jcs.dumps(d) + "\n").encode()
if extra:
files.update(extra)
return files, nodes, vals, d
def build_hale(out):
src = json.load(open(os.path.join(ROOT, "fixtures", "src", "hale2026harmonic.json")))
image = png(4, 3, (40, 90, 160))
image_sha = sha256_hex(image)
key, names, side, final, states, records = build_document(src)
side[names.ids["@pa"]]["_image"] = image_sha
doc = final.doc()
pa = doc.nodes[names.ids["@pa"]]
pa["attrs"]["image"] = {"sha256": image_sha, "media_type": "image/png", "native": True}
pa["attrs"]["crop"] = {"sha256": crop_sha("panel a"), "media_type": "image/png", "dpi": 300}
P = src["pages"]
wsha = H(P["witness"]["sha_seed"])
wid = witness_id(wsha)
for n in final.nodes:
if n["kind"] in LEAVES:
n["prov"] = {"witnesses": [wid], "backend": "docling (illustrative)"}
if n.get("fidelity") == "F2":
n["prov"]["corroborated_by"] = [wid]
pages, boxes = page_layout(final.doc(), side, src, wid)
p2 = [p for p, _ in pages if p["page"] == 2][0]
for t in pa["attrs"]["figure_text"]:
t["page_fp"] = p2["page_fp"]
t["boxes"] = [[90.0, 300.0, 140.0, 310.0]]
witnesses = [{"id": wid, "sha256": wsha, "size": P["witness"]["size"], "kind": "pdf", "media_type": "application/pdf",
"pages": len(P["labels"]), "text_layer": True, "version_label": "version of record",
"origin": {"source": "publisher", "doi": "10.5555/example.hale.2026", "retrieved_at": "2026-10-01T08:00:00Z"},
"rights": rights_record("subscription")}]
receipts, by_name = seal_receipts(src["receipts"], names, states, final, key, wsha)
# The dataset reported from Table 1.
final_doc = final.doc()
vals = hashing.compute(final_doc)
rows = [("ct-ct", "CT–CT", "@cc"), ("ct-hc", "CT–HC", "@ch2"), ("ow-hw", "OW–HW", "@ow")]
csv = ["key,bond,set,k_r,r_eq"]
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()
csv.append("%s,%s,%s,%s,%s" % (row_key, bond, st, kv, rv))
for field, nid in (("k_r", kid), ("r_eq", rid)):
anchors.append({"resource": "bond-parameters", "row": row_key, "field": field,
"anchor": AN.make_anchor(final_doc, vals, nid, final.rev, store=STORE)})
ds_id = "ds-hale-bond-params"
datapackage = {"format": "scienting-dataset/1", "id": ds_id, "name": "hale-bond-params",
"title": "Harmonic bond parameters reported in Table 1",
"resources": [{"name": "bond-parameters", "path": "data.csv", "format": "csv", "encoding": "utf-8",
"schema": {"fields": [
{"name": "key", "type": "string"}, {"name": "bond", "type": "string"},
{"name": "set", "type": "string"},
{"name": "k_r", "type": "number", "unit": "kcal / mol / angstrom ** 2"},
{"name": "r_eq", "type": "number", "unit": "angstrom",
"uncertainty": {"kind": "unknown"}}],
"primaryKey": "key"},
"origin": {"kind": "reported", "anchors": "anchors.jsonl"}}]}
eq = names.ids["@eq1"]
mathml = ('<math display="block"><msub><mi>E</mi><mtext>bond</mtext></msub><mo>=</mo><mfrac><mn>1</mn><mn>2'
'</mn></mfrac><msub><mi>K</mi><mi>r</mi></msub><msup><mrow><mo>(</mo><mi>r</mi><mo>−</mo><msub><mi>r'
'</mi><mtext>eq</mtext></msub><mo>)</mo></mrow><mn>2</mn></msup></math>')
extra = {
"assets/%s.png" % image_sha: image,
"datasets/%s/datapackage.json" % ds_id: (jcs.dumps(datapackage) + "\n").encode(),
"datasets/%s/data.csv" % ds_id: ("\n".join(csv) + "\n").encode(),
"datasets/%s/anchors.jsonl" % ds_id: jsonl(anchors),
"derived/mathml.jsonl": jsonl([{"node": eq, "run": None, "tool": {"name": "hand", "version": "0"},
"mathml": mathml}]),
}
absent = [{"path": "assets/%s.png" % crop_sha("panel a"), "sha256": crop_sha("panel a"), "size": 18213,
"present": False}]
files, nodes, vals, d = package_files(src, key, names, side, final, records, receipts, ROSTER, witnesses, pages,
boxes, extra, absent)
write(os.path.join(out, "fixtures", "packages", key), files, absent)
return {"src": src, "key": key, "names": names, "states": states, "final": final, "records": records,
"receipts": receipts, "by_name": by_name, "pages": pages, "files": files, "nodes": nodes, "values": vals,
"document": d, "witness": witnesses[0], "absent": absent}
def build_checks_only(out, hale):
"""The same document exported to another machine of the team: text withheld, hashes keyed."""
final = hale["final"]
doc = final.doc()
k = crypto.team_key(TEAM_SECRET)
keyed = hashing.compute(doc, hashing.Hasher(k))
nodes = []
for nid in doc.preorder():
n = copy.deepcopy(doc.nodes[nid])
if n["kind"] in LEAVES:
n.pop("content", None)
n["withheld"] = True
if n["kind"] == "panel":
n["attrs"].pop("figure_text", None)
nodes.append(node_record(n, keyed))
f = dict(hale["files"])
for p in ("corrections.jsonl", "receipts.jsonl", "roster.json", "derived/mathml.jsonl"):
f.pop(p, None)
for p in [p for p in f if p.startswith("datasets/")]:
f.pop(p)
f["nodes.jsonl"] = jsonl(nodes)
refs = [{"node": r["node"], "label": r["label"], "resolution": r["resolution"]}
for r in map(json.loads, f["references.jsonl"].decode().splitlines())]
f["references.jsonl"] = jsonl(refs)
d = dict(hale["document"])
d.update({"policy": "checks-only", "hashing": {"scheme": "hmac-team", "key_id": crypto.key_id(k)},
"root": keyed[doc.root]["node_hash"]})
d["meta_hash"] = hashing.meta_hash(d, hashing.Hasher(k))
f["document.json"] = (jcs.dumps(d) + "\n").encode()
write(os.path.join(out, "fixtures", "packages", "hale2026harmonic.checks-only"), f, hale["absent"])
def build_simple(out, name, used=None):
src = json.load(open(os.path.join(ROOT, "fixtures", "src", name + ".json")))
key, names, side, final, states, records = build_document(src)
witnesses = []
pages = boxes = None
if "pages" in src:
P = src["pages"]
wsha = H(P["witness"]["sha_seed"])
wid = witness_id(wsha)
jsha = H(P["jats"]["sha_seed"])
jid = witness_id(jsha)
for n in final.nodes:
if n["kind"] in LEAVES:
n["prov"] = {"witnesses": [jid, wid], "backend": "jats reader (illustrative)", "corroborated_by": [wid]}
pages, boxes = page_layout(final.doc(), side, src, wid)
witnesses = [
{"id": jid, "sha256": jsha, "size": P["jats"]["size"], "kind": "jats", "media_type": "application/xml",
"version_label": "version of record",
"origin": {"source": "repository", "doi": "10.5555/example.okafor.2025"}, "rights": rights_record("open")},
{"id": wid, "sha256": wsha, "size": P["witness"]["size"], "kind": "pdf", "media_type": "application/pdf",
"pages": len(P["labels"]), "text_layer": True, "version_label": "version of record",
"origin": {"source": "publisher", "doi": "10.5555/example.okafor.2025"}, "rights": rights_record("open")}]
else:
T = src["typst"]
tsha = H(T["sha_seed"])
tid = witness_id(tsha)
for n in final.nodes:
if n["kind"] in LEAVES:
n["prov"] = {"witnesses": [tid], "backend": "typst reader (illustrative)"}
witnesses = [{"id": tid, "sha256": tsha, "size": T["size"], "kind": "typst", "media_type": "application/zip",
"version_label": "draft 3", "origin": {"source": "team"}, "rights": rights_record("own")}]
files, nodes, vals, d = package_files(src, key, names, side, final, records, None, None, witnesses, pages, boxes)
write(os.path.join(out, "fixtures", "packages", key), files)
return {"key": key, "names": names, "final": final, "files": files, "values": vals, "src": src}
# --- the tiny document used for corrections vectors and invalid packages -----------------
TINY = {"document": {"key": "tiny2026example", "kind": "article", "work": {"key": "tiny2026example", "same_as": []},
"version": {"type": "version-of-record"}, "relations": [], "meta": {"title": "A tiny example"},
"language": "en", "license": {"spdx": "CC0-1.0"}, "policy": "full"},
"tree": {"kind": "document", "children": [
{"kind": "section", "name": "@sec", "label": "1", "level": 1, "title": "Results", "children": [
{"name": "@p1", "p": [
{"name": "@a", "s": ["The value is 42.0 at 300 K."], "fidelity": "F2"},
{"name": "@b", "s": ["See ", {"xref": "@t", "text": "Table 1"}, " and the note.", {"cite": ["@r"], "text": "1"}],
"fidelity": "F2"}]},
{"name": "@p2", "p": [{"name": "@c", "s": ["A second paragraph."], "fidelity": "F2"},
{"name": "@d", "s": ["It ends here."], "fidelity": "F2"}]},
{"kind": "table", "name": "@t", "label": "Table 1",
"columns": [{"name": "@cx", "header_path": ["@hx"]}, {"name": "@cy", "header_path": ["@hy"]}],
"children": [
{"kind": "table-head", "children": [{"row": [
{"name": "@hx", "c": ["x"], "role": "column-header", "col": "@cx"},
{"name": "@hy", "c": ["y"], "role": "column-header", "col": "@cy"}]}]},
{"kind": "table-body", "name": "@tb", "children": [
{"name": "@row1", "header_path": ["@x1"], "row": [
{"name": "@x1", "c": ["1"], "role": "row-header", "col": "@cx"},
{"name": "@y1", "c": ["2.5"], "col": "@cy"}]}]}]}]},
{"kind": "back", "children": [{"kind": "references", "children": [
{"kind": "reference", "name": "@r", "label": "1", "runs": ["1. An Example, 2020."],
"resolution": {"type": "none"}, "fidelity": "F2"}]}]}]}}
def tiny_package(out_dir, receipts=True):
key, names, side, final, states, records = build_document(copy.deepcopy(TINY))
wsha = H("tiny2026example: a file (synthetic)")
wit = [{"id": witness_id(wsha), "sha256": wsha, "size": 1000, "kind": "pdf", "media_type": "application/pdf",
"pages": 1, "origin": {"source": "author"}, "rights": rights_record("own")}]
recs = None
if receipts:
recs, _ = seal_receipts([{"name": "t1", "by": "ana", "level": "F3", "scope": "all", "at": "2026-10-11T12:00:00Z",
"nodes": ["@a", "@b", "@c", "@d", "@x1", "@y1", "@hx", "@hy", "@r"]}],
names, states, final, key, wsha)
files, nodes, vals, d = package_files(TINY, key, names, side, final, records, recs, ROSTER if receipts else None,
wit)
if out_dir:
write(out_dir, files)
return {"key": key, "names": names, "final": final, "files": files, "values": vals}
# --- vectors --------------------------------------------------------------------
def wv(out, name, cases):
path = os.path.join(out, "vectors", name)
with open(path, "wb") as f:
f.write(jsonl(cases))
def sentence_doc(runs, key="vectors"):
"""A one-sentence document, so projections of runs can be computed with the node functions."""
nodes = [{"id": "aaaaaaa", "kind": "document", "parent": None, "ord": 0},
{"id": "bbbbbbb", "kind": "section", "parent": "aaaaaaa", "ord": 0, "attrs": {"level": 1}},
{"id": "ccccccc", "kind": "paragraph", "parent": "bbbbbbb", "ord": 0},
{"id": "ddddddd", "kind": "sentence", "parent": "ccccccc", "ord": 0, "content": runs}]
return Doc(key, nodes), "ddddddd"
def projection_case(cid, runs, note=None):
doc, s = sentence_doc(runs)
strict, cx = hashing.strict(doc, s)
lossy = hashing.lossy_from_strict(strict)
plain = hashing.plain(doc, s)
c = {"id": cid, "runs": runs, "plain": plain, "normalized": norm(plain), "strict": strict, "lossy": lossy,
"complex": cx, "text_hash": H(norm(plain)), "match_hash": H(strict), "align_key": H(lossy)}
if note:
c["note"] = note
return c
T = lambda t, **kw: dict({"kind": "text", "text": t}, **({"style": kw["style"]} if "style" in kw else {})) # noqa: E731
M = lambda s, n="tex": {"kind": "math", "notation": n, "source": s} # noqa: E731
def parse_doubles(text):
"""Parse JSON as an ECMAScript engine does: every number is an IEEE 754 double."""
return json.loads(text, parse_int=lambda x: int(x) if abs(int(x)) <= 2**53 else float(x))
def vectors(out, hale, tiny):
# Canonical JSON.
cj = [
("sort-utf16", '{"\\u20ac": 1, "\\ud83d\\ude00": 2, "a": 3, "\\ufb01": 4}'),
("numbers", '[0, -0.0, 1.0, 100.0, 72.10, 1E21, 1e-7, 0.000001, 123456789012345678901, -2.5e-5, 4.50e2]'),
("escapes", '{"s": "tab\\tnl\\ncr\\rq\\"bs\\\\ctl\\u0001del\\u007fnbsp\\u00a0slash\\/"}'),
("nested", '{ "b" : [ true , false , null ] , "a" : { "z" : "" , "y" : [ ] } }'),
("strings-stay-unnormalized", '{"cafe\\u0301": "e\\u0301", "caf\\u00e9": "\\u00e9"}'),
]
wv(out, "canonical-json.jsonl", [{"id": i, "input_json": t, "output": jcs.dumps(parse_doubles(t))} for i, t in cj])
# Normalization for hashing.
ns = [("nfc", "cafe\u0301"), ("collapse", "a \t\n b"), ("trim", "\u00a0 a b \u3000"),
("angstrom-sign", "1.5 \u212b"), ("nbsp-thin", "300\u2009K and 10\u00a0mol"),
("zero-width-kept", "a\u200bb"), ("ligature-kept", "\ufb01t"), ("micro-kept", "5 \u00b5m"),
("line-separator", "one\u2028two")]
wv(out, "normalize.jsonl", [{"id": i, "input": s, "output": norm(s), "sha256": H(norm(s))} for i, s in ns])
# IDs.
idc = []
for doc_key, kind, th, occ in [("cornell1995second", "sentence", H("A sentence."), 0),
("cornell1995second", "sentence", H("A sentence."), 1),
("cornell1995second.si", "sentence", H("A sentence."), 0),
("hale2026harmonic", "cell", H("310.0"), 0),
("hale2026harmonic", "column", H("Set A $K_r$ (kcal mol⁻¹ Å⁻²)"), 0)]:
payload = jcs.dumps(["scienting-id/1", doc_key, kind, th, occ])
idc.append({"id": "mint-%s-%s-%d" % (doc_key, kind, occ), "doc": doc_key, "kind": kind, "text_hash": th,
"occurrence": occ, "payload": payload, "sha256": H(payload),
"node_id": mint_node_id(doc_key, kind, th, occ)})
for hx in ("00" * 32, "ff" * 32, H("x")):
idc.append({"id": "letter-first-" + hx[:8], "digest": hx, "node_id": letter_first(bytes.fromhex(hx), 7),
"record_id": record_id(hx)})
wv(out, "ids.jsonl", idc)
# Notation.
notes = [("tex", "H_2O"), ("tex", "_2"), ("tex", "\\mathrm{H}_{2}\\mathrm{O}"), ("typst", "H_2 O"),
("typst", "upright(H)_2 upright(O)"), ("tex", "\\mathrm{cm}^{-1}"), ("typst", "\"cm\"^(-1)"),
("tex", "10^{-3}"), ("typst", "10^(-3)"), ("tex", "K_r"), ("tex", "K_{\\mathrm{r}}"), ("typst", "K_r"),
("tex", "\\alpha_i"), ("tex", "\\alpha_{i}"), ("tex", "x'_1"), ("tex", "x^{\\prime}"),
("tex", "T = 300\\,\\mathrm{K}"), ("tex", "-1.5"), ("tex", "\\epsilon"), ("typst", "epsilon.alt"),
("tex", "a \\cdot b"), ("typst", "a dot b"), ("tex", "a \\times b \\pm c"), ("tex", "\\left(x\\right)^2"),
("typst", "(a+b)^2"), ("tex", "\\frac{a}{b}"), ("typst", "a/b"), ("tex", "\\sqrt{2}"),
("tex", "\\mathbf{F}"), ("tex", "{ab}_2"), ("tex", "x_1_2"), ("tex", "\\Delta G^\\circ"),
("tex", "E_{\\mathrm{bond}} = \\frac{1}{2} K_r (r - r_{\\mathrm{eq}})^2"), ("typst", "E = K_r (r - r_\"eq\")^2")]
wv(out, "notation.jsonl", [{"id": "%s:%s" % (n, s), "notation": n, "source": s, "canonical": fold_math(n, s)}
for n, s in notes])
# Projections.
pc = [
projection_case("h2o-math", [T("The solvent is "), M("H_2O"), T(".")]),
projection_case("h2o-split-math", [T("The solvent is H"), M("_2"), T("O.")]),
projection_case("h2o-mathrm", [T("The solvent is "), M("\\mathrm{H}_{2}\\mathrm{O}"), T(".")]),
projection_case("h2o-sub", [T("The solvent is H"), T("2", style=["sub"]), T("O.")]),
projection_case("h2o-unicode", [T("The solvent is H\u2082O.")]),
projection_case("h2o-typst", [T("The solvent is "), M("H_2 O", "typst"), T(".")]),
projection_case("wavenumber-unicode", [T("A band near 1595 cm\u207b\u00b9.")]),
projection_case("wavenumber-sup", [T("A band near 1595 cm"), T("-1", style=["sup"]), T(".")]),
projection_case("wavenumber-math", [T("A band near 1595 "), M("\\mathrm{cm}^{-1}"), T(".")]),
projection_case("wavenumber-typst", [T("A band near 1595 "), M("\"cm\"^(-1)", "typst"), T(".")]),
projection_case("milli-unicode", [T("Values below 10\u207b\u00b3 were dropped.")]),
projection_case("milli-sup", [T("Values below 10"), T("\u22123", style=["sup"]), T(" were dropped.")]),
projection_case("milli-math", [T("Values below "), M("10^{-3}"), T(" were dropped.")]),
projection_case("kr-math", [T("The constant "), M("K_r"), T(" is large.")]),
projection_case("kr-mathrm", [T("The constant "), M("K_{\\mathrm{r}}"), T(" is large.")]),
projection_case("kr-sub", [T("The constant K"), T("r", style=["sub"]), T(" is large.")]),
projection_case("kr-unicode", [T("The constant K\u1d63 is large.")]),
projection_case("ligature", [T("The \ufb01t is good.")]),
projection_case("no-ligature", [T("The fit is good.")]),
projection_case("micro-sign", [T("A 5 \u00b5m film.")]),
projection_case("greek-mu", [T("A 5 \u03bcm film.")]),
projection_case("lost-superscript", [T("A rate of 103 per second.")], "OCR lost the superscript."),
projection_case("superscript", [T("A rate of 10\u00b3 per second.")]),
projection_case("cite-mark", [T("The energy"), {"kind": "cite", "refs": [], "text": "3"}, T(" is low.")]),
projection_case("cite-as-text", [T("The energy3 is low.")]),
projection_case("complex", [T("The energy is "), M("\\frac{1}{2} k x^2"), T(" exactly.")]),
projection_case("hyphen-before-digit", [T("pages 10-20 and CT\u2013CT with 6-31G*")]),
projection_case("en-dash-before-digit", [T("pages 10\u201320 and CT-CT with 6\u201331G*")]),
projection_case("astral", [T("Let "), M("\U0001d465"), T(" vary.")]),
]
groups = [
{"id": "h2o", "same": "match_hash",
"cases": ["h2o-math", "h2o-split-math", "h2o-mathrm", "h2o-sub", "h2o-unicode", "h2o-typst"]},
{"id": "wavenumber", "same": "match_hash",
"cases": ["wavenumber-unicode", "wavenumber-sup", "wavenumber-math", "wavenumber-typst"]},
{"id": "milli", "same": "match_hash", "cases": ["milli-unicode", "milli-sup", "milli-math"]},
{"id": "kr", "same": "match_hash", "cases": ["kr-math", "kr-mathrm", "kr-sub", "kr-unicode"]},
{"id": "ligature", "same": "match_hash", "cases": ["ligature", "no-ligature"]},
{"id": "micro", "same": "match_hash", "cases": ["micro-sign", "greek-mu"]},
{"id": "103-align", "same": "align_key", "cases": ["lost-superscript", "superscript"]},
{"id": "103-match", "differ": "match_hash", "cases": ["lost-superscript", "superscript"]},
{"id": "cite-structure", "differ": "match_hash", "cases": ["cite-mark", "cite-as-text"]},
{"id": "dashes-align", "same": "align_key", "cases": ["hyphen-before-digit", "en-dash-before-digit"]},
{"id": "dashes-text", "differ": "text_hash", "cases": ["hyphen-before-digit", "en-dash-before-digit"]},
]
byid = {c["id"]: c for c in pc}
for g in groups:
vals = {byid[c][g.get("same") or g.get("differ")] for c in g["cases"]}
assert (len(vals) == 1) if "same" in g else (len(vals) == len(g["cases"])), g
wv(out, "projections.jsonl", pc)
wv(out, "projection-groups.jsonl", groups)
# Offsets.
off = []
for cid, runs, span in [("bmp", [T("A sentence about H"), M("_2"), T("O.")], [18, 24]),
("astral", [T("Let "), M("\U0001d465"), T(" vary with "), M("\U0001d466"), T(".")], [4, 7])]:
doc, s = sentence_doc(runs)
p = hashing.plain(doc, s)
from tools.oracle.text import cp_to_utf16
off.append({"id": cid, "runs": runs, "plain": p, "span": span, "exact": p[span[0]:span[1]],
"utf16_span": [cp_to_utf16(p, span[0]), cp_to_utf16(p, span[1])], "code_points": len(p),
"utf16_units": cp_to_utf16(p, len(p))})
wv(out, "offsets.jsonl", off)
# Five values on a small tree: the tiny document.
tnodes = [{k: v for k, v in n.items() if k not in ("text_hash", "match_hash", "align_key", "own_hash", "node_hash")}
for n in map(json.loads, tiny["files"]["nodes.jsonl"].decode().splitlines())]
tdoc = Doc("tiny2026example", tnodes)
tv = hashing.compute(tdoc)
own = {nid: hashing.own_content(tdoc, nid, lambda x: tv[x]["text_hash"]) for nid in tdoc.order}
wv(out, "hashes.jsonl", [{"id": "tiny2026example", "doc": "tiny2026example", "nodes": tnodes,
"own_content": own, "values": tv}])
# Addresses.
ad = [("plain", "cornell1995second#k3m9q2a"), ("pinned", "cornell1995second#k3m9q2a@4f1a0c"),
("span", "cornell1995second#k3m9q2a@4f1a0c[12:57]"), ("store", "smithlab/cornell1995second#k3m9q2a"),
("span-no-pin", "dong2026machine.v2#k3m9q2a[0:0]"), ("si", "cornell1995second.si#m2x8p4d"),
("alias", "cornell1995second#b-p03-text-004"), ("bad-reversed", "cornell1995second#k3m9q2a[57:12]"),
("bad-pin", "cornell1995second#k3m9q2a@4F1A0C"), ("bad-leading-zero", "k#k3m9q2a[01:2]"),
("bad-no-node", "cornell1995second#"), ("bad-upper-key", "Cornell1995#k3m9q2a"),
("bad-two-stores", "a/b/c#k3m9q2a"), ("bad-pin-length", "k#k3m9q2a@4f1a")]
wv(out, "addresses.jsonl", [{"id": i, "address": a, "parsed": AN.parse_address(a)} for i, a in ad])
# Keyed hashes.
k = crypto.team_key(TEAM_SECRET)
fps = [p["page_fp"] for p, _ in hale["pages"]]
pk = crypto.page_key(fps, "layer:smithlab/kp4m2x7q")
text = "310.0"
keyed = [
{"id": "team-key", "secret": TEAM_SECRET.hex(), "key": k.hex(), "key_id": crypto.key_id(k)},
{"id": "team-hmac-text", "key": k.hex(), "text": text, "text_hash_plain": H(text),
"text_hash_keyed": hashing.Hasher(k)(text)},
{"id": "page-hashes", "page_fps": fps, "page_hashes": [crypto.page_hash(f) for f in fps]},
{"id": "page-key", "page_fps": fps, "context": "layer:smithlab/kp4m2x7q", "key": pk.hex(),
"quote": "310.0", "quote_mac": hashing.Hasher(pk)(norm(text))},
{"id": "non-holder", "note": "Without the pages, a guess hashes to the plain value, which the export never "
"carries; with one page changed, the key differs.",
"guess": text, "guess_sha256": H(text),
"wrong_page_fps": [fps[0], H("another page")],
"wrong_key": crypto.page_key([fps[0], H("another page")], "layer:smithlab/kp4m2x7q").hex(),
"quote_mac": hashing.Hasher(pk)(norm(text))},
]
assert keyed[4]["wrong_key"] != pk.hex() and keyed[4]["guess_sha256"] != keyed[4]["quote_mac"]
wv(out, "keyed.jsonl", keyed)
# Fingerprints: two downloads of one page, stamped differently.
stamps = json.load(open(os.path.join(ROOT, "registry", "stamps", "scienting-stamps-1.json")))["rules"]
_, p1 = hale["pages"][0]
body = [l for l in p1["lines"] if "box" not in l]
acs1 = dict(p1)
acs2 = dict(p1, lines=body + [
{"text": "Downloaded via EXAMPLE PHARMA INC on March 2, 2027 at 09:14:55 (UTC).", "box": [8.0, 210.0, 20.0, 590.0]},
{"text": "See https://pubs.acs.org/sharingguidelines for options on how to legitimately share published articles.",
"box": [580.0, 200.0, 592.0, 600.0]}])
wiley = {"size": [595.276, 841.89], "images": [H("figure 2 image stream (synthetic)")], "lines": [
{"text": "Results"}, {"text": "The fitted slope is 0.42 \u00b1 0.03 per kelvin."},
{"text": "Downloaded from https://onlinelibrary.wiley.com/doi/10.5555/example.wiley by Example University, "
"Wiley Online Library on [11/10/2026]. See the Terms and Conditions (https://onlinelibrary.wiley.com/"
"terms-and-conditions) on Wiley Online Library for rules of use.", "box": [580.0, 100.0, 592.0, 740.0]}]}
wiley2 = dict(wiley, lines=wiley["lines"][:2] + [
{"text": "Downloaded from https://onlinelibrary.wiley.com/doi/10.5555/example.wiley by Example Pharma Inc, "
"Wiley Online Library on [02/03/2027].", "box": [580.0, 100.0, 592.0, 740.0]},
{"text": "See the Terms and Conditions (https://onlinelibrary.wiley.com/terms-and-conditions) on Wiley Online "
"Library for rules of use.", "box": [580.0, 60.0, 592.0, 99.0]}])
changed = dict(acs1, lines=[dict(l, text=l["text"].replace("1595", "1594")) for l in acs1["lines"]])
scan = {"size": [612.4, 791.6], "lines": [], "images": [H("scanned page image (synthetic)")]}
fpc = []
for cid, pg in [("acs-download-1", acs1), ("acs-download-2", acs2), ("acs-changed-text", changed),
("wiley-download-1", wiley), ("wiley-download-2", wiley2), ("scanned", scan)]:
fp, removed = FP.fingerprint(pg, stamps)
kept = [l["text"] for l in pg["lines"] if not FP.is_stamp(l, pg["size"], stamps)]
fpc.append({"id": cid, "page": pg, "rules": "scienting-stamps-1", "removed": removed,
"text": FP.fp_text(" ".join(kept)), "page_fp": fp})
assert fpc[0]["page_fp"] == fpc[1]["page_fp"] != fpc[2]["page_fp"] and fpc[3]["page_fp"] == fpc[4]["page_fp"]
wv(out, "fingerprints.jsonl", fpc)
# Merkle roots.
mk = []
for n in (1, 2, 3, 4, 5, 7):
items = [["node", mint_node_id("m", "sentence", H(str(i)), 0), H("h%d" % i)] for i in range(n)]
mk.append({"id": "items-%d" % n, "items": items, "root": crypto.merkle_root(items)})
wv(out, "merkle.jsonl", mk)
# Signatures.
r1 = hale["by_name"]["r1"]
sk = bytes.fromhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
wv(out, "signatures.jsonl", [
{"id": "rfc8032-test-1", "secret": sk.hex(), "public": crypto.ed25519_public(sk).hex(), "message": "",
"signature": crypto.ed25519_sign(sk, b"").hex()},
{"id": "receipt-r1", "receipt": r1, "record_hash": RC.record_hash(r1),
"payload": RC.signing_payload(r1).decode(), "public": ROSTER_BY_ID["ana"]["public"],
"record_id": r1["id"]}])
# Spot-checks.
q1 = hale["by_name"]["q1"]
wv(out, "spotcheck.jsonl", [{"id": "q1", "receipt": r1, "seed_payload": RC.seed_payload(r1).decode(),
"checker_public": ROSTER_BY_ID["ben-spot"]["public"], "seed": q1["qa"]["seed"],
"n": len(r1["nodes"]), "rate": "0.05", "minimum": 10,
"size": RC.sample_size(len(r1["nodes"])), "sample": q1["qa"]["sample"]}]
+ [{"id": "size-%d" % n, "n": n, "rate": "0.05", "minimum": 10, "size": RC.sample_size(n)}
for n in (3, 10, 199, 200, 201, 3000)])
# Bounds.
wv(out, "bounds.jsonl", [{"id": "k%d-n%d" % (kk, nn), "errors": kk, "sampled": nn, "confidence": "0.95",
"bound": RC.bound_string(kk, nn), "approx": round(RC.upper_bound(kk, nn), 9)}
for kk, nn in [(0, 10), (0, 300), (1, 10), (1, 300), (2, 50), (5, 100), (0, 1), (10, 10)]])
# Alignment.
def item(i, runs, container, context="paragraph"):
doc, s = sentence_doc(runs)
st, cx = hashing.strict(doc, s)
return {"id": i, "runs": runs, "strict": st, "lossy": hashing.lossy_from_strict(st), "complex": cx,
"container": container, "context": context}
A = [item("a1", [T("Methods")], "h", "heading"),
item("a2", [T("Every bond was scanned in steps of 0.01 \u00c5 around its minimum.")], "p1"),
item("a3", [T("where "), M("\\frac{a}{b}"), T(" is the bond length.")], "p1"),
item("a4", [T("The fitted rate is 10\u00b3 per second at room temperature.")], "p1"),
item("a5", [T("where "), M("\\frac{c}{d}"), T(" is the bond length.")], "p2"),
item("a6", [T("This sentence was dropped by the second reading entirely.")], "p2"),
item("a7", [T("The last sentence of the section is unique and long enough.")], "p2")]
B = [item("b1", [T("Methods")], "x", "paragraph"),
item("b2", [T("Every bond was scanned in steps of 0.01 \u00c5 around its minimum.")], "q1"),
item("b3", [T("where "), M("\\frac{a}{b}"), T(" is the bond length.")], "q1"),
item("b4", [T("The fitted rate is 103 per second at room temperature.")], "q2"),
item("b5", [T("where "), M("\\frac{c}{e}"), T(" is the bond length.")], "q2"),
item("b6", [T("A sentence only the second reading has, also long.")], "q2"),
item("b7", [T("The last sentence of the section is unique and long enough.")], "q2")]
wv(out, "alignment.jsonl", [{"id": "two-readings", "a": A, "b": B, "expect": AL.align(A, B)}])
# Corrections, op by op, on the tiny document.
wv(out, "corrections.jsonl", corrections_vectors())
# Anchor resolution against the hale package.
wv(out, "resolve.jsonl", resolve_vectors(hale))
# Rights.
wv(out, "rights.jsonl", rights_vectors(hale))
def corrections_vectors():
tiny = tiny_package(None, receipts=False)
st0 = tiny["final"]
nm = tiny["names"].ids
crop = [{"kind": "crop", "sha256": crop_sha("tiny")}]
person = [{"kind": "person", "by": "ana"}]
base = {"author": {"key": "ana"}, "reason": "Vector.", "at": "2026-10-11T12:00:00Z"}
cases = [
("replace-digit", {"op": "replace", "target": nm["@a"], "find": "42.0", "replace": "42.5", "evidence": crop}),
("replace-digit-no-crop", {"op": "replace", "target": nm["@a"], "find": "42.0", "replace": "42.5", "evidence": person}),
("replace-word", {"op": "replace", "target": nm["@c"], "find": "second", "replace": "later", "evidence": person}),
("replace-not-unique", {"op": "replace", "target": nm["@a"], "find": "0", "replace": "1", "evidence": crop}),
("replace-runs", {"op": "replace", "target": nm["@a"], "find": "300 K", "replace": [
{"kind": "math", "notation": "tex", "source": "300\\,\\mathrm{K}"}], "evidence": crop}),
("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-node", {"op": "insert", "parent": nm["@p2"], "place_after": nm["@c"],
"node": {"kind": "sentence", "content": [{"kind": "text", "text": "A dropped sentence."}]},
"evidence": crop}),
("delete", {"op": "delete", "target": nm["@p2"], "evidence": person}),
("delete-digits-no-crop", {"op": "delete", "target": nm["@p1"], "evidence": person}),
("split", {"op": "split", "target": nm["@a"], "offset": 13, "evidence": person}),
("split-inside-xref", {"op": "split", "target": nm["@b"], "offset": 6, "evidence": person}),
("merge", {"op": "merge", "target": nm["@c"], "with": nm["@d"], "joiner": " ", "evidence": person}),
("merge-not-adjacent", {"op": "merge", "target": nm["@a"], "with": nm["@c"], "evidence": person}),
("move", {"op": "move", "target": nm["@d"], "parent": nm["@p1"], "place_after": None, "evidence": person}),
("reclassify", {"op": "reclassify", "target": nm["@p2"], "kind": "quote", "evidence": person}),
("reclassify-bad", {"op": "reclassify", "target": nm["@a"], "kind": "paragraph", "evidence": person}),
("relabel-section", {"op": "relabel", "target": nm["@sec"], "label": "2", "evidence": crop}),
("relabel-digit-no-crop", {"op": "relabel", "target": nm["@t"], "label": "Table 2", "evidence": person}),
("set-colspan", {"op": "set", "target": nm["@y1"], "attr": "colspan", "value": 2, "evidence": person}),
("set-not-settable", {"op": "set", "target": nm["@y1"], "attr": "level", "value": 2, "evidence": person}),
]
out = []
vals0 = hashing.compute(st0.doc())
before = [dict(n, **vals0[n["id"]]) for n in st0.nodes]
for cid, c in cases:
c = dict(base, **c)
case = {"id": cid, "doc": st0.key, "before": {"nodes": before, "ids": [], "rev": 0}, "correction": c}
try:
st1, rec = CO.apply(st0, c)
v1 = hashing.compute(st1.doc())
case["expect"] = {"record": rec, "nodes": [dict(n, **v1[n["id"]]) for n in st1.nodes], "ids": st1.ids}
except Refusal as e:
case["expect"] = {"refusal": e.rule}
out.append(case)
return out
def resolve_vectors(hale):
src, names, states, final = hale["src"], hale["names"], hale["states"], hale["final"]
s0 = states[0]
d0 = s0.doc()
v0 = hashing.compute(d0)
out = []
for cid, a in src["anchors_at_rev0"].items():
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)
i = t.index(a["quote"])
span = [i, i + len(a["quote"])]
anchor = AN.make_anchor(d0, v0, nid, 0, span=span, store=STORE)
if "alias" in a:
anchor["node"] = a["alias"]
res = AN.resolve(final, anchor)
out.append({"id": cid, "package": "fixtures/packages/hale2026harmonic", "anchor": anchor, "expect": res})
return out
def rights_vectors(hale):
out = OUT
w = hale["witness"]
stages = json.load(open(os.path.join(ROOT, "registry", "stages", "scienting-stages-default.json")))["stages"]
policy = json.load(open(os.path.join(out, "fixtures", "records", "policy.json")))
cases = []
for cid, stage, proc, now, pols in [
("convert-local", "convert-model", "local", "2026-10-11T00:00:00Z", []),
("convert-outside", "convert-model", "outside", "2026-10-11T00:00:00Z", []),
("review-tenancy-unknown", "review-model", "tenancy", "2026-10-11T00:00:00Z", []),
("review-tenancy-policy", "review-model", "tenancy", "2026-10-11T00:00:00Z", [policy]),
("index", "index", "local", "2026-10-11T00:00:00Z", []),
("embed-outside-refused", "embed", "outside-no-retention", "2026-10-11T00:00:00Z", [policy]),
("publish-quotes", "publish-quotes", "local", "2026-10-11T00:00:00Z", []),
("read-expired", "read", "local", "2027-09-01T00:00:00Z", []),
("accessible-copy-unknown", "accessible-copy", "local", "2026-10-11T00:00:00Z", []),
]:
uses, expired = RI.effective(w, pols, now)
ok, lacking = RI.check(stages[stage], proc, uses)
cases.append({"id": cid, "rights": w["rights"], "origin": w["origin"], "witness_sha256": w["sha256"],
"policies": pols, "stage": stage, "requires": stages[stage], "processor": proc, "now": now,
"expect": {"allowed": ok, "lacking": lacking, "expired": expired}})
oa = rights_record("open")
sub = w["rights"]
cases.append({"id": "combine", "rights": [oa, sub],
"expect": RI.combine([RI.flat(oa), RI.flat(sub)])})
backends = [json.load(open(os.path.join(out, "fixtures", "records", "backend-%s.json" % b))) for b in ("docling", "examplevendor")]
profiles = [json.load(open(os.path.join(out, "fixtures", "records", "profile-%s.json" % p))) for p in ("smithlab", "acme")]
for b in backends:
for p in profiles:
ok, failed = RI.backend_allowed(b, p)
cases.append({"id": "backend-%s-%s" % (b["name"], p["team"]), "backend": b, "profile": p,
"expect": {"allowed": ok, "failed": failed}})
return cases
# --- records ------------------------------------------------------------------------
def wr(out, rel, rec):
path = os.path.join(out, "fixtures", "records", rel)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "wb") as f:
f.write((jcs.dumps(rec) + "\n").encode() if not isinstance(rec, bytes) else rec)
CTX = ["http://www.w3.org/ns/anno.jsonld", "https://scient.ing/ns/scienting/1.jsonld"]
def ann_hash(a):
return H(jcs.dumpb({k: v for k, v in a.items() if k not in ("status", "receipt", "modified")}))
def records(out, hale):
names, final = hale["names"], hale["final"]
doc = final.doc()
vals = hashing.compute(doc)
rev = final.rev
I = names.ids
def anchor(name, quote=None):
nid = I[name]
span = None
if quote:
t = hashing.plain(doc, nid)
i = t.index(quote)
span = [i, i + len(quote)]
return AN.make_anchor(doc, vals, nid, rev, span=span, store=STORE)
wr(out, "roster.json", ROSTER)
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))
long_anchor = AN.make_anchor(doc, vals, I["@s2"], rev, store=STORE)
wr(out, "anchor-leaf.json", long_anchor)
# A public layer's anchor on a paywalled paper: keyed by the pages, no text.
fps = [p["page_fp"] for p, _ in hale["pages"]]
layer_id = "urn:scienting:layer:smithlab/kp4m2x7q"
pk = crypto.page_key(fps, "layer:smithlab/kp4m2x7q")
hk = hashing.Hasher(pk)
ka = anchor("@cc_ak")
keyed_vals = hashing.compute(doc, hk)
ka = {"format": "scienting-anchor/1", "store": STORE, "doc": ka["doc"], "node": ka["node"], "kind": "cell",
"own_hash": keyed_vals[ka["node"]]["own_hash"], "node_hash": keyed_vals[ka["node"]]["node_hash"],
"quote": {"mac": hk(norm(ka["quote"]["exact"])), "length": len(ka["quote"]["exact"])},
"context": {"table_label_mac": hk(norm(ka["context"]["table_label"])),
"row_path_mac": [hk(norm(x)) for x in ka["context"]["row_path"]],
"column_path_mac": [hk(norm(x)) for x in ka["context"]["column_path"]]},
"rev": rev, "keyed": {"scheme": "hkdf-pages", "context": "layer:smithlab/kp4m2x7q",
"pages": [crypto.page_hash(f) for f in fps]}}
wr(out, "anchor-keyed.json", ka)
# Rights, policy, backends, profiles.
wr(out, "rights-open.json", rights_record("open"))
wr(out, "rights-subscription.json", rights_record("subscription"))
ccc = rights_record("subscription")
ccc.update({"license": {"text": "Annual collective license with internal AI re-use (fictional)."},
"basis": "collective-license", "valid_until": "2027-06-30T23:59:59Z"})
wr(out, "rights-collective.json", ccc)
policy = RC.seal({"format": "scienting-policy/1", "project": "smithlab-charges",
"applies_to": {"basis": ["subscription"], "doi_prefixes": ["10.5555/"]},
"uses": {"ai": {"internal/tenancy": True}},
"statement": "Our institutional agreement covers internal AI use in our own cloud account for "
"these papers (fictional).",
"by": "ana", "at": "2026-10-05T09:00:00Z", "valid_until": "2027-08-31T23:59:59Z"},
secret("ana"), "ana")
wr(out, "policy.json", policy)
wr(out, "backend-docling.json", {
"format": "scienting-backend/1", "name": "docling", "version": "illustrative", "stage": "read",
"processor": "local", "code_license": {"spdx": "MIT"},
"weights": [{"name": "layout model", "license": {"spdx": "Apache-2.0"}}], "conditions": []})
wr(out, "backend-examplevendor.json", {
"format": "scienting-backend/1", "name": "examplevendor", "version": "1.0", "stage": "read",
"processor": "local", "code_license": {"spdx": "Apache-2.0"},
"weights": [{"name": "example weights", "license": {"text": "A fictional weight license with conditions."}}],
"conditions": [{"id": "revenue-cap", "kind": "revenue-cap", "usd": 5000000},
{"id": "no-competition", "kind": "no-competition", "vendor": "examplevendor"},
{"id": "purpose", "kind": "purpose", "allowed": ["research", "personal", "education", "startup"]}]})
wr(out, "profile-smithlab.json", {"format": "scienting-profile/1", "team": "smithlab", "purpose": "research",
"revenue_or_funding_usd": {"at_most": 2000000}, "competes_with": [],
"recorded_by": "ana", "at": "2026-10-01T00:00:00Z"})
wr(out, "profile-acme.json", {"format": "scienting-profile/1", "team": "acme", "purpose": "commercial",
"revenue_or_funding_usd": {"at_most": None}, "competes_with": None,
"recorded_by": "acme-admin", "at": "2026-10-01T00:00:00Z"})
# A layer with one annotation of each common kind.
creator = {"id": "urn:scienting:key:ana", "type": "Person", "name": "Ana Example"}
model = {"id": "urn:scienting:software:example-model", "type": "Software", "name": "A model (illustrative)"}
def target(a):
return {"type": "SpecificResource", "source": "urn:scienting:doc:smithlab/hale2026harmonic",
"selector": [{"type": "AnchorSelector", "anchor": a}]
+ ([{"type": "TextQuoteSelector", "exact": a["quote"]["exact"],
"prefix": a["quote"].get("prefix", ""), "suffix": a["quote"].get("suffix", "")}]
if "quote" in a and "exact" in a["quote"] else [])}
def ann(body, tgt, motivation, status="proposed", who=creator, created="2026-10-11T19:00:00Z"):
a = {"@context": CTX, "type": "Annotation", "layer": layer_id, "motivation": motivation, "creator": who,
"created": created, "body": body, "target": tgt}
a["id"] = "urn:scienting:annotation:smithlab/" + record_id(H(jcs.dumpb(a)))
a["status"] = status
return a
ex_body = {"format": "scienting-exercise/1", "exercise": "anchor",
"prompt": "Find the table cell that gives the Set A bond constant for the CT–CT bond.",
"key": {"elements": [{"anchors": [anchor("@cc_ak")], "points": "1",
"feedback": {"hit": "That is the cell.", "miss": "Look in Table 1, row CT–CT."}}]},
"rule": {"match": "same-node"}}
check_body = {"format": "scienting-exercise/1", "exercise": "check",
"prompt": "Does the cited passage support the claim that harmonic terms are the most common choice?",
"key": {"verdict": {"value": "supports", "points": "1"},
"elements": [{"anchors": [anchor("@s1", "the most common description")], "points": "1"}]},
"rule": {"match": "overlap", "min_overlap": "0.5"}}
anns = [
ann({"type": "TextualBody", "kind": "gloss", "audience": "novice", "format": "text/markdown", "language": "en",
"value": "A *harmonic* term treats a bond like a spring: the energy grows with the square of the stretch."},
target(anchor("@s2", "bond force constant")), "describing", "confirmed"),
ann({"type": "SpecificResource", "kind": "claim-link", "source": "lode:amber/k-bond", "pin": "illustrative-pin",
"role": "states"}, target(anchor("@s2")), "linking", "confirmed"),
ann({"type": "TextualBody", "kind": "comment", "value": "The step size matters more than the text admits."},
target(anchor("@s6a", "steps of 0.01")), "commenting"),
ann({"type": "TextualBody", "kind": "question", "for": "group",
"value": "Why fit to a scan first and to frequencies second?"},
target(AN.make_anchor(doc, vals, I["@sec2"], rev, store=STORE)), "questioning"),
ann({"type": "TextualBody", "kind": "citation-context", "reference": I["@r2"], "verdict": "cannot-tell",
"reason": "value", "value": "Cited for the band position; the reference is not in the library."},
target(anchor("@s3", "1595")), "assessing"),
ann({"type": "TextualBody", "kind": "generated", "value": "Two panels: an energy scan and its fit residual.",
"generator": {"model": "example-model", "run": "illustrative-run-1"}},
target(AN.make_anchor(doc, vals, I["@fig1"], rev, store=STORE)), "describing", "proposed", model),
ann({"type": "TextualBody", "kind": "exercise", "value": ex_body["prompt"], "exercise": ex_body},
target(AN.make_anchor(doc, vals, I["@tab1"], rev, store=STORE)), "assessing", "confirmed"),
ann({"type": "TextualBody", "kind": "exercise", "value": check_body["prompt"], "exercise": check_body},
target(anchor("@s1")), "assessing", "confirmed"),
]
anns.append(ann({"type": "TextualBody", "kind": "comment", "value": "Agreed; see the residual panel."},
{"type": "SpecificResource", "source": anns[2]["id"]}, "replying", created="2026-10-11T19:05:00Z"))
confirmed = [a for a in anns if a["status"] == "confirmed"]
lr = RC.seal({"format": "scienting-receipt/1", "doc": "hale2026harmonic", "rev": rev, "level": "F3",
"scope": "targeted:layer", "nodes": [],
"records": sorted([["annotation", a["id"].rsplit("/", 1)[1], ann_hash(a)] for a in confirmed]),
"evidence": [], "by": "ana", "at": "2026-10-11T19:30:00Z"}, secret("ana"), "ana")
for a in confirmed:
a["receipt"] = lr["id"]
wr(out, "layer/layer.json", {"@context": CTX, "format": "scienting-layer/1", "id": layer_id,
"type": "AnnotationCollection", "label": "Smith lab reading of Hale and Osei",
"purpose": "Glosses, claim links and exercises for new students.",
"audience": "student", "visibility": "team", "license": {"spdx": "CC-BY-4.0"},
"creator": creator, "created": "2026-10-11T19:00:00Z", "project": "smithlab-charges",
"docs": ["smithlab/hale2026harmonic"], "total": len(anns)})
wr(out, "layer/annotations.jsonl", jsonl(anns))
wr(out, "layer/receipt.json", lr)
# Submissions and grades.
ex_ann = anns[6]
sub_ok = {"format": "scienting-submission/1", "exercise": ex_ann["id"], "doc": "smithlab/hale2026harmonic",
"rev": rev, "student": "s-7k2m", "answers": {"anchors": [anchor("@cc_ak")]},
"submitted_at": "2026-10-20T10:00:00Z"}
sub_ok["id"] = record_id(H(jcs.dumpb(sub_ok)))
sub_miss = dict(sub_ok, student="s-9q4p", answers={"anchors": [anchor("@cc_bk")]})
sub_miss.pop("id")
sub_miss["id"] = record_id(H(jcs.dumpb(sub_miss)))
ch_ann = anns[7]
sub_check = {"format": "scienting-submission/1", "exercise": ch_ann["id"], "doc": "smithlab/hale2026harmonic",
"rev": rev, "student": "s-7k2m",
"answers": {"verdict": "supports", "anchors": [anchor("@s1", "most common description of covalent")]},
"submitted_at": "2026-10-20T10:05:00Z"}
sub_check["id"] = record_id(H(jcs.dumpb(sub_check)))
grades = []
for name, sub, body in (("anchor-hit", sub_ok, ex_body), ("anchor-miss", sub_miss, ex_body),
("check-overlap", sub_check, check_body)):
g = GR.grade(body, sub, final)
wr(out, "submissions/%s.json" % name, sub)
wr(out, "grades/%s.json" % name, g)
grades.append({"id": name, "package": "fixtures/packages/hale2026harmonic", "exercise": body,
"submission": sub, "expect": g})
wv(out, "grading.jsonl", grades)
# Project.
wr(out, "project.json", {
"format": "scienting-project/1", "key": "smithlab-charges", "store": STORE, "name": "Charges and bonds",
"members": [{"person": "Ana Example", "role": "owner", "keys": ["ana"]},
{"person": "Ben Example", "role": "editor", "keys": ["ben", "ben-spot"]},
{"person": "Cara Example", "role": "reader", "keys": ["cara"]}],
"documents": [{"doc": "smithlab/hale2026harmonic", "reason": "The bond constants we start from.",
"tags": ["parameters"], "status": {"Ben Example": "read", "Cara Example": "reading"}},
{"doc": "okafor2025open", "reason": "Solvent shifts for the validation set.", "tags": ["spectra"]}],
"paths": [{"id": "onboarding", "title": "First week", "steps": [
{"type": "document", "ref": "smithlab/hale2026harmonic", "note": "Read the introduction."},
{"type": "node", "ref": "smithlab/hale2026harmonic#" + I["@tab1"], "note": "Then the table."},
{"type": "claim", "ref": "lode:amber/k-bond"}]}],
"layers": [layer_id], "datasets": ["ds-hale-bond-params", "ds-hale-ctct-scan"], "views": ["ctct-scan"],
"drafts": ["smithlab/smithlab2027charges"], "claims": {"lode": "amber"},
"settings": {"digit_rule": "anchored-sections", "drift_threshold": "0.25",
"spot_check": {"rate": "0.05", "minimum": 10}, "rights_unknown": "refuse",
"stages": "scienting-stages-default"}})
# A digitization of panel a, and the dataset it yields.
p2 = hale["pages"][1][0]
pa = I["@pa"]
ft = {t["text"]: t["id"] for t in doc.nodes[pa]["attrs"]["figure_text"]}
pts = [((110.0, 360.0), ("1.45", "3.10")), ((150.0, 330.0), ("1.50", "0.40")), ((190.0, 300.0), ("1.55", "2.80"))]
dg = {"format": "scienting-digitization/1", "doc": "smithlab/hale2026harmonic",
"target": {"anchor": AN.make_anchor(doc, vals, pa, rev, store=STORE), "page_fp": p2["page_fp"],
"region": [72.0, 260.0, 300.0, 410.0], "witness": hale["witness"]["sha256"]},
"axes": [{"id": "x", "scale": "linear", "unit": "angstrom",
"calibration": [{"at": [90.0, 400.0], "value": "1.4"}, {"at": [210.0, 400.0], "value": "1.6"}],
"check": {"at": [150.0, 400.0], "value": "1.5"}, "label": {"panel": pa, "figure_text": ft["r (Å)"]}},
{"id": "y", "scale": "linear", "unit": "kcal / mol",
"calibration": [{"at": [80.0, 400.0], "value": "0"}, {"at": [80.0, 280.0], "value": "4"}],
"label": {"panel": pa, "figure_text": ft["E (kcal mol⁻¹)"]}}],
"series": [{"id": "scan", "legend": "scan", "mark": {"color": "#285aa0", "marker": "circle"}, "kind": "points",
"values": [{"at": list(at), "value": {"x": x, "y": y},
"uncertainty": {"x": "0.01", "y": "0.05", "resolution": "0.01", "marker": "0.02",
"calibration": "0.01"}} for at, (x, y) in pts]}],
"method": {"kind": "manual", "tool": {"name": "hand", "version": "0"}},
"verification": {"image": "", "residual": "0.4", "max_residual": "0.9"},
"confirmation": None, "dataset": "ds-hale-ctct-scan", "rev": rev}
dg["verification"]["image"] = crop_sha("overlay of panel a")
dg["id"] = record_id(H(jcs.dumpb(dg)))
wr(out, "digitization.json", dg)
body = {k: v for k, v in dg.items() if k != "confirmation"}
conf = RC.seal({"format": "scienting-receipt/1", "doc": "hale2026harmonic", "rev": rev, "level": "F3",
"scope": "targeted:digitization", "nodes": [],
"records": [["digitization", dg["id"], H(jcs.dumpb(body))]],
"evidence": [{"kind": "crop", "node": pa, "sha256": crop_sha("overlay of panel a")}],
"by": "ana", "at": "2026-10-11T20:00:00Z"}, secret("ana"), "ana")
wr(out, "digitization-confirmation.json", conf)
ds = {"format": "scienting-dataset/1", "id": "ds-hale-ctct-scan", "name": "hale-ctct-scan",
"title": "Energy along the C–C stretch, read from Figure 1a",
"resources": [{"name": "scan", "path": "data.csv", "format": "csv", "encoding": "utf-8",
"schema": {"fields": [{"name": "point", "type": "string"},
{"name": "r", "type": "number", "unit": "angstrom",
"uncertainty": {"field": "r_u", "kind": "resolution"}},
{"name": "r_u", "type": "number", "unit": "angstrom"},
{"name": "e", "type": "number", "unit": "kcal / mol",
"uncertainty": {"field": "e_u", "kind": "resolution"}},
{"name": "e_u", "type": "number", "unit": "kcal / mol"}],
"primaryKey": "point"},
"origin": {"kind": "digitized", "digitization": dg["id"], "anchors": "anchors.jsonl"}}]}
csvd = "point,r,r_u,e,e_u\n" + "".join("p%d,%s,0.01,%s,0.05\n" % (i, x, y) for i, (_, (x, y)) in enumerate(pts))
va = [{"resource": "scan", "row": "p%d" % i, "field": f, "digitization": dg["id"], "series": "scan", "point": i}
for i in range(len(pts)) for f in ("r", "e")]
wr(out, "datasets/ds-hale-ctct-scan/datapackage.json", ds)
wr(out, "datasets/ds-hale-ctct-scan/data.csv", csvd.encode())
wr(out, "datasets/ds-hale-ctct-scan/anchors.jsonl", jsonl(va))
ds_files = sorted([["anchors.jsonl", H(jsonl(va)), len(jsonl(va))], ["data.csv", H(csvd), len(csvd.encode())],
["datapackage.json", H(jcs.dumps(ds) + "\n"), len((jcs.dumps(ds) + "\n").encode())]])
ds_hash = H(jcs.dumpb({"format": "scienting-dataset/1", "files": ds_files}))
wr(out, "view.vl.json", {
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"usermeta": {"scienting": {"format": "scienting-view/1", "id": "ctct-scan",
"data": [{"name": "ds-hale-ctct-scan", "dataset": "ds-hale-ctct-scan",
"hash": ds_hash, "resource": "scan"}]}},
"data": {"name": "ds-hale-ctct-scan"}, "mark": {"type": "point"},
"encoding": {"x": {"field": "r", "type": "quantitative", "title": "r (Å)"},
"y": {"field": "e", "type": "quantitative", "title": "E (kcal/mol)"}}})
# QA report.
q1 = hale["by_name"]["q1"]
q2 = hale["by_name"]["q2"]
n = len(q1["qa"]["sample"]) + len(q2["qa"]["sample"])
k = len(q1["qa"]["errors"]) + len(q2["qa"]["errors"])
wr(out, "qa-report.json", {"format": "scienting-qa-report/1", "id": record_id(H("qa report 1")),
"delivery": "smithlab evidence pilot (fictional)", "docs": ["smithlab/hale2026harmonic"],
"receipts": [hale["by_name"]["r1"]["id"], hale["by_name"]["r4"]["id"]],
"reviewers": [{"key": "ana", "sampled": n, "errors": k, "confidence": "0.95",
"method": "clopper-pearson", "bound": RC.bound_string(k, n)}],
"at": "2026-10-12T10:00:00Z"})
# Draft snapshot and sentence IDs.
s5 = I["@s5"]
snap_anchor = AN.make_anchor(doc, vals, s5, rev, store=STORE)
wr(out, "snapshot.json", {"format": "scienting-snapshot/1", "store": STORE, "made_at": "2026-10-11T21:00:00Z",
"quotes": [{"address": AN.format_address(STORE, "hale2026harmonic", s5, vals[s5]["node_hash"]),
"anchor": snap_anchor, "text": norm(hashing.plain(doc, s5)),
"fidelity": "F3", "result": "current"}],
"claims": [{"ref": "amber/k-bond", "pin": "illustrative-pin", "level": "illustrative",
"typst": "K_r"}],
"datasets": [{"id": "ds-hale-ctct-scan", "hash": ds_hash}]})
draft = Names.registry["smithlab2027charges"]
dd = Doc("smithlab2027charges", [json.loads(x) for x in open(os.path.join(
out, "fixtures", "packages", "smithlab2027charges", "nodes.jsonl"))])
lines = []
for nid in dd.preorder():
if dd.nodes[nid]["kind"] == "sentence":
sec = next((a for a in reversed(dd.ancestors(nid)) if dd.nodes[a]["kind"] == "section"), None)
lines.append({"id": nid, "text_hash": dd.nodes[nid]["text_hash"], "section": sec,
"ord": dd.nodes[nid]["ord"]})
lines[-1]["pinned"] = "energy-sentence"
wr(out, "draft-ids.jsonl", jsonl(lines))
# --- invalid packages ---------------------------------------------------------------
def invalid(out):
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
def edit_nodes(fn):
ns = copy.deepcopy(nodes)
fn(ns)
return jsonl(ns)
def by(ns, name):
return next(n for n in ns if n["id"] == nm[name])
def case(name, rule, change, note, raw=None):
f = dict(files)
f.update(change)
d = os.path.join(base, name, "package")
write(d, f)
if raw:
raw(d)
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(
lambda ns: by(ns, "@a").update(content=[{"kind": "text", "text": "The value is 42.5 at 300 K."}]))},
"Text changed without its hashes.")
case("unknown-kind", "package.schema", {"nodes.jsonl": edit_nodes(lambda ns: by(ns, "@p2").update(kind="aside"))},
"Kinds outside the list are refused.")
case("heading-not-first", "doc.tree.child", {"nodes.jsonl": edit_nodes(lambda ns: _swap_first(ns, nm["@sec"]))},
"A heading must be a section's first child.")
case("cite-not-reference", "doc.run.cite", {"nodes.jsonl": edit_nodes(
lambda ns: by(ns, "@b").update(content=[r if r["kind"] != "cite" else dict(r, refs=[nm["@a"]])
for r in by(ns, "@b")["content"]]))},
"A cite run names a node that is not a reference.")
case("unlisted-file", "package.manifest.unlisted", {}, "A file the manifest does not list.",
raw=lambda d: open(os.path.join(d, "notes.txt"), "w").write("stray\n"))
case("manifest-hash", "package.manifest.hash", {}, "A file changed after the manifest was written.",
raw=lambda d: open(os.path.join(d, "provenance.json"), "a").write(" "))
case("not-canonical", "package.file.canonical", {}, "A line that is valid JSON but not canonical JSON.",
raw=lambda d: _rewrite_noncanonical(d))
case("root-mismatch", "doc.hash.root", {"document.json": _doc_edit(files, root="1" * 64)},
"document.json's root is not the root node's node_hash.")
case("receipt-root", "doc.receipt.root", {"receipts.jsonl": _receipt_edit(files, root="2" * 64)},
"A receipt whose Merkle root does not recompute.")
case("receipt-signature", "doc.receipt.signature", {"receipts.jsonl": _receipt_edit(files, at="2026-10-11T12:00:01Z")},
"A receipt changed after it was signed.")
case("checks-only-plain", "doc.policy.withheld", {"document.json": _doc_edit(files, policy="checks-only")},
"A checks-only package that still carries text and plain hashes.")
case("bad-id", "package.schema", {"nodes.jsonl": edit_nodes(lambda ns: _rename(ns, nm["@d"], "i1lo0uu"))},
"IDs use Crockford base32, which has no i, l, o or u.")
case("empty-optional-file", "package.file.empty", {"suspects.jsonl": b""}, "An optional JSONL file with no records.")
case("alias-dangling", "doc.id.successor", {"ids.jsonl": jsonl([{"id": "b-p01-text-001", "kind": "alias",
"successor": "zzzzzzz"}])},
"An alias whose node does not exist.")
case("location-unknown-page", "doc.location.page", {"locations.jsonl": jsonl([
{"node": nm["@a"], "page_fp": "3" * 64, "page": 1, "boxes": [[72, 72, 200, 84]]}])},
"A location on a page no witness has.")
case("style-sup-sub", "doc.run.style", {"nodes.jsonl": edit_nodes(lambda ns: by(ns, "@c").update(
content=[{"kind": "text", "text": "A second paragraph.", "style": ["sub", "sup"]}]))},
"A run cannot be both a subscript and a superscript.")
def _swap_first(ns, sec):
kids = sorted([n for n in ns if n["parent"] == sec], key=lambda n: n["ord"])
kids[0]["ord"], kids[1]["ord"] = kids[1]["ord"], kids[0]["ord"]
d = Doc("x", ns)
order = d.preorder()
ns.sort(key=lambda n: order.index(n["id"]))
def _rename(ns, old, new):
for n in ns:
if n["id"] == old:
n["id"] = new
def _doc_edit(files, **kw):
d = json.loads(files["document.json"])
d.update(kw)
return (jcs.dumps(d) + "\n").encode()
def _receipt_edit(files, **kw):
rs = [json.loads(x) for x in files["receipts.jsonl"].decode().splitlines()]
rs[0].update(kw)
return jsonl(rs)
def _rewrite_noncanonical(d):
p = os.path.join(d, "nodes.jsonl")
lines = open(p).read().splitlines()
lines[0] = json.dumps(json.loads(lines[0]), indent=None, separators=(", ", ": "))
data = ("\n".join(lines) + "\n").encode()
open(p, "wb").write(data)
m = json.load(open(os.path.join(d, "MANIFEST.json")))
for f in m["files"]:
if f["path"] == "nodes.jsonl":
f["sha256"], f["size"] = H(data), len(data)
open(os.path.join(d, "MANIFEST.json"), "w").write(jcs.dumps(m) + "\n")
# --- main -----------------------------------------------------------------------------
OUT = ROOT
def main(out):
global OUT
OUT = out
for sub in ("fixtures/packages", "fixtures/records", "fixtures/invalid", "fixtures/keys"):
p = os.path.join(out, sub)
if os.path.exists(p):
shutil.rmtree(p)
os.makedirs(p)
vec = os.path.join(out, "vectors")
os.makedirs(vec, exist_ok=True)
for f in os.listdir(vec):
if f.endswith(".jsonl"):
os.remove(os.path.join(vec, f))
for kid in KEYS:
with open(os.path.join(out, "fixtures", "keys", kid + ".ed25519.hex"), "w") as f:
f.write(secret(kid).hex() + "\n")
with open(os.path.join(out, "fixtures", "keys", "smithlab-team-secret.hex"), "w") as f:
f.write(TEAM_SECRET.hex() + "\n")
Names.registry.clear()
hale = build_hale(out)
build_checks_only(out, hale)
build_simple(out, "okafor2025open")
build_simple(out, "smithlab2027charges")
tiny = tiny_package(os.path.join(out, "fixtures", "packages", "tiny2026example"))
records(out, hale)
vectors(out, hale, tiny)
invalid(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)