Align backend/ with docs/Backend.md (Mandate Next.js service)#5
Merged
Conversation
The backend folder previously held a tiny FastAPI 'health' stub that bore no
relation to the brief in backend/docs/. This commit replaces it with the
Next.js Route Handler service that the docs (and the matching front-end app)
were already designed around.
Highlights:
- Folder layout mirrors backend/docs/Backend.md §1 exactly:
app/api/run, app/api/memo/[runId], app/api/amend, app/api/health
agents/orchestrator.ts + agents/desks/{company,founder,investor,round,
mandate,wire-safety}.ts + agents/{synthesise,memo,amend,parse-prompt}.ts
lib/{contract,types,mandate-loader,mandate-evaluator,cache,ledger,util}.ts
lib/sources/{specter,companies-house,opensanctions,whois,pdf-parse,llm}.ts
fixtures/{specter,companies-house,opensanctions,whois,pdfs}/ + fund-state.json
MANDATE.md (parsed by the loader)
scripts/smoke.ts (end-to-end driver)
- lib/contract.ts is the cross-team seam (RunEvent/DeskFinding/Verdict/
MemoData/AmendmentDraft) and matches the schema in Backend.md §2.
- Orchestrator fans out six desks with Promise.allSettled, a 30s per-desk
timeout, and degraded findings on tier-2 failures (Backend.md §14).
- Mandate desk uses a pure rule evaluator (lib/mandate-evaluator.ts) — no
LLM in the hot path.
- Wire-safety desk catches the demo BEC case: lookalike domain (edit
distance 2 vs verified acme.co), domain age 6 days, DKIM fail, and
account-holder-name mismatch. Verdict is hold; an amendment draft is
produced with the lookalike domain captured.
- Every external source defaults to fixtures so the demo runs cleanly with
no API keys; DEMO_FORCE_FIXTURES=true forces the fixture path on stage.
- Verified clean: tsc --noEmit clean, next build clean (no warnings),
smoke run hits both clean and BEC scenarios end-to-end via SSE and
reaches ALL CHECKS PASSED.
backend/.env.example, backend/README.md and root AGENTS.md updated to
reflect the new structure and dev workflow.
Co-authored-by: Mr T <trenchsheikh@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
backend/docs/is a complete, opinionated brief for a Next.js Route Handler service called Mandate: six diligence desks fanning out in parallel, an SSE event stream into the Next.js frontend, fixture fallbacks, and an amendment-PR flow. The matching frontend infront-end/already imports the contract types and event names from that brief.But
backend/itself was a tiny Python FastAPI stub with/and/health— completely out of step with the docs. The user asked us to walk throughbackend/docs/, follow the structure, and make sure it's "all working clean."This PR rebuilds
backend/to matchdocs/Backend.mdexactly, end-to-end, and verifies it works.What changed
Folder layout now mirrors
docs/Backend.md§1:Behaviour highlights
lib/contract.tsis the single source of truth forRunEvent,DeskFinding,Verdict,MemoData, andAmendmentDraft. The shapes match the spec verbatim.Promise.allSettled, a 30s per-desk timeout, and degraded findings on tier-2 failure (per §14).acme.co), domain age 6 days, DKIM fail, and account-holder-name mismatch with the SPA entity.DEMO_FORCE_FIXTURES=trueis the on-stage panic button (per §13).proceedand holds it onhold(per §11).agents/amend.tsproduces a real diff againstMANDATE.md'swire_safetyblock and captures the offending domain.Removed
backend/app/__init__.py,backend/app/main.py,backend/requirements.txt— the prior FastAPI stub.MANDATE.mdis now at backend root where the loader looks for it (per §5.1).Verification
backend/.env.example,backend/README.md, and rootAGENTS.mdare updated to reflect the new dev workflow.npm installnpm run typecheck(tsc --noEmit)npm run build(next build)GET /api/health{"status":"ok",...}proceed, memo generatedhold, memo generated, amendment diff capturesacrne.coThe clean smoke transcript is attached:
backend_smoke_clean_and_bec.txt
How to run locally
No API keys are required; fixtures cover every source. Set
OPENAI_API_KEY(orCURSOR_API_KEY) to enable the editorial-summary sentence on the IC memo and the LLM-generated amendment rationale.Notes / future work
lib/sources/*throwlive-not-implementedon purpose — they're stubs ready to be filled in once Specter / Companies House / OpenSanctions / WHOIS keys exist. The shape of the cache/fixture layer doesn't change when keys are added.parse-prompt.tsis deterministic regex-based for now (Backend.md §12 calls for an LLM). Plumbing exists inlib/sources/llm.tsto swap it in without touching callers.next/fontwarnings, lint/eslint config, Dockerfile, and a CI workflow are not part of this PR — they were not blocking the brief.Slack Thread