Skip to content
Beskid Beskid

Beskid

Jump to a Beskid service

Beskid

Jump to a Beskid service

Beskid blog · Design

OpenSpec: When Your Spec Drifts, Your Catalog Catches You

Beskid's normative spec lives in OpenSpec. A catalog.json indexes every document, requirement, and decision. When the spec changes and the catalog doesn't, CI fails. The catalog is the immune system for documentation truth.

Published
← All posts

The Beskid platform spec has 185 capabilities, 519 requirements, 304 normative sources, and 178 spec documents spread across dozens of directories under openspec/specs/. The question every language project faces at this scale is not “is the documentation correct?” — it is “how do you know?”

Beskid’s answer is OpenSpec. And OpenSpec’s immune system is a single JSON file called catalog.json.

The normative specification lives in openspec/specs. Each capability gets a directory: community--spec-maintenance--architecture/, language-meta--composition--dependency-injection/, compiler--build-pipeline--backends-jit-aot/. Each directory contains a spec.md with SHALL requirements, scenarios, decisions, and cross-references to related capabilities.

The website reads OpenSpec directly at build time. There is no separate website copy of the spec. There is no “docs” folder that drifts out of sync with the source of truth. The website is a projection. OpenSpec is the authority.

The catalog — openspec/catalog.json — indexes every document, every requirement, every decision, every legacy slug, every book link. It is not written by hand. It is generated by scripts/openspec/build-catalog.ts. The generation is deterministic: same inputs, same catalog. The catalog is checked into the repository. Every change to the spec must be accompanied by a catalog regeneration — and CI verifies this.

This is where it gets interesting. CI runs scripts/openspec/validate-standard.ts on every push. The validator does not check that the spec “looks good.” It checks that reality matches the catalog:

  • Every document path in the catalog must exist on disk.
  • Every document’s sourceHash must match SHA-256 of the actual file content. If you edited a spec document and forgot to regenerate the catalog, CI fails.
  • Every entry’s legacy slugs must match its records’ slugs — so old links don’t rot.
  • Every TBD Purpose header must be resolved — a hard-fail assertion. No “we’ll document this later” escapes the gate.
  • Every document not marked as informative must have the correct authority and disposition fields.
  • Book traceability links in the catalog must match the actual cross-references derived from the spec content.

The commit history of catalog regenerations tells the story. Commit 501554d5 — “regenerate openspec catalog for AGENTS.md drift.” Commit e6c36925. Commit ac7eb2d2. Commit 13f58085. Commit e8d7e6c9. Each one is a guardrail that fired: the spec changed, the catalog noticed, CI blocked the merge, someone regenerated the catalog, the merge went green.

These commits are not bugs. They are proof that the immune system works. The catalog says what the spec should look like. The validator checks what the spec actually looks like. When they disagree, the merge fails. When they agree, the documentation is provably self-consistent.

The validate-standard tool has one rule that is deliberately cruel: TBD Purpose headers in archived specs are a hard failure. Not a warning. Not a “fix later.” A hard, gate-blocking failure.

AGENTS.md — the file that defines how agents interact with the project — is in the catalog. When AGENTS.md drifts from the catalog, CI fails. When a spec document has a Purpose section that says “TBD,” CI fails. There is no grace period. There is no “we’ll document this in the next sprint.”

The rationale: a spec with TBD purposes is a spec that has not finished thinking. If you cannot state why a capability exists, you should not archive its spec. The hard-fail forces the conversation: either write the purpose, or keep the spec provisional. Provisional specs are excluded from the catalog’s normative index. They can exist, but they cannot claim authority.

OpenSpec is the same pattern Beskid keeps rediscovering, applied to documentation:

DomainOld approachOpenSpec approach
Spec consistency”We updated the docs”sourceHash must match or CI fails
Link integrity”Links probably work”Every slug validated against catalog index
Purpose completeness”TBD is fine for now”Hard-fail on unresolved TBD Purpose
Cross-reference accuracyManual Book-to-Spec linksBook traceability verified against catalog
Authority tracking”This doc seems normative”Explicit authority/disposition per document

In every case, the move is the same: from trust to verify. From “we wrote this correctly” to “the catalog proves this is correct.”

The platform-spec site at spec.beskid-lang.org serves the OpenSpec catalog directly. The site/platform-spec server reads openspec/catalog.json at startup, builds the navigation tree from the catalog’s document index, and serves spec pages with the catalog’s cross-reference data embedded. There is no separate database. No CMS. No content API that could drift from the source of truth.

The catalog is also how the tracker knows which spec capabilities to link against delivery bands. When a v0.4 task says “implement D-EXEC-ABI-0003,” the tracker resolves that identifier against the catalog to find the exact spec section, its requirements, and its decisions. The tracker doesn’t maintain its own spec map. It reads the catalog.

This is the contract that makes Beskid’s documentation stack sustainable: the spec is the source of truth, the catalog indexes the spec, the validator enforces the catalog, CI enforces the validator, and every tooling surface reads the catalog instead of inventing its own metadata. One chain. One truth. One gate.

Cross-reference The Platform Spec Cutover and Trust to Verify for the earlier decisions that made this architecture possible. The catalog is the immune system that makes the cutover stick.

OpenSpec catalogvalidate-standard scriptcatalog regeneration CI drift