northern lantern house
The Bindery — Operating Notes
What the Bindery is, what it refuses to do, and what to reach for when something is wrong.
What this is
Six WooCommerce stores keep selling and keep the money. Everything else — what a product is,
what it grants, what it delivers and who owns it — happens here, once, instead of six times in
six admin panels.
That is the whole idea, and it is worth being precise about the part that is easy to get
wrong. The Bindery is not a shop. It never takes a payment, never issues a refund, never
holds a card and never decides a price. WooCommerce does all of that, on the site where the
product already sells, and it goes on doing it whether or not this application is running.
What the Bindery owns is the answer to four questions that currently have no single place to
live:
- What exists? Every product across every store, in one table.
- What does it grant? A SKU grants an edition. A SKU is never an edition.
- Where do the two disagree? With the store, with the register, with the content tree.
- Who owns it? A licence key, held on a device, and nothing else.
The one line that everything else follows from
> The register is a projection, and can always be rebuilt from WooCommerce orders plus the
> Git content tree.
Losing it is a re-sync, not a disaster. That is what makes the rest safe to move quickly on,
and it is why nothing in this system is allowed to hold state that exists nowhere else.
There is exactly one exception, and it is deliberate: a reader's own notes and reading
position live on their device, exist in no register, and export without an account.
The two faces
Two faces on one spine, and a person using one should never see a control that belongs to the
other.
The Bench
Private. Every product across every store in one table, with the places the store, the
register and the content tree disagree — ranked by what each one costs a person, and each
carrying the specific thing that closes it.
The Bench is the face that pays for the project on the first day, because six admin panels
become one screen. It is also the face that has to be honest about itself: **every figure on
it says whether it is live.** The banner is part of the frame rather than something each
screen remembers to include, because the screen it gets left off is the screen where a
decision gets made on a stale number.
The Library
For buyers. Everything owned, from every publication, behind one licence key. No password, no
account, no email round trip, and it opens with no signal at all.
The Library is the face the reports were about, and it arrives second on purpose — on a spine
that is already load-bearing rather than as a speculative build.
Why they are one application and not two
Because a buyer holding two keys from two publications is the thing the whole architecture
exists to serve, and localStorage is partitioned by origin. Two deployments would be two
libraries that can never meet, whatever either one intended.
The licence key is the whole authentication system
There is no password on any screen here, no email box, no code to wait for and no account to
create. That is not a shortcut. It is the largest single decision in the project, and it was
made against two prior specifications that both said to build the opposite.
How it works
- On order completion the bridge mints a key from 32 bytes of CSPRNG. Never
Math.random—
this is a credential.
- The register stores only
sha256(key). A register dump is not a list of keys. - The key is delivered in the WooCommerce order email **that already sends and already
lands**. No new sender, no new domain, no SPF, no DKIM, no DMARC, no deliverability project.
- It arrives in a URL fragment, which browsers do not transmit — so it is in no access
log, no referrer and no proxy trace — and it is scrubbed out of the address bar the moment
it has been taken.
- The register signs a statement of what that key holds. The device verifies the signature
locally, with a public key. No database, no network, no service in the read path.
What that deletes
No password storage. No password reset. No one-time codes. No magic links. No transactional
email provider. No session service. No account-merge system. No authentication dependency in
the offline path.
And one thing it makes better rather than merely cheaper
Somebody who bought from one shop under a work address and from another under a personal one
adds both keys to the same device and has one library. The key is the join, not the email.
The prior specifications spent pages mitigating that problem. Here it does not exist.
The two rules that keep it honest
- A key can be passed around, and the answer is a soft cap that counts and never blocks. The
cost of one wrongly locked-out customer exceeds the cost of a shared key by a wide margin.
- A register outage cannot lock out a paying customer. That was the worst email this business
could receive, and it is now structurally unreachable rather than carefully avoided.
The offline promise
> Someone standing in a river with no signal opens what they bought. Any proposal that
> weakens this is rejected without further argument.
That sentence outranks every other requirement in the contract, so it is worth saying exactly
what makes it true rather than leaving it as a promise.
What happens when the app opens
The licence key is in local storage. The signed grant is beside it. The signature is checked
on the device with a public key baked into the build. The editions already downloaded are in
IndexedDB. No network. No register. No WordPress. No WooCommerce.
The network is used for exactly three things, all optional and all non-blocking: refreshing
the grant before it lapses, downloading an edition not yet held, and — only if it was turned
on — pushing a reader's own work to their own record.
What that costs, in practice
- The faces are in the deploy. Four font files, latin only, about 93 KB. A render-blocking
stylesheet from a third party is a network dependency on the critical path of a cold open,
which is the one thing this promise cannot have.
- The shell is precached whole. Editions are not: they are the product, they are large,
and the reader downloads them deliberately, with a checksum and a version, into storage they
can see and clear.
- An update waits to be told. A new version sits in the wings until somebody presses a
button. Swapping the application out from under a person mid-read is how a reading position
vanishes and an app gets a reputation.
The one thing this promise is not
It is not DRM. Once a buyer has legitimately downloaded bytes for offline use, no server
reaches into a disconnected device. What is promised is that unauthenticated access is
prevented, that entitlement is enforced on delivery, and that future sync is revocable. It is
not that purchased content cannot be copied by the person who bought it.
Reading drift
Drift is where the store, the register and the content tree disagree. It is the screen that
pays for the project, and it is built on one rule:
> A drift row is never a warning to be lived with. It carries what is true in each place,
> which place should win, and the specific action that closes it.
A report that says "these disagree" and stops is how a fleet acquires four documents that all
claim to be the register.
Severity is about consequence, not tidiness
- Blocking means somebody has paid and cannot get what they paid for, or is about to.
Nothing else earns the word.
- Serious means it will cost somebody something soon.
- Untidy means it is wrong and nobody is currently hurt by it.
Authority, including the honest refusal
Every row names which source wins. Most of the time that is the store, the register or the
content tree. Sometimes it is the operator — which means the system genuinely cannot know
and refuses to guess. A price ladder recorded in two documents that disagree on five of six
products is exactly that, and quietly picking one would be worse than saying so.
The three findings that were live when this was written
- Seven products selling between $9 and $89 with no SKU at all, so nothing can grant,
bundle, reconcile or deliver them through an application — and every file at a public
address where a link is the whole security model.
- Two products at $129 and $149 published with an empty SKU.
- One publication shipping two catalogues in one repository with zero SKU overlap, both
claiming to be the source of truth.
Twenty-two products were selling across three publications. The register knew about four.
When something is wrong
The Bench says "Not live"
That is the honest state, not a failure. It means no operator token has been pasted into this
browser, or the register did not answer. The reading shown underneath it is the last one
taken, and its date is on screen. Settings is where the token goes; it is stored in that
browser and never in the deployment.
A buyer says their library is empty
Work through it in this order, because the cheapest checks come first.
- Has the key been redeemed on this device? An unredeemed key is a real, non-broken state
and the screen says so rather than claiming nothing is owned.
- Did the register publish its verifying key? A device that has never been handed one
cannot check a signature, and it will say that instead of claiming the record is forged —
telling a paying buyer they have been tampered with when a deploy was incomplete is the
worst available answer.
- Is the SKU registered? A purchase whose SKU the register has not adopted expands to no
editions. The order is recorded, nothing is lost, and the Bench shows it as an unregistered
offer with the remedy attached.
- Has the token lapsed? It lives thirty days and renews whenever the app is opened online.
A device that has been in a drawer renews on its first open.
An edition will not download
The reader distinguishes every way that can fail, and the sentence on screen is the diagnosis.
An interrupted download says so and leaves nothing half-recorded: the offline badge appears
only after the bytes are stored _and_ checked, so its presence is the record that the check
happened.
Something threw
Nothing of the reader's went with it. Keys, notes, reading positions and downloaded editions
live in storage the failing screen does not reach, and reloading is safe.
The rules that are not to be worked around
- Do not put publication-specific logic in the bridge. One plugin on six sites is the only
property keeping it maintainable.
- Do not make a SKU an edition id, however convenient it looks for one product.
- Do not add a password, a magic link or an email provider.
- Do not let the Bench show a figure without saying whether it is live.
- Do not overwrite a published version with different bytes. Correct forward.
- Do not weaken the offline promise for any reason.
Colophon
The Bindery interface uses Playfair Display italic for display, Manrope for interface and IBM
Plex Mono for data — the Northern Lantern House faces, served from this origin rather than
fetched from anybody. The deterministic publication PDF uses PDF built-in print faces so the
file stays self-contained and byte-reproducible without bundling font files into each edition.
Drawings, not stock photography, because the subjects here are systems. A diagram is the
honest picture.
This edition is the first thing to run through the four-output build, and it is deliberately
the operator's manual rather than something for sale: it exercises every path a real edition
takes — manifest, sections, checksum, application package, offline index, standalone HTML and
publication PDF — without anybody having to pay to find out whether the build works.
It is marked open, which means it needs no grant to read. That is a thing every publisher
does and a thing this model could not express until it existed.