OverPowered International

OverPowered International

Client ·Jul 2026 → ongoing ·gated

NMLS Document Repository

A law school's archive of fifteen thousand judgments, statutes and past papers, rebuilt by AI coding agents under a written governance model. It was in production twenty-three days after the first commit, with the whole archive published five days later. The old site keeps the record until the school signs a cutover, and a weekly sync keeps the two level.

[ COVER ]
Deployment: gated Work: ongoing
Timeframe
Jul 2026 → ongoing
Tags
Enterprise · django · migration · governance
Credits
The kind of build

Where it started

Norman Manley Law School's document repository holds about fifteen thousand judgments, statutes and past examination papers, the reference collection a law student works from. It ran on Drupal 7, a platform whose support ended years ago, with a taxonomy people navigate by habit and no safe way to move any of it.

The brief did not arrive as a tender. The person who ran the old system, and had meant to replace it for years, had watched the work on another project and asked. The brief they handed over became the authority for every decision that followed, cited by section.

Two conditions shaped the build. The new site had to be fully deployed by 17 August, ten days before term, on hosting that cost under forty dollars a month. And the old site would stay open, keeping the record until the school signs a cutover.

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.

[ ARCHITECTURE ]
Where a document goes, and who is allowed to be wrong about it Two panels. 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, search and machine access can show. Beside all of it, the legacy Drupal system remains the source of record until a signed cutover; the migration moves its documents into private storage. THE DATA PATH, AS THE DECISIONS THAT SHAPED ITprivate storagequarantine · scan · promote to animmutable, digest-addressed copyADR 0004extractiontext from the file; OCR onlywhere the file has no textADR 0005PostgreSQLAUTHORITATIVEthe record, its taxonomy,workflow, migration stateADR 0003 · 0010OpenSearcha projection, fed by an outbox— rebuildable from scratchADR 0003A SEARCH FAILURE CANNOT CORRUPT A RECORD,AND THE INDEX CAN BE THROWN AWAY ANDREBUILT — THAT IS THE POINT OF THE SHAPE THEN · PUBLISH, AND THE LEGACY SYSTEMpublish — a capability of its ownmetadata, file safety, storage, extractionand index policy must all pass; editing arecord never implies the right to publish itADR 0018readersearchmachine accessSTILL THE SOURCE OF RECORDthe legacy systemauthoritative until a signed cutover; rehearsalsyncs are read-only and repeatable; a missingsource record is a review item, never a deletionMIGRATION → PRIVATE STORAGEADR 0011EVERY DECISION CITES THE BRIEF. TWOWERE LATER SUPERSEDED — BY A NEWRECORD, 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.

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, and a scan with no readable text is marked as having none. Publication is its own capability, distinct from editing, and gates everything the reader and the search can show.

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.

The whole stack also runs on one machine with no cloud account, so 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, two hundred rows from the old site's public export, wrote fifty and published none: three rows in four lacked a field the new site required. The source moved to the old site's own weekly database dump, which also records when each document last changed. On 17 August the whole archive loaded, 15,156 documents and 15,147 files with no load failures, and two days later all but one were published.

[ MIGRATION ]
How fifteen thousand documents were moved Two panels: six steps in order, 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; both were built for, and the old site's weekly database dump replaced the export as the source. Then the state today: 15,167 documents loaded and published, a weekly sync keeping them level with the old site, and the cutover not yet signed. A SUCCESSFUL COPY IS NOT PROOF OF MIGRATION01Discoverread-only. nothing installed, restartedor mutated on the legacy host02Extractprovenance-backed: manifests, hashes,an errors file, the exact commit03Transformdeterministic — the same inputyields the same output, every run04Loadidempotent; every term and spellingkept, nothing silently mergedfirst wall, day one of real datathe export had no subject-heading column thetarget required; taxonomy arrived as nameswhere the loader demanded identifiers. bothwere built for, and the old site's weeklydatabase dump replaced the export as the source THEN · THE LAST TWO STEPS, AND WHERE IT IS05Reconcilecounts, identifiers, bytes and hashesagree, or the difference is explained06Cutoverauthority moves on a signed approval— not when the copy succeedsWHERE IT IS01 – 05 done15,167 documents loaded and published,every term and spelling intact; a weeklysync keeps them level with the old site06 pendingthe cutover waits on the school; thelegacy system still answers for the record

Six rules, one at a time. The first trial of the public export hit two walls at the load step on its first day, and the old site's own weekly dump replaced the export as the source. Authority moves only on a signed approval.

It runs in containers on one cloud host, live behind a sign-in since 14 August. Each week a job restores the old site's dump, loads what changed and publishes what the old site published; on 12 October both held the same 15,167 documents.

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 they had corrections: small, specific 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 it is the part of this project most worth copying: stop reading the code to decide whether it works, and measure the running system.

