Files
catena/docs/contract.md
T

2.7 KiB

The contract

Every program here obeys all of this, or it does not ship. It is worth reading once, because it is what lets these be used together without reading any of them.

Back to the index.

One job

A tool that needs a --mode flag to change what it is is two tools.

stdin to stdout

Input on stdin or a path argument, result on stdout, diagnostics on stderr only — never content and chatter on the same stream. A tool that takes a path instead of a stream says why in its --help; pdftriage does, because it reads two specific page ranges rather than the whole file.

Streams, not documents

Anything list-shaped is NDJSON: one record per line, flushed as it is produced, so a slow stage never blocks the pipe behind it.

The reader is strict on purpose. It refuses a record whose version it does not know, it stops at the first bad line rather than reading past a disagreement about the format, and it bounds how much one line may cost — a filter pointed at a binary file should fail, not allocate the file.

Predicates answer with exit codes

Code Meaning
0 Yes, or done
1 No
2 Could not tell
64 The command line was wrong
65 The input was not what the tool can work on
69 A program it needs is not installed
70 The tool's own fault
74 Reading or writing failed

The failure codes start where sysexits.h puts them so that "no" and "broke" are never the same number. That is what makes this work:

for f in *.pdf; do pdftriage --quiet "$f" && ocrmypdf "$f" "ocr/$f"; done

A consumer closing the pipe is not a failure. … | head -1 exits 0, like every other filter.

No network, no database, no config file, no state

Two exceptions, both declared in --help:

  • emb, whose whole job is an HTTP call.
  • pdftriage, which shells out to pdftotext and pdfinfo and exits 69 when either is missing.

No ambient knowledge

No hostnames, no local paths, no private vocabularies compiled in. General scheme data — ISBN check digits, the HTML character references — may be; anything local is a --rules file.

Deterministic

Same bytes in, same bytes out, across runs and machines. No hash-order dependence, no clock, no locale reads. Floats that JSON cannot carry are refused where they are produced rather than written as null and lost.

--json everywhere

The human-readable default is for a person at a terminal; --json is the contract, and it does not change shape without a major version. Tools whose only sensible output is a stream of records write that stream and have no --json to add.