# Phase 2 — Turn decisions into commitments

## Continue one case across three perspectives

Phase 1 ended with an assigned application. This phase adds an assessment decision, an offer, borrower acceptance and a funding reservation. The same case history connects the steps. There is still no disbursement, repayment or real money.

Use [the interactive application](/build-series/modern-dotnet-application/demo) with the local API running. The setup commands are in [Phase 1](/build-series/modern-dotnet-application/phase-1-architecture-blueprint).

## Make a decision with a reason

As the assigned operations reviewer, enter a reason of 10–500 characters and approve or decline. The API checks the role, the reviewer’s ownership, the expected revision, the current status and synthetic evidence. A borrower cannot approve a case by calling the endpoint directly. Another reviewer cannot overwrite its owner’s decision.

Approval moves the application to Approved. It does not generate a payment or reserve capital. Decline ends the current application journey while preserving the reason. A second contradictory decision is rejected rather than silently replacing the first.

## Issue terms that can be identified

After approval, issue offer v1 with a reason. Its terms snapshot the principal, twelve-month term, zero demo interest, zero fees and an expiry seven days from issue. Total repayable equals the principal under these teaching assumptions; a repayment schedule has not yet been implemented.

The reviewer may supersede an unaccepted offer. Issuing v2 marks v1 Superseded while keeping it inspectable. Accepted offers cannot be rewritten through this operation. Changing an accepted agreement would require a separately designed amendment process.

Offer version and application revision answer different questions. The offer version identifies exact terms. The application revision identifies the whole case state observed by the caller. Acceptance supplies both, so a stale view or an outdated offer is rejected.

## Accept the exact offer

Switch back to the borrower. Review the displayed terms and select the confirmation checkbox. Acceptance records the offered version and timestamp. The service rejects an expired or superseded offer. Operations must issue a new version after expiry; the interface does not silently extend the old terms.

The borrower’s action changes the case to Accepted. This is a demonstration of explicit consent to synthetic terms, not an electronic-signature service or a legally verified lending agreement.

## Reveal the eligible opportunity

Switch to Cedar Demo Funding. Accepted cases appear as opportunities identified by case number and principal. Borrower documents, names, purposes and internal review notes are excluded from the partner response. Accepted terms are visible because they define the commitment being considered.

Cedar starts with £50,000 of synthetic capacity. Each opportunity is funded by one partner in full. The interface separates available capacity from reserved commitments. Deployed and received amounts remain £0 because this phase has not executed a payment.

## Reserve without overspending

Reserve an accepted case. The API checks that capacity remains sufficient, the current revision matches, the case is still Accepted and nothing is already reserved. It changes the status to Funding Reserved and records the partner and amount.

The capacity calculation and reservation occur inside the same single-process mutation lock. Two concurrent requests against different cases cannot both spend the same final capacity. A repeated reservation against one case cannot reserve its principal again. The integration tests exercise both situations.

The lifecycle is Approved → Offer Issued → Accepted → Funding Reserved. These are distinct events. The final state means a commitment exists; it does not mean funds were sent, received or reconciled. Reservation cancellation, expiry and release rules remain future work, so the demo has no misleading “refund” shortcut.

## Understand the implementation boundary

The ASP.NET Core service enforces the rules and provides durable local JSON state. Angular presents the three perspectives, reports API errors and allows refreshing after a stale update. The activity history records the actor, time, action and explanation. It is application history, not an immutable financial audit ledger.

The service binds to loopback and restricts browser origins to the documented local previews. Demo session selection is intentionally open to anyone on the same machine. Do not expose the API or enter personal information. A deployable service needs real authentication, an authoritative database, operational controls and a more complete financial model.

## Run the failure scenarios

```powershell
dotnet build marketplace/Marketplace.Api
node scripts/test-marketplace.mjs
```

The HTTP suite checks successful decisions and reservations, but also invalid amounts, missing evidence, unassigned reviewers, borrower isolation, stale commands, superseded and expired offers, duplicate creation, duplicate reservations and competing requests for limited capacity. It restarts the API to verify that successful state was saved, and runs against a temporary store rather than your review data.

For a manual concurrency exercise, open the same case in two browser tabs under Jordan’s identity. Act in one tab, then act using the older state in the other. The server rejects the stale command. Refresh before choosing the next action; do not blindly retry a transition whose outcome might already be saved.

## What remains after Phase 2

The next work should establish real identity and SQL persistence before expanding the payment lifecycle. Payment preparation, independent authorization, uncertain outcomes, reconciliation, repayment schedules, arrears and closure require their own rules and tests. They are not implied by the funding reservation demonstrated here.

## Guided practice

Issue v1, supersede it with v2, accept v2 as the borrower and reserve capital as Cedar. Read the case history in operations and explain why the reserved figure changes while deployed and received remain zero.