[ MILESTONES ]
Six weeks, gate by gate Two panels: 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 DEPLOYMENTfour things that broke, and whymilestone recordedgate passed on written evidencesomething broke — root cause recorded same dayfirst production deploymentJULY23 Julrepository created27 Jullocal platform validated — thewhole stack, no cloud account28 Julgates 1–3: domain andAPI, identity and MFA29 Juldeployment deferred bydecision; build local-firstAUGUST2 Auggate 4: experience and interaction design3 Auggates 5–7: private storage · extractionand search · migration tooling4 Auggate 8: machine access and analytics THE LOG, CONTINUEDAUGUST7 Aug110 files stranded mid-extraction— cause found, not swept9 Augdocument lifecycle proven endto end through the product11 Augtrial load of the realexport — first wall13 Augcost floor priced; runtimedecision reversed with a record14 Augfirst production deployment —three defects reading had missed17 Augextraction score measured: anunreadable scan had scored 1.019 Augcitation extraction measuredagainst 400 real judgments23 Augsearch audited against theoriginal site, both liveSEPTEMBER2 Sepkeyword review queue shipped

The build's first six weeks, from its milestone log. The marks in red found something broken, and each carried its root cause the same day. The first production deployment found three defects that reading the code had missed.

Every PDF failed to open on the live site from the day the viewer shipped, and no test caught it, because the reader fell back to the extracted text without a word. Opening a judgment on production found it on 25 August. The cause was one line of the security policy, which never allowed the viewer's own file links, and it was fixed that day.

Search was measured against the old site on the live archive. A named case returned 439 results where the old site returns 2, and the empty search screen's sort order carried silently into a reader's first query. Both were fixed, and the named case now returns 8.

[ SCREENSHOT ]
The search surface mid-query, measured against the old site with both live. Under relevance the ranking agreed with the old site's; the fault was a default sort that carried silently into a reader's first search.

The search surface mid-query, measured against the old site with both live. Under relevance the ranking agreed with the old site's; the fault was a default sort that carried silently into a reader's first search.

A guess that the two sites had drifted was settled by counting both: on 11 October the old site held 15,167 documents and the new one 15,156. The weekly job meant to keep them level had never been switched on and could not have run. It was fixed and run that night, and now publishes on its own.

The reviewing taught money too. Hosting was first priced at $88.87 a month, against a ceiling of forty. A cost study priced each step down to a floor of $23.85, every rate pulled from the pricing API and every memory figure read from the running containers, and beside its largest saving it notes that this could not be verified and must not be presented as settled. The host was sized from it, inside the ceiling.

[ COST FLOOR ]
The cost floor: $88.87 a month down to $23.85 Two panels, 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 · PER MONTH$88.87 down to $23.85ordered by dollars saved per hour of workEvery rate: AWS Pricing API, queried 2026-08-13.Every memory figure behind it: read outof the running containers, same date.Bill as stated$88.871. Delete the Cognito line — already $01h · gives up: nothing$61.512. 1-year EC2 Savings Plan, t4g.large1h · gives up: 12 months in the t4g family$43.183. Root disk 30 GB, not 802h · gives up: headroom for an unsized index$39.18Rungs 1–3 are four hours and nocode touched — 56% of the bill. THE REST OF THE LADDER4. Fix the outbox re-dispatch loop6h · gives up: nothing — a gate, saves $0$39.185. Trim the search heap; S3 over SeaweedFS13h · gives up: an object-storage abstraction$39.186. Resize to t4g.medium, re-commit4h · gives up: the slack that hides problems$23.85And the document caps its own biggest number:the largest single number in theladder, marked unverified and not tobe presented to anyone as settled.

The cost study's six rungs, from the first price of $88.87 a month to a floor of $23.85, 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 study capping its own largest number.

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, and the publish surface shows the one record that is not published.

Where it stands

Live behind a sign-in since 14 August, with the whole archive loaded and all but one of its 15,167 documents published.

[ SCREENSHOT ]
The publish gate on 9 September: 15,156 records available, none with a processing issue, and the single record that is not published shown rather than left out. Every document is managed from source file to publication.

The publish gate on 9 September: 15,156 records available, none with a processing issue, and the single record that is not published shown rather than left out. Every document is managed from source file to publication.

In September the school's Librarian reviewed it and asked for seven things, from exports and backups to examination papers sorted by course. All seven were built and accepted on the live site on 11 October, and a full backup package was built and verified.

No student has been invited yet. Inviting 1,824 waits on two decisions, where the list of who may join comes from and how many join at a time, and the site has no domain of its own to send mail from, so for now an invitation is a link given to the person directly. The old site keeps the record until a cutover is signed, the arrangement the whole build was designed around.

It was built with AI coding agents, under the governance described above, and that is what the governance is for: the brief as the only authority, every decision recorded with its reasons, and the work checked on the running system. A plan scoped in months reached production in just over three weeks.

Timelines are shortening and expectations rising. The standard can rise with them when it is written down and enforced, and the written record leaves with the work, so an institution that has to answer for its archive can show how it was moved.

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,167
Documents loaded, all but one published, kept level with the old site each week
23 days
From the first commit to production
7 of 7
The school's review asks, built and accepted on the live site
26
Decisions recorded with their reasons; a changed decision gets a new record

Next — Vanrox Group →

Sitting on an archive nobody can safely move?