Validation Rules
Malformed specs fail fast. specdown validates documents against the spec syntaxdepends at parse time and rejects errors before any adapter is invoked.
Rules
| Rule | Rejection |
|---|---|
| Unclosed code block | Fenced block without closing ``` |
| Check without table | > check:name with no params and no table following |
| Hook without code block | > setup or > teardown not followed by a code block |
| Hook without executable block | Hook followed by a non-executable code block (e.g. ```json) |
| Table needs columns | Header row must define at least one column |
| Table needs rows | At least one data row must follow the header |
| Table row/column mismatch | Data row has a different number of cells than the header |
| Block needs target | run: without a target name (e.g. run:shell) |
| Invalid capture syntax | Capture name doesn't match $VarName pattern |
| No duplicate captures | -> $a, $a on a single block |
No capture on !fail |
!fail and -> $var are mutually exclusive |
| Alloy ref exists | alloy:ref must reference a model defined in the same document |
| Alloy module redeclaration | A model fragment redeclares module after its first fragment |
| Invalid alloy ref syntax | Malformed alloy:ref directive |
| Variables resolved | Every ${name} in blocks and prose must trace to a prior capture |
Example: Validation Error
A representative validation error — a check directive without a following table is rejected at parse time:
Reject bare check directive with no table
mkdir -p .tmp-test
printf '# Bad\n\n> check:x\n\nJust prose.\n' > .tmp-test/fnt.spec.md
printf '# T\n\n- [Fnt](fnt.spec.md)\n' > .tmp-test/index.spec.md
cat <<'CFG' > .tmp-test/fnt-cfg.json
{"entry":"index.spec.md","adapters":[{"name":"s","command":["true"],"blocks":["run:shell"],"checks":["x"]}]}
CFG
! specdown run -config .tmp-test/fnt-cfg.json 2>/dev/nullAdding parameters makes the directive valid as a parameterized check call:
Accept parameterized check call without table
mkdir -p .tmp-test
cat <<'SPEC' > .tmp-test/check-call.spec.md
# Check Call
Some prose.
> check:verify(field=plan, expected=STANDARD)
More prose.
SPEC
printf '# T\n\n- [FC](check-call.spec.md)\n' > .tmp-test/index.spec.md
cat <<'CFG' > .tmp-test/check-call-cfg.json
{"entry":"index.spec.md","adapters":[{"name":"s","command":["true"],"blocks":[],"checks":["verify"]}]}
CFG
specdown run -config .tmp-test/check-call-cfg.json -dry-run 2>&1All other validation rules are verified by unit tests.