Start with the people
Alex is a fictional borrower. Jordan is an operations reviewer. Cedar is a fictional funding partner. They need different views of the same case. Alex should understand the next step; Jordan needs enough context to review it; Cedar should only see an eligible opportunity after an offer has been accepted.
This first phase implements a saved borrower application and a review queue. Phase 2 continues the same case through a recorded decision, a versioned offer and a funding commitment. The wider marketplace includes many more capabilities. These two phases do not implement real payments, customer onboarding, identity verification or loan servicing.
Make the assumptions visible
The demonstration selects a single synthetic product: £1,000–£25,000, twelve months, zero interest and no fees. It assumes one funding partner reserves the full principal of each loan. These defaults make the workflow inspectable without pretending we have settled a commercial lending model.
Only fictional data should be entered. The evidence checkbox attaches a synthetic evidence flag; it does not upload or verify a document. Demo identities can be selected freely. That is a teaching aid, not production authentication.
Run the implementation
The website is Angular. The accompanying service is an ASP.NET Core application targeting .NET 10. From the website repository, build and start the API in one terminal:
dotnet build marketplace/Marketplace.Api
dotnet run --project marketplace/Marketplace.Api --no-buildThe API listens on http://127.0.0.1:5147. In another terminal, build and serve the website:
npm run build
node scripts/serve-built-site.mjs 4330Open the interactive application. Select Alex Morgan and open the workspace. If the API is unavailable, the page explains how to recover. A public static hosting upload cannot run this .NET service.
Save before submitting
Create a fictional purpose and an amount within the product bounds. Saving creates a Draft. A draft may exist without evidence. Submission requires saved synthetic evidence, a valid amount and a purpose of 10–300 characters. Changes in the form must be saved before submission; submission always acts on the server’s saved values.
The API owns these rules. Disabling a button is a convenience, not the control itself. A direct request that bypasses the interface encounters the same validation. A failed action leaves the stored record and activity history unchanged.
Creation includes a client-generated request identifier. Retrying the same creation request with identical details returns the existing draft. Reusing that identifier with different details is rejected. Other commands use the expected application revision: repeating a command after it committed is rejected as stale, and the client should refresh the authoritative result.
Hand work to a named reviewer
Switch to Jordan Lee. Submitted applications appear in the review queue. “Assign to me & start review” moves one to In Review and records Jordan as its owner. Casey, the second demo reviewer, can inspect the case but cannot decide Jordan’s case.
The owner can request more information with a reason. The borrower sees that reason in the history, revises the application and resubmits. Resubmission returns the case to the queue for fresh assignment. A borrower may withdraw before a decision; withdrawal retains the history.
The explicit progression is Draft → Submitted → In Review. Information Required loops through a saved revision and another submission. Withdrawn and Declined are terminal in this demonstration. Reopening, reassignment and appeals are future work rather than hidden shortcuts.
Keep the three views separate
The server derives an actor from a demo session token. Borrowers receive only their own applications. Operations receive the review records. A partner only sees accepted opportunities and its own commitments, with internal notes, borrower names, purposes and evidence omitted from the response.
This demonstrates server-side permission and record-scope checks, but the session creation endpoint intentionally allows choosing any demo actor. Real sign-in, account verification, token expiry, access revocation and deployment hardening are needed before any shared or public service.
Preserve state and reject stale changes
The service persists a JSON snapshot after each successful mutation. A process-wide lock serializes changes. It validates and changes a copied snapshot, writes a temporary file, replaces the stored file, then exposes the new in-memory state. If validation or a disk write fails, the live state is not partly updated.
Every command carries the revision that the caller saw. Two reviewers may both read revision 2. Only the first valid mutation can advance it to revision 3. The second receives HTTP 409 and must refresh. This protects against lost updates in one running API process.
This is intentionally a single-process local persistence design. It is not multi-instance SQL concurrency, a transactional ledger, tamper-evident auditing or a guarantee against machine-level power loss. Do not run two API processes against the same data file.
Inspect the evidence
Run the HTTP integration checks after building the API:
node scripts/test-marketplace.mjsThe checks use their own temporary synthetic store. They cover invalid submission, borrower scope, reviewer ownership, stale revisions, requests for information and persistence across an API restart. Phase 2 adds offer and capacity scenarios. The tests verify the HTTP contract and saved state rather than merely checking disabled buttons.
What the next phase adds
An application being ready for review is different from being approved. Approval is different from acceptance. In Phase 2, we make those distinctions observable through offer versions and funding reservations.