docs← Back to article

Markdown for LLMs

Architecture

The source Markdown for this article. Copy it into your assistant or download it as a text file.

Download this articlePlain text ↗
# Architecture

This page maps the deadline example as built: the layers, the data path
from a form submission to a stored capture, the runtime singleton, and
the boundaries between browser, example server, and engine.

```text
docs/build/examples/deadline-app
```

## The data path

One request travels eight steps. Each step is one module with its own
exports, listed in the table below; nothing is shared between requests
except the model handle described after it.

1. **Form.** The page shows four fields — the event date, the period
   length in days, an optional candidate end date, and the legal time —
   and the browser posts them to the example call.
2. **Validate.** The raw fields are checked and turned into a typed form
   input, or the request is rejected before any engine call runs.
3. **Facts.** The adapter turns the validated form into canon facts and
   a case input. It stamps its own version (`1.0.0`) so a capture
   records which mapping produced it.
4. **Query.** Two questions are built: a collect query that computes the
   end date and a truth query that checks the candidate. Both carry the
   legal time, the `Europe/Berlin` time zone, and the deadline policy
   `urn:de:corpus:clir:bgb-fristen#BGB_FRISTEN_TAG`. (Time zone,
   calendar data, and counting policy are three separate context
   elements — see the facts chapter.)
5. **Engine.** The questions run against the open model, which returns
   the raw answer documents.
6. **Read.** Each answer is reduced to one of four kinds: a value, a
   claim, not computed, or unknown.
7. **View.** The reduced answer becomes the shape the page and the API
   response render, including the inconclusive shapes.
8. **Capture.** The case, the pins, and the answer are stored with the
   canonical document bytes and their checksum. Replay later reproduces
   the answer and detects edited bytes; a separate step maps a report of
   missing premises to follow-up questions.

The command line runs the same steps without the server: `evaluate`
computes and stores, `replay` reproduces from a capture.

## Module table

| Step | Module | Exports | Role |
|---|---|---|---|
| 1 | `public/index.html` | the form | collect the four fields: `eventDate`, `durationDays`, `candidateEnd?`, `legalTime` |
| 2 | `src/input-schema.ts` | `validateFormInput`, `validateDraftInput`, `FormInput`, `DraftInput` | reject bad forms and drafts before compute |
| 3 | `src/to-case.ts` | `ADAPTER_VERSION`, `toFacts`, `toCaseInput`, `toDraftCaseInput` | map fields to facts and case input |
| 4 | `src/query.ts` | `buildCollect`, `buildTruth` | build the collect and truth questions |
| 5 | `src/runtime.ts` | `openModel`, `evaluateCollect`, `evaluateTruth`, `explainWhy`, `explainDraft` | hold the model; run questions |
| 6 | `src/read-result.ts` | `readCollect`, `readTruth`, `AppResult` | reduce answers to `value`, `claim`, `not-computed`, `unknown` |
| 7 | `src/to-view.ts` | `toViewModel` | render any `AppResult` for display |
| 8 | `src/capture.ts` | `CAPTURE_FORMAT`, `captureEvaluation` | archive captures as `deadline-app.capture/1` with `documentBase64` and `sha256` |
| 8 | `src/replay.ts` | `replayCapture`, `verifyCaptureIntegrity` | reproduce answers; check document bytes |
| 8 | `src/missing.ts` | `ASKABLE`, `missingFacts` | turn `whyNot` blockers into questions |
| — | `src/batch.ts` | `runBatch` | stream many cases to a line report |
| — | `src/check-contract.ts` | `APP_PREDICATES`, `checkContract` | test whether a model serves this app |
| — | `src/server.ts` | `handleRequest`, `createServer` | serve the form and the three API calls (evaluate, replay, capture download) |
| — | `src/cli.ts` | `evaluate`, `explain`, `batch`, `check-contract`, `replay` | run the flow without the server |
| — | `python/deadline.py` | the same flow | mirror the flow in Python |

## The runtime singleton

`openModel` opens the model once and hands the same handle to every
caller:

```text
open('de.bgb.fristen@0.1.0', { offline: true })
```

The version pin makes answers reproducible; `offline: true` keeps
execution local (`via` is `local`) with no network in the call path.
Each evaluation builds a fresh case input per request through
`toCaseInput`, so one request cannot see another request's facts: state
isolation comes from fresh inputs, not from locks.

Execution is sequential per request: each handler awaits its engine
calls in turn. The server makes no concurrency claim beyond what
`node:http` with `await` gives — requests interleave only where the
runtime yields, and no request mutates state another request reads.

## Trust boundaries in words

Three zones, two crossings:

- **Browser.** Holds the form and the rendered answer. It is untrusted
  input: values may be missing, mistyped, or hostile. Nothing the
  browser sends reaches the engine unchecked.
- **Example server.** Holds validation, the adapter, query building, the
  model handle, reading, view shaping, and captures. It checks the form
  first (`validateFormInput`), maps only the four fixed fields, and pins
  the model version, the policy, the time zone, and the calendar data
  itself — none of those cross over from the browser.
- **Engine.** Holds the canon and the evaluation. It sees only typed
  facts and questions; it answers with statuses, proof, and hashes.

The first crossing (browser to server) carries the four-field JSON
request in and a view plus a capture id out; a separate download
carries the full stored capture out for one id. The second crossing
(server to engine) carries facts and questions in and answer documents
out. Captures never cross back into the engine as trusted input:
replay re-runs the pinned case and compares, and
`verifyCaptureIntegrity` fails closed on edited bytes.

```text
browser --(4 fields)--> server --(facts + questions)--> engine
browser <--(view + capture id)-- server <--(answer documents)-- engine
browser <--(stored capture file)-- server
```

## The Python mirror

`python/deadline.py` follows the same eight steps against the same
pinned model. Statuses and values match the TypeScript path; the result
hash and document bytes do not, so a capture is replayed by the SDK
that saved it. Where a step needs `questions`, `unfold`, or
`focused_truth`, the Python path reads the manifest the TypeScript side
produced at author time.

## Next

- [Testing: what the suite checks](/build/application/testing/)