Reservation Confirmed or Work Accepted: Two .NET Architectures
A real HTTP and SQLite fixture shows why a persisted request and a confirmed reservation are different promises. Admission, worker processing and terminal outcomes are verified separately.
Explore the mechanism โ
Reading tools Listen or translate
Read aloud
Uses an installed voice on your device.
Translate article
Machine translation. Code and evidence notes stay in English.
Original English text.
START WITH THE MECHANISM
Interactive companion and laboratory update: 12 November 2026. The editorial date places the topic on the timeline; this current explanation and its measured evidence retain their actual preparation and execution dates.
THE FULL INVESTIGATION
Reservation Confirmed or Work Accepted: Two .NET Architectures
Dates and technology context
This is a retrospective editorial timeline. Topic context: 8 January 2026. First publicly available: 12 November 2026. Current text, visuals and laboratory revisions were prepared on 12 November 2026.
The real ASP.NET/SQLite two-caller one-stock fixture, 13 checks, HTTP responses and admission/drain timings are 2026-11-12 additions, not evidence of a past production reservation service.
Two APIs can both return a successful HTTP response while making different promises. One has reserved the resource. The other has accepted responsibility for trying to reserve it later. If the interface calls both outcomes success without preserving the distinction, the user inherits an architectural ambiguity.
This is not an argument that every request should wait for final completion. Deferred work can be a useful design. It is an argument that the response must mean what the system has actually established. A shorter response time is not a free improvement if it quietly changes a confirmed outcome into an accepted request.
The executable companion compares those two boundaries with real ASP.NET Core requests and a SQLite-backed store. The resource is deliberately small: one unit of synthetic stock. Two callers ask for it. The immediate path confirms one request and rejects the other before responding. The deferred path accepts both requests, then confirms one and rejects the other when the worker is explicitly run.
The laboratory was prepared and executed on 12 November 2026. It does not contact a customer service, a cloud queue or a payment provider. Its purpose is to make the difference between admission and completion visible in code, state, HTTP responses and measured intervals.
Decide which promise the caller needs
01 / A concrete example
Successful admission and confirmed stock differ
The response can be successful while the resource is not yet reserved.
Interpretation and evidence
- Do not render202as confirmation.
- Expose the eventual result.
- Choose the promise before the endpoint.
An immediate reservation contract says that the resource is assigned when the response is accepted as confirmed. A deferred contract says that a request has been recorded and will be considered by another stage. The latter can still produce a valid rejection after the initial response.
Those promises support different user experiences. A customer who must make a connected decision may need confirmation before proceeding. Another workflow may be able to wait, inspect a status endpoint or receive a later notification. The architecture should follow that need rather than assume that every shorter request is automatically better.
The semantics of HTTP 202are consistent with this distinction: acceptance does not establish that the requested processing has completed. The surrounding representation needs to make the operation's current state understandable. HTTP semantics.
An asynchronous request-reply design also needs a way to discover the eventual result. Microsoft's architecture guidance describes validation before admission and a status resource for later observation. The fixture in this article uses a known status route to keep the example small; it is not a complete production implementation of every discovery, retry or notification detail. Asynchronous request-reply pattern.
The important agreement is the meaning of each state. Accepted must not be rendered as reserved. Rejected after admission must not disappear from a success metric merely because the first HTTP exchange returned a 2xx code.
Build the smallest resource that can expose the difference
The database contains one stock row with an initial remaining quantity of one. It also contains request rows with an identity, quantity and state. The fixture creates separate databases for the immediate and deferred paths so that they begin with the same declared resource availability.
A request has a nonempty identity and a positive quantity. Its identity is also the idempotency boundary for this example. Repeating the same identity and quantity returns the stored outcome. Reusing the identity with a different quantity is rejected as a conflict.
These are trusted synthetic requests on a loopback test server. The application does not implement authentication, multi-tenant inventory or a public ordering system. The deliberately narrow input makes the state transition easy to inspect without suggesting that a few routes are a complete commercial reservation service.
The stock change is conditional inside a transaction:
UPDATE Stock
SET Remaining = Remaining - $quantity
WHERE Id = 1
AND Remaining >= $quantity;
One affected row means the quantity was reserved. Zero affected rows means it was not available under this operation. The request outcome is recorded in the same local transaction. A successful code path therefore does not rely on reading a quantity and later writing an unguarded decremented value.
The SQL is an application-owned fixture, not a general inventory algorithm. Multiple products, expirations, partial fulfillment and external side effects would introduce additional invariants. The example uses one resource to make the immediate/deferred boundary unmistakable.
Immediate confirmation establishes a terminal result
The immediate endpoint invokes the reservation transition before responding. For request s1 with quantity one, the stock is reduced to zero and the stored outcome becomes Confirmed. The HTTP response uses 201 with that state. A second distinct request s2 cannot reserve the same unit and receives 409 with Rejected.
The correctness run repeats s1 with the same quantity. It receives the previously stored confirmation and does not consume another unit. It then reuses s1 with quantity two and requires a conflict. These are different cases: an identical retry is not a new order, while changed content under an old identity is not an ordinary duplicate.
The chosen response code for the identical retry is the same as the original stored result in this fixture. A production API may choose another documented response convention, but it should preserve the same business outcome and avoid a second effect. The code is not offered as a universal rule for every idempotent HTTP interface.
The endpoint's confirmation is meaningful because the local state transition has already happened. It still does not prove a wider business process completed. If fulfilling the reservation requires another system, that system's effect and failure boundary would need its own contract.
This is the first reason to name the boundary precisely. Confirmed in the laboratory means the synthetic stock row was successfully updated and the request outcome recorded. It does not mean a parcel shipped, a payment settled or a human accepted a work order.
Deferred admission records work before deciding the outcome
02 / Watch the mechanism
One unit, two accepted requests
Static view. Play to see how the mechanism unfolds.
Both admissions succeeded. The worker later confirmed only one request, exactly as the fixture contract allows.
Interpretation and evidence
- Admission is not allocation.
- Check terminal outcomes.
- Keep an explicitworkerboundary.
The deferred endpoint writes a request in Accepted state without decrementing stock. Both a1 and a2 therefore receive 202 while the original stock quantity remains one. The database has taken responsibility for two pending requests, not promised two reservations.
The worker is then triggered explicitly by the test harness. It reads pending requests in the fixture's declared insertion order, applies the same reservation transition and records terminal outcomes. The first request becomes Confirmed. The second becomes Rejected because the single unit is already assigned.
This is an actual separation between HTTP admission and later processing. It is not an automatic background scheduler or a message broker. The harness controls when the worker drains the pending set so that the intermediate Accepted state can be checked deterministically.
That choice makes one important behavior visible: the initial response can be correct even when the eventual outcome is rejection. A client that treats 202 as final success would violate the application's stated contract, regardless of how quickly the endpoint responded.
The worker processes the selected pending set in one transaction in this educational implementation. That is a bounded convenience, not a recommendation to process an arbitrarily large queue under one database lock. A production worker would need a batching, fairness and recovery policy appropriate to its volume and side effects.
The recorded HTTP trace shows two different meanings of success
The accepted functional run uses a real ephemeral Kestrel endpoint and HttpClient. It does not call the controller methods directly in place of an HTTP exchange. The selected response bodies and status codes are checked independently.
| Path | Request | Initial response | Resource state at that point |
|---|---|---|---|
| Immediate | s1 | 201, Confirmed | One unit consumed |
| Immediate | s2 | 409, Rejected | No unit available |
| Immediate retry | s1 again | 201, Confirmed | No second consumption |
| Deferred | a1 | 202, Accepted | Stock still unchanged |
| Deferred | a2 | 202, Accepted | Stock still unchanged |
After the explicit worker drain, a1 is Confirmed and a2 is Rejected. The status endpoint returns the recorded rejection for a2. The database's remaining stock is zero. Running the worker again finds no pending work and does not consume more stock.
The test also rejects a nonpositive quantity and a changed request identity payload. In total, the accepted run contains 13 explicit checks. Those checks cover the demonstrated path, not every possible malformed HTTP body, absent status resource or concurrent request interleaving.
The databases are file-backed. Before processing the pending requests, the harness creates a new store instance on the same file and verifies that the Accepted row remains. This is a reopen check, not a process crash or power-loss test. The terminology matters because a stronger durability claim requires a stronger experiment.
The server is bound only to loopback, and the harness stops and disposes it in a finally block. Database connections use the fixture's owned paths, and the temporary files are removed afterward. No user database or public service is involved.
Idempotency protects both admission and completion
03 / A concrete example
Identity protects the operation from duplication
The fixture checks immutable quantity before reusing a stored result. It does not implement distributedexactly-once delivery.
Interpretation and evidence
- Do not change retry content.
- Scope identity deliberately.
- Local transaction is not remoteatomicity.
An uncertain client reply can lead to a repeated request. The system needs to know whether that repeat represents the same intended work. In this fixture, identity and quantity define that relationship. Repeating the same request returns its stored state instead of creating another pending row or applying another stock decrement.
That local behavior is useful in both architectures. On the immediate path, it protects the resource from repeated consumption. On the deferred path, it protects the queue from admitting the same work again. The protection belongs at the application boundary, not only in a client's retry policy.
The fixture rejects identity reuse with changed content because otherwise the stored outcome would refer to a different request. A production system might include more fields in that immutable request contract, such as resource identity, actor scope or policy version. The same principle applies: a retry cannot silently change the action whose result it is asking about.
The example does not implement distributed exactly-once processing. Its stock and request state live in one SQLite transaction. If a worker also called an external service, a local commit could not automatically make that remote effect atomic. The system would need an idempotency or reconciliation contract with the receiver.
The related durable-agent and authority laboratories explore those uncertainties with their own explicit boundaries. Their results should not be borrowed as proof that this simple reservation service already contains the same recovery mechanism. Each artifact needs to say which part of the larger design it actually implements.
Measure admission and drain separately
04 / A concrete example
The two clocks measure different work
The observed times are for different persistence phases, not a benchmark proving onearchitecture faster.
Interpretation and evidence
- Keep all outcomes.
- Do not subtractunlikeintervals.
- User completion includes waiting.
The local measurement creates five fresh stores. Each has 50 units of stock and accepts 100 distinct deferred requests. It records the time for those 100 durable admissions, then separately records the time for one worker drain. Every sample must finish with 50 confirmed and 50 rejected outcomes and zero stock remaining.
The measured path invokes the store directly. It does not time HTTP, client polling, network delay or the scheduling interval before the worker is triggered. Those choices keep the observation focused on the two local persistence phases.
| Sample | Accepted | Confirmed / rejected | Admission interval | Worker drain |
|---|---|---|---|---|
| 1 | 100 | 50 / 50 | 133.9607 ms | 6.8154 ms |
| 2 | 100 | 50 / 50 | 125.8803 ms | 3.2679 ms |
| 3 | 100 | 50 / 50 | 133.4330 ms | 2.9831 ms |
| 4 | 100 | 50 / 50 | 126.7314 ms | 3.0588 ms |
| 5 | 100 | 50 / 50 | 128.7751 ms | 3.0586 ms |
The admission phase performs one transaction per request, while the worker drain processes its selected set in one transaction. The durations therefore cannot be subtracted to estimate the benefit of asynchronous architecture. They represent different work and different transaction boundaries.
The result also does not say that a user receives confirmation in the drain time shown. The worker begins when the harness invokes it. A real scheduler, backlog or paused worker could add waiting before that phase. The user-visible completion boundary includes that wait, while the local drain measurement does not.
These are five observations on a shared developer host, using .NET 10.0.15 and SDK 10.0.400. They are useful for reproducing the fixture's behavior and inspecting the work done by each phase. They do not establish a production throughput ceiling, a latency percentile or a service-level objective.
The most important outcome is the distribution of terminal states. Every sample accepted 100 requests but confirmed only 50 reservations. If the report called all 100 initial responses successful reservations, it would be wrong even if every duration were measured perfectly.
The interface must carry the architecture's uncertainty
A deferred client needs a state that can remain pending without pretending to be complete. It also needs a way to observe rejection after admission. A message saying that work was received should not be visually indistinguishable from a confirmation that the resource is assigned.
A production API should make the status resource discoverable and define its lifecycle. This fixture's client knows /requests/{id} directly; it does not implement a full Location-header, retention or notification policy. Those are visible next steps rather than implied features.
Cancellation introduces another question. Cancelling the client's wait does not necessarily remove the persisted request or undo a reservation. The current lab does not implement business cancellation, so it makes no claim about releasing stock after a disconnected client. A complete service needs a policy for which states permit cancellation and what happens when processing has already begun.
The same distinction applies to retries. A transport retry asks for the result of the same request. A new business attempt may be a different operation. A user interface that generates a new identity for every refresh can accidentally turn uncertainty into duplicate work even when the server's idempotency mechanism is correct.
This is why architecture and interface design belong in the same conversation. The server's state machine defines what the client can honestly promise. A polished interface cannot repair a contract that fails to distinguish admission, confirmation and rejection.
Deferred work needs an operational owner
05 / A concrete example
A stored request needs an operational owner
The laboratory separates state persistence from scheduling rather than hiding a simpledrain behind a productionqueue label.
Interpretation and evidence
- Name what starts the worker.
- Define unfinished-workhandling.
- Add recovery tests before strongerpromises.
A database row in Accepted state is evidence of recorded work, but it does not process itself. The system needs an owner for scheduling, retries, stale work, operational visibility and shutdown. The educational worker here is invoked explicitly and has no independent service lifecycle.
That limitation is intentional. It keeps the example focused on the promise at the HTTP boundary. A production implementation might use a durable queue, a hosted worker or another scheduling mechanism, but each choice creates additional acceptance criteria. Persisting a request is not proof that those criteria have been met.
For example, the worker's insertion-order policy is simple and deterministic in the fixture. It is not a fairness guarantee across tenants or request classes. A large pending set could hold the worker transaction for too long. A request could remain unresolved if the worker never starts. Those are operational design questions, not bugs that an HTTP 202response somehow fixes.
A useful operational view would distinguish admitted requests, currently processing work, confirmed outcomes, terminal rejections and unresolved cases. Counts should use those states consistently. An attempt metric should not be substituted for an operation outcome merely because it is easier to collect.
The laboratory leaves those wider controls unimplemented and names them. That is more useful than hiding an in-memory loop behind a production-sounding queue label. The reader can see exactly which mechanism is ready to reproduce and which responsibility remains to be designed.
Choose between promises, not response times alone
The immediate approach may be appropriate when the caller needs a terminal reservation and the operation can complete within an acceptable request boundary. The deferred approach may be appropriate when work needs another lifecycle and the user can handle later confirmation or rejection.
Neither is universally more modern or scalable. Each changes the agreement between the system and its caller. A valid choice depends on the product's required commitment, the failure model and the operational ability to carry unfinished work.
The code makes that tradeoff concrete. Both approaches use the same reservation rule. They differ in when that rule is applied relative to the initial response. That is the architectural decision the experiment exposes.
Download the complete reservation laboratory and raw results. The project includes the HTTP fixture, persistent request state, negative cases and the separate admission/drain measurements. Run correctness mode before measurement mode and use a new owned fixture directory for each invocation.
The lesson is not to avoid asynchronous processing. It is to make its promise explicit. Accepted work is a real state with real responsibilities. Confirmed reservation is another state. A system becomes easier to trust when its code, API, metrics and interface preserve the difference all the way to the user's decision.
A transaction boundary is not a scheduling policy
The worker's transaction gives the selected drain a clear local outcome: the pending rows and stock changes are committed together. It does not decide when the worker should run, which caller deserves priority or how much work should be admitted before the system becomes overloaded. Those policies remain outside the database transaction.
In this fixture, requests are processed in insertion order. That order is useful for the deterministic expectation that a1 receives the single available unit before a2. A production service might need a priority, fairness or expiration rule instead. Changing the ORDER BY clause without reviewing the contract could change which user receives a resource while leaving all row-count assertions green.
The current test also sends its HTTP requests sequentially. The conditional stock update is a useful mechanism, but the article does not claim that this run exercised arbitrary concurrent callers or multiple independent workers. A broader service needs tests for the interleavings it permits and for the database isolation behavior it relies on.
A future worker could take a small batch, reserve each request separately, or use a claim/lease protocol. Each option changes the failure and recovery boundary. If the worker stops after a partial batch, which requests are still pending? Can another worker distinguish unfinished work from work whose result was committed but not acknowledged? The current one-transaction drain avoids some of those states in a tiny fixture; it does not make them disappear from a different architecture.
This is why the source is presented as a reference laboratory. It makes the promise boundary executable without hiding the responsibilities of a full queue service. An engineer can use it to discuss admission and confirmation, then add the scheduling and recovery contract their application actually needs.
Check the identity before treating a repeated request as harmless
The idempotency behavior depends on comparing the existing request with the new one. A repeated identifier with a different quantity is rejected. Returning the old result without checking the quantity would make the endpoint appear stable while answering a different request from the one the caller submitted.
The input model in the laboratory has only two fields. A real reservation might include a resource identifier, actor, tenant, price agreement or policy revision. Every field that changes the meaning of the action must participate in the identity contract or be resolved from trusted state. The example does not claim that quantity alone is sufficient for a general commerce API.
The stored outcome also has a lifecycle. Repeating an Accepted request can return Accepted until the worker updates it. Repeating a Confirmed or Rejected request returns the terminal state already recorded. A client should not create a new identifier merely because it is impatient for a different answer. That would request new work rather than observe the original one.
The status route in the fixture returns the recorded state for a known request. It is deliberately small and does not provide a complete missing-resource, authorization or retention policy. Those omissions are documented so that a reader does not confuse the testing route with a hardened public API.
A useful production acceptance suite would include the permitted identity scope, duplicate delivery, conflicting payloads and status visibility for the relevant actors. The demonstrated checks cover the selected synthetic scope and expose the distinction that such a suite must preserve.
Keep acceptance and rejection in the same report
The deferred path can look excellent if a dashboard counts only initial2xxresponses. All100requests in each measured batch were admitted. Half were later rejected because the fixture had only50units. A report that stops at admission would omit the outcome that matters to the caller.
The correct interpretation is not that the queue malfunctioned. The system behaved according to its declared deferred contract. The question is whether that contract is the one the product intended to offer. If the interface promised guaranteed reservations at admission, the design would be wrong even though the implementation passed every selected test.
This is a useful review exercise for an AI-generated or human-written API alike. Ask what the successful response proves, which state is authoritative and how the client learns about an unfavorable terminal result. Then inspect the code and tests against those answers. A compiler, a framework and a green HTTP status cannot supply the product meaning on their own.
The runtime and source pins are part of that reproducibility contract. The companion uses SDK 10.0.400 and runtime10.0.15 with roll-forward disabled. Its measured JSON preserves all five samples and their validated counts. A future run should retain its own environment and source identity instead of silently replacing these observations.
Source code and execution evidence
Download the complete lab, source record and raw execution results. This package supports the specific execution claims in this article. It is not a production certification.