OverPowered International

OverPowered International

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.

[ COVER ]
Deployment: gated Work: ongoing
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.

[ ARCHITECTURE ]
Where a document goes, and who is allowed to be wrong about it A pipeline drawn as decisions. A file enters private storage, is quarantined and scanned, then promoted to an immutable copy. Its text is extracted. The record lives in PostgreSQL, which alone holds authority. The search index is a rebuildable projection fed through an outbox, so a search failure cannot corrupt a record. Publication is its own capability, distinct from editing, and gates what the reader and search can show. Beside all of it, the legacy Drupal system remains the source of record until a signed cutover. THE DATA PATH, AS THE DECISIONS THAT SHAPED IT private storage quarantine · scan · promote to an immutable, digest-addressed copy ADR 0004 extraction text from the file; OCR only where the file has no text ADR 0005 PostgreSQL the record, its taxonomy, workflow, migration state ADR 0003 · 0010 AUTHORITATIVE OpenSearch a projection, fed by an outbox — rebuildable from scratch ADR 0003 publish — a capability of its own metadata, file safety, storage, extraction and index policy must all pass; editing a record never implies the right to publish it ADR 0018 reader search machine access STILL THE SOURCE OF RECORD the legacy system authoritative until a signed cutover; rehearsal syncs are read-only and repeatable; a missing source record is a review item, never a deletion ADR 0011 MIGRATION A SEARCH FAILURE CANNOT CORRUPT A RECORD, AND THE INDEX CAN BE THROWN AWAY AND REBUILT — THAT IS THE POINT OF THE SHAPE EVERY DECISION CITES THE BRIEF. TWO WERE LATER SUPERSEDED — BY A NEW RECORD, NEVER BY REWRITING THE OLD ONE

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.

[ SCREENSHOT ]
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.

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.

[ SCREENSHOT ]
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 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.

[ MIGRATION ]
How fifteen thousand documents were moved Six steps in a row: discover, extract, transform, load, reconcile, cutover. Each is a rule, not a task. The load step is marked: a trial load of the real export hit two walls on its first day, and both were recorded as the next items to build rather than worked around. Below, the state today: the corpus is loaded and published; the cutover has not been signed. A SUCCESSFUL COPY IS NOT PROOF OF MIGRATION 01 Discover read-only. nothing installed, restarted or mutated on the legacy host 02 Extract provenance-backed: manifests, hashes, an errors file, the exact commit 03 Transform deterministic — the same input yields the same output, every run 04 Load idempotent; every term and spelling kept, nothing silently merged 05 Reconcile counts, identifiers, bytes and hashes agree, or the difference is explained 06 Cutover authority moves on a signed approval — not when the copy succeeds first wall, day one of real data the export had no subject-heading column the target required; taxonomy arrived as names where the loader demanded identifiers. both became the next items to build, not workarounds WHERE IT IS 01 – 05 done 15,156 documents loaded, published and searchable, every term and spelling intact 06 pending the cutover waits on the school; the legacy system still answers for the record

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.

[ MILESTONES ]
Six weeks, gate by gate A dated log from repository creation on 23 July 2026 to 2 September, one row per milestone. Eight phase gates passed on written evidence, a first production deployment on 14 August, and four milestones that found something broken: stranded extraction jobs, a trial load of real data that hit a wall, an extraction score that graded an unreadable scan perfect, and a search audit against the original site. Each break carried its root cause the same day. SIX WEEKS · EIGHT GATES · ONE DEPLOYMENT · FOUR THINGS THAT BROKE, AND WHY 07 23 repository created 07 27 local platform validated — the whole stack, no cloud account 07 28 gates 1–3: domain and API, identity and MFA 07 29 deployment deferred by decision; build local-first AUGUST 08 02 gate 4: experience and interaction design 08 03 gates 5–7: private storage · extraction and search · migration tooling 08 04 gate 8: machine access and analytics 08 07 110 files stranded mid-extraction — cause found, not swept 08 09 document lifecycle proven end to end through the product 08 11 trial load of the real export — first wall 08 13 cost floor priced; runtime decision reversed with a record 08 14 first production deployment — three defects reading had missed 08 17 extraction score measured: an unreadable scan had scored 1.0 08 19 citation extraction measured against 400 real judgments 08 23 search audited against the original site, both live SEPTEMBER 09 02 keyword review queue shipped milestone recorded gate passed on written evidence something broke — root cause recorded same day first production deployment

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.

[ SCREENSHOT ]
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 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.

[ COST FLOOR ]
The cost floor: $88.87 a month down to $23.85 Seven bars, each a rung of a cost ladder ordered by dollars saved per hour of work, from a stated $88.87 per month to a floor of $23.85. The first three rungs need four hours and no code change and reach $39.18. Every rate came from the AWS Pricing API and every memory figure was read out of the running containers. THE COST FLOOR · ORDERED BY DOLLARS SAVED PER HOUR OF WORK Every rate: AWS Pricing API, queried 2026-08-13. Every memory figure behind it: read out of the running containers, same date. Bill as stated $88.87 1. Delete the Cognito line — already $0 1h · gives up: nothing $61.51 2. 1-year EC2 Savings Plan, t4g.large 1h · gives up: 12 months in the t4g family $43.18 3. Root disk 30 GB, not 80 2h · gives up: headroom for an unsized index $39.18 4. Fix the outbox re-dispatch loop 6h · gives up: nothing — a gate, saves $0 $39.18 5. Trim the search heap; S3 over SeaweedFS 13h · gives up: an object-storage abstraction $39.18 6. Resize to t4g.medium, re-commit 4h · gives up: the slack that hides problems $23.85 Rungs 1–3 are four hours and no code touched — 56% of the bill. And the document caps its own biggest number: “This rung is the largest single number in the ladder and I could not verify it. Do not present it to anyone as settled.”

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 →

Sitting on an archive nobody can safely move?