All projects

Testero

Evidence-backed origination orchestration

Home equity origination scatters the facts of a case across documents, policy versions, vendor work and the people chasing all three. Testero keeps one canonical case state, traces every material fact to the page it came from, and derives readiness with deterministic rules instead of asserting it.

Role
Sole builder and technical decision-maker
Status
Deployed demo, fully synthetic case
Core stack
TypeScript · Next.js · Zustand · Zod · Vitest
Tests
40 rule and invariant tests
The Testero case view for a synthetic file: a readiness panel reading Not ready with two blockers, a decision-state column listing blockers and a policy conflict by rule ID, a ranked list of next best actions each naming the rule it unblocks, and an evidence panel flagging a contradiction in ownership vesting between the application and the recorded deed.
Readiness, the blockers behind it, what to do next, and the evidence each judgement rests on, on one screen.

Engineering brief

Architecture
A plain TypeScript domain layer with no React, Next or network imports, holding the case model, ten effective-dated rules, the readiness projection and the action planner. Next.js and Zustand render a projection of that state; Zod guards the one API boundary.
The hard part
Refusing to resolve things the evidence does not resolve. Missing facts stay UNKNOWN, disagreeing sources stay contradicted with both sides retained, and an unsettled policy stays CONFLICT, so the system never asserts a readiness nobody decided.
Testing
40 rule and invariant tests over the domain core, plus 6 metrics tests. They run without a renderer because the domain imports nothing, which is the practical payoff of keeping the layer framework-independent.
Known limitation
One synthetic case, fixtured extraction, and no persistence or authentication. The rules and transitions are real; the volume, the document pipeline and the multi-user concerns are not.
The problem

Readiness gets asserted, not derived

A home equity investment file is ready when a specific set of conditions hold. In practice nobody can say which ones, because the facts live in six places: the application, the recorded deed, a mortgage statement, a credit response, vendor orders and whatever someone typed into a note.

So readiness becomes a judgement someone makes and then defends. When the file stalls, the question is not just what is missing. It is which source was believed, which rule version applied on the day, and who decided. Correctness matters here in a specific way: a system that is confidently wrong about ownership is worse than one that says it does not know.

The model

One canonical state, projected into everything else

There is one case state. Readiness, the blocker list, the action queue and the event timeline are all projections over it rather than separate stores that have to be kept in step. Nothing writes a readiness label, so nothing can write the wrong one.

That decision is what makes the demo behave correctly under change. Verifying a trust certification does not set the file to ready; it adds an assertion, and readiness is recomputed from the rule set at version time. The screen is downstream of the rules, always.

Evidence

Every material fact knows where it came from

An assertion is not a value. It carries the text as written and a normalized value for comparison, the document, page and block it was taken from, the extractor that produced it, a confidence, a verification status and whether the fact is material or merely supporting. Block coordinates are percentages of the page box, so a highlight lands correctly at any size.

Material facts are the ones that gate decisions, and below a confidence threshold they require human verification before a rule will lean on them. Superseded assertions are retained and pointed at their replacement rather than deleted, so the history of what was believed survives.

Where two sources disagree on a material field, the case records a contradiction and keeps both sides. Testero never silently picks a winner.

In the demo case the application reports individual ownership and the recorded deed vests title in a revocable trust. That is not a data-quality nuisance to be cleaned up. It is the finding, and the rule that depends on it stays unresolved until a person names the authoritative record.

Rules

Ten rules, seven verdicts, no booleans

Each rule is effective-dated and evaluates against the case state at version time. A rule may return exactly one of seven verdicts, and the absence of a boolean is the point: missing evidence is UNKNOWN rather than a quiet failure, and an unsettled internal policy is CONFLICT rather than a guess.

FLOOD-INIT-001Flood determination on file at intake
OWN-VEST-001How title is vested, and whether sources agree
OWN-TRUST-002Trust certification when the deed vests in a trust
DOC-MTG-003Mortgage statement present and current
CREDIT-UW-004Consumer credit file released, not frozen
MTG-BAL-005Mortgage balance reconciles across sources
CRED-INQ-006Inquiry letter of explanation, under a conflicted policy
PACER-INIT-007Bankruptcy search at intake
TITLE-INIT-001Title order placed
VAL-HYBRID-002Hybrid valuation ordered

