2026-07-21T00:35:13Z by Showboat 0.6.1
This is a Simple English version of a longer, more technical page. It uses short sentences and explains each hard word.
The library keeps facts about books and readers in a computer. Staff members read some of these facts in reports. A report is a table of facts, like a list of books.
Some facts are private. A private fact is a fact about one person, like a name, a phone number, or a library card number. Private facts must not go into a report that many staff members can open.
This page shows the one rule that keeps private facts out of the open reports. The rule is short: a report may show a fact only if a person wrote that fact on a list first. If a fact is not on the list, the computer refuses it.
The page follows one report through this rule. Then it follows the same report to a new computer that will show it one day. That computer is ready, but no one has turned it on yet. Every box of computer text below is real. A tool can run the same steps again and get the same result.
Two versions. This page uses Simple English. There is also a full version for people who work with computers. It uses the real tools and gives more detail: read the full version →.
For a long time, the library ran one small web page for each report. Each page had its own settings for who was allowed to see it.
This became a problem. There were many pages. Each page had its own settings. A person had to keep all of them correct. One wrong setting on one page could show a private fact by mistake.
So the library made a change. Now there are only a few big groups, called tiers. A tier is a group of reports that share one setting for who may see them. The open tier holds the reports that any staff member may see. A report joins the open tier only after it follows the rule on this page.
Each report comes with a small text file. This file is called a manifest. The manifest lists the rules for that report.
The most important part of the manifest is the allowlist. The allowlist names every column the report is allowed to show. A column is one kind of fact in a table, like a title, an author, or an email address.
The allowlist is the whole rule. It is not a clever program that tries to spot private facts. It is just a plain list that a person writes and another person can check. A report shows a column only if the column is on this list.
Below, the real program reads one manifest and prints its list:
uv run python docs/demos/declare-to-publish/_driver.py declare
The declaration — the whole boundary is this list
=================================================
sensitivity open
declared columns ['work_key', 'title', 'author', 'format', 'call_number']
justified PII cols {'call_number': 'shelf location, not a patron barcode'}
content exceptions {'title': ['phone']}
Parsed by the real contract validator. Every column a mart may expose on
the open tier is named above. A column absent from this list cannot be
published — you declare your way IN, you never scan your way out.
Now look at a report with a column called patron_email.
No one wrote patron_email in the manifest. Nothing else is
wrong with the report.
The checker looks at the report. A checker is a program that looks at a report before the report goes out. The checker does not need to know that this column holds email addresses. It refuses the column for one reason: no one wrote the column on the list.
This is the most important idea on this page. A new private column is refused the day it appears. No person needs to write a new rule first. The rule already says no to everything that is not on the list.
Below, the same checker that the library uses says no:
uv run python docs/demos/declare-to-publish/_driver.py default-deny
Default-deny — an undeclared column is refused before a value is read
=====================================================================
fixture table `works` columns: work_key, title, patron_email
`patron_email` is not in the manifest allowlist.
RESULT: REFUSED — the open tier will not accept this mart:
[works] undeclared column 'patron_email' (default-deny)
The leak dies at silence-in-the-manifest. The guard never had to
recognise an email address — the column was simply never declared.
A column can be on the allowlist and still be refused. This happens when the name of the column looks like a private fact.
The checker keeps a small list of scary words, like
barcode, patron, and email. If a
column name holds one of these words, the checker wants more than a
place on the list. It wants a written reason in the manifest that says
why this column is safe.
Here the column is named patron_barcode. It is on the
allowlist. But the manifest gives no written reason for it. So the
checker says no:
uv run python docs/demos/declare-to-publish/_driver.py name-lint
Second gate — an allowlisted but PII-named column needs written justification
=============================================================================
`patron_barcode` IS in the allowlist, but has no pii_justifications entry.
RESULT: REFUSED — the open tier will not accept this mart:
[works] allowlisted PII-shaped column 'patron_barcode' lacks justification
The column NAME (barcode/patron/email/...) trips a lint even once declared;
clearing it takes an explicit, reviewable reason in the manifest — not silence.
Only now does the checker read the words inside the report. It looks for shapes that match an email address, a phone number, or a library card number.
This second look is a help, but it is not the real rule. It can miss things. It cannot spot a name, a birth date, or a number written with no spaces. The library knows this and says so in its own notes. The list in Step 1 is the real wall. This second look is only a net behind the wall.
Here a column named author is on the list and has a safe
name. But one value inside it has the shape of a library card number.
The net catches it:
uv run python docs/demos/declare-to-publish/_driver.py content-net
The content scan — a best-effort net, NOT the boundary
======================================================
`author` is allowlisted and benignly named, but one value is barcode-shaped.
RESULT: REFUSED — the open tier will not accept this mart:
[works] barcode-shaped content in 'author'
This secondary regex sweep catches shaped values in free text. It is
explicitly best-effort: it misses names, DOBs, and unseparated numbers by
construction. The wall is the allowlist above; this is a backstop behind it.
The rule does not say no to everything. A report passes when it is honest about itself.
To pass, a report names every column on the list. It gives a written reason for any column with a scary name. And it marks any safe value that only looks private, like a phone number printed inside the title of a book.
The same checker runs on the real computer every day. Once, as a test, a worker put a file full of private facts into the open group on purpose. The checker refused it. The reason it gave was the rule from Step 2: the file had columns that were not on the list.
Here is a report that is honest about every column. The checker lets it through:
uv run python docs/demos/declare-to-publish/_driver.py clean
A fully-declared, clean mart — the guard gets out of the way
============================================================
Every column declared; `call_number` justified; a phone-shaped title
admitted only because `title=["phone"]` is declared in content_exceptions.
RESULT: clean — no offenders. This mart may publish to the open tier.
Default-deny is not a blanket 'no' — a mart that declares itself honestly
passes. The boundary rewards declaration, it doesn't punish publishing.
Today the open reports live on one computer. The plan is to move them to a new computer. The code for this move is written and checked, but no one has turned it on. No report has gone to the new computer yet.
The next steps show that code running where it is safe to run. They are also honest about where it cannot run yet.
When a report is sent, the computer also writes a short note that says whether the job worked. This note is called telemetry. Telemetry lets a worker see that the job ran.
But telemetry only watches the job. It must never be able to stop the job. If the setup for telemetry is wrong, the job must still send the report.
So the code has two paths. The strict path stops with an error when telemetry is set up wrong. The sending path does not. It sees the same error, writes the note off, and keeps going. The report still goes out. Below, both paths run with a wrong setup:
uv run python docs/demos/declare-to-publish/_driver.py telemetry
Telemetry fails OPEN — a monitor can never kill the thing it monitors
=====================================================================
TelemetryClient.from_env() with no config -> raises RuntimeError
push/cli._telemetry_client() same config -> returns a client, buffer disabled = True
from_env() is strict on purpose (one typo shouldn't run a whole fleet
un-telemetered). But the PUSH must serve marts even when telemetry is
misconfigured, so cli catches the raise and degrades to a no-op buffer.
The house stance is fail-open: never let the monitor take down the mart.
Before the library turns the new computer on, it wants a safe test. This test is called a dry run. A dry run does all the work of a real send, but it stops just before it sends anything. It cannot reach the new computer at all.
A dry run gets the report ready and writes the page that staff will see. Then it stops. Below, a dry run works on a copy of the reports, so the result is the same every time:
uv run python docs/demos/declare-to-publish/_driver.py dryrun
Gate 1a — the real dry run compiles and stops before delivery
=============================================================
dry-run: would deliver 1 mart(s) to reporting-server
compiled manifest — files: ['holdings.sqlite']
That one line is the whole of what a dry run does to the world: it
compiles the manifest, renders the staff landing page, and returns — the
code path stops at push/cli.py:84, before deliver() ever opens an ssh
connection. It is the safe first rung of the cutover ladder.
The same command was run once for real, on the true 370 MB report, on the computer that builds it (run on 2026-07-20). It is not run again by the checking tool. The name of the new computer is left out here. It stopped just as safely:
$ python -m chimpy_lake.marts push open <server> --dry-run
dry-run: would deliver 1 mart(s) to <server>
It left behind only a short summary of what it would have sent. Nothing left the computer:
{
"files": [
{ "name": "catalog_holdings.sqlite", "bytes": 370196480 }
],
"instance": "<server>",
"registry_commit": "d07f250"
}
The line registry_commit: d07f250 is a label. It says
which exact set of rules the report was built with. The report carries
its own label, so the new computer can see it.
A tool can run every box above again and check that the result is the same. Under that, the tests for the checker and the sending code all pass:
uv run pytest tests/marts/test_pii_guard.py tests/marts/push/test_push_seam.py tests/marts/push/test_push_cli.py -q 2>&1 | tail -1 | sed -E 's/ in [0-9.]+s//'38 passed
Everything above is finished. It runs, and the result can be checked. But the move to the new computer is not done. Here is what is left, in plain words:
The one idea holds all the way down. The open reports are safe to show because a report gets in only by naming every one of its columns first. The code that will carry a report to the new computer is built to the same care. It waits for a person to walk the last few steps.
← all walkthroughs · Rendered from a8057af on 2026-07-21 · showboat verify: reproduces. A living artifact — the version ledger is git.