18 Commits
Author SHA1 Message Date
funkpower 8f048efd69 bindings: let go of the interpreter while reading a book
read_colophon is several milliseconds of computation over a few hundred
kilobytes, and it held the GIL for all of them. A caller reading a
library through asyncio.to_thread therefore got no overlap whatsoever:
six books in flight, one book's worth of work at a time.

Measured on this library, that was most of why a concurrent pass still
paced at four and a half hours.
2026-09-21 10:57:38 -04:00
funkpower 0416359c65 bindings: colophon's own question, and the reply folded back in
colophon has composed the question about what its pages did not settle
since it was written, and merged a reply back into the record it read.
Both were reachable only from a terminal, so a pipeline wanting the same
fallback would have had to write its own prompt — a second one, drifting
from the first, asking for a shape merge does not accept.

merge fills only the unsettled fields, so a model cannot overwrite what a
book printed about itself. That is the property that makes asking one
safe, and it belongs to colophon rather than to any caller.
2026-09-21 10:49:51 -04:00
funkpower 18cc217c0d bindings: what a book says about itself, and what the register says back
book-pipeline reaches for colophon and bibfetch on every book it repairs.
Shelling out would mean a process per book per tool across five thousand
books, and parsing back the JSON it is about to parse anyway.

bibfetch's fetch loop lived in main.rs, so binding it meant either
lifting it or writing a second one. A second implementation of 'which
register answers about this kind of identifier, and in what order' would
drift from the first, and the copy nobody runs from a terminal would
drift silently. It is now bibfetch::ask, and the command line calls it
too — verified by running the CLI before and after and diffing.

fetch_identifier releases the GIL around the request. It is the only
function here that opens a socket, and holding the interpreter for the
length of an HTTP call would stop every other thread in it.

bibfetch is optional and only the python feature turns it on: its HTTP
client does not build for wasm32, and an unconditional dependency would
have taken the JavaScript package down with it.
2026-09-21 10:20:24 -04:00
funkpower 57bc4356ab publish as catena-corpus on PyPI and npm
Both registries already have a `catena`: an HPE blockchain client on PyPI,
and a JavaScript OOP wrapper at 2.2.0 on npm. Neither is going anywhere, so
the distributions take a name that is free on both.

The Python import is unaffected — a distribution name and a module name are
independent there, so `pip install catena-corpus` still gives `import
catena`, and nothing in the examples or the bindings changes. On npm the
package name is the import specifier, so that one does change: `import
{ chunkMarkdown } from "catena-corpus"`.

The crates keep their own names. `catena` is free on crates.io.
2026-09-21 08:15:26 -04:00
funkpower d1ed82d112 bindings: the catalogue audit book-pipeline had its own copy of
bibclean is "catalogue records audited and repaired, with a reason for every
change", and book-pipeline's metadata module was a second implementation of
the same thing — ported from it in the first place. Twelve functions bound:
the audit, its severity, the two commonest faults, the three repairs that
need no lookup, ISBNs checked and found, and whether two titles name the same
work.

Checked against that Python across all 5,385 books in the library — every
title, every author, every ISBN, every filename, and four thousand title
pairs. Identical on all of it but one: `The Soul After Death: Contemporary
'After-Death' Experiences`, where the Python lowercased a word inside the
quotation marks and bibclean leaves it alone.
2026-09-20 15:22:06 -04:00
funkpower b28e272506 bindings: what a closed subject scheme says about a heading
book-pipeline held its own copy of the scheme operations bibclean already
had. The scheme itself — which headings a collection uses, their aliases,
their parents — stays with the collection, because that is the most
collection-specific thing there is. What a scheme *means* is the same
question everywhere, and this answers it: `canonical_subject`,
`compose_subjects`, `needs_subject`.

Checked against the Python over the library's 228 distinct tags and all
5,385 books: identical on every one.
2026-09-20 15:15:48 -04:00
funkpower 7a08ed953d bindings: the author a record's own text can offer
book-pipeline had its own copy of bibclean's author recovery. Bound on all
four surfaces so it can stop.

Checked against that Python over all 5,385 books in the library: 5,375
identical. On the other ten the Python had taken a translator, an illustrator
or the publisher off a copyright line — Sally-Ann Spencer for *The Dwarves*,
Thomas Nelson for a table of contents, C. S. Lewis for a book about Tolkien —
and bibclean either finds the real author in the Library of Congress block or
says nothing. Every one of the ten was read by hand.
2026-09-20 15:10:35 -04:00
funkpower 6aeea58147 bindings: the footnote signal, which book-pipeline still had its own copy of
`looks_like_footnote_citations` is the function chunkmd's `looks_like_citations`
was ported from, and book-pipeline was still running the Python original at
four call sites. Bound on all four surfaces so it can stop.

Checked against the Python over 34,458 chunks of the live corpus: no
disagreement.
2026-09-20 14:44:09 -04:00
funkpower c071f84829 citefind: the references in a bibliography, and what could not be read off them
Cuts a reference list into entries — at the note numbers where there are any,
at the layout where there are not — and reads each as far as its punctuation
allows. Fields that would not parse are named in `unsettled` rather than
guessed at, and `--prompt` asks a model about exactly those and no others.

Over 202 real reference sections from the corpus: 8,566 references, 95% with
a title, 82% with a year, 46% settling every field with no model involved.
A title is refused where the entry was never divided, so no record reports
its own text back as the work it cites.

`ibid.` is flagged and left alone — it points at a work, and the entry does
not say which. A rule of dashes is filled in, because the style defines it as
the author above.

chunkmd gains `note_starts` and `cites_something`: recognising a page of
notes and splitting one are the same knowledge, and two copies of that
pattern would drift.
2026-09-20 10:38:48 -04:00
funkpower 0b849c39c4 cueseg: chapter by vectors somebody else made, where there are some
The hashed bag of words asks only whether two stretches repeat the same
words, and cannot tell that ships and vessels are one subject. An embedding
can. segment_with takes one vector per window and falls back to the words
when it is given none, or the wrong number of them — a caller that embedded
some other recording's windows is asking about that recording.

windows_of gives the text to embed, in the order the vectors come back in,
so nobody has to reimplement the windowing pass to use this.

Bound in all three languages, and on the command line as --windows and
--embeddings, which pipe through emb. The C ABI version goes to 2:
catena_segment_cues gained an argument.
2026-09-20 09:12:04 -04:00
funkpower dd09499e68 bindings: cueseg, so a transcript can be chaptered in process 2026-09-20 09:00:26 -04:00
funkpower 577b381868 bindings: how to verify a swap, without the scaffolding for one 2026-09-19 20:43:16 -04:00
funkpower 645116d5ed shadow: run both implementations over a corpus and diff them 2026-09-19 20:40:15 -04:00
funkpower d47881ef3b idnorm and bibfetch: an identifier read, and a register asked 2026-09-19 19:19:54 -04:00
funkpower 05b363ea8a seriesof: the series a book belongs to, and which volume this is 2026-09-19 19:03:47 -04:00
funkpower e9d22ee1fa bindings: typecheck the declarations the package ships 2026-09-19 18:57:24 -04:00
funkpower 48600bd90c bindings: a C ABI and an npm package alongside the wheel 2026-09-19 18:40:08 -04:00
funkpower cf126dddc3 bindings: the libraries in process, for Python 2026-09-19 17:54:18 -04:00