Markdown for LLMs
Security and privacy
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Security and privacy
This page inventories what the deadline example handles, where it rests,
and what leaves the machine. The design keeps the surface small: four
fixed fields in, a rendered answer and a capture file out, nothing else.
```text
docs/build/examples/deadline-app
```
## Data inventory
The application handles exactly four user-supplied strings:
| Field | Example | Sensitivity |
|---|---|---|
| `eventDate` | `2026-03-06` | a date the user typed |
| `durationDays` | `14` | a count the user typed |
| `candidateEnd` | `2026-03-20` | a date the user typed, optional |
| `legalTime` | `2026-09-17` | the date the law is projected on |
There is no account, no session, and no profile. The dates carry no
personal detail beyond what the user typed into the form; the
application adds the time zone (`Europe/Berlin`), the deadline policy
(`urn:de:corpus:clir:bgb-fristen#BGB_FRISTEN_TAG`), and the model pin
(`de.bgb.fristen@0.1.0`) itself.
Derived data — facts, queries, answer documents, views, captures —
stays on the machine that runs the server or the command line. Nothing
is sent to analytics, nothing identifying is placed in the URL, and the
engine runs locally (`via` is `local`).
## The fixed-field API
The example call accepts the four fields above and nothing else:
```text
POST /api/deadline/evaluate 4 fixed fields only
POST /api/deadline/replay a stored capture id, never a pasted capture
GET /api/deadline/capture the stored file for one capture id
```
There is no predicate passthrough, no package passthrough, and no URL
passthrough: the caller cannot name a relation, pick a canon, or point
the server at an address. Unknown fields are refused with `400` — the
server never silently drops input it does not understand. The replay
call accepts the 12-hex-character id of a capture the server itself
stored, re-runs the pinned case from that file, and compares — it
does not execute anything the caller hands it. The download call
serves the stored file bytes for one id, with the same id check
before the id becomes a path segment.
Each exchange below shows the status and error string the handler
returns:
```text
POST /api/deadline/evaluate
{"eventDate":"2026-03-06","durationDays":14,"legalTime":"2026-09-17"}
-> 200 {"view":{"headline":"The period ends on 2026-03-20",…},
"captureId":"e2b377c0b255"}
```
```text
POST /api/deadline/evaluate (one extra field)
{…,"predicate":"frist_ende"}
-> 400 {"errors":["unknown fields: predicate"]}
```
```text
POST /api/deadline/replay
{"captureId":"e2b377c0b255"}
-> 200 {"integrity":{"ok":true,…},"replay":{"match":true,…}}
```
```text
POST /api/deadline/replay (id the server never stored)
{"captureId":"0123456789ab"}
-> 404 {"errors":["unknown capture 0123456789ab"]}
```
```text
POST /api/deadline/replay (capture content instead of an id)
{"captureId":{"format":"deadline-app.capture/1"}}
-> 400 {"errors":["captureId must be 12 hex characters"]}
```
```text
GET /api/deadline/capture?captureId=e2b377c0b255
-> 200 the stored capture file (format deadline-app.capture/1)
```
```text
GET /api/deadline/capture?captureId=0123456789ab
-> 404 {"errors":["unknown capture 0123456789ab"]}
```
The trust boundary is the id: the server reads only files it wrote
(`captures/<id>.json`), and anything that is not a stored id is a
client error, never a document to execute.
## Validation before compute
`validateFormInput` runs before any engine call. A form with a missing
event date, a non-numeric duration, a malformed candidate, or a missing
legal time is rejected with a validation error; the adapter, the query
builders, and the engine never see it. This order — check, then compute —
is what the contract checks in the suite pin down.
## What is stored
Storing on the server and handing the user an archive are two
different actions. Each server evaluation is stored automatically —
there is no opt-out — while downloads happen only on explicit user
action:
| Action | Computes? | Creates a server file? | Hands the user an archive? |
|---|---|---|---|
| `POST /api/deadline/evaluate` | yes | yes, always: `captures/<random-id>.json` | no — answers `{view, captureId}` |
| Form Save summary button | no | no (already stored) | downloads the `{view, captureId}` answer JSON (a summary, not a replayable archive) |
| Form Download capture button | no | no (already stored) | downloads the full stored capture for the latest id |
| CLI `evaluate` without `--out` | yes | no (no server involved) | no — prints the view |
| CLI `evaluate --out file` | yes | no | writes the full capture to `file` |
| `POST /api/deadline/replay` | re-runs, stores nothing new | no | no — answers `{captureId, integrity, replay}` |
The two form buttons download different things on purpose. Save
summary downloads the latest answer payload (view plus id): enough
to show, not enough to replay elsewhere. Download capture fetches
the full stored archive — frozen case, query, hashes, document
bytes — the same file the evaluate call already wrote and the same
file replay reads. That archive is portable: copy it to another
machine with the pinned dependencies and `replay` it there with no
access to the original server's store. To keep a capture, keep the
file; to drop one, delete the file.
A capture holds the form, the adapter version (`ADAPTER_VERSION 1.0.0`),
the version pins, and the answer, including the canonical document as
`documentBase64` with its `sha256` under
`CAPTURE_FORMAT deadline-app.capture/1`. It is a complete record: enough
to reproduce the answer with `replayCapture`, and enough to audit what
was asked.
The server never overwrites a stored capture — recomputing under
new pins writes a new file, as the versioning page describes. Ids
are random per call (`randomBytes`), so repeated identical
evaluations — supervisor probes included — each leave their own
file; there is no deduplication. Point probe traffic at a scratch
`DEADLINE_APP_CAPTURES` directory and rotate it, or accept the
growth.
## Export is an explicit user action
Nothing leaves the machine except by an action the user takes:
submitting the form to the local server, downloading or copying a
capture file, or running the command line. The application makes no
outbound calls in its run path — no registry, no assistant host, no
serving endpoint — and the offline page shows how to confirm that with
the network disabled.
## Secrets: there are none
The example uses no tokens, no passwords, and no API keys. There is
nothing to rotate and nothing to place in the environment except the
server port. If a deployment wraps the example with authentication or
TLS termination, that layer owns its own secrets; the application code
reads none.
## Logging guidance
Log what identifies an answer, not the answer itself:
- log the capture id, the statuses (`COMPUTED`, `TRUE_ONLY`,
`NEITHER`), the rule identifiers that fired, and the three hashes;
- do not log full answer documents or full captures by default;
- log validation rejections with the field name and the reason, without
echoing hostile payloads at length;
- log replay mismatches with both hashes (stored and recomputed) so the
difference is auditable.
Full documents belong in captures the user chose to keep, not in logs
that rotate away.
## Next
- [Deployment: the one way to run the server](/build/application/deployment/)