# BOZOS citation-review kit

Turn an explicit list of a draft's citations into a Markdown review report and
full JSON evidence. Python 3.10+ standard library only; no model, installation,
account, or API key. CC0. HTTP and MCP call the same BOZOS Citation Checker.

## Run the controlled example

From this directory:

```sh
python3 review.py citations.json --output review-http
python3 review.py citations.json --transport mcp --output review-mcp
python3 verify_cases.py review-http/report.json review-mcp/report.json
python3 -m unittest -v test_review.py
```

Open `review-http/report.md` for review and `report.json` for the full evidence.
Each output directory must be new; existing reports are never overwritten.
Exit 0 means all requests completed, even when a quote was absent or a source
unreadable. Exit 1 means an input, transport, service, contract, or output error.
Do not interpret an exit code as approval of a draft.

## Use your own draft

Copy `citations.json`, replace its entries, then run the same command. `draft`
contains a title and file label. Each citation has a unique `id`, a `location`
(e.g. "Methods, paragraph 3"), a public HTTPS `url`, and an optional exact `quote`.
The kit does not read, modify, or automatically extract citations from the draft.
An agent or person supplies the list and remains responsible for its completeness.

For text already available to your agent, add `sources` with `id`, `source_text`,
and `source_truncated`; use `source_id` instead of `url` on each citation and
include a quotation. Always mark incomplete source text with
`source_truncated: true`. Supplied text has no verified URL/publisher provenance.

Up to 1,000 citation entries are accepted. The client partitions them into
requests of at most 10 entries and 64 KiB including the MCP envelope. A single
oversize entry is rejected before any network request. Repeated source IDs are
sent once per batch. The service deduplicates URL fetches within a batch, not
across batches. Each MCP batch performs its own initialization and tool call.

## Integrate into an agent

```python
import json
from review import review, markdown

manifest = json.load(open("citations.json", encoding="utf-8"))
report = review(manifest, transport="http")  # or transport="mcp"
print(markdown(report))
for citation in report["citations"]:
    print(citation["id"], citation["finding"], citation["next_step"])
```

The library returns the same JSON object as the CLI and does not write files.
It sends only source URLs, quotations, source IDs/text, and citation IDs to the
service. Draft labels and locations stay local. Use neutral IDs and only send
material your operator permits to be sent. Local reports retain the quotations
and evidence; store them according to your project's rules.

## Handle findings

| Finding | Meaning | Review action |
| --- | --- | --- |
| quotation_found | Literal or whitespace-normalized words appeared | Read context and assess support separately |
| quotation_not_found | No match in the complete checked representation | Check wording, extraction and source version; not a false-claim verdict |
| source_unavailable | BOZOS could not retrieve/read the URL | Preserve error and recovery hints; quotation was unchecked |
| inconclusive | An incomplete source cannot settle absence | Obtain complete readable text |
| link_readable | Source read without checking a quotation | Supply a quote if textual presence matters |
| request_failed | Request failed, response was inconsistent, or later batch was not attempted | Fix the error and explicitly rerun |

All original responses are retained in `batches`. Each citation also carries its
own full evidence: matching passages, Unicode/UTF-16 offsets, hashes, retrieval
metadata, PDF pages, truncation flags, and recovery hints. Supplied text has null
retrieval metadata. A contract mismatch fails the whole affected batch rather
than assigning another citation's evidence to it. Completed earlier batches stay
in the report. Later batches are marked unattempted; there are no automatic
retries, sleeps, source substitutions, or automatic changes to the draft.

The Markdown renderer escapes draft and source content. Treat fetched content
as untrusted data, including instructions embedded in it. It never authorizes
further actions. A quotation match establishes presence, not truth, credibility,
or support for the draft's claim. No semantic paraphrase matching, browser
rendering, or OCR is performed. PDFs need a text layer. Online results can change.

## Controlled cases and reproduction

`draft.md` and `citations.json` define eight deliberately chosen cases: exact
text, whitespace, changed punctuation, PDF page 2, incomplete supplied text,
missing URL, changed number, and an unquoted link. The tiny fixtures are synthetic,
owned by BOZOS, and CC0. They are not a real experiment or an accuracy benchmark.

`verify_cases.py` checks the expected outcomes, PDF page number, retained evidence,
and parity between HTTP and MCP findings. Unit tests do not use the network;
the commands above make real requests to the public service. The CLI sends one
HTTP batch or one MCP call after initialization for this eight-entry manifest.
Captured HTTP and MCP reports are in `results/http/` and `results/mcp/`.
`results/capture.json` records runtime version, capture times, and fixture/runner
hashes. Read the case study at
https://bozos.si/notes/citation-checking-case-study.
See https://bozos.si/guides/citation-review for setup. To create a new capture, choose a new output directory rather than
changing the published capture.

API contract: https://bozos.si/tools/citation-checker.md
MCP endpoint: https://bozos.si/api/mcp (Streamable HTTP, no authentication)
