Markdown for LLMs
Troubleshooting the deadline app
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Troubleshooting the deadline app
## At a glance
- **Goal:** match each failure you will actually meet to its cause and
its fix, without guessing.
- **You need:** any chapter — this page is the escape hatch every
other chapter points to. Read the failing line first, then the
symptom row, then run the chapter's check again.
- **Run:** the two diagnostic commands most rows lead to — is the
question executable, and what does the draft still miss:
```bash
node --import tsx src/cli.ts check-contract
node --import tsx src/cli.ts explain --candidate 2026-03-20 --event 2026-03-06
```
- **Files:** most fixes land in the adapter or the queries:
```text
src/to-case.ts MODEL_SPEC, fact predicates and units
src/query.ts question names and argument shapes
```
## Diagnose in order
Read failures outside in: first the thrown error (the application
failed before any answer existed), then `evaluationStatus` (the engine
declined), then `truthStatus` and values (the engine answered, and the
answer may be inconclusive). Each layer has its own fix, and skipping
a layer mislabels the fault — most often by reading a declined
evaluation as a wrong value.
## Symptoms
| Symptom | Cause | Fix |
|---|---|---|
| `PackageNotFound` on open | canon package not installed, or the spec names a version that is not there | `npm ci`; confirm `MODEL_SPEC` is `de.bgb.fristen@0.1.0` and the installed canon package is 0.1.5 |
| `FactError` with `nearest: [...]` | a predicate name the canon does not know — misspelt, or renamed by a model change | pick the intended name from `nearest`; fix it in `to-case` (facts) or `query` (questions) |
| `NEITHER` on every question | missing facts, an underived-but-declared claim, or a wrong candidate (an undeclared predicate raises `FactError` instead — see the row above) | first `check-contract` confirms the questions are executable, then `explain` on the draft: the ask list names the missing fields |
| collect answers `COMPUTED` with no values | the rules derived nothing for these facts — usually a missing premise | same as above: `explain` on the matching draft names the gap; do not render "no value" as an end date |
| `sources: []` on every answer | this canon build attaches no source anchors to answers | expected on this build: show the applied rule identifier as the address and state the empty list plainly |
| hash mismatch replaying a good capture | bytes compared across SDKs, a case or query edit, or a spec mismatch | replay with the same SDK that saved the capture; read the replay detail — it distinguishes mismatch from refusal from an integrity failure |
| `replay refused: capture model … ≠ replay model …` | the capture was saved under another spec | replay against the recorded spec, not the open one; versions are never mixed |
| `address already in use` on `npm start` | the port is busy, usually a previous server still running | stop the previous server, or `PORT=8788 npm start`; `HOST` overrides the loopback bind |
| `tsx: command not found` | the TypeScript runner is not installed | `npm ci` installs the pinned toolchain; the suite needs it too |
| type error on a predicate argument | a value that does not match the declared parameter type — a date where the canon wants a quantity, or a misspelt unit | read the parameter list from `questions('en')` and fix the adapter; `'14 calendar_day'` needs the exact unit name |
| `not-computed` on a previously fine case | the call now declines: a reading must be chosen, or a question needs a decision the engine will not make | the view shows the status (`Not computed: …`); treat it as a prompt for input, not as a crash |
| checksum mismatch on the download | a corrupt or substituted archive | re-download; do not proceed past a failed `shasum -c` |
## Two symptoms newcomers misread
Two rows deserve emphasis because newcomers misread them most. `NEITHER`
everywhere feels like the engine is broken; it means neither the claim
nor its negation is supported. Missing facts are one possible cause,
not the only one — full input also answers `NEITHER` on a wrong
candidate date. Check the query against the model first (an unknown
relation is an integration error, not a `NEITHER`), then investigate
the blockers: run `explain` before changing anything — on the partial
example case it asks for `durationDays` and reports the computed
intermediate as not askable.
The hash mismatch row feels like corruption; across SDKs it is normal.
TypeScript and Python return the same statuses and the same values on
the same case, but their canonical bytes differ, so their `result`
hashes differ too. Same-SDK comparison is the rule; the capture format
records enough (`adapter`, `model`, `versions`) to enforce it.
## Limits and errors
- This table covers the application's failures, not the canon's
content: if the rules compute a date you believe the provisions do
not support, that is a question for the canon author, with the rule
identifier and the capture attached.
- Python has no `questions`, no `unfold`, and no `focused_truth`. A
Python `AttributeError` on one of those names is not a bug in your
code — read the manifest from the TypeScript SDK instead.
- A document-checksum failure on a capture has no fix except
discarding the file. Re-export from the case that produced it.
## Check your understanding
Break one thing on purpose, watch the expected failure, then restore
it.
**Stage 1 — break the adapter.** In `src/to-case.ts`, delete the
`frist_dauer_tage` line from `toFacts` (leave `toDraftFacts` as it
is), then run the missing-facts tests:
```bash
node --import tsx --test test/missing.test.ts
```
Exactly one test fails — the complete form now sends no duration, so
the questionnaire asks for `durationDays` again instead of stopping on
`NEITHER`:
```text
not ok 5 - a complete case with a wrong candidate ends the questionnaire on NEITHER
expected: 'none-askable'
actual: 'ask'
# tests 14
# pass 13
# fail 1
```
**Stage 2 — restore the line** and run the same command again. The
suite is green:
```text
ok 1 - a draft with only the event asks for durationDays
ok 2 - an empty draft asks for both fields
ok 5 - a complete case with a wrong candidate ends the questionnaire on NEITHER
# tests 14
# pass 14
# fail 0
```
If stage 1 shows no failure, the tests are not running against your
edit — check the working tree before trusting any green line.
Predict before you open each answer.
<details>
<summary>In stage 1, why do the draft tests 1–4 still pass?</summary>
They build their facts with `toDraftFacts`, which still sends the
duration when the draft has one. Only test 5 builds a complete case
with `toFacts`, the function you edited.
</details>
<details>
<summary>Every question answers `NEITHER` on a fully filled form. What do you run first?</summary>
`check-contract`, to confirm the questions are executable — an
unknown predicate would raise `FactError`, not `NEITHER`. Then
`explain` on the draft. A full form with a wrong candidate date also
answers `NEITHER`, so missing facts are only one possible cause.
</details>
## Next
- [Build with Arxo: route map](/build/)
- [Quickstart](/guide/quickstart/) to revisit the single-file version
- [How to read an answer](/guide/reading-an-answer/)
- [Connect an AI assistant](/guide/mcp/)