Rule identifiers are not decoration. A blocker on screen names the rule that produced it, and every next action declares the rule IDs it is expected to move off the blocking list. The queue then ranks actions by how many current blockers each one actually clears, rather than by age, so the top of the list is the thing most worth doing.

Engineering decisions

The calls that shaped it

The domain layer imports nothing

No React, no Next, no fetch. The rules, the case-state projection and the action planner are plain TypeScript, which is why 40 tests can exercise them without a renderer or a network. A domain that can only be tested through a component eventually stops being tested.

Readiness is derived, never stored

There is no `isReady` column to fall out of date. Readiness is a projection over the current assertions and the rule set at version time, so clearing a blocker re-derives the answer instead of editing it. The screen cannot disagree with the rules because it is computed from them.

Seven verdicts, because a boolean lies

A rule returns PASS, FAIL, UNKNOWN, WARNING, MANUAL_REVIEW, CONFLICT or NOT_APPLICABLE. Missing evidence is UNKNOWN, not a quiet FAIL, and a policy the firm has not settled is CONFLICT. Collapsing those into pass or fail is how a system starts asserting things nobody decided.

Verdict and treatment are separate concerns

The same FAIL can be a submission blocker or a tracked trailing dependency, depending on whether a covering vendor order is already in flight and expected before the gate date. Keeping the verdict and its queue treatment as different fields is what lets the queue stay useful without weakening the rule.

Every material fact carries its provenance

An assertion records the value as written, a normalized value for comparison, the source document, the page, the block, the extractor, a confidence and a verification status. Block geometry is stored as percentages of the page box, so a highlight resolves at any rendering size.

Contradictions keep both sides

When two sources disagree on a material field, the case holds a contradiction with both sides retained rather than resolving to whichever document loaded last. The field stays contradicted until a person names the authoritative record. That is a deliberate refusal to guess.

Actions are ranked by what they unblock

Each action declares the rule IDs it is expected to move off the blocking list, and the planner ranks by how many current blockers it actually clears. Ownership is explicit too: an action waiting on a vendor, a policy owner or the homeowner is a different queue state from one waiting on the processor.

The API cannot be talked into a case the engine did not build

The client posts a demo step and a small set of operator overrides, validated with Zod, not a case object. The server rebuilds the state from the same domain code before anything reads it, so a crafted request cannot smuggle in a case state that never passed the rule engine.

Limits

What this does not establish

One case, fully synthetic

Six intake documents and one borrower. The rules are real and the state transitions are real; the volume that would expose performance and concurrency questions is not there.

Extraction is fixtured, not learned

Assertions arrive with confidences and page coordinates already attached. A production system would need a document pipeline, and the interesting failure mode would move to extraction quality rather than rule evaluation.

No persistence and no authorization

State lives in the browser for the length of a session. Roles are modeled in the domain as owners of actions, but there is no authentication, no audit storage and no multi-user concurrency.

Policy versioning is demonstrated, not administered

Rules are effective-dated and one rule deliberately runs under a conflicted policy, but there is no interface for a policy owner to author or retire a version.

Next

What a real deployment would change

Ordered by what would hurt first. The domain core is the part designed to survive all of it.

  1. 01

    Persistence and an event log

    The case state is already a projection, so the change is to persist the assertions and decisions as an append-only log and rebuild from it. The projection code does not move.

  2. 02

    Document extraction as a real boundary

    An extraction service producing the same assertion shape, with confidence thresholds routing low-confidence material facts to human verification rather than into the rules.

  3. 03

    Authorization on the decision, not the screen

    Approving an exception or naming an authoritative record is where permission matters. Those are already discrete domain operations, which is the right place to enforce it.

  4. 04

    Policy administration and history

    Effective-dated rules need an editor, a review path and the ability to answer what the rule set said on the day a decision was made.

An independent portfolio project on fully synthetic data. No real borrower, document or lender is represented, and it is not deployed commercially.