Markdown for LLMs
Start: install, run, and test
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Start: install, run, and test
This chapter runs the example application; the following chapters take
it apart layer by layer. You start from an empty directory and finish
with the form open in your browser and the first two answers on screen.
## At a glance
- **Goal:** get the example application running from an empty
directory and see the first computed answers — the end date of a
14-day period, and the check of a candidate end date.
- **You need:** Node.js 20 or newer with npm, plus `curl` and `unzip`;
no repository checkout. Versions are listed in the
[route overview](/build/).
- **Run:**
```bash
curl -O https://docs.arxo.io/build/deadline-app-0.1.0.zip
curl -O https://docs.arxo.io/build/deadline-app-0.1.0.zip.sha256
shasum -a 256 -c deadline-app-0.1.0.zip.sha256
unzip -q deadline-app-0.1.0.zip
cd deadline-app
npm ci
npm test
npm start
```
- **Files:** this chapter changes nothing; it runs what the archive
contains.
```text
(none — this chapter only runs what the archive contains)
deadline-app-0.1.0.zip 29 files: package.json, package-lock.json, tsconfig.json,
README.md, src/*.ts (13), test/*.test.ts (8),
test/fixtures (1), python/*.py (2), public/index.html
```
## Check your Node version
The starter needs Node 20 or newer (`engines: >=20`); for a new setup use
a supported LTS, with Node 24 recommended. Any unzipper works in place of `unzip`:
the archive is a plain zip. Python 3.12 or newer is needed only for the
Python mirror. The section [Choose a Node version](#choose-a-node-version)
below gives the support dates.
## Download and verify the archive
Download and verify the versioned archive (example 0.1.0):
```bash
curl -O https://docs.arxo.io/build/deadline-app-0.1.0.zip
curl -O https://docs.arxo.io/build/deadline-app-0.1.0.zip.sha256
shasum -a 256 -c deadline-app-0.1.0.zip.sha256
```
Expected: `deadline-app-0.1.0.zip: OK`. (On Linux the same check is
`sha256sum -c`.) The verified checksum is:
```text
4b8186fcb2caedf9316ff02fdca7032c6713bddc1fe672713fb44eebe0950411
```
## Install the pinned dependencies
Unpack and install the pinned dependencies from the lockfile:
```bash
unzip -q deadline-app-0.1.0.zip
cd deadline-app
npm ci
```
`npm ci` installs exactly what `package-lock.json` pins: `@arxo/law`
0.3.3 with `@arxo/canon-bgb-fristen` 0.1.5, plus the pinned
`tsx`/`typescript`/`@types/node` toolchain. No version floats.
## Run the tests
Run the suite — a separate step, before any server:
```bash
npm test
```
```text
# tests 63
# pass 63
# fail 0
```
63 tests pin every answer shape the later chapters rely on:
`2026-03-20` for the example case, `TRUE_ONLY` on the right
candidate, `NEITHER` on a wrong candidate and on partial facts,
the draft questions from `explain`, and the capture/replay matrix.
If any line fails here, stop: every later chapter assumes this suite
holds. The last chapter lists each symptom with its cause and fix.
## Start the server and see the first answers
Start the server — it runs in the foreground, so this is its own
terminal step:
```bash
npm start
```
```text
deadline-app listening on http://localhost:8787/
```
Open `http://localhost:8787/`: the form, pre-filled with the example
case. The command line runs the same two calls without the browser:
```bash
node --import tsx src/cli.ts evaluate --event 2026-03-06 --days 14
node --import tsx src/cli.ts evaluate --event 2026-03-06 --days 14 --candidate 2026-03-20
```
The first call computes the end date. It prints the app's reading of
the answer — headline, rules, hashes; the full SDK shape arrives in
chapter 3:
```text
The period ends on 2026-03-20
Computed from the event date and the duration by the pinned canon.
rules: TagesfristEnde
sources: No sources anchored in this canon build — the rule names above carry the provenance.
program: sha256:468e3fe17c6a28371c6ce8258ae7928c496593673d4b73a52c2a9c40cb50ee33
semantic: sha256:90e53b453107dfee9422a5fe24774823430b5c82c3577b9321a0838a6e54c695
result: sha256:ae9a7b19361154c868eb9b28d5ac6395b1d2844053259a1abfd6f75e300de812
```
The second call checks the proposed date and establishes it:
```text
Yes — the proposed date is established
The engine established that the period ends on the proposed date.
rules: TagesfristEnde
```
`TRUE_ONLY` behind that headline means the computation supports the
claim and not its negation. A wrong candidate answers `NEITHER`,
which is a normal inconclusive result, not an error — chapter 3
explains why it is not `FALSE_ONLY`.
## How the two answers are produced
The model spec names the exact canon version the application pins. It
lives in one place, beside the deadline policy and the time zone:
```ts
// src/to-case.ts
export const MODEL_SPEC = 'de.bgb.fristen@0.1.0';
export const POLICY = 'urn:de:corpus:clir:bgb-fristen#BGB_FRISTEN_TAG';
export const TIMEZONE = 'Europe/Berlin';
```
The application opens the model once and asks two questions.
`buildCollect` asks "which dates end this period"; `buildTruth` asks
"does the period end on this date". Both read the same facts. Opening
with `offline: true` resolves the pinned canon locally:
```ts
// src/runtime.ts
// Calculate mode: which date(s) end the period?
export async function evaluateCollect(input: FormInput): Promise<Answer> {
const model = await openModel();
const query = buildCollect();
return model.collect(query.predicate, query.args, toCaseInput(input));
}
// Verify mode: does the period end on the proposed date?
export async function evaluateTruth(input: FormInput, candidate: string): Promise<Answer> {
const model = await openModel();
const query = buildTruth(candidate);
return model.truth(query.predicate, query.args, toCaseInput(input));
}
```
## Choose a Node version
Two versions matter. The minimum the starter supports is Node 20
(`engines: >=20`), but for a new setup use a supported LTS — Node 24
("Krypton", Active LTS until 20 October 2026, supported until 30 April
2028) is the recommended route; Node 20 itself is EOL since April 2026.
## Match the release manifest
The same checksum, file list, and version are bound together in
the release manifest beside the archive,
`deadline-app-0.1.0.release.json`. If any copy of these numbers —
on a page, in a text export, in a cached view — disagrees with
the manifest, that copy is a stale edition, not a second release:
published bytes never change under their name.
## Change the host or port
The server binds loopback only and defaults to port 8787; `HOST` and
`PORT` override both:
```ts
// src/server.ts
// Loopback only by default: the printed URL is the reachable URL.
// Set HOST=0.0.0.0 deliberately to expose the example on the network.
const host = process.env.HOST ?? '127.0.0.1';
const port = Number(process.env.PORT ?? '8787');
createServer().listen(port, host, () => {
console.log(`deadline-app listening on http://${host === '127.0.0.1' ? 'localhost' : host}:${port}/`);
});
```
## Limits and errors
- A checksum mismatch on the download means a corrupt or substituted
file: re-download, do not proceed.
- `PackageNotFound` on `openModel` means the canon package is not
installed. The fix is `npm ci`, not a looser version.
- Node below 20 is outside `engines` (`>=20`), but that field alone
only warns: without `engine-strict` npm installs anyway, and the
starter ships no `.npmrc` to harden it. If old-Node symptoms
appear, the fix is upgrading Node — not a looser version. The
troubleshooting chapter lists each symptom with its fix.
## Check your understanding
The suite you ran above (`npm test`, 63 of 63 passing) is this chapter's
test run; there is no need to repeat it. Run one call the chapter has
not shown yet — the check of a wrong candidate:
```bash
node --import tsx src/cli.ts evaluate --event 2026-03-06 --days 14 --candidate 2026-03-21
```
```text
Not established either way
The engine neither established nor refuted the proposed date; it does not guess.
rules: TagesfristEnde
```
Before reading each answer, predict it.
<details>
<summary>The CLI prints an end date for one command and a yes-or-no headline for the other. What decides which question it asks?</summary>
The `--candidate` flag. Without it the CLI calls `evaluateCollect`,
which asks for the end date (`buildCollect`). With it the CLI calls
`evaluateTruth`, which checks that one date (`buildTruth`).
</details>
<details>
<summary>What happens if you run the first command without the --days flag?</summary>
The CLI refuses it before any engine call. It prints
`error: durationDays must be an integer from 1 to 36500` and exits with
code 2; no answer is computed and nothing is guessed in place of the
missing duration.
</details>
<details>
<summary>The lockfile installs the canon package at 0.1.5, but the model spec says de.bgb.fristen@0.1.0. Is that a mismatch?</summary>
No. 0.1.5 is the version of the package that delivers the canon; 0.1.0
is the version of the canon itself, which the application pins in
`MODEL_SPEC`. Package 0.1.5 carries model version 0.1.0.
</details>
## Next
- [Bind input: from form fields to facts](/build/bind-input/)
- [Quickstart](/guide/quickstart/) for the same canon in a single file
- [TypeScript SDK](/build/sdk/typescript/) and [loading and pinning](/build/sdk/loading-and-pinning/) for the contracts this chapter uses