Acceptance infrastructure
Why the first checker is easy
The hard part starts when the decision has to survive changing rules, missing evidence, corrections, money, and other systems depending on it.
The honest version of almost every build decision is this: we wrote an if (passed) and a results table in an afternoon. True. That is also where it stops being easy.
Each of the hard parts arrives as an ordinary requirement, usually after the result already matters to an invoice, another service, or someone who didn't write the checker.
Here is how the afternoon project grows.
Preserve what counts
Version one puts the rule next to the call site:
const passed = result.records.length === 100Then somebody changes the requirement. Each record must have a company name, a valid domain, and a source URL. The next delivery uses the new rule. The previous delivery used the old one.
If the results table stores only passed = true, you no longer know what passed meant. Re-running old data under the new rule rewrites history.
So the checker needs the criteria and the version of the agreement that governed the delivery, fixed before the result is judged. Otherwise a buyer can move the bar after seeing the work.
The question that makes this layer grow is: which definition of done produced this result?
Gather the evidence
The returned object is often only one piece of evidence. A data delivery might need the rows, a schema report, and the source URLs.
Now the checker has to record which exact material the judge saw and where it came from. Otherwise two systems can argue about "the result" while holding different inputs. A digest doesn't prove the evidence is true. It proves what the decision was made on.
The question: can we identify the exact evidence behind the answer?
Integrate the judge
The first judge is whatever was convenient: a test suite, a model call, an external API, or a person clicking a button. Each returns a different shape. A model can time out. A person can decline to decide.
The adapter starts small. Then it has to record the judge version, separate a judgment from a transport error, and normalize everything into one stable contract.
None of this requires Spoolis. It requires ownership. The judge stays yours; the result shape has to stay stable.
The question: what stays stable when the judge changes or fails?
Passed, failed, partial, or uncertain
Booleans force a branch, and they lie when the system has no answer. Suppose the domain-reachability service times out for 12 of 100 records. Rejecting them says the provider failed. Accepting them says the criterion passed. Neither happened.
That third state is expensive because every reader has to respect it. Billing must not charge it as accepted. You can adopt a policy that treats uncertainty as failure for one action, but the record should still say the judge was uncertain. Policy can collapse a state for a branch. History shouldn't erase it.
The question: what do we record when no valid judgment was produced?
Compute earned value for partial work
A single deliverable can be all or nothing. A batch usually isn't.
Take 100 records at $1 per accepted record. Ninety-six satisfy the criteria, two fail, and two are uncertain. The accepted count is 96 and the earned amount is $96.
The arithmetic is simple. The invariants aren't:
- counts reconcile with delivered units
- price comes from the governing agreement
- earned value stays within the committed amount
- reruns never earn the same unit twice
- corrections name the units they replace
Deterministic code computes these from the accepted units and the agreed rule. Models don't author them.
The question: how do partial results become one defensible earned amount?
When yesterday's accepted result changes
This is where the afternoon project stops being a checker.
The batch of 100 is accepted at 96. The invoice goes out. The agent that ordered the records continues to its next step and spends more money on the strength of those 96. Two days later a re-check shows that eight of the accepted domains were parked pages. The judgment changes.
Now what? The invoice exists. The downstream work happened. A results table that flips 96 to 88 rewrites history under a decision other systems relied on. A table that keeps 96 is wrong.
The honest shape is a correction: a new result that supersedes specific units, points back at the one it replaces, and leaves the original readable. Then every consumer that acted on the first result can find out that it changed and decide what to do under its own policy. Billing may issue a credit. The agent may re-run one step.
Retries make the same demand from the other direction. A worker times out and resubmits the whole request. Retrying is the runtime's decision, not the acceptance system's. The acceptance system has to tell apart the same request delivered twice, a fresh attempt, a correction to specific units, and a judge rerun over unchanged evidence. Otherwise duplicate acceptance becomes duplicate billing.
The question: is this new work, another observation, or a change to a result somebody already acted on?
Keep the history
The first results table answers the application's current query. History has to answer a later dispute.
Someone will ask why record 47 didn't count. The useful answer includes the agreement version, the criterion, the evidence reference, the judge's result, and any correction that followed. "The row says false" isn't enough once an invoice depended on it.
An append-only, signed record makes the decision reconstructable and portable, and its value rises sharply the moment another party relies on the result. History should stay narrow: it proves which evidence and decision were bound together, not that a source told the truth or a human judge was right.
The question: could a later reader reconstruct what happened without asking the original engineer?
Give downstream systems one result
At first the checker's own application is the only reader. Then billing wants the accepted count and an agent wants to know whether it may continue.
If each reads raw evidence and judge output, each rebuilds acceptance, and they will disagree about whether a timeout meant rejection or a corrected unit counted.
A durable Outcome gives them one object: the agreement, condition results, evidence digest, acceptance state, unit counts, earned amount, and any correction. It doesn't tell any of them what to do. Your runtime owns continue, retry, hold, or stop. Your billing system owns the invoice. Your payment rail owns settlement. Accepted state is the shared fact. Execution stays with the systems that own it.
The question: how many systems are allowed to reinterpret what counted?
When the boolean is enough
Often. If one system does the work, judges it, and acts on the result, a boolean may be enough.
Keep the local checker when:
- one judge you control produces the answer, and the result stays in one application
- work is indivisible, so partial acceptance doesn't exist
- retries have no economic consequence
- no money, access, deployment, or chained purchase depends on the result
An internal summarization tool that checks its output is nonempty needs none of this. Even with money involved, one cheap API response is what you bought, and ordinary request handling is all you need.
Don't turn a local invariant into a platform because the architecture diagram looks tidier.
When it becomes infrastructure
The boolean stops being enough when the result has to outlive the moment it was judged: the roles split, the result can change later, or another system has to rely on it. The transition arrives as questions:
- The first dispute: "What rule was in force when this failed?"
- The first partial delivery: "Why are we paying for all 100 when 96 counted?"
- The first uncertain result: "Did the provider fail, or did our judge time out?"
- The first correction: "We already invoiced. Does this replace the failed unit or create another one?"
- The first outside consumer: "Can billing read this without copying our checker logic?"
One question isn't a reason to buy infrastructure. The signal is that acceptance has become a shared boundary with its own invariants, history, and consumers. That is when acceptance infrastructure is a useful name. It lets a team decide deliberately whether to own it.
Why a team might externalize it
Not because the first checker is beyond it. Because the ownership persists after the build: judge adapters, schema versions, state transitions, economic invariants, duplicate protection, corrections, history, signing, consumer compatibility, and all of it again when the second judge or the fifth consumer arrives.
Domain owners keep the criteria, the evidence, and the judge. The infrastructure owner keeps the state machine and the portable result.
Spoolis is one way to draw that line. The accept() purchased-work guide shows the shape: send criteria and evidence, receive an Outcome, and let your runtime decide what happens next. Spoolis owns accepted state. It doesn't become the orchestrator.
You can build it. The question is whether you want to keep owning it.
The checker answers "did it pass?"
The infrastructure answers "what counted, under which rules, based on what evidence, what changed later, and what can safely happen next?"
Go deeper
- What is acceptance infrastructure? defines the category and its boundaries.
- The agent said done explains why a completion claim is not yet an accepted result.
- Routing disposition vs. acceptance state separates the result from the caller's next action.
- Accept purchased work shows the SDK boundary in code.