Client ·Jul 2026 → ongoing ·gated
NMLS Document Repository
A law school's document archive, replaced in six weeks by AI-driven development under a governance model strict enough to make that safe — with the old system kept in charge until the new one has earned it.
- Timeframe
- Jul 2026 → ongoing
- Tags
- Enterprise · django · migration · governance
- Credits
- Xern Mottley — Engineer
Where it started
Norman Manley Law School's document repository — fifteen thousand case files, judgments and pieces of legislation, the reference collection a law student works from — ran on Drupal 7, a platform whose support ended years ago. A taxonomy people navigate by habit, and no safe way to move any of it.
The brief did not arrive as a tender. Rohan Smith, who managed the legacy system and had meant to replace it for years, had watched the work on another project and asked. The brief he handed over became the authoritative source for every decision that followed, cited by section.
What was done
A replacement, and a way to prove at every step that it was safe to switch to.
The shape is a set of decisions, each recorded with its reasons. Records live in PostgreSQL and nothing else owns them. The search index is a projection of those records, fed through an outbox and rebuildable from scratch — so a search failure cannot corrupt a record, and the index can be thrown away and remade. A document enters private storage, is quarantined and scanned, and only then promoted to an immutable copy. Its text is extracted, with optical character recognition only where a file has none. Publication is its own capability, distinct from editing, and gates everything the reader and the search can show.
The data path, drawn as the decisions that shaped it. PostgreSQL alone holds authority; the search index is a rebuildable projection, so a search failure cannot corrupt a record. Publication is a capability of its own. The legacy system stays the source of record until a signed cutover.
Every source term, spelling and variant was carried across without silent merging; canonical names come only through a reviewed editorial process. That is why a 1970 judgment arrives with its court, its parties and its pagination intact, beside the scan it came from.
A 1970 Court of Appeal judgment — a public record — with its court, parties and pagination intact beside the original scan. The panel states that the text came from optical character recognition and may misread words, and says to check quotations against the original before citing.
Every document passes the same gate before anyone can read it. The administration surface counts what is available, what has a processing issue, and what is not published — and shows the one record that is not.
The publish gate: 15,156 records available, none with a processing issue, and the single record that is not published shown rather than omitted. Every document is managed from source file to publication.
The whole stack runs on one machine with no cloud account. That is a contract, not a convenience: a reviewer can run the thing they are reviewing.
Moving the archive was treated as six rules rather than one task: discover read-only, extract with provenance, transform deterministically, load idempotently, reconcile counts and hashes, and move authority only on a signed cutover. The first trial load of the real export hit two walls on its first day — the export had no subject-heading column the target required, and taxonomy arrived as names where the loader demanded identifiers. Both were recorded as the next things to build. Neither was worked around.
Six rules, not one task. The load step is where the real export hit its first walls, on day one; both became the next things to build. Authority moves on a signed approval, not when the copy succeeds.
The arrangement that shapes all of it: the legacy system keeps authority over the archive until a cutover is approved. The new system is deployed and live behind a sign-in wall, waiting on the school's own QA gates. Not one student has been invited yet, and that is deliberate — the invitation is a decision the school makes, not a switch the build flips.
The decision that mattered
The hard part was being reviewed — and what that turned into.
This was the first time working under someone more senior, and he had corrections. Small, specific, unglamorous ones, from the shape of the reader's toolbar to what the search box did on the third character of a case name, returned until they were right. The habit that came out of six weeks of that is the one thing in this project most worth copying: stop reading the code to decide whether it works, and measure it.
Three examples, each with its cause written down the same day.
A hundred and ten files were stuck mid-extraction, invisible to every operator signal. The handoff blamed a missing dispatch path. Refuting that claim found the opposite: a setting meant to protect against a worker dying mid-job was consuming the one redelivery a dead worker earns, and reporting success. It did not cover the failure — it created it. The fix was to stop destroying the delivery, not to add a second path. All hundred and ten recovered, verified on the live pipeline.
Two changes to extraction scoring passed every test and were refuted, because the tests had stubbed the extractor with response shapes the real one never produces. Measured against the running extractor, a wholly unreadable three-page scan had been scoring a perfect 1.0. A unit test cannot tell a plausible number from a measured one.
The search was audited against the original site — both systems live, the same twenty-four cases. The ranking was correct under relevance and agreed with the original's verdict. But the empty search screen had chosen newest as its order, and that choice silently persisted into a user's first real query. The ranking was never broken. The default was.
Six weeks, gate by gate. The marks in red are the milestones that found something broken — each carried its root cause the same day. The first production deployment found three defects that reading the code had missed.
The search surface mid-query. Audited against the original site with both systems live: the ranking agreed with the original's under relevance. The defect was a default sort that persisted silently into a user's first search — never the ranking itself.
The discipline reached the decisions too. An early record had turned the brief's prefer a managed container service into do not use a single host and stamped it a baseline. Rather than deploy against a standing prohibition quietly, a new record went back to the brief, found the alternative named exactly once in the whole brief and only as a preference, and superseded the old decision — leaving its reasoning intact, under the project's own rule that a changed decision gets a new record and never a rewrite.
What the reviewing taught, in the end, was mostly about money. Balancing containers and libraries against what they cost to run is a different discipline from making them work, and it produced a document that prices the whole design down to its floor.
Six rungs from a stated $88.87 a month to a $23.85 floor, ordered by dollars saved per hour of work. The first three need four hours and no code change. The note at the foot is the document capping its own largest number.
Eighty-nine dollars a month to twenty-four, every rate pulled from the pricing API and every memory figure read out of the running containers. And it caps its own best number: against the largest saving sits a note that it could not be verified and must not be presented as settled. The product carries the same habit — the reader warns that its text came from OCR and may misread words; an undated judgment reads year not recorded; the publish surface shows the one record that is not published. An unverified number is worth less than a smaller one you can stand behind.
Where it stands
Deployed to a single cloud host with TLS, live behind a sign-in wall, and waiting on the school's QA gates.
The archive is migrated and published: 15,156 documents, every one but a single record searchable, nothing flagged for attention. The people are not migrated — inviting them is the school's decision, and it stays open until the school makes it. The legacy system still answers for the record until a cutover is signed, which is the arrangement the whole build was designed around.
This was built with AI coding agents, under the governance described above — and that is what the governance is for. A plan scoped in months landed in six weeks, not by cutting corners but by making corners impossible to cut: the brief as the only authority, every decision recorded with its reasons, every milestone proven in writing before a gate opened, and separate lanes whose job was to refute what the others claimed. Eight phases were accepted on written evidence. Twenty-five decisions carry their reasons, two of them superseded by later records rather than rewritten.
Timelines compress. Expectations rise. So does the standard, if the standard is written down and enforced — and that record leaves with the work, so an institution that has to answer for its archive can show how it moved. This is the direction the work is going, and this project is what it looks like when it goes well.
Everything shown on this page is public. The documents are published court judgments and legislation — the public records of a public institution — and nothing here identifies a student, an account, or a host.
- 15,156
- Documents migrated, published and searchable
- 25
- Decisions recorded with their reasons — two superseded by a later record rather than rewritten
- 8
- Phases accepted on written evidence, in six weeks
- $88.87 → $23.85
- Monthly running cost, priced from the API rather than estimated
Next — Vanrox Group →