specodelic
specodelic (spk) is a specification format and toolchain for AI
agents: specs are plain markdown with YAML frontmatter (the
Intent layer) and three fixed tables — Constraints, Model,
Properties — under a closed schema. The linter never reads prose, so prose is free; everything the
toolchain reasons about lives in the structured rows.
The repo dogfoods itself: every corpus file in
openspec/specs/ is a spec written in the
format it describes.
Hit jargon? The glossary defines every term a cold read needs, from EARS to the guarantee ladder.
Is this for you? The format presumes a spec-first workflow — the spec is the design artifact and the implementation is agent-authored, regenerable, machine-checked code. If humans hand-maintain the code and nothing consumes the structured rows, the layers are pure tax, not leverage. The full when-not-to-use statement (assumption, honest one-line test, and the cases where an ordinary design doc wins) is in USAGE.md.
New to the format? Read the worked example — one reservation/order spec built layer by layer, with every lint finding and counterexample the author hit on the way.
Before trusting a green checkmark, read verification boundaries — the guarantee ladder from lint-clean to matches-oracle, and what each rung does not assure.
Thinking about a design rather than code? Specs as the design substrate is the canvas for that — and which command when? routes any task to the right verb. Questions a cold read asks are answered in the FAQ.
The four layers
| Layer | Carries | Checked by |
|---|---|---|
| Intent (frontmatter) | id, kind, statement (one EARS sentence) | linter.frontmatter |
| Constraints | invariants and effects, [[wiki-linked]] traces | linter.schema_shape, graph |
| Model | states + guarded transitions (a state machine) | linter.model_shape |
| Properties | deriving properties with generator + predicate | linter.coverage |
The pipeline
spk walks a spec file through stages, each with its own command:
spk lint— the gate: every rule, every file.spk compile— Constraints → TOML, Properties → proptest scaffolding, Model → TLA+ module (all three artifacts, byte-stable).spk model-check— the compiled model interpreted as a pc-automaton by the embedded stateright backend.spk verify— executes the emitted proptest scaffolding.
Plus tooling around the corpus: spk graph (reference graph with
fan-in/out and dangling detection), spk rename / spk merge /
spk refactor / spk orchestrate, spk doctor (workspace diagnosis),
spk explain (the embedded format guide for agents), spk new (scaffold),
spk init and spk feedback.
Domain packs extend the format without a format revision: a
kind: profile manifest declares typed sections, fiber kinds, and
reference fields for a domain (no config file — lint discovers packs by
corpus scan). Six packs ship in-repo — the five standard packs
(data.lineage, numeric.predicates, empirical.registry,
quant.finance, svc.contracts — still draft) plus the bioimage
D6 pilot (bioimage.data, the first cross-pack ## Requires); see
the packs spec and the
using packs guide.
For agents
No repo access needed: spk explain serves the distilled format guide
from inside the binary — ten topics (format, ears, kinds,
references, lifecycle, lint-rules, dual-format, packs,
graph-views, conform), and every lint
finding carries its own rule_id and one-line semantics. See llms.txt
at the site root for a machine summary.
Ecosystem
specodelic is part of the charly-vibes tool
ecosystem
— the ecosystem map shows how spk relates to the sibling tools
(wai, beads, openspec, genesis-vibes, and the checkers). For the two
relationships that touch this repo daily — the OpenSpec dual-format
protocol and the ah/espectacular correspondence companion — see
tool relationships.
Install
cargo install specodelic
Then spk --version --json (reports the embedded format_revision),
spk new my.first_spec, and spk lint my.first_spec.md.
Documentation provenance
Rendered docs are generated artifacts; the repository sources are authoritative — edit the repo sources, not the rendered output.
Release Status
Generated artifact (from Cargo.toml + git ref). Do not hand-edit — the repo source files remain authoritative.
- Version: v0.11.0
- Built from: main @ d4d9c47
Installation & Quick Start
Install
# macOS (Homebrew)
brew tap charly-vibes/charly
brew install specodelic
# Linux / macOS (binary download — all five release targets, see checksums.txt)
# Grab the archive matching your platform from:
# https://github.com/charly-vibes/specodelic/releases/latest
# specodelic_{VERSION}_linux_{amd64|arm64}.tar.gz · specodelic_{VERSION}_darwin_{amd64|arm64}.tar.gz
tar xz specodelic_*_linux_amd64.tar.gz && sudo mv specodelic spk /usr/local/bin/
# Windows (Scoop)
scoop bucket add charly https://github.com/charly-vibes/scoop-charly.git
scoop install specodelic
# Any platform (Cargo)
cargo install specodelic
(Rust stable toolchain; published on crates.io.)
Installs two binaries: specodelic and its alias spk.
Quick start
All output below was captured verbatim from specodelic 0.11.0. On a TTY
the tool prints human-readable text (shown here); when stdout is piped it
emits a JSON envelope instead.
# Scaffold a spec — the template teaches its own tables
spk new order.cancel --file order.cancel.md
created order.cancel.md
→ Run: specodelic lint order.cancel.md
(--file picks the target path; without it the scaffold lands in the
openspec tree at openspec/specs/<cap>/spec.md, which must already exist.)
# Lint it — findings carry their own rule id + semantics
spk lint order.cancel.md
lint: 1 file(s) linted, 0 finding(s), 0 advisory warning(s)
→ Run: specodelic graph
What a finding looks like. A typical first edit — replacing the
file-qualified wiki-link in the traces_to column with bare text — is a
real mistake, and lint reports it:
spk lint order.cancel.md
lint: 1 file(s) linted, 1 finding(s), 0 advisory warning(s)
order.cancel [linter.single_root_reachable] 4 row(s) unreachable from any intent row — an orphaned island (traces_to/derives_from/guard/from-to/emits): order.cancel.c1, order.cancel.initial, order.cancel.p1, order.cancel.t1
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
That is linter.single_root_reachable hard-failing: every row must reach
its file’s own intent row through own-file typed links, and bare text is
not a wiki-link — so c1 loses its reachability path and drags the state,
transition, and property hanging off it down as one orphaned island.
# Derive the reference graph (fan-in/out, dangling links)
spk graph order.cancel.md
graph: 1 file(s), 5 node(s), 5 edge(s), 0 dangling, 0 typing violation(s), 0 supersedes cycle(s), 0 external boundary(ies)
→ Run: specodelic lint
# Compile to artifacts: TOML + proptest scaffolding + TLA+ module
spk compile order.cancel.md --out-dir specodelic/
compile: 1 compiled, 0 failed
order.cancel.md [order.cancel] → specodelic/order.cancel.toml, specodelic/order.cancel_props.rs, specodelic/order.cancel.tla
→ Run: specodelic verify (consumes the *_props.rs artifacts)
# Model-check the compiled module (embedded stateright backend)
spk model-check order.cancel.md
model-check: 1 checked, 0 failed
order.cancel.md [order.cancel]: exploration_only (1 states explored) → specodelic/order.cancel.check.json
claims: 0 evaluated, 1 unchecked, 0 blocking
unchecked: c1
→ Run: specodelic verify (consumes the *.check.json reports)
In an existing workspace
spk doctor # diagnose the workspace (self-hosting vs consumer mode)
spk init # write the SPECODELIC managed block into AGENTS.md
spk explain # the embedded format guide — ten topics, no repo access needed
spk feedback # send feedback about the tool
Self-hosting
This repo’s own corpus (openspec/specs/ — the single tree,
specodelic-jf1) is linted by the tool itself:
just lint-specs # spk lint openspec/specs
just ci # fmt-check + clippy + tests + release build + openspec gates
Command Reference
Exit codes (uniform across verbs): 0 = a clean run; 1 = the stage
produced findings or a tool-level failure; 2 = invocation error —
nothing was processed (path not found, no spec files matched, unreadable
input). clap argument-parse failures also exit 2. The JSON envelope’s
envelope_kind is "error" and ok is false whenever the run is not
a clean run — findings and failures never ride a success-shaped envelope
(openspec/specs/errors/spec.md envelope_error_kind + exit_code_mapping).
All commands emit through the genesis envelope: JSON for pipes,
human-readable for TTYs (--human / --format json to override).
Failures carry a remediation hint. Hit jargon? The
glossary defines every term a cold read needs.
spk lint <files|dirs>
Use when: you want the gate — before committing, in CI, or wired as
the pre-commit hook (spk hooks install). It is the first stage every
other verb assumes (spk explain lifecycle).
The gate. Runs every rule in the catalog over spec files (recursively;
files without frontmatter are skipped with a warning). Each finding
carries rule_id (linter.<name>) and rule_semantics.
- Spec: linter rules (one file per rule family)
When nothing was found and the repo carries an openspec/ tree (consumer
repos keep their corpus under openspec/changes/*/specs/), the failure
hint names it — spk lint openspec (gh#2.2).
Ingestion is hostile-input hardened (specodelic-suz): only regular files
under a 2 MiB cap are ever read — a FIFO, device file, or oversized file
named *.md is labeled, named, and skipped (never blocking or
unbounded-memory); when nothing else was linted, the labeled notes ride
the failure envelope. A parse error (malformed frontmatter) fails the
stage (exit 1) even when the rest of the batch linted clean — a
corrupted spec is never a silent pass (specodelic-in9: the pre-commit
gate must not let it commit); the error names the offending file and
the file was NOT linted.
Domain packs
A workspace extends the format with domain packs: kind: profile spec
files whose six manifest tables (Sections, Kinds, References, Checkers,
Floors, Requires) declare typed sections, fiber kinds, reference fields,
checkers, and floors. Lint discovers packs by corpus scan (no config file),
activates them when a file’s vocabulary matches, and reports:
linter.pack_shapefindings — a malformed pack manifest is a labeled failure- pack-activation notes (warnings channel, exit 0) and
orphan_vocabularylabeled failures — pack vocabulary used with no pack discovered or declared is never silent: ausesedge naming an absent pack, or a pack-qualified token in a kind/field position whose namespace has no discovered pack, names the candidate pack and both remediations - lifecycle/revision-skew advisories (draft packs activate advisory-first;
a pack pinned to an older base
format_revisionis named)
The five in-repo standard packs live in packs/
(data.lineage, numeric.predicates, empirical.registry,
quant.finance, svc.contracts — the last still draft); a
consuming file opts in by using the pack’s vocabulary, or explicitly
via a uses reference to the pack’s frontmatter id.
- Spec: packs · Guide: using packs
A *.checklist.md file in the linted tree declares an external checklist
(openspec/specs/linter-external_completeness/spec.md — mp1 row 10’s manifest format):
a flat ## Items list plus a ## Mapping table
(item/status/mapped_ids/rationale). The five
linter.external_completeness rules then check every item is explicitly
covered (mapped ids resolve to real constraint/property rows) or
waived (with a stated rationale). This pass is optional and never
gates a lifecycle stage — no checklist declared, nothing checked;
spk compile’s precondition gate deliberately does not run it.
spk graph <files|dirs>
Use when: you need to see who references what — dangling references, fan-in/fan-out, typing violations, supersedes cycles — before making rename, merge, or refactor decisions (the advisors build on it).
Derives the reference graph from every typed reference field: nodes,
edges (including a Transition’s from/to state edges), fan-in/fan-out
per row, dangling references, typing violations (the Reference Typing
table — forbidden edges are reported, never recorded), and supersedes
cycles. The rename/merge/refactor advisors build on it.
Text projections (--format edges|dot|mermaid): edges writes the
raw six-column TSV edge list (source_id, source_kind, field, target_id,
target_kind, annotation — every recorded edge one row, every typing
violation one annotation row); dot and mermaid write byte-stable
Graphviz/Mermaid text projections (visual grammar: solid = state
machine, dashed = guards, bold = emits, dotted = traceability, red
dashed = dangling/violations). Rendering stays external — pipe into
dot -Tsvg or paste into a mermaid renderer; there is no --render.
The projections are the one documented exception to the envelope:
raw text straight to stdout, so awk/jq pipelines consume it without an
envelope parser. --view wiring (requires --format, composes with
all three) selects the wiring view: constraints.satisfies edges
project to file level (consumer file → the producer’s published
contract), self-loops are dropped, and the remaining cross-file
instances aggregate per distinct pair; a corpus with no remaining
edges emits a labeled no_wiring element, never a silently clean
diagram. Derived diagrams of this repo’s own corpus (wiring, states,
trace, schema) live in graph views; spk explain graph-views carries the same taxonomy and one-pipe recipes for your
own corpora. The DOT projection renders externally — including through
gently, the family’s native Graph::Easy port (spk graph <corpus> --format dot | gently --as ascii); rendering stays a pipe, never a
built-in flag.
- Spec: graph
Same discovery hint as lint: with an openspec/ tree present, the
zero-files failure suggests spk graph openspec.
Exit codes (both modes): 0 clean, 1 findings (typing violations — the annotation rows still ride along in projections), 2 invocation error (no spec files on an existing path is clean-empty, but a nonexistent or unreadable path is an error — a typo’d path is not a clean empty corpus).
spk new <id>
Use when: you are starting a fresh spec and want the four-layer skeleton plus the per-layer guidance comments instead of a blank file.
Scaffolds a spec file from the template — per-layer HTML-comment guidance (valid kinds, what a guard may cite, law-case requirements), rendered from the same constants the linter enforces.
spk migrate <file> [--dry-run]
Use when: you have an existing openspec delta to wrap (plain form)
or — with --rekey — a 0.6.0-era dual-format file still carrying
id: spec that must be re-keyed to its real parent-dir-derived id for
the 0.6.0 → 0.7.0 corpus migration (spk lint <tree> finds them).
Wraps an existing openspec delta file in place into the Revision 19
pair: the plain delta stays byte-untouched and the generated sibling
layer.md sidecar carries the specodelic layer — frontmatter (the
parent-directory-derived id with a .layer suffix plus an EARS
scaffold statement) and wired scaffold layers (scaffold_*
placeholder rows — constraint, one-state model, deriving property).
Existing sidecar pieces pass through verbatim; only missing pieces are
inserted. A file that already carries frontmatter is refused, never
rewritten — re-running is safe. The scaffold lints clean as written;
keep it green while you replace the placeholders. Files not named
spec.md get a naming-law warning (openspec requires the delta
filename spec.md). --dry-run prints the resulting content without
writing. --rekey instead rewrites an id: spec dual-format file
(0.6.0-era) to its Revision 18 real id — derived from the parent
directory — re-keying every [[spec.*]] ref; a file already carrying
its real id is an idempotent no-op, and a plain delta or undeducible id
is refused without rewriting. This is the sanctioned 0.6.0 → 0.7.0
corpus migration path: spk lint <tree> finds the files,
spk migrate <file> --rekey per file.
spk compile <files> --out-dir <dir>
Use when: a spec is lint-clean and you want its executable artifacts
— the Constraints TOML, proptest scaffolding, and TLA+ module that
model-check and verify consume.
Compiles a lint-clean spec into three artifacts: <stem>.toml
(Constraints), <stem>_props.rs (proptest scaffolding), <stem>.tla
(TLA+ module). Total or labeled failure — never a silent partial result.
Property and invariant fragments opt in per cell with a **lang:**
marker whose tag is drawn from the closed set {rust, py, ts}; a tag
outside the set (e.g. **go:**) is a labeled extraction failure naming
the tag and the closed set, never silence. **rust:** is the only
executable tag — a **py:** or **ts:** fragment gates compile with a
labeled failure naming its emitter follow-up
(add-py-fragment-emission / add-ts-fragment-emission), never a
silent fall-through to Rust emission.
The tag shape is lowercase-initial ([a-z][a-z0-9_-]* before :**):
ordinary bold prose (**Note:**) stays prose, but a lowercase bold
label in fragment position (**note:**) is tag-shaped and therefore a
labeled extraction failure naming the tag and the closed set — keep
prose labels capitalized, or move them out of fragment position.
Two binding layers, never cross-wired:
- Artifact scaffolds (specodelic). compile emits the fragment
verbatim into the artifact and records it in metadata comments and
the
.check.json/manifest staleness keys. Those comments are staleness records, not contracts (CORR-001’s correction). - Contract bindings (espectacular). ah binds existing tests to
deployed scenarios via contract-TOML entries (
[[tests.cargo]],[[tests.pytest]],[[tests.shell]]flags). Binding is not compilation — the layers have different wall-clock, caching, and toolchain stories (design D3 of the language-neutral binding change).
The claim path between them is kernel.binding: an invariant-kind
Constraint may carry a kernel.binding column, and compile extracts
the cell as an opaque string into the Constraints TOML artifact —
verbatim, never parsed, validated, or interpreted (design D4 of
add-min-expr-kernel: no registry; the name is namespaced against pack
binding columns). An external checker claims a constraint through
contract-TOML flags — binding its own tests to the deployed
scenarios — while specodelic never learns the checker’s language: the
bridge carries the claim, it never absorbs the checker.
The boundary between the tools is tracing vs semantics: espectacular’s
property-untraced finding is bookkeeping and may grow similar
tracing findings, but ah must never grow semantic findings — a
hypothetical property-uncompiled would enforce compile.md’s
semantics from ah, crossing the sibling-tool boundary (read-only over
openspec/, never enforcing against the specs/ corpus).
- Spec: compile
spk model-check <files>
Use when: you want bounded evidence that the compiled model’s
required claims hold (spk explain lifecycle: model_check requires a
complete Model section). Read the outcome honestly —
claim_aggregate_governs (openspec/specs/model_check/spec.md) fixes the priority:
no_counterexample— clean: a nonempty required set all verified over a completed bounded exploration (the native backend earns it when the corpus carries executable claims; it is not TLC-only).counterexample_found— a required claim was refuted; exit 1, the finding names the claim id.timed_out— the depth/state/time bound expired before the exploration completed; exit 0, but not clean.exploration_only— the exploration completed but required claims were unknown, unsupported, or missing — including a prose-only corpus whose required set is empty; exit 0, but not clean.
Only no_counterexample satisfies spk verify’s model gate.
Runs the compiled model through a model-check backend within a stated
bound (--max-depth, --max-states, --timeout-secs). Two backends:
- stateright (default) — embedded BFS exploration over the compiled
model, executing the claims opted into evaluation:
**rust:**predicate fragments compiled into the module run inside the exploration, and**kernel:**claims evaluate over the kernel grammar. Prose guards are never interpreted. - tlc (opt-in,
--backend tlc --tlc-jar <tla2tools.jar>) — the TLA+ TLC reference engine as a JVM subprocess over the compiled module,-depthas the stated bound (--max-stateshas no TLC equivalent and is a labeled error). The JVM binary comes fromPATHorSPK_TLC_JAVA; a missing binary or jar is amissing_checkererror, never a verdict.
Claim gates (model_check.md’s required_claims_classified +
claim_aggregate_governs): a run partitions each parsed file’s
invariant-kind Constraints into a required set — those opted into
evaluation (**rust:** fragment, **kernel:**, or citation) — and an
explicitly unchecked set of prose-only invariants: prose wording, a
constraint’s name, or a deriving Property never implies evaluation, and
advisory/effect/extension-point rows are never required. The outcome is
governed by the required claims’ statuses with fixed priority: any
refuted required claim is counterexample_found naming it; absent
refutation, an exhausted time/state/depth budget is timed_out;
otherwise any unknown, unsupported, or missing required claim is
exploration_only with reasons — and so is an empty required set (a
prose-only corpus has nothing required, so a completed run stays
exploration_only). Only a nonempty required set all verified over a
completed bounded exploration reports no_counterexample — the native
backend earns it when the corpus carries executable claims; it is not
TLC-only. A refuted kernel claim (counterexample_found — CHANGELOG
#116’s failing aggregate outcome) is findings: exit 1 with an error-kind
envelope (ok:false), the payload intact.
Evidence: the run report persists as <stem>.check.json
(claim_schema_version 1) with the consumed module’s SHA-256, the
backend engine + version, canonical claim records (id, evaluator kind,
status, reason where not verified), expected_claim_ids /
unchecked_claim_ids, and a scope_sha256 digest of the parsed
structured content and the consumed compiled artifacts. The digest
binds structured content, not filesystem paths: reordering CLI inputs
or editing prose alone preserves it, while two inputs differing in
invariant content never share one. Verify (and orchestrate’s verify
stage) recompute the digest and the required claim set from the current
inputs and reject stale, foreign, or malformed reports with a rerun
hint naming the model-check command — a stored report is never
rewritten to manufacture evidence. A command’s parsed inputs must
claim unique intent ids: two files with the same id fail
duplicate_corpus_identity with a rename hint before compilation,
evaluation, or report writes (Revision 18: dual-format files carry
real ids and compose like any corpus — the former id: spec
isolated-scope rule retired with id: spec itself). A **kernel:** constraint
cell that breaks the closed grammar is a labeled kernel_grammar
failure before any run — the same validation compile applies — so a
stale compile can never silently demote a malformed kernel claim to
unchecked and report clean. The honest non-failure
outcomes keep exit 0 — timed_out (the bound expired) and
exploration_only (a completed exploration over a corpus with no
executable claims) are real terminal outcomes, not failures
(model_check.md); spk verify’s model gate is what rejects them as
not-clean.
- Spec: model_check
spk verify <files>
Use when: you want the conjunction — executed proptest properties
and a current clean model verdict (no_counterexample). Choose
verify over re-running model-check when a current .check.json
exists and you want the full gate without re-exploring the model —
verify never re-compiles and never re-runs the model checker; choose
model-check when you need fresh bounded exploration evidence itself.
Neither is a substitute for the application’s own test suite
(spk explain lifecycle).
Executes the emitted proptest scaffolding and rejects stale clean
results via the .check.json hash.
Both gates must hold — verified is the conjunction, never a single
green stage:
- Properties gate — the
*_props.rsartifact is staleness-checked against the current spec by block-metadata fingerprint (ids, cases, generators, predicates — hand-translated predicate bodies keep the fingerprint, spec edits change it), then really executed: the artifact is staged into a scratch cargo crate and run viacargo test, so an un-translatedtodo_predicate!fails honestly (properties_failed), never skipped. Failing blocks report proptest’s shrunk minimal input. - Model gate — the
<stem>.check.jsonrun report must be current (itsartifact_sha256matches the on-disk<stem>.tla; a missing module fails closed as stale), itsscope_sha256digest and required claim set must match the current inputs (verify recomputes them — stale, foreign, or malformed reports fail with a rerun hint, never rewritten), and its outcome must beno_counterexample— earned only when every required claim verified over a completed bounded exploration (exploration_onlyandtimed_outare explicitly not clean).
--timeout-secs <N> (default 600; 0 = unbounded) bounds the whole
cargo run on a wall-clock clock: a hanging (likely pathological)
predicate is killed and reported as a labeled properties_timed_out
block — never a silent hang, and never indistinguishable from a test
failure (no block verdicts exist after a kill, so the bound is raised
and the run repeated, not misread as a failing property).
.data.status is verified exactly under the conjunction, else the
first blocking stage (missing_properties_artifact,
stale_properties_artifact, properties_failed,
properties_timed_out, properties_uncompilable, runner_unavailable,
missing_model_run, stale_model_run, model_not_clean). Verify never
re-compiles and never re-runs the model checker. A blocked verify is
findings, never a success-shaped envelope: exit 1 with ok:false /
envelope_kind:"error", the payload (and .data.status) intact.
Scope note: verify’s stored scope digest binds the run’s structured
inputs (contributing files, claim records, artifact SHA-256), not
result statuses — a hand-forged check.json with invented verified
statuses does not yield a clean model gate: verify recomputes the scope
and the required claim set from the current inputs, and any digest,
record-shape, or artifact mismatch is rejected with a rerun hint
(openspec/specs/verify/spec.md evidence_scope_bound; the persisted report is never
rewritten).
The properties gate’s scratch crate lives under the system temp dir
(specodelic-verify/), with a shared target/ dir so proptest
compiles once per machine. A retention policy keeps it bounded:
orphaned per-invocation crate dirs (from killed runs) are pruned after
24 hours, and if the shared target/ dir exceeds 4 GiB it is dropped
whole — the next verify pays one cold proptest rebuild. Set
SPECODELIC_VERIFY_SCRATCH to relocate the scratch base (e.g. off a size-capped tmpfs).
- Spec: verify
spk conform <file> --oracle scenarios.jsonl [--closed-world]
Use when: you have evidence from outside the spec — traces recorded
from a legacy system, a curated case set, or a reference implementation —
and you want each trace classified against what the spec declares and can
actually execute. Every other verb evaluates the spec’s own claims;
conform is the one verb that checks the spec against reality. It is
strictly read-only (help text states it): it never advances the
lifecycle, is never an orchestrate stage, and writes nothing.
Each JSONL trace record (one per line, carrying id, an optional setup
state, and a trace of actions with observations) receives exactly one
verdict from a closed set — never collapsed to pass/fail:
permitted— a declared, executable claim covers the trace and it agreesforbidden— the trace violates a declared executable claim (in any invocation mode), or — only under--closed-world— nothing declared covers itunderspecified— no declared Model element or claim covers the trace (open-world default: a spec gap, never a prohibition)unknown— a covering claim exists but is prose-only (prose is never silently checked)unsupported— the covering claim’s evaluator kind is not executable in this run
The run report carries a conform-local report_schema_version, per-trace
records sorted by scenario id (no timestamps — reruns over identical
inputs are byte-identical), a scope_sha256 binding the parsed spec and
the consumed corpus bytes, and a fixed evidence_scope field in both
the JSON envelope and the --human view — “agreement on the supplied
corpus; not a proof of behavioral equality”. That scope is data, not a
disclaimer: a finite trace corpus agreeing with the spec never proves the
spec describes the system.
Exit codes: 0 = no forbidden/unsupported verdict; 1 = any such
verdict; 2 = invocation error or gate refusal (stale artifacts,
lint-dirty file, malformed corpus) with zero verdict records emitted.
unknown and underspecified surface as counts but never fail the run —
they are spec-gap findings, not refutations.
- Book: conform — external oracle evidence (spec page lands with the corpus archive; see book: verification boundaries)
spk parse <file>
Use when: a downstream tool or script needs one file’s structured IR
as JSON — syntax-only, it succeeds even where lint would fail (chain
spk lint yourself).
Emits the parsed Spec IR for one file as a JSON envelope (specodelic-9rv):
data carries the full structured layer — intent, constraints rows,
states, transitions, properties rows (cells as keyed maps, not generated
text), and structured links. Parse is syntax-only: it succeeds on files
that fail spk lint and embeds no lint status — chain spk lint
yourself (the envelope’s hint names it). Exactly one file per invocation
(no globbing — consumers loop); unparseable or missing input is a labeled
error envelope with a remediation hint and a non-zero exit.
spk doctor
Use when: onboarding a repo or after a format-revision bump — it classifies the workspace and warns (never fails) on corpus revision lag.
Classifies the workspace (self_hosting vs consumer), checks the
local corpus’s Revision N against the binary’s embedded
format_revision — warns, never fails, on lag. A corpus discovery
check names where the specs live (specs/, an openspec/ tree with a
file count, or the discovery rule).
spk explain [topic]
Use when: you are offline or want the canonical wording — the
embedded format guide, no repository access required. Nine topics:
format, ears, kinds, references, lifecycle, lint-rules,
dual-format, packs, graph-views.
spk init, spk feedback
Use when: provisioning an agent-facing repo (init writes the
SPECODELIC managed block) or filing an upstream issue (feedback).
spk init writes/refreshes the SPECODELIC managed block in the
repo’s AGENTS.md (rule catalog + format revision). spk feedback
opens a prefilled feedback channel for the tool.
spk hooks install, spk hooks uninstall
Use when: you want the dual-format lint gate (spk lint openspec)
to run on every commit — marker-guarded and additive in the lefthook
chain, never a commit trap.
spk hooks install wires the dual-format gate (spk lint openspec)
into the repo’s pre-commit chain as a marker-guarded managed block in
lefthook.yml — purely additive (beads’ core.hooksPath → lefthook
chain keeps flowing), idempotent, indentation inferred from the
stage’s own children. Install runs the gate once and reports the
outcome (data.outcome, data.gate_dry_run); a failing gate is a
warning with the failure summary and an escape hint (spk hooks uninstall), never a commit trap. Requires a lefthook config (never
created from scratch) and an openspec/ tree; husky and prek repos
get a labeled refusal with a manual-wiring hint. spk hooks uninstall strips only the managed block.
spk archive-companion <CHANGE_ID>
Use when: archiving an openspec change whose deltas carry the
dual-format layer in sibling layer.md sidecars — the stock openspec archive regenerates deployed specs and strips the layer (spk explain dual-format has the wrap recipe).
spk archive-companion archives an openspec change while preserving
its dual-format layer (specodelic-fzo, GH#7; reworked for Revision 19,
add-delta-layer-sidecar): the companion invokes openspec archive
with default spec application — plain deltas are openspec’s native
merge — then re-attaches the layer to each regenerated deployed spec
from the change’s archived layer.md sidecar. Fail-closed: a delta
without a restorable sidecar is refused BEFORE the destructive
archive, with the spk migrate wrap path as the hint, and the
assembled deployed spec is shape-asserted (no delta headers,
frontmatter + the three tables) before anything lands — the tool never
deploys a stripped spec. Re-running on an already-archived change
skips the openspec invocation and re-attaches to identical deployed
bytes; a change with no spec deltas succeeds with an empty restored
list; --dry-run resolves the plan without invoking openspec or
writing anything. just archive-change delegates to this command.
spk rename <old_id> <new_id> [files|dirs]
Use when: an id changes and every referencing [[link]] (plus
checklist mapped_ids and the filename itself) must follow — computed
and verified in memory before any byte is written, so a failed rename
leaves the repo byte-identical.
The atomic rename (openspec/specs/rename/spec.md): updates the defining row (an
Intent frontmatter id or a table row’s qualified id) and every
referencing [[link]] — including child refs like [[old_id.child]] —
as one transaction. Declared checklists reach into the same law: every
*.checklist.md’s mapped_ids cells are rewritten too (bare and
[[…]] spellings alike), and the verify gate re-runs the
external-completeness resolution over the post-rename corpus, so a
missed cell is a labeled rejection, not a silent dangle. A file whose
Intent id is renamed also gets its filename renamed per the - ⇔ .
naming law. Everything is computed and verified in memory (re-parse +
zero dangling) before any byte is written, so a failed rename leaves
the repo byte-identical: collisions, unknown ids, and verify-gate
rejections are labeled failures with remediation hints.
- Spec: rename
Merge check
Use when: two branch tips both touched spec ids and you want what a textually clean merge cannot see — id collisions, dangling renames, intersecting blast radii — checked before joining the trees.
spk merge --branch <incoming-tree> [--base <ancestor-tree>] [current-tree]
checks what a textually clean 3-way merge cannot see
(merge), over the two branch tips’ spec trees:
- id collisions — an id minted on both branches (absent from the ancestor, or divergently edited on both) is flagged as a collision.
- dangling renames — a rename on one branch plus a fresh reference
to the old name on the other is flagged for replay (
spk renamedoes the rewriting after the trees are joined). - blast radii — each branch’s touched ids get a fan-in/fan-out
closure computed from that branch’s own graph artifact; intersecting
radii require human review (
needs_review) even when the file diffs don’t overlap. - relint gate — the union tree must re-lint clean with zero dangling
references before the merge reports
merged.
Exit 0 = merged, 1 = findings (verdict needs_review or failed),
2 = invalid invocation (missing --branch, empty incoming tree).
- Spec: merge
spk orchestrate <files|dirs> [--out-dir <dir>] [--backend tlc --tlc-jar <jar>]
Use when: you want the whole pipeline — parse → lint → compile → model_check → verify — in the fixed stage order with each gate enforced, halting at the first failing stage, in one invocation.
Drives the full pipeline — parse → lint → compile → model_check → verify — in the order orchestrate fixes, gating
each stage transition exactly as specodelic
specifies and halting at the first stage that fails:
- Lint stage — the six Checker Ownership checkers run in dependency
order: frontmatter first, then referential_integrity → graph_shape →
model_shape (branch A), ears_syntax (branch B), schema_shape (branch
C). A failed checker’s dependents are skipped (never invoked,
never reported as failed) while independent branches still run and
report.
linter.external_completenessruns when a checklist is declared and never gates. - Compile stage — advances past the coverage checker’s verdict only
(
compile_gate_matches_coverage), never a looser or stricter check. - Model_check stage — passes only when every file’s outcome is
no_counterexample;exploration_onlyandtimed_outare honestly not clean, so a corpus whose required claims never all verify halts here — a prose-only corpus (empty required set) always reportsexploration_only, while corpora with executable claims can pass on the native backend (use--backend tlc --tlc-jarfor the TLC reference engine over TLA+ modules). - Verify stage — the conjunction of both gates (see
spk verify).
.data.stages lists every stage with a status (passed / failed /
skipped) and the failed stage’s exact findings; every skip carries its
reason. Rename is never part of a run. Re-running against an unchanged
repo is byte-identical.
- Spec: orchestrate
Refactor advisor
Use when: you are planning a tidy-first split — advisory findings (high unrelated fan-in, changeset subset ownership) that never gate a run.
spk refactor [paths] [--high-fan-in N] [--changeset id1,id2] is the
tidy-first split advisor (refactor): it flags nodes
whose incoming cross-file dependents share no namespace segment below the
root (nearest common ancestor = root — decision of record 2026-09-30),
and — with --changeset — nodes whose owned rows the changeset touches
only as a strict subset without depending on the rest. Every count comes
from spk graph’s derived edges, never an independent markdown walk. The
high-fan-in threshold is per-invocation configuration
(threshold_is_per_repo_setting; default 3). Findings are advisory:
exit 0 whether or not anything is flagged.
- Spec: refactor
Which command when? — routing a task to the pipeline
The full reference is commands.md; this page answers the earlier question — I have X, what do I run? — including which verbs gate on which, and which findings are advisory.
The spine first
The pipeline verbs form a gated chain — each stage refuses to run on a file the earlier stages have not blessed:
parse → lint → compile → model-check → verify
(gates on lint) (consumes (gates on
artifacts) model-check)
spk compile only runs on a file that passed lint and coverage
(openspec/specs/compile/spec.md, precondition_satisfied); spk verify never
re-compiles — it consumes the artifacts a prior compile wrote to the
same out-dir, so a stale compile is a rerun, never an acceptance;
spk orchestrate applies the same stage discipline across a whole
corpus run. spk conform stands outside this chain: it consumes and
classifies, but never gates a lifecycle transition.
Routing table
| What you have / want | Run | Notes |
|---|---|---|
| A new spec | spk new <id>, then spk lint | The scaffold teaches its own tables — quick start |
| A design question, not code | the design canvas, running the loop in it | Specs first; implementation is a rendering |
| “Is this file format-valid?” | spk lint | The gate; findings self-describe — spk explain lint-rules |
| The machine to challenge the design | spk compile, then spk model-check | Bounded exploration; a counterexample is often the design lesson |
| Executable checks / generated test cases | spk compile, then spk verify | Only opted-in invariant claims are checked; prose never is |
| Traces from an existing system, oracle, or curated cases | spk conform <file> --oracle scenarios.jsonl | Open-world default; --closed-world makes an uncovered trace forbidden — conform |
| Diagrams of the corpus | spk graph | Projections and derived views — graph views |
| An id changed | spk rename <old_id> <new_id> | In-memory verified, atomic; rewrites links, filenames, checklists |
| Two branches both touched specs | spk merge --branch <incoming> | Collisions, dangling renames, blast radii — before joining trees |
| A file got too big / high fan-in | spk refactor | Advisory only — it flags split candidates, never edits |
| The whole pipeline over many files | spk orchestrate | Optional --backend tlc --tlc-jar — note TLC cannot execute Rust fragments |
| The workspace feels wrong | spk doctor | Workspace diagnosis with remediation hints |
| An OpenSpec delta to bring into dual format | spk migrate <file> [--dry-run] | Wraps the delta into the dual-format skeleton |
| The dual-format lint gate in pre-commit | spk hooks install | Marker-guarded managed block; never claims core.hooksPath |
| An OpenSpec change to archive, preserving the layer | spk archive-companion <CHANGE_ID> [--dry-run] | Fails closed on a delta lacking the dual-format layer |
| The format explained to an agent, no repo access | spk explain [topic], spk parse <file>, llms.txt | Nine explain topics — For agents |
Two honest maturity notes
spk model-checkis bounded, and honest about it: exhaustive runs reportexploration_only, never a fabricated clean; a backend that cannot execute Rust fragments reportsexploration_only, never a fabricated pass (model_check).spk refactoradvises — it never edits. Its findings are exit-0 regardless (refactor).
More boundaries: verification boundaries — what a green check actually buys you.
Glossary
The jargon this book (and the openspec/specs/ corpus) uses, defined for
a cold read. Definitions are one to three sentences; where a deeper explanation
exists, the entry links to it instead of restating.
The format and its files
- spec / specodelic file — one markdown file: YAML frontmatter plus
fixed tables under a closed schema. Every corpus file in
openspec/specs/is a spec written in the format it describes — see the core format. - frontmatter — the YAML block at the top of a spec file carrying the
file-level identity:
id,kind, andstatement. Checked shape-first bylinter.frontmatter. - Intent — the file-level layer: a real
id, akind, and one EARSstatementsaying what the spec promises. - four layers — the fixed structure of every spec: frontmatter (Intent), Constraints, Model, Properties. The linter never reads prose — everything the toolchain reasons about lives in these structured rows. See architecture.
- EARS — Easy Approach to Requirements Syntax: five fixed sentence
patterns (Ubiquitous, Event-Driven, State-Driven, Unwanted-Behavior,
Optional-Feature) with an imperative
SHALL. A spec’sstatementmust match exactly one pattern; conformance is shape-only grammar checking.spk explain earshas the pattern table. - kind — the type tag on a spec file (
kind: intent,kind: profile) or a table row (invariant,effect,advisory,extension_point,deriving, …), drawn from closed sets: a value outside the set is a labeled finding, never silently accepted. See kinds. - Constraints — the table of the spec’s claims (invariants, effects,
advisories, extension points), with
[[wiki-linked]]traces. Compiles to TOML. - Model — the behavior table: states plus guarded transitions — a
state machine. Compiles to a TLA+ module and is explored by
spk model-check. - Properties — the table of deriving properties, each with a
generator + predicate. Compiles to proptest scaffolding and is
executed by
spk verify. - corpus — the set of spec files a command operates on, usually a
whole tree (
openspec/specs/here; consumer repos pass any dir). Lint discovers packs by scanning it;spk graphderives edges over it. - lifecycle — the ordered stage gates a spec moves through —
parse → lint → compile → model_check → verify— each stage gated on the previous one’s clean run.spk explain lifecyclestates the gates;spk orchestratedrives the whole run. - dogfooding — the repo eating its own dog food: every file in
specs/is a spec in the format specodelic defines, so the tool validates itself with every lint run (see self-hosting round, below). - naming law — a spec file’s frontmatter
idequals its filename stem with-⇔.:linter-graph_shape.md⇔id: linter.graph_shape. - format_revision / Revision N — the format’s version stamp. Each
corpus file states the current
Revision Nin its revision history, and the binary embedsformat_revision(reported byspk --version --json);spk doctorwarns when a corpus lags the binary, never fails. - wiki-linked reference — a
[[id]]link in a table cell pointing at another row or file. Typed reference fields are checked byspk graphandlinter.referential_integrity. - link kinds — the typed reference fields that connect rows:
traces_to/derives_fromtrace provenance (derives_fromis the coverage edge — a Property derives from its Constraint),emits/observesare a typed effect protocol between files (an observation is not a dependency),satisfiesconsumes a publishedextension_pointcontract,supersedesreplaces an older row. The authority is specodelic.md’s Reference Typing table. - pack (domain pack) — a
kind: profilespec file whose manifest tables declare typed sections, fiber kinds, and reference fields for a domain. Discovered by corpus scan (no config file); a consuming file opts in explicitly via ausesreference to the pack’s id. See packs. - fiber kind — a kind declared by a pack. While the pack is active, the base closed kind set widens to base ∪ active-pack-fiber; the same token without an active pack fires a labeled finding.
- external checklist — a
*.checklist.mdfile declaring items that must each be explicitlycovered(mapped to real constraint/property rows) orwaivedwith a rationale; checked bylinter.external_completeness, which never gates a lifecycle stage. - dual-format — one markdown file that parses as both a specodelic
spec and an OpenSpec delta: the specodelic half (frontmatter +
Constraints/Model/Properties) plus the openspec half (
## ADDED Requirementsmirrored verbatim in## Requirements). One file, two parsers, requirement text authored once.spk explain dual-formatcarries the protocol and the migration recipe.
The toolchain and its output
spk— the CLI binary; the crate is namedspecodelic.- finding — one labeled rule violation a checker reports, carrying a
rule_id(linter.<name>) and one-line semantics. Findings fail a stage (exit 1) and never ride a success-shaped envelope. - envelope — the output wrapper every command emits through:
JSON (
ok,envelope_kind,data) for pipes, human-readable for TTYs. Failures carry a remediation hint. See errors. - artifact — a compiled output on disk:
<stem>.toml(Constraints),<stem>_props.rs(proptest scaffolding),<stem>.tla(TLA+ module), and<stem>.check.json(the model-check run report). - scaffold — placeholder rows or sections that a template or
spk migrateinserts so a file lints clean immediately, to be replaced with real content while staying green. - staleness — compiled artifacts carry digests or fingerprints of the
spec they were compiled from.
spk verifyrecomputes them and rejects stale, foreign, or malformed reports with a rerun hint — a stored report is never rewritten to manufacture evidence. scope_sha256— the digest in the<stem>.check.jsonrun report binding the run’s structured content and consumed compiled artifacts. Prose edits and input reordering preserve it; any invariant-content change breaks it — so a report can never be reused as evidence for different inputs. See model_check.
Verification and checking
- lint-clean — the file passes the join of the gating linter
checkers (exit 0 from
spk lint). Rung 1 of the ladder; format-valid, not verified. - guarantee ladder — the five rungs of what a green checkmark actually assures, from lint-clean to matches-oracle (and the better-design rung a counterexample puts you on). A file’s honest status is the highest rung it has evidence for. See verification boundaries.
- coverage — every Constraint row must have at least one Property
deriving from it (
p.derives_from == c), checked bylinter.coverage. Uncovered constraints are invisible to the rest of the pipeline. - proptest — Rust’s property-based-testing library: a generator
produces many random inputs and a predicate asserts a property over
each. The Properties table compiles to proptest scaffolding;
spk verifyexecutes it and reports the shrunk minimal failing input. - TLA+ — a formal specification language for concurrent and
stateful systems. The Model table compiles to a TLA+ module (
.tla). - stateright — the embedded Rust model-checking backend (the
default): breadth-first exhaustive exploration of the compiled model
within stated bounds (
--max-depth,--max-states,--timeout-secs). - TLC — the TLA+ reference model checker, opt-in as a JVM subprocess
(
--backend tlc --tlc-jar <tla2tools.jar>); a missing binary or jar is amissing_checkererror, never a verdict. - pc-automaton — how the compiled Model is interpreted for checking:
a program-counter automaton whose state is an index into the Model’s
states and whose actions are the transitions, explored by stateright
with exactly the semantics the committed
.tlaemission commits to. - kernel — the opt-in
**kernel:**marker placing an expression cell in a closed atomic grammar (equality, comparisons, bounded ∀/∃, reference atomics likeresolves/unique/acyclic) that evaluates three-valued (verified / counterexample / unknown) during model checks. A cell outside the grammar is a labeled failure, never prose silently ignored. See compile. kernel.binding— an opaque string (test node id, filter, or shell payload) on an invariant row, surfaced verbatim in compiled artifacts so external checkers (likeah) can bind their tests to spec claims; the toolchain never parses or interprets it.- claim partition — the split a model-check run makes of each file’s invariant-kind Constraints into a required set (those opted into evaluation: a Rust fragment, a kernel claim, or a citation) and an explicitly unchecked set (prose-only). The run’s outcome is governed by the required set only — prose wording never implies evaluation. See model_check.
- model-check outcomes — the four terminal states of
spk model-check:no_counterexample(every required claim verified over a completed bounded exploration),counterexample_found(a required claim was refuted),timed_out(the bound expired), andexploration_only(completed exploration over a corpus with no executable claims). The last two are honest non-failures —spk verify’s model gate is what rejects them as not clean. - counterexample — a concrete input sequence a model checker found that violates a claim. The ladder’s better-design rung treats it as the design lesson, not a tool failure; the worked example shows the precedent.
- oracle — the executable ground truth a spec’s claims are measured
against. Here that is the compiled property blocks and required
invariant claims that
spk verifyexecutes; rung 4 of the ladder, matches-oracle, means exactly that they ran and passed. - SUT — System Under Test: in classic property-based testing, the
implementation the properties run against. In specodelic the checked
“system” is the spec’s own compiled model and claims, so a
verifiedverdict attests about the specification, never about a deployed implementation — see verification boundaries. - advisory — a Constraint kind that records intent without enforcing: advisory rows can never gate a transition (by typing, not convention) and are never required in a model-check run — but their findings are still labeled, never silent.
- fan-in / fan-out — per-row counts of incoming and outgoing
references in the derived graph. High fan-in flags a row too many
others depend on — a
spk refactorcandidate. - dangling reference — a
[[id]]link pointing at nothing that exists. Detected byspk graphandlinter.referential_integrity; a post-merge corpus with zero dangling references is the relint gate.
Ecosystem and workflow
- self-hosting round — the
specs/corpus is specodelic’s own specification, written in the format it defines, so the tool lints and verifies its own documentation the way it would a user’s spec. The round keeps that corpus lint-clean as the format grows, making the tool’s own spec the first consumer of every new rule. See status. ah/ espectacular — the companion CLI enforcing spec↔test correspondence via scenario-contract TOMLs; read-only overopenspec/— it never enforces against thespecs/corpus.
FAQ — the questions a cold read asks
These are the questions three independent cold reads of this project
(two external AI models in 2026-10, plus a third whose export is still
pending) actually asked while onboarding. Every answer either states an
editorial judgment or links to the authoritative page — answers never
restate machine-generated truth (spk explain topics, lint-rule
semantics, corpus-derived views), because a hand-copied restatement
becomes an unconsulted claim the first time the format revises.
For the design-oriented questions (“why do the layers impose these constraints at all?”), see specs as the design substrate. For “which command when?”, see the decision guide.
Basics
What is specodelic in one sentence?
A specification format and CLI toolchain for AI agents: specs are plain markdown with a closed four-layer schema, checked by a linter that never reads your prose. The introduction and the glossary expand every term in that sentence.
Is this a documentation format or a testing tool?
Neither, exactly — it is a format with a checking pipeline. The structured tables are linted, compiled, model-checked, and verified; the prose stays free for humans. What a green check attests — and what it does not — is the subject of verification boundaries.
Should I use this for my hand-maintained codebase?
Probably not. The format presumes spec-first, agent-authored, regenerable code — its value comes from something mechanical consuming the structured rows (compiled properties, the TLA+ model, the contract gate). Under hand-maintained code the coverage/anchoring discipline is pure tax: every third-party evaluation of the format reached this verdict independently. The positioning section of USAGE.md states the assumption, the honest one-line test (“will anything mechanical consume the structured rows?”), and the cases where an ordinary design doc wins.
Why can’t I write prose and have the linter check it?
Deliberately not. The linter never parses prose, so prose is free to
argue, motivate, and explain. Anything the toolchain reasons about must
live in a structured row — a prose-only invariant stays explicitly
unchecked and is never counted as verified
(verification boundaries, rung 4), and
spk conform reports prose-covered traces as unknown rather than
silently passing them (conform).
Why is the schema closed? Why can’t I add a fifth table?
A closed schema is what makes every other guarantee stateable: row kinds
are closed sets, every [[wiki-link]] is typed, and format evolution is
additive-only through numbered revisions — the theory behind this is in
theory.md, the mechanism in
specodelic.md. Domain-specific structure goes
through domain packs, which extend the format without
a format revision.
Checks and guarantees
What does a green check actually mean?
It depends on which rung of the guarantee ladder you are standing on: lint-clean, well-modeled, checked-within-bounds, matches-oracle, better-design. Each rung presupposes the ones below it, and each is honest about what it does not assure.
Is my spec “done”?
A file’s honest status is the highest rung it has evidence for, never
a single undifferentiated PASS. Lint-clean means format-valid; verified
means the compiled property blocks executed and every opted-in invariant
claim checked — and still not application correctness. Walk the ladder
in verification boundaries.
model-check returned a counterexample — is the tool broken?
Usually the opposite: a refuted claim names the violated invariant, and the counterexample is often the design lesson rather than a tool failure. The worked example records the precedent — the checker found a two-step trace to a state the author swore was unreachable, and the over-claiming row turned out to be the bug.
What can the checkers NOT find?
Known limitations are stated up front, each backed by a runnable command — for example, coverage cannot detect a missing constraint. See verification boundaries — known limitations.
Workflows
Which command should I run when?
The decision guide routes “what do I have, what do
I want” to the right verb — including the honest maturity notes on
advisory verbs like spk refactor.
I have an existing system or oracle but no specodelic implementation — how do I check specs against it?
spk conform is the one verb that brings evidence from outside the
spec: it classifies traces recorded from a legacy system, a curated case
set, or a reference implementation against what the spec declares. The
verdict taxonomy (permitted / forbidden / underspecified /
unknown / unsupported) and the open- vs closed-world choice are in
conform.
How do I get large numbers of scenarios out of a spec?
Two complementary verbs: spk model-check explores the compiled model’s
reachable state space within a bound
(model_check), and spk verify executes the
compiled property blocks whose generators produce concrete cases
(verify). For scenarios coming from outside the
spec, use conform instead.
Can one spec be implemented in different languages or paradigms?
The layers are implementation-neutral by construction — the pattern of
“several interchangeable implementations of one contract” is documented
in USAGE.md §2.3. One honesty note: **rust:**
predicate fragments are compiled and executed natively by
model-check/verify; **py:** and **ts:** fragments are
illustrative and are not executable
(known limitations).
Format mechanics
A lint finding says linter.<something> — what does it mean?
Every finding carries its own rule id and one-line semantics string.
spk explain lint-rules prints the catalog from the same table the
linter emits from, and each rule has a page under Lint Rules in this
book — start at the linter overview.
How do I safely rename an id, or merge two diverged branches?
spk rename rewrites the defining row, every referencing [[link]],
and checklist mapped_ids as one in-memory-verified transaction
(rename); spk merge --branch checks id collisions,
dangling renames, and intersecting blast radii before you join the
trees (merge).
Where are the docs agents read without repo access?
spk explain serves the distilled format guide from inside the binary —
nine topics (format, ears, kinds, references, lifecycle,
lint-rules, dual-format, packs, graph-views) — and llms.txt at
the site root is the machine summary (For agents).
Ecosystem
How does specodelic relate to OpenSpec?
OpenSpec changes are the engineering-truth workflow; specodelic specs
share the same grammar via the dual-format protocol, and ah/
espectacular enforces spec↔test correspondence read-only. The full
division of ownership is in tool relationships.
Can I extend the format for my domain?
Through a domain pack: a kind: profile manifest
declaring typed sections, fiber kinds, and reference fields — discovered
by corpus scan, no config file. The using packs guide
walks the whole loop. Six packs ship in-repo — five standard
packs (data.lineage, numeric.predicates, empirical.registry,
quant.finance, svc.contracts — the last still draft) plus the
first cross-pack ## Requires pilot (bioimage.data).
Specs as the design substrate — a canvas for thinking about the system
The four layers are not documentation sections that later get implemented. They are the place where the system is designed: the contract you reason over while inventing, refining, and validating a design. An implementation — in Rust, Python, whatever paradigm — is a later, optional rendering of a model that already lives in the spec, and it is the rendering that must keep proving itself, never the other way around.
Provenance. This page is distilled from three independent cold-reads of the project by external AI models (2026-10, against v0.7.0); the questions their humans asked seeded the FAQ, and the answers were re-grounded against the current corpus before being written down here. Where this page and the format disagree, the format wins —
spk explain formatis the machine truth these paragraphs compress, and architecture holds for the lint pipeline’s pass structure.
The shift: the spec is the artifact
Most design docs describe a system that lives somewhere else — in a codebase, in someone’s head, in a slide deck. A specodelic corpus makes the design itself the load-bearing artifact:
- It is lintable — the closed schema means every design decision is in a place the toolchain can see.
- It is connected — the
[[wiki-link]]s are typed foreign keys, so claims trace upward to the promise they serve and downward to the behavior that verifies them (architecture). - It is checkable — the pipeline can challenge the design, not just its spelling (the design loop).
Each layer is a question you must answer
The format’s constraints are not arbitrary gatekeeping — each layer forces one design question to have an explicit, wired answer. If you skip the question, lint tells you where.
Intent — what do we promise? One EARS-patterned sentence in the frontmatter. If a feature cannot be stated in one sentence, the feature is not understood yet; the EARS patterns (kinds and statement, EARS) force the promise to be about a system response with defined conditions, not a vague ambition.
Constraints — what must always hold, and what may be observed? Invariants and effects, each a table row tracing to the Intent it serves. This is where you discover whether your invariants are actually invariant, and which observable outcomes are contractual rather than incidental (specodelic.md).
Model — what can happen? A state machine: states, guarded transitions. The guards must cite the Constraints — the checker demands the wiring — so “the model says so” always decomposes into “these named claims say so”. Writing the Model is where you find out that your constraints were silent about the failure paths (linter.model_shape).
Properties — what would convince us? Every constraint needs a deriving property with a generator and a predicate (coverage, linter.coverage). A claim with no way to challenge it is a wish, not a design decision.
The design loop
spk new → spk lint → spk compile → spk model-check → spk verify is usually presented as a CI pipeline. It works equally well as
a thinking loop, run while designing:
- Draft the layers — the scaffold from
spk newteaches its own tables (quick start). - Lint early and often; every finding is self-describing, so each failed pass is one concrete design question (“this transition has no guard — what claim says when it may fire?”).
- Compile and model-check to let the machine explore what you wrote;
a
counterexample_foundverdict is the highest-value design moment, not a failure — see the better-design rung and the worked example, where the checker caught the author over-claiming a rejection state. - Verify what can execute; record what cannot, honestly (verification boundaries).
The loop converges on a corpus where every claim is wired, guarded, and either checked or explicitly unchecked — a design that can be handed to an implementer, human or agent, with an honest account of what has and has not been challenged.
Where the canvas ends
The format can force accountable design, not good design. A wrong but internally coherent model lints clean (that is rung 2 of the guarantee ladder, not a defect); the checkers can only challenge what you wrote, and a missing constraint is the blind spot they cannot see past (known limitations). The honest status of any corpus is the highest rung it has evidence for — this page is the canvas, the ladder is the audit.
Design conversations — the specodelic-design skill
The one-line summary: run design conversations that end in a lint-clean Specodelic spec (a four-layer markdown format — Intent, Constraints, Model, Properties).
The specodelic-design skill is a guided conversation procedure —
Listen → Intent → Constraints → Model → Properties → Assemble — that
ends in a spec the toolchain accepts. It ships inside the spk binary
as a managed skill pack: the protocol prose, a format reference rendered
from the same tables spk explain serves, and the book’s worked example
embedded verbatim.
The procedure’s single authoring surface is the tracked source
skill-packs/specodelic-design/SKILL.md
— this page deliberately does not duplicate it. The worked example is
reservation-order, the same file the
binary embeds, so the skill and the book can never disagree.
Install it into your agent’s skill directory:
$ spk skill install # default: ~/.claude/skills
$ spk skill install --dir ~/.config/agents/skills # any target
Every installed file carries a managed marker; spk doctor reports a
stale install (older binary) with the regenerate command, and a missing
one only as an optional hint.
For the thinking behind the four layers the conversation builds, see the design canvas; for the questions a cold read asks, see the FAQ.
specodelic — the core format
id: specodelic kind: intent statement: “THE specodelic SHALL represent a feature’s intent, constraints, state model, and properties as one parseable markdown file conforming to a fixed schema.”
Specodelic
A feature’s specification is a markdown file with YAML frontmatter, a fixed set of tables, and wiki-link references between them. This file describes that format’s own constraints, lifecycle, and properties — it is a spec written in the format, about the tool that checks it (see USAGE.md §0 for the project/format/tool/subject terminology).
Purpose
A feature’s specification is a markdown file with YAML frontmatter, a fixed set of tables, and wiki-link references between them. This file describes that format’s own constraints, lifecycle, and properties — it is a spec written in the format, about the tool that checks it (see USAGE.md §0 for the project/format/tool/subject terminology).
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| frontmatter_valid | invariant | frontmatter has id, kind, statement; kind ∈ {intent, profile} — profile marks a domain pack (see [[packs]], Revision 14) | [[specodelic]] |
| unique_id | invariant | ∀ row ∈ file: unique(row.id) | [[specodelic]] |
| total_refs | invariant | **kernel:** resolves(traces_to) ∧ resolves(derives_from) ∧ resolves(guard) ∧ resolves(supersedes) ∧ resolves(emits) ∧ resolves(satisfies) ∧ resolves(observes) ∧ resolves(uses) ∧ resolves(from) ∧ resolves(to) — no dangling [[...]] | [[specodelic]] |
| acyclic_traces | invariant | the traces_to/derives_from graph is a DAG | [[specodelic]] |
| guard_required | invariant | ∀ transition: guard != null | [[specodelic]] |
| ears_statement | invariant | intent.statement matches one of the 5 EARS patterns | [[specodelic]] |
| one_capability_per_row | invariant | no row.id joins two intents via "and"/"or" | [[specodelic]] |
| coverage | invariant | **kernel:** ∀ c ∈ Constraint: ∃ p ∈ Property: p.derives_from == c | [[specodelic]] |
| law_requires_cases | invariant | ∀ property where kind == "law": the property's predicate enumerates its required cases in machine-findable **name:** case-label form, and the label set includes identity and associativity — a floor, not a ceiling: a given law may require further named cases (e.g. unit, counit, naturality, triangle identity) in addition, as extra case labels in the same form; a prose mention of a case name is not an enumeration | [[specodelic]] |
| no_boolean_columns | invariant | schema defines no bool column type; states are named variants only | [[specodelic]] |
| append_only_variants | invariant | ∀ id-set S governed by 𝒦 — a variant-table's ids (chiefly States), a Constraint/Property row's own kind value-set, or the Reference Typing table's field set — across revisions r < r': S(r) ⊆ S(r'), grown only under a new Revision heading, never silently; an existing member's typing may narrow only in the same Revision that introduces the kind-split it depends on, and only if the narrowing invalidates nothing valid at r | [[specodelic]] |
| prose_untouched | invariant | parser never inspects rationale/description content | [[specodelic]] |
| model_present | invariant | [[model.state]] and [[model.transition]] sections both exist | [[specodelic]] |
| no_counterexample | invariant | the selected model-check backend (see [[model_check]]) finds no violated invariant | [[specodelic]] |
| properties_pass | invariant | all compiled proptest! blocks pass | [[specodelic]] |
| id_matches_file | invariant | frontmatter.id == expected_id_from_path(path) — a file named spec.md derives its id from its PARENT DIRECTORY name; a file named layer.md derives the parent-directory id plus a .layer suffix (Revision 19); any other file from its own stem; - maps only to the namespace dot, _ is preserved literally within a segment; a bare spec.md with no parent directory falls back to the stem. E.g. linter-graph_shape.md declares id: linter.graph_shape; openspec/specs/ge-cli/spec.md declares id: ge.cli (Revision 18); openspec/changes/<id>/specs/cap-a/layer.md declares id: cap.a.layer (Revision 19) | [[specodelic]] |
| ref_kind_compatible | invariant | ∀ ref: target.kind ∈ allowed_targets(field) — see Reference Typing below | [[specodelic]] |
| single_root_reachable | invariant | ∀ constraint/property/state/transition row in file: the row reaches the file's own intent row through own-file primary linkage (traces_to/derives_from chains resolved within the file) — cross-file typed edges (guard citations of foreign constraints, satisfies, observes) are outbound leaves, never reachability paths | [[specodelic]] |
| every_state_used | invariant | ∀ state: state appears as from or to in ≥ 1 transition | [[specodelic]] |
| every_transition_valid | invariant | **kernel:** ∀ t ∈ Transition: ¬(t.from == ⊥) ∧ ¬(t.to == ⊥) | [[specodelic]] |
| constraint_kind_closed | invariant | ∀ Constraint row: row.kind ∈ {invariant, advisory, effect, extension_point} — see [[kinds.constraint_row_shape]] | [[specodelic]] |
| property_kind_closed | invariant | ∀ Property row: row.kind ∈ {unit, law} — see [[kinds.property_row_shape]] | [[specodelic]] |
| supersedes_acyclic | invariant | **kernel:** acyclic(supersedes) — the supersedes graph alone (Constraint→Constraint, Property→Property) contains no cycle | [[specodelic]] |
The four rows carrying the **kernel:** marker are this file’s
executable slice — the min-expr kernel’s closed atomic grammar
(add-min-expr-kernel), each backed by its own coverage property row.
The remaining data-dependent cells stay informal by recorded decision,
not by omission, and no new advisory class was invented to force them
in: unique_id (an id is not a schema morphism — uniqueness has no
acset-backed atomic), guard_required (¬(t.guard == ⊥) would conflate
prose-guard presence with absence), every_state_used (needs ∨,
outside the closed grammar), acyclic_traces (the union-graph DAG is
not expressible in the closed grammar — per-morphism acyclicity is a
strict weakening), and the kind-set/typing cells (outside the closed
grammar). A cell migrates only when a Revision widens the closed
grammar to carry it — a migration that would need a new advisory class
is a migration bug, not a corpus fact.
Reference Typing
Which kind a reference field may point at — every reference field here
is a typed foreign key.
ref_kind_compatible is this table read as a constraint.
| Field | Appears on | Must resolve to |
|---|---|---|
traces_to | Constraint | Intent |
derives_from | Property | Constraint, or the same Property when the deriving row is itself a law — a law derives from the top-level constraint or law it restates (e.g. the checker-file *_naturality laws deriving from specodelic.rename_naturality); the graph acyclicity invariant (acyclic_traces, which already includes derives_from) is what keeps this same-kind self-reference edge well-formed |
guard | Transition | an invariant Constraint — or a State, the “has reached state X” pattern (Revision 12) — an advisory Constraint can never gate a transition, by typing, not by convention. A guard may be prose; when its gating condition corresponds to a declared constraint, the guard must cite it — a prose-only guard is machine-uninterpreted (the model-check backend reports invariants_checked: []) |
from / to | Transition | State |
supersedes | Constraint, Property | same kind as the row it appears on (Constraint→Constraint, Property→Property) |
emits | State | Constraint, kind == effect only — a state’s declared Moore-machine output; optional and absent on a state with no output of its own |
satisfies | Constraint, any file | Constraint, kind == extension_point only — one-directional, outbound from a consumer’s file to a contract published elsewhere, possibly in a file the consumer’s author never edits or fully reads. The row carrying satisfies still has its own ordinary traces_to pointing at its own file’s Intent, so single_root_reachable needs no carve-out: satisfies is an extra outbound pointer, exactly like guard or emits, not a second reachability edge |
observes | Constraint, any file | Constraint, kind == effect only — a declared observable: an outbound pointer from the row that consumes/monitors the output to the effect row that publishes it, possibly cross-file. The row carrying observes still has its own ordinary traces_to pointing at its own file’s Intent, so single_root_reachable needs no carve-out: observes is an extra outbound pointer, exactly like guard, emits, or satisfies — not a second reachability edge, and it joins no acyclic edge set (an observation claim is not a dependency; mutual cross-file observation is well-formed). Checking “every effect is observed” is linter-observability.md’s contract — advisory, never gating in this Revision |
uses | Constraint, any file | Intent of a kind: profile file (the pack’s frontmatter id) — set-valued (multiple [[...]] targets in one cell, like traces_to); declares a file’s explicit pack enablement, upgradeable from vocabulary-triggered activation ([[packs]]). An outbound leaf joining no reachability path and no acyclic edge set — nothing targets it, single_root_reachable needs no carve-out, and mutual pack use (two files each uses-ing the other’s pack) is well-formed (Revision 14) |
Model
States
draftparsedlintedcompiledmodel_checkedverified
Transitions
| id | from | to | guard |
|---|---|---|---|
| parse | draft | parsed | [[specodelic.frontmatter_valid]] ∧ [[specodelic.id_matches_file]] |
| lint | parsed | linted | ∀ checker ∈ checker_ownership: checker.state == passed — see Checker Ownership below |
| compile | linted | compiled | [[specodelic.coverage]] ∧ [[specodelic.law_requires_cases]] |
| model_check | compiled | model_checked | [[specodelic.model_present]] |
| verify | model_checked | verified | [[specodelic.no_counterexample]] ∧ [[specodelic.properties_pass]] |
Checker Ownership
linted is not one flat conjunction — it is the join point of eight
independently-specified checker files, each its own instance of this same
format (a spec-of-a-linter-check, verifying a spec). This table replaces
the constraint-by-constraint guard with a reference to where each
constraint is actually enforced, and the partial order those checkers run
in.
| Checker file | Owns constraints | Depends on |
|---|---|---|
linter-frontmatter.md | frontmatter_valid, id_matches_file | — (first gate) |
linter-referential_integrity.md | unique_id, total_refs, ref_kind_compatible | linter-frontmatter.md |
linter-graph_shape.md | acyclic_traces, single_root_reachable, supersedes_acyclic | linter-referential_integrity.md |
linter-model_shape.md | guard_required, model_present, every_state_used, every_transition_valid, no_boolean_columns | linter-graph_shape.md |
linter-failure_shape.md | terminal_states_emit, error_labels_unique, guard_negation_total | linter-model_shape.md (the failure-shape walk reads the Model’s states, transitions and emits edges — the model must be proven well-formed first) |
linter-ears_syntax.md | ears_statement, one_capability_per_row | linter-frontmatter.md (parallel to the above branch) |
linter-schema_shape.md | append_only_variants, prose_untouched, constraint_kind_closed, property_kind_closed | linter-frontmatter.md (parallel to the referential/graph/model branch) |
linter-coverage.md | coverage, law_requires_cases | linter-graph_shape.md, linter-model_shape.md |
Two further checker files exist but sit outside this table on purpose
(they are instances of the same format, just never gating):
linter-observability.md — advisory findings on the warnings channel,
exit 0 by its own advisory_severity invariant — and
linter-external_completeness.md — runs only when a repo declares an
external checklist, and its outcome never gates per orchestrate.md’s
external_completeness_never_gating. Their absence from the table is a
membership statement: linted is the join over the eight rows above
only.
linted is an all-must-pass dependency gate
over this diagram: lint fires only when every terminal node reports
passed, not when constraints are checked in some fixed sequence; the two
branches after linter-frontmatter.md (referential→graph→model, and
ears-syntax / schema-shape) run independently of each other.
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| referential_integrity | unit | [[specodelic.total_refs]] | arbitrary_spec_file() | ∀ ref ∈ file: lookup(ref) ≠ None |
| acyclicity | unit | [[specodelic.acyclic_traces]] | arbitrary_spec_repo() | is_dag(traces_to_graph(repo)) |
| rename_naturality | law | [[specodelic.unique_id]] | arbitrary_spec_repo(), arbitrary_id_rename() | identity: rename(I, a, a) == I associativity: rename(rename(I,a,b), b,c) == rename(I,a,c) naturality: compile(rename(I)) == rename(compile(I)) |
| filename_convention | unit | [[specodelic.id_matches_file]] | (id, mismatched_filename) pairs | check(id, filename) == failed |
| typing_enforced | unit | [[specodelic.ref_kind_compatible]] | guard_field_pointing_at_a_property_row() | check(file) == failed |
| own_intent_reachable | unit | [[specodelic.single_root_reachable]] | spec_repo_with(disconnected_constraint_cluster: true) | check(repo) == failed |
| model_well_formed | unit | [[specodelic.every_transition_valid]] | spec_file_with(transition.to_not_in_states: true) | check(file) == failed |
| advisory_cannot_gate | unit | [[specodelic.ref_kind_compatible]] | guard_field_pointing_at_an_advisory_constraint() | check(file) == failed |
| state_guard_citation_accepted | unit | [[specodelic.ref_kind_compatible]] | transition_guard_citing_a_declared_state() | check(file) == passed — the “has reached state X” guard pattern (Revision 12); a State target gates nothing, it records progress — but it is a legal typed citation |
| supersedes_cross_kind_rejected | unit | [[specodelic.ref_kind_compatible]] | constraint_row_with(supersedes: a_property_row_id) | check(file) == failed |
| constraint_derives_from_rejected | unit | [[specodelic.ref_kind_compatible]] | constraint_row_with(derives_from: a_constraint_row_id) | check(file) == failed — the Appears-on column is normative, not descriptive: derives_from appears on Property rows only (decided of record 2026-10-01, specodelic-huf; a constraint is derived FROM by properties, it does not derive), and ref_kind_compatible reads the column source-side the same way it already reads supersedes’ same-kind rule |
| supersedes_cycle_rejected | unit | [[specodelic.supersedes_acyclic]] | spec_repo_with(supersedes_cycle: length ≥ 2) | check(repo) == failed |
| no_stored_superseded_flag | unit | [[specodelic.no_boolean_columns]] | schema_with(explicit is_superseded: bool column) | check(schema) == rejected — supersession status is derived (superseded(x) ⟺ ∃ y. y.supersedes ∋ x), never stored |
| emits_cannot_target_non_effect | unit | [[specodelic.ref_kind_compatible]] | emits_field_pointing_at_an_invariant_constraint() | check(file) == failed — same shape as advisory_cannot_gate, mirrored onto the other direction of the kind-split |
| satisfies_wrong_kind_rejected | unit | [[specodelic.ref_kind_compatible]] | satisfies_field_pointing_at_an_invariant_constraint() | check(file) == failed — same shape as advisory_cannot_gate and emits_cannot_target_non_effect, mirrored onto the third kind-typed reference field |
| extension_point_needs_no_reachability_carveout | unit | [[specodelic.single_root_reachable]] | constraint_row_with(satisfies: an_extension_point_in_another_file, traces_to: own_file_intent) | check(row) == passed — satisfies adds no reachability obligation; the row is reachable the ordinary way, through its own traces_to |
| frontmatter_missing_field_rejected | unit | [[specodelic.frontmatter_valid]] | frontmatter_missing(one of id, kind, statement) | check(file) == failed |
| guardless_transition_rejected | unit | [[specodelic.guard_required]] | transition_row_with(empty guard cell) | check(file) == failed |
| non_ears_statement_rejected | unit | [[specodelic.ears_statement]] | statement_outside_the_five_ears_patterns("the system should maybe work") | check(file) == failed |
| multi_intent_join_rejected | unit | [[specodelic.one_capability_per_row]] | row_id_joined_to_two_intents_via_and() | check(file) == failed |
| coverage_gap_flagged | unit | [[specodelic.coverage]] | spec_file_with(constraint_with_no_deriving_property: true) | lint(file) reports rule == failed |
| law_missing_case_rejected | unit | [[specodelic.law_requires_cases]] | law_property_missing("associativity") | check(file) == failed |
| orphan_state_rejected | unit | [[specodelic.every_state_used]] | state_appearing_in_no_transition() | check(file) == failed |
| missing_model_section_rejected | unit | [[specodelic.model_present]] | spec_file_without_states_or_transitions() | check(file) == failed |
| invariant_violation_blocks_verify | unit | [[specodelic.no_counterexample]] | model_with_a_violated_invariant() | check(model_check) == failed |
| failing_proptest_blocks_verify | unit | [[specodelic.properties_pass]] | compiled_proptest_block_that_fails() | check(verify) == failed |
| prose_does_not_affect_lint | unit | [[specodelic.prose_untouched]] | two_specs_identical_except_rationale_prose() | lint(a) == lint(b) — differing prose never changes a lint result |
| silently_shrunk_id_set_rejected | unit | [[specodelic.append_only_variants]] | (r, r') where a 𝒦-governed id-set member was removed with no Revision heading | check(revisions) == failed |
| constraint_kind_out_of_set_rejected | unit | [[specodelic.constraint_kind_closed]] | constraint_row_with(kind: "made_up") | check(file) == failed |
| property_kind_out_of_set_rejected | unit | [[specodelic.property_kind_closed]] | property_row_with(kind: "audit") | check(file) == failed |
Notes
This file is itself a document instance
of its own schema (Intent, Constraint, State, Transition,
Property). Running this file through its own lint transition should
produce zero findings — if it doesn’t, the format and its own description
of itself disagree, the same way a self-hosting compiler that fails to
compile its own source has a bootstrapping bug rather than an ordinary
bug.
One thing the format cannot check about itself: whether no_boolean_columns
is itself satisfiable without a boolean-shaped escape hatch somewhere in
the tooling (e.g. a linter’s own pass/fail result). That’s outside 𝒦 —
a property of the compiler, not of any instance it compiles — flagged as
Needs Human Review rather than silently assumed.
Revision 2
Decomposing the linter into linter-frontmatter.md,
linter-referential_integrity.md, linter-graph_shape.md, and
linter-model_shape.md surfaced four gaps in Revision 1’s constraint list,
now resolved and folded in above:
id_matches_file— resolved as a structural rule, not left open: a file’s declaredidmust equal its filename with hyphens read as namespace dots (linter-frontmatter.md⇒linter.frontmatter). Applies uniformly to every spec file, since every file’s frontmatterkindisintentregardless of whether it sits at the top of the feature tree or describes one narrow checker.ref_kind_compatible— resolved by writing out𝒦’s morphism typing explicitly (see Reference Typing table) instead of leaving it as an unstated assumption a checker would have had to invent.single_root_reachable— resolved as reachability from the file’s own declared intent specifically, not any intent in the repo, since the looser version would silently accept a cross-feature reference filed under the wrong id.every_state_used/every_transition_valid— folded in as prerequisites formodel_presentto mean anything downstream, since a danglingtofield would otherwise reach the TLA+ generator undetected.
The lint transition’s guard was also restructured: the flat seven-clause
conjunction from Revision 1 was itself an instance of the God-transition
pathology modularity-diagnostician flags in code, just written in a
guard field instead of a function body. It’s replaced with a reference to
the Checker Ownership table, so linted is now legible as a limit over an
explicit dependency diagram rather than an opaque AND of everything at
once — the fix is the same move as extracting a God Object into cohesive
collaborators, applied to a guard expression instead of a class.
Since this revision, linter-ears_syntax.md, linter-schema_shape.md, and
linter-coverage.md were all written; none surfaced further gaps in the
constraint list above (see each file’s own Notes section).
Revision 3
A Rule-of-5 review (rule-of-5-universal) of the full corpus found one
CRITICAL correctness bug in Revision 2’s id_matches_file constraint, plus
documentation drift left over from before the last three checker files
existed. Both fixed here:
id_matches_filewas not invertible. The original rule (“hyphens read as namespace dots”) assumed a filename’s hyphens could be unambiguously mapped back to dots, but hyphens were also standing in for underscores within a segment (linter-graph-shape.mdfor idlinter.graph_shape).dots_to_dashes⁻¹doesn’t exist as a function when the forward map isn’t injective. Fixed by reserving-exclusively for the namespace dot and preserving_literally in filenames; five files were renamed to match:linter-referential_integrity.md,linter-graph_shape.md,linter-model_shape.md,linter-ears_syntax.md,linter-schema_shape.md. Without this fix, the linter as specified would have rejected five of its own seven checker files — a bootstrapping failure the format’s own Notes explicitly claims should never happen.
Revision 4
Writing kinds.md — the standalone definition of 𝒦’s five objects,
STATUS.md §4’s P0 item — surfaced one gap: nothing in Revision 3’s list
required a Constraint row’s or Property row’s own kind column to come
from a closed set. frontmatter_valid only ever checked the file-level
kind == "intent"; a Property row with kind: "made_up" had no
constraint anywhere rejecting it. Added constraint_kind_closed
(kind ∈ {invariant}) and property_kind_closed (kind ∈ {unit, law}),
each tracing to the corresponding row-shape constraint now specified in
kinds.md. Enforcement landed the same session: linter-schema_shape.md
now owns both (see its updated Checker Ownership row above).
- Stale “not yet written” notes removed from the Checker Ownership
table and this section, left over from before
linter-schema_shape.mdandlinter-coverage.mdexisted.
Revision 5
Checking this format against ten features a real polyglot tool ecosystem needed (non-gating quality signals, confidence-scored checks, scenario supersession, cross-language adapter contracts, self-reported-vs-verified claims, versioned shared dependencies, an ad hoc “needs human review” escape hatch, feedback/regression loops, priority-ordered fallback installs, and multi-target compilation) found that seven of the ten were already representable with the mechanisms above and needed no schema change — writing them down for the record, so the next reviewer doesn’t re-open them:
- Non-gating quality signals and “needs human review” as structure,
not prose turned out to be the same gap, and it closes by finally
exercising
kind_field_extensibleonConstraint.kindthe way it was already exercised onProperty.kind: addingadvisoryabove. An advisory Constraint carries no obligation on any transition, for free, onceguard’s Reference Typing row is narrowed tokind == invariant(see below) — non-gating by typing, not by the checker remembering to ignore it. - Confidence-scored / statistical properties need no new mechanism:
a Property’s
predicateis already an unparsed boolean-valued expression (prose_untouchedonly forbids the parser reading rationale prose, not expr/predicate fields). A threshold likeprecision(check(corpus)) ≥ 0.95is an ordinary unit Property with a fixed-corpus generator; the scoring algorithm behind the threshold is the feature’s own concern, not the format’s. - Feedback/regression loops and priority-ordered fallback steps
need no new mechanism either: States/Transitions is an open table, not
a closed enum, so
revise: verified → draftor a guard expressing “try A, else B, else C” via mutually-exclusive guard clauses both fit the existing Model section without touching𝒦. - Cross-language adapter/plugin contracts don’t need a sixth object
in
𝒦— they need their own file, the same waycompile.md,model_check.md, andverify.mdalready got their own files instead of growingspecodelic.md’s Model section. - Self-reported-vs-verified claims and versioned shared dependencies both turned out to be downstream of the one real structural gap below, once supersession makes a row’s provenance and lineage traceable instead of overwritten.
One gap needed an actual addition, generalized rather than special-cased:
supersedes.append_only_variantsalready guarantees a superseded row is never deleted — but nothing let a newer row declare what it replaces, so retirement had to be said in prose or not at all. Rather than add a bespoke “supersession” concept,supersedesis specified as one more row in the Reference Typing table — structurally identical totraces_to/derives_from, just self-typed (Constraint→ Constraint, Property→Property). Its cycle-freedom is checked as its ownsupersedes_acyclicinvariant rather than folded intoacyclic_traces, for the same reason Revision 2 broke thelintguard into independent checkers instead of one larger conjunction: lineage and intent-tracing are different graphs answering different questions, and merging them would recreate the God-invariant shape Revision 2 removed. Whether a row is “current” is never stored — it’s computed (superseded(x) ⟺ ∃ y. y.supersedes ∋ x) — so no boolean column is needed andno_boolean_columnscovers it with no changes of its own.reference_field_extensible. Addingsupersedesexposed that the Reference Typing table itself had no stated growth path, unlike thekindcolumns (kind_field_extensible, inkinds.md). The same append-only-by-Revision discipline now applies to the table as a whole, not just to the value sets inside it — the general rule this codifies is that a Revision’s job is to widen𝒦’s typing, in either dimension, never to leave a second unwritten one beside the first.
Enforcement cost was uneven, and it’s worth recording why: linter- referential_integrity.md’s ref_kind_compatible needed zero code
changes for either supersedes or the narrowed guard row, because it
was already written to read allowed_targets(field) from this table
rather than hardcoding it per field — the Reference Typing table is its
only input. linter-schema_shape.md and linter-graph_shape.md were not
so lucky: reference_field_extensible and supersedes_acyclic needed
genuinely new local checks (an append-only-table diff, and a second small
DAG check kept independent of acyclic rather than unioned into it — see
each file’s own Notes), which are now implemented in both files rather
than left pending.
Self-correction: reference_field_extensible’s first draft in this
Revision forbade retyping an existing reference field at all — which its
own paragraph above immediately violates, by narrowing guard from any
Constraint to kind == invariant only. The rule above is the corrected
version: a narrowing is permitted in the same Revision that introduces
the kind-split it depends on, and only when it invalidates nothing that
was previously valid (true here, since no advisory Constraint — and
therefore no guard pointing at one — could have existed before this
Revision introduced the kind itself).
Revision 6
Checked this format against a second, unrelated domain — a pure-Python,
lazy, pipe-composed data library whose operations are grounded in category
theory (an adjoint triple, a closed expression grammar, Moore-machine
reducers). Unlike Revision 5, most of what looked missing here turned out
to already be one Revision-5 pattern applied a second time, or an existing
mechanism nobody had pointed at yet. Following this file’s and
AGENTS.md’s own instruction to fold gaps back the same session rather
than let them accumulate, both are done here, and the general workflow
is now written up on its own in USAGE.md — read that file before
writing a domain spec (one describing a piece of software, not this
repo’s own linter), since everything below assumes its vocabulary
(sealed enumeration, staged lifecycle, conformance-by-limit).
Simplified, not extended — three rules become one. kind_field_extensible
(kinds.md), reference_field_extensible (Revision 5, above), and the old
narrow wording of append_only_variants were the same rule discovered
three times: an id-set governed by 𝒦 only grows, and only under a new
Revision heading. append_only_variants above now states that once, for
all three cases (variant-table ids, a kind column’s value-set, and the
Reference Typing table’s field set); reference_field_extensible is
retired as a separate row, and kinds.md’s kind_field_extensible is
reworded to cite this constraint rather than restate its logic (see that
file’s own Revision). This is the same move Revision 2 made collapsing a
flat lint guard into the Checker Ownership table: one stated rule with
several instances, not several hand-maintained copies that can drift out
of sync with each other — which they had (Revision 5’s version of this
rule already didn’t match kind_field_extensible’s wording word-for-word,
despite saying the same thing).
One real addition: emits, giving State an output. A Moore machine
is states, transitions, and an output function of state — the third part
had nowhere to live, since kinds.md’s state_row_shape fixed State to
exactly {id}. Rather than add a sixth 𝒦 object, this reuses exactly
the move Revision 5 validated on guard: kind-split Constraint again
({invariant, advisory, effect} — one more kind_field_extensible-governed
widening, now expressed via append_only_variants), then add one row to
Reference Typing: emits | State | Constraint, kind == effect only. The
new kind lives on Constraint, not on State or Transition, because a
State’s output is a claim about a value, the same shape every other
Constraint already makes — it needs no new row shape, only a new place a
State may point at one. emits is optional: a state with no declared
output is an ordinary automaton state, not a Moore state, and both are
valid. linter-referential_integrity.md’s ref_kind_compatible needed
zero code changes for this either, for the same reason Revision 5
noted for supersedes — except its wording needed a fix genuinely found
this session: it named traces_to/derives_from/guard explicitly,
which was already stale (it didn’t mention supersedes, added last
Revision) and would have gone stale again with emits. Reworded there to
read the Reference Typing table generically, matching what the check
already does in practice.
Confirmed representable with no schema change, written up in USAGE.md
as patterns rather than repeated here: a spec author’s own closed,
exhaustively-matched enumeration (a “sealed AST”) is a Constraints-table
id-set under the same generalized append_only_variants rule — it belongs
there, not in Model/States, because it’s a closed value shape, not a
lifecycle; a build-then-execute staging (laziness) is two ordinary
guarded states, the same shape compile.md’s extracting → emitting
already uses; conformance across multiple interchangeable implementations
is the Checker Ownership pattern read backwards — N files each owning a
slice of one shared Intent, joined as a limit — already fully built and
running on this repo, just not yet named as a reusable pattern anywhere;
and law_requires_cases’s two required cases were already a stated floor,
now said explicitly in its own row above, so an adjunction or functor law
needing unit/counit/naturality/triangle-identity cases adds them to its
own property rather than waiting on a schema change.
Deliberately left alone: expr/generator/predicate remain
unparsed strings. Revision 5 already noted this in passing for confidence
thresholds; it’s restated here as a considered boundary, not an oversight
— turning categorical laws into something a checker executes rather than
a human reads is a different tool (a theorem prover), not a wider lint
schema, and is out of scope for the same reason no_prose_field_parsed’s
audit-vs-generator mismatch is flagged Needs Human Review rather than
forced into a shape it doesn’t fit.
Revision 7
Checked this format against a third kind of gap: Julia-style multiple
dispatch and, more sharply, Clojure-style protocols/multimethods, where a
consumer extends a generic function or interface for their own type
from a file the original author never edits and may never read — the
expression-problem pattern, and the concrete mechanism behind the Open/
Closed Principle (closed for modification, open for extension). Every
existing pattern in USAGE.md §2 assumes one file eventually enumerates
its whole id-set (append_only_variants grows a set inside the file that
owns it); an open-world conformer breaks that assumption structurally,
not by omission — the same honest boundedness model_check.md already
admits for unbounded recursive structures, here across files instead of
depth.
Scope deliberately narrowed before specifying anything: an earlier
draft of this Revision added a two-way implements edge and a
single_root_reachable carve-out so the origin file could enumerate its
own conformers. Discarded — verifying unknown, not-yet-written code was
never a goal this format’s own boundedness rules could honestly support,
and every previous Revision that tried to overreach past what’s checkable
(no_prose_field_parsed’s parser-audit case, the categorical-law
theorem-proving boundary) ended up walked back to a narrower, honest
claim. The kept scope: this format publishes a contract an extension
must satisfy, typed and lint-checkable as a well-formed statement; it
never claims to verify who or what actually implements it. That’s a
communication artifact, not an enforcement one — the same distinction
no_boolean_columns’s Notes already draws between what 𝒦 can check
about itself and what it can’t.
One new Constraint kind, one new one-directional reference field, both minimal:
extension_point—constraint_kind_closedwidens from{invariant, advisory, effect}to{invariant, advisory, effect, extension_point}, one morekind_field_extensible-governed widening (kinds.mdgets the matching Revision). Anextension_pointrow’sexprstates the method signature(s) and behavioral expectation a conformer must provide — e.g.∀ T conforming: symmetry(T)::SymmetryTag, canonical_order(T)::Vector{Int}— the same field every other Constraint kind already has, just aimed outward at code the origin file’s author doesn’t write.satisfies— one new row in the Reference Typing table: a Constraint on any file may pointsatisfiesat anextension_pointConstraint on any other file, declaring “this row is my conformance to that published contract.” Structurally identical totraces_to(a typed, outbound, one-directional pointer), deliberately not bidirectional likesupersedes— the origin file gets no inbound edge and no obligation to know its conformers exist, by construction rather than by convention.
Two things this addition needs nothing new for, checked explicitly so
the next reviewer doesn’t re-open them (AGENTS.md #3a):
guardalready excludesextension_pointfrom gating.guard’s Reference Typing row already readsConstraint, kind == invariant only—advisory,effect, and nowextension_pointare all excluded by the same existing typing fact, not by a new “cannot gate” invariant. Exactly theadvisory_cannot_gateprecedentAGENTS.md#3a calls out: check the type system before adding a restatement.single_root_reachableneeds no carve-out. A Constraint row carryingsatisfiesis still an ordinary row of its own file’s Constraints table, with its own ordinarytraces_topointing at that file’s own Intent —satisfiesis an extra outbound pointer sitting besidetraces_toon the same row, exactly the wayguardandemitsalready coexist with it without affecting reachability. No forest, no second root, no change to the existing invariant at all — this is the concrete payoff of narrowing scope before specifying: the two-wayimplementsdraft would have needed a carve-out; the one-waysatisfiesdoesn’t.linter-referential_integrity.mdneeds zero code changes, the same claim Revision 5 made forsupersedesand Revision 6 made foremits, for the same reason:ref_kind_compatiblealready readsallowed_targets(field)generically from the Reference Typing table rather than naming fields by hand (Revision 6 fixed the one place its wording still hadn’t caught up to that). A fourth typed field is not a fourth special case there.
What this deliberately still doesn’t do: there is no way to ask
“do all current and future conformers satisfy this contract” as a single
checkable fact, and there won’t be one — that question is unbounded across
files and time the same way an arbitrarily-nested expression tree is
unbounded across depth (USAGE.md §4), and forcing a false sense of
completeness onto it would be a worse outcome than leaving it explicitly
unclaimed. What is checkable, per conformer, is entirely ordinary:
ref_kind_compatible confirms a consumer’s satisfies row resolves to a
real extension_point row of the right kind; whether that consumer’s own
expr is actually faithful to the contract it names is the same kind of
human/test-suite judgment call every other invariant row already relies
on — satisfies adds traceability of intent to conform, not proof of
conformance.
Note on an existing, narrower use of “Open/Closed” in this repo:
STATUS.md §2 already lists “Switch/case rigidity (OCP violation)” as
solved by append-only variant tables — that’s OCP within one file’s own
governed id-set (a new case is an insert, not an edit to an existing
match). This Revision’s extension_point/satisfies is OCP in the
other, harder sense — extension by a party who never touches the origin
file at all. Both are real and both now have a home; they shouldn’t be
read as the same mechanism solving the same problem twice, which is
exactly the kind of same-token-different-guarantee confusion AGENTS.md
#6 already tracks (CLAR-001 through CLAR-003) — flagged here rather
than silently left for a future reviewer to notice on their own.
Revision 8
The model-checking story became backend-pluggable, and Alloy left the
corpus language. no_counterexample’s expr named “TLC/Alloy” — a tool
coupling that model_check.md had already outgrown: its contract
(bounded exploration, minimal counterexample, invariant named by id) is
engine-neutral, so naming engines in the core invariant list pinned an
implementation detail into the format itself.
no_counterexamplenow says “the selected model-check backend”, andmodel_check.md’s Notes specify the backends: stateright (default, an embedded Rust model-checking crate — BFS exploration givescounterexample_is_minimalby construction;target_max_depth/timeoutgive the stated bound andtimed_out) and TLC (opt-in, JVM subprocess, the reference engine for the.tlamodule).- Alloy was dropped everywhere (
compile.md,STATUS.md,USAGE.md,linter-model_shape.md): with a native default and a reference TLA+ engine it had no remaining role, and a SAT-based engine returns an instance, not a minimal trace — which would fightcounterexample_is_minimalrather than satisfy it. model_check.backend_identifiedis new: a run report names the engine and version that produced it, so reports from different backends are attributable and comparable.
No variant table changed — this Revision rewords one constraint’s expr
and adds one constraint to a non-core file, so append_only_variants is
satisfied trivially. The .tla module remains an unconditional compile
artifact regardless of backend (see compile.md’s Notes).
Revision 9
Declared outputs became observable claims. Revision 7 added
extension_point/satisfies (a published contract and its outbound
consumers) and Revision 6 added emits (a state’s Moore-machine
output) — but nothing distinguished an effect that something must
observe from one nobody watches: a spec could declare outputs that
nothing observes and lint clean.
- The Reference Typing table gains
observes: Constraint, any file → Constraint, kind ==effectonly — mirroring thesatisfiesprecedent end to end (one optional column, one typed row,ref_kind_compatibleneeds zero special cases). A row carryingobserveskeeps its own ordinarytraces_to, sosingle_root_reachableneeds no carve-out. observesjoins no acyclic edge set:linter-graph_shape.md’sacyclicunion is closed and unchanged — an observation claim is not a dependency, so mutual cross-file observation is well-formed.- Checking “every effect is observed” is
linter-observability.md’s contract: advisory severity (warnings channel, exit 0), never gating a lifecycle transition in this Revision; gating is decided after dogfooding produces real friction evidence. kinds.mdRevision 5 states the general rule this row relies on: optional typed reference columns may appear besidetraces_to(satisfies,observes), superseding the “exactly four fields” reading ofconstraint_row_shape(HITL ticketspecodelic-mp1row 9, resolved by that Revision).
No variant table changed — one new Reference Typing row, grown under
this heading per append_only_variants.
Revision 10
Two long-standing “Needs Human Review” questions were decided of record
(HITL ticket specodelic-mp1 rows 7 and 8, user-approved 2026-09-30,
advised by typed Jev evaluations, jev-1.13.0):
- Guard typing — hybrid (row 7). A transition guard may be prose,
but when its gating condition corresponds to a declared constraint,
the guard must cite it. The Reference Typing table’s
guardrow carries the rule; the target typing is unchanged (Constraint, kind ==invariantonly). This is the only policy consistent with both halves of the format’s own philosophy:guardis a typed foreign key (machine-readable edges exist where constraints exist), yetprose_untouchedand the model-check reality (prose guards are uninterpreted —invariants_checked: []) mean banning prose outright buys nothing. The bridge pattern is precedented in the corpus: the hooks capability spec’s guards were bridged by appending typed citations to their prose. Banning prose would churn ~48 corpus rows into invented one-off constraint rows; leaving prose uncited strands half the corpus outside the typed graph. - Reachability — tiered own-file intent (row 8).
single_root_reachableis reworded: a row reaches the file’s OWN intent through own-file primary linkage (traces_to/derives_fromchains resolved within the file); cross-file typed edges (guardcitations of foreign constraints,satisfies,observes) are outbound leaves, never reachability paths — exactly the wording the table already givessatisfies/observes(“not a second reachability edge”). Enforcement is tiered: rows whose only connection is cross-file are advisory-flagged first; hard enforcement flips after corpus reconciliation (specodelic-cxq) anchors them. The strict own-file reading the old expr implied would fail ~150 of 375 corpus rows, much of it legitimate cross-file structure; the looser some-intent reading the shipped checker implemented silently accepts a cross-feature reference filed under the wrong id. Tiering keeps the wrong-file catch without a churn cliff.
No variant table changed — two existing rows reworded under this
heading per append_only_variants; the guard field’s target set is
narrowed not at all and the kind sets are untouched.
Revision 11
derives_from gains the same-kind law edge (Reference Typing row
reworded; specodelic-cxq corpus reconciliation, 2026-09-30). A
Property row whose own kind is law may point derives_from at a
Constraint or at the same-kind Property — the top-level law it
restates — because the checker files’ *_naturality laws instantiate
exactly this file’s rename_naturality law, and banning that edge
forced the corpus into either illegal targets or prose-only linkage.
Well-formedness of the same-kind edge is not a new invariant:
acyclic_traces already includes derives_from in its edge set, so a
derivational cycle among laws is a lint finding, and coverage’s
no_orphan_property still requires every derives_from target to
resolve. A unit Property deriving from a Property remains a violation.
No variant table changed — one existing Reference Typing row reworded
under this heading per append_only_variants; the target set WIDENS
only for law sources, so nothing valid at Revision 10 is invalidated.
Revision 12
guard’s target set widens to admit a State (Reference Typing row
reworded; specodelic-tik reconciliation of the shipped checker,
2026-09-30). Revision 10’s prose said “the target typing is unchanged
(Constraint, kind == invariant only)”, but the reconciliation commit
that shipped the hybrid guard policy (specodelic-cxq) also encoded
the “has reached state X” pattern in graph.rs’s typing_violation
and in the independent reference oracle: a transition guard may cite a
State — the pattern graph.md’s own extract transition
([[specodelic.parsed]]), refactor.md’s analyze, and
orchestrate.md’s start_lint all use. The table text never followed
the checker; this Revision makes the table the authority again. A
State citation records progress, it gates nothing — but it is a legal
typed citation, and advisory Constraints still can never gate a
transition, by typing, not by convention. The stale side-effects are
reconciled in the same stroke: graph.md’s
wrongly_typed_edge_rejected fixture/note (which contradicted
graph.md’s own Model extract), USAGE.md’s §2.6 typing remark, and
the embedded guide’s REFERENCE_TYPING row.
One existing Reference Typing row reworded and one deriving Property
added under this heading per append_only_variants; the target set
WIDENS only, so nothing valid at Revision 11 is invalidated — a
unit Property (state_guard_citation_accepted) now pins the
acceptance beside the existing rejection rows.
Revision 13
Law-row named cases become machine-checkable (specodelic-9qw,
2026-10-01). The law_requires_cases row is reworded: the cases a law
requires are enumerated as **name:** case labels in the property’s
own predicate — the form compile’s required_law_cases has parsed
since the compile functor shipped — instead of free prose whose mention
of a case name the linter could not tell apart from a declaration. The
identity and associativity floor stands, unchanged, as a minimum; extra
named cases (commutativity, idempotence, naturality, unit/counit,
triangle identity, …) remain first-class — each becomes its own
checkable declaration and its own proptest block. The floor is now
lint-enforced (linter.law_cases, executing linter-coverage.md‘s
every_law_has_cases) ahead of compile’s precondition gate, so
required_law_cases’ unlabeled fallback survives only as
defense-in-depth for input that skipped the gate.
One Constraint row reworded under this heading per
append_only_variants; the case set WIDENS only (labels beyond the
floor stay legal), so nothing valid at Revision 12 is invalidated — a
law predicate whose cases lived only in prose now fails lint instead
of silently passing.
Revision 14
The extension mechanism becomes first-class: domain packs
(specodelic-dcx, add-domain-pack-mechanism, 2026-10-02). Independent
vendors converged on the same extension moves — optional sections,
per-kind case-label floors, new outbound reference fields — but nothing
in the format named an extension, so pack vocabulary collided in the
global closed kind sets. A pack is now a declared, discoverable, versioned
artifact in the corpus itself; its full contract is [[packs]].
frontmatter_validreworded — the frontmatter-kind closed set grows once, narrowly:kind ∈ {intent, profile}.profilemarks a pack file; every other rule for profile files is pack-fiber-relative (pack-shape checking, not base-set growth). Nothing valid at Revision 13 is invalidated — a pure widening.- Reference Typing grows by
uses— Constraint, any file → Intent of akind: profilefile, set-valued, outbound leaf. This is the one new generating morphism the mechanism requires;satisfiesis NOT overloaded (conforming to a published extension_point contract and enabling a vocabulary fiber are different relations). - Base closed sets freeze by policy —
constraint_kind_closed,property_kind_closed, and the Reference Typing field set stop growing except by true format Revisions like this one; pack vocabulary is per-pack (namespaced, fiber-relative — the Grothendieck construction one level up), never global. - Checker Ownership stays closed: the pack manifest’s structural
rule (
pack_shape, owned by [[packs]]) sits outside the eight-row gating table on thelinter-observabilityprecedent — it is conditional on pack discovery and advisory-first by its own contract, and a workspace with nokind: profilefiles lints byte-identically to Revision 13.
One Constraint row reworded and one Reference Typing row added under this
heading per append_only_variants; every widening, no narrowing — the
profile kind is opt-in per file and uses is optional per row.
Revision 15
Executable predicate fragments (specodelic-rjb, 2026-10-03 — the
specodelic-mp1 row 7 option-C decision of record). The verified gate was
unreachable by construction: every compiled predicate was a
todo_predicate! placeholder that panics at execution, and the native
model-check backend executed zero invariant predicates, so verify could
never honestly report verified for any file. The way out is opt-in
verbatim Rust:
- **The
**rust:** marker.** A Property'spredicatecell or an invariant-kind Constraint'sexprcell may carry exactly one**rust:marker; the cell text after it is the fragment — a Rust boolean expression emitted verbatim (compile.md'spredicate_fragment_opt_in,invariant_fragment_opt_in). Property fragments bind the block's generated valuesv0…(one per named generator, each aString`); invariant fragments bind the current state’s id. A cell without the marker compiles and checks exactly as before — pure widening. - Verbatim, not interpreted. The fragment is user Rust, compiled by
the Rust compiler, never re-parsed into a mini-language. Property
fragments land in the proptest! artifact (verify’s existing scratch
crate executes them); invariant fragments execute through a
dependency-free scratch-crate BFS run whose engine attribution is
native-bfs(model_check.md’sexecutable_invariants_execute) — exploration_only remains the honest outcome wherever no fragment is executable, and TLC, which cannot execute Rust, never claims clean. - Rejections are labeled, never silent. A fragment on a law-kind
Property (each case needs its own body), a non-invariant Constraint, or
a Transition guard (the program-counter model has no data binding a
guard could constrain — deferred of record) fails extraction with a
label naming the rule (compile.md’s
fragment_law_rejected,fragment_guard_rejected). - Hygiene is defense-in-depth, not a sandbox (compile.md’s
fragment_hygiene): fragments run on the invoking user’s machine with the invoking user’s privileges, exactly like every other compiled artifact; the banned-token list (unsafe,extern,include!,std::fs,std::process,std::net,std::env,asm!,Command) rejects the escape hatches a table cell should never legitimately need. A panicking fragment is a violation, never a pass (model_check.md’sinvariant_totality).
No row of this file’s own tables changes in this Revision — the growth
lives entirely in compile.md, model_check.md, and verify.md
(fragments_reach_verified pins the now-reachable verdict). The only
reworded rows sit in those files; nothing valid at Revision 14 is
invalidated, and the proptest scaffolding’s element type simplifies from
the GenVal wrapper to plain String in the same stroke.
Revision 16 — 2026-10-04
Closed language-tag fragments (specodelic-lf3, 2026-10-04 — design
of record in openspec/changes/add-language-neutral-property-binding).
Revision 15’s opt-in made predicate cells executable but Rust-bound: the
**rust:** marker names the one supported language, and a cell wanting
executable Python or TypeScript fragments has no honest form. This
Revision widens the marker to a closed tag set — the format grammar
grows once, per-language emission machinery stays out (it lives with
espectacular’s contract binding and per-language emitters, none built
here):
- The tag set is closed —
{rust, py, ts}, the same closed-set discipline asproperty_kind_closed(the grammar owns the enumeration, a cell cannot carry a tag it doesn’t declare). The fragment-position rule and the non-empty-fragment requirement carry over verbatim per tag (compile.md’sfragment_language_closed, wideningpredicate_fragment_opt_in/invariant_fragment_opt_in’s grammar);**rust:**remains the fully specified case, byte-identical semantics. - Unknown tags fail labeled, never silent —
**go:**in fragment position is a labeled extraction failure naming the unknown tag and the closed set (sd1’s discipline: no silently ignored markers anywhere in the grammar;compile.md’sunknown_tag_rejected). - Rust widening is pure — every cell that extracted under Revision
15’s grammar extracts byte-identical after it (
compile.md’srust_back_compat); a cell without any marker compiles exactly as before. Nothing valid at Revision 15 is invalidated. - No emitter, no artifact — a
**py:**or**ts:**fragment fails compile labeled, with a remediation hint naming the per-language emitter follow-up (compile.md’sno_emitter_labeled_failure). Executable emission for those languages is deferred of record to the per-language follow-ups (add-py-fragment-emission,add-ts-fragment-emission) — an accepted-tag-without-emitter would silently fall through to Rust, which is the vacuous outcome this Revision exists to prevent. - Mention is not extraction — a
**rust:**/**py:**/**ts:**occurrence anywhere other than fragment position (mid-span, in the defining rows ofcompile.md’s own table, or in prose between spans) is a mention of the mechanism and never extracts (compile.md’smention_not_extraction, carrying sd1’s rule into the widened grammar).
No row of this file’s own tables changes in this Revision — the growth
lives entirely in compile.md, mirroring Revision 15’s shape. Nothing
valid at Revision 15 is invalidated, and the proptest/verify machinery
is untouched.
Revision 17 — 2026-10-08
The min-expr kernel (add-min-expr-kernel, 2026-10-08). The
data-dependent cells — the equational and bounded-quantified ones — had
no defined semantics: the linter and model-check verify structure, but
“resolves is unique” or “every reachable row satisfies a bound” was
prose only. This Revision defines a decidable bounded fragment of the
internal language of Set^𝒦 — the kernel — and makes it first-class
format vocabulary:
- One opt-in marker, closed grammar. An expr cell opts in per cell
with the kernel: marker in fragment position, under the same
fragment-position rule as the executable-fragment markers; the
expression is parsed against the closed atomic set — equality,
comparisons, bounded ∀/∃ over the finite instances
I(k), ∧/¬, and the reference-typed atomicsresolves/unique/acyclic/reachable— and a non-member atomic fails labeled naming the atomic and the closed set (compile.md’skernel_expr_opt_in). A cell without the marker compiles exactly as before — pure widening, nothing valid at Revision 16 is invalidated. - Three-valued, never coerced. Evaluation over the finite instances
reports exactly one of verified, counterexample, unknown;
unknownis honest (the claim could not be discharged), propagates under Kleene rules, and never coerces to pass or to counterexample. - Semantics never outrun proven machinery. Each v0 atomic names its
grounding —
acyclic/reachableon graph traversal,unique/resolveson acset traversal, equality, comparisons, and bounded quantifiers on model_check bounded evaluation — and an atomic without a grounding entry cannot ship. The widening law: a new atomic (pack predicates included) registers only if it is decidable over finite instances; a predicate failing the gate stays pack-side, checked by contract-TOML runners rather than by the kernel. - Guard citations evaluate. A guard citation (
[[a]] ∧ [[b]], ¬) is upgraded from inert annotation to a kernel-evaluated claim with the same three-valued honesty — an undischargable citation reports unknown, never pass. Executable guard fragments remain rejected of record:fragment_guard_rejected’s deferral condition (a data-carrying state space) is not met, and this Revision does not claim it. kernel.binding, the opaque bridge. An invariant-kind Constraint may carry akernel.bindingcell, extracted and surfaced verbatim and never interpreted by the toolchain; external checkers claim constraints through contract-TOMLflagsbinding, and no registry is built (compile.md’skernel_binding_opaque).
The grammar growth lives in compile.md (kernel_expr_opt_in,
kernel_binding_opaque), mirroring Revision 16’s shape. This file’s own
Constraints table carries the migrated kernel rows — total_refs,
coverage, every_transition_valid, and supersedes_acyclic opt in
with the kernel: marker; their per-file migration (USAGE.md examples,
then this file’s invariants) was gated lint-clean and command-verified
before this Revision’s heading, and this Revision declares the grammar
those cells use. Nothing valid at Revision 16 is invalidated.
Revision 18 — 2026-10-08
Single-tree spec authoring: the id law derives spec.md from its
parent directory (update-id-derivation-spec-md, specodelic-mcy,
2026-10-08). The old law — frontmatter.id == replace(stem(path), "-", ".") — forced every file named spec.md (the openspec-mandated
filename, delta or deployed capability spec) to declare id: spec, and
id: spec files resolved as self-contained (refs within the file only,
total_refs/total_refs’s scoped index, and the command scope law’s
isolated_scope_required arm). That structurally locked a two-tree
architecture: a real-id corpus for the typed graph plus a deployed
dual-format tree produced by per-repo hand-rolled deploy transforms.
This Revision retires the distinction with one rule:
- The id law —
expected_id_from_path(path): a file namedspec.mdderives its expected id from its PARENT DIRECTORY name (openspec/specs/ge-cli/spec.md→id: ge.cli); any other file derives from its own stem;-maps to the namespace dot,_is literal; a barespec.mdwith no parent directory falls back to the stem. A real-id corpus file dropped asopenspec/specs/<cap>/spec.mdyields exactly one finding — a mismatchedid_matches_file. id: specretires.dual_format_validno longer polices the id (the naming law is the only id gate a dual-format file answers to),total_refsresolves every file corpus-wide (the self-containment scoping and its bare-local arm are gone — real-id files are distinguished by their own ids, so the false-resolution CORR-001 guarded against needed theid: speccollision to exist), and the command scope law reduces toduplicate_corpus_identity(model_check.md’s isolated-scope rule retired with its subject). Real-id dual-format files compose like any corpus; a same-id pair (transitionally: an active change’s delta and its deployed spec) is refused by the identity law on command paths.- Scaffolds teach the single tree.
spk newtargetsopenspec/specs/<id>/spec.mdby default;spk migrategenerates the real derived id in the frontmatter it scaffolds. New users cannot produce the two-tree distinction.
Pure widening plus one narrowing of dead machinery: nothing valid at
Revision 17 is invalidated — a file that satisfied the old law still
satisfies the new one (stem files were already stem-derived; spec.md
files under capability directories now satisfy the law with their real
ids, which the old law rejected — but no corpus content at Revision 17
depended on that rejection being the only legal reading). The
rename tool’s filename law (rename.md’s
new_id_matches_filename) inherits expected_id_from_path verbatim.
Revision 19 — 2026-10-24
Change deltas go plain; the layer rides beside
(add-delta-layer-sidecar, specodelic-s8fg Stage 2). Stage 1
(2026-10-19) made deployed capability specs 1.x-strict-valid by
stripping delta sections at deploy; the remaining 1.x rejections were the
dual-format change deltas themselves — the byte-identical ## Requirements
mirror and the layer tables in the same file. This Revision retires the
dual-format delta class with one rule:
- Plain deltas + sidecar. A change delta under
openspec/changes/<id>/specs/<cap>/is a plain openspec file —## ADDED/MODIFIED/REMOVED Requirements(plus## Purpose), no frontmatter, no layer tables, no mirror; openspec validate is its gate. The specodelic layer (frontmatter + Constraints/Model/Properties) is carried by a siblinglayer.mdsidecar — a real, corpus-linted specodelic file whosetraces_tocitations resolve corpus-wide. - The id law gains the sidecar arm.
expected_id_from_path: a file namedlayer.mdderives its expected id from its PARENT DIRECTORY name plus a.layersuffix (specs/cap-a/layer.md→id: cap.a.layer) — the sibling of thespec.mdrule, keeping sidecars per-capability unique and co-archived with their delta.id: spec’s retirement logic (Revision 18) is untouched. - The mirror retires by construction.
requirement_driftanddual_format_valid’s delta-pairing arm police the pairing of a delta section with a same-file## Requirementsmirror — a shape plain deltas can no longer produce without failing openspec validation. Both retire; the dual-format capability spec class (deployedopenspec/specs/<cap>/spec.md) keeps every other rule unchanged.migrate’s mirror generation retires with it — the wrap path now scaffolds the sidecar.
The retirement narrows exactly the shape the corpus already stopped
authoring: spec-integration’s intent of record (Revision 18 era) already
declared “change deltas stay plain openspec files”, and no deployed
capability spec is invalidated — the capability-spec class keeps every
rule it had. archive-companion’s deploy re-assembly (openspec applies,
the companion re-attaches the layer from the archived sidecar) is
tool semantics, recorded in archive-companion.md — not a format law.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: referential_integrity
- WHEN
arbitrary_spec_file() - THEN
∀ ref ∈ file: lookup(ref) ≠ None - VERIFIES [[specodelic.referential_integrity]]
Scenario: acyclicity
- WHEN
arbitrary_spec_repo() - THEN
is_dag(traces_to_graph(repo)) - VERIFIES [[specodelic.acyclicity]]
Scenario: rename_naturality
- WHEN
arbitrary_spec_repo(), arbitrary_id_rename() - THEN identity:
rename(I, a, a) == Iassociativity:rename(rename(I,a,b), b,c) == rename(I,a,c)naturality:compile(rename(I)) == rename(compile(I)) - VERIFIES [[specodelic.rename_naturality]]
Scenario: filename_convention
- WHEN
(id, mismatched_filename)pairs - THEN
check(id, filename) == failed - VERIFIES [[specodelic.filename_convention]]
Scenario: typing_enforced
- WHEN
guard_field_pointing_at_a_property_row() - THEN
check(file) == failed - VERIFIES [[specodelic.typing_enforced]]
Scenario: own_intent_reachable
- WHEN
spec_repo_with(disconnected_constraint_cluster: true) - THEN
check(repo) == failed - VERIFIES [[specodelic.own_intent_reachable]]
Scenario: model_well_formed
- WHEN
spec_file_with(transition.to_not_in_states: true) - THEN
check(file) == failed - VERIFIES [[specodelic.model_well_formed]]
Scenario: advisory_cannot_gate
- WHEN
guard_field_pointing_at_an_advisory_constraint() - THEN
check(file) == failed - VERIFIES [[specodelic.advisory_cannot_gate]]
Scenario: state_guard_citation_accepted
- WHEN
transition_guard_citing_a_declared_state() - THEN
check(file) == passed— the “has reached state X” guard pattern (Revision 12); a State target gates nothing, it records progress — but it is a legal typed citation - VERIFIES [[specodelic.state_guard_citation_accepted]]
Scenario: supersedes_cross_kind_rejected
- WHEN
constraint_row_with(supersedes: a_property_row_id) - THEN
check(file) == failed - VERIFIES [[specodelic.supersedes_cross_kind_rejected]]
Scenario: constraint_derives_from_rejected
- WHEN
constraint_row_with(derives_from: a_constraint_row_id) - THEN
check(file) == failed— the Appears-on column is normative, not descriptive:derives_fromappears on Property rows only (decided of record 2026-10-01,specodelic-huf; a constraint is derived FROM by properties, it does not derive), andref_kind_compatiblereads the column source-side the same way it already readssupersedes’ same-kind rule - VERIFIES [[specodelic.constraint_derives_from_rejected]]
Scenario: supersedes_cycle_rejected
- WHEN
spec_repo_with(supersedes_cycle: length ≥ 2) - THEN
check(repo) == failed - VERIFIES [[specodelic.supersedes_cycle_rejected]]
Scenario: no_stored_superseded_flag
- WHEN
schema_with(explicit is_superseded: bool column) - THEN
check(schema) == rejected— supersession status is derived (superseded(x) ⟺ ∃ y. y.supersedes ∋ x), never stored - VERIFIES [[specodelic.no_stored_superseded_flag]]
Scenario: emits_cannot_target_non_effect
- WHEN
emits_field_pointing_at_an_invariant_constraint() - THEN
check(file) == failed— same shape asadvisory_cannot_gate, mirrored onto the other direction of the kind-split - VERIFIES [[specodelic.emits_cannot_target_non_effect]]
Scenario: satisfies_wrong_kind_rejected
- WHEN
satisfies_field_pointing_at_an_invariant_constraint() - THEN
check(file) == failed— same shape asadvisory_cannot_gateandemits_cannot_target_non_effect, mirrored onto the third kind-typed reference field - VERIFIES [[specodelic.satisfies_wrong_kind_rejected]]
Scenario: extension_point_needs_no_reachability_carveout
- WHEN
constraint_row_with(satisfies: an_extension_point_in_another_file, traces_to: own_file_intent) - THEN
check(row) == passed—satisfiesadds no reachability obligation; the row is reachable the ordinary way, through its owntraces_to - VERIFIES [[specodelic.extension_point_needs_no_reachability_carveout]]
Scenario: frontmatter_missing_field_rejected
- WHEN
frontmatter_missing(one of id, kind, statement) - THEN
check(file) == failed - VERIFIES [[specodelic.frontmatter_missing_field_rejected]]
Scenario: guardless_transition_rejected
- WHEN
transition_row_with(empty guard cell) - THEN
check(file) == failed - VERIFIES [[specodelic.guardless_transition_rejected]]
Scenario: non_ears_statement_rejected
- WHEN
statement_outside_the_five_ears_patterns("the system should maybe work") - THEN
check(file) == failed - VERIFIES [[specodelic.non_ears_statement_rejected]]
Scenario: multi_intent_join_rejected
- WHEN
row_id_joined_to_two_intents_via_and() - THEN
check(file) == failed - VERIFIES [[specodelic.multi_intent_join_rejected]]
Scenario: coverage_gap_flagged
- WHEN
spec_file_with(constraint_with_no_deriving_property: true) - THEN
lint(file) reports rule == failed - VERIFIES [[specodelic.coverage_gap_flagged]]
Scenario: law_missing_case_rejected
- WHEN
law_property_missing("associativity") - THEN
check(file) == failed - VERIFIES [[specodelic.law_missing_case_rejected]]
Scenario: orphan_state_rejected
- WHEN
state_appearing_in_no_transition() - THEN
check(file) == failed - VERIFIES [[specodelic.orphan_state_rejected]]
Scenario: missing_model_section_rejected
- WHEN
spec_file_without_states_or_transitions() - THEN
check(file) == failed - VERIFIES [[specodelic.missing_model_section_rejected]]
Scenario: invariant_violation_blocks_verify
- WHEN
model_with_a_violated_invariant() - THEN
check(model_check) == failed - VERIFIES [[specodelic.invariant_violation_blocks_verify]]
Scenario: failing_proptest_blocks_verify
- WHEN
compiled_proptest_block_that_fails() - THEN
check(verify) == failed - VERIFIES [[specodelic.failing_proptest_blocks_verify]]
Scenario: prose_does_not_affect_lint
- WHEN
two_specs_identical_except_rationale_prose() - THEN
lint(a) == lint(b)— differing prose never changes a lint result - VERIFIES [[specodelic.prose_does_not_affect_lint]]
Scenario: silently_shrunk_id_set_rejected
- WHEN
(r, r')where a 𝒦-governed id-set member was removed with no Revision heading - THEN
check(revisions) == failed - VERIFIES [[specodelic.silently_shrunk_id_set_rejected]]
Scenario: constraint_kind_out_of_set_rejected
- WHEN
constraint_row_with(kind: "made_up") - THEN
check(file) == failed - VERIFIES [[specodelic.constraint_kind_out_of_set_rejected]]
Scenario: property_kind_out_of_set_rejected
- WHEN
property_row_with(kind: "audit") - THEN
check(file) == failed - VERIFIES [[specodelic.property_kind_out_of_set_rejected]]
kinds — closed kind value sets
id: kinds kind: intent statement: “THE kinds spec SHALL define the field set, sub-kind values, and shape-validity rule for each of the five schema objects (Intent, Constraint, State, Transition, Property) that every specodelic file instantiates.”
Kinds
The five objects of 𝒦 — Intent, Constraint, State, Transition,
Property — are referenced constantly throughout this repo but have never
been specified as subjects in their own right (STATUS.md §4, P0). This
file is that specification: for each kind, its exact field set and which
values its own internal kind column (where it has one) may take. Every
later checker — linter.frontmatter through linter.coverage — assumes a
row has already been correctly bucketed into one of these five before it
runs its own check; this file is what makes that bucketing itself a
checkable fact instead of an unstated assumption.
Purpose
The five objects of 𝒦 — Intent, Constraint, State, Transition,
Property — are referenced constantly throughout this repo but have never
been specified as subjects in their own right (STATUS.md §4, P0). This
file is that specification: for each kind, its exact field set and which
values its own internal kind column (where it has one) may take. Every
later checker — linter.frontmatter through linter.coverage — assumes a
row has already been correctly bucketed into one of these five before it
runs its own check; this file is what makes that bucketing itself a
checkable fact instead of an unstated assumption.
Constraints
| id | kind | expr | traces_to | satisfies |
|————————|———–|——————————————————————————————————————————————————|–––––––––––|
| kind_enum_closed | invariant | the five kinds are exactly {Intent, Constraint, State, Transition, Property}, one per top-level section of a spec file (frontmatter, Constraints table, Model/States, Model/Transitions, Properties table) | [[kinds]] | |
| intent_row_shape | invariant | an Intent row is the file's frontmatter block; its fields are exactly {id, kind, statement}, with kind == "intent", plus optionally-declared frontmatter keys (currently checked_against_core, per AGENTS.md's convention — see Revision 7) | [[kinds]] | |
| constraint_row_shape | invariant | a Constraint row is one row of the Constraints table; its base fields are exactly {id, kind, expr, traces_to}, optionally followed by typed reference columns — each such column must be declared in specodelic.md's Reference Typing table (currently satisfies, observes) and carries no meaning beyond that typing; kind ∈ {invariant, advisory, effect, extension_point}, extended per-file by the active packs' declared kinds (openspec/specs/packs/spec.md fiber kinds — never narrower, see linter-schema_shape Notes) | [[kinds]] | |
| state_row_shape | invariant | a State row is one bullet under Model/States; its fields are exactly {id, emits?} and it has no kind column of its own — states are named variants, never a typed column; emitsis optional and, when present, must resolve to a Constraint with kind == "effect" (seespecodelic.md's Reference Typing table) — a state with no emits is a bare automaton state, not a Moore state, and both are well-formed | [[kinds]] | |
| transition_row_shape | invariant | a Transition row is one row of the Model/Transitions table; its fields are exactly {id, from, to, guard}, with no kind column of its own | [[kinds]] | |
| property_row_shape | invariant | a Property row is one row of the Properties table; its fields are exactly {id, kind, derives_from, generator, predicate}, with kind ∈ {unit, law}, extended per-file by the active packs' declared kinds (openspec/specs/packs/spec.md fiber kinds — never narrower, see linter-schema_shape Notes) | [[kinds]] | |
| kind_field_extensible | invariant | a Constraint or Property row's own kind value-set is one instance of [[specodelic.append_only_variants]] — it grows only under a new Revision heading in this file, never silently | [[kinds]] | |
| kind_assignment_failure | effect | kinds.kind_assignment_failure(detail) | [[kinds]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| shape_check_failure | effect | kinds.shape_check_failure(detail) | [[kinds]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
unclassifiedkind_assignedshape_checkedpassedkind_failed(emits:[[kinds.kind_assignment_failure]])shape_failed(emits:[[kinds.shape_check_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| begin | unclassified | kind_assigned | row belongs to exactly one of the five sections (frontmatter / Constraints table / Model States / Model Transitions / Properties table) |
| assign_ok | kind_assigned | shape_checked | [[kinds.kind_enum_closed]] |
| assign_fail | kind_assigned | kind_failed | ¬([[kinds.kind_enum_closed]]) |
| accept | shape_checked | passed | (row.kind==Intent ∧ [[kinds.intent_row_shape]]) ∨ (row.kind==Constraint ∧ [[kinds.constraint_row_shape]]) ∨ (row.kind==State ∧ [[kinds.state_row_shape]]) ∨ (row.kind==Transition ∧ [[kinds.transition_row_shape]]) ∨ (row.kind==Property ∧ [[kinds.property_row_shape]]) |
| reject | shape_checked | shape_failed | ¬((row.kind==Intent ∧ [[kinds.intent_row_shape]]) ∨ (row.kind==Constraint ∧ [[kinds.constraint_row_shape]]) ∨ (row.kind==State ∧ [[kinds.state_row_shape]]) ∨ (row.kind==Transition ∧ [[kinds.transition_row_shape]]) ∨ (row.kind==Property ∧ [[kinds.property_row_shape]])) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| unknown_kind_rejected | unit | [[kinds.kind_enum_closed]] | row_belonging_to_a_sixth_section("Actions") | check(row) == failed |
| intent_missing_statement_rejected | unit | [[kinds.intent_row_shape]] | intent_row_without("statement") | check(row) == failed |
| constraint_row_extra_field_rejected | unit | [[kinds.constraint_row_shape]] | constraint_row_with(extra_field: "guard") | check(row) == failed |
| state_row_with_extra_field_rejected | unit | [[kinds.state_row_shape]] | state_row_with(extra_field: "kind") | check(row) == failed |
| transition_missing_guard_rejected | unit | [[kinds.transition_row_shape]] | transition_row_without("guard") | check(row) == failed |
| property_row_bad_subkind_rejected | unit | [[kinds.property_row_shape]] | property_row_with(kind: "audit") | check(row) == failed |
| subkind_addition_without_revision_rejected | unit | [[kinds.kind_field_extensible]] | diff_adding_subkind_value_with_no_new_Revision_heading() | check(diff) == failed |
| constraint_row_advisory_accepted | unit | [[kinds.constraint_row_shape]] | constraint_row_with(kind: "advisory") | check(row) == passed |
| constraint_row_effect_accepted | unit | [[kinds.constraint_row_shape]] | constraint_row_with(kind: "effect") | check(row) == passed |
| state_row_emits_accepted | unit | [[kinds.state_row_shape]] | state_row_with(emits: an_effect_kind_constraint_id) | check(row) == passed |
| state_row_without_emits_still_passes | unit | [[kinds.state_row_shape]] | state_row_with(fields: {id}) — no emits | check(row) == passed — emits is optional, not a second required field |
| state_row_emits_wrong_kind_rejected | unit | [[kinds.state_row_shape]] | state_row_with(emits: an_invariant_kind_constraint_id) | check(row) == failed |
| constraint_row_extension_point_accepted | unit | [[kinds.constraint_row_shape]] | constraint_row_with(kind: "extension_point") | check(row) == passed |
| well_formed_row_passes | unit | [[kinds.kind_enum_closed]] | arbitrary_well_formed_row_of_one_kind() | check(row) == passed |
| kind_shape_naturality | law | [[kinds.kind_enum_closed]] | arbitrary_row_of_one_kind(), arbitrary_id_rename() | identity: assigned_kind(rename(row,a,a)) == assigned_kind(row) associativity: assigned_kind(rename(rename(row,a,b),b,c)) == assigned_kind(rename(row,a,c)) — renaming a row’s id never changes which of the five kinds it belongs to |
| kind_assignment_failure_label_asserted | unit | [[kinds.kind_assignment_failure]] | kind_assignment_failure_raised() | error_label == "kinds.kind_assignment_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| shape_check_failure_label_asserted | unit | [[kinds.shape_check_failure]] | shape_check_failure_raised() | error_label == "kinds.shape_check_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
Fiber-kind extension of the base subkind sets (2026-10-03, beads
specodelic-ung): a domain pack’s ## Kinds rows declare
pack-qualified fiber kinds (e.g. empirical.statistic), and a base
Constraint/Property row’s kind column accepts them when the pack is
active for the file — the closed-set walkers’ effective set is
base ∪ active-pack-fiber, never narrower (the base sets above do not
grow; kind_field_extensible‘s append-only discipline is untouched —
the extension is per-fiber, governed by openspec/specs/packs/spec.md, and dissolves
when the pack is not active, firing the labeled finding instead of
passing silently). The empirical-registry pack is the first consumer:
its kind targets the base Properties table’s kind column directly
(the sibling packs’ kinds live in pack-added sections, which is why
they never needed this).
Two different things are called “kind” in this repo, and they must not be
conflated. 𝒦’s five objects (Intent, Constraint, State,
Transition, Property) are what this file calls kinds — which section
of a spec file a row lives in. Separately, two of those kinds carry their
own kind column as one of their fields: a Constraint row’s kind
(currently only invariant) and a Property row’s kind (unit or
law). These are sub-classifications within an already-assigned 𝒦-kind,
not alternate values of it — a Property row with kind: "law" is still a
Property in 𝒦’s sense, never a sixth object. Flagging this as
CLAR-002 (same shape as STATUS.md’s open CLAR-001, the
coverage/linter.coverage collision): worth an explicit rename in a
future revision (e.g. subkind for the column, reserving kind for the
𝒦-level classification alone) rather than leaving two meanings on one
word. Not resolved here, since renaming a live column is exactly the kind
of change that should go through rename_naturality, not be done by hand.
property_row_shape’s kind ∈ {unit, law} is the authoritative home for
the enum linter-schema_shape.md’s EXCL-001 wants a third member added
to (kind = "audit", for claims like no_prose_field_parsed that are
about the parser’s implementation rather than about spec-file content).
When EXCL-001 is actioned, this file’s property_row_shape constraint
is what gets a new Revision, and property_row_bad_subkind_rejected’s
generator would need to change from a rejection case to an acceptance
case for kind: "audit" specifically.
A gap surfaced writing this file, folded into specodelic.md as
Revision 4 (same session, per AGENTS.md): nothing before this file
required a Constraint row’s kind or a Property row’s kind to come from
a closed set at all — frontmatter_valid only ever checked the file-level
kind == "intent". A Property row with kind: "made_up" had no
constraint anywhere rejecting it. constraint_row_shape and
property_row_shape above close that, and specodelic.md now carries
matching top-level invariants (constraint_kind_closed,
property_kind_closed) tracing back to this file. Follow-up closed the
same session: linter-schema_shape.md now owns and enforces both — see
its kind_ok transition and the two ..._kind_invalid_rejected
properties.
Revision 2
constraint_row_shape’s kind set widens from {invariant} to
{invariant, advisory} — kind_field_extensible exercised exactly as it
was designed to be, one Revision heading per widening, nothing silent.
An advisory Constraint is still fully a row of the Constraints table
(same four fields, same table); the only place its meaning differs is
outside this file entirely, in specodelic.md’s Reference Typing table,
which now types guard’s target as Constraint, kind == invariant —
this file owns row shape, not what other rows may reference a row, so
that cross-row rule correctly lives there and not here. property_kind_closed
is untouched: EXCL-001’s pending audit value is a separate, still-open
question and this Revision takes no position on it.
Revision 3
Two changes, both from specodelic.md Revision 6, folded in here the
same session per AGENTS.md:
-
constraint_row_shape’skindset widens again, from{invariant, advisory}to{invariant, advisory, effect}. Same pattern as Revision 2 — one morekind_field_extensible-governed widening, row shape itself untouched (still{id, kind, expr, traces_to}). What aneffect-kind Constraint means to other rows is again a cross-row fact that lives inspecodelic.md’s Reference Typing table, not here: onlyemitsmay target one, the same way onlyguardmay target aninvariantone. -
state_row_shapegains an optional field,emits. This is a row-shape change, unlike the twokindwidenings above — State’s field set moves from{id}to{id, emits?}, which is why it’s stated as its own bullet rather than folded into thekind_field_extensibleframing.emitsgives a State the output half of a Moore machine (states, transitions, and a function from state to output — only the first two had anywhere to live before this). Making it optional rather than required means a plain automaton state and a Moore state are both well-formedStaterows; nothing about existing specs (none of which useemits) becomes invalid by this widening, sovariant_set_grows_only- style backward compatibility holds automatically. SeeUSAGE.mdfor the worked pattern (a reducer’s three phases, each with its ownemits). -
kind_field_extensible’s wording was also corrected, independent of either change above: it used to restate, in its own words, the same “grows only via Revision” rulespecodelic.md’sappend_only_variantsalready stated for variant tables — the two had drifted slightly out of sync (compare Revision 5’s wording ofappend_only_variantsagainst this file’skind_field_extensibleat the time).specodelic.mdRevision 6 generalizedappend_only_variantsto cover kind value-sets and the Reference Typing table too; this file’s row now just cites that one rule instead of keeping a second copy of the logic.
Revision 4
constraint_row_shape’s kind set widens a third time, from
{invariant, advisory, effect} to {invariant, advisory, effect, extension_point}, folded in from specodelic.md Revision 7 the same
session per AGENTS.md. Same pattern as Revisions 2 and 3 — row shape
itself is untouched (still exactly {id, kind, expr, traces_to}); only
the closed value-set kind_field_extensible governs grows by one member.
An extension_point row states a contract a consumer’s code must
satisfy (a method signature, a behavioral expectation) rather than an
invariant this file’s own author enforces — what makes a Constraint
reference that row meaningful lives, as with effect/emits, entirely
outside this file, in specodelic.md’s Reference Typing table, which now
types a new field, satisfies, at Constraint, kind == extension_point only. constraint_row_extension_point_accepted above is the matching
acceptance case, added the same way constraint_row_advisory_accepted
and constraint_row_effect_accepted were.
Revision 5
constraint_row_shape stated its fields as exactly
{id, kind, expr, traces_to} — but the format already carries optional
typed reference columns beside traces_to: Revision 7 of
specodelic.md added satisfies (USAGE §2.6’s five-column example), and
Revision 9 adds observes. The “exactly four fields” reading and the
Reference Typing table contradicted each other — HITL ticket
specodelic-mp1 row 9 asked which governs; this Revision answers: the
table governs.
constraint_row_shapenow states the general rule: base fields are exactly{id, kind, expr, traces_to}, plus optional typed reference columns — each such column must be declared inspecodelic.md’s Reference Typing table, and carries no meaning beyond that typing (the table row is the whole contract). Noobservesspecial case — the next field costs one typing row, zero shape edits.- No existing file changes meaning: every optional column in use
(
satisfies) was already valid under the table-driven reading; the narrowing “exactly four” was the only reading it violated, and no checker enforced it.
Row shape is otherwise untouched — the same
general-rule-instead-of-special-case move as kind_field_extensible.
Revision 6
EXCL-001 is resolved by dissolution — property_row_shape’s kind set
stays {unit, law} and no audit member is added (HITL ticket
specodelic-mp1 row 5, user-approved 2026-09-30, advised by a typed Jev
evaluation, jev-1.13.0, confidence 1.00).
The only instance that ever motivated an audit kind was
linter-schema_shape.md’s no_prose_field_parsed — a claim about the
parser’s implementation, framed as “verified by code audit, not by a
runnable PBT case”. That framing was wrong: the claim is mechanically
testable by a planted-prose differential parse (generate a spec with
prose in rationale/description, parse, assert no parsed row contains
it). An enum member whose definition is “not machine-verified” would
crack the format’s promise that Properties are runnable, and
kind_field_extensible should be spent when the shape genuinely needs
it — not on a singleton. The rewrite of that property to a runnable
unit row lands with the corpus-reconciliation work (specodelic-cxq);
a genuinely unrunnable implementation claim remains a possible future
widening, on demand and under a Revision heading, as always.
Revision 7
Two spec-text fixes from the Rule-of-5 corpus review (2026-10-01,
specodelic-x4w, CORR-002 + CORR-003, both MEDIUM) — rewordings that
narrow nothing, folded under one Revision heading:
reject’s guard was a tautology. It read¬(intent_row_shape ∧ constraint_row_shape ∧ state_row_shape ∧ transition_row_shape ∧ property_row_shape)— but no single row can satisfy all five mutually-exclusive shape conditions (a frontmatter block is never also a table row), so the conjunction is always false andrejectfired unconditionally fromshape_checked: well-formed rows had bothacceptandrejectenabled — a non-deterministic model. Fixed to negate the applicable disjunct:¬((row.kind==Intent ∧ intent_row_shape) ∨ (row.kind==Constraint ∧ constraint_row_shape) ∨ …)— exactly one disjunct applies per row, and its negation is the rejection condition. Contrast (kept as-is):linter-graph_shape.md’s same-shapedrejectis correct — its four conjuncts co-apply to one artifact (the whole graph), not to five alternative shapes of one row.intent_row_shapeacknowledges optionally-declared frontmatter keys. It read “fields are exactly {id, kind, statement}”, but ten corpus files carry a fourth key,checked_against_core(theAGENTS.mdconvention recording a core-check pass) — documented inopenspec/specs/AGENTS.md, undeclared in the format.constraint_row_shapereceived the optional-declared-columns carve-out inspecodelic.mdRevision 9’s move (this file’s Revision 5); the Intent layer never did. Now: base fields stay exactly{id, kind, statement}, plus optionally-declared frontmatter keys (currentlychecked_against_core, perAGENTS.md’s convention). No linter change accompanies this —frontmatter_validis a subset check (it never rejects unknown keys) and stays one; the spec text is the only guard, and the pinning fixture (extra_frontmatter_key_lints_clean, tests/cli.rs) asserts the tool-level reading: a spec carrying the key lints clean, and the parser captures it as an extra field rather than dropping it.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: unknown_kind_rejected
- WHEN
row_belonging_to_a_sixth_section("Actions") - THEN
check(row) == failed - VERIFIES [[kinds.unknown_kind_rejected]]
Scenario: intent_missing_statement_rejected
- WHEN
intent_row_without("statement") - THEN
check(row) == failed - VERIFIES [[kinds.intent_missing_statement_rejected]]
Scenario: constraint_row_extra_field_rejected
- WHEN
constraint_row_with(extra_field: "guard") - THEN
check(row) == failed - VERIFIES [[kinds.constraint_row_extra_field_rejected]]
Scenario: state_row_with_extra_field_rejected
- WHEN
state_row_with(extra_field: "kind") - THEN
check(row) == failed - VERIFIES [[kinds.state_row_with_extra_field_rejected]]
Scenario: transition_missing_guard_rejected
- WHEN
transition_row_without("guard") - THEN
check(row) == failed - VERIFIES [[kinds.transition_missing_guard_rejected]]
Scenario: property_row_bad_subkind_rejected
- WHEN
property_row_with(kind: "audit") - THEN
check(row) == failed - VERIFIES [[kinds.property_row_bad_subkind_rejected]]
Scenario: subkind_addition_without_revision_rejected
- WHEN
diff_adding_subkind_value_with_no_new_Revision_heading() - THEN
check(diff) == failed - VERIFIES [[kinds.subkind_addition_without_revision_rejected]]
Scenario: constraint_row_advisory_accepted
- WHEN
constraint_row_with(kind: "advisory") - THEN
check(row) == passed - VERIFIES [[kinds.constraint_row_advisory_accepted]]
Scenario: constraint_row_effect_accepted
- WHEN
constraint_row_with(kind: "effect") - THEN
check(row) == passed - VERIFIES [[kinds.constraint_row_effect_accepted]]
Scenario: state_row_emits_accepted
- WHEN
state_row_with(emits: an_effect_kind_constraint_id) - THEN
check(row) == passed - VERIFIES [[kinds.state_row_emits_accepted]]
Scenario: state_row_without_emits_still_passes
- WHEN
state_row_with(fields: {id})— noemits - THEN
check(row) == passed—emitsis optional, not a second required field - VERIFIES [[kinds.state_row_without_emits_still_passes]]
Scenario: state_row_emits_wrong_kind_rejected
- WHEN
state_row_with(emits: an_invariant_kind_constraint_id) - THEN
check(row) == failed - VERIFIES [[kinds.state_row_emits_wrong_kind_rejected]]
Scenario: constraint_row_extension_point_accepted
- WHEN
constraint_row_with(kind: "extension_point") - THEN
check(row) == passed - VERIFIES [[kinds.constraint_row_extension_point_accepted]]
Scenario: well_formed_row_passes
- WHEN
arbitrary_well_formed_row_of_one_kind() - THEN
check(row) == passed - VERIFIES [[kinds.well_formed_row_passes]]
Scenario: kind_shape_naturality
- WHEN
arbitrary_row_of_one_kind(), arbitrary_id_rename() - THEN identity:
assigned_kind(rename(row,a,a)) == assigned_kind(row)associativity:assigned_kind(rename(rename(row,a,b),b,c)) == assigned_kind(rename(row,a,c))— renaming a row’s id never changes which of the five kinds it belongs to - VERIFIES [[kinds.kind_shape_naturality]]
Scenario: kind_assignment_failure_label_asserted
- WHEN
kind_assignment_failure_raised() - THEN
error_label == "kinds.kind_assignment_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[kinds.kind_assignment_failure_label_asserted]]
Scenario: shape_check_failure_label_asserted
- WHEN
shape_check_failure_raised() - THEN
error_label == "kinds.shape_check_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[kinds.shape_check_failure_label_asserted]]
packs — domain-pack mechanism
id: packs kind: intent checked_against_core: clear statement: “WHEN a workspace enables a domain pack, THE format SHALL make the extension mechanism first-class — a kind: profile spec file whose six per-facet manifest tables declare exactly the sections, namespaced kinds, reference fields, checkers, floors, and requirements the pack introduces — and SHALL activate pack checking advisory-first by vocabulary use or a declared uses edge, never by config files, never by narrowing any base closed set, and never by changing how files without packs lint.”
Packs
Make specodelic’s extension mechanism first-class: a domain pack is a declared, discoverable, versioned artifact in the corpus itself — not a convention followed by hand. Base closed sets stop growing; vocabulary is pack-fiber-relative (the Grothendieck construction one level up, per [[theory]] precedent); independent packs coexist namespaced instead of colliding in the global kind sets.
Purpose
Make specodelic’s extension mechanism first-class: a domain pack is a declared, discoverable, versioned artifact in the corpus itself — not a convention followed by hand. Base closed sets stop growing; vocabulary is pack-fiber-relative (the Grothendieck construction one level up, per [[theory]] precedent); independent packs coexist namespaced instead of colliding in the global kind sets.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| pack_is_profile_file | invariant | a pack is a four-layer spec file with kind: profile in frontmatter — no separate artifact format, no external manifest, self-hosting preserved | [[packs]] |
| manifest_six_tables | invariant | a pack manifest is exactly six per-facet closed tables — ## Sections, ## Kinds, ## References, ## Checkers, ## Floors, ## Requires — each with a fixed row shape checked by the pack_shape lint rule | [[packs]] |
| discovery_corpus_scan | invariant | every kind: profile file in the workspace is a candidate pack, found by corpus scan alone anchored at the git repository toplevel (the lint target directory when no git root exists) — no config file, no registry outside the corpus, and a subdirectory lint discovers packs living anywhere in the workspace | [[packs]] |
| namespaced_vocabulary | invariant | pack-declared kinds, sections, reference fields, and predicate names are pack-qualified in fiber scope; two packs claiming the same mechanism is legitimate, collision exists only at vocabulary level and resolves by pack-qualified naming | [[packs]] |
| base_closed_sets_frozen | invariant | base closed sets (constraint kinds, property kinds, intent kinds, reference typing) stop growing except by true format Revision; a pack can never narrow or retype an existing set entry | [[packs]] |
| advisory_first_opt_in | invariant | pack checking activates advisory-first when a file uses the pack's declared vocabulary, upgradeable to a declared uses edge; activation is per-pack — identical vocabulary declared by two discovered packs activates both with findings attributed per pack — and vocabulary matching is longest-prefix; files without packs lint byte-identically to pre-mechanism behavior | [[packs]] |
| orphan_vocabulary_labeled | invariant | declared vocabulary used with no pack discovered or declared produces a labeled failure finding (non-zero exit) naming the candidate pack — prefix-derived when only the namespace is known — and both remediations; never a generic dangling message, never silent | [[packs]] |
| uses_is_outbound_leaf | invariant | the uses reference field appears as a set-valued column on any Constraint row and resolves to the intent of a kind: profile file (the pack's frontmatter id); it is an outbound leaf joining no reachability path and no acyclic edge set — nothing targets it, single_root_reachable needs no carve-out, mutual pack use is well-formed | [[packs]] |
| pack_self_exempt | invariant | a pack file is never a consumer of declared vocabulary — its own manifest rows never trigger its or any pack's checkers and never produce orphan findings | [[packs]] |
| lifecycle_labeled | invariant | pack states are draft, published, deprecated; pack_shape must pass at parse in every state and draft packs' checkers activate advisory-first naming the draft status; the pack-authored ## Requires table pins the base format_revision and pack deps; deprecated packs stay advisory with deprecation named in findings | [[packs]] |
| skew_advisory | invariant | revision skew between a pack's Requires base pin and the workspace corpus revision is a labeled advisory on the warnings channel — never silent, never failing | [[packs]] |
| honest_empty | invariant | a pack checker with no workspace fixtures, data, or vocabulary instances reports an empty checked-set honestly — no fabricated discoveries | [[packs]] |
| append_only_packs | invariant | append_only_variants applies to packs themselves — a pack release may only add vocabulary, never remove or narrow previously declared entries | [[packs]] |
Model
States
draftpublisheddeprecated
Transitions
| id | from | to | guard |
|---|---|---|---|
| publish | draft | published | [[packs.pack_is_profile_file]] ∧ [[packs.manifest_six_tables]] — pack_shape reports zero findings over the pack's own declared vocabulary |
| deprecate | published | deprecated | [[packs.lifecycle_labeled]] — maintainer marks the pack deprecated; findings name the deprecation, vocabulary still checks |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| manifest_round_trips | unit | [[packs.manifest_six_tables]] | pack_file_parsed_then_manifest_read() | declared entries equal parsed manifest rows — no drift, no prose leakage |
| discovery_finds_all_packs | unit | [[packs.discovery_corpus_scan]] | corpus_with_n_profile_files() | scan reports exactly n packs, order-independent |
| subdir_lint_discovers_workspace_packs | unit | [[packs.discovery_corpus_scan]] | lint_subdirectory_with_pack_at_workspace_root() | pack discovered, its vocabulary checks run, zero orphan findings |
| no_pack_no_change | unit | [[packs.advisory_first_opt_in]] | corpus_linted_before_and_after_mechanism() | lint findings byte-identical for a corpus using no pack vocabulary |
| overlapping_vocabulary_activates_both | unit | [[packs.advisory_first_opt_in]] | two_packs_declare_identical_vocabulary_and_a_file_uses_it() | both packs' checkers run; findings attributed per pack |
| orphan_finding_names_pack | unit | [[packs.orphan_vocabulary_labeled]] | corpus_using_vocabulary_without_pack() | failure finding names the candidate pack and both remediations, exit non-zero |
| uses_edge_typing | unit | [[packs.uses_is_outbound_leaf]] | file_with_uses_edge_to_profile_id() | graph edge typed uses resolves to the profile id; wrong-kind targets violate typing |
| pack_self_activation_absent | unit | [[packs.pack_self_exempt]] | pack_file_linted_alone() | its checkers report an empty checked-set; no orphan findings from the pack's own manifest rows |
| skew_is_warning_not_failure | unit | [[packs.skew_advisory]] | pack_pinned_to_older_revision() | exit 0 with warning-channel advisory naming the pack's base pin and the corpus revision |
| empty_vocabulary_honest | unit | [[packs.honest_empty]] | pack_enabled_without_instances() | checker reports empty checked-set; no fabricated findings |
| narrowing_rejected | unit | [[packs.append_only_packs]] | pack_removing_previously_declared_kind() | pack_shape fails naming the removed entry and the append-only law |
| profile_file_is_four_layer | unit | [[packs.pack_is_profile_file]] | pack_parsed_as_standard_spec() | all four layers parse from the pack file alone — no sidecar artifact needed |
| pack_vocabulary_qualified | unit | [[packs.namespaced_vocabulary]] | two_packs_claim_same_mechanism() | both packs' declared vocabulary resolves without cross-pack collision |
| base_sets_unchanged_by_packs | unit | [[packs.base_closed_sets_frozen]] | guide_sets_compared_across_pack_enable() | INTENT_KINDS and Reference Typing sets identical with and without packs discovered |
| lifecycle_states_parsed | unit | [[packs.lifecycle_labeled]] | pack_model_section_parsed() | each lifecycle state yields exactly its declared checking behavior |
| fiber_kinds_typeable | unit | [[packs.namespaced_vocabulary]] | base_table_kind_column_with_active_fiber_kind() | the base closed-set walkers accept the pack-qualified kind when the pack is active; the same token without the pack fires the labeled finding (base ∪ active-pack-fiber, never narrower) |
Notes
Fiber kinds in base-table kind columns (2026-10-03, beads
specodelic-ung): mechanism v1’s closed-set walkers
(property_kind_closed / constraint_kind_closed,
openspec/specs/linter-schema_shape/spec.md) walked the static base sets, so a
pack-declared fiber kind in a base-table kind column fired the
finding even with its pack discovered — the sibling packs never hit
this because their kinds live in pack-added sections. The walkers’
effective set is now base ∪ active-pack-fiber: the ## Kinds tokens of
every pack active for the file (declared uses edge or vocabulary
match — the same advisory-first activation rule as everything else
here). This completes namespaced_vocabulary: a fiber kind is
typeable in fiber scope, per-file. The base sets themselves do not
grow (no format Revision; base pins stay valid) and a fiber kind with
no active declaring pack stays outside the closed set — the labeled
finding fires, never a silent pass. First consumer:
empirical.registry’s empirical.statistic (the
empirical-registry-pack capability spec’s fiber_kinds_typeable
constraint and kind_column_accepts_fiber_kinds property).
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: manifest_round_trips
- WHEN
pack_file_parsed_then_manifest_read() - THEN
declared entries equal parsed manifest rows — no drift, no prose leakage - VERIFIES [[packs.manifest_round_trips]]
Scenario: discovery_finds_all_packs
- WHEN
corpus_with_n_profile_files() - THEN
scan reports exactly n packs, order-independent - VERIFIES [[packs.discovery_finds_all_packs]]
Scenario: subdir_lint_discovers_workspace_packs
- WHEN
lint_subdirectory_with_pack_at_workspace_root() - THEN
pack discovered, its vocabulary checks run, zero orphan findings - VERIFIES [[packs.subdir_lint_discovers_workspace_packs]]
Scenario: no_pack_no_change
- WHEN
corpus_linted_before_and_after_mechanism() - THEN
lint findings byte-identical for a corpus using no pack vocabulary - VERIFIES [[packs.no_pack_no_change]]
Scenario: overlapping_vocabulary_activates_both
- WHEN
two_packs_declare_identical_vocabulary_and_a_file_uses_it() - THEN
both packs' checkers run; findings attributed per pack - VERIFIES [[packs.overlapping_vocabulary_activates_both]]
Scenario: orphan_finding_names_pack
- WHEN
corpus_using_vocabulary_without_pack() - THEN
failure finding names the candidate pack and both remediations, exit non-zero - VERIFIES [[packs.orphan_finding_names_pack]]
Scenario: uses_edge_typing
- WHEN
file_with_uses_edge_to_profile_id() - THEN
graph edge typed uses resolves to the profile id; wrong-kind targets violate typing - VERIFIES [[packs.uses_edge_typing]]
Scenario: pack_self_activation_absent
- WHEN
pack_file_linted_alone() - THEN
its checkers report an empty checked-set; no orphan findings from the pack's own manifest rows - VERIFIES [[packs.pack_self_activation_absent]]
Scenario: skew_is_warning_not_failure
- WHEN
pack_pinned_to_older_revision() - THEN
exit 0 with warning-channel advisory naming the pack's base pin and the corpus revision - VERIFIES [[packs.skew_is_warning_not_failure]]
Scenario: empty_vocabulary_honest
- WHEN
pack_enabled_without_instances() - THEN
checker reports empty checked-set; no fabricated findings - VERIFIES [[packs.empty_vocabulary_honest]]
Scenario: narrowing_rejected
- WHEN
pack_removing_previously_declared_kind() - THEN
pack_shape fails naming the removed entry and the append-only law - VERIFIES [[packs.narrowing_rejected]]
Scenario: profile_file_is_four_layer
- WHEN
pack_parsed_as_standard_spec() - THEN
all four layers parse from the pack file alone — no sidecar artifact needed - VERIFIES [[packs.profile_file_is_four_layer]]
Scenario: pack_vocabulary_qualified
- WHEN
two_packs_claim_same_mechanism() - THEN
both packs' declared vocabulary resolves without cross-pack collision - VERIFIES [[packs.pack_vocabulary_qualified]]
Scenario: base_sets_unchanged_by_packs
- WHEN
guide_sets_compared_across_pack_enable() - THEN
INTENT_KINDS and Reference Typing sets identical with and without packs discovered - VERIFIES [[packs.base_sets_unchanged_by_packs]]
Scenario: lifecycle_states_parsed
- WHEN
pack_model_section_parsed() - THEN
each lifecycle state yields exactly its declared checking behavior - VERIFIES [[packs.lifecycle_states_parsed]]
Scenario: fiber_kinds_typeable
- WHEN
base_table_kind_column_with_active_fiber_kind() - THEN
the base closed-set walkers accept the pack-qualified kind when the pack is active; the same token without the pack fires the labeled finding (base ∪ active-pack-fiber, never narrower) - VERIFIES [[packs.fiber_kinds_typeable]]
Using packs
A domain pack is specodelic’s extension mechanism: a way to give a domain — data lineage, numeric predicates, bioimage metadata — its own typed sections, kinds, and reference fields without a format revision and without touching the core’s closed kind sets. This page is the practical guide: what a pack is, how a workspace enables one, what it adds, and the CLI loop you run against a pack-enabled spec. The machine truth lives in the packs spec; this page explains how to use the mechanism, not how it is specified.
Everything shown below is real output from specodelic 0.11.0 (on a
TTY spk prints human-readable text; piped, it emits a JSON envelope).
What a pack is
A pack is a four-layer spec file with kind: profile in
frontmatter — the same Intent → Constraints → Model → Properties
shape as every other spec, plus six per-facet manifest tables:
## Sections,## Kinds,## References,## Checkers,## Floors,## Requires
There is no separate artifact format and no sidecar manifest: the pack
file is self-hosting, and linter.pack_shape checks the six tables’
row shapes at lint time in every lifecycle state. A malformed manifest
is a labeled failure, never a silent pass.
In this repo the pack definitions live in
packs/*.md
(packs/data-lineage.md, packs/numeric-predicates.md, …). The
frontmatter there is the source of truth for the pack inventory —
CI derives the pack set from it
(scripts/check_pack_doc_counts.py, the specodelic-typ0 gate) so
doc pages that talk about “the standard packs” cannot drift from what
actually ships. The in-repo packs are the five standard packs
(data.lineage, numeric.predicates, empirical.registry,
quant.finance, and svc.contracts — the last still draft) plus
the bioimage.data D6 pilot (the first cross-pack ## Requires
user). Each pack also has a capability spec in the rendered
corpus — e.g.
data-lineage-pack.
How a pack is enabled: corpus scan, not a config file
This is the part people coming from other tools trip on, so it is
stated plainly: there is no packs.toml, no config file, no
registry outside the corpus. Enablement is corpus-scan discovery:
- Every
kind: profilefile in the workspace is a candidate pack, found by scanning alone. Put akind: profilefile anywhere in the repo and it is a pack. - The scan is anchored at the git repository toplevel of each linted
directory (the lint target directory itself when no git root
exists). Every linted path contributes its anchor, so discovery is
order-independent:
spk lint spec/a.md spec/b.mdandspk lint spec/b.md spec/a.mddiscover the same pack set. Linting a subdirectory still discovers packs living anywhere in the workspace, because the anchor is the toplevel, not the subdirectory. - The pack’s frontmatter
id(e.g.data.lineage) is how everything else refers to it.
Activation is advisory-first and opt-in per file: a spec activates
a discovered pack either by using its vocabulary (a word-boundary
match; matching is longest-prefix) or by declaring a uses
reference to the pack’s id on a Constraint row. A pack file is never
a consumer of its own vocabulary — its manifest rows never trigger
its checkers. Identical vocabulary declared by two discovered packs
activates both, with findings attributed per pack.
What a pack adds
Once a pack is active for a file, the mechanism adds six things. All
of them are namespaced: pack vocabulary is pack-qualified
(data.dataset, not dataset), so two packs may independently claim
a same-named concept and never collide — collision resolves by
pack-qualified naming, not by error.
- Namespaced sections. The pack’s
## Sectionstable declares typed extra sections with a fixed row shape.data.lineagedeclares a## Datasection whose rows are| name | kind | dtype | units | binding |. - Fiber kinds. Pack-declared kinds usable in the closed
kindcolumns of base tables. The base closed sets never grow — the walkers accept a fiber kind only when its declaring pack is active for that file (base ∪ active-pack-fiber, never narrower); the same token without its pack is the labeledorphan_vocabularyfinding below. - Reference fields. Set-valued typed references such as
produced_by/consumed_by, resolving to declared rows of the same file (see the pack’s## Referencestable). - Pack-qualified checkers. Rules named inside the pack’s
namespace, e.g.
data.lineage.closure. Checkers ship as declarations — the mechanism interprets the declaration at activation and reports honestly. - Kind-dependent case-label floors. A pack’s
## Floorstable can require named cases for law-kind properties of specific fiber kinds (e.g.data.artifactlaws must carry aprovenance:case). - A
## Requirespin. Each pack pins the base format revision it was written against; skew with the workspace corpus is advisory (below), never a failure.
Two hard guarantees round this off:
- Byte-identical guarantee. A spec that uses none of the pack
vocabulary lints byte-identically to pre-mechanism behavior — no
pack advisory, no new findings, nothing. With zero
kind: profilefiles discovered, the machinery is inert. - Append-only packs. A pack release may only add vocabulary;
removing or narrowing a previously declared entry fails
linter.pack_shape, naming the entry and the append-only law. The same law keeps base closed sets frozen — a pack can never narrow or retype an existing entry.
Lifecycle and honest-empty checkers
A pack is draft, published, or deprecated (declared through the
pack’s own Model section). draft packs activate advisory-first with
the draft status named in the finding; deprecated packs keep checking
with the deprecation named. A checker with no workspace fixtures,
data, or vocabulary instances reports an empty checked-set —
honestly, with no fabricated discoveries. That is exactly what the
activation advisories below show: empty checked-set (no violations to report).
The CLI loop
Set up a scratch workspace with a pack file and a consumer spec (here:
data.lineage from the repo’s packs/ directory, and a lab-ETL
spec):
mkdir demo && cd demo && git init
cp /path/to/specodelic/packs/data-lineage.md .
spk lint — discovery and activation
A consumer spec that declares a typed ## Data table using the pack’s
vocabulary:
---
id: lab.etl
kind: intent
statement: "WHEN the lab runs its ETL pipeline THE system SHALL record every dataset the pipeline produces or consumes."
---
## Data
| name | kind | dtype | units | binding |
|-----------|---------------|-------|-------|------------------|
| raw_reads | data.dataset | fastq | reads | s3://lab/reads |
| counts | data.artifact | tsv | count | parquet://counts |
| row | produced_by | consumed_by |
|-----------|-------------|-------------|
| raw_reads | | counts |
| counts | raw_reads | qc_report |
Lint it (the rest of the four-layer file is elided here):
spk lint lab-etl.md
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory lab.etl [linter.observability] pack `data.lineage` activated for `lab.etl` (vocabulary match: data.dataset, data.artifact, Data, produced_by, consumed_by) — empty checked-set (no violations to report)
→ Run: specodelic graph
exit 0 — the pack was discovered by corpus scan (the git toplevel is the anchor) and activated by vocabulary match. The advisory is the activation note on the warnings channel: the pack’s declared rules ran and found nothing to report. No uses edge was needed.
The explicit route is a declared uses edge — a uses column on
a Constraint row pointing at the pack’s frontmatter id:
| id | kind | expr | traces_to | uses |
|----|-----------|----------|---------------|-----------------|
| c1 | invariant | `plain` | [[lab.etl]] | [[data.lineage]] |
lint: 1 file(s) linted, 0 finding(s), 2 advisory warning(s)
advisory lab.etl [linter.skew_advisory] pack `data.lineage` pins base Revision 14, workspace corpus is at Revision 18 — revision skew is advisory (never failing); consider updating the pack's `## Requires` base pin
advisory lab.etl [linter.observability] pack `data.lineage` activated for `lab.etl` (declared uses edge) — declared rules: 6; empty checked-set (no violations to report)
→ Run: specodelic graph
The skew_advisory fires because this copy of the pack pins an older
base revision than the corpus — a labeled advisory on the warnings
channel, never a failure.
spk lint — what failure looks like
A declared uses edge naming a pack that is not discovered — say
you lint the consumer file in a directory with no pack file in it —
fails with labeled findings, never a silent pass (exit 1):
lint: 1 file(s) linted, 2 finding(s), 0 advisory warning(s)
lab-etl.md [linter.total_refs] dangling reference `[[data.lineage]]` from constraints.uses — target not defined in any spec file
lab.etl [linter.orphan_vocabulary] orphan vocabulary: `uses` edge targets `data.lineage`, but no `kind: profile` pack with that id is discovered — candidate pack `data.lineage`; remediations: add/enable a `kind: profile` pack file declaring it, or fix the vocabulary (remove or retype the `uses` edge)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
The orphan_vocabulary finding always names the candidate
pack — prefix-derived from the vocabulary token’s namespace when only
the namespace is known (a data.* token with no data.lineage-like
pack discovered names the data.* namespace) — and states both
remediations: enable a pack declaring the vocabulary, or fix the
vocabulary. Pack-qualified tokens in a base table’s kind column
fire the same rule when their namespace has no discovered pack; the
pack-added section’s own rows stay inert without the pack. A
malformed pack manifest (wrong row shapes, a missing facet table, a
non-pack-qualified kind) is a linter.pack_shape labeled failure on
the pack file itself.
spk graph and spk compile — same loop, pack-aware
spk graph lab-etl.md
graph: 1 file(s), 5 node(s), 5 edge(s), 0 dangling, 0 typing violation(s), 0 supersedes cycle(s), 0 external boundary(ies)
→ Run: specodelic lint
spk compile lab-etl.md
compile: 1 compiled, 0 failed
lab-etl.md [lab.etl] → specodelic/lab-etl.toml, specodelic/lab-etl_props.rs, specodelic/lab-etl.tla
→ Run: specodelic verify (consumes the *_props.rs artifacts)
The declared uses edge resolves corpus-wide: with the pack file
in the linted corpus, both the dangling-reference finding and the
orphan_vocabulary finding clear. It is an outbound leaf — it
joins no reachability path and no acyclic edge set (mutual pack use
is well-formed), and v1’s graph projection records no edge row for
it: pack enablement is a lint-level fact. Compile and verify treat a
pack-enabled spec like any other: pack vocabulary adds no
compile-time artifacts, it extends what lint understands.
Where to go next
- The mechanism’s normative home: packs — domain-pack mechanism (every guarantee on this page is a constraint or property row there).
- The in-repo packs’ capability specs: data-lineage-pack, numeric-predicates-pack, empirical-registry-pack, bioimage-data-pack, quant-finance-pack, svc-contracts-pack.
- Writing a spec end-to-end (without packs): the worked example.
- Rule semantics offline:
spk explain packsandspk explain lint-rules.
graph
id: graph kind: intent checked_against_core: clear statement: “THE graph tool SHALL derive, from every typed reference field in the repo, a single authoritative adjacency structure over 𝒦’s kinds, kept as a generated artifact that is never hand-edited, and SHALL answer transitive-closure (blast-radius) queries against it without re-walking markdown per query.”
Graph
Every checker in this repo (linter-referential_integrity.md,
linter-graph_shape.md, rename.md) already walks the reference edges
specodelic.md’s Reference Typing table defines — but each does it
independently, on demand, by re-reading markdown. Nothing keeps a single
standing structure of “what points at what” that a tool could query
directly. This file specifies that structure: a derived artifact, not a
hand-authored one, so merge.md’s conflict check and refactor.md’s
fan-in signal can both query it instead of each re-deriving reachability
their own way — the same “one stated fact, not several hand-maintained
copies” discipline specodelic.md Revision 6 already applied to
append_only_variants, here applied to the graph itself rather than a
constraint.
Purpose
Every checker in this repo (linter-referential_integrity.md,
linter-graph_shape.md, rename.md) already walks the reference edges
specodelic.md’s Reference Typing table defines — but each does it
independently, on demand, by re-reading markdown. Nothing keeps a single
standing structure of “what points at what” that a tool could query
directly. This file specifies that structure: a derived artifact, not a
hand-authored one, so merge.md’s conflict check and refactor.md’s
fan-in signal can both query it instead of each re-deriving reachability
their own way — the same “one stated fact, not several hand-maintained
copies” discipline specodelic.md Revision 6 already applied to
append_only_variants, here applied to the graph itself rather than a
constraint.
Constraints
| id | kind | expr | traces_to | satisfies |
|———————————–|———–|——————————————————————————————————————————————————————|–––––––––––|
| graph_is_derived_not_authored | invariant | every edge in the graph artifact is produced by extraction from repo files; the artifact contains no information a hand edit to it could add that isn't already present in some file's reference field | [[graph]] | |
| total_extraction | invariant | ∀ reference field instance (traces_to/derives_from/guard/from/to/supersedes/emits/satisfies/observes) in any parsed file: exactly one corresponding edge exists in the graph — a guard’s conjunction of several [[id]] citations counts as one edge per citation, not one edge per transition; the enumeration restates the Reference Typing table’s field set (the authority — satisfies joined it in Revision 7) and links in non-reference columns (expr, predicate, frontmatter) are total_refs’ beat, never edges | [[graph]] | |
| edge_kind_matches_typing | invariant | ∀ edge (source, field, target): (source.kind, field, target.kind) ∈ [[specodelic]]'s Reference Typing table — the graph never records an edge the typing table wouldn't allow, whether or not the underlying file is otherwise well-formed | [[graph]] | |
| deterministic_derivation | invariant | re-deriving the graph from an unchanged repo produces a byte-identical artifact | [[graph]] | |
| blast_radius_is_transitive_closure | invariant | blast_radius(id) == the forward and backward transitive closure of edges reachable from id, computed entirely within the derived artifact — never by re-walking source files | [[graph]] | |
| stale_graph_detected | invariant | ∃ file whose on-disk content hash differs from the hash recorded at the artifact's last extraction ⟹ the artifact reports itself stale rather than silently serving an outdated query result | [[graph]] | |
| external_boundary_derived | invariant | a file is classified as an external boundary iff it hosts ≥1 extension_point Constraint — the classification is a pure derivation over extracted structure, never an authored tag: removing the rows removes the classification, so a stale boundary tag cannot exist | [[graph]] | |
| extraction_failure | effect | graph.extraction_failure(detail) | [[graph]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
unindexedextractingindexedqueryablefailed(emits:[[graph.extraction_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| extract | unindexed | extracting | [[specodelic.parsed]] — every file in the repo has independently reached that state (see specodelic.md’s own lifecycle) |
| extract_ok | extracting | indexed | [[graph.total_extraction]] ∧ [[graph.edge_kind_matches_typing]] |
| extract_fail | extracting | failed | ¬([[graph.total_extraction]] ∧ [[graph.edge_kind_matches_typing]]) |
| publish | indexed | queryable | [[graph.deterministic_derivation]] ∧ [[graph.graph_is_derived_not_authored]] |
| invalidate | queryable | unindexed | [[graph.stale_graph_detected]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| every_edge_extracted | unit | [[graph.total_extraction]] | arbitrary_spec_repo() | count(edges(graph)) == count(reference_field_instances(repo)) |
| wrongly_typed_edge_rejected | unit | [[graph.edge_kind_matches_typing]] | hand_inserted_edge(source_kind: State, field: "guard", target_kind: Property) | check(artifact) == rejected — guard targets an invariant Constraint or a State (specodelic.md Revision 12); a Property target is still rejected |
| rerun_deterministic | unit | [[graph.deterministic_derivation]] | run_extraction_twice_against_unchanged_repo() | artifact(run_1) == artifact(run_2) |
| blast_radius_transitive | unit | [[graph.blast_radius_is_transitive_closure]] | chain(a traces_to b, b derives_from c) | c ∈ blast_radius(a) — reachable through two hops, no direct edge required |
| blast_radius_bidirectional | unit | [[graph.blast_radius_is_transitive_closure]] | edge(a → b) | a ∈ blast_radius(b) ∧ b ∈ blast_radius(a) — renaming a must be able to find dependent b, and vice versa |
| stale_detected_on_edit | unit | [[graph.stale_graph_detected]] | edit_one_reference_field_without_re_extracting() | check(artifact) == stale |
| hand_authored_edge_rejected | unit | [[graph.graph_is_derived_not_authored]] | graph_artifact_with_a_manually_inserted_edge_absent_from_any_file() | check(artifact) == rejected |
| boundary_tracks_extension_points | unit | [[graph.external_boundary_derived]] | file_hosting_extension_point_row_then_all_removed() | classified(file) == true before, == false after removal — no authored residue |
| observes_edge_extracted | unit | [[graph.total_extraction]] | constraint_row_carrying_observes_pointing_at_effect() | exactly one edge, kind constraints.observes — and zero edges when the column is absent |
| extraction_failure_label_asserted | unit | [[graph.extraction_failure]] | extraction_failure_raised() | error_label == "graph.extraction_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
checked_against_core: clear (see AGENTS.md’s convention). One resolved
ambiguity worth recording anyway: total_extraction counts each [[id]]
citation inside a conjunctive guard (A ∧ B ∧ C) as its own edge, not the
whole guard as one edge — otherwise blast_radius would under-count what a
transition actually depends on.
observes (specodelic.md Revision 9) joins total_extraction but no
cycle set. An observation claim is not a dependency: the acyclic
invariant’s edge set (linter-graph_shape.md) is a closed union that
observes deliberately does not join — satisfies and emits are
excluded by the same closure — so mutual cross-file observation is
well-formed. The external-boundary classification is the same
derived-not-authored discipline applied to visibility: a file is an
external boundary because it published a contract, and the fact
disappears when the publication does.
Scope boundary, Needs Human Review: this file does not replace
linter-referential_integrity.md’s total_refs/unique_id or
linter-graph_shape.md’s acyclic_traces/single_root_reachable — those
checkers still own those constraints. What’s genuinely open is whether
those two files should be refactored to query this artifact internally
rather than independently re-deriving reachability, now that a shared
artifact exists. That’s an efficiency and single-source-of-truth question,
not a correctness gap, so it’s flagged rather than forced — changing
either linter file’s internal mechanism without changing what it checks
is exactly the kind of edit AGENTS.md’s workflow should route through a
deliberate decision, not a side effect of adding this file.
Why extraction only requires parsed, not linted. Building the
adjacency structure needs a reference field’s syntactic presence, not its
resolution — a dangling [[typo_id]] still produces an edge (to a target
that doesn’t yet exist), which is precisely the shape total_refs needs
to check against. Requiring linted first would make the graph unable to
represent the very files whose brokenness the existing checkers need to
detect.
merge.md and refactor.md both depend on this file for blast-radius and
fan-in queries respectively, rather than each re-deriving reachability —
see each file’s own Constraints for how.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: every_edge_extracted
- WHEN
arbitrary_spec_repo() - THEN
count(edges(graph)) == count(reference_field_instances(repo)) - VERIFIES [[graph.every_edge_extracted]]
Scenario: wrongly_typed_edge_rejected
- WHEN
hand_inserted_edge(source_kind: State, field: "guard", target_kind: Property) - THEN
check(artifact) == rejected—guardtargets an invariant Constraint or a State (specodelic.md Revision 12); a Property target is still rejected - VERIFIES [[graph.wrongly_typed_edge_rejected]]
Scenario: rerun_deterministic
- WHEN
run_extraction_twice_against_unchanged_repo() - THEN
artifact(run_1) == artifact(run_2) - VERIFIES [[graph.rerun_deterministic]]
Scenario: blast_radius_transitive
- WHEN
chain(a traces_to b, b derives_from c) - THEN
c ∈ blast_radius(a)— reachable through two hops, no direct edge required - VERIFIES [[graph.blast_radius_transitive]]
Scenario: blast_radius_bidirectional
- WHEN
edge(a → b) - THEN
a ∈ blast_radius(b) ∧ b ∈ blast_radius(a)— renamingamust be able to find dependentb, and vice versa - VERIFIES [[graph.blast_radius_bidirectional]]
Scenario: stale_detected_on_edit
- WHEN
edit_one_reference_field_without_re_extracting() - THEN
check(artifact) == stale - VERIFIES [[graph.stale_detected_on_edit]]
Scenario: hand_authored_edge_rejected
- WHEN
graph_artifact_with_a_manually_inserted_edge_absent_from_any_file() - THEN
check(artifact) == rejected - VERIFIES [[graph.hand_authored_edge_rejected]]
Scenario: boundary_tracks_extension_points
- WHEN
file_hosting_extension_point_row_then_all_removed() - THEN
classified(file) == true before, == false after removal — no authored residue - VERIFIES [[graph.boundary_tracks_extension_points]]
Scenario: observes_edge_extracted
- WHEN
constraint_row_carrying_observes_pointing_at_effect() - THEN
exactly one edge, kind constraints.observes — and zero edges when the column is absent - VERIFIES [[graph.observes_edge_extracted]]
Scenario: extraction_failure_label_asserted
- WHEN
extraction_failure_raised() - THEN
error_label == "graph.extraction_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[graph.extraction_failure_label_asserted]]
compile
id: compile kind: intent checked_against_core: clear statement: “THE Compile transition SHALL translate a linted-and-covered spec file’s Constraints table, Model section, and Properties table into a TOML document, a TLA+ module, and a set of proptest! blocks respectively, without dropping or altering any id.”
Compile
specodelic.md names compile as the transition from linted to
compiled, gated on coverage and law_requires_cases, but never
specifies what compiling actually produces (STATUS.md §4, P0). This
file is that specification: Compile is the functor Set^𝒦 → TOML
(constraints), Set^𝒦 → TLA+ (the model), and
Set^𝒦 → proptest! (properties) that STATUS.md §1 describes — one
functor with three target categories, not three unrelated tools that
happen to run at the same pipeline stage.
Purpose
specodelic.md names compile as the transition from linted to
compiled, gated on coverage and law_requires_cases, but never
specifies what compiling actually produces (STATUS.md §4, P0). This
file is that specification: Compile is the functor Set^𝒦 → TOML
(constraints), Set^𝒦 → TLA+ (the model), and
Set^𝒦 → proptest! (properties) that STATUS.md §1 describes — one
functor with three target categories, not three unrelated tools that
happen to run at the same pipeline stage.
Constraints
| id | kind | expr | traces_to | satisfies |
|---|---|---|---|---|
| precondition_satisfied | invariant | Compile only runs on a file that has passed both lint and coverage — restates [[specodelic.compile]]’s own guard (transition, not row) | [[compile]] | |
| constraint_table_to_toml | invariant | every Constraint row compiles to one TOML table entry {id, kind, expr, traces_to}, field-for-field, with no lossy transformation | [[compile]] | |
| model_to_tla | invariant | the Model section compiles to one TLA+ module — always emitted, regardless of which model_check backend (native stateright or TLC) later runs against the model: the module opens with a single-line TLA+ module header (dashes + MODULE name + dashes — the only form an engine parses); each State becomes a value in the module's state variable's range, each Transition becomes one disjunct of the Next action guarded by its guard field, and one closing stuttering disjunct (UNCHANGED vpc) ends Next — guards travel as prose comments, so a terminal state must not read as an engine-side deadlock; a State with an emitsfield additionally becomes one entry in anOutputfunction from that state value to the effect-kind Constraint'sexpr— absent for a state with noemits, never a default/null entry | [[compile]] | |
| properties_to_proptest | invariant | each Property row compiles to one proptest! block: generator becomes the block's input strategy, predicate becomes its assertion body — verbatim as a **rust:** fragment when the row opts in ([[compile.predicate_fragment_opt_in]]), else the un-translated todo_predicate! placeholder that compiles here and fails only at execution; a law-kind property compiles to one block per case enumerated in its predicate's **name:** case labels (the identity and associativity floor is lint-enforced ahead of compile, specodelic.md Revision 13) | [[compile]] | |
| predicate_fragment_opt_in | invariant | a Property's predicate cell may opt into executable translation with exactly one **rust:** marker and a non-empty fragment after it — the fragment is a Rust boolean expression emitted verbatim as the block's assertion body, the cell text after the marker — to the cell's end or the code span's closing backtick — is the fragment, binding the block's generated values (v0…, one per generator named in the generator cell, each a String); the marker opts in only in fragment position — starting the cell or immediately opening the fragment's code span, a backtick directly before it — an occurrence anywhere else (mid-span, as in the defining rows of this very table, or in prose between spans) is a mention of the mechanism and never extracts (specodelic-sd1); a cell with no marker compiles exactly as before (the placeholder) — pure widening, nothing valid before this Revision is invalidated | [[compile]] | |
| invariant_fragment_opt_in | invariant | a Constraint's expr cell may carry the same **rust:** fragment when the row's kind is invariant, under the same fragment-position rule as [[compile.predicate_fragment_opt_in]] — the fragment becomes an executable invariant model_check's native backend executes (model_check.md Revision 15); a fragment on a Constraint of any other kind (effect, advisory, a pack fiber kind) is a labeled extraction failure, never a silently ignored marker | [[compile]] | |
| fragment_law_rejected | invariant | a law-kind Property row's predicate cell must not carry a **rust:** fragment — each required case needs its own assertion body and one fragment cannot honestly serve several named cases; violation is a labeled extraction failure | [[compile]] | |
| fragment_guard_rejected | invariant | a Transition's guard cell must not carry a **rust:** fragment in this Revision — the program-counter model has no data binding a guard could constrain, so executable guards have no defined semantics; violation is a labeled extraction failure, never a silently ignored marker (decision of record: deferred to a future Revision alongside a data-carrying state space) | [[compile]] | |
| fragment_language_closed | invariant | the executable-fragment tag set is closed: a cell in fragment position may carry exactly one **rust:**, **py:**, or **ts:** marker per the grammar below — the same closed-set discipline as [[specodelic.property_kind_closed]] and [[specodelic.constraint_kind_closed]]; the fragment-position rule and the non-empty-fragment requirement carry over verbatim per tag, and **rust:** remains the fully specified case with byte-identical semantics (widening [[compile.predicate_fragment_opt_in]]/[[compile.invariant_fragment_opt_in]]'s grammar; [[specodelic-lf3]] Revision 16) | [[compile]] | |
| unknown_tag_rejected | invariant | a marker in fragment position whose tag is not in the closed set — **go:**, **java:**, any non-member — is a labeled extraction failure naming the unknown tag and the closed set, never a silently ignored marker (sd1's no-silent-markers discipline carried into the widened grammar; [[specodelic-lf3]] Revision 16) | [[compile]] | |
| kernel_expr_opt_in | invariant | an expr cell may opt into kernel translation per cell with exactly one **kernel:** marker and a non-empty kernel expression after it, under the same fragment-position rule as [[compile.predicate_fragment_opt_in]] — the expression is parsed against the closed atomic grammar (equality, comparisons, bounded ∀/∃ over the finite instances, ∧/¬, and the reference-typed atomics resolves/unique/acyclic/reachable; the closed set's name is kernel_grammar_closed) and evaluates three-valued per model_check.md; an expression using a non-member atomic fails labeled naming the atomic and the closed set — never prose, never silently ignored; a cell without the marker compiles exactly as before (pure widening, nothing valid before this Revision is invalidated; add-min-expr-kernel Revision 17) | [[compile]] | |
| kernel_binding_opaque | invariant | an invariant-kind Constraint's kernel.binding cell extracts as an opaque string surfaced verbatim in the compiled Constraints artifact — no parse, no validation, no interpretation of its internals by the toolchain — and the claim path for an external checker is its contract-TOML flags binding (a test node id, a filter expression, or a shell command payload), not a toolchain registry; a row with no binding column compiles exactly as before (design D4, add-min-expr-kernel Revision 17) | [[compile]] | |
| rust_back_compat | invariant | every cell that extracted a **rust:** fragment under Revision 15's grammar extracts a byte-identical fragment under the widened grammar, and a cell without any marker compiles exactly as before — pure widening, nothing valid at Revision 15 is invalidated ([[specodelic-lf3]] Revision 16) | [[compile]] | |
| no_emitter_labeled_failure | invariant | a **py:** or **ts:** fragment in fragment position fails compile labeled, with a remediation hint naming the per-language emitter follow-up — executable emission for those languages is deferred of record ([[specodelic-lf3]] Revision 16, follow-ups add-py-fragment-emission/add-ts-fragment-emission); an accepted tag without an emitter would silently fall through to Rust, the vacuous outcome the closed set exists to prevent | [[compile]] | |
| mention_not_extraction | invariant | a **rust:**/**py:**/**ts:** occurrence anywhere other than fragment position — mid-span, as in the defining rows of this very table, or in prose between spans — is a mention of the mechanism and never extracts (sd1's rule carried into the widened grammar; [[specodelic-lf3]] Revision 16) | [[compile]] | |
| fragment_hygiene | invariant | a fragment's text must not contain unsafe, extern, include!, include_str!, include_bytes!, std::fs, std::process, std::net, std::env, asm!, or Command — defense-in-depth against a fragment escaping its artifact's role (fragments run on the invoking user's machine with the invoking user's privileges, exactly like every other compiled artifact; the Rust compiler is not a sandbox); violation is a labeled extraction failure naming the token | [[compile]] | |
| compile_is_total | invariant | ∀ file that has passed lint and coverage: Compile either produces all three artifacts or reports, for exactly one of them, which stage failed and why — it never returns a silent partial result | [[compile]] | |
| compile_preserves_ids | invariant | every id present in the source file appears, unchanged, in at least one compiled artifact — no id is silently dropped in translation | [[compile]] | |
| no_semantic_drift | invariant | re-parsing a compiled artifact and re-emitting it yields output identical to the original compile — Compile is idempotent under its own round trip | [[compile]] | |
| extraction_failure | effect | compile.extraction_failure(row_id, reason) — the extraction stage failed on row row_id because reason | [[compile]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| emission_failure | effect | compile.emission_failure(detail) — the emission stage failed because detail | [[compile]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
not_startedextractingemittingcompiledextract_failed(emits:[[compile.extraction_failure]])emit_failed(emits:[[compile.emission_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| begin | not_started | extracting | [[compile.precondition_satisfied]] |
| extract_ok | extracting | emitting | [[compile.constraint_table_to_toml]] ∧ [[compile.model_to_tla]] ∧ [[compile.properties_to_proptest]] |
| extract_fail | extracting | extract_failed | ¬([[compile.constraint_table_to_toml]] ∧ [[compile.model_to_tla]] ∧ [[compile.properties_to_proptest]]) |
| accept | emitting | compiled | [[compile.compile_preserves_ids]] ∧ [[compile.no_semantic_drift]] |
| reject | emitting | emit_failed | ¬([[compile.compile_preserves_ids]] ∧ [[compile.no_semantic_drift]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| precondition_violation_rejected | unit | [[compile.precondition_satisfied]] | file_that_has_not_passed_lint() | check(file) == failed |
| toml_roundtrips | unit | [[compile.constraint_table_to_toml]] | arbitrary_constraint_row() | parse_toml(compile(row)) == row |
| tla_disjunct_count_matches | unit | [[compile.model_to_tla]] | arbitrary_model_section(n_transitions) | count(disjuncts(compile(model).Next)) == n_transitions + 1 — the closing stuttering disjunct |
| output_function_covers_emitting_states_only | unit | [[compile.model_to_tla]] | arbitrary_model_section(n_states_with_emits, n_states_without) | domain(compile(model).Output) == the_states_with_emits — no entry for the rest |
| proptest_block_per_property | unit | [[compile.properties_to_proptest]] | arbitrary_property_row() | count(compile(row).blocks) ≥ 1 |
| law_property_compiles_required_cases | unit | [[compile.properties_to_proptest]] | law_property_row_with(cases: ["identity", "associativity"]) | count(compile(row).blocks) == 2 |
| compile_completes_or_fails_cleanly | unit | [[compile.compile_is_total]] | arbitrary_linted_and_covered_file() | compile(file) ∈ {all_three_artifacts, single_labeled_failure} — never partial |
| id_preservation_holds | unit | [[compile.compile_preserves_ids]] | arbitrary_linted_and_covered_file() | ids_in(file) ⊆ ids_in(compile(file)) |
| roundtrip_stable | unit | [[compile.no_semantic_drift]] | arbitrary_linted_and_covered_file() | compile(file) == compile(parse(compile(file))) |
| extraction_failure_label_asserted | unit | [[compile.extraction_failure]] | extraction_stage_fails_on_row() | error_label == "compile.extraction_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| emission_failure_label_asserted | unit | [[compile.emission_failure]] | emission_stage_fails() | error_label == "compile.emission_failure" — same three-site rename rule (EDGE-002) |
| fragment_body_emitted_verbatim | unit | [[compile.predicate_fragment_opt_in]] | property_row_with_rust_fragment() | the emitted block body contains the fragment verbatim |
| prose_predicate_stays_placeholder | unit | [[compile.predicate_fragment_opt_in]] | property_row_without_fragment() | compile(row).body contains todo_predicate! — no widening of pre-Revision behavior |
| fragment_values_bind_generators | unit | [[compile.predicate_fragment_opt_in]] | property_row_with_two_generators() | the emitted block's args are v0, v1 — one per named generator, each a String |
| rust_fragment_semantics_unchanged | unit | [[compile.rust_back_compat]] | revision_15_rust_fragment_cell() | extract(cell) == revision_15_extract(cell) — byte-identical, pure widening |
| unknown_tag_labeled | unit | [[compile.unknown_tag_rejected]] | property_row_with_marker_tag("**go:") | error_stage == fragment_extraction ∧ message names the tag and the closed set |
| non_fragment_tag_is_mention | unit | [[compile.mention_not_extraction]] | property_row_with_mid_span_marker_tag("**py:") | compile(row) == compile(row_without_marker) — mid-span occurrence never extracts |
| one_tag_extracts_per_cell | unit | [[compile.fragment_language_closed]] | property_row_with_marker_tag("**py:") | extract(cell) == Some(fragment_under("py")) — a fragment-position cell extracts under exactly one tag from the closed set {rust, py, ts}; acceptance is grammar-level and emitter-independent |
| missing_emitter_labeled | unit | [[compile.no_emitter_labeled_failure]] | property_row_with_marker_tag("**py:") | error_label == "compile.emission_failure" ∧ the remediation hint names the per-language emitter follow-up — never a silent fall-through to Rust emission |
| law_fragment_labeled | unit | [[compile.fragment_law_rejected]] | law_row_with_fragment() | error_stage == fragment_extraction |
| hygiene_violation_labeled | unit | [[compile.fragment_hygiene]] | fragment_with(banned_token: "std::fs") | error_stage == fragment_extraction ∧ message names the token |
| guard_fragment_labeled | unit | [[compile.fragment_guard_rejected]] | transition_row_with_fragment_guard() | error_stage == fragment_extraction |
| non_invariant_fragment_labeled | unit | [[compile.invariant_fragment_opt_in]] | effect_row_with_fragment() | error_stage == fragment_extraction |
| kernel_expr_extracts | unit | [[compile.kernel_expr_opt_in]] | invariant_cell_with_kernel_marker() | the expression extracts under the closed grammar and evaluates to exactly one of verified, counterexample, unknown |
| kernel_nonmember_labeled | unit | [[compile.kernel_expr_opt_in]] | invariant_cell_with_nonmember_atomic() | error_stage == kernel_grammar ∧ the message names the offending atomic and the closed set |
| binding_text_surfaces_verbatim | unit | [[compile.kernel_binding_opaque]] | constraint_row_with_arbitrary_binding_text() | the compiled TOML carries the binding cell text verbatim — no interpretation, no validation of its internals |
| claim_path_is_flags | unit | [[compile.kernel_binding_opaque]] | external_checker_claiming_a_constraint() | the claim is carried by contract-TOML flags binding; the toolchain builds no registry |
Notes
This file is what makes specodelic.md’s rename_naturality law fully
stated. That property’s naturality case reads
compile(rename(I)) == rename(compile(I)) — until now that referenced a
compile function with no specification of its own, so the claim was
unfalsifiable in practice even though it was written down. With
constraint_table_to_toml, model_to_tla, and properties_to_proptest
now pinned down field-for-field, compile(·) in that law means something
concrete: three specific, checkable translations, not an unnamed black
box.
compile_is_total’s “never a silent partial result” is the reason
extracting/emitting are split into two states rather than one: a
failure during extraction (a malformed Constraint/Model/Property row) and
a failure during emission (the three extracted representations don’t
agree well enough to produce one coherent output — e.g. a Transition’s
guard references a Constraint id that extraction resolved but the TOML
and TLA+ passes disagree about its type) are different failure classes,
and collapsing them into one failed transition would have been the same
God-transition pattern specodelic.md Revision 2 already removed from
lint.
Updated for specodelic.md Revision 6: model_to_tla now also
compiles a State’s optional emits field into a small Output function
alongside Next — the piece that turns a compiled automaton into a
compiled Moore machine. This is additive to the existing translation,
not a restructuring of it: a Model with no emits fields anywhere
compiles exactly as before, Output is simply empty.
checked_against_core: clear (see AGENTS.md’s convention) — every
constraint here traces to this file’s own intent rather than to a
top-level invariant, the same shape linter-frontmatter.md through
linter-coverage.md use for constraints scoped entirely to one feature.
The .tla module is a product of compile alone, not of any checker
choice. model_check may run a native interpreter backend that never
reads the module (see model_check.md’s Notes on backends), but the
module is still emitted: it is the human-reviewable, engine-portable form
of the compiled model, and TLC — the opt-in reference engine — consumes it
directly. Correspondingly the corpus language no longer offers “or
Alloy”: with a native default backend and a reference TLA+ engine it had
no remaining role, and dropping it keeps model_to_tla a single, fully
specified translation.
model_check and verify (STATUS.md §4, both now done — see
model_check.md and verify.md) both consume this
file’s output directly: model_check runs a model-check backend against
the TLA+ module model_to_tla produces, and verify runs the
proptest! blocks properties_to_proptest produces — each specified in
its own checker file.
Executable predicate fragments (specodelic.md Revision 15, specodelic-rjb).
Every predicate and expr cell may now opt into executable translation with a
**rust:** marker — the cell text after the marker is a Rust boolean
expression, emitted verbatim, no mini-language in between. This closes the
gap the compile functor shipped with: a predicate was compiled to a
todo_predicate! placeholder that compiles and panics at execution, so
verify’s properties gate failed honestly but could never pass. The four
rejection rows keep the marker honest: a fragment on a law row, a
non-invariant Constraint, or a Transition guard is a labeled extraction
failure, not a silently ignored marker (guards have no binding over the
program-counter model — deferred of record). fragment_hygiene is the
security/totality guard, deliberately defense-in-depth rather than a
sandbox: fragments run on the invoking user’s machine with the invoking
user’s privileges, the same trust every compiled artifact already had —
the banned-token list catches the escape hatches a table cell should never
legitimately need. The scaffolding’s element type is now String (the
GenVal tuple-struct wrapper retired in the same Revision) so fragments
bind plain generator values — the wrapper existed only to carry the
placeholder story, and fragments make that story optional.
The closed language-tag grammar (specodelic.md Revision 16, specodelic-lf3).
The opt-in marker’s tag is drawn from the closed set {rust, py, ts};
a marker is ** + a tag-shaped label + :** where the tag matches
[a-z][a-z0-9_-]* — the lowercase-initial shape is what separates a
marker from ordinary bold prose (**Note:** stays prose). Acceptance
is grammar-level and emitter-independent: **py:** and **ts:**
extract like **rust:**, but gate compile labeled
(no_emitter_labeled_failure) until their emitters land, since an
accepted tag without an emitter would silently fall through to Rust —
the vacuous outcome the closed set exists to prevent. A tag-shaped
marker outside the closed set fails labeled, naming the tag and the
closed set (unknown_tag_rejected). The one grammar collision is
resolved by row kind: a law-kind predicate’s **label:** occurrences
are the law-case grammar (Revision 13), never fragment markers, so the
fragment grammar exempts law rows; every other fragment-position
occurrence is checked.
Two binding layers, one per tool, never merged (decision of record
2026-10-04, specodelic-lf3 — corrected review finding CORR-001, design
openspec/changes/add-language-neutral-property-binding D3): the
artifact’s // id: / // case: metadata comments and contract-TOML
flags are not two spellings of one neutral ABI. The comments key
per-block verdicts inside a specodelic artifact — properties_to_proptest
emits them, properties_to_proptest’s own metadata comments key them, and
nothing outside this file’s emission consumes them. Contract-TOML flags
bind existing tests — pytest node ids, shell escape hatches — to
scenarios, which is espectacular’s layer and stays there. Ownership:
compile.md owns fragment extraction and artifact emission (the
closed-tag grammar above); espectacular owns contract binding of
existing tests (its [runners] config, already shipped — no parallel
registry is built here, CORR-002); specodelic.md owns the format
grammar (Revision 16’s closed set). Nothing in this file consumes or
produces espectacular contract binding — merging the layers would couple
scaffold execution policy to contract-binding policy with different
wall-clock, caching, and toolchain stories.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: precondition_violation_rejected
- WHEN
file_that_has_not_passed_lint() - THEN
check(file) == failed - VERIFIES [[compile.precondition_violation_rejected]]
Scenario: toml_roundtrips
- WHEN
arbitrary_constraint_row() - THEN
parse_toml(compile(row)) == row - VERIFIES [[compile.toml_roundtrips]]
Scenario: tla_disjunct_count_matches
- WHEN
arbitrary_model_section(n_transitions) - THEN
count(disjuncts(compile(model).Next)) == n_transitions + 1— the closing stuttering disjunct - VERIFIES [[compile.tla_disjunct_count_matches]]
Scenario: output_function_covers_emitting_states_only
- WHEN
arbitrary_model_section(n_states_with_emits, n_states_without) - THEN
domain(compile(model).Output) == the_states_with_emits— no entry for the rest - VERIFIES [[compile.output_function_covers_emitting_states_only]]
Scenario: proptest_block_per_property
- WHEN
arbitrary_property_row() - THEN
count(compile(row).blocks) ≥ 1 - VERIFIES [[compile.proptest_block_per_property]]
Scenario: law_property_compiles_required_cases
- WHEN
law_property_row_with(cases: ["identity", "associativity"]) - THEN
count(compile(row).blocks) == 2 - VERIFIES [[compile.law_property_compiles_required_cases]]
Scenario: compile_completes_or_fails_cleanly
- WHEN
arbitrary_linted_and_covered_file() - THEN
compile(file) ∈ {all_three_artifacts, single_labeled_failure}— never partial - VERIFIES [[compile.compile_completes_or_fails_cleanly]]
Scenario: id_preservation_holds
- WHEN
arbitrary_linted_and_covered_file() - THEN
ids_in(file) ⊆ ids_in(compile(file)) - VERIFIES [[compile.id_preservation_holds]]
Scenario: roundtrip_stable
- WHEN
arbitrary_linted_and_covered_file() - THEN
compile(file) == compile(parse(compile(file))) - VERIFIES [[compile.roundtrip_stable]]
Scenario: extraction_failure_label_asserted
- WHEN
extraction_stage_fails_on_row() - THEN
error_label == "compile.extraction_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[compile.extraction_failure_label_asserted]]
Scenario: emission_failure_label_asserted
- WHEN
emission_stage_fails() - THEN
error_label == "compile.emission_failure"— same three-site rename rule (EDGE-002) - VERIFIES [[compile.emission_failure_label_asserted]]
Scenario: fragment_body_emitted_verbatim
- WHEN
property_row_with_rust_fragment() - THEN
the emitted block body contains the fragment verbatim - VERIFIES [[compile.fragment_body_emitted_verbatim]]
Scenario: prose_predicate_stays_placeholder
- WHEN
property_row_without_fragment() - THEN
compile(row).body contains todo_predicate!— no widening of pre-Revision behavior - VERIFIES [[compile.prose_predicate_stays_placeholder]]
Scenario: fragment_values_bind_generators
- WHEN
property_row_with_two_generators() - THEN
the emitted block's args are v0, v1 — one per named generator, each a String - VERIFIES [[compile.fragment_values_bind_generators]]
Scenario: rust_fragment_semantics_unchanged
- WHEN
revision_15_rust_fragment_cell() - THEN
extract(cell) == revision_15_extract(cell)— byte-identical, pure widening - VERIFIES [[compile.rust_fragment_semantics_unchanged]]
Scenario: unknown_tag_labeled
- WHEN
property_row_with_marker_tag("**go:") - THEN
error_stage == fragment_extraction ∧ message names the tag and the closed set - VERIFIES [[compile.unknown_tag_labeled]]
Scenario: non_fragment_tag_is_mention
- WHEN
property_row_with_mid_span_marker_tag("**py:") - THEN
compile(row) == compile(row_without_marker)— mid-span occurrence never extracts - VERIFIES [[compile.non_fragment_tag_is_mention]]
Scenario: one_tag_extracts_per_cell
- WHEN
property_row_with_marker_tag("**py:") - THEN
extract(cell) == Some(fragment_under("py"))— a fragment-position cell extracts under exactly one tag from the closed set {rust, py, ts}; acceptance is grammar-level and emitter-independent - VERIFIES [[compile.one_tag_extracts_per_cell]]
Scenario: missing_emitter_labeled
- WHEN
property_row_with_marker_tag("**py:") - THEN
error_label == "compile.emission_failure" ∧ the remediation hint names the per-language emitter follow-up— never a silent fall-through to Rust emission - VERIFIES [[compile.missing_emitter_labeled]]
Scenario: law_fragment_labeled
- WHEN
law_row_with_fragment() - THEN
error_stage == fragment_extraction - VERIFIES [[compile.law_fragment_labeled]]
Scenario: hygiene_violation_labeled
- WHEN
fragment_with(banned_token: "std::fs") - THEN
error_stage == fragment_extraction ∧ message names the token - VERIFIES [[compile.hygiene_violation_labeled]]
Scenario: guard_fragment_labeled
- WHEN
transition_row_with_fragment_guard() - THEN
error_stage == fragment_extraction - VERIFIES [[compile.guard_fragment_labeled]]
Scenario: non_invariant_fragment_labeled
- WHEN
effect_row_with_fragment() - THEN
error_stage == fragment_extraction - VERIFIES [[compile.non_invariant_fragment_labeled]]
Scenario: kernel_expr_extracts
- WHEN
invariant_cell_with_kernel_marker() - THEN
the expression extracts under the closed grammar and evaluates to exactly one of verified, counterexample, unknown - VERIFIES [[compile.kernel_expr_extracts]]
Scenario: kernel_nonmember_labeled
- WHEN
invariant_cell_with_nonmember_atomic() - THEN
error_stage == kernel_grammar ∧ the message names the offending atomic and the closed set - VERIFIES [[compile.kernel_nonmember_labeled]]
Scenario: binding_text_surfaces_verbatim
- WHEN
constraint_row_with_arbitrary_binding_text() - THEN
the compiled TOML carries the binding cell text verbatim — no interpretation, no validation of its internals - VERIFIES [[compile.binding_text_surfaces_verbatim]]
Scenario: claim_path_is_flags
- WHEN
external_checker_claiming_a_constraint() - THEN
the claim is carried by contract-TOML flags binding; the toolchain builds no registry - VERIFIES [[compile.claim_path_is_flags]]
model_check
id: model_check kind: intent checked_against_core: clear statement: “THE model_check step SHALL run a model-check backend — stateright (embedded, default) or TLC (opt-in) — against the model compile.md’s model_to_tla produces, and SHALL report either that no counterexample was found within a stated bound, or a minimal counterexample trace naming the violated invariant.”
Model check
specodelic.md names model_check as the compiled → model_checked
transition, guarded only by model_present — a structural check that
a Model section exists. Whether the model actually holds
(no_counterexample) is a separate invariant, consumed later by the
verify transition’s guard, not by model_check itself. That split was
never made explicit anywhere (STATUS.md §4, P0): this file specifies
the run model_check names — what “invoking the checker” actually does
to the module compile.md produces, and what a stated result must
contain.
Purpose
specodelic.md names model_check as the compiled → model_checked
transition, guarded only by model_present — a structural check that
a Model section exists. Whether the model actually holds
(no_counterexample) is a separate invariant, consumed later by the
verify transition’s guard, not by model_check itself. That split was
never made explicit anywhere (STATUS.md §4, P0): this file specifies
the run model_check names — what “invoking the checker” actually does
to the module compile.md produces, and what a stated result must
contain.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| checker_invoked | invariant | reaching model_checked always corresponds to an actual checker run, by the selected backend, against the current compiled model — model_present's structural check is not itself that run | [[model_check]] |
| exhaustive_within_bound | invariant | a run explores all reachable states up to a stated bound (state-space size or step count) before it may report no counterexample; the bound is part of the report, since unbounded checking of a spec with no finite state space never terminates | [[model_check]] |
| counterexample_is_minimal | invariant | when a run finds a counterexample, it reports the shortest violating transition sequence found, not merely the first one encountered | [[model_check]] |
| counterexample_names_violated_invariant | invariant | a counterexample report names exactly one violated invariant, by its Constraints-table id, together with the full state trace leading to it | [[model_check]] |
| backend_identified | invariant | a run report names the backend engine and version that produced it, so two backends' reports on the same compiled model and bound are attributable and comparable | [[model_check]] |
| no_counterexample_feeds_verify | invariant | [[specodelic.no_counterexample]] holds iff the most recent run on the current compiled module reported clean within its stated bound | [[model_check]] |
| rerun_on_model_change | invariant | a run's clean result does not satisfy [[specodelic.no_counterexample]] once the Model section (States or Transitions) or any executable invariant fragment has changed since that run — the model must be re-run, not assumed still clean (the fragment extension is specodelic.md Revision 15; the compiled module carries the fragment manifest, so the artifact hash covers it) | [[model_check]] |
| executable_invariants_execute | invariant | the native backend executes every executable invariant fragment (the **rust:** marker in a kind == invariant Constraint's expr — specodelic.md Revision 15) as an invariant over the compiled model: each evaluated at every reachable state, each named by its Constraints-table id in the report's invariants_checked. Fragments are Rust, compiled verbatim — executed by a scratch-crate run, since the interpreter backend cannot evaluate user Rust in-process; a backend that cannot execute fragments reports exploration_only, never a fabricated clean | [[model_check]] |
| invariant_totality | invariant | an executable invariant fragment evaluation that panics reports the invariant violated at the state where the panic occurred — a panicking predicate is never a pass and never silently skipped | [[model_check]] |
| required_claims_classified | invariant | a run partitions each parsed file's invariant-kind Constraints into a required set — those opted into executable (rust: fragment), kernel, or citation evaluation — and an unchecked set of prose-only invariants: prose wording, a constraint's name, or merely having a deriving Property never implies evaluation, and advisory/effect/extension-point rows are never required | [[model_check]] |
| claim_aggregate_governs | invariant | the run outcome is governed by the required claims' statuses with fixed priority: any refuted required claim yields counterexample_found naming it; absent refutation, an exhausted time/state/depth budget yields timed_out; otherwise any unknown, unsupported, or missing required claim yields exploration_only with reasons — and so does an empty required set; a nonempty required set all verified over a completed bounded exploration is the only clean | [[model_check]] |
| claim_report_schema | invariant | the run's persisted report declares claim_schema_version 1, canonical qualified claim records (id, evaluator kind, status, reason where not verified), expected_claim_ids, unchecked_claim_ids, and a scope_sha256 digest of the parsed structured content and the consumed compiled artifacts — the digest binds structured content, not filesystem paths: reordering CLI paths or editing prose alone preserves it, while two inputs differing in invariant content never share one | [[model_check]] |
| corpus_identity_scope | invariant | a model-check, verify, or orchestrate invocation refuses, before compilation, evaluation, or report writes, any input set in which two parsed files claim the same intent id — labeled duplicate_corpus_identity with a rename hint (Revision 18: the former isolated-scope rule retired with id: spec itself; dual-format files carry real intent ids and compose like any corpus, so identity uniqueness is the only scope law left) | [[model_check]] |
Model
States
not_runrunningcleancounterexample_foundtimed_outexploration_only
Transitions
| id | from | to | guard |
|---|---|---|---|
| begin | not_run | running | [[model_check.checker_invoked]] |
| finish_clean | running | clean | [[model_check.exhaustive_within_bound]] ∧ [[model_check.claim_aggregate_governs]] ∧ no violation found within the bound ∧ every required claim verified |
| finish_violation | running | counterexample_found | [[model_check.counterexample_is_minimal]] ∧ [[model_check.counterexample_names_violated_invariant]] |
| finish_timeout | running | timed_out | the stated bound was not reached before the run's time/state budget expired |
| finish_exploration | running | exploration_only | [[model_check.exhaustive_within_bound]] ∧ the backend executed no invariant predicates — a completed exploration is evidence the space ends within the bound, never that the model holds |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| stale_claim_rejected | unit | [[model_check.checker_invoked]] | compiled_module_with_zero_runs_against_it() | no_counterexample(file) == undetermined — never defaults to true |
| bound_must_be_stated | unit | [[model_check.exhaustive_within_bound]] | run_report_with_no_stated_bound() | check(report) == invalid |
| shortest_counterexample_reported | unit | [[model_check.counterexample_is_minimal]] | model_with(two_violating_traces_of_different_length: true) | reported_trace(check(model)) == the_shorter_of_the_two |
| violated_invariant_named | unit | [[model_check.counterexample_names_violated_invariant]] | model_with_exactly_one_violated_invariant() | check(model).violated_invariant_id == the_expected_id |
| backend_named_in_report | unit | [[model_check.backend_identified]] | run_report_from_any_backend() | check(report).backend == a named engine and version |
| clean_run_satisfies_verify_gate | unit | [[model_check.no_counterexample_feeds_verify]] | arbitrary_model_with_no_violation_within_bound() | no_counterexample(file) == true |
| stale_result_invalidated_by_edit | unit | [[model_check.rerun_on_model_change]] | (clean_run, model_edited_afterward_with_no_rerun) | no_counterexample(file) == false — until re-run |
| clean_model_passes | unit | [[model_check.checker_invoked]] | arbitrary_model_with_no_violation_within_bound() | check(model) == clean |
| exploration_run_is_not_a_clean_verdict | unit | [[model_check.no_counterexample_feeds_verify]] | model_with_only_prose_invariants_exhausted_within_bound() | check(model).outcome == exploration_only — never read as no_counterexample |
| fragment_invariants_checked | unit | [[model_check.executable_invariants_execute]] | compiled_model_with_one_executable_invariant() | check(model).invariants_checked == [the_invariant_id] |
| fragment_clean_run_is_no_counterexample | unit | [[model_check.executable_invariants_execute]] | model_with_executable_invariants_no_violation() | check(model).outcome == no_counterexample — the model gate’s reachable leg |
| panicking_fragment_violates | unit | [[model_check.invariant_totality]] | model_with_panicking_invariant_fragment() | check(model).outcome == counterexample_found ∧ violated_invariant_id == the_panicking_id |
| fragment_violation_traces | unit | [[model_check.counterexample_names_violated_invariant]] | model_with_falsifiable_executable_invariant() | check(model).trace == the_reachable_state_path — the trace leg is the existing constraint’s, not a new one |
| false_claim_is_counterexample_found | unit | [[model_check.claim_aggregate_governs]] | model_with(passing_rust_claim, counterexample_kernel_claim) | check(model) == counterexample_found naming the false claim id — a passing Rust claim never hides it |
| unknown_or_missing_claim_blocks_clean | unit | [[model_check.claim_aggregate_governs]] | model_with(unknown_or_missing_required_claim) | check(model) == exploration_only with reasons naming the claim ids |
| exhausted_bound_stays_timed_out | unit | [[model_check.claim_aggregate_governs]] | model_with(all_verified_claims_but_exhausted_bound) | check(model) == timed_out — verified claims cannot outrank an incomplete exploration |
| prose_only_is_unchecked_not_required | unit | [[model_check.required_claims_classified]] | model_with_only_prose_invariants() | required_claims(model) == [] ∧ unchecked_claims(model) == those ids — never implied verified |
| report_binds_claims_and_scope_digest | unit | [[model_check.claim_report_schema]] | run_report_from_current_inputs() | report.claim_schema_version == 1 ∧ expected/unchecked ids present ∧ scope_sha256 present |
| reordered_paths_and_prose_preserve_scope | unit | [[model_check.claim_report_schema]] | same_structured_inputs_in_different_cli_order() | scope_sha256 unchanged — the digest binds content, not paths |
| swapped_reports_rejected_unrewritten | unit | [[model_check.claim_report_schema]] | two_files_same_claim_names_opposite_invariants() | digest(a) ≠ digest(b) ∧ each run rejects the other's report without rewriting it |
| combined_same_identity_scope_refused | unit | [[model_check.corpus_identity_scope]] | two_parsed_files_claiming_the_same_intent_id() | check(invocation) == duplicate_corpus_identity before any artifact or report write (Revision 18) |
| distinct_ids_compose_and_lint_together | unit | [[model_check.corpus_identity_scope]] | several_dual_format_files_with_distinct_ids_linted_and_checked_together() | check(lint) == valid — files with distinct real ids compose like any corpus (Revision 18) |
Notes
This clarifies a naming trap in specodelic.md’s lifecycle, flagged
as CLAR-003 (same shape as CLAR-001, CLAR-002 — not resolved here for
the same reason: renaming a live state name should go through
rename_naturality, not be done by hand). model_checked sounds like it
means “the model was checked and holds,” but its guard,
[[specodelic.model_present]], only confirms a Model section exists —
checker_invoked above is what “a run actually happened” means, and
whether that run found a counterexample (no_counterexample) is a fact
consumed only by the next transition, verify. A file can be in state
model_checked while its most recent run found a counterexample, or
before any run has happened at all if model_present alone were treated
as sufficient. no_counterexample_feeds_verify and
rerun_on_model_change above are what close that gap: no one may claim
no_counterexample without having actually run this state machine to
clean, and a clean result older than the last Model edit doesn’t
count.
timed_out is a real terminal outcome, not a failure this file
papers over as “counterexample” or “clean” by default: an unbounded or
very large state space may exhaust its budget before exhaustive_within_bound
is satisfied either way. So is exploration_only (specodelic-len): the
native stateright backend interprets guards as prose (Decision 3,
Option A) and executes zero invariant predicates, so its completed runs
terminate in finish_exploration, NOT finish_clean — a completed
exploration proves the space ends within the bound and nothing more.
no_counterexample is reserved for a backend that actually executed
invariant predicates and found no violation; consumers (verify’s
gate, the no_counterexample_feeds_verify property above) must treat
exploration_only exactly like timed_out — not-clean, though not
evidence of a violation either. Backends. The contract above is engine-neutral,
and any backend satisfying it sits behind the same state machine. Two are
specified: stateright (default) — an embedded Rust model-checking crate,
no external dependencies: its breadth-first exploration makes
counterexample_is_minimal hold by construction (the first counterexample
found is the shortest), target_max_depth/timeout/target_state_count
give exhaustive_within_bound’s stated bound and timed_out’s
budget-exhausted outcome directly, and its named properties give
counterexample_names_violated_invariant when each property is named after
the Constraint id it derives from. The native backend interprets the
compiled model (states as values, transitions as actions) rather than
generating Rust source. TLC (opt-in) — the mature JVM reference engine,
run as a subprocess against the .tla module compile.md emits, with
-depth as the stated bound. Alloy was dropped from the corpus language
alongside this change: with a native default and a reference TLA+ engine it
had no remaining role, and a SAT-based engine returns an instance, not a
minimal trace — which would fight counterexample_is_minimal rather than
satisfy it. verify’s downstream guard treats timed_out
the same as counterexample_found for the purpose of
no_counterexample (both are not-clean), but they are reported
differently, since a timeout is not evidence of a violation — just of an
inconclusive run.
checked_against_core: clear (see AGENTS.md’s convention) —
no_counterexample and model_present already existed there and are
referenced, not restated. verify (STATUS.md §4, now done — see
verify.md) consumes this file’s clean/counterexample_found
outcome alongside compile.md’s proptest! blocks.
Executable invariants (specodelic.md Revision 15, specodelic-rjb).
Decision 3’s Option C ships: the **rust:** fragment (see compile.md)
turns a kind == invariant Constraint into something the native backend
actually executes — as a scratch-crate run, because a Rust expression
cannot be evaluated in-process from source text. The scratch runner is a
dependency-free breadth-first search over the same program-counter model
the embedded interpreter walks, which keeps counterexample_is_minimal
holding by construction (BFS discovers the shortest violating path) and
keeps the bound semantics honest: a completed exploration with ≥ 1
executed invariant and no violation is no_counterexample; zero executed
invariants stays exploration_only; a budget cap is timed_out. A
panicking fragment is a violation at the state where it panicked
(invariant_totality — a predicate that explodes is not a predicate that
holds). The engine attribution of the scratch run is native-bfs with
the tool’s own version — it is not stateright, and backend_identified
requires the report not to imply otherwise. TLC cannot execute Rust
fragments at all: against the same artifact it still reports
exploration_only, which is the honesty this file already pinned for
prose predicates.
The module’s staleness key now covers fragments: model_to_tla emits the
executable-invariant manifest (id + fragment verbatim) as module comments,
so editing a fragment changes the artifact hash and rerun_on_model_change
(reworded above to name fragments explicitly) fails the stale report
closed — an invariant that changed since the clean run is not the
invariant the run checked.
Claim-gated aggregation (define-verification-claim-gates).
claim_aggregate_governs refines what a clean run means without
redefining any state: a refuted required claim is a real
counterexample_found (named, via the existing minimal-trace machinery),
an exhausted bound is still timed_out, and an incomplete required set
— unknown, unsupported, missing, or empty — lands in exploration_only,
joining the zero-predicates case the Notes above already pinned. The
priority order matters and is normative: refutation outranks exhaustion
outranks incompleteness, so a passing Rust claim can never launder a
false kernel or citation claim into no_counterexample.
claim_report_schema gives the verdict an evidence carrier: a versioned
report whose scope_sha256 digest binds the parsed structured content
and consumed artifacts — not filesystem paths — so reordering CLI inputs
or editing prose alone reuses a report, while genuinely different
invariant content never does.
corpus_identity_scope (named dual_format_isolated_scope before
Revision 18) is the corpus-identity side of the same policy: the intent
ids of a command’s parsed inputs must be unique. Before Revision 18
this was two rules — the identity law plus a separate isolated-scope
refusal for id: spec files — but id: spec itself retired in
Revision 18 (specodelic-mcy): dual-format files now carry real ids
and the identity law is the only scope law left, strictly stronger
than the old pair (a same-id delta/deployed pair is now refused
outright, where the old law’s per-file exemption let them coexist).
Revision-discipline note. These rows deliberately do NOT touch
specodelic.md (this file’s scope-law revision rode specodelic.md
Revision 18 without touching no_counterexample’s expr there):
“the selected model-check backend finds no violated invariant”, and this
file — already the owner of that row’s precise meaning via
no_counterexample_feeds_verify — narrows the reachable clean leg
instead of restating it in the core (the AGENTS.md rule 3a preference
for widening an existing constraint’s scope over restating it).
verify.md carries the acceptance-side twins: the claim gate on
verified, scope-bound evidence, and view parity. All three
implementations share one aggregation path (design D4 of
openspec/changes/define-verification-claim-gates), so model-check,
verify, and orchestrate cannot drift apart on these rules.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: stale_claim_rejected
- WHEN
compiled_module_with_zero_runs_against_it() - THEN
no_counterexample(file) == undetermined— never defaults to true - VERIFIES [[model_check.stale_claim_rejected]]
Scenario: bound_must_be_stated
- WHEN
run_report_with_no_stated_bound() - THEN
check(report) == invalid - VERIFIES [[model_check.bound_must_be_stated]]
Scenario: shortest_counterexample_reported
- WHEN
model_with(two_violating_traces_of_different_length: true) - THEN
reported_trace(check(model)) == the_shorter_of_the_two - VERIFIES [[model_check.shortest_counterexample_reported]]
Scenario: violated_invariant_named
- WHEN
model_with_exactly_one_violated_invariant() - THEN
check(model).violated_invariant_id == the_expected_id - VERIFIES [[model_check.violated_invariant_named]]
Scenario: backend_named_in_report
- WHEN
run_report_from_any_backend() - THEN
check(report).backend == a named engine and version - VERIFIES [[model_check.backend_named_in_report]]
Scenario: clean_run_satisfies_verify_gate
- WHEN
arbitrary_model_with_no_violation_within_bound() - THEN
no_counterexample(file) == true - VERIFIES [[model_check.clean_run_satisfies_verify_gate]]
Scenario: stale_result_invalidated_by_edit
- WHEN
(clean_run, model_edited_afterward_with_no_rerun) - THEN
no_counterexample(file) == false— until re-run - VERIFIES [[model_check.stale_result_invalidated_by_edit]]
Scenario: clean_model_passes
- WHEN
arbitrary_model_with_no_violation_within_bound() - THEN
check(model) == clean - VERIFIES [[model_check.clean_model_passes]]
Scenario: exploration_run_is_not_a_clean_verdict
- WHEN
model_with_only_prose_invariants_exhausted_within_bound() - THEN
check(model).outcome == exploration_only— never read as no_counterexample - VERIFIES [[model_check.exploration_run_is_not_a_clean_verdict]]
Scenario: fragment_invariants_checked
- WHEN
compiled_model_with_one_executable_invariant() - THEN
check(model).invariants_checked == [the_invariant_id] - VERIFIES [[model_check.fragment_invariants_checked]]
Scenario: fragment_clean_run_is_no_counterexample
- WHEN
model_with_executable_invariants_no_violation() - THEN
check(model).outcome == no_counterexample— the model gate’s reachable leg - VERIFIES [[model_check.fragment_clean_run_is_no_counterexample]]
Scenario: panicking_fragment_violates
- WHEN
model_with_panicking_invariant_fragment() - THEN
check(model).outcome == counterexample_found ∧ violated_invariant_id == the_panicking_id - VERIFIES [[model_check.panicking_fragment_violates]]
Scenario: fragment_violation_traces
- WHEN
model_with_falsifiable_executable_invariant() - THEN
check(model).trace == the_reachable_state_path— the trace leg is the existing constraint’s, not a new one - VERIFIES [[model_check.fragment_violation_traces]]
Scenario: false_claim_is_counterexample_found
- WHEN
model_with(passing_rust_claim, counterexample_kernel_claim) - THEN
check(model) == counterexample_found naming the false claim id— a passing Rust claim never hides it - VERIFIES [[model_check.false_claim_is_counterexample_found]]
Scenario: unknown_or_missing_claim_blocks_clean
- WHEN
model_with(unknown_or_missing_required_claim) - THEN
check(model) == exploration_only with reasons naming the claim ids - VERIFIES [[model_check.unknown_or_missing_claim_blocks_clean]]
Scenario: exhausted_bound_stays_timed_out
- WHEN
model_with(all_verified_claims_but_exhausted_bound) - THEN
check(model) == timed_out— verified claims cannot outrank an incomplete exploration - VERIFIES [[model_check.exhausted_bound_stays_timed_out]]
Scenario: prose_only_is_unchecked_not_required
- WHEN
model_with_only_prose_invariants() - THEN
required_claims(model) == [] ∧ unchecked_claims(model) == those ids— never implied verified - VERIFIES [[model_check.prose_only_is_unchecked_not_required]]
Scenario: report_binds_claims_and_scope_digest
- WHEN
run_report_from_current_inputs() - THEN
report.claim_schema_version == 1 ∧ expected/unchecked ids present ∧ scope_sha256 present - VERIFIES [[model_check.report_binds_claims_and_scope_digest]]
Scenario: reordered_paths_and_prose_preserve_scope
- WHEN
same_structured_inputs_in_different_cli_order() - THEN
scope_sha256 unchanged— the digest binds content, not paths - VERIFIES [[model_check.reordered_paths_and_prose_preserve_scope]]
Scenario: swapped_reports_rejected_unrewritten
- WHEN
two_files_same_claim_names_opposite_invariants() - THEN
digest(a) ≠ digest(b) ∧ each run rejects the other's report without rewriting it - VERIFIES [[model_check.swapped_reports_rejected_unrewritten]]
Scenario: combined_same_identity_scope_refused
- WHEN
two_parsed_files_claiming_the_same_intent_id() - THEN
check(invocation) == duplicate_corpus_identitybefore any artifact or report write (Revision 18) - VERIFIES [[model_check.combined_same_identity_scope_refused]]
Scenario: distinct_ids_compose_and_lint_together
- WHEN
several_dual_format_files_with_distinct_ids_linted_and_checked_together() - THEN
check(lint) == valid— files with distinct real ids compose like any corpus (Revision 18) - VERIFIES [[model_check.distinct_ids_compose_and_lint_together]]
verify
id: verify kind: intent checked_against_core: clear statement: “THE verify step SHALL run every proptest! block compile.md’s properties_to_proptest produced to completion, and SHALL transition a file to verified only when all of them pass and model_check.md’s most recent run against the current compiled artifact reported clean.”
Verify
specodelic.md names verify as the model_checked → verified
transition, guarded by no_counterexample ∧ properties_pass — but neither
what “running the properties” means nor how that conjunction is actually
enforced was ever specified (STATUS.md §4, P0, last of the three
pipeline items). This file closes it: verify is what actually executes
compile.md’s output and combines it with model_check.md’s outcome
into the one fact specodelic.md’s lifecycle calls verified.
Purpose
specodelic.md names verify as the model_checked → verified
transition, guarded by no_counterexample ∧ properties_pass — but neither
what “running the properties” means nor how that conjunction is actually
enforced was ever specified (STATUS.md §4, P0, last of the three
pipeline items). This file closes it: verify is what actually executes
compile.md’s output and combines it with model_check.md’s outcome
into the one fact specodelic.md’s lifecycle calls verified.
Constraints
| id | kind | expr | traces_to | satisfies |
|————————————|———–|——————————————————————————————————————————————————|–––––––––––|
| blocks_run_to_completion | invariant | verify actually executes every proptest! block compile.md produced against the current compiled artifact — properties_pass is never satisfied by a partial run or a cached prior result | [[verify]] | |
| properties_pass_reflects_latest_run | invariant | [[specodelic.properties_pass]] holds iff every block from the most recent run against the current compiled artifact passed; a run predating the last edit to Properties, Constraints, or Model does not count | [[verify]] | |
| failure_reports_shrunk_counterexample | invariant | when a proptest! block fails, the report gives the shrunk (minimal) failing input, not the first randomly-generated failing input encountered | [[verify]] | |
| both_gates_required | invariant | verify only transitions a file to verified when both [[specodelic.no_counterexample]] (model_check.md's clean outcome) and [[specodelic.properties_pass]] hold — passing properties alone, or a clean model alone, is insufficient | [[verify]] | |
| law_cases_unexecuted | invariant | for a law-kind property compiled into multiple blocks (one per required case), all compiled blocks must pass — a partial pass (identity passes, associativity fails) is a failure of the property, not a partial success | [[verify]] | |
| verify_is_idempotent | invariant | re-running verify against an unchanged compiled artifact yields the same pass/fail outcome as the prior run, even though proptest may resample inputs each run for coverage | [[verify]] | |
| bounded_wall_clock | invariant | the verify runner bounds its cargo test run on a wall-clock clock (default 600s, –timeout-secs 0 disables the bound); a run exceeding the bound is killed and reported as a labeled timeout — never a silent hang, and never indistinguishable from a block failure | [[verify]] | |
| verification_failure | effect | verify.verification_failure(detail) | [[verify]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| required_claims_govern_acceptance | invariant | verify only transitions a file to verified when every required claim (model_check.md's required_claims_classified set) is verified in the current report AND every generated property block passes — a passing property run alone never covers an unknown, missing, or refuted claim; invalid or duplicate claim records are rejected as malformed evidence, never inferred as unknown success, and prose-only invariants stay unchecked | [[verify]] | |
| evidence_scope_bound | invariant | verify recomputes the scope digest and the required claim set from the current inputs and compares them against the stored report: old or unrecognized claim-schema versions, absent scope fingerprints, omitted or duplicate records, changed contributing inputs, or artifact mismatch fail with a rerun hint naming the model-check command — a stored report is never rewritten to manufacture evidence, and the digest binds structured content, not filesystem paths, so swapped reports from different scopes never verify | [[verify]] | |
| assurance_views_agree | invariant | CLI JSON, human output, and the persisted report agree on evaluated, unchecked, and blocking claims — no view reports verified while another names a blocker — and release documentation distinguishes structural lint, bounded model exploration, verified declared claims, and external application-test binding without advertising pending capabilities as implemented | [[verify]] | |
Model
States
not_runrunningproperties_evaluatedverifiedfailed(emits:[[verify.verification_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| begin | not_run | running | [[verify.blocks_run_to_completion]] |
| evaluate | running | properties_evaluated | every compiled proptest! block has recorded pass or fail |
| accept | properties_evaluated | verified | [[verify.both_gates_required]] ∧ [[verify.properties_pass_reflects_latest_run]] ∧ [[verify.law_cases_unexecuted]] ∧ [[verify.required_claims_govern_acceptance]] ∧ [[verify.evidence_scope_bound]] |
| reject | properties_evaluated | failed | ¬([[verify.both_gates_required]] ∧ [[verify.properties_pass_reflects_latest_run]] ∧ [[verify.law_cases_unexecuted]] ∧ [[verify.required_claims_govern_acceptance]] ∧ [[verify.evidence_scope_bound]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| partial_run_rejected | unit | [[verify.blocks_run_to_completion]] | run_that_skips_one_compiled_block() | check(run) == invalid |
| cached_result_rejected | unit | [[verify.properties_pass_reflects_latest_run]] | (passing_run, properties_edited_afterward_with_no_rerun) | properties_pass(file) == false — until re-run |
| shrunk_counterexample_reported | unit | [[verify.failure_reports_shrunk_counterexample]] | block_with(large_random_failing_input: true, smaller_reachable_by_shrinking: true) | reported_input(check(block)) == the_smaller_one |
| single_gate_insufficient | unit | [[verify.both_gates_required]] | file_with(properties_pass: true, no_counterexample: false) | check(file) == failed |
| both_gates_clean_passes | unit | [[verify.both_gates_required]] | file_with(properties_pass: true, no_counterexample: true) | check(file) == verified |
| partial_law_case_rejected | unit | [[verify.law_cases_unexecuted]] | law_property_with(identity_case: "pass", associativity_case: "fail") | check(property) == failed |
| rerun_matches_prior_outcome | unit | [[verify.verify_is_idempotent]] | (run_1, run_2) on an unchanged compiled artifact | outcome(run_1) == outcome(run_2) |
| verification_failure_label_asserted | unit | [[verify.verification_failure]] | verification_failure_raised() | error_label == "verify.verification_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| hang_reported_as_labeled_timeout | unit | [[verify.bounded_wall_clock]] | runner_with(predicate_that_never_terminates, timeout: 1) | check(run) == timeout_labeled ∧ outcome(run) ≠ failed — a timeout is no verdict on the property: distinct from properties_failed so a legitimately long suite can be re-run with a raised bound instead of misread as a failing predicate |
| fragments_reach_verified | unit | [[verify.both_gates_required]] | fixture_with(fragment_predicate_passing, executable_invariant_clean) | check(file) == verified — with executable fragments (specodelic.md Revision 15) both gates are reachable: a passing executable predicate and a clean executable-invariant run verify a real file, the state the gate could never reach before this Revision |
| claim_complete_evidence_verifies | unit | [[verify.required_claims_govern_acceptance]] | fixture_with(all_required_claims_verified, properties_pass) | check(file) == verified with the exact required and unchecked sets |
| false_claim_blocks_verified | unit | [[verify.required_claims_govern_acceptance]] | file_with(properties_pass: true, counterexample_kernel_claim) | check(file) == failed naming the false claim id — properties never cover a refuted claim |
| malformed_records_rejected | unit | [[verify.required_claims_govern_acceptance]] | report_with(missing_or_duplicate_claim_record) | check(file) == failed with a rerun hint — never inferred as unknown success |
| stale_report_rejected_with_rerun_hint | unit | [[verify.evidence_scope_bound]] | (clean_report, structurally_edited_inputs_afterward) | check(file) == failed naming the rerun command — the report is never rewritten |
| swapped_scope_report_rejected | unit | [[verify.evidence_scope_bound]] | report_from_a_different_structured_scope() | check(file) == failed — the digest binds structured content, not paths |
| views_agree_on_blockers | unit | [[verify.assurance_views_agree]] | run_with(required_unknown_claim_and_prose_invariant) | json.blockers == human.blockers == report.blockers ∧ unchecked sets agree |
| release_docs_match_implemented_capabilities | unit | [[verify.assurance_views_agree]] | docs_built_for_a_release() | version derives from Cargo metadata ∧ pending capabilities not advertised as implemented |
Notes
This closes STATUS.md §4’s P0. The three pipeline items —
compile.md, model_check.md, and this file — now cover the full
linted → compiled → model_checked → verified lifecycle
specodelic.md names but had never specified past the data-shape level.
STATUS.md §1 describes “something you can simulate before writing
code” as one of the four things a spec file gives you; compile.md is
what makes the simulation buildable, model_check.md is what makes
running it meaningful, and this file is what makes trusting its result
well-defined rather than an unstated conjunction of two booleans.
both_gates_required is the one constraint doing the most work here: it
restates specodelic.md’s bare no_counterexample ∧ properties_pass
guard, but as an actual mechanism — single_gate_insufficient’s test
case (properties pass, model doesn’t) is exactly the failure mode a
project would hit if someone treated a green proptest run alone as
sufficient to ship, skipping the model checker.
law_cases_unexecuted is a deliberate echo of specodelic.md’s
law_requires_cases (which gates compiling a law-kind property on it
having ≥ 2 cases in the first place). That constraint ensures the cases
exist before compilation; this one ensures they’re all actually run and
passing before verification — the same discipline applied at two
different pipeline stages, on either side of compile.md.
checked_against_core: clear (see AGENTS.md’s convention). With this
file, STATUS.md §4’s P0 is fully done; P1 (the rename/refactor tool)
is next.
Executable fragments make verified reachable (specodelic.md
Revision 15, specodelic-rjb). Before that Revision the verdict’s
verified status was unreachable by construction — every compiled block
carried a todo_predicate! placeholder that panics at execution, and the
native backend executed zero invariant predicates, so both gates failed
honestly on every file. fragments_reach_verified pins the way out
without touching both_gates_required or any honesty rule: a predicate
opting in with **rust:** executes for real, an executable invariant
gives the model run a predicate to actually check, and verified becomes
the conjunction both gates always demanded — reachable, never redefined.
Files whose cells carry no fragment behave exactly as before.
Claim-gated acceptance and scope-bound evidence
(define-verification-claim-gates). required_claims_govern_acceptance
completes both_gates_required rather than replacing it: the two gates
already demanded — a clean model run and passing properties — are now
joined by the third input they were silently missing, the per-claim
statuses the model report carries. A refuted or unknown kernel/citation
claim blocks verified even with a green proptest run; prose-only
invariants stay explicitly unchecked, and having a deriving Property
never makes one executable. evidence_scope_bound is what makes the
first constraint trustworthy: verify recomputes scope and the required
set from the current inputs instead of trusting the stored report’s
word, so a stale, swapped, or malformed report fails with a rerun hint —
old evidence is rejected, never silently re-scored or rewritten. This is
deliberately stricter than the pre-change acceptance path; the rerun
instruction ships with the release. assurance_views_agree keeps the
three report surfaces (CLI JSON, human output, the persisted
<stem>.check.json) from telling different stories about the same run.
The claim-side machine itself (required/unchecked classification, the
aggregate priority, the report schema, and dual-format command
isolation) is specified once, in model_check.md — this file consumes
it, mirroring how it already consumes model_check’s clean outcome rather
than restating specodelic.md’s bare guard. No specodelic.md
Constraint is touched, so no Revision bump: the acceptance conjunction
lives here (see the Revision-discipline note there).
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: partial_run_rejected
- WHEN
run_that_skips_one_compiled_block() - THEN
check(run) == invalid - VERIFIES [[verify.partial_run_rejected]]
Scenario: cached_result_rejected
- WHEN
(passing_run, properties_edited_afterward_with_no_rerun) - THEN
properties_pass(file) == false— until re-run - VERIFIES [[verify.cached_result_rejected]]
Scenario: shrunk_counterexample_reported
- WHEN
block_with(large_random_failing_input: true, smaller_reachable_by_shrinking: true) - THEN
reported_input(check(block)) == the_smaller_one - VERIFIES [[verify.shrunk_counterexample_reported]]
Scenario: single_gate_insufficient
- WHEN
file_with(properties_pass: true, no_counterexample: false) - THEN
check(file) == failed - VERIFIES [[verify.single_gate_insufficient]]
Scenario: both_gates_clean_passes
- WHEN
file_with(properties_pass: true, no_counterexample: true) - THEN
check(file) == verified - VERIFIES [[verify.both_gates_clean_passes]]
Scenario: partial_law_case_rejected
- WHEN
law_property_with(identity_case: "pass", associativity_case: "fail") - THEN
check(property) == failed - VERIFIES [[verify.partial_law_case_rejected]]
Scenario: rerun_matches_prior_outcome
- WHEN
(run_1, run_2)on an unchanged compiled artifact - THEN
outcome(run_1) == outcome(run_2) - VERIFIES [[verify.rerun_matches_prior_outcome]]
Scenario: verification_failure_label_asserted
- WHEN
verification_failure_raised() - THEN
error_label == "verify.verification_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[verify.verification_failure_label_asserted]]
Scenario: hang_reported_as_labeled_timeout
- WHEN
runner_with(predicate_that_never_terminates, timeout: 1) - THEN
check(run) == timeout_labeled ∧ outcome(run) ≠ failed— a timeout is no verdict on the property: distinct from properties_failed so a legitimately long suite can be re-run with a raised bound instead of misread as a failing predicate - VERIFIES [[verify.hang_reported_as_labeled_timeout]]
Scenario: fragments_reach_verified
- WHEN
fixture_with(fragment_predicate_passing, executable_invariant_clean) - THEN
check(file) == verified— with executable fragments (specodelic.md Revision 15) both gates are reachable: a passing executable predicate and a clean executable-invariant run verify a real file, the state the gate could never reach before this Revision - VERIFIES [[verify.fragments_reach_verified]]
Scenario: claim_complete_evidence_verifies
- WHEN
fixture_with(all_required_claims_verified, properties_pass) - THEN
check(file) == verified with the exact required and unchecked sets - VERIFIES [[verify.claim_complete_evidence_verifies]]
Scenario: false_claim_blocks_verified
- WHEN
file_with(properties_pass: true, counterexample_kernel_claim) - THEN
check(file) == failed naming the false claim id— properties never cover a refuted claim - VERIFIES [[verify.false_claim_blocks_verified]]
Scenario: malformed_records_rejected
- WHEN
report_with(missing_or_duplicate_claim_record) - THEN
check(file) == failed with a rerun hint— never inferred as unknown success - VERIFIES [[verify.malformed_records_rejected]]
Scenario: stale_report_rejected_with_rerun_hint
- WHEN
(clean_report, structurally_edited_inputs_afterward) - THEN
check(file) == failed naming the rerun command — the report is never rewritten - VERIFIES [[verify.stale_report_rejected_with_rerun_hint]]
Scenario: swapped_scope_report_rejected
- WHEN
report_from_a_different_structured_scope() - THEN
check(file) == failed — the digest binds structured content, not paths - VERIFIES [[verify.swapped_scope_report_rejected]]
Scenario: views_agree_on_blockers
- WHEN
run_with(required_unknown_claim_and_prose_invariant) - THEN
json.blockers == human.blockers == report.blockers ∧ unchecked sets agree - VERIFIES [[verify.views_agree_on_blockers]]
Scenario: release_docs_match_implemented_capabilities
- WHEN
docs_built_for_a_release() - THEN
version derives from Cargo metadata ∧ pending capabilities not advertised as implemented - VERIFIES [[verify.release_docs_match_implemented_capabilities]]
rename
id: rename kind: intent checked_against_core: clear statement: “WHEN a user requests renaming a spec row’s id, THE rename tool SHALL update the defining row and every referencing [[link]] as a single atomic operation, or leave the repo unchanged.”
Rename Tool
rename_naturality is asserted as a property in three other files
(specodelic.md, linter-referential_integrity.md,
linter-graph_shape.md’s topo_sort_naturality), but the tool those
properties are about has never had its own Intent, Constraints, Model, or
Properties (STATUS.md §4, P0). This file is that spec: what a rename
request actually does, step by step, and what must hold before it’s allowed
to report success.
A rename is the concrete operation whose existence makes η : I ⇒ I'
(STATUS.md §1’s natural transformation between two instances of 𝒦) more
than a formal description — it’s the thing that has to actually construct
I' from I and a single (old_id, new_id) pair, honoring naturality
rather than merely being checked against it after the fact.
Purpose
rename_naturality is asserted as a property in three other files
(specodelic.md, linter-referential_integrity.md,
linter-graph_shape.md’s topo_sort_naturality), but the tool those
properties are about has never had its own Intent, Constraints, Model, or
Properties (STATUS.md §4, P0). This file is that spec: what a rename
request actually does, step by step, and what must hold before it’s allowed
to report success.
A rename is the concrete operation whose existence makes η : I ⇒ I'
(STATUS.md §1’s natural transformation between two instances of 𝒦) more
than a formal description — it’s the thing that has to actually construct
I' from I and a single (old_id, new_id) pair, honoring naturality
rather than merely being checked against it after the fact.
Constraints
| id | kind | expr | traces_to | satisfies |
|––––––––––––––|———–|–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|–––––––––––|
| new_id_available | invariant | new_id ∉ index(repo) — renaming never collides with an existing id | [[rename]] | |
| new_id_matches_filename | invariant | if the renamed row is a file's own Intent, [[specodelic.id_matches_file]] must hold between new_id and the (possibly also renamed) filename | [[rename]] | |
| old_id_fully_replaced | invariant | ∀ [[old_id]] reference anywhere in repo: rewritten to [[new_id]]; zero occurrences of old_id remain post-rename | [[rename]] | |
| atomic_operation | invariant | the definition-row update, every reference rewrite, and any required filename change apply as one transaction: all succeed, or the repo is left byte-identical to its pre-rename state | [[rename]] | |
| kind_unchanged | invariant | assigned_kind(row) is the same before and after rename — both 𝒦's five-object kind and, where present, the row's own kind/sub-kind column — see [[kinds.kind_shape_naturality]] | [[rename]] | |
| prose_untouched_by_rename | invariant | rename rewrites only [[id]] wiki-link syntax and structured id fields (frontmatter id, table id/traces_to/derives_from/guard/from/to cells) — it never edits rationale/description prose, even if the prose happens to mention the old name in words | [[rename]] | |
| validation_failure | effect | rename.validation_failure(new_id, reason) | [[rename]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| apply_failure | effect | rename.apply_failure(detail) | [[rename]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| post_check_failure | effect | rename.post_check_failure(detail) | [[rename]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
requestedcheckedapplyingappliedverifyingpassedvalidate_failed(emits:[[rename.validation_failure]])apply_failed(emits:[[rename.apply_failure]])check_failed(emits:[[rename.post_check_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| validate | requested | checked | [[rename.new_id_available]] ∧ [[rename.new_id_matches_filename]] |
| validate_fail | requested | validate_failed | ¬([[rename.new_id_available]] ∧ [[rename.new_id_matches_filename]]) |
| apply | checked | applying | [[rename.atomic_operation]] |
| apply_ok | applying | applied | [[rename.old_id_fully_replaced]] ∧ [[rename.kind_unchanged]] ∧ [[rename.prose_untouched_by_rename]] |
| apply_fail | applying | apply_failed | ¬([[rename.old_id_fully_replaced]] ∧ [[rename.kind_unchanged]] ∧ [[rename.prose_untouched_by_rename]]) — atomic_operation requires this path to roll back to the pre-rename repo, not to leave a partial edit |
| verify | applied | verifying | linter.referential_integrity and linter.graph_shape are re-run against the renamed repo |
| accept | verifying | passed | [[linter.referential_integrity.ref_resolves]] ∧ [[linter.graph_shape.acyclic]] |
| reject | verifying | check_failed | ¬([[linter.referential_integrity.ref_resolves]] ∧ [[linter.graph_shape.acyclic]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| rename_naturality | law | [[rename.old_id_fully_replaced]] | arbitrary_spec_repo(), arbitrary_id_rename() | identity: rename(I, a, a) == I associativity: rename(rename(I,a,b), b,c) == rename(I,a,c) naturality: compile(rename(I)) == rename(compile(I)) |
| collision_rejected | unit | [[rename.new_id_available]] | (repo_with_id: "x", new_id: "x_taken_by_other_row") | check(request) == failed |
| filename_mismatch_rejected | unit | [[rename.new_id_matches_filename]] | rename_of_a_files_own_intent_id_without_a_matching_filename_change() | check(request) == failed |
| partial_failure_rolls_back | unit | [[rename.atomic_operation]] | apply_interrupted_after_definition_row_updated_but_before_all_refs_rewritten() | post_state(repo) == pre_state(repo) |
| stray_ref_caught_by_verify | unit | [[rename.old_id_fully_replaced]] | rename_that_misses_one_[[old_id]]_occurrence() | check(request) == failed — caught at verify, not silently accepted |
| prose_mention_left_alone | unit | [[rename.prose_untouched_by_rename]] | row_whose_rationale_prose_contains_the_old_id_as_a_word() | rationale_text(post_rename_row) == rationale_text(pre_rename_row) |
| clean_rename_passes | unit | [[rename.old_id_fully_replaced]] | well_formed_repo(), id_not_used_elsewhere() | check(request) == passed |
| kind_preserved_by_rename | unit | [[rename.kind_unchanged]] | rename_of_a_constraint_row_to_a_new_id() | assigned_kind(post_rename_row) == assigned_kind(pre_rename_row) — both the five-object kind and, where present, the row’s own kind column survive the rewrite |
| validation_failure_label_asserted | unit | [[rename.validation_failure]] | validation_failure_raised() | error_label == "rename.validation_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| apply_failure_label_asserted | unit | [[rename.apply_failure]] | apply_failure_raised() | error_label == "rename.apply_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| post_check_failure_label_asserted | unit | [[rename.post_check_failure]] | post_check_failure_raised() | error_label == "rename.post_check_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
checked_against_core: clear (see AGENTS.md’s convention). The
properties this file needs (atomicity, prose-non-interference,
kind-preservation) are local to what a rename operation must guarantee,
the same way compile.md, model_check.md, and verify.md each carry
constraints specific to their own step of the lifecycle without requiring
new entries in
specodelic.md itself — this file’s Intent is the fourth instance of
that pattern, not an exception to it.
rename_naturality now exists in four places (specodelic.md,
linter-referential_integrity.md, linter-graph_shape.md’s
topo_sort_naturality, and here). That’s intentional, not duplication to
clean up: each site states the same law at the altitude that site owns —
specodelic.md as the format’s top-level refactor-safety guarantee,
the two linter files as “this is exactly the check that would catch a
naive rename tool,” and this file as the actual operation those laws are
about. This file is the one whose apply/apply_ok/apply_fail
transitions give the law something to be a law of, rather than a fact
asserted with no corresponding process.
Why verify re-runs two checkers instead of all six. apply_ok
already establishes old_id_fully_replaced (no dangling old-id refs) and
kind_unchanged directly as this file’s own constraints. What apply_ok
can’t see on its own is whether the rewrite altered anything at the
repo-graph level it wasn’t watching for — a same-file collision the local
check missed, or a graph edge that only becomes visible once every file’s
refs are re-indexed together. linter.referential_integrity and
linter.graph_shape are exactly the two checkers whose owned constraints
(unique_across_repo, ref_resolves, acyclic, single_root_reachable)
depend on cross-file state a single-rename’s local bookkeeping can’t fully
self-certify. The other four checkers (model_shape, ears_syntax,
schema_shape, coverage) check properties a rename can’t perturb — it
never changes a transition’s guard-presence, a statement’s EARS pattern,
a table’s column shape, or whether a constraint has a deriving property —
so re-running them would be redundant Needs Human Review overhead, not
signal.
Decision of record (2026-09-30, user-approved; advised by a typed
Jev evaluation, jev-1.13.0, conf 0.74): a batch rename is one
atomic transaction, not n independent ones. All files touched by
the batch are staged and validated as a set — the same
precondition-then-apply-then-reverify shape a single rename already
uses — and any validation failure aborts the whole batch, leaving every
file byte-identical to before. Partial success is exactly the failure
mode the single-rename atomic_operation constraint exists to prevent:
a half-renamed namespace is a corpus where ref_resolves dangles by
design, and the whole-commit atomicity git already provides makes
per-file partial commits a policy choice a tool shouldn’t silently make.
atomic_operation therefore means the same thing at batch scope as at
single-rename scope: all-or-nothing. A batch-rename implementation is a
separate follow-up ticket; nothing in this file’s constraint text
changes.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: rename_naturality
- WHEN
arbitrary_spec_repo(), arbitrary_id_rename() - THEN identity:
rename(I, a, a) == Iassociativity:rename(rename(I,a,b), b,c) == rename(I,a,c)naturality:compile(rename(I)) == rename(compile(I)) - VERIFIES [[rename.rename_naturality]]
Scenario: collision_rejected
- WHEN
(repo_with_id: "x", new_id: "x_taken_by_other_row") - THEN
check(request) == failed - VERIFIES [[rename.collision_rejected]]
Scenario: filename_mismatch_rejected
- WHEN
rename_of_a_files_own_intent_id_without_a_matching_filename_change() - THEN
check(request) == failed - VERIFIES [[rename.filename_mismatch_rejected]]
Scenario: partial_failure_rolls_back
- WHEN
apply_interrupted_after_definition_row_updated_but_before_all_refs_rewritten() - THEN
post_state(repo) == pre_state(repo) - VERIFIES [[rename.partial_failure_rolls_back]]
Scenario: stray_ref_caught_by_verify
- WHEN
rename_that_misses_one_[[old_id]]_occurrence() - THEN
check(request) == failed— caught atverify, not silently accepted - VERIFIES [[rename.stray_ref_caught_by_verify]]
Scenario: prose_mention_left_alone
- WHEN
row_whose_rationale_prose_contains_the_old_id_as_a_word() - THEN
rationale_text(post_rename_row) == rationale_text(pre_rename_row) - VERIFIES [[rename.prose_mention_left_alone]]
Scenario: clean_rename_passes
- WHEN
well_formed_repo(), id_not_used_elsewhere() - THEN
check(request) == passed - VERIFIES [[rename.clean_rename_passes]]
Scenario: kind_preserved_by_rename
- WHEN
rename_of_a_constraint_row_to_a_new_id() - THEN
assigned_kind(post_rename_row) == assigned_kind(pre_rename_row)— both the five-object kind and, where present, the row’s own kind column survive the rewrite - VERIFIES [[rename.kind_preserved_by_rename]]
Scenario: validation_failure_label_asserted
- WHEN
validation_failure_raised() - THEN
error_label == "rename.validation_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[rename.validation_failure_label_asserted]]
Scenario: apply_failure_label_asserted
- WHEN
apply_failure_raised() - THEN
error_label == "rename.apply_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[rename.apply_failure_label_asserted]]
Scenario: post_check_failure_label_asserted
- WHEN
post_check_failure_raised() - THEN
error_label == "rename.post_check_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[rename.post_check_failure_label_asserted]]
merge
id: merge kind: intent checked_against_core: clear statement: “WHEN two branches derived from a common ancestor spec repo are joined, THE merge tool SHALL detect id collisions and reference breaks introduced by the union of their edits — using [[graph]]’s blast-radius query rather than re-deriving reachability independently — before reporting the merge complete, and SHALL never report success against a tree that [[linter.referential_integrity]] or [[linter.graph_shape]] would reject.”
Merge
A textually clean 3-way merge (no conflict markers) can still produce a
semantically broken repo: two branches mint the same new id independently,
or one branch renames an id (rename.md) while the other adds a fresh
reference to the old name in a file the rename never touched. Git’s
line-based merge can’t see either problem — both are clean at the text
layer. This file specifies the check that runs after git’s merge
succeeds: it reuses graph.md’s derived reachability rather than
re-deriving it, and rename.md’s actual rewrite mechanism rather than
inventing a second one, applied across two histories instead of one linear
line — the “existing mechanism, new scope” move AGENTS.md #3a asks for
before adding anything new.
Purpose
A textually clean 3-way merge (no conflict markers) can still produce a
semantically broken repo: two branches mint the same new id independently,
or one branch renames an id (rename.md) while the other adds a fresh
reference to the old name in a file the rename never touched. Git’s
line-based merge can’t see either problem — both are clean at the text
layer. This file specifies the check that runs after git’s merge
succeeds: it reuses graph.md’s derived reachability rather than
re-deriving it, and rename.md’s actual rewrite mechanism rather than
inventing a second one, applied across two histories instead of one linear
line — the “existing mechanism, new scope” move AGENTS.md #3a asks for
before adding anything new.
Constraints
| id | kind | expr | traces_to | satisfies |
|——————————————|———–|––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|–––––––––––|
| graph_reused_not_rederived | invariant | merge's collision and blast-radius checks query [[graph]]'s artifact for both branch tips; merge never independently re-walks markdown for reachability | [[merge]] | |
| no_new_id_collision | invariant | ∀ id ∈ index(branch_A) ∩ index(branch_B): id was already defined, identically, in the common ancestor — an id newly minted by both branches independently is a collision, not a merge | [[merge]] | |
| blast_radii_recorded_pre_merge | invariant | before applying the union of edits, merge records [[graph]].blast_radius(touched_ids) for each branch separately, against the common ancestor's graph | [[merge]] | |
| semantic_conflict_iff_blast_radius_intersects | invariant | blast_radius_A(touched_A) ∩ blast_radius_B(touched_B) ≠ ∅ ⟹ the merge requires human review, even when the two branches' literal file diffs don't overlap | [[merge]] | |
| rename_replayed_onto_foreign_edits | invariant | if branch A contains a [[rename]] application of (old_id, new_id) and branch B independently adds a new [[old_id]] reference within blast_radius_A, the merge rewrites that reference to [[new_id]] using rename.md's own rewrite mechanism — it is never left dangling and never silently dropped | [[merge]] | |
| post_merge_relint_required | invariant | merge is not reported passed until [[linter.referential_integrity]] and [[linter.graph_shape]] are re-run against the merged tree and both report passed | [[merge]] | |
| sequential_number_reassigned_on_conflict | invariant | if both branches independently claim the same next sequential number (a CHANGELOG entry or Revision heading), the merge tool renumbers one of the two rather than allowing a silent duplicate | [[merge]] | |
| collision_failure | effect | merge.collision_failure(detail) | [[merge]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| reverification_failure | effect | merge.reverification_failure(detail) | [[merge]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| merge_aborted | effect | merge.merge_aborted(detail) | [[merge]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
divergedcollision_checkedneeds_reviewrename_replayedref_integrity_reverifiedmergedcollision_failed(emits:[[merge.collision_failure]])reverification_failed(emits:[[merge.reverification_failure]])merge_failed(emits:[[merge.merge_aborted]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| check_collisions | diverged | collision_checked | [[merge.no_new_id_collision]] ∧ [[merge.graph_reused_not_rederived]] |
| collision_fail | diverged | collision_failed | ¬([[merge.no_new_id_collision]] ∧ [[merge.graph_reused_not_rederived]]) |
| flag_review | collision_checked | needs_review | [[merge.semantic_conflict_iff_blast_radius_intersects]] ∧ [[merge.blast_radii_recorded_pre_merge]] |
| auto_proceed | collision_checked | rename_replayed | ¬flag_review.guard ∧ [[merge.rename_replayed_onto_foreign_edits]] |
| resolved | needs_review | rename_replayed | a human has explicitly approved merging the intersecting blast radii ∧ [[merge.rename_replayed_onto_foreign_edits]] |
| reverify | rename_replayed | ref_integrity_reverified | [[merge.post_merge_relint_required]] |
| reverify_fail | rename_replayed | reverification_failed | ¬([[merge.post_merge_relint_required]]) |
| accept | ref_integrity_reverified | merged | [[merge.sequential_number_reassigned_on_conflict]] |
| reject | ref_integrity_reverified | merge_failed | ¬([[merge.sequential_number_reassigned_on_conflict]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| collision_across_branches_rejected | unit | [[merge.no_new_id_collision]] | two_branches_each_independently_defining_new_id("x") | check(merge) == failed |
| inherited_id_not_falsely_flagged | unit | [[merge.no_new_id_collision]] | id_defined_in_common_ancestor_unchanged_in_both_branches() | check(merge) != failed |
| textually_clean_semantic_conflict_flagged | unit | [[merge.semantic_conflict_iff_blast_radius_intersects]] | branch_A(renames x to y), branch_B(adds a new [[x]] reference in a file branch_A never touched) | check(merge) == needs_review |
| rename_replay_repairs_dangling_ref | unit | [[merge.rename_replayed_onto_foreign_edits]] | same scenario, after human approval | post_merge_repo has zero occurrences of [[x]]; branch_B's new reference now reads [[y]] |
| disjoint_blast_radii_auto_merge | unit | [[merge.semantic_conflict_iff_blast_radius_intersects]] | two_branches_touching_disjoint_namespaces_with_disjoint_blast_radii() | check(merge) == merged — no human review required |
| duplicate_sequential_number_renumbered | unit | [[merge.sequential_number_reassigned_on_conflict]] | both_branches_add_a_CHANGELOG_entry_claiming_the_same_next_number() | post_merge_repo has two distinct sequential numbers; neither is duplicated |
| relint_gates_merge | unit | [[merge.post_merge_relint_required]] | merge_where(linter.referential_integrity or linter.graph_shape: fails against the merged tree) | check(merge) == failed — mirrors rename.md’s stray_ref_caught_by_verify |
| blast_radius_recorded_before_apply | unit | [[merge.blast_radii_recorded_pre_merge]] | merge_history_where_edits_were_applied_before_both_branches'_radii_were_recorded() | check(merge) == failed — apply never precedes the recording step, for either branch |
| reachability_from_graph_artifact_only | unit | [[merge.graph_reused_not_rederived]] | merge_invoked_with_the_markdown_walker_patched_to_panic() | merge of an otherwise-clean diverged repo succeeds — collision and blast-radius checks only query [[graph]]’s artifact |
| collision_failure_label_asserted | unit | [[merge.collision_failure]] | collision_failure_raised() | error_label == "merge.collision_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| reverification_failure_label_asserted | unit | [[merge.reverification_failure]] | reverification_failure_raised() | error_label == "merge.reverification_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| merge_aborted_label_asserted | unit | [[merge.merge_aborted]] | merge_aborted_raised() | error_label == "merge.merge_aborted" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
checked_against_core: clear (see AGENTS.md’s convention).
rename_replayed_onto_foreign_edits
was checked against AGENTS.md #3a specifically before being added as a
new row rather than reusing rename.md’s old_id_fully_replaced as-is:
it isn’t a duplicate, because it operates over two divergent histories
being joined, not one linear history — old_id_fully_replaced guarantees
completeness within a single rename’s own transaction, and says nothing
about a reference minted on a different branch that the rename operation
never saw. This file’s constraint is what makes that reference get
rewritten anyway, once the branches meet.
What this file doesn’t replace. Two edits to the same row’s own field on both branches is an ordinary textual conflict — git already marks it, already forces a human to resolve it, and nothing here changes that. This file exists only for the cases where the text merge is clean but the result isn’t: id collisions and rename/reference splits that don’t overlap a single line.
Depends on graph.md, not a local reachability walk. Every blast-radius
computation in this file’s Constraints is a graph query, computed once
against the common ancestor for each branch (blast_radii_recorded_pre_merge)
rather than recomputed after the union is applied — recomputing post-union
would already have mixed the two branches’ edges together and lost the
ability to tell which branch’s change reached which id.
Depends on rename.md, not a second rewrite mechanism. resolved/
auto_proceed’s rewrite step calls the same rewrite rename.md’s apply
transition performs — this file only decides when a cross-branch replay
is required, never how the rewrite itself works.
Decision of record (2026-09-30, user-approved; HITL ticket specodelic-mp1
row 2): “a human has explicitly approved” means an explicit, attributable,
recorded human decision by the operator — a deliberate approve step (never
inferred, timed-out into, or defaulted) whose decision lands in the repo’s
decision trail (change notes / ticket). No CI gate or role requirement is
assumed: the format describes tool contracts, not hosting policy, and an
implementation may layer a gate or role on top, but the floor the
resolved guard checks is the attributable operator decision itself. CI
gates and roles were deliberately excluded — both presume infrastructure
the spec cannot see, whereas a recorded operator decision is checkable by
the merge lifecycle itself. This paragraph records the decision; constraint
text is unchanged, same treatment as rename.md’s batch-rename decision.
Resolved question (was Needs Human Review): what “a human has
explicitly approved” (the resolved transition’s guard) means operationally
is now decided — see the decision of record above. The mechanism-agnostic
floor stands: an attributable operator decision, recorded in the repo’s
decision trail; gates and roles are layers an implementation may add, not
assumptions the format makes.
Open question, relationship to refactor.md: a blast-radius
intersection that triggers needs_review here may also be exactly the
“unrelated fan-in” shape refactor.md flags as a tidy-first candidate —
both files answer different questions (can this merge proceed safely, vs.
should this node be split before more changes land on it) and are
deliberately left as two files rather than merged into one, but a
same-session finding from both on the same node is worth surfacing
together rather than as two unrelated notices. Left open rather than
wired automatically.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: collision_across_branches_rejected
- WHEN
two_branches_each_independently_defining_new_id("x") - THEN
check(merge) == failed - VERIFIES [[merge.collision_across_branches_rejected]]
Scenario: inherited_id_not_falsely_flagged
- WHEN
id_defined_in_common_ancestor_unchanged_in_both_branches() - THEN
check(merge) != failed - VERIFIES [[merge.inherited_id_not_falsely_flagged]]
Scenario: textually_clean_semantic_conflict_flagged
- WHEN
branch_A(renames x to y), branch_B(adds a new [[x]] reference in a file branch_A never touched) - THEN
check(merge) == needs_review - VERIFIES [[merge.textually_clean_semantic_conflict_flagged]]
Scenario: rename_replay_repairs_dangling_ref
- WHEN
same scenario, after human approval - THEN
post_merge_repo has zero occurrences of [[x]]; branch_B's new reference now reads [[y]] - VERIFIES [[merge.rename_replay_repairs_dangling_ref]]
Scenario: disjoint_blast_radii_auto_merge
- WHEN
two_branches_touching_disjoint_namespaces_with_disjoint_blast_radii() - THEN
check(merge) == merged— no human review required - VERIFIES [[merge.disjoint_blast_radii_auto_merge]]
Scenario: duplicate_sequential_number_renumbered
- WHEN
both_branches_add_a_CHANGELOG_entry_claiming_the_same_next_number() - THEN
post_merge_repo has two distinct sequential numbers; neither is duplicated - VERIFIES [[merge.duplicate_sequential_number_renumbered]]
Scenario: relint_gates_merge
- WHEN
merge_where(linter.referential_integrity or linter.graph_shape: fails against the merged tree) - THEN
check(merge) == failed— mirrorsrename.md’sstray_ref_caught_by_verify - VERIFIES [[merge.relint_gates_merge]]
Scenario: blast_radius_recorded_before_apply
- WHEN
merge_history_where_edits_were_applied_before_both_branches'_radii_were_recorded() - THEN
check(merge) == failed— apply never precedes the recording step, for either branch - VERIFIES [[merge.blast_radius_recorded_before_apply]]
Scenario: reachability_from_graph_artifact_only
- WHEN
merge_invoked_with_the_markdown_walker_patched_to_panic() - THEN
merge of an otherwise-clean diverged repo succeeds— collision and blast-radius checks only query [[graph]]’s artifact - VERIFIES [[merge.reachability_from_graph_artifact_only]]
Scenario: collision_failure_label_asserted
- WHEN
collision_failure_raised() - THEN
error_label == "merge.collision_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[merge.collision_failure_label_asserted]]
Scenario: reverification_failure_label_asserted
- WHEN
reverification_failure_raised() - THEN
error_label == "merge.reverification_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[merge.reverification_failure_label_asserted]]
Scenario: merge_aborted_label_asserted
- WHEN
merge_aborted_raised() - THEN
error_label == "merge.merge_aborted"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[merge.merge_aborted_label_asserted]]
refactor
id: refactor kind: intent checked_against_core: clear statement: “WHEN a proposed changeset would edit rows owned by a node with high, unrelated fan-in in [[graph]], THE refactor advisor SHALL surface a non-gating suggestion to split that node first, and SHALL never block or delay any transition in [[specodelic]]’s or [[orchestrate]]’s lifecycle.”
Refactor Advisor
STATUS.md §2 already names the pathology this mechanizes — “God object /
cyclic dependency” — and specodelic.md Revision 2 already fixed one
real instance of it by hand (splitting the flat 7-clause lint guard into
the Checker Ownership table) before anyone had a tool that would have
flagged it proactively. This file is that tool’s spec: Kent Beck’s
tidy-first rule (“separate the structural change from the behavioral one”)
read as a graph query — a node with fan-in from unrelated namespaces, or a
changeset that only needs a fraction of what a node owns, is a candidate
for splitting before the behavioral edit lands, not after.
Purpose
STATUS.md §2 already names the pathology this mechanizes — “God object /
cyclic dependency” — and specodelic.md Revision 2 already fixed one
real instance of it by hand (splitting the flat 7-clause lint guard into
the Checker Ownership table) before anyone had a tool that would have
flagged it proactively. This file is that tool’s spec: Kent Beck’s
tidy-first rule (“separate the structural change from the behavioral one”)
read as a graph query — a node with fan-in from unrelated namespaces, or a
changeset that only needs a fraction of what a node owns, is a candidate
for splitting before the behavioral edit lands, not after.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| fan_in_read_from_graph | invariant | the advisor's dependent-count for node N is [[graph]]'s blast_radius(N) restricted to incoming edges — never independently recomputed by walking markdown | [[refactor]] |
| unrelated_fan_in_defined | invariant | a dependent is "unrelated" to N when N and the dependent share no namespace segment below the repo root — i.e. their nearest common ancestor in the id namespace is the root itself | [[refactor]] |
| narrow_diff_heuristic | invariant | a changeset naming a strict subset of node N's owned Constraint/Property rows as its actual target, while depending on none of N's other owned rows, is flagged regardless of N's fan-in count | [[refactor]] |
| threshold_is_per_repo_setting | invariant | the fan-in count that counts as "high" is a configured value, not a number fixed in this file — "high" is relative to a given repo's typical fan-in | [[refactor]] |
| advisory_finding_emitted | effect | output == {node_id, dependent_count, unrelated_namespace_count, suggested_split: bool} | [[refactor]] |
Model
States
idleanalyzingcleanfoundemits: [[refactor.advisory_finding_emitted]]
Transitions
| id | from | to | guard |
|---|---|---|---|
| analyze | idle | analyzing | [[graph.queryable]] — graph’s artifact is published and queryable (see graph.md’s own lifecycle) |
| flag | analyzing | found | [[refactor.unrelated_fan_in_defined]] ∨ [[refactor.narrow_diff_heuristic]] |
| clear | analyzing | clean | ¬flag.guard |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| fan_in_graph_sourced | unit | [[refactor.fan_in_read_from_graph]] | node_with_precomputed_blast_radius_in_graph() | advisor_source_calls(markdown_walker) == 0 — every count comes from graph’s artifact |
| unrelated_fan_in_flagged | unit | [[refactor.unrelated_fan_in_defined]] | node_referenced_by_dependents_in_three_disjoint_top_level_namespaces() | check(node) == found |
| related_fan_in_not_flagged | unit | [[refactor.unrelated_fan_in_defined]] | node_referenced_only_by_dependents_within_its_own_namespace_subtree() | check(node) == clean |
| narrow_diff_flagged_low_fan_in | unit | [[refactor.narrow_diff_heuristic]] | changeset_touching_one_of_five_constraints_owned_by_a_node_with_fan_in(1) | check(node) == found — flagged even though fan-in alone wouldn’t trigger it |
| emitted_finding_shape | unit | [[refactor.advisory_finding_emitted]] | found state reached for node N with dependent_count 4, 2 unrelated | emits(found) == {node_id: N, dependent_count: 4, unrelated_namespace_count: 2, suggested_split: true} |
| threshold_read_from_config | unit | [[refactor.threshold_is_per_repo_setting]] | same_node_same_changeset_under_two_repo_configs_with_different_high_fan_in_values() | flagged(config_A) != flagged(config_B) — the decision follows the repo’s configured threshold, never a constant embedded in this file |
Notes
checked_against_core: clear (see AGENTS.md’s convention). Nothing new
was needed to make the finding non-gating: advisory_finding_emitted is
kind == effect, and [[specodelic.ref_kind_compatible]]’s own typing
already restricts every transition’s guard field to kind == invariant
Constraints only, tested generically by [[specodelic.advisory_cannot_gate]].
An effect-kind Constraint is excluded from guard-eligibility by that same
rule, for the same reason an advisory-kind one is — so this file needed
no invariant of its own asserting “the finding can’t gate anything”; that
was already true, globally, before this file existed. (An earlier draft of
this file carried two such invariants plus a redundant unit test — removed
as an instance of the exact “one rule, restated three ways” pattern
specodelic.md Revision 6 already caught once, for append_only_variants.)
advisory_finding_emitted’s emits reuses the Moore-machine mechanism
Revision 6 built for State; this file adds no sixth 𝒦 object and no
new kind of Constraint — a new consumer of an existing mechanism, not a
new one.
This file never decides how to split a node. It only detects the
fan-in/narrow-diff shape and emits a finding; the actual split — a new
file, an updated traces_to, an updated Checker Ownership-style table if
the node is itself a dependency hub — is an ordinary edit, checked the
normal way by the six existing linters. Deciding how to split is a
design judgment this file deliberately doesn’t automate.
Depends on graph.md, not on re-deriving reachability: analyze’s
guard cites graph’s own queryable state by typed citation
([[graph.queryable]] — a State, which Revision 10’s guard typing admits)
instead of bare prose (the same “cite another file’s state fact when it
isn’t itself a Constraint row” pattern orchestrate.md already uses for
model_check.md/verify.md),
and every fan-in count is a graph.blast_radius query, never an
independent markdown walk.
Decision of record (2026-09-30, user-approved; advised by a typed
Jev evaluation, jev-1.13.0, conf 0.54): “unrelated fan-in” keys off
the id namespace — the “shares no namespace segment below root”
test stands as written. Physical directory placement is rejected as the
keying: ids are the format’s primary identity (id_matches_file ties
them to filenames, the Reference Typing table types them as foreign
keys), so relatedness judged by namespace is consistent with how every
other checker reads the corpus, while directory placement would make
the advisory’s meaning depend on filesystem layout the format doesn’t
own. The flat-namespace failure mode is real but contained: the finding
is kind == advisory-shaped (non-gating by typing), and the threshold
is already per-repo configurable — a flat-namespace repo raises the
threshold or lives with noise. A configurable keying mode (namespace or
directory) stays a possible future widening under
kind_field_extensible-style governance if a real flat-namespace corpus
ever adopts the format; nothing in this file’s constraint text changes.
Open question, Needs Human Review: threshold_is_per_repo_setting
deliberately leaves the actual number unset here — this file specifies
that the threshold is configurable, not what a reasonable default is.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: fan_in_graph_sourced
- WHEN
node_with_precomputed_blast_radius_in_graph() - THEN
advisor_source_calls(markdown_walker) == 0— every count comes fromgraph’s artifact - VERIFIES [[refactor.fan_in_graph_sourced]]
Scenario: unrelated_fan_in_flagged
- WHEN
node_referenced_by_dependents_in_three_disjoint_top_level_namespaces() - THEN
check(node) == found - VERIFIES [[refactor.unrelated_fan_in_flagged]]
Scenario: related_fan_in_not_flagged
- WHEN
node_referenced_only_by_dependents_within_its_own_namespace_subtree() - THEN
check(node) == clean - VERIFIES [[refactor.related_fan_in_not_flagged]]
Scenario: narrow_diff_flagged_low_fan_in
- WHEN
changeset_touching_one_of_five_constraints_owned_by_a_node_with_fan_in(1) - THEN
check(node) == found— flagged even though fan-in alone wouldn’t trigger it - VERIFIES [[refactor.narrow_diff_flagged_low_fan_in]]
Scenario: emitted_finding_shape
- WHEN
found state reached for node N with dependent_count 4, 2 unrelated - THEN
emits(found) == {node_id: N, dependent_count: 4, unrelated_namespace_count: 2, suggested_split: true} - VERIFIES [[refactor.emitted_finding_shape]]
Scenario: threshold_read_from_config
- WHEN
same_node_same_changeset_under_two_repo_configs_with_different_high_fan_in_values() - THEN
flagged(config_A) != flagged(config_B)— the decision follows the repo’s configured threshold, never a constant embedded in this file - VERIFIES [[refactor.threshold_read_from_config]]
orchestrate
id: orchestrate
kind: intent
checked_against_core: clear
statement: “WHEN a repo’s files have each reached parsed, THE orchestrator SHALL run every Checker Ownership checker in dependency order and then drive compile, model_check, and verify in sequence, halting at the first stage that fails.”
Orchestrator
STATUS.md §4’s last prioritized item: something that actually runs the
pipeline specodelic.md describes, the way ddl orchestrates the rest of
that tool ecosystem. Every stage already has its own spec —
linter-frontmatter.md through linter-schema_shape.md plus
linter-coverage.md for lint, compile.md for compile,
model_check.md for model_check, verify.md for verify — but nothing
until now specified the thing that calls them in the right order, on the
right inputs, and stops (or doesn’t) when one of them fails. This file is
that spec, not a reimplementation of any stage’s own logic: it owns
sequencing and gating, and defers every actual check to the file that
already owns it.
Purpose
STATUS.md §4’s last prioritized item: something that actually runs the
pipeline specodelic.md describes, the way ddl orchestrates the rest of
that tool ecosystem. Every stage already has its own spec —
linter-frontmatter.md through linter-schema_shape.md plus
linter-coverage.md for lint, compile.md for compile,
model_check.md for model_check, verify.md for verify — but nothing
until now specified the thing that calls them in the right order, on the
right inputs, and stops (or doesn’t) when one of them fails. This file is
that spec, not a reimplementation of any stage’s own logic: it owns
sequencing and gating, and defers every actual check to the file that
already owns it.
Constraints
| id | kind | expr | traces_to | satisfies |
|—————————————|———–|————————————————————————————————————————————————|–––––––––––|
| lint_gate_matches_checker_ownership | invariant | orchestrator reports lint success iff every terminal node of specodelic.md's Checker Ownership table reports passed — never a subset or a superset of that table | [[orchestrate]] | |
| dependency_respecting_skip | invariant | a checker is invoked only after every checker listed in its Checker Ownership 'Depends on' column has reported passed; if a dependency reports failed, its dependents are skipped (not invoked, not reported as failed) rather than run against input the failed checker hasn't validated | [[orchestrate]] | |
| independent_branches_run_regardless | invariant | two checkers with no dependency relation between them (referential_integrity→graph_shape→model_shape vs. ears_syntax vs. schema_shape) each run and report independently — one branch failing never skips or blocks the other | [[orchestrate]] | |
| compile_gate_matches_coverage | invariant | orchestrator advances to model_check iff linter.coverage reports passed — exactly specodelic.md's compile transition gate (coverage + law_requires_cases), not a looser or stricter check | [[orchestrate]] | |
| stage_order_fixed | invariant | compile is never invoked before every file's lint stage reports passed; model_check is never invoked before compile reports compiled; verify is never invoked before model_check reports model_checked | [[orchestrate]] | |
| external_completeness_never_gating | invariant | linter.external_completeness runs only if the repo declares a checklist, and its outcome (passed/failed/not_applicable) never blocks or delays lint, compile, model_check, or verify | [[orchestrate]] | |
| deterministic_rerun | invariant | running the orchestrator twice against an unchanged repo produces byte-identical reports | [[orchestrate]] | |
| lint_stage_failure | effect | orchestrate.lint_stage_failure(stage, detail) | [[orchestrate]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| compile_stage_failure | effect | orchestrate.compile_stage_failure(stage, detail) | [[orchestrate]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| model_check_stage_failure | effect | orchestrate.model_check_stage_failure(stage, detail) | [[orchestrate]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| verify_stage_failure | effect | orchestrate.verify_stage_failure(stage, detail) | [[orchestrate]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
idlelint_stagecompile_stagemodel_check_stageverify_stagesucceededlint_failed(emits:[[orchestrate.lint_stage_failure]])compile_failed(emits:[[orchestrate.compile_stage_failure]])model_check_failed(emits:[[orchestrate.model_check_stage_failure]])verify_failed(emits:[[orchestrate.verify_stage_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| start_lint | idle | lint_stage | [[specodelic.parsed]] — every file in the repo has independently reached that state (see specodelic.md’s own lifecycle) |
| lint_ok | lint_stage | compile_stage | [[orchestrate.lint_gate_matches_checker_ownership]] ∧ [[orchestrate.dependency_respecting_skip]] ∧ [[orchestrate.independent_branches_run_regardless]] |
| lint_fail | lint_stage | lint_failed | ¬lint_ok.guard |
| compile_ok | compile_stage | model_check_stage | [[orchestrate.compile_gate_matches_coverage]] |
| compile_fail | compile_stage | compile_failed | ¬compile_ok.guard |
| model_check_ok | model_check_stage | verify_stage | [[orchestrate.stage_order_fixed]] ∧ model_check.md's model_checked/no_counterexample distinction resolved |
| model_check_fail | model_check_stage | model_check_failed | ¬model_check_ok.guard |
| verify_ok | verify_stage | succeeded | [[orchestrate.stage_order_fixed]] ∧ verify.md's both_gates_required holds |
| verify_fail | verify_stage | verify_failed | ¬verify_ok.guard |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| upstream_failure_skips_dependents | unit | [[orchestrate.dependency_respecting_skip]] | repo_where(linter.frontmatter: fails) | invocation_count(linter.referential_integrity) == 0 — never run against unvalidated input |
| independent_branch_failure_isolated | unit | [[orchestrate.independent_branches_run_regardless]] | repo_where(linter.ears_syntax: fails, linter.referential_integrity_branch: passes) | report(linter.graph_shape) == passed — the passing branch’s own outcome is unaffected |
| compile_blocked_on_partial_lint | unit | [[orchestrate.stage_order_fixed]] | repo_where(five_of_six_checkers_pass, one_still_pending) | invocation_count(compile) == 0 |
| external_completeness_failure_ignored | unit | [[orchestrate.external_completeness_never_gating]] | repo_where(all_seven_checkers_and_pipeline_pass, declared_checklist: fails) | orchestrate reaches succeeded |
| rerun_idempotent | unit | [[orchestrate.deterministic_rerun]] | run_orchestrator_twice_against_unchanged_repo() | report(run_1) == report(run_2) |
| clean_repo_succeeds | unit | [[orchestrate.lint_gate_matches_checker_ownership]] | arbitrary_repo_that_independently_passes_lint_compile_model_check_verify() | orchestrate reaches succeeded |
| coverage_failure_holds_compile | unit | [[orchestrate.compile_gate_matches_coverage]] | repo_where(linter.coverage: failed, every_other_checker_and_pipeline_stage: passed) | orchestrate halts at compile_stage — never reaches model_check_stage, and by exactly the coverage checker’s verdict, not a looser or stricter one |
| lint_stage_failure_label_asserted | unit | [[orchestrate.lint_stage_failure]] | lint_stage_failure_raised() | error_label == "orchestrate.lint_stage_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| compile_stage_failure_label_asserted | unit | [[orchestrate.compile_stage_failure]] | compile_stage_failure_raised() | error_label == "orchestrate.compile_stage_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| model_check_stage_failure_label_asserted | unit | [[orchestrate.model_check_stage_failure]] | model_check_stage_failure_raised() | error_label == "orchestrate.model_check_stage_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| verify_stage_failure_label_asserted | unit | [[orchestrate.verify_stage_failure]] | verify_stage_failure_raised() | error_label == "orchestrate.verify_stage_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
checked_against_core: clear (see AGENTS.md’s convention). As with
rename.md and linter-external_completeness.md, this file’s constraints
are local to what sequencing and gating specifically require — none of
them assert anything about spec-file content that specodelic.md itself
should own.
Scope boundary: this file drives the four-stage pipeline, not
rename. rename.md’s (old_id, new_id) operation is invoked on
demand by a person or tool, never automatically as part of a
lint/compile/model_check/verify run — folding it in here would make an
ordinary pipeline run capable of silently mutating the repo it’s checking,
which is exactly the kind of implicit side effect AGENTS.md’s workflow
rules exist to prevent. If a repo’s CI wants “verify, then rename some
stale id, then re-verify” as one recipe, that’s a policy composed of
orchestrate and rename calls by whatever invokes both — not a fact
either file should assert about the other.
Why model_check_ok/verify_ok use a plain-text clause alongside a
bracketed one. stage_order_fixed only says when model_check/
verify may start, not what result they must report to count as success
— that’s model_check.md’s model_checked/no_counterexample
distinction (CLAR-003) and verify.md‘s both_gates_required,
respectively, neither of which is itself phrased as a single Constraint
row this file could point a guard at. Rather than restate those files’
internal logic as a new orchestrate-local constraint (duplicating logic
this file doesn’t own), the guard names the fact in prose and leaves the
actual check to the file that already owns it — the same “same claim,
different altitude, don’t restate the logic” discipline
linter-referential_integrity.md’s Notes describe for rename_naturality.
Where graph.md, refactor.md, and merge.md fit. None of the three
are part of this file’s own four-stage pipeline. This file needs no
refactor_advisory_never_gates-style invariant of its own: refactor.md’s
finding is emitted via emits (kind == effect), and
[[specodelic.ref_kind_compatible]]’s existing typing already makes any
effect- or advisory-kind Constraint ineligible as a guard target,
tested generically by [[specodelic.advisory_cannot_gate]] — nothing
here can reference refactor.md’s output as a guard even by mistake, so
asserting it again would only restate an already-proven fact. Contrast
external_completeness_never_gating above, which is still needed: that
checker’s own constraints are invariant-kind, so its non-gating status
holds only because no transition below happens to cite it — a wiring fact
this file must keep asserting, not a typing fact guaranteed forever the
way refactor.md’s is. graph.md is a standing derived artifact other
files query, not a pipeline stage, and merge.md runs at merge time,
outside a single repo’s own lint→verify run, the same way rename.md is
explicitly out of scope above. This orchestrator stays scoped to the four
stages specodelic.md already defines; it doesn’t grow a fifth or sixth
stage just because new files exist to query.
graph.md’s reference graph and this file’s Checker Ownership table are
two different graphs, not one generalized into the other. Checker
Ownership’s “Depends on” column is tool-execution-order metadata — which
linter must finish before another starts — fixed at design time and never
derived from any spec file’s content. graph.md’s adjacency structure is
content-reference edges (traces_to/guard/emits/etc.) mechanically
extracted from spec files. They happen to look similar (both are small
dependency graphs over roughly the same set of files) but answer different
questions, and folding Checker Ownership into graph.md — replacing this
table with a graph.md query — would conflate tooling sequencing with
content dependency. Flagged here specifically because the resemblance
makes that an easy mistake to make later, not because either file is
unclear on its own.
With this file, every item STATUS.md §4 had prioritized (P0 through
the old P2) is now specced: the compile/verify pipeline, the rename
tool, external completeness, and this orchestrator. What’s left in §4 is
the lower-severity, already-flagged loose ends (CLAR-001 through
CLAR-003, EXCL-001, and the acknowledged no_prose_field_parsed shape
mismatch) — none of which block one another or this file, and none of
which were newly surfaced by writing it.
Decision of record (mp1 row 4, 2026-09-30): the orchestrator stays
scoped to parsed → verified. The original open question — whether
this file should also own driving draft → parsed for each file, or
only assume files have independently reached parsed as
start_lint’s precondition — is resolved in favor of scope: parsing
remains outside this file’s Model. The orchestrator still runs the
parse step and reports its outcome (a spec parse error is the only
parse failure; hostile-input and non-spec skips are labeled warnings
per the ingestion gate), and parse failures gate lint through
start_lint’s precondition — but the draft→parsed transition’s guards
([[specodelic.frontmatter_valid]] ∧ [[specodelic.id_matches_file]])
are enforced where they already live, in the frontmatter checker and
the parser, not re-owned here. Rationale: the spec deliberately scoped
the Model to the four pipeline stages; nothing in pipeline v1 requires
a single entry point that owns parsing, and a run against a
draft-only repo is simply a run whose parse stage reports the files
that never reached parsed — not a different Model.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: upstream_failure_skips_dependents
- WHEN
repo_where(linter.frontmatter: fails) - THEN
invocation_count(linter.referential_integrity) == 0— never run against unvalidated input - VERIFIES [[orchestrate.upstream_failure_skips_dependents]]
Scenario: independent_branch_failure_isolated
- WHEN
repo_where(linter.ears_syntax: fails, linter.referential_integrity_branch: passes) - THEN
report(linter.graph_shape) == passed— the passing branch’s own outcome is unaffected - VERIFIES [[orchestrate.independent_branch_failure_isolated]]
Scenario: compile_blocked_on_partial_lint
- WHEN
repo_where(five_of_six_checkers_pass, one_still_pending) - THEN
invocation_count(compile) == 0 - VERIFIES [[orchestrate.compile_blocked_on_partial_lint]]
Scenario: external_completeness_failure_ignored
- WHEN
repo_where(all_seven_checkers_and_pipeline_pass, declared_checklist: fails) - THEN
orchestrate reaches succeeded - VERIFIES [[orchestrate.external_completeness_failure_ignored]]
Scenario: rerun_idempotent
- WHEN
run_orchestrator_twice_against_unchanged_repo() - THEN
report(run_1) == report(run_2) - VERIFIES [[orchestrate.rerun_idempotent]]
Scenario: clean_repo_succeeds
- WHEN
arbitrary_repo_that_independently_passes_lint_compile_model_check_verify() - THEN
orchestrate reaches succeeded - VERIFIES [[orchestrate.clean_repo_succeeds]]
Scenario: coverage_failure_holds_compile
- WHEN
repo_where(linter.coverage: failed, every_other_checker_and_pipeline_stage: passed) - THEN
orchestrate halts at compile_stage— never reaches model_check_stage, and by exactly the coverage checker’s verdict, not a looser or stricter one - VERIFIES [[orchestrate.coverage_failure_holds_compile]]
Scenario: lint_stage_failure_label_asserted
- WHEN
lint_stage_failure_raised() - THEN
error_label == "orchestrate.lint_stage_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[orchestrate.lint_stage_failure_label_asserted]]
Scenario: compile_stage_failure_label_asserted
- WHEN
compile_stage_failure_raised() - THEN
error_label == "orchestrate.compile_stage_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[orchestrate.compile_stage_failure_label_asserted]]
Scenario: model_check_stage_failure_label_asserted
- WHEN
model_check_stage_failure_raised() - THEN
error_label == "orchestrate.model_check_stage_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[orchestrate.model_check_stage_failure_label_asserted]]
Scenario: verify_stage_failure_label_asserted
- WHEN
verify_stage_failure_raised() - THEN
error_label == "orchestrate.verify_stage_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[orchestrate.verify_stage_failure_label_asserted]]
conform — checking a spec against reality, not against itself
Every other verb in the pipeline evaluates the spec’s own claims: lint
checks the file’s shape, compile and model-check explore the model the
file declares, verify checks the file’s opt-in claims. spk conform is
the one verb that brings in evidence from outside the spec: it
classifies traces recorded from an external oracle — a legacy system, a
curated set of cases, a reference implementation — against what the spec
declares and can actually execute.
Usage
$ spk conform <spec-file> --oracle scenarios.jsonl [--closed-world]
The scenario corpus is JSONL: one record per line, each carrying id,
an optional setup state, and a trace of actions with observations.
Anything else — unknown fields, malformed lines, missing or duplicate
ids — is refused with a remediation hint, never silently ignored.
The verdict taxonomy
Every trace receives exactly one verdict from a closed set, each with a labeled reason:
| Verdict | Meaning |
|---|---|
permitted | a declared, executable claim covers the trace and the trace agrees with it |
forbidden | the trace violates a declared executable claim (any invocation mode), or — only under --closed-world — nothing declared covers it |
underspecified | no declared Model element or claim covers the trace (open-world default: a missing transition is a spec gap, never a prohibition) |
unknown | a claim covers the trace but is prose-only — prose is never silently checked |
unsupported | the covering claim’s evaluator kind is not executable in this run |
Exit codes follow the same honesty discipline as the rest of the
pipeline: 0 = no forbidden or unsupported verdict; 1 = any such
verdict; 2 = invocation error or gate refusal. unknown and
underspecified are surfaced as counts but never fail the run — they are
spec-gap findings, not refutations.
Gates and lifecycle
conform runs only over a file that parses, lints clean, and has current
compiled artifacts — the same stage discipline orchestrate applies
between stages. A stale artifact or a lint-dirty file is refused with the
remediation hint (spk compile / spk lint), and no verdicts are
derived from it.
conform is deliberately outside the artifact lifecycle: it never
advances draft → parsed → linted → compiled → model_checked → verified,
is never a stage of spk orchestrate, and a conform run — even one that
ends in forbidden verdicts — never gates or blocks a lifecycle
transition, and writes nothing: not the spec, not the artifacts, not the
report. It consumes, classifies, and reports.
Evidence scope
The report — in both the JSON envelope and the --human view — carries a
fixed evidence_scope field. It is data, not documentation, so no
reformatting can strip it:
agreement on the supplied corpus; not a proof of behavioral equality
A finite set of recorded traces can agree with the spec without the spec describing the system. conform tells you exactly how far its evidence reaches — and no further. For how that fits the other verbs’ guarantees, see verification boundaries; for the lifecycle stages conform stays outside of, see orchestrate and the command reference.
conform — capability
id: conform kind: intent statement: “WHEN conform runs against a lint-clean spec file with current compiled artifacts and an external scenario corpus, THE capability SHALL classify each oracle trace with a verdict from the closed taxonomy, SHALL forbid positively-contradicted traces in any invocation mode while reserving closed-world-declared forbidden for uncovered traces, SHALL keep underspecified, unknown, and unsupported distinct with reasons, and SHALL emit a scope-bound, deterministic report that never treats uninterpreted prose as checked or agreement on the corpus as behavioral equality.”
conform Specification
Purpose
Check a spec against reality instead of against itself: classify traces recorded from an external oracle (a legacy system, curated cases, a reference implementation) against what the spec declares and can execute. conform is the external-evidence leg the pipeline lacks — every other verb evaluates the spec’s own claims. Its discipline is the negative space: a missing transition is underspecification, never prohibition; prose is never silently checked; agreement on a finite corpus is never behavioral equality. conform is read-only over specs and oracles alike — it evaluates, it never generates traces, implementations, or verdicts beyond its declared scope.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| read_only_evaluation | invariant | conform consumes the parsed/linted/compiled spec artifacts and the scenario corpus as given; it never writes spec files, never generates or records traces, and never implements the domain system | [[conform]] |
| closed_world_gated | invariant | forbidden verdicts come from exactly two rules, both recorded in the verdict record: a trace that violates a declared executable claim is forbidden in any invocation mode (the same fact model_check reports as counterexample_found); a trace no declared Model element or claim covers is forbidden only under an explicitly declared closed-world mode (--closed-world) and is underspecified in the default open-world mode — absence of coverage alone is never forbidden without the declaration | [[conform]] |
| verdict_taxonomy_distinct | invariant | every trace receives exactly one verdict from the closed set {permitted, forbidden, underspecified, unknown, unsupported}: underspecified (no declared claim or Model element covers the trace) ≠ unknown (a claim covers it but is not interpretable) ≠ unsupported (the covering claim's evaluator kind is not executable in this run), each verdict record carrying its reason | [[conform]] |
| prose_never_checked | invariant | verdicts derive only from structural Model facts and executable claims (kernel, rust: fragment, citation); a prose-only claim covering a trace contributes unknown with reasons — prose wording, a constraint's name, or a deriving Property never implies a permitted/forbidden judgment, mirroring required_claims_classified | [[conform]] |
| verdict_report_schema | invariant | the run report declares its own conform-local report_schema_version, per-trace verdict records (scenario id, verdict, reason, evaluated claim ids, closed_world flag), an evidence_scope field stating agreement-on-corpus-not-equality, and a scope digest binding the parsed structured content and the consumed scenario corpus bytes — reordered CLI paths or prose edits preserve the digest, changed claims or scenarios never share one; re-running conform over unchanged inputs is byte-identical: verdict records sorted by scenario id, no timestamps, no iteration-order leakage | [[conform]] |
| exit_code_verdict_mapping | invariant | exit 0 iff no verdict in the report is forbidden or unsupported; exit 1 iff any verdict is forbidden or unsupported; unknown and underspecified verdicts never affect the exit code and are surfaced with counts in both report views; invocation errors and gate refusals remain exit 2 without verdict records | [[conform]] |
| artifact_gate | invariant | conform runs only over a spec file that parses, lints clean, and has current compiled artifacts; an ungated invocation is refused with a remediation hint naming the failing stage — verdicts are never derived from stale or invalid artifacts | [[conform]] |
| lifecycle_outside | invariant | conform is an evaluation verb outside the artifact lifecycle: it never advances draft→parsed→linted→compiled→model_checked→verified, is never part of orchestrate, and a conform run never gates or blocks a lifecycle transition | [[conform]] |
Model
States
input_readartifacts_gatedclaims_classifiedtraces_classifiedreported
Transitions
| id | from | to | guard |
|---|---|---|---|
| gate_artifacts | input_read | artifacts_gated | [[conform.artifact_gate]] ∧ the target file parses, lints clean, and its compiled artifacts are current |
| classify_claims | artifacts_gated | claims_classified | [[conform.prose_never_checked]] ∧ each invariant claim is classified executable or prose-only with its evaluator kind |
| classify_traces | claims_classified | traces_classified | [[conform.verdict_taxonomy_distinct]] ∧ every corpus trace has been assigned exactly one closed-taxonomy verdict with reason |
| emit_report | traces_classified | reported | [[conform.verdict_report_schema]] ∧ the report carries per-trace records, evidence_scope, and the scope digest |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| consumes_never_writes | unit | [[conform.read_only_evaluation]] | conform_run_over_fixture_corpus() | git status clean and spec bytes unchanged after the run — no artifact outside the report was written |
| contradiction_forbidden_any_mode | unit | [[conform.closed_world_gated]] | contradicting_trace_without_closed_world() | verdict == forbidden ∧ record.closed_world == false ∧ reason names the contradicted executable claim |
| closed_world_forbidden_recorded | unit | [[conform.closed_world_gated]] | uncovered_trace_with_closed_world() | verdict == forbidden ∧ record.closed_world == true ∧ reason names the closed-world exhaustiveness declaration |
| uncovered_trace_never_forbidden | unit | [[conform.closed_world_gated]] | uncovered_trace_without_closed_world() | verdict == underspecified — absence of coverage is never forbidden without the declaration |
| taxonomy_is_total_and_distinct | unit | [[conform.verdict_taxonomy_distinct]] | corpus_with_all_five_verdicts() | each trace has exactly one verdict; all five values are reachable; no trace maps to two |
| prose_yields_unknown | unit | [[conform.prose_never_checked]] | spec_with_prose_only_invariant() | covering traces report unknown with reason naming the prose claim — never permitted/forbidden |
| unsupported_kind_named | unit | [[conform.prose_never_checked]] | claim_with_unsupported_evaluator_kind() | verdict == unsupported ∧ reason names the evaluator kind |
| report_schema_roundtrip | unit | [[conform.verdict_report_schema]] | run_report_persisted_and_reread() | report carries report_schema_version, per-trace records, evidence_scope, scope_sha256 |
| digest_binds_scenarios | unit | [[conform.verdict_report_schema]] | two_runs_differing_only_in_scenarios() | different scenario bytes → different scope digest; identical inputs → identical digest |
| rerun_byte_identical | unit | [[conform.verdict_report_schema]] | same_inputs_conformed_twice() | both reports byte-identical — records sorted by scenario id, no timestamps |
| exit_codes_match_verdicts | unit | [[conform.exit_code_verdict_mapping]] | reports_across_all_verdict_classes() | exit code matches the mapping for each verdict-class composition |
| stale_artifacts_refused | unit | [[conform.artifact_gate]] | spec_modified_after_compile() | invocation refused with remediation hint naming the stage — no verdict records emitted |
| lint_dirty_refused | unit | [[conform.artifact_gate]] | spec_with_lint_findings() | invocation refused with hint to run spk lint — no verdict records emitted |
| outside_lifecycle | unit | [[conform.lifecycle_outside]] | orchestrate_run_with_conform_available() | orchestrate never invokes conform; conform run leaves the artifact lifecycle stage unchanged |
Requirements
Requirement: Oracle trace classification
The system SHALL provide spk conform <spec-path> --oracle <scenarios.jsonl>
classifying each trace in the corpus with exactly one verdict from the
closed taxonomy {permitted, forbidden, underspecified, unknown,
unsupported}, each verdict record carrying its reason and the ids of the
claims evaluated, deriving only from structural Model facts and executable
claims — never from prose, never from a constraint’s name, and never from
the existence of a deriving Property.
Scenario: Executable claim contradicts a trace
- WHEN a corpus trace’s observed transition violates an executable invariant claim (
**rust:**fragment or kernel atom) over the compiled model - THEN the trace is classified
forbiddenand the verdict record names the contradicted claim id in its reason
Scenario: Prose-only claim covers a trace
- WHEN the only claim covering a trace has a prose-only expr (no kernel,
**rust:**, or citation marker) - THEN the trace’s verdict is
unknownwith a reason naming the prose claim — neverpermittedorforbidden
Scenario: No declared claim covers a trace
- WHEN a trace references a transition or observation no declared Model element or claim covers
- THEN the verdict is
underspecifiedin the default open-world mode
Requirement: Closed-world gate
The system SHALL separate the two prohibition evidence classes: a trace
that violates a declared, executable claim SHALL be forbidden in any
invocation mode, and the closed-world declaration SHALL be recorded in
every verdict record and the report header.
Scenario: Contradiction forbidden in any mode
- WHEN a trace violates an executable claim and the run was invoked without
--closed-world - THEN the verdict is
forbiddenand its record carriesclosed_world: falsewith the contradicted claim id in the reason
Requirement: Uncovered traces follow the closed-world declaration
A trace no declared Model element or claim covers SHALL be forbidden
only when the invocation explicitly declared closed-world mode via
--closed-world; in the default open-world mode, absence of coverage
SHALL yield underspecified, never forbidden.
Scenario: Uncovered trace forbidden under the declaration
- WHEN a trace no declared claim or Model element covers and the run was invoked with
--closed-world - THEN the verdict is
forbiddenand its record carriesclosed_world: truewith the exhaustiveness declaration named in the reason
Scenario: Uncovered trace underspecified without the declaration
- WHEN the same uncovered trace occurs in a run without
--closed-world - THEN the verdict is
underspecified, neverforbidden
Requirement: Verdict report schema and evidence scope
The system SHALL emit the verdict report through the standard envelope
conventions (JSON default for pipes, --human for TTYs), carrying a
conform-local report_schema_version, per-trace verdict records (scenario
id, verdict, reason, evaluated claim ids, closed_world flag), and an
evidence_scope field stating that the report establishes agreement on
the supplied corpus and not behavioral equality.
Scenario: Evidence scope present in both views
- WHEN the report is rendered as JSON and as
--human - THEN both views carry the same
evidence_scopestatement — no view can omit it
Requirement: Scope digest and deterministic re-runs
The report SHALL carry a scope digest binding the parsed structured content and the consumed scenario corpus bytes. Re-running conform over unchanged inputs SHALL produce byte-identical reports — verdict records sorted by scenario id, no timestamps, no iteration-order leakage — and a corpus with zero traces SHALL yield a valid report with zero records and the evidence_scope intact.
Scenario: Scope digest binds inputs
- WHEN two runs consume identical spec content but scenario corpora differing by one byte
- THEN the two reports carry different scope digests; identical inputs yield identical digests
Scenario: Re-runs are byte-identical
- WHEN conform runs twice over unchanged spec and corpus inputs
- THEN both reports are byte-identical — records sorted by scenario id, no timestamps
Scenario: Empty corpus yields a valid empty report
- WHEN the
--oraclefile contains zero trace records - THEN conform exits 0 with a valid report carrying zero verdict records and the evidence_scope intact
Requirement: Exit-code contract
The system SHALL map exit codes to verdict classes: exit 0 when no verdict
in the report is forbidden or unsupported; exit 1 when any verdict is
forbidden or unsupported; unknown and underspecified verdicts
SHALL NOT affect the exit code and SHALL be surfaced with counts in both
report views. Invocation errors and gate refusals SHALL remain exit 2.
Scenario: Violation-class verdict fails the run
- WHEN the report contains at least one
forbiddenorunsupportedverdict - THEN conform exits 1
Scenario: Coverage gaps do not fail the run
- WHEN the report contains only
permitted,unknown, andunderspecifiedverdicts - THEN conform exits 0 with the unknown and underspecified counts visible in both views
Scenario: Invocation error stays exit 2
- WHEN the spec path does not exist or the corpus is refused by the artifact gate
- THEN conform exits 2 without emitting verdict records
Requirement: Artifact gate
The system SHALL refuse to run conform over a spec file that does not
parse, does not lint clean, or whose compiled artifacts are not current,
with a remediation hint naming the failing stage (run spk lint / run spk compile), and SHALL NOT emit verdict records from such an invocation.
Scenario: Stale artifacts refused
- WHEN the spec file’s structured content changed after its last compile
- THEN conform refuses with a hint naming compile, and no verdict records are emitted
Scenario: Lint-dirty file refused
- WHEN the target file carries lint findings
- THEN conform refuses with a hint naming lint, and no verdict records are emitted
Requirement: Outside the artifact lifecycle
The system SHALL keep conform outside the artifact lifecycle: a conform run
SHALL NOT advance the spec artifact’s lifecycle stage, SHALL NOT gate any
lifecycle transition, and SHALL NOT be part of orchestrate runs.
Scenario: Orchestrate never runs conform
- WHEN
spk orchestrateruns over a spec file with conform available - THEN the pipeline is lint → compile → model_check → verify as specified and conform is not invoked
Scenario: Conform does not advance lifecycle
- WHEN conform completes over a
model_checkedartifact - THEN the artifact’s lifecycle stage is unchanged after the run
errors
id: errors kind: intent statement: “THE error contract SHALL publish the cross-cutting output contract — error-kind envelopes with ok:false, the 0/1/2 exit-code mapping, one file-id-namespaced labeled error per failing stage, and a non-empty remediation hint — as extension_point rows that tool files satisfy from their own files, and SHALL require every failure terminal in a tool file’s Model to emit a labeled error Constraint backed by a falsifying property asserting the exact label.”
Errors
Purpose
Errors in the corpus become typed, namespaced, and falsifiable facts
instead of CHANGELOG lore and mute failed states. The cross-cutting
output contract (envelope kind, exit codes, one labeled failure per
stage, remediation hints) lives here as extension_point rows every
tool file points satisfies at; every tool file’s failure terminal
emits a file-owned labeled error Constraint; every label carries a unit
property asserting the exact label — the same red→green discipline
every other corpus fact already has.
Scope note: this file spans two altitudes, kept distinct throughout —
lint-time corpus rules (failure states, guards: about the content of
specs/*.md files) and the runtime output contract (envelope, exit
codes: about tool behavior). “Emits” names the format mechanism;
runtime reporting is “reports”. Enforcement routing is itself specced:
enforcement_routed below states which tier enforces each rule today,
which waits for linter-failure_shape, and which rides the
compile/verify pipeline.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| error_expr_shape | invariant | every error Constraint's expr is a file-id-namespaced variant head: <owning-file-id>.<variant_head>(field, …) — the label names its owning file, so label uniqueness is a per-file property and cross-file collisions are structurally impossible (pattern-checking the structured expr field follows the ears_statement precedent — expr is not rationale; prose_untouched is untouched) | [[errors]] |
| failure_state_emits | invariant | every failure terminal state in a tool file's Model carries an emits edge to an effect Constraint owned by that file, whose expr satisfies error_expr_shape — a failure state emits nothing is a malformed model (phase-2 terminals timed_out / exploration_only are excluded in v1) | [[errors]] |
| failure_class_is_state | invariant | a state with ≥2 inbound failure transitions whose guard citation sets differ is malformed — each distinct failure class is its own failure state; decidable from the graph alone (compile.md's two-class prose argument is the rationale, never the check — prose is not read) | [[errors]] |
| guard_negation_typed | invariant | a failure transition whose guard cites ≥1 [[id]] must cite exactly the citation set of the success transition(s) it negates — a negated disjunction cites the union of its branches' citation sets; a failure transition citing no ids at all is either on the recorded carve-out list (orchestrate.md's stage pipeline, per its own Notes) or malformed — the carve-out is a checked list in linter-failure_shape.md, never a silent assumption | [[errors]] |
| single_labeled_failure | invariant | a failing tool stage reports exactly one labeled error naming the failing stage and class — never a silent partial result and never an unlabeled failure (restates compile_is_total at contract altitude) | [[errors]] |
| envelope_error_kind | extension_point | a failing stage's report is an error-kind envelope with ok == false — the ok:false ↔ "error" correspondence is the published contract row, not CHANGELOG lore | [[errors]] |
| exit_code_mapping | extension_point | the output contract maps outcomes to exit codes: 0 for a clean run, 1 for findings-or-failure, 2 for invocation error — the mapping is part of the published contract, not per-tool convention | [[errors]] |
| remediation_hint_present | extension_point | every error report carries a non-empty remediation hint (one row per concern: envelope kind, exit codes, and hint are separate rows, so satisfies edges compose granularly and each concern has its own falsifying property) | [[errors]] |
| error_property_names_label | invariant | every error Constraint has ≥1 unit property whose predicate asserts the exact label string — a label with no falsifying property is not specced, only named | [[errors]] |
| contract_published | invariant | the output contract rows live in openspec/specs/errors/spec.md as kind == extension_point, and exist only there — capability constraints state requirements about them, never duplicate them; tool files point satisfies at the errors.md rows from their own files, per the extension_point mechanism | [[errors]] |
| enforcement_routed | invariant | every rule in this file states its enforcement tier and no scenario overclaims the present tool: tier 1 — enforced today by existing checkers (emits→effect typing via ref_kind_compatible, every_state_used, coverage); tier 2 — enforced by linter-failure_shape once implemented (label-shape pattern check, mute failure states, carve-out list, negation-set equality), its scenarios are normative for that checker, not claims about today's spk lint; tier 3 — runtime rows (single labeled failure, remediation hint, exit codes) enforced via the compile/verify pipeline fixtures | [[errors]] |
Model
States
draftpublished
Transitions
| id | from | to | guard |
|---|---|---|---|
| publish | draft | published | [[errors.contract_published]] — the rows exist here, extension_point-kind, and only here |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| unnamespaced_label_rejected | unit | [[errors.error_expr_shape]] | error_constraint_with_label_lacking_file_id_prefix() | check(file) == failed — a bare extraction_failure(...) head is not a valid label (tier 2) |
| mute_failure_state_detected | unit | [[errors.failure_state_emits]] | tool_file_with_failure_terminal_and_no_emits_edge() | check(file) == failed — the corpus’s pre-change shape, asserting the red is real (tier 2; the emits-typing half is tier 1) |
| emits_wrong_kind_rejected | unit | [[errors.failure_state_emits]] | failure_state_emitting_an_invariant_constraint() | check(file) == failed — existing ref_kind_compatible typing, restated as the contract’s edge (tier 1) |
| multiclass_single_state_detected | unit | [[errors.failure_class_is_state]] | failed_state_with_inbound_failure_transitions_citing_different_constraint_sets() | check(file) == failed — compile.md’s pre-change shape, detected from the graph (tier 2) |
| untyped_negation_detected | unit | [[errors.guard_negation_typed]] | failure_transition_whose_citation_set_differs_from_its_success_transition() | check(file) == failed — citation-set inequality is graph-decidable (tier 2) |
| zero_citation_failure_flagged | unit | [[errors.guard_negation_typed]] | failure_transition_citing_zero_intrafile_constraints_not_on_carveout_list() | check(file) == failed — the carve-out is a checked list, not an escape hatch (tier 2) |
| carved_out_guard_not_flagged | unit | [[errors.guard_negation_typed]] | orchestrate_stage_fail_transition() | check(file) == passed — the carve-out is checked, not assumed (tier 2) |
| labeled_failure_is_single | unit | [[errors.single_labeled_failure]] | failing_stage_with_two_extractable_errors() | report(stage) == one labeled error naming stage and class — never a partial bundle (tier 3) |
| envelope_kind_wrong_rejected | unit | [[errors.envelope_error_kind]] | failing_stage_reported_as_ok_envelope() | check(report) == failed — a failure must never ride a success-shaped envelope (tier 3) |
| exit_code_mismatch_rejected | unit | [[errors.exit_code_mapping]] | failure_stage_with_wrong_exit_code() | check(report) == failed — exit codes are contract, not convention (tier 3) |
| hint_missing_rejected | unit | [[errors.remediation_hint_present]] | error_report_with_empty_remediation_hint() | check(report) == failed (tier 3) |
| label_property_asserts_exact_label | unit | [[errors.error_property_names_label]] | error_constraint_with_property_predicating_a_different_label() | check(file) == failed — coverage alone cannot catch this; the property must name the label |
| routing_matches_reality | unit | [[errors.enforcement_routed]] | each_rule_checked_against_its_stated_tier() | every scenario's tool claim agrees with its tier — no scenario claims today's spk lint for a tier-2 rule |
| contract_satisfied_from_consumer | unit | [[errors.contract_published]] | tool_file_with_satisfies_pointing_at_contract_row() | extraction yields exactly one satisfies edge and lint passes — the satisfies precedent’s shape |
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: unnamespaced_label_rejected
- WHEN
error_constraint_with_label_lacking_file_id_prefix() - THEN
check(file) == failed— a bareextraction_failure(...)head is not a valid label (tier 2) - VERIFIES [[errors.unnamespaced_label_rejected]]
Scenario: mute_failure_state_detected
- WHEN
tool_file_with_failure_terminal_and_no_emits_edge() - THEN
check(file) == failed— the corpus’s pre-change shape, asserting the red is real (tier 2; the emits-typing half is tier 1) - VERIFIES [[errors.mute_failure_state_detected]]
Scenario: emits_wrong_kind_rejected
- WHEN
failure_state_emitting_an_invariant_constraint() - THEN
check(file) == failed— existing ref_kind_compatible typing, restated as the contract’s edge (tier 1) - VERIFIES [[errors.emits_wrong_kind_rejected]]
Scenario: multiclass_single_state_detected
- WHEN
failed_state_with_inbound_failure_transitions_citing_different_constraint_sets() - THEN
check(file) == failed— compile.md’s pre-change shape, detected from the graph (tier 2) - VERIFIES [[errors.multiclass_single_state_detected]]
Scenario: untyped_negation_detected
- WHEN
failure_transition_whose_citation_set_differs_from_its_success_transition() - THEN
check(file) == failed— citation-set inequality is graph-decidable (tier 2) - VERIFIES [[errors.untyped_negation_detected]]
Scenario: zero_citation_failure_flagged
- WHEN
failure_transition_citing_zero_intrafile_constraints_not_on_carveout_list() - THEN
check(file) == failed— the carve-out is a checked list, not an escape hatch (tier 2) - VERIFIES [[errors.zero_citation_failure_flagged]]
Scenario: carved_out_guard_not_flagged
- WHEN
orchestrate_stage_fail_transition() - THEN
check(file) == passed— the carve-out is checked, not assumed (tier 2) - VERIFIES [[errors.carved_out_guard_not_flagged]]
Scenario: labeled_failure_is_single
- WHEN
failing_stage_with_two_extractable_errors() - THEN
report(stage) == one labeled error naming stage and class— never a partial bundle (tier 3) - VERIFIES [[errors.labeled_failure_is_single]]
Scenario: envelope_kind_wrong_rejected
- WHEN
failing_stage_reported_as_ok_envelope() - THEN
check(report) == failed— a failure must never ride a success-shaped envelope (tier 3) - VERIFIES [[errors.envelope_kind_wrong_rejected]]
Scenario: exit_code_mismatch_rejected
- WHEN
failure_stage_with_wrong_exit_code() - THEN
check(report) == failed— exit codes are contract, not convention (tier 3) - VERIFIES [[errors.exit_code_mismatch_rejected]]
Scenario: hint_missing_rejected
- WHEN
error_report_with_empty_remediation_hint() - THEN
check(report) == failed(tier 3) - VERIFIES [[errors.hint_missing_rejected]]
Scenario: label_property_asserts_exact_label
- WHEN
error_constraint_with_property_predicating_a_different_label() - THEN
check(file) == failed— coverage alone cannot catch this; the property must name the label - VERIFIES [[errors.label_property_asserts_exact_label]]
Scenario: routing_matches_reality
- WHEN
each_rule_checked_against_its_stated_tier() - THEN
every scenario's tool claim agrees with its tier — no scenario claims today's spk lint for a tier-2 rule - VERIFIES [[errors.routing_matches_reality]]
Scenario: contract_satisfied_from_consumer
- WHEN
tool_file_with_satisfies_pointing_at_contract_row() - THEN
extraction yields exactly one satisfies edge and lint passes— the satisfies precedent’s shape - VERIFIES [[errors.contract_satisfied_from_consumer]]
Graph views — derived diagrams of the corpus
Every diagram on this page is derived: rendered at docs build time by
just docs-graphs from this repo’s own corpus — never committed, never
hand-edited. That is the whole point: views are never more current or
more correct than the graph artifact they were rendered from. If a diagram and the corpus disagree, the corpus wins
and the diagram is stale — regenerate it (just docs-graphs), don’t fix
it.
The embedded primer (spk explain graph-views) carries the same
taxonomy and the one-pipe render recipes for your own corpora.
Wiring — producer→consumer connections
spk graph specs --view wiring --format mermaid: the file-level
projection of constraints.satisfies edges — which spec file’s
constraints consume which extension points. Self-loops are dropped; a
corpus with no cross-file wiring renders a labeled no_wiring note,
never a silently clean diagram.
flowchart LR compile["compile"] errors["errors"] graph["graph"] kinds["kinds"] linter_coverage["linter.coverage"] linter_ears_syntax["linter.ears_syntax"] linter_external_completeness["linter.external_completeness"] linter_failure_shape["linter.failure_shape"] linter_frontmatter["linter.frontmatter"] linter_graph_shape["linter.graph_shape"] linter_model_shape["linter.model_shape"] linter_referential_integrity["linter.referential_integrity"] linter_schema_shape["linter.schema_shape"] merge["merge"] orchestrate["orchestrate"] rename["rename"] verify["verify"] compile -->|"6 satisfies"| errors graph -->|"3 satisfies"| errors kinds -->|"6 satisfies"| errors linter_coverage -->|"6 satisfies"| errors linter_ears_syntax -->|"6 satisfies"| errors linter_external_completeness -->|"9 satisfies"| errors linter_failure_shape -->|"3 satisfies"| errors linter_frontmatter -->|"3 satisfies"| errors linter_graph_shape -->|"3 satisfies"| errors linter_model_shape -->|"6 satisfies"| errors linter_referential_integrity -->|"6 satisfies"| errors linter_schema_shape -->|"9 satisfies"| errors merge -->|"9 satisfies"| errors orchestrate -->|"12 satisfies"| errors rename -->|"9 satisfies"| errors verify -->|"3 satisfies"| errors
The same wiring, as the terminal sees it — rendered at docs build time
through the family’s external renderer (spk graph --format dot | gently --as ascii, the D8 pipe; see the boundary decision,
openspec/decisions/2026-10-16-gently-render-boundary.md). ASCII needs
no JavaScript and survives llms.txt, cat, and every plain-text
consumer:
+------------------------------+ 6 satisfies +---------------------------------+
| compile | -------------> | |
+------------------------------+ | |
+------------------------------+ | |
| graph | -------------->| errors |
+------------------------------+ | |
+------------------------------+ | |
| kinds | -------------->| |
+------------------------------+ +---------------------------------+
^
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+------------------------------+ |
| linter.coverage | -----------------|
+------------------------------+ |
|
|
|
|
|
|
|
|
|
+------------------------------+ |
| linter.ears_syntax | -----------------|
+------------------------------+ |
|
|
|
|
|
|
|
|
|
+------------------------------+ |
| linter.external_completeness | -----------------|
+------------------------------+ |
|
|
|
|
|
|
|
|-------------------------------+
| |
+------------------------------+ |
| verify | |
+------------------------------+ |
|
+-------------------------------+
|
|
|
|
|
+-------------------------------+
| |
+------------------------------+ |
| linter.failure_shape | |
+------------------------------+ |
|
+-------------------------------|
| |
+------------------------------+ |
| linter.frontmatter | |
+------------------------------+ |
|
+-------------------------------|
| |
+------------------------------+ |
| linter.graph_shape | |
+------------------------------+ |
|
+-------------------------------|
| |
+------------------------------+ |
| linter.model_shape | |
+------------------------------+ |
|
+-------------------------------|
| |
+------------------------------+ |
| linter.referential_integrity | |
+------------------------------+ |
|
+-------------------------------|
| |
+------------------------------+ |
| linter.schema_shape | |
+------------------------------+ |
|
+-------------------------------|
| |
+------------------------------+ |
| merge | |
+------------------------------+ |
|
+-------------------------------|
| |
+------------------------------+ |
| orchestrate | |
+------------------------------+ |
|
+-------------------------------+
|
+------------------------------+
| rename |
+------------------------------+
+------------------------------+
| node |
+------------------------------+
+------------------------------+
| edge |
+------------------------------+
Per-file state machines
graph_views.py states: one Mermaid flowchart per owning file,
grouped by subgraph. States stay distinct, guard edges render dashed,
typing violations render as annotated red elements, and fan-in counts
distinct sources.
%% state view — derived from spk graph --format edges (transitions grouped by owning file; guards dashed per D8)
flowchart LR
subgraph sg_acset_core["acset.core"]
direction LR
acset_core_approve["acset.core.approve"]
acset_core_approved(["acset.core.approved (fan-in 2)"])
acset_core_archive["acset.core.archive"]
acset_core_archived(["acset.core.archived (fan-in 1)"])
acset_core_implement["acset.core.implement"]
acset_core_implemented(["acset.core.implemented (fan-in 2)"])
acset_core_process_lifecycle["acset.core.process_lifecycle (fan-in 3)"]
acset_core_proposed(["acset.core.proposed (fan-in 1)"])
acset_core_approve -->|"transitions.from"| acset_core_proposed
acset_core_approve -.->|"transitions.guard"| acset_core_process_lifecycle
acset_core_approve -->|"transitions.to"| acset_core_approved
acset_core_archive -->|"transitions.from"| acset_core_implemented
acset_core_archive -.->|"transitions.guard"| acset_core_process_lifecycle
acset_core_archive -->|"transitions.to"| acset_core_archived
acset_core_implement -->|"transitions.from"| acset_core_approved
acset_core_implement -.->|"transitions.guard"| acset_core_process_lifecycle
acset_core_implement -->|"transitions.to"| acset_core_implemented
end
subgraph sg_acset_writer["acset.writer"]
direction LR
acset_writer_applied(["acset.writer.applied (fan-in 3)"])
acset_writer_apply["acset.writer.apply"]
acset_writer_apply_fail["acset.writer.apply_fail"]
acset_writer_apply_failed(["acset.writer.apply_failed (fan-in 1)"])
acset_writer_edit_application_faithful["acset.writer.edit_application_faithful (fan-in 2)"]
acset_writer_edits_compose["acset.writer.edits_compose (fan-in 2)"]
acset_writer_emit_identity["acset.writer.emit_identity (fan-in 2)"]
acset_writer_filename_follows_intent_id["acset.writer.filename_follows_intent_id (fan-in 2)"]
acset_writer_record["acset.writer.record"]
acset_writer_record_fail["acset.writer.record_fail"]
acset_writer_reparse["acset.writer.reparse"]
acset_writer_reparse_fail["acset.writer.reparse_fail"]
acset_writer_requested(["acset.writer.requested (fan-in 2)"])
acset_writer_roundtrip_failed(["acset.writer.roundtrip_failed (fan-in 1)"])
acset_writer_source_spans_recorded["acset.writer.source_spans_recorded (fan-in 2)"]
acset_writer_span_failed(["acset.writer.span_failed (fan-in 1)"])
acset_writer_spanned(["acset.writer.spanned (fan-in 3)"])
acset_writer_untouched_bytes_preserved["acset.writer.untouched_bytes_preserved (fan-in 2)"]
acset_writer_verified(["acset.writer.verified (fan-in 1)"])
acset_writer_width_padding_policy["acset.writer.width_padding_policy (fan-in 2)"]
acset_writer_write_set_atomic["acset.writer.write_set_atomic (fan-in 2)"]
acset_writer_apply -->|"transitions.from"| acset_writer_spanned
acset_writer_apply -.->|"transitions.guard"| acset_writer_filename_follows_intent_id
acset_writer_apply -.->|"transitions.guard"| acset_writer_untouched_bytes_preserved
acset_writer_apply -.->|"transitions.guard"| acset_writer_width_padding_policy
acset_writer_apply -.->|"transitions.guard"| acset_writer_write_set_atomic
acset_writer_apply -->|"transitions.to"| acset_writer_applied
acset_writer_apply_fail -->|"transitions.from"| acset_writer_spanned
acset_writer_apply_fail -.->|"transitions.guard"| acset_writer_filename_follows_intent_id
acset_writer_apply_fail -.->|"transitions.guard"| acset_writer_untouched_bytes_preserved
acset_writer_apply_fail -.->|"transitions.guard"| acset_writer_width_padding_policy
acset_writer_apply_fail -.->|"transitions.guard"| acset_writer_write_set_atomic
acset_writer_apply_fail -->|"transitions.to"| acset_writer_apply_failed
acset_writer_record -->|"transitions.from"| acset_writer_requested
acset_writer_record -.->|"transitions.guard"| acset_writer_emit_identity
acset_writer_record -.->|"transitions.guard"| acset_writer_source_spans_recorded
acset_writer_record -->|"transitions.to"| acset_writer_spanned
acset_writer_record_fail -->|"transitions.from"| acset_writer_requested
acset_writer_record_fail -.->|"transitions.guard"| acset_writer_emit_identity
acset_writer_record_fail -.->|"transitions.guard"| acset_writer_source_spans_recorded
acset_writer_record_fail -->|"transitions.to"| acset_writer_span_failed
acset_writer_reparse -->|"transitions.from"| acset_writer_applied
acset_writer_reparse -.->|"transitions.guard"| acset_writer_edit_application_faithful
acset_writer_reparse -.->|"transitions.guard"| acset_writer_edits_compose
acset_writer_reparse -->|"transitions.to"| acset_writer_verified
acset_writer_reparse_fail -->|"transitions.from"| acset_writer_applied
acset_writer_reparse_fail -.->|"transitions.guard"| acset_writer_edit_application_faithful
acset_writer_reparse_fail -.->|"transitions.guard"| acset_writer_edits_compose
acset_writer_reparse_fail -->|"transitions.to"| acset_writer_roundtrip_failed
end
subgraph sg_archive_companion["archive.companion"]
direction LR
archive_companion_archived(["archive.companion.archived (fan-in 3)"])
archive_companion_attach_shape_asserted["archive.companion.attach_shape_asserted (fan-in 1)"]
archive_companion_deploy["archive.companion.deploy"]
archive_companion_fail_closed_unverifiable["archive.companion.fail_closed_unverifiable (fan-in 2)"]
archive_companion_layer_preserved["archive.companion.layer_preserved (fan-in 1)"]
archive_companion_locate["archive.companion.locate"]
archive_companion_openspec_not_forked["archive.companion.openspec_not_forked (fan-in 1)"]
archive_companion_refused(["archive.companion.refused (fan-in 1)"])
archive_companion_reject["archive.companion.reject"]
archive_companion_resolved(["archive.companion.resolved (fan-in 1)"])
archive_companion_restored(["archive.companion.restored (fan-in 1)"])
archive_companion_deploy -->|"transitions.from"| archive_companion_archived
archive_companion_deploy -.->|"transitions.guard"| archive_companion_attach_shape_asserted
archive_companion_deploy -.->|"transitions.guard"| archive_companion_fail_closed_unverifiable
archive_companion_deploy -.->|"transitions.guard"| archive_companion_layer_preserved
archive_companion_deploy -->|"transitions.to"| archive_companion_restored
archive_companion_locate -->|"transitions.from"| archive_companion_resolved
archive_companion_locate -.->|"transitions.guard"| archive_companion_openspec_not_forked
archive_companion_locate -->|"transitions.to"| archive_companion_archived
archive_companion_reject -->|"transitions.from"| archive_companion_archived
archive_companion_reject -.->|"transitions.guard"| archive_companion_fail_closed_unverifiable
archive_companion_reject -->|"transitions.to"| archive_companion_refused
end
subgraph sg_bioimage_data_pack["bioimage.data.pack"]
direction LR
bioimage_data_pack_axes_section_shape["bioimage.data.pack.axes_section_shape (fan-in 1)"]
bioimage_data_pack_deprecate["bioimage.data.pack.deprecate"]
bioimage_data_pack_deprecated(["bioimage.data.pack.deprecated (fan-in 1)"])
bioimage_data_pack_draft(["bioimage.data.pack.draft (fan-in 1)"])
bioimage_data_pack_pack_file_declared["bioimage.data.pack.pack_file_declared (fan-in 2)"]
bioimage_data_pack_publish["bioimage.data.pack.publish"]
bioimage_data_pack_published(["bioimage.data.pack.published (fan-in 2)"])
bioimage_data_pack_deprecate -->|"transitions.from"| bioimage_data_pack_published
bioimage_data_pack_deprecate -.->|"transitions.guard"| bioimage_data_pack_pack_file_declared
bioimage_data_pack_deprecate -->|"transitions.to"| bioimage_data_pack_deprecated
bioimage_data_pack_publish -->|"transitions.from"| bioimage_data_pack_draft
bioimage_data_pack_publish -.->|"transitions.guard"| bioimage_data_pack_axes_section_shape
bioimage_data_pack_publish -.->|"transitions.guard"| bioimage_data_pack_pack_file_declared
bioimage_data_pack_publish -->|"transitions.to"| bioimage_data_pack_published
end
subgraph sg_compile["compile"]
direction LR
compile_accept["compile.accept"]
compile_begin["compile.begin"]
compile_compile_preserves_ids["compile.compile_preserves_ids (fan-in 2)"]
compile_compiled(["compile.compiled (fan-in 1)"])
compile_constraint_table_to_toml["compile.constraint_table_to_toml (fan-in 2)"]
compile_emit_failed(["compile.emit_failed (fan-in 1)"])
compile_emitting(["compile.emitting (fan-in 3)"])
compile_extract_fail["compile.extract_fail"]
compile_extract_failed(["compile.extract_failed (fan-in 1)"])
compile_extract_ok["compile.extract_ok"]
compile_extracting(["compile.extracting (fan-in 3)"])
compile_model_to_tla["compile.model_to_tla (fan-in 2)"]
compile_no_semantic_drift["compile.no_semantic_drift (fan-in 2)"]
compile_not_started(["compile.not_started (fan-in 1)"])
compile_precondition_satisfied["compile.precondition_satisfied (fan-in 1)"]
compile_properties_to_proptest["compile.properties_to_proptest (fan-in 2)"]
compile_reject["compile.reject"]
compile_accept -->|"transitions.from"| compile_emitting
compile_accept -.->|"transitions.guard"| compile_compile_preserves_ids
compile_accept -.->|"transitions.guard"| compile_no_semantic_drift
compile_accept -->|"transitions.to"| compile_compiled
compile_begin -->|"transitions.from"| compile_not_started
compile_begin -.->|"transitions.guard"| compile_precondition_satisfied
compile_begin -->|"transitions.to"| compile_extracting
compile_extract_fail -->|"transitions.from"| compile_extracting
compile_extract_fail -.->|"transitions.guard"| compile_constraint_table_to_toml
compile_extract_fail -.->|"transitions.guard"| compile_model_to_tla
compile_extract_fail -.->|"transitions.guard"| compile_properties_to_proptest
compile_extract_fail -->|"transitions.to"| compile_extract_failed
compile_extract_ok -->|"transitions.from"| compile_extracting
compile_extract_ok -.->|"transitions.guard"| compile_constraint_table_to_toml
compile_extract_ok -.->|"transitions.guard"| compile_model_to_tla
compile_extract_ok -.->|"transitions.guard"| compile_properties_to_proptest
compile_extract_ok -->|"transitions.to"| compile_emitting
compile_reject -->|"transitions.from"| compile_emitting
compile_reject -.->|"transitions.guard"| compile_compile_preserves_ids
compile_reject -.->|"transitions.guard"| compile_no_semantic_drift
compile_reject -->|"transitions.to"| compile_emit_failed
end
subgraph sg_conform["conform"]
direction LR
conform_artifact_gate["conform.artifact_gate (fan-in 1)"]
conform_artifacts_gated(["conform.artifacts_gated (fan-in 2)"])
conform_claims_classified(["conform.claims_classified (fan-in 2)"])
conform_classify_claims["conform.classify_claims"]
conform_classify_traces["conform.classify_traces"]
conform_emit_report["conform.emit_report"]
conform_gate_artifacts["conform.gate_artifacts"]
conform_input_read(["conform.input_read (fan-in 1)"])
conform_prose_never_checked["conform.prose_never_checked (fan-in 1)"]
conform_reported(["conform.reported (fan-in 1)"])
conform_traces_classified(["conform.traces_classified (fan-in 2)"])
conform_verdict_report_schema["conform.verdict_report_schema (fan-in 1)"]
conform_verdict_taxonomy_distinct["conform.verdict_taxonomy_distinct (fan-in 1)"]
conform_classify_claims -->|"transitions.from"| conform_artifacts_gated
conform_classify_claims -.->|"transitions.guard"| conform_prose_never_checked
conform_classify_claims -->|"transitions.to"| conform_claims_classified
conform_classify_traces -->|"transitions.from"| conform_claims_classified
conform_classify_traces -.->|"transitions.guard"| conform_verdict_taxonomy_distinct
conform_classify_traces -->|"transitions.to"| conform_traces_classified
conform_emit_report -->|"transitions.from"| conform_traces_classified
conform_emit_report -.->|"transitions.guard"| conform_verdict_report_schema
conform_emit_report -->|"transitions.to"| conform_reported
conform_gate_artifacts -->|"transitions.from"| conform_input_read
conform_gate_artifacts -.->|"transitions.guard"| conform_artifact_gate
conform_gate_artifacts -->|"transitions.to"| conform_artifacts_gated
end
subgraph sg_data_lineage_pack["data.lineage.pack"]
direction LR
data_lineage_pack_data_section_row_shape["data.lineage.pack.data_section_row_shape (fan-in 1)"]
data_lineage_pack_deprecate["data.lineage.pack.deprecate"]
data_lineage_pack_deprecated(["data.lineage.pack.deprecated (fan-in 1)"])
data_lineage_pack_draft(["data.lineage.pack.draft (fan-in 1)"])
data_lineage_pack_pack_file_declared["data.lineage.pack.pack_file_declared (fan-in 2)"]
data_lineage_pack_publish["data.lineage.pack.publish"]
data_lineage_pack_published(["data.lineage.pack.published (fan-in 2)"])
data_lineage_pack_deprecate -->|"transitions.from"| data_lineage_pack_published
data_lineage_pack_deprecate -.->|"transitions.guard"| data_lineage_pack_pack_file_declared
data_lineage_pack_deprecate -->|"transitions.to"| data_lineage_pack_deprecated
data_lineage_pack_publish -->|"transitions.from"| data_lineage_pack_draft
data_lineage_pack_publish -.->|"transitions.guard"| data_lineage_pack_data_section_row_shape
data_lineage_pack_publish -.->|"transitions.guard"| data_lineage_pack_pack_file_declared
data_lineage_pack_publish -->|"transitions.to"| data_lineage_pack_published
end
subgraph sg_docs_site["docs.site"]
direction LR
docs_site_authored(["docs.site.authored (fan-in 1)"])
docs_site_built(["docs.site.built (fan-in 2)"])
docs_site_deployed(["docs.site.deployed (fan-in 1)"])
docs_site_docs_build["docs.site.docs_build"]
docs_site_docs_deploy["docs.site.docs_deploy"]
docs_site_pages_deployed["docs.site.pages_deployed (fan-in 1)"]
docs_site_site_present["docs.site.site_present (fan-in 1)"]
docs_site_docs_build -->|"transitions.from"| docs_site_authored
docs_site_docs_build -.->|"transitions.guard"| docs_site_site_present
docs_site_docs_build -->|"transitions.to"| docs_site_built
docs_site_docs_deploy -->|"transitions.from"| docs_site_built
docs_site_docs_deploy -.->|"transitions.guard"| docs_site_pages_deployed
docs_site_docs_deploy -->|"transitions.to"| docs_site_deployed
end
subgraph sg_doctor["doctor"]
direction LR
doctor_classified(["doctor.classified (fan-in 2)"])
doctor_classify["doctor.classify"]
doctor_currency["doctor.currency"]
doctor_currency_checked(["doctor.currency_checked (fan-in 1)"])
doctor_currency_warning["doctor.currency_warning (fan-in 1)"]
doctor_mode_detection["doctor.mode_detection (fan-in 1)"]
doctor_probed(["doctor.probed (fan-in 1)"])
doctor_classify -->|"transitions.from"| doctor_probed
doctor_classify -.->|"transitions.guard"| doctor_mode_detection
doctor_classify -->|"transitions.to"| doctor_classified
doctor_currency -->|"transitions.from"| doctor_classified
doctor_currency -.->|"transitions.guard"| doctor_currency_warning
doctor_currency -->|"transitions.to"| doctor_currency_checked
end
subgraph sg_embedded_guide["embedded.guide"]
direction LR
embedded_guide_explain_topics["embedded.guide.explain_topics (fan-in 1)"]
embedded_guide_queried(["embedded.guide.queried (fan-in 1)"])
embedded_guide_render["embedded.guide.render"]
embedded_guide_rendered(["embedded.guide.rendered (fan-in 1)"])
embedded_guide_resolve["embedded.guide.resolve"]
embedded_guide_resolved(["embedded.guide.resolved (fan-in 2)"])
embedded_guide_revision_pinned["embedded.guide.revision_pinned (fan-in 1)"]
embedded_guide_render -->|"transitions.from"| embedded_guide_resolved
embedded_guide_render -.->|"transitions.guard"| embedded_guide_revision_pinned
embedded_guide_render -->|"transitions.to"| embedded_guide_rendered
embedded_guide_resolve -->|"transitions.from"| embedded_guide_queried
embedded_guide_resolve -.->|"transitions.guard"| embedded_guide_explain_topics
embedded_guide_resolve -->|"transitions.to"| embedded_guide_resolved
end
subgraph sg_empirical_registry_pack["empirical.registry.pack"]
direction LR
empirical_registry_pack_deprecate["empirical.registry.pack.deprecate"]
empirical_registry_pack_deprecated(["empirical.registry.pack.deprecated (fan-in 1)"])
empirical_registry_pack_draft(["empirical.registry.pack.draft (fan-in 1)"])
empirical_registry_pack_pack_file_declared["empirical.registry.pack.pack_file_declared (fan-in 2)"]
empirical_registry_pack_publish["empirical.registry.pack.publish"]
empirical_registry_pack_published(["empirical.registry.pack.published (fan-in 2)"])
empirical_registry_pack_stat_test_section_shape["empirical.registry.pack.stat_test_section_shape (fan-in 1)"]
empirical_registry_pack_deprecate -->|"transitions.from"| empirical_registry_pack_published
empirical_registry_pack_deprecate -.->|"transitions.guard"| empirical_registry_pack_pack_file_declared
empirical_registry_pack_deprecate -->|"transitions.to"| empirical_registry_pack_deprecated
empirical_registry_pack_publish -->|"transitions.from"| empirical_registry_pack_draft
empirical_registry_pack_publish -.->|"transitions.guard"| empirical_registry_pack_pack_file_declared
empirical_registry_pack_publish -.->|"transitions.guard"| empirical_registry_pack_stat_test_section_shape
empirical_registry_pack_publish -->|"transitions.to"| empirical_registry_pack_published
end
subgraph sg_error_contract["error.contract"]
direction LR
error_contract_approve["error.contract.approve"]
error_contract_approved(["error.contract.approved (fan-in 2)"])
error_contract_archive["error.contract.archive"]
error_contract_archived(["error.contract.archived (fan-in 1)"])
error_contract_implement["error.contract.implement"]
error_contract_implemented(["error.contract.implemented (fan-in 2)"])
error_contract_process_lifecycle["error.contract.process_lifecycle (fan-in 3)"]
error_contract_proposed(["error.contract.proposed (fan-in 1)"])
error_contract_approve -->|"transitions.from"| error_contract_proposed
error_contract_approve -.->|"transitions.guard"| error_contract_process_lifecycle
error_contract_approve -->|"transitions.to"| error_contract_approved
error_contract_archive -->|"transitions.from"| error_contract_implemented
error_contract_archive -.->|"transitions.guard"| error_contract_process_lifecycle
error_contract_archive -->|"transitions.to"| error_contract_archived
error_contract_implement -->|"transitions.from"| error_contract_approved
error_contract_implement -.->|"transitions.guard"| error_contract_process_lifecycle
error_contract_implement -->|"transitions.to"| error_contract_implemented
end
subgraph sg_errors["errors"]
direction LR
errors_contract_published["errors.contract_published (fan-in 1)"]
errors_draft(["errors.draft (fan-in 1)"])
errors_publish["errors.publish"]
errors_published(["errors.published (fan-in 1)"])
errors_publish -->|"transitions.from"| errors_draft
errors_publish -.->|"transitions.guard"| errors_contract_published
errors_publish -->|"transitions.to"| errors_published
end
subgraph sg_graph["graph"]
direction LR
graph_deterministic_derivation["graph.deterministic_derivation (fan-in 1)"]
graph_edge_kind_matches_typing["graph.edge_kind_matches_typing (fan-in 2)"]
graph_extract["graph.extract"]
graph_extract_fail["graph.extract_fail"]
graph_extract_ok["graph.extract_ok"]
graph_extracting(["graph.extracting (fan-in 3)"])
graph_failed(["graph.failed (fan-in 1)"])
graph_graph_is_derived_not_authored["graph.graph_is_derived_not_authored (fan-in 1)"]
graph_indexed(["graph.indexed (fan-in 2)"])
graph_invalidate["graph.invalidate"]
graph_publish["graph.publish"]
graph_queryable(["graph.queryable (fan-in 3)"])
graph_stale_graph_detected["graph.stale_graph_detected (fan-in 1)"]
graph_total_extraction["graph.total_extraction (fan-in 2)"]
graph_unindexed(["graph.unindexed (fan-in 2)"])
specodelic_parsed(["specodelic.parsed (fan-in 4)"])
graph_extract -->|"transitions.from"| graph_unindexed
graph_extract -.->|"transitions.guard"| specodelic_parsed
graph_extract -->|"transitions.to"| graph_extracting
graph_extract_fail -->|"transitions.from"| graph_extracting
graph_extract_fail -.->|"transitions.guard"| graph_edge_kind_matches_typing
graph_extract_fail -.->|"transitions.guard"| graph_total_extraction
graph_extract_fail -->|"transitions.to"| graph_failed
graph_extract_ok -->|"transitions.from"| graph_extracting
graph_extract_ok -.->|"transitions.guard"| graph_edge_kind_matches_typing
graph_extract_ok -.->|"transitions.guard"| graph_total_extraction
graph_extract_ok -->|"transitions.to"| graph_indexed
graph_invalidate -->|"transitions.from"| graph_queryable
graph_invalidate -.->|"transitions.guard"| graph_stale_graph_detected
graph_invalidate -->|"transitions.to"| graph_unindexed
graph_publish -->|"transitions.from"| graph_indexed
graph_publish -.->|"transitions.guard"| graph_deterministic_derivation
graph_publish -.->|"transitions.guard"| graph_graph_is_derived_not_authored
graph_publish -->|"transitions.to"| graph_queryable
end
subgraph sg_graph_views["graph.views"]
direction LR
graph_views_approve["graph.views.approve"]
graph_views_approved(["graph.views.approved (fan-in 2)"])
graph_views_archive["graph.views.archive"]
graph_views_archived(["graph.views.archived (fan-in 1)"])
graph_views_implement["graph.views.implement"]
graph_views_implemented(["graph.views.implemented (fan-in 2)"])
graph_views_process_lifecycle["graph.views.process_lifecycle (fan-in 3)"]
graph_views_proposed(["graph.views.proposed (fan-in 1)"])
graph_views_approve -->|"transitions.from"| graph_views_proposed
graph_views_approve -.->|"transitions.guard"| graph_views_process_lifecycle
graph_views_approve -->|"transitions.to"| graph_views_approved
graph_views_archive -->|"transitions.from"| graph_views_implemented
graph_views_archive -.->|"transitions.guard"| graph_views_process_lifecycle
graph_views_archive -->|"transitions.to"| graph_views_archived
graph_views_implement -->|"transitions.from"| graph_views_approved
graph_views_implement -.->|"transitions.guard"| graph_views_process_lifecycle
graph_views_implement -->|"transitions.to"| graph_views_implemented
end
subgraph sg_hooks["hooks"]
direction LR
hooks_chain_preserved["hooks.chain_preserved (fan-in 2)"]
hooks_honest_anchor["hooks.honest_anchor (fan-in 1)"]
hooks_idempotent_lifecycle["hooks.idempotent_lifecycle (fan-in 2)"]
hooks_install_ok["hooks.install_ok"]
hooks_refuse["hooks.refuse"]
hooks_refused(["hooks.refused (fan-in 1)"])
hooks_reinstall_ok["hooks.reinstall_ok"]
hooks_uninstall_notwired["hooks.uninstall_notwired"]
hooks_uninstall_ok["hooks.uninstall_ok"]
hooks_unwired(["hooks.unwired (fan-in 5)"])
hooks_wired(["hooks.wired (fan-in 4)"])
hooks_install_ok -->|"transitions.from"| hooks_unwired
hooks_install_ok -.->|"transitions.guard"| hooks_chain_preserved
hooks_install_ok -->|"transitions.to"| hooks_wired
hooks_refuse -->|"transitions.from"| hooks_unwired
hooks_refuse -.->|"transitions.guard"| hooks_honest_anchor
hooks_refuse -->|"transitions.to"| hooks_refused
hooks_reinstall_ok -->|"transitions.from"| hooks_wired
hooks_reinstall_ok -.->|"transitions.guard"| hooks_idempotent_lifecycle
hooks_reinstall_ok -->|"transitions.to"| hooks_wired
hooks_uninstall_notwired -->|"transitions.from"| hooks_unwired
hooks_uninstall_notwired -.->|"transitions.guard"| hooks_idempotent_lifecycle
hooks_uninstall_notwired -->|"transitions.to"| hooks_unwired
hooks_uninstall_ok -->|"transitions.from"| hooks_wired
hooks_uninstall_ok -.->|"transitions.guard"| hooks_chain_preserved
hooks_uninstall_ok -->|"transitions.to"| hooks_unwired
end
subgraph sg_kernel["kernel"]
direction LR
kernel_declare["kernel.declare"]
kernel_discharge["kernel.discharge"]
kernel_expr_absent(["kernel.expr_absent (fan-in 1)"])
kernel_expr_evaluated(["kernel.expr_evaluated (fan-in 4)"])
kernel_expr_unknown(["kernel.expr_unknown (fan-in 1)"])
kernel_kernel_decidable["kernel.kernel_decidable (fan-in 1)"]
kernel_kernel_grammar_closed["kernel.kernel_grammar_closed (fan-in 1)"]
kernel_kernel_status_three_valued["kernel.kernel_status_three_valued (fan-in 1)"]
kernel_widen["kernel.widen"]
kernel_declare -->|"transitions.from"| kernel_expr_absent
kernel_declare -.->|"transitions.guard"| kernel_kernel_grammar_closed
kernel_declare -->|"transitions.to"| kernel_expr_evaluated
kernel_discharge -->|"transitions.from"| kernel_expr_evaluated
kernel_discharge -.->|"transitions.guard"| kernel_kernel_status_three_valued
kernel_discharge -->|"transitions.to"| kernel_expr_unknown
kernel_widen -->|"transitions.from"| kernel_expr_evaluated
kernel_widen -.->|"transitions.guard"| kernel_kernel_decidable
kernel_widen -->|"transitions.to"| kernel_expr_evaluated
end
subgraph sg_kinds["kinds"]
direction LR
kinds_accept["kinds.accept"]
kinds_assign_fail["kinds.assign_fail"]
kinds_assign_ok["kinds.assign_ok"]
kinds_begin["kinds.begin"]
kinds_constraint_row_shape["kinds.constraint_row_shape (fan-in 2)"]
kinds_intent_row_shape["kinds.intent_row_shape (fan-in 2)"]
kinds_kind_assigned(["kinds.kind_assigned (fan-in 3)"])
kinds_kind_enum_closed["kinds.kind_enum_closed (fan-in 2)"]
kinds_kind_failed(["kinds.kind_failed (fan-in 1)"])
kinds_passed(["kinds.passed (fan-in 1)"])
kinds_property_row_shape["kinds.property_row_shape (fan-in 2)"]
kinds_reject["kinds.reject"]
kinds_shape_checked(["kinds.shape_checked (fan-in 3)"])
kinds_shape_failed(["kinds.shape_failed (fan-in 1)"])
kinds_state_row_shape["kinds.state_row_shape (fan-in 2)"]
kinds_transition_row_shape["kinds.transition_row_shape (fan-in 2)"]
kinds_unclassified(["kinds.unclassified (fan-in 1)"])
kinds_accept -->|"transitions.from"| kinds_shape_checked
kinds_accept -.->|"transitions.guard"| kinds_constraint_row_shape
kinds_accept -.->|"transitions.guard"| kinds_intent_row_shape
kinds_accept -.->|"transitions.guard"| kinds_property_row_shape
kinds_accept -.->|"transitions.guard"| kinds_state_row_shape
kinds_accept -.->|"transitions.guard"| kinds_transition_row_shape
kinds_accept -->|"transitions.to"| kinds_passed
kinds_assign_fail -->|"transitions.from"| kinds_kind_assigned
kinds_assign_fail -.->|"transitions.guard"| kinds_kind_enum_closed
kinds_assign_fail -->|"transitions.to"| kinds_kind_failed
kinds_assign_ok -->|"transitions.from"| kinds_kind_assigned
kinds_assign_ok -.->|"transitions.guard"| kinds_kind_enum_closed
kinds_assign_ok -->|"transitions.to"| kinds_shape_checked
kinds_begin -->|"transitions.from"| kinds_unclassified
kinds_begin -->|"transitions.to"| kinds_kind_assigned
kinds_reject -->|"transitions.from"| kinds_shape_checked
kinds_reject -.->|"transitions.guard"| kinds_constraint_row_shape
kinds_reject -.->|"transitions.guard"| kinds_intent_row_shape
kinds_reject -.->|"transitions.guard"| kinds_property_row_shape
kinds_reject -.->|"transitions.guard"| kinds_state_row_shape
kinds_reject -.->|"transitions.guard"| kinds_transition_row_shape
kinds_reject -->|"transitions.to"| kinds_shape_failed
end
subgraph sg_lint_findings["lint.findings"]
direction LR
lint_findings_emit["lint.findings.emit"]
lint_findings_emitted(["lint.findings.emitted (fan-in 1)"])
lint_findings_found(["lint.findings.found (fan-in 1)"])
lint_findings_self_describing["lint.findings.self_describing (fan-in 1)"]
lint_findings_emit -->|"transitions.from"| lint_findings_found
lint_findings_emit -.->|"transitions.guard"| lint_findings_self_describing
lint_findings_emit -->|"transitions.to"| lint_findings_emitted
end
subgraph sg_linter_coverage["linter.coverage"]
direction LR
linter_coverage_accept["linter.coverage.accept"]
linter_coverage_begin["linter.coverage.begin"]
linter_coverage_count_fail["linter.coverage.count_fail"]
linter_coverage_count_failed(["linter.coverage.count_failed (fan-in 1)"])
linter_coverage_count_ok["linter.coverage.count_ok"]
linter_coverage_counting(["linter.coverage.counting (fan-in 3)"])
linter_coverage_every_constraint_covered["linter.coverage.every_constraint_covered (fan-in 2)"]
linter_coverage_every_law_has_cases["linter.coverage.every_law_has_cases (fan-in 2)"]
linter_coverage_law_check_failed(["linter.coverage.law_check_failed (fan-in 1)"])
linter_coverage_law_checking(["linter.coverage.law_checking (fan-in 3)"])
linter_coverage_no_orphan_property["linter.coverage.no_orphan_property (fan-in 2)"]
linter_coverage_passed(["linter.coverage.passed (fan-in 1)"])
linter_coverage_reject["linter.coverage.reject"]
linter_coverage_unchecked(["linter.coverage.unchecked (fan-in 1)"])
linter_coverage_accept -->|"transitions.from"| linter_coverage_law_checking
linter_coverage_accept -.->|"transitions.guard"| linter_coverage_every_law_has_cases
linter_coverage_accept -->|"transitions.to"| linter_coverage_passed
linter_coverage_begin -->|"transitions.from"| linter_coverage_unchecked
linter_coverage_begin -->|"transitions.to"| linter_coverage_counting
linter_coverage_count_fail -->|"transitions.from"| linter_coverage_counting
linter_coverage_count_fail -.->|"transitions.guard"| linter_coverage_every_constraint_covered
linter_coverage_count_fail -.->|"transitions.guard"| linter_coverage_no_orphan_property
linter_coverage_count_fail -->|"transitions.to"| linter_coverage_count_failed
linter_coverage_count_ok -->|"transitions.from"| linter_coverage_counting
linter_coverage_count_ok -.->|"transitions.guard"| linter_coverage_every_constraint_covered
linter_coverage_count_ok -.->|"transitions.guard"| linter_coverage_no_orphan_property
linter_coverage_count_ok -->|"transitions.to"| linter_coverage_law_checking
linter_coverage_reject -->|"transitions.from"| linter_coverage_law_checking
linter_coverage_reject -.->|"transitions.guard"| linter_coverage_every_law_has_cases
linter_coverage_reject -->|"transitions.to"| linter_coverage_law_check_failed
end
subgraph sg_linter_ears_syntax["linter.ears_syntax"]
direction LR
linter_ears_syntax_accept["linter.ears_syntax.accept"]
linter_ears_syntax_begin["linter.ears_syntax.begin"]
linter_ears_syntax_ears_pattern_match["linter.ears_syntax.ears_pattern_match (fan-in 2)"]
linter_ears_syntax_has_shall["linter.ears_syntax.has_shall (fan-in 2)"]
linter_ears_syntax_id_check_failed(["linter.ears_syntax.id_check_failed (fan-in 1)"])
linter_ears_syntax_id_checking(["linter.ears_syntax.id_checking (fan-in 3)"])
linter_ears_syntax_no_conjoined_id["linter.ears_syntax.no_conjoined_id (fan-in 2)"]
linter_ears_syntax_no_universal_in_id["linter.ears_syntax.no_universal_in_id (fan-in 2)"]
linter_ears_syntax_passed(["linter.ears_syntax.passed (fan-in 1)"])
linter_ears_syntax_pattern_fail["linter.ears_syntax.pattern_fail"]
linter_ears_syntax_pattern_failed(["linter.ears_syntax.pattern_failed (fan-in 1)"])
linter_ears_syntax_pattern_matching(["linter.ears_syntax.pattern_matching (fan-in 3)"])
linter_ears_syntax_pattern_ok["linter.ears_syntax.pattern_ok"]
linter_ears_syntax_reject["linter.ears_syntax.reject"]
linter_ears_syntax_unchecked(["linter.ears_syntax.unchecked (fan-in 1)"])
linter_ears_syntax_accept -->|"transitions.from"| linter_ears_syntax_id_checking
linter_ears_syntax_accept -.->|"transitions.guard"| linter_ears_syntax_no_conjoined_id
linter_ears_syntax_accept -.->|"transitions.guard"| linter_ears_syntax_no_universal_in_id
linter_ears_syntax_accept -->|"transitions.to"| linter_ears_syntax_passed
linter_ears_syntax_begin -->|"transitions.from"| linter_ears_syntax_unchecked
linter_ears_syntax_begin -->|"transitions.to"| linter_ears_syntax_pattern_matching
linter_ears_syntax_pattern_fail -->|"transitions.from"| linter_ears_syntax_pattern_matching
linter_ears_syntax_pattern_fail -.->|"transitions.guard"| linter_ears_syntax_ears_pattern_match
linter_ears_syntax_pattern_fail -.->|"transitions.guard"| linter_ears_syntax_has_shall
linter_ears_syntax_pattern_fail -->|"transitions.to"| linter_ears_syntax_pattern_failed
linter_ears_syntax_pattern_ok -->|"transitions.from"| linter_ears_syntax_pattern_matching
linter_ears_syntax_pattern_ok -.->|"transitions.guard"| linter_ears_syntax_ears_pattern_match
linter_ears_syntax_pattern_ok -.->|"transitions.guard"| linter_ears_syntax_has_shall
linter_ears_syntax_pattern_ok -->|"transitions.to"| linter_ears_syntax_id_checking
linter_ears_syntax_reject -->|"transitions.from"| linter_ears_syntax_id_checking
linter_ears_syntax_reject -.->|"transitions.guard"| linter_ears_syntax_no_conjoined_id
linter_ears_syntax_reject -.->|"transitions.guard"| linter_ears_syntax_no_universal_in_id
linter_ears_syntax_reject -->|"transitions.to"| linter_ears_syntax_id_check_failed
end
subgraph sg_linter_external_completeness["linter.external_completeness"]
direction LR
linter_external_completeness_accept["linter.external_completeness.accept"]
linter_external_completeness_check_fail["linter.external_completeness.check_fail"]
linter_external_completeness_check_mapping["linter.external_completeness.check_mapping"]
linter_external_completeness_checklist_well_formed["linter.external_completeness.checklist_well_formed (fan-in 2)"]
linter_external_completeness_covered_maps_resolve["linter.external_completeness.covered_maps_resolve (fan-in 2)"]
linter_external_completeness_every_item_accounted["linter.external_completeness.every_item_accounted (fan-in 2)"]
linter_external_completeness_load["linter.external_completeness.load"]
linter_external_completeness_load_fail["linter.external_completeness.load_fail"]
linter_external_completeness_load_failed(["linter.external_completeness.load_failed (fan-in 1)"])
linter_external_completeness_loaded(["linter.external_completeness.loaded (fan-in 3)"])
linter_external_completeness_mapping_checked(["linter.external_completeness.mapping_checked (fan-in 3)"])
linter_external_completeness_mapping_failed(["linter.external_completeness.mapping_failed (fan-in 1)"])
linter_external_completeness_no_duplicate_claim["linter.external_completeness.no_duplicate_claim (fan-in 2)"]
linter_external_completeness_not_applicable(["linter.external_completeness.not_applicable (fan-in 2)"])
linter_external_completeness_passed(["linter.external_completeness.passed (fan-in 1)"])
linter_external_completeness_reject["linter.external_completeness.reject"]
linter_external_completeness_resolution_failed(["linter.external_completeness.resolution_failed (fan-in 1)"])
linter_external_completeness_waiver_has_rationale["linter.external_completeness.waiver_has_rationale (fan-in 2)"]
linter_external_completeness_accept -->|"transitions.from"| linter_external_completeness_mapping_checked
linter_external_completeness_accept -.->|"transitions.guard"| linter_external_completeness_covered_maps_resolve
linter_external_completeness_accept -.->|"transitions.guard"| linter_external_completeness_waiver_has_rationale
linter_external_completeness_accept -->|"transitions.to"| linter_external_completeness_passed
linter_external_completeness_check_fail -->|"transitions.from"| linter_external_completeness_loaded
linter_external_completeness_check_fail -.->|"transitions.guard"| linter_external_completeness_every_item_accounted
linter_external_completeness_check_fail -.->|"transitions.guard"| linter_external_completeness_no_duplicate_claim
linter_external_completeness_check_fail -->|"transitions.to"| linter_external_completeness_mapping_failed
linter_external_completeness_check_mapping -->|"transitions.from"| linter_external_completeness_loaded
linter_external_completeness_check_mapping -.->|"transitions.guard"| linter_external_completeness_every_item_accounted
linter_external_completeness_check_mapping -.->|"transitions.guard"| linter_external_completeness_no_duplicate_claim
linter_external_completeness_check_mapping -->|"transitions.to"| linter_external_completeness_mapping_checked
linter_external_completeness_load -->|"transitions.from"| linter_external_completeness_not_applicable
linter_external_completeness_load -.->|"transitions.guard"| linter_external_completeness_checklist_well_formed
linter_external_completeness_load -->|"transitions.to"| linter_external_completeness_loaded
linter_external_completeness_load_fail -->|"transitions.from"| linter_external_completeness_not_applicable
linter_external_completeness_load_fail -.->|"transitions.guard"| linter_external_completeness_checklist_well_formed
linter_external_completeness_load_fail -->|"transitions.to"| linter_external_completeness_load_failed
linter_external_completeness_reject -->|"transitions.from"| linter_external_completeness_mapping_checked
linter_external_completeness_reject -.->|"transitions.guard"| linter_external_completeness_covered_maps_resolve
linter_external_completeness_reject -.->|"transitions.guard"| linter_external_completeness_waiver_has_rationale
linter_external_completeness_reject -->|"transitions.to"| linter_external_completeness_resolution_failed
end
subgraph sg_linter_failure_shape["linter.failure_shape"]
direction LR
linter_failure_shape_accept["linter.failure_shape.accept"]
linter_failure_shape_begin["linter.failure_shape.begin"]
linter_failure_shape_checking(["linter.failure_shape.checking (fan-in 3)"])
linter_failure_shape_error_labels_unique["linter.failure_shape.error_labels_unique (fan-in 2)"]
linter_failure_shape_failed(["linter.failure_shape.failed (fan-in 1)"])
linter_failure_shape_guard_negation_total["linter.failure_shape.guard_negation_total (fan-in 2)"]
linter_failure_shape_passed(["linter.failure_shape.passed (fan-in 1)"])
linter_failure_shape_reject["linter.failure_shape.reject"]
linter_failure_shape_terminal_states_emit["linter.failure_shape.terminal_states_emit (fan-in 2)"]
linter_failure_shape_unchecked(["linter.failure_shape.unchecked (fan-in 1)"])
linter_failure_shape_accept -->|"transitions.from"| linter_failure_shape_checking
linter_failure_shape_accept -.->|"transitions.guard"| linter_failure_shape_error_labels_unique
linter_failure_shape_accept -.->|"transitions.guard"| linter_failure_shape_guard_negation_total
linter_failure_shape_accept -.->|"transitions.guard"| linter_failure_shape_terminal_states_emit
linter_failure_shape_accept -->|"transitions.to"| linter_failure_shape_passed
linter_failure_shape_begin -->|"transitions.from"| linter_failure_shape_unchecked
linter_failure_shape_begin -->|"transitions.to"| linter_failure_shape_checking
linter_failure_shape_reject -->|"transitions.from"| linter_failure_shape_checking
linter_failure_shape_reject -.->|"transitions.guard"| linter_failure_shape_error_labels_unique
linter_failure_shape_reject -.->|"transitions.guard"| linter_failure_shape_guard_negation_total
linter_failure_shape_reject -.->|"transitions.guard"| linter_failure_shape_terminal_states_emit
linter_failure_shape_reject -->|"transitions.to"| linter_failure_shape_failed
end
subgraph sg_linter_frontmatter["linter.frontmatter"]
direction LR
linter_frontmatter_accept["linter.frontmatter.accept"]
linter_frontmatter_begin["linter.frontmatter.begin"]
linter_frontmatter_checking(["linter.frontmatter.checking (fan-in 3)"])
linter_frontmatter_failed(["linter.frontmatter.failed (fan-in 1)"])
linter_frontmatter_has_id["linter.frontmatter.has_id (fan-in 2)"]
linter_frontmatter_has_kind["linter.frontmatter.has_kind (fan-in 2)"]
linter_frontmatter_has_statement["linter.frontmatter.has_statement (fan-in 2)"]
linter_frontmatter_id_matches_file["linter.frontmatter.id_matches_file (fan-in 2)"]
linter_frontmatter_passed(["linter.frontmatter.passed (fan-in 1)"])
linter_frontmatter_reject["linter.frontmatter.reject"]
linter_frontmatter_unchecked(["linter.frontmatter.unchecked (fan-in 1)"])
linter_frontmatter_accept -->|"transitions.from"| linter_frontmatter_checking
linter_frontmatter_accept -.->|"transitions.guard"| linter_frontmatter_has_id
linter_frontmatter_accept -.->|"transitions.guard"| linter_frontmatter_has_kind
linter_frontmatter_accept -.->|"transitions.guard"| linter_frontmatter_has_statement
linter_frontmatter_accept -.->|"transitions.guard"| linter_frontmatter_id_matches_file
linter_frontmatter_accept -->|"transitions.to"| linter_frontmatter_passed
linter_frontmatter_begin -->|"transitions.from"| linter_frontmatter_unchecked
linter_frontmatter_begin -->|"transitions.to"| linter_frontmatter_checking
linter_frontmatter_reject -->|"transitions.from"| linter_frontmatter_checking
linter_frontmatter_reject -.->|"transitions.guard"| linter_frontmatter_has_id
linter_frontmatter_reject -.->|"transitions.guard"| linter_frontmatter_has_kind
linter_frontmatter_reject -.->|"transitions.guard"| linter_frontmatter_has_statement
linter_frontmatter_reject -.->|"transitions.guard"| linter_frontmatter_id_matches_file
linter_frontmatter_reject -->|"transitions.to"| linter_frontmatter_failed
end
subgraph sg_linter_graph_shape["linter.graph_shape"]
direction LR
linter_graph_shape_accept["linter.graph_shape.accept"]
linter_graph_shape_acyclic["linter.graph_shape.acyclic (fan-in 4)"]
linter_graph_shape_build_graph["linter.graph_shape.build_graph"]
linter_graph_shape_built(["linter.graph_shape.built (fan-in 2)"])
linter_graph_shape_failed(["linter.graph_shape.failed (fan-in 1)"])
linter_graph_shape_no_self_ref["linter.graph_shape.no_self_ref (fan-in 2)"]
linter_graph_shape_passed(["linter.graph_shape.passed (fan-in 1)"])
linter_graph_shape_reject["linter.graph_shape.reject"]
linter_graph_shape_single_root_reachable["linter.graph_shape.single_root_reachable (fan-in 2)"]
linter_graph_shape_supersedes_dag["linter.graph_shape.supersedes_dag (fan-in 2)"]
linter_graph_shape_traverse["linter.graph_shape.traverse"]
linter_graph_shape_traversing(["linter.graph_shape.traversing (fan-in 3)"])
linter_graph_shape_unbuilt(["linter.graph_shape.unbuilt (fan-in 1)"])
linter_graph_shape_accept -->|"transitions.from"| linter_graph_shape_traversing
linter_graph_shape_accept -.->|"transitions.guard"| linter_graph_shape_acyclic
linter_graph_shape_accept -.->|"transitions.guard"| linter_graph_shape_no_self_ref
linter_graph_shape_accept -.->|"transitions.guard"| linter_graph_shape_single_root_reachable
linter_graph_shape_accept -.->|"transitions.guard"| linter_graph_shape_supersedes_dag
linter_graph_shape_accept -->|"transitions.to"| linter_graph_shape_passed
linter_graph_shape_build_graph -->|"transitions.from"| linter_graph_shape_unbuilt
linter_graph_shape_build_graph -->|"transitions.to"| linter_graph_shape_built
linter_graph_shape_reject -->|"transitions.from"| linter_graph_shape_traversing
linter_graph_shape_reject -.->|"transitions.guard"| linter_graph_shape_acyclic
linter_graph_shape_reject -.->|"transitions.guard"| linter_graph_shape_no_self_ref
linter_graph_shape_reject -.->|"transitions.guard"| linter_graph_shape_single_root_reachable
linter_graph_shape_reject -.->|"transitions.guard"| linter_graph_shape_supersedes_dag
linter_graph_shape_reject -->|"transitions.to"| linter_graph_shape_failed
linter_graph_shape_traverse -->|"transitions.from"| linter_graph_shape_built
linter_graph_shape_traverse -->|"transitions.to"| linter_graph_shape_traversing
end
subgraph sg_linter_model_shape["linter.model_shape"]
direction LR
linter_model_shape_accept["linter.model_shape.accept"]
linter_model_shape_check_pairing["linter.model_shape.check_pairing"]
linter_model_shape_every_state_used["linter.model_shape.every_state_used (fan-in 2)"]
linter_model_shape_every_transition_valid["linter.model_shape.every_transition_valid (fan-in 2)"]
linter_model_shape_field_check_failed(["linter.model_shape.field_check_failed (fan-in 1)"])
linter_model_shape_guard_present["linter.model_shape.guard_present (fan-in 2)"]
linter_model_shape_model_sections_paired["linter.model_shape.model_sections_paired (fan-in 2)"]
linter_model_shape_no_bool_state_field["linter.model_shape.no_bool_state_field (fan-in 2)"]
linter_model_shape_pairing_checked(["linter.model_shape.pairing_checked (fan-in 3)"])
linter_model_shape_pairing_fail["linter.model_shape.pairing_fail"]
linter_model_shape_pairing_failed(["linter.model_shape.pairing_failed (fan-in 1)"])
linter_model_shape_pairing_ok["linter.model_shape.pairing_ok"]
linter_model_shape_passed(["linter.model_shape.passed (fan-in 1)"])
linter_model_shape_reject["linter.model_shape.reject"]
linter_model_shape_states_checked(["linter.model_shape.states_checked (fan-in 3)"])
linter_model_shape_ungrouped(["linter.model_shape.ungrouped (fan-in 1)"])
linter_model_shape_accept -->|"transitions.from"| linter_model_shape_states_checked
linter_model_shape_accept -.->|"transitions.guard"| linter_model_shape_guard_present
linter_model_shape_accept -.->|"transitions.guard"| linter_model_shape_no_bool_state_field
linter_model_shape_accept -->|"transitions.to"| linter_model_shape_passed
linter_model_shape_check_pairing -->|"transitions.from"| linter_model_shape_ungrouped
linter_model_shape_check_pairing -->|"transitions.to"| linter_model_shape_pairing_checked
linter_model_shape_pairing_fail -->|"transitions.from"| linter_model_shape_pairing_checked
linter_model_shape_pairing_fail -.->|"transitions.guard"| linter_model_shape_every_state_used
linter_model_shape_pairing_fail -.->|"transitions.guard"| linter_model_shape_every_transition_valid
linter_model_shape_pairing_fail -.->|"transitions.guard"| linter_model_shape_model_sections_paired
linter_model_shape_pairing_fail -->|"transitions.to"| linter_model_shape_pairing_failed
linter_model_shape_pairing_ok -->|"transitions.from"| linter_model_shape_pairing_checked
linter_model_shape_pairing_ok -.->|"transitions.guard"| linter_model_shape_every_state_used
linter_model_shape_pairing_ok -.->|"transitions.guard"| linter_model_shape_every_transition_valid
linter_model_shape_pairing_ok -.->|"transitions.guard"| linter_model_shape_model_sections_paired
linter_model_shape_pairing_ok -->|"transitions.to"| linter_model_shape_states_checked
linter_model_shape_reject -->|"transitions.from"| linter_model_shape_states_checked
linter_model_shape_reject -.->|"transitions.guard"| linter_model_shape_guard_present
linter_model_shape_reject -.->|"transitions.guard"| linter_model_shape_no_bool_state_field
linter_model_shape_reject -->|"transitions.to"| linter_model_shape_field_check_failed
end
subgraph sg_linter_observability["linter.observability"]
direction LR
linter_observability_observation_universe["linter.observability.observation_universe (fan-in 1)"]
linter_observability_observed(["linter.observability.observed (fan-in 1)"])
linter_observability_scan_effects["linter.observability.scan_effects"]
linter_observability_scanning(["linter.observability.scanning (fan-in 2)"])
linter_observability_unobserved_warned(["linter.observability.unobserved_warned (fan-in 1)"])
linter_observability_warn_unobserved["linter.observability.warn_unobserved"]
linter_observability_scan_effects -->|"transitions.from"| linter_observability_scanning
linter_observability_scan_effects -->|"transitions.to"| linter_observability_observed
linter_observability_warn_unobserved -->|"transitions.from"| linter_observability_scanning
linter_observability_warn_unobserved -.->|"transitions.guard"| linter_observability_observation_universe
linter_observability_warn_unobserved -->|"transitions.to"| linter_observability_unobserved_warned
end
subgraph sg_linter_referential_integrity["linter.referential_integrity"]
direction LR
linter_referential_integrity_accept["linter.referential_integrity.accept"]
linter_referential_integrity_build_index["linter.referential_integrity.build_index"]
linter_referential_integrity_index_fail["linter.referential_integrity.index_fail"]
linter_referential_integrity_index_failed(["linter.referential_integrity.index_failed (fan-in 1)"])
linter_referential_integrity_index_ok["linter.referential_integrity.index_ok"]
linter_referential_integrity_indexed(["linter.referential_integrity.indexed (fan-in 2)"])
linter_referential_integrity_indexing(["linter.referential_integrity.indexing (fan-in 3)"])
linter_referential_integrity_passed(["linter.referential_integrity.passed (fan-in 1)"])
linter_referential_integrity_ref_kind_compatible["linter.referential_integrity.ref_kind_compatible (fan-in 2)"]
linter_referential_integrity_ref_resolves["linter.referential_integrity.ref_resolves (fan-in 4)"]
linter_referential_integrity_reject["linter.referential_integrity.reject"]
linter_referential_integrity_resolution_failed(["linter.referential_integrity.resolution_failed (fan-in 1)"])
linter_referential_integrity_resolve_refs["linter.referential_integrity.resolve_refs"]
linter_referential_integrity_resolving(["linter.referential_integrity.resolving (fan-in 3)"])
linter_referential_integrity_unindexed(["linter.referential_integrity.unindexed (fan-in 1)"])
linter_referential_integrity_unique_across_repo["linter.referential_integrity.unique_across_repo (fan-in 2)"]
linter_referential_integrity_unique_within_file["linter.referential_integrity.unique_within_file (fan-in 2)"]
linter_referential_integrity_accept -->|"transitions.from"| linter_referential_integrity_resolving
linter_referential_integrity_accept -.->|"transitions.guard"| linter_referential_integrity_ref_kind_compatible
linter_referential_integrity_accept -.->|"transitions.guard"| linter_referential_integrity_ref_resolves
linter_referential_integrity_accept -->|"transitions.to"| linter_referential_integrity_passed
linter_referential_integrity_build_index -->|"transitions.from"| linter_referential_integrity_unindexed
linter_referential_integrity_build_index -->|"transitions.to"| linter_referential_integrity_indexing
linter_referential_integrity_index_fail -->|"transitions.from"| linter_referential_integrity_indexing
linter_referential_integrity_index_fail -.->|"transitions.guard"| linter_referential_integrity_unique_across_repo
linter_referential_integrity_index_fail -.->|"transitions.guard"| linter_referential_integrity_unique_within_file
linter_referential_integrity_index_fail -->|"transitions.to"| linter_referential_integrity_index_failed
linter_referential_integrity_index_ok -->|"transitions.from"| linter_referential_integrity_indexing
linter_referential_integrity_index_ok -.->|"transitions.guard"| linter_referential_integrity_unique_across_repo
linter_referential_integrity_index_ok -.->|"transitions.guard"| linter_referential_integrity_unique_within_file
linter_referential_integrity_index_ok -->|"transitions.to"| linter_referential_integrity_indexed
linter_referential_integrity_reject -->|"transitions.from"| linter_referential_integrity_resolving
linter_referential_integrity_reject -.->|"transitions.guard"| linter_referential_integrity_ref_kind_compatible
linter_referential_integrity_reject -.->|"transitions.guard"| linter_referential_integrity_ref_resolves
linter_referential_integrity_reject -->|"transitions.to"| linter_referential_integrity_resolution_failed
linter_referential_integrity_resolve_refs -->|"transitions.from"| linter_referential_integrity_indexed
linter_referential_integrity_resolve_refs -->|"transitions.to"| linter_referential_integrity_resolving
end
subgraph sg_linter_schema_shape["linter.schema_shape"]
direction LR
linter_schema_shape_accept["linter.schema_shape.accept"]
linter_schema_shape_begin["linter.schema_shape.begin"]
linter_schema_shape_constraint_kind_closed["linter.schema_shape.constraint_kind_closed (fan-in 2)"]
linter_schema_shape_diff_fail["linter.schema_shape.diff_fail"]
linter_schema_shape_diff_failed(["linter.schema_shape.diff_failed (fan-in 1)"])
linter_schema_shape_diff_ok["linter.schema_shape.diff_ok"]
linter_schema_shape_diff_skip["linter.schema_shape.diff_skip"]
linter_schema_shape_diffing(["linter.schema_shape.diffing (fan-in 4)"])
linter_schema_shape_id_set_grows_only["linter.schema_shape.id_set_grows_only (fan-in 2)"]
linter_schema_shape_id_set_order_stable["linter.schema_shape.id_set_order_stable (fan-in 2)"]
linter_schema_shape_kind_checking(["linter.schema_shape.kind_checking (fan-in 3)"])
linter_schema_shape_kind_fail["linter.schema_shape.kind_fail"]
linter_schema_shape_kind_failed(["linter.schema_shape.kind_failed (fan-in 1)"])
linter_schema_shape_kind_ok["linter.schema_shape.kind_ok"]
linter_schema_shape_no_prose_field_parsed["linter.schema_shape.no_prose_field_parsed (fan-in 2)"]
linter_schema_shape_parser_audit_failed(["linter.schema_shape.parser_audit_failed (fan-in 1)"])
linter_schema_shape_parser_audited(["linter.schema_shape.parser_audited (fan-in 4)"])
linter_schema_shape_passed(["linter.schema_shape.passed (fan-in 1)"])
linter_schema_shape_property_kind_closed["linter.schema_shape.property_kind_closed (fan-in 2)"]
linter_schema_shape_prose_field_passthrough["linter.schema_shape.prose_field_passthrough (fan-in 2)"]
linter_schema_shape_reject["linter.schema_shape.reject"]
linter_schema_shape_unchecked(["linter.schema_shape.unchecked (fan-in 1)"])
linter_schema_shape_accept -->|"transitions.from"| linter_schema_shape_parser_audited
linter_schema_shape_accept -.->|"transitions.guard"| linter_schema_shape_no_prose_field_parsed
linter_schema_shape_accept -.->|"transitions.guard"| linter_schema_shape_prose_field_passthrough
linter_schema_shape_accept -->|"transitions.to"| linter_schema_shape_passed
linter_schema_shape_begin -->|"transitions.from"| linter_schema_shape_unchecked
linter_schema_shape_begin -->|"transitions.to"| linter_schema_shape_kind_checking
linter_schema_shape_diff_fail -->|"transitions.from"| linter_schema_shape_diffing
linter_schema_shape_diff_fail -.->|"transitions.guard"| linter_schema_shape_id_set_grows_only
linter_schema_shape_diff_fail -.->|"transitions.guard"| linter_schema_shape_id_set_order_stable
linter_schema_shape_diff_fail -->|"transitions.to"| linter_schema_shape_diff_failed
linter_schema_shape_diff_ok -->|"transitions.from"| linter_schema_shape_diffing
linter_schema_shape_diff_ok -.->|"transitions.guard"| linter_schema_shape_id_set_grows_only
linter_schema_shape_diff_ok -.->|"transitions.guard"| linter_schema_shape_id_set_order_stable
linter_schema_shape_diff_ok -->|"transitions.to"| linter_schema_shape_parser_audited
linter_schema_shape_diff_skip -->|"transitions.from"| linter_schema_shape_diffing
linter_schema_shape_diff_skip -->|"transitions.to"| linter_schema_shape_parser_audited
linter_schema_shape_kind_fail -->|"transitions.from"| linter_schema_shape_kind_checking
linter_schema_shape_kind_fail -.->|"transitions.guard"| linter_schema_shape_constraint_kind_closed
linter_schema_shape_kind_fail -.->|"transitions.guard"| linter_schema_shape_property_kind_closed
linter_schema_shape_kind_fail -->|"transitions.to"| linter_schema_shape_kind_failed
linter_schema_shape_kind_ok -->|"transitions.from"| linter_schema_shape_kind_checking
linter_schema_shape_kind_ok -.->|"transitions.guard"| linter_schema_shape_constraint_kind_closed
linter_schema_shape_kind_ok -.->|"transitions.guard"| linter_schema_shape_property_kind_closed
linter_schema_shape_kind_ok -->|"transitions.to"| linter_schema_shape_diffing
linter_schema_shape_reject -->|"transitions.from"| linter_schema_shape_parser_audited
linter_schema_shape_reject -.->|"transitions.guard"| linter_schema_shape_no_prose_field_parsed
linter_schema_shape_reject -.->|"transitions.guard"| linter_schema_shape_prose_field_passthrough
linter_schema_shape_reject -->|"transitions.to"| linter_schema_shape_parser_audit_failed
end
subgraph sg_merge["merge"]
direction LR
merge_accept["merge.accept"]
merge_auto_proceed["merge.auto_proceed"]
merge_blast_radii_recorded_pre_merge["merge.blast_radii_recorded_pre_merge (fan-in 1)"]
merge_check_collisions["merge.check_collisions"]
merge_collision_checked(["merge.collision_checked (fan-in 3)"])
merge_collision_fail["merge.collision_fail"]
merge_collision_failed(["merge.collision_failed (fan-in 1)"])
merge_diverged(["merge.diverged (fan-in 2)"])
merge_flag_review["merge.flag_review"]
merge_graph_reused_not_rederived["merge.graph_reused_not_rederived (fan-in 2)"]
merge_merge_failed(["merge.merge_failed (fan-in 1)"])
merge_merged(["merge.merged (fan-in 1)"])
merge_needs_review(["merge.needs_review (fan-in 2)"])
merge_no_new_id_collision["merge.no_new_id_collision (fan-in 2)"]
merge_post_merge_relint_required["merge.post_merge_relint_required (fan-in 2)"]
merge_ref_integrity_reverified(["merge.ref_integrity_reverified (fan-in 3)"])
merge_reject["merge.reject"]
merge_rename_replayed(["merge.rename_replayed (fan-in 4)"])
merge_rename_replayed_onto_foreign_edits["merge.rename_replayed_onto_foreign_edits (fan-in 2)"]
merge_resolved["merge.resolved"]
merge_reverification_failed(["merge.reverification_failed (fan-in 1)"])
merge_reverify["merge.reverify"]
merge_reverify_fail["merge.reverify_fail"]
merge_semantic_conflict_iff_blast_radius_intersects["merge.semantic_conflict_iff_blast_radius_intersects (fan-in 1)"]
merge_sequential_number_reassigned_on_conflict["merge.sequential_number_reassigned_on_conflict (fan-in 2)"]
merge_accept -->|"transitions.from"| merge_ref_integrity_reverified
merge_accept -.->|"transitions.guard"| merge_sequential_number_reassigned_on_conflict
merge_accept -->|"transitions.to"| merge_merged
merge_auto_proceed -->|"transitions.from"| merge_collision_checked
merge_auto_proceed -.->|"transitions.guard"| merge_rename_replayed_onto_foreign_edits
merge_auto_proceed -->|"transitions.to"| merge_rename_replayed
merge_check_collisions -->|"transitions.from"| merge_diverged
merge_check_collisions -.->|"transitions.guard"| merge_graph_reused_not_rederived
merge_check_collisions -.->|"transitions.guard"| merge_no_new_id_collision
merge_check_collisions -->|"transitions.to"| merge_collision_checked
merge_collision_fail -->|"transitions.from"| merge_diverged
merge_collision_fail -.->|"transitions.guard"| merge_graph_reused_not_rederived
merge_collision_fail -.->|"transitions.guard"| merge_no_new_id_collision
merge_collision_fail -->|"transitions.to"| merge_collision_failed
merge_flag_review -->|"transitions.from"| merge_collision_checked
merge_flag_review -.->|"transitions.guard"| merge_blast_radii_recorded_pre_merge
merge_flag_review -.->|"transitions.guard"| merge_semantic_conflict_iff_blast_radius_intersects
merge_flag_review -->|"transitions.to"| merge_needs_review
merge_reject -->|"transitions.from"| merge_ref_integrity_reverified
merge_reject -.->|"transitions.guard"| merge_sequential_number_reassigned_on_conflict
merge_reject -->|"transitions.to"| merge_merge_failed
merge_resolved -->|"transitions.from"| merge_needs_review
merge_resolved -.->|"transitions.guard"| merge_rename_replayed_onto_foreign_edits
merge_resolved -->|"transitions.to"| merge_rename_replayed
merge_reverify -->|"transitions.from"| merge_rename_replayed
merge_reverify -.->|"transitions.guard"| merge_post_merge_relint_required
merge_reverify -->|"transitions.to"| merge_ref_integrity_reverified
merge_reverify_fail -->|"transitions.from"| merge_rename_replayed
merge_reverify_fail -.->|"transitions.guard"| merge_post_merge_relint_required
merge_reverify_fail -->|"transitions.to"| merge_reverification_failed
end
subgraph sg_migrate["migrate"]
direction LR
migrate_begin_wrap["migrate.begin_wrap"]
migrate_delta_text_preserved["migrate.delta_text_preserved (fan-in 1)"]
migrate_dry_run_pass["migrate.dry_run_pass"]
migrate_dry_run_writes_nothing["migrate.dry_run_writes_nothing (fan-in 1)"]
migrate_merge_adds_only_missing["migrate.merge_adds_only_missing (fan-in 1)"]
migrate_merge_only["migrate.merge_only"]
migrate_refuse["migrate.refuse"]
migrate_refuse_already_migrated["migrate.refuse_already_migrated (fan-in 1)"]
migrate_refused(["migrate.refused (fan-in 2)"])
migrate_rekey_derives_real_id["migrate.rekey_derives_real_id (fan-in 1)"]
migrate_rekey_refusals["migrate.rekey_refusals (fan-in 1)"]
migrate_rekey_refuse["migrate.rekey_refuse"]
migrate_rekey_result["migrate.rekey_result"]
migrate_rekeyed(["migrate.rekeyed (fan-in 1)"])
migrate_scaffold_lints_clean["migrate.scaffold_lints_clean (fan-in 1)"]
migrate_sidecar_generated["migrate.sidecar_generated (fan-in 1)"]
migrate_unwrapping(["migrate.unwrapping (fan-in 4)"])
migrate_verify_green["migrate.verify_green"]
migrate_wrapping(["migrate.wrapping (fan-in 5)"])
migrate_write_result["migrate.write_result"]
migrate_written(["migrate.written (fan-in 4)"])
migrate_begin_wrap -->|"transitions.from"| migrate_unwrapping
migrate_begin_wrap -.->|"transitions.guard"| migrate_delta_text_preserved
migrate_begin_wrap -->|"transitions.to"| migrate_wrapping
migrate_dry_run_pass -->|"transitions.from"| migrate_wrapping
migrate_dry_run_pass -.->|"transitions.guard"| migrate_dry_run_writes_nothing
migrate_dry_run_pass -->|"transitions.to"| migrate_wrapping
migrate_merge_only -->|"transitions.from"| migrate_wrapping
migrate_merge_only -.->|"transitions.guard"| migrate_merge_adds_only_missing
migrate_merge_only -->|"transitions.to"| migrate_written
migrate_refuse -->|"transitions.from"| migrate_unwrapping
migrate_refuse -.->|"transitions.guard"| migrate_refuse_already_migrated
migrate_refuse -->|"transitions.to"| migrate_refused
migrate_rekey_refuse -->|"transitions.from"| migrate_unwrapping
migrate_rekey_refuse -.->|"transitions.guard"| migrate_rekey_refusals
migrate_rekey_refuse -->|"transitions.to"| migrate_refused
migrate_rekey_result -->|"transitions.from"| migrate_unwrapping
migrate_rekey_result -.->|"transitions.guard"| migrate_rekey_derives_real_id
migrate_rekey_result -->|"transitions.to"| migrate_rekeyed
migrate_verify_green -->|"transitions.from"| migrate_written
migrate_verify_green -.->|"transitions.guard"| migrate_scaffold_lints_clean
migrate_verify_green -->|"transitions.to"| migrate_written
migrate_write_result -->|"transitions.from"| migrate_wrapping
migrate_write_result -.->|"transitions.guard"| migrate_sidecar_generated
migrate_write_result -->|"transitions.to"| migrate_written
end
subgraph sg_model_check["model.check"]
direction LR
model_check_activate["model.check.activate"]
model_check_agreement_active(["model.check.agreement_active (fan-in 1)"])
model_check_backend_agreement_ci["model.check.backend_agreement_ci (fan-in 1)"]
model_check_citation_evaluated(["model.check.citation_evaluated (fan-in 2)"])
model_check_citation_inert(["model.check.citation_inert (fan-in 1)"])
model_check_guard_citation_evaluated["model.check.guard_citation_evaluated (fan-in 1)"]
model_check_upgrade["model.check.upgrade"]
model_check_activate -->|"transitions.from"| model_check_citation_evaluated
model_check_activate -.->|"transitions.guard"| model_check_backend_agreement_ci
model_check_activate -->|"transitions.to"| model_check_agreement_active
model_check_upgrade -->|"transitions.from"| model_check_citation_inert
model_check_upgrade -.->|"transitions.guard"| model_check_guard_citation_evaluated
model_check_upgrade -->|"transitions.to"| model_check_citation_evaluated
end
subgraph sg_model_check_2["model_check"]
direction LR
model_check_begin["model_check.begin"]
model_check_checker_invoked["model_check.checker_invoked (fan-in 1)"]
model_check_claim_aggregate_governs["model_check.claim_aggregate_governs (fan-in 1)"]
model_check_clean(["model_check.clean (fan-in 1)"])
model_check_counterexample_found(["model_check.counterexample_found (fan-in 1)"])
model_check_counterexample_is_minimal["model_check.counterexample_is_minimal (fan-in 1)"]
model_check_counterexample_names_violated_invariant["model_check.counterexample_names_violated_invariant (fan-in 1)"]
model_check_exhaustive_within_bound["model_check.exhaustive_within_bound (fan-in 2)"]
model_check_exploration_only(["model_check.exploration_only (fan-in 1)"])
model_check_finish_clean["model_check.finish_clean"]
model_check_finish_exploration["model_check.finish_exploration"]
model_check_finish_timeout["model_check.finish_timeout"]
model_check_finish_violation["model_check.finish_violation"]
model_check_not_run(["model_check.not_run (fan-in 1)"])
model_check_running(["model_check.running (fan-in 5)"])
model_check_timed_out(["model_check.timed_out (fan-in 1)"])
model_check_begin -->|"transitions.from"| model_check_not_run
model_check_begin -.->|"transitions.guard"| model_check_checker_invoked
model_check_begin -->|"transitions.to"| model_check_running
model_check_finish_clean -->|"transitions.from"| model_check_running
model_check_finish_clean -.->|"transitions.guard"| model_check_claim_aggregate_governs
model_check_finish_clean -.->|"transitions.guard"| model_check_exhaustive_within_bound
model_check_finish_clean -->|"transitions.to"| model_check_clean
model_check_finish_exploration -->|"transitions.from"| model_check_running
model_check_finish_exploration -.->|"transitions.guard"| model_check_exhaustive_within_bound
model_check_finish_exploration -->|"transitions.to"| model_check_exploration_only
model_check_finish_timeout -->|"transitions.from"| model_check_running
model_check_finish_timeout -->|"transitions.to"| model_check_timed_out
model_check_finish_violation -->|"transitions.from"| model_check_running
model_check_finish_violation -.->|"transitions.guard"| model_check_counterexample_is_minimal
model_check_finish_violation -.->|"transitions.guard"| model_check_counterexample_names_violated_invariant
model_check_finish_violation -->|"transitions.to"| model_check_counterexample_found
end
subgraph sg_numeric_predicates_pack["numeric.predicates.pack"]
direction LR
numeric_predicates_pack_deprecate["numeric.predicates.pack.deprecate"]
numeric_predicates_pack_deprecated(["numeric.predicates.pack.deprecated (fan-in 1)"])
numeric_predicates_pack_draft(["numeric.predicates.pack.draft (fan-in 1)"])
numeric_predicates_pack_pack_file_declared["numeric.predicates.pack.pack_file_declared (fan-in 2)"]
numeric_predicates_pack_publish["numeric.predicates.pack.publish"]
numeric_predicates_pack_published(["numeric.predicates.pack.published (fan-in 2)"])
numeric_predicates_pack_quantity_section_shape["numeric.predicates.pack.quantity_section_shape (fan-in 1)"]
numeric_predicates_pack_deprecate -->|"transitions.from"| numeric_predicates_pack_published
numeric_predicates_pack_deprecate -.->|"transitions.guard"| numeric_predicates_pack_pack_file_declared
numeric_predicates_pack_deprecate -->|"transitions.to"| numeric_predicates_pack_deprecated
numeric_predicates_pack_publish -->|"transitions.from"| numeric_predicates_pack_draft
numeric_predicates_pack_publish -.->|"transitions.guard"| numeric_predicates_pack_pack_file_declared
numeric_predicates_pack_publish -.->|"transitions.guard"| numeric_predicates_pack_quantity_section_shape
numeric_predicates_pack_publish -->|"transitions.to"| numeric_predicates_pack_published
end
subgraph sg_observability["observability"]
direction LR
observability_approve["observability.approve"]
observability_approved(["observability.approved (fan-in 2)"])
observability_archive["observability.archive"]
observability_archived(["observability.archived (fan-in 1)"])
observability_implement["observability.implement"]
observability_implemented(["observability.implemented (fan-in 2)"])
observability_process_lifecycle["observability.process_lifecycle (fan-in 3)"]
observability_proposed(["observability.proposed (fan-in 1)"])
observability_approve -->|"transitions.from"| observability_proposed
observability_approve -.->|"transitions.guard"| observability_process_lifecycle
observability_approve -->|"transitions.to"| observability_approved
observability_archive -->|"transitions.from"| observability_implemented
observability_archive -.->|"transitions.guard"| observability_process_lifecycle
observability_archive -->|"transitions.to"| observability_archived
observability_implement -->|"transitions.from"| observability_approved
observability_implement -.->|"transitions.guard"| observability_process_lifecycle
observability_implement -->|"transitions.to"| observability_implemented
end
subgraph sg_orchestrate["orchestrate"]
direction LR
orchestrate_compile_fail["orchestrate.compile_fail"]
orchestrate_compile_failed(["orchestrate.compile_failed (fan-in 1)"])
orchestrate_compile_gate_matches_coverage["orchestrate.compile_gate_matches_coverage (fan-in 1)"]
orchestrate_compile_ok["orchestrate.compile_ok"]
orchestrate_compile_stage(["orchestrate.compile_stage (fan-in 3)"])
orchestrate_dependency_respecting_skip["orchestrate.dependency_respecting_skip (fan-in 1)"]
orchestrate_idle(["orchestrate.idle (fan-in 1)"])
orchestrate_independent_branches_run_regardless["orchestrate.independent_branches_run_regardless (fan-in 1)"]
orchestrate_lint_fail["orchestrate.lint_fail"]
orchestrate_lint_failed(["orchestrate.lint_failed (fan-in 1)"])
orchestrate_lint_gate_matches_checker_ownership["orchestrate.lint_gate_matches_checker_ownership (fan-in 1)"]
orchestrate_lint_ok["orchestrate.lint_ok"]
orchestrate_lint_stage(["orchestrate.lint_stage (fan-in 3)"])
orchestrate_model_check_fail["orchestrate.model_check_fail"]
orchestrate_model_check_failed(["orchestrate.model_check_failed (fan-in 1)"])
orchestrate_model_check_ok["orchestrate.model_check_ok"]
orchestrate_model_check_stage(["orchestrate.model_check_stage (fan-in 3)"])
orchestrate_stage_order_fixed["orchestrate.stage_order_fixed (fan-in 2)"]
orchestrate_start_lint["orchestrate.start_lint"]
orchestrate_succeeded(["orchestrate.succeeded (fan-in 1)"])
orchestrate_verify_fail["orchestrate.verify_fail"]
orchestrate_verify_failed(["orchestrate.verify_failed (fan-in 1)"])
orchestrate_verify_ok["orchestrate.verify_ok"]
orchestrate_verify_stage(["orchestrate.verify_stage (fan-in 3)"])
specodelic_parsed(["specodelic.parsed (fan-in 4)"])
orchestrate_compile_fail -->|"transitions.from"| orchestrate_compile_stage
orchestrate_compile_fail -->|"transitions.to"| orchestrate_compile_failed
orchestrate_compile_ok -->|"transitions.from"| orchestrate_compile_stage
orchestrate_compile_ok -.->|"transitions.guard"| orchestrate_compile_gate_matches_coverage
orchestrate_compile_ok -->|"transitions.to"| orchestrate_model_check_stage
orchestrate_lint_fail -->|"transitions.from"| orchestrate_lint_stage
orchestrate_lint_fail -->|"transitions.to"| orchestrate_lint_failed
orchestrate_lint_ok -->|"transitions.from"| orchestrate_lint_stage
orchestrate_lint_ok -.->|"transitions.guard"| orchestrate_dependency_respecting_skip
orchestrate_lint_ok -.->|"transitions.guard"| orchestrate_independent_branches_run_regardless
orchestrate_lint_ok -.->|"transitions.guard"| orchestrate_lint_gate_matches_checker_ownership
orchestrate_lint_ok -->|"transitions.to"| orchestrate_compile_stage
orchestrate_model_check_fail -->|"transitions.from"| orchestrate_model_check_stage
orchestrate_model_check_fail -->|"transitions.to"| orchestrate_model_check_failed
orchestrate_model_check_ok -->|"transitions.from"| orchestrate_model_check_stage
orchestrate_model_check_ok -.->|"transitions.guard"| orchestrate_stage_order_fixed
orchestrate_model_check_ok -->|"transitions.to"| orchestrate_verify_stage
orchestrate_start_lint -->|"transitions.from"| orchestrate_idle
orchestrate_start_lint -.->|"transitions.guard"| specodelic_parsed
orchestrate_start_lint -->|"transitions.to"| orchestrate_lint_stage
orchestrate_verify_fail -->|"transitions.from"| orchestrate_verify_stage
orchestrate_verify_fail -->|"transitions.to"| orchestrate_verify_failed
orchestrate_verify_ok -->|"transitions.from"| orchestrate_verify_stage
orchestrate_verify_ok -.->|"transitions.guard"| orchestrate_stage_order_fixed
orchestrate_verify_ok -->|"transitions.to"| orchestrate_succeeded
end
subgraph sg_packs["packs"]
direction LR
packs_deprecate["packs.deprecate"]
packs_deprecated(["packs.deprecated (fan-in 1)"])
packs_draft(["packs.draft (fan-in 1)"])
packs_lifecycle_labeled["packs.lifecycle_labeled (fan-in 1)"]
packs_manifest_six_tables["packs.manifest_six_tables (fan-in 1)"]
packs_pack_is_profile_file["packs.pack_is_profile_file (fan-in 1)"]
packs_publish["packs.publish"]
packs_published(["packs.published (fan-in 2)"])
packs_deprecate -->|"transitions.from"| packs_published
packs_deprecate -.->|"transitions.guard"| packs_lifecycle_labeled
packs_deprecate -->|"transitions.to"| packs_deprecated
packs_publish -->|"transitions.from"| packs_draft
packs_publish -.->|"transitions.guard"| packs_manifest_six_tables
packs_publish -.->|"transitions.guard"| packs_pack_is_profile_file
packs_publish -->|"transitions.to"| packs_published
end
subgraph sg_parse["parse"]
direction LR
parse_C_parse_envelope["parse.C-parse-envelope (fan-in 1)"]
parse_C_parse_ir["parse.C-parse-ir (fan-in 2)"]
parse_emitted(["parse.emitted (fan-in 1)"])
parse_failed(["parse.failed (fan-in 1)"])
parse_parsed(["parse.parsed (fan-in 2)"])
parse_reading(["parse.reading (fan-in 2)"])
parse_t_emit["parse.t-emit"]
parse_t_fail["parse.t-fail"]
parse_t_parse["parse.t-parse"]
parse_t_emit -->|"transitions.from"| parse_parsed
parse_t_emit -.->|"transitions.guard"| parse_C_parse_envelope
parse_t_emit -->|"transitions.to"| parse_emitted
parse_t_fail -->|"transitions.from"| parse_reading
parse_t_fail -.->|"transitions.guard"| parse_C_parse_ir
parse_t_fail -->|"transitions.to"| parse_failed
parse_t_parse -->|"transitions.from"| parse_reading
parse_t_parse -.->|"transitions.guard"| parse_C_parse_ir
parse_t_parse -->|"transitions.to"| parse_parsed
end
subgraph sg_quant_finance_pack["quant.finance.pack"]
direction LR
quant_finance_pack_deprecate["quant.finance.pack.deprecate"]
quant_finance_pack_deprecated(["quant.finance.pack.deprecated (fan-in 1)"])
quant_finance_pack_draft(["quant.finance.pack.draft (fan-in 1)"])
quant_finance_pack_limits_section_shape["quant.finance.pack.limits_section_shape (fan-in 1)"]
quant_finance_pack_pack_file_declared["quant.finance.pack.pack_file_declared (fan-in 2)"]
quant_finance_pack_publish["quant.finance.pack.publish"]
quant_finance_pack_published(["quant.finance.pack.published (fan-in 2)"])
quant_finance_pack_deprecate -->|"transitions.from"| quant_finance_pack_published
quant_finance_pack_deprecate -.->|"transitions.guard"| quant_finance_pack_pack_file_declared
quant_finance_pack_deprecate -->|"transitions.to"| quant_finance_pack_deprecated
quant_finance_pack_publish -->|"transitions.from"| quant_finance_pack_draft
quant_finance_pack_publish -.->|"transitions.guard"| quant_finance_pack_limits_section_shape
quant_finance_pack_publish -.->|"transitions.guard"| quant_finance_pack_pack_file_declared
quant_finance_pack_publish -->|"transitions.to"| quant_finance_pack_published
end
subgraph sg_refactor["refactor"]
direction LR
graph_queryable(["graph.queryable (fan-in 3)"])
refactor_analyze["refactor.analyze"]
refactor_analyzing(["refactor.analyzing (fan-in 3)"])
refactor_clean(["refactor.clean (fan-in 1)"])
refactor_clear["refactor.clear"]
refactor_flag["refactor.flag"]
refactor_found(["refactor.found (fan-in 1)"])
refactor_idle(["refactor.idle (fan-in 1)"])
refactor_narrow_diff_heuristic["refactor.narrow_diff_heuristic (fan-in 1)"]
refactor_unrelated_fan_in_defined["refactor.unrelated_fan_in_defined (fan-in 1)"]
refactor_analyze -->|"transitions.from"| refactor_idle
refactor_analyze -.->|"transitions.guard"| graph_queryable
refactor_analyze -->|"transitions.to"| refactor_analyzing
refactor_clear -->|"transitions.from"| refactor_analyzing
refactor_clear -->|"transitions.to"| refactor_clean
refactor_flag -->|"transitions.from"| refactor_analyzing
refactor_flag -.->|"transitions.guard"| refactor_narrow_diff_heuristic
refactor_flag -.->|"transitions.guard"| refactor_unrelated_fan_in_defined
refactor_flag -->|"transitions.to"| refactor_found
end
subgraph sg_rename["rename"]
direction LR
linter_graph_shape_acyclic["linter.graph_shape.acyclic (fan-in 4)"]
linter_referential_integrity_ref_resolves["linter.referential_integrity.ref_resolves (fan-in 4)"]
rename_accept["rename.accept"]
rename_applied(["rename.applied (fan-in 2)"])
rename_apply["rename.apply"]
rename_apply_fail["rename.apply_fail"]
rename_apply_failed(["rename.apply_failed (fan-in 1)"])
rename_apply_ok["rename.apply_ok"]
rename_applying(["rename.applying (fan-in 3)"])
rename_atomic_operation["rename.atomic_operation (fan-in 1)"]
rename_check_failed(["rename.check_failed (fan-in 1)"])
rename_checked(["rename.checked (fan-in 2)"])
rename_kind_unchanged["rename.kind_unchanged (fan-in 2)"]
rename_new_id_available["rename.new_id_available (fan-in 2)"]
rename_new_id_matches_filename["rename.new_id_matches_filename (fan-in 2)"]
rename_old_id_fully_replaced["rename.old_id_fully_replaced (fan-in 2)"]
rename_passed(["rename.passed (fan-in 1)"])
rename_prose_untouched_by_rename["rename.prose_untouched_by_rename (fan-in 2)"]
rename_reject["rename.reject"]
rename_requested(["rename.requested (fan-in 2)"])
rename_validate["rename.validate"]
rename_validate_fail["rename.validate_fail"]
rename_validate_failed(["rename.validate_failed (fan-in 1)"])
rename_verify["rename.verify"]
rename_verifying(["rename.verifying (fan-in 3)"])
rename_accept -->|"transitions.from"| rename_verifying
rename_accept -.->|"transitions.guard"| linter_graph_shape_acyclic
rename_accept -.->|"transitions.guard"| linter_referential_integrity_ref_resolves
rename_accept -->|"transitions.to"| rename_passed
rename_apply -->|"transitions.from"| rename_checked
rename_apply -.->|"transitions.guard"| rename_atomic_operation
rename_apply -->|"transitions.to"| rename_applying
rename_apply_fail -->|"transitions.from"| rename_applying
rename_apply_fail -.->|"transitions.guard"| rename_kind_unchanged
rename_apply_fail -.->|"transitions.guard"| rename_old_id_fully_replaced
rename_apply_fail -.->|"transitions.guard"| rename_prose_untouched_by_rename
rename_apply_fail -->|"transitions.to"| rename_apply_failed
rename_apply_ok -->|"transitions.from"| rename_applying
rename_apply_ok -.->|"transitions.guard"| rename_kind_unchanged
rename_apply_ok -.->|"transitions.guard"| rename_old_id_fully_replaced
rename_apply_ok -.->|"transitions.guard"| rename_prose_untouched_by_rename
rename_apply_ok -->|"transitions.to"| rename_applied
rename_reject -->|"transitions.from"| rename_verifying
rename_reject -.->|"transitions.guard"| linter_graph_shape_acyclic
rename_reject -.->|"transitions.guard"| linter_referential_integrity_ref_resolves
rename_reject -->|"transitions.to"| rename_check_failed
rename_validate -->|"transitions.from"| rename_requested
rename_validate -.->|"transitions.guard"| rename_new_id_available
rename_validate -.->|"transitions.guard"| rename_new_id_matches_filename
rename_validate -->|"transitions.to"| rename_checked
rename_validate_fail -->|"transitions.from"| rename_requested
rename_validate_fail -.->|"transitions.guard"| rename_new_id_available
rename_validate_fail -.->|"transitions.guard"| rename_new_id_matches_filename
rename_validate_fail -->|"transitions.to"| rename_validate_failed
rename_verify -->|"transitions.from"| rename_applied
rename_verify -->|"transitions.to"| rename_verifying
end
subgraph sg_skill_pack["skill.pack"]
direction LR
skill_pack_doctor_flags_staleness["skill.pack.doctor_flags_staleness (fan-in 1)"]
skill_pack_drift["skill.pack.drift"]
skill_pack_embedded_single_source["skill.pack.embedded_single_source (fan-in 1)"]
skill_pack_example_lints_clean["skill.pack.example_lints_clean (fan-in 1)"]
skill_pack_generate["skill.pack.generate"]
skill_pack_generated(["skill.pack.generated (fan-in 3)"])
skill_pack_install["skill.pack.install"]
skill_pack_install_writes_and_reports["skill.pack.install_writes_and_reports (fan-in 3)"]
skill_pack_installed(["skill.pack.installed (fan-in 3)"])
skill_pack_marker_marks_every_install["skill.pack.marker_marks_every_install (fan-in 1)"]
skill_pack_mentions_membership_tested["skill.pack.mentions_membership_tested (fan-in 1)"]
skill_pack_refresh["skill.pack.refresh"]
skill_pack_refuse["skill.pack.refuse"]
skill_pack_refused(["skill.pack.refused (fan-in 1)"])
skill_pack_stale(["skill.pack.stale (fan-in 2)"])
skill_pack_tracked(["skill.pack.tracked (fan-in 1)"])
skill_pack_drift -->|"transitions.from"| skill_pack_installed
skill_pack_drift -.->|"transitions.guard"| skill_pack_doctor_flags_staleness
skill_pack_drift -->|"transitions.to"| skill_pack_stale
skill_pack_generate -->|"transitions.from"| skill_pack_tracked
skill_pack_generate -.->|"transitions.guard"| skill_pack_embedded_single_source
skill_pack_generate -.->|"transitions.guard"| skill_pack_example_lints_clean
skill_pack_generate -.->|"transitions.guard"| skill_pack_mentions_membership_tested
skill_pack_generate -->|"transitions.to"| skill_pack_generated
skill_pack_install -->|"transitions.from"| skill_pack_generated
skill_pack_install -.->|"transitions.guard"| skill_pack_install_writes_and_reports
skill_pack_install -.->|"transitions.guard"| skill_pack_marker_marks_every_install
skill_pack_install -->|"transitions.to"| skill_pack_installed
skill_pack_refresh -->|"transitions.from"| skill_pack_stale
skill_pack_refresh -.->|"transitions.guard"| skill_pack_install_writes_and_reports
skill_pack_refresh -->|"transitions.to"| skill_pack_installed
skill_pack_refuse -->|"transitions.from"| skill_pack_generated
skill_pack_refuse -.->|"transitions.guard"| skill_pack_install_writes_and_reports
skill_pack_refuse -->|"transitions.to"| skill_pack_refused
end
subgraph sg_spec_integration["spec.integration"]
direction LR
spec_integration_both_parsers_accept["spec.integration.both_parsers_accept (fan-in 1)"]
spec_integration_deploy_pair["spec.integration.deploy_pair"]
spec_integration_deployed(["spec.integration.deployed (fan-in 2)"])
spec_integration_deployed_shape_closed["spec.integration.deployed_shape_closed (fan-in 1)"]
spec_integration_duplicate_corpus_identity["spec.integration.duplicate_corpus_identity (fan-in 1)"]
spec_integration_gate["spec.integration.gate"]
spec_integration_gated(["spec.integration.gated (fan-in 1)"])
spec_integration_plain_delta(["spec.integration.plain_delta (fan-in 1)"])
spec_integration_plain_deltas_carried["spec.integration.plain_deltas_carried (fan-in 1)"]
spec_integration_deploy_pair -->|"transitions.from"| spec_integration_plain_delta
spec_integration_deploy_pair -.->|"transitions.guard"| spec_integration_both_parsers_accept
spec_integration_deploy_pair -.->|"transitions.guard"| spec_integration_plain_deltas_carried
spec_integration_deploy_pair -->|"transitions.to"| spec_integration_deployed
spec_integration_gate -->|"transitions.from"| spec_integration_deployed
spec_integration_gate -.->|"transitions.guard"| spec_integration_deployed_shape_closed
spec_integration_gate -.->|"transitions.guard"| spec_integration_duplicate_corpus_identity
spec_integration_gate -->|"transitions.to"| spec_integration_gated
end
subgraph sg_specodelic["specodelic"]
direction LR
specodelic_compile["specodelic.compile"]
specodelic_compiled(["specodelic.compiled (fan-in 2)"])
specodelic_coverage["specodelic.coverage (fan-in 1)"]
specodelic_draft(["specodelic.draft (fan-in 1)"])
specodelic_frontmatter_valid["specodelic.frontmatter_valid (fan-in 1)"]
specodelic_id_matches_file["specodelic.id_matches_file (fan-in 1)"]
specodelic_law_requires_cases["specodelic.law_requires_cases (fan-in 1)"]
specodelic_lint["specodelic.lint"]
specodelic_linted(["specodelic.linted (fan-in 2)"])
specodelic_model_check["specodelic.model_check"]
specodelic_model_checked(["specodelic.model_checked (fan-in 2)"])
specodelic_model_present["specodelic.model_present (fan-in 1)"]
specodelic_no_counterexample["specodelic.no_counterexample (fan-in 1)"]
specodelic_parse["specodelic.parse"]
specodelic_parsed(["specodelic.parsed (fan-in 4)"])
specodelic_properties_pass["specodelic.properties_pass (fan-in 1)"]
specodelic_verified(["specodelic.verified (fan-in 1)"])
specodelic_verify["specodelic.verify"]
specodelic_compile -->|"transitions.from"| specodelic_linted
specodelic_compile -.->|"transitions.guard"| specodelic_coverage
specodelic_compile -.->|"transitions.guard"| specodelic_law_requires_cases
specodelic_compile -->|"transitions.to"| specodelic_compiled
specodelic_lint -->|"transitions.from"| specodelic_parsed
specodelic_lint -->|"transitions.to"| specodelic_linted
specodelic_model_check -->|"transitions.from"| specodelic_compiled
specodelic_model_check -.->|"transitions.guard"| specodelic_model_present
specodelic_model_check -->|"transitions.to"| specodelic_model_checked
specodelic_parse -->|"transitions.from"| specodelic_draft
specodelic_parse -.->|"transitions.guard"| specodelic_frontmatter_valid
specodelic_parse -.->|"transitions.guard"| specodelic_id_matches_file
specodelic_parse -->|"transitions.to"| specodelic_parsed
specodelic_verify -->|"transitions.from"| specodelic_model_checked
specodelic_verify -.->|"transitions.guard"| specodelic_no_counterexample
specodelic_verify -.->|"transitions.guard"| specodelic_properties_pass
specodelic_verify -->|"transitions.to"| specodelic_verified
end
subgraph sg_svc_contracts_pack["svc.contracts.pack"]
direction LR
svc_contracts_pack_deprecate["svc.contracts.pack.deprecate"]
svc_contracts_pack_deprecated(["svc.contracts.pack.deprecated (fan-in 1)"])
svc_contracts_pack_draft(["svc.contracts.pack.draft (fan-in 1)"])
svc_contracts_pack_interfaces_section_shape["svc.contracts.pack.interfaces_section_shape (fan-in 1)"]
svc_contracts_pack_pack_file_declared["svc.contracts.pack.pack_file_declared (fan-in 2)"]
svc_contracts_pack_publish["svc.contracts.pack.publish"]
svc_contracts_pack_published(["svc.contracts.pack.published (fan-in 2)"])
svc_contracts_pack_deprecate -->|"transitions.from"| svc_contracts_pack_published
svc_contracts_pack_deprecate -.->|"transitions.guard"| svc_contracts_pack_pack_file_declared
svc_contracts_pack_deprecate -->|"transitions.to"| svc_contracts_pack_deprecated
svc_contracts_pack_publish -->|"transitions.from"| svc_contracts_pack_draft
svc_contracts_pack_publish -.->|"transitions.guard"| svc_contracts_pack_interfaces_section_shape
svc_contracts_pack_publish -.->|"transitions.guard"| svc_contracts_pack_pack_file_declared
svc_contracts_pack_publish -->|"transitions.to"| svc_contracts_pack_published
end
subgraph sg_verification_claims["verification.claims"]
direction LR
verification_claims_approve["verification.claims.approve"]
verification_claims_approved(["verification.claims.approved (fan-in 2)"])
verification_claims_deploy["verification.claims.deploy"]
verification_claims_deployed(["verification.claims.deployed (fan-in 1)"])
verification_claims_implement["verification.claims.implement"]
verification_claims_implemented(["verification.claims.implemented (fan-in 2)"])
verification_claims_process_lifecycle["verification.claims.process_lifecycle (fan-in 3)"]
verification_claims_proposed(["verification.claims.proposed (fan-in 1)"])
verification_claims_approve -->|"transitions.from"| verification_claims_proposed
verification_claims_approve -.->|"transitions.guard"| verification_claims_process_lifecycle
verification_claims_approve -->|"transitions.to"| verification_claims_approved
verification_claims_deploy -->|"transitions.from"| verification_claims_implemented
verification_claims_deploy -.->|"transitions.guard"| verification_claims_process_lifecycle
verification_claims_deploy -->|"transitions.to"| verification_claims_deployed
verification_claims_implement -->|"transitions.from"| verification_claims_approved
verification_claims_implement -.->|"transitions.guard"| verification_claims_process_lifecycle
verification_claims_implement -->|"transitions.to"| verification_claims_implemented
end
subgraph sg_verify["verify"]
direction LR
verify_accept["verify.accept"]
verify_begin["verify.begin"]
verify_blocks_run_to_completion["verify.blocks_run_to_completion (fan-in 1)"]
verify_both_gates_required["verify.both_gates_required (fan-in 2)"]
verify_evaluate["verify.evaluate"]
verify_evidence_scope_bound["verify.evidence_scope_bound (fan-in 2)"]
verify_failed(["verify.failed (fan-in 1)"])
verify_law_cases_unexecuted["verify.law_cases_unexecuted (fan-in 2)"]
verify_not_run(["verify.not_run (fan-in 1)"])
verify_properties_evaluated(["verify.properties_evaluated (fan-in 3)"])
verify_properties_pass_reflects_latest_run["verify.properties_pass_reflects_latest_run (fan-in 2)"]
verify_reject["verify.reject"]
verify_required_claims_govern_acceptance["verify.required_claims_govern_acceptance (fan-in 2)"]
verify_running(["verify.running (fan-in 2)"])
verify_verified(["verify.verified (fan-in 1)"])
verify_accept -->|"transitions.from"| verify_properties_evaluated
verify_accept -.->|"transitions.guard"| verify_both_gates_required
verify_accept -.->|"transitions.guard"| verify_evidence_scope_bound
verify_accept -.->|"transitions.guard"| verify_law_cases_unexecuted
verify_accept -.->|"transitions.guard"| verify_properties_pass_reflects_latest_run
verify_accept -.->|"transitions.guard"| verify_required_claims_govern_acceptance
verify_accept -->|"transitions.to"| verify_verified
verify_begin -->|"transitions.from"| verify_not_run
verify_begin -.->|"transitions.guard"| verify_blocks_run_to_completion
verify_begin -->|"transitions.to"| verify_running
verify_evaluate -->|"transitions.from"| verify_running
verify_evaluate -->|"transitions.to"| verify_properties_evaluated
verify_reject -->|"transitions.from"| verify_properties_evaluated
verify_reject -.->|"transitions.guard"| verify_both_gates_required
verify_reject -.->|"transitions.guard"| verify_evidence_scope_bound
verify_reject -.->|"transitions.guard"| verify_law_cases_unexecuted
verify_reject -.->|"transitions.guard"| verify_properties_pass_reflects_latest_run
verify_reject -.->|"transitions.guard"| verify_required_claims_govern_acceptance
verify_reject -->|"transitions.to"| verify_failed
end
Traceability — file dependencies
graph_views.py trace: every edge collapsed to its owning intent,
cross-file dependencies deduped, fan-in annotated per intent.
%% traceability view — derived from spk graph --format edges (edges collapsed to owning intents; fan-in over distinct source intents per D2) flowchart LR acset_core["acset.core"] acset_writer["acset.writer"] archive_companion["archive.companion"] bioimage_data_pack["bioimage.data.pack"] compile["compile"] conform["conform"] data_lineage_pack["data.lineage.pack"] docs_site["docs.site"] doctor["doctor"] embedded_guide["embedded.guide"] empirical_registry_pack["empirical.registry.pack"] error_contract["error.contract"] errors["errors (fan-in 16)"] graph["graph (fan-in 1)"] graph_views["graph.views"] hooks["hooks"] kernel["kernel"] kinds["kinds"] lint_findings["lint.findings"] linter_coverage["linter.coverage"] linter_ears_syntax["linter.ears_syntax"] linter_external_completeness["linter.external_completeness"] linter_failure_shape["linter.failure_shape"] linter_frontmatter["linter.frontmatter"] linter_graph_shape["linter.graph_shape (fan-in 1)"] linter_model_shape["linter.model_shape"] linter_observability["linter.observability"] linter_referential_integrity["linter.referential_integrity (fan-in 1)"] linter_schema_shape["linter.schema_shape"] merge["merge"] migrate["migrate"] model_check["model.check"] model_check_2["model_check"] numeric_predicates_pack["numeric.predicates.pack"] observability["observability"] orchestrate["orchestrate"] packs["packs"] parse["parse"] quant_finance_pack["quant.finance.pack"] refactor["refactor"] rename["rename"] skill_pack["skill.pack"] spec_integration["spec.integration"] specodelic["specodelic (fan-in 5)"] svc_contracts_pack["svc.contracts.pack"] verification_claims["verification.claims"] verify["verify"] compile --> errors graph --> errors graph --> specodelic kinds --> errors linter_coverage --> errors linter_coverage --> specodelic linter_ears_syntax --> errors linter_external_completeness --> errors linter_external_completeness --> specodelic linter_failure_shape --> errors linter_frontmatter --> errors linter_graph_shape --> errors linter_model_shape --> errors linter_referential_integrity --> errors linter_referential_integrity --> specodelic linter_schema_shape --> errors merge --> errors orchestrate --> errors orchestrate --> specodelic refactor --> graph rename --> errors rename --> linter_graph_shape rename --> linter_referential_integrity verify --> errors
The typing diagram — revision-labeled
graph_views.py schema: the Reference Typing table (which reference
field may point at what), rendered from the versioned acset Schema
export (spk guide --schema --json) alone. The label carries the format
revision the export was generated at (format_revision) — a diagram
labeled Revision 17 was rendered from a Revision 17 binary.
%% schema view — derived from the acset Schema export (format revision: specodelic.md Revision 19) flowchart LR schema["schema — specodelic.md Revision 19"] Constraint["Constraint"] Intent["Intent"] Property["Property"] State["State"] Transition["Transition"] Constraint -- "observes [target=effect]" --> Constraint Constraint -- "satisfies [target=extension_point]" --> Constraint Constraint -- "supersedes" --> Constraint Constraint -- "traces_to" --> Intent Constraint -- "uses [target=profile]" --> Intent Property -- "derives_from" --> Constraint Property -- "derives_from_law (column: derives_from) [source=law]" --> Property Property -- "supersedes" --> Property State -- "emits [target=effect]" --> Constraint Transition -- "from" --> State Transition -- "guard [target=invariant]" --> Constraint Transition -- "guard_state (column: guard)" --> State Transition -- "to" --> State
Whole-corpus terminal view
The complete corpus projection — every typed reference edge, same visual
grammar (solid = state machine, dashed = guards, dotted =
traceability) — rendered through gently --as ascii at build time:
+-------------------------------------------------------------+ transitions.guard +------------------------------------------------------------+ constraints.traces_to +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| acset.core.approve | - - - - - - - - - - - - -> | | .........................> | |
+-------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ | acset.core.process_lifecycle | | |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| acset.core.approved | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | |
^ | ^ | |
|--------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
| | | |
| | | |
|--------------------------------------------------------------+ | | |
| | | | |
+-------------------------------------------------------------+ | | | |
| acset.core.implement | -+---------------------------| | |
+-------------------------------------------------------------+ | | | |
| | | |
+--------------------------------------------------------------+ |-------------------------------------------------------------+ | acset.core |
| | | | |
| +------------------------------------------------------------+ | | |
| | acset.core.process_lifecycle_checked | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| acset.core.walk_parity_fan_and_cycles | .+.......................> | acset.core.parity_with_existing | -+------------------------>| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
v | | |
+-------------------------------------------------------------+ | | |
| acset.core.proposed | | | |
+-------------------------------------------------------------+ | | |
| | |
| | |
| | |
| | |
| | |
| +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ^
| +---------------------------+
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
+-------------------------------------------------------------| +--------------------------------------------------------------+
| |
+-------------------------------------------------------------+ transitions.to +------------------------------------------------------------+ states.emits +-------------------------------------------------------------+ constraints.satisfies +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ constraints.traces_to +--------+
| graph.extract_fail | -+-----------------------> | graph.failed | -+-----------------------> | | -+-----------------------> | | .......................> | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | | |
| | | | | | | | |
| +-------------------------------------------------------------| | graph.extraction_failure | | | | >| |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | +-------------------------------------------------------------+ | | | | |
| | | ^ | | | | |
| | | |--------------------------------------------------------------| | | | errors |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | +--------+
| | | | | | | ^
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | errors.envelope_error_kind | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | ^ |
|---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| v | | | v | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| graph.wrongly_typed_edge_rejected | .+.......................> | graph.edge_kind_matches_typing | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| ^ | | | | | | |
| |-------------------------------------------------------------| | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | graph | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | | | | ^ | | |
|---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ |----------------------------------------------------------------------------------------------------------------------------------------------+ |
| v | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | | | |
| graph.extract | -+-----------------------> | graph.extracting | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------| |-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| graph.unindexed | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------| | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| specodelic.parsed | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+ | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| graph.observes_edge_extracted | .+.......................> | | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | graph.total_extraction | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
| |-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | | |
| | | +-------------------------------------------------------------+ | | | |
| | | | graph.extraction_failure_label_asserted | | | | |
| | | +-------------------------------------------------------------+ | | | |
| | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | | | | |
| verify.verification_failure_label_asserted | .+.......................> | | -+---------------------------+--------------------------------------------------------------+---------------------------+ | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | verify.verification_failure | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+-----------------------------------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| v | |
+-------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| errors.exit_code_mismatch_rejected | .+.......................> | | -----------------------------------------------|
+-------------------------------------------------------------+ | | | |
| | | |
+--------------------------------------------------------------| | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | errors.exit_code_mapping | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | ^ |
+--------------------------------------------------------------|---------------------------+ |
| v |
+-------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| errors.hint_missing_rejected | .+.......................> | | -----------------------------------------------|
+-------------------------------------------------------------+ | | | |
| | | |
+--------------------------------------------------------------| | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | errors.remediation_hint_present | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | ^ | |
+--------------------------------------------------------------+-----------------------------------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ |
| | | | | v |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| verify.views_agree_on_blockers | .+.......................> | verify.assurance_views_agree | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------+---------------------------|-------------------------------------------------------------+ | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | verify | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | ^ |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| kinds.assign_fail | -+-----------------------> | kinds.kind_failed | -+-----------------------> | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | kinds.kind_assignment_failure | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | | | |
| kinds.begin | -+-----------------------> | kinds.kind_assigned | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
| transitions.from | +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| kinds.unclassified | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
|---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | v | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| kinds.well_formed_row_passes | .+.......................> | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+ | kinds.kind_enum_closed | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | kinds | | |
| +------------------------------------------------------------+ | | | | | | |
| ^ | | | | | | |
| +-------------------------------------------------------------+ | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | | | | ^ | |
| | | +--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | | | |
| | | +-------------------------------------------------------------+ | | | |
| | | | kinds.kind_assignment_failure_label_asserted | | | | |
| | | +-------------------------------------------------------------+ | | | |
| | | | | | |
+------------------------------------------------------------------------------------------+-------------------------------------------------------------+------------------------------------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | |
+-------------------------------------------------------------------------------------------------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ |tates.emits +--------------------------------------------------------------+ | | |
| | -+-----------------------> | kinds.shape_failed | -+-----------------------> | | | | |
| | | +-------------------------------------------------------------+ | | | | | |
| | | | | | | | |
| kinds.reject | | +--------------------------------------------------------------| | kinds.shape_check_failure | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
+-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| | | | ^ | | |
+--------------------------------------------------------------+-----------------------------------------------------------------------------------------+---------------------------+--------------------------------------------------------------| +---------------------------------------------------------------+ | |
| | v | | | |
+-------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | | |
| kinds.constraint_row_extra_field_rejected | .+.......................> | | -+---------------------------| | |
+-------------------------------------------------------------+ | | | | | | |
| | | | | | |
+--------------------------------------------------------------+ | kinds.constraint_row_shape | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | | |
| | ^ | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| kinds.intent_missing_statement_rejected | .+.......................> | kinds.intent_row_shape | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
|---------------------------+-------------------------------------------------------------| | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| kinds.property_row_bad_subkind_rejected | .+.......................> | kinds.property_row_shape | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------|---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------| | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| kinds.shape_checked | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ | | |
| v | | | |
+-------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | | |
| kinds.state_row_without_emits_still_passes | .+.......................> | | -+---------------------------| | |
+-------------------------------------------------------------+ | | | | | | |
| | | | | | |
+--------------------------------------------------------------+ | kinds.state_row_shape | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | | |
| | ^ | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| kinds.transition_missing_guard_rejected | .+.......................> | kinds.transition_row_shape | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | |
| | | +-------------------------------------------------------------+ | | | |
| | | | kinds.shape_check_failure_label_asserted | -+---------------------------| | |
| | | +-------------------------------------------------------------+ | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------| | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| linter.coverage.count_fail | -+-----------------------> | linter.coverage.count_failed | -+-----------------------> | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | linter.coverage.count_failure | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |ransitions.from +------------------------------------------------------------+ | | | | | |
| linter.coverage.count_ok | -+-----------------------> | linter.coverage.counting | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------|---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +-----------------------------------------------------------------+ |
| | | | | v | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| linter.coverage.uncovered_constraint_rejected | .+.......................> | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+ | linter.coverage.every_constraint_covered | | | | | linter.coverage | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | +------------------------------------------------------------+ | | | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | ^ | | | ^ | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | +---------------------------+ +-----------------------------------------------------------------+ |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.coverage.unlabeled_law_rejected | .+.......................> | | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | linter.coverage.every_law_has_cases | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| linter.coverage.law_checking | | | | | linter.coverage.count_failure_label_asserted | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
^ | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.coverage.orphan_property_rejected | .+.......................> | linter.coverage.no_orphan_property | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| | | | | | | |
| +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ |tates.emits +--------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------+ | |
| | linter.coverage.reject | -+-----------------------> | linter.coverage.law_check_failed | -+-----------------------> | | <+........................ | linter.coverage.law_case_failure_label_asserted | | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | | +----------------------------------------------------------------+ | |
| | | | | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ | linter.coverage.law_case_failure | | +-----------------------------------------------------------------+ |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | +--------------------------------------------------------------+ | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| linter.ears_syntax.pattern_fail | -+-----------------------> | linter.ears_syntax.pattern_failed | -+-----------------------> | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | linter.ears_syntax.pattern_failure | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +-----------------------------------------------------------------+ |
| v | | | v | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| linter.ears_syntax.valid_ears_passes | .+.......................> | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+ | linter.ears_syntax.ears_pattern_match | | | | | linter.ears_syntax | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | +------------------------------------------------------------+ | | | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | ^ | | | ^ | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------| | +---------------------------+ +-----------------------------------------------------------------+ |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.ears_syntax.missing_shall_rejected | .+.......................> | linter.ears_syntax.has_shall | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
|---------------------------+-------------------------------------------------------------| | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | | | |
| linter.ears_syntax.begin | -+-----------------------> | linter.ears_syntax.pattern_matching | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
| transitions.from | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| linter.ears_syntax.unchecked | | | | | linter.ears_syntax.pattern_failure_label_asserted | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| | | | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------| +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ |roperties.derives_from +--------------------------------------------------------------+ | | |
| linter.ears_syntax.reject | -+-----------------------> | linter.ears_syntax.id_check_failed | -+-----------------------> | | <+........................ | linter.ears_syntax.id_check_failure_label_asserted | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
| transitions.from | +-------------------------------------------------------------+ | linter.ears_syntax.id_check_failure | | +---------------------------------------------------------------+ | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| linter.ears_syntax.id_checking | | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
^ | | | | | | |
+--------------------------------------------------------------+ | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.ears_syntax.conjoined_id_rejected | .+.......................> | linter.ears_syntax.no_conjoined_id | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
|---------------------------+ | | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.ears_syntax.universal_token_rejected | .+.......................> | linter.ears_syntax.no_universal_in_id | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
|---------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| linter.external_completeness.check_fail | -+-----------------------> | linter.external_completeness.mapping_failed | -+-----------------------> | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | linter.external_completeness.mapping_failure | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | v | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| linter.external_completeness.unmapped_item_rejected | .+.......................> | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+ | linter.external_completeness.every_item_accounted | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | linter.external_completeness | | |
| | +------------------------------------------------------------+ | | | | | | |
| | ^ | | | | | | |
| | +-------------------------------------------------------------+ | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | | | | | ^ | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.external_completeness.unrationalized_waiver_rejected | .+.......................> | linter.external_completeness.waiver_has_rationale | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| linter.external_completeness.loaded | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| linter.external_completeness.duplicate_claim_rejected | .+.......................> | linter.external_completeness.no_duplicate_claim | -| | linter.external_completeness.mapping_failure_label_asserted | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| ^ | | | | |
| +-------------------------------------------------------------| +--------------------------------------------------------------| | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| linter.external_completeness.load_fail | -+-----------------------> | linter.external_completeness.load_failed | -+-----------------------> | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------|---------------------------+ | | linter.external_completeness.manifest_failure | | | | |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | | |
| linter.external_completeness.malformed_checklist_rejected | .+.......................> | linter.external_completeness.checklist_well_formed | -| | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| ^ | ^ | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+---------------------------| | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | | | | +---------------------------------------------------------------+ | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | +--------------------------------------------------------------+ | | |
| linter.external_completeness.not_applicable | | | | | | | linter.external_completeness.manifest_failure_label_asserted | | | |
+-------------------------------------------------------------+ | | | | | +--------------------------------------------------------------+ | | |
^ | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------| +-----------------------------------------------------------------+ |
| | | | | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ |tates.emits +--------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------+ | |
| | | linter.external_completeness.reject | -+-----------------------> | linter.external_completeness.resolution_failed | -+-----------------------> | | <+........................ | linter.external_completeness.resolution_failure_label_asserted | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | | +----------------------------------------------------------------+ | |
| | | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ | linter.external_completeness.resolution_failure | | +-----------------------------------------------------------------+ |
| v | | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | | | |
| linter.external_completeness.dangling_mapped_id_rejected | .+.......................> | linter.external_completeness.covered_maps_resolve | -+---------------------------+--------------------------------------------------------------| | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | +---------------------------------------------------------------+ | |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ | | | | |
| | v | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | linter.external_completeness.mapping_checked | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| | -+-----------------------> | linter.failure_shape.failed | -+-----------------------> | | -+---------------------------| | |
| | | +------------------------------------------------------------+ | | | | | | |
| | | | | | | | | |
| linter.failure_shape.reject | | +-------------------------------------------------------------+ | linter.failure_shape.check_failure | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| | | ^ | | | |
|---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ | |
| v | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| linter.failure_shape.begin | -+-----------------------> | linter.failure_shape.checking | | | | | linter.failure_shape.check_failure_label_asserted | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| | ^ | | | | | |
| transitions.from | +-------------------------------------------------------------+ | | +---------------------------------------------------------------+ | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| linter.failure_shape.unchecked | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
|---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ | |
| v | | | v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| linter.failure_shape.label_collision_rejected | .+.......................> | linter.failure_shape.error_labels_unique | | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | | |
| ^ | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | linter.failure_shape | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | +--------------------------------------------------------------+ | | |
| | | | | | ^ | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ | |
| v | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| linter.failure_shape.zero_citation_flagged | .+.......................> | | -+........................ | linter.failure_shape.carved_out_guard_passes | | | | |
+-------------------------------------------------------------+ | | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
+--------------------------------------------------------------+ | linter.failure_shape.guard_negation_total | | +--------------------------------------------------------------+ | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------| | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.failure_shape.mute_terminal_rejected | .+.......................> | | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | linter.failure_shape.terminal_states_emit | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| | -+-----------------------> | linter.frontmatter.failed | -+-----------------------> | | -+---------------------------| | |
| | | +------------------------------------------------------------+ | | | | | | |
| | | | | | | | | |
| linter.frontmatter.reject | | +-------------------------------------------------------------+ | linter.frontmatter.check_failure | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| | | ^ | | | |
|---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ | |
| v | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| linter.frontmatter.begin | -+-----------------------> | linter.frontmatter.checking | | | | | linter.frontmatter.check_failure_label_asserted | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| | ^ | | | | | |
| transitions.from | +-------------------------------------------------------------+ | | +---------------------------------------------------------------+ | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| linter.frontmatter.unchecked | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
|---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +-----------------------------------------------------------------+ |
| v | | | v | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| linter.frontmatter.valid_passes | .+.......................> | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+ | linter.frontmatter.has_id | | | | | linter.frontmatter | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | +------------------------------------------------------------+ | | | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | ^ | | | ^ | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------|---------------------------+--------------------------------------------------------------+---------------------------+ +-----------------------------------------------------------------+ |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.frontmatter.wrong_kind_rejected | .+.......................> | linter.frontmatter.has_kind | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
|---------------------------+-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| v | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| linter.frontmatter.missing_statement_rejected | .+.......................> | linter.frontmatter.has_statement | -| | linter.graph_shape.check_failure_label_asserted | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| ^ | | | | | |
|---------------------------+-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.frontmatter.filename_mismatch | .+.......................> | linter.frontmatter.id_matches_file | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
| +-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| | v | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| | -+-----------------------> | linter.graph_shape.failed | -+-----------------------> | | -+---------------------------+-------------------------------------------------------------------------------------------+ |
| | | +------------------------------------------------------------+ | | | | | | |
| | | | | | | | | |
| linter.graph_shape.reject | | +-------------------------------------------------------------+ | linter.graph_shape.check_failure | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
+--------------------------------------------------------------|---------------------------+-------------------------------------------------------------+------------------------------------------------------------------------------------------+---------------------------+---------------------------------------------------------------+---------------------------+ |
| v | | | v |
+-------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | | +---------------------------------------------------------------------------------------------------------------------------------------------+ |
| rename.accept | -+- - - - - - - - - - - -> | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+ | | | | | | linter.graph_shape | |
| | | | | | | | | |
| | | | | | | | | |
| | | linter.graph_shape.acyclic | | | | >| | |
| | | | | | | +---------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | | | ^ |
| | | | | | +---------------------------| |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | | |
| | ^ | | | |
+--------------------------------------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------+---------------------------------------------------------------+---------------------------+ |
| v | | | v |
+-------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | | +---------------------------------------------------------------------------------------------------------------------------------------------+ |
| linter.referential_integrity.dangling_ref_rejected | .+.......................> | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | linter.referential_integrity.ref_resolves | | | | | linter.referential_integrity | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | | +---------------------------------------------------------------------------------------------------------------------------------------------+ |
| | ^ | | | ^ |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+---------------------------+ |
| | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | | |
| linter.referential_integrity.wrong_target_kind | .+.......................> | linter.referential_integrity.ref_kind_compatible | -+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| ^ | | | | | | |
+--------------------------------------------------------------|---------------------------+-------------------------------------------------------------+ | | | | | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| rename.passed | | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------| | | | | | | | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| rename.verifying | | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
^ | | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | | | | | |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | | |
| linter.graph_shape.self_ref_rejected | .+.......................> | linter.graph_shape.no_self_ref | -+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| ^ | | | | | | |
|---------------------------+-------------------------------------------------------------| | | | | | |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | | |
| linter.graph_shape.orphan_subgraph_rejected | .+.......................> | linter.graph_shape.single_root_reachable | -+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| ^ | | | | | | |
|---------------------------+-------------------------------------------------------------| +--------------------------------------------------------------+ | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | | |
| linter.graph_shape.supersedes_dag_ignores_traces_cycle | .+.......................> | | -+........................ | linter.graph_shape.supersedes_cycle_rejected | | | | | |
+-------------------------------------------------------------+ | | | | +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
+--------------------------------------------------------------| | linter.graph_shape.supersedes_dag | | +--------------------------------------------------------------+ | | | |
v | | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | | |
| linter.graph_shape.traversing | | | | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
^ | ^ | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | | |
| | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | | |
| | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | | |
| | -+-----------------------> | linter.model_shape.pairing_failed | -+-----------------------> | | -+---------------------------| | | |
| | | +------------------------------------------------------------+ | | | | | | | |
| | | | | | | | | | |
| linter.model_shape.pairing_fail | | +-------------------------------------------------------------+ | linter.model_shape.pairing_failure | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | | |
| | | ^ | | | | |
|---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +---------------------------+-----------------------------------------------------------------+ |
| v | | | v | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| linter.model_shape.unreachable_state_rejected | .+.......................> | linter.model_shape.every_state_used | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| ^ | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | linter.model_shape | | |
| | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | | | | | |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | | |
| linter.model_shape.unpaired_states_rejected | .+.......................> | linter.model_shape.model_sections_paired | -+---------------------------+--------------------------------------------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| ^ | | | ^ | |
|---------------------------+-------------------------------------------------------------|---------------------------+--------------------------------------------------------------+---------------------------+ +---------------------------+-----------------------------------------------------------------+ |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | | |
| linter.model_shape.well_formed_model_passes | .+.......................> | | -+---------------------------+--------------------------------------------------------------+---------------------------| | | |
+-------------------------------------------------------------+ | | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+ | linter.model_shape.every_transition_valid | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | | |
| | ^ | | | | | | |
| | +-------------------------------------------------------------+ | | | | | |
| | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | | |
| v | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | | |
| linter.model_shape.check_pairing | -+-----------------------> | linter.model_shape.pairing_checked | | | linter.model_shape.pairing_failure_label_asserted | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | | |
| | ^ | | | | | |
| transitions.from | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| linter.model_shape.ungrouped | | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | | |
| | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | | |
| linter.model_shape.reject | -+-----------------------> | linter.model_shape.field_check_failed | -+-----------------------> | | -+---------------------------| | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------|---------------------------+ | | linter.model_shape.field_check_failure | | +---------------------------------------------------------------+ | |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ | | |
| linter.model_shape.missing_guard_rejected | .+.......................> | linter.model_shape.guard_present | -| | | <+------------------------ | linter.model_shape.field_check_failure_label_asserted | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| ^ | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | | | | | |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | | |
| linter.model_shape.bool_column_rejected | .+.......................> | linter.model_shape.no_bool_state_field | -+---------------------------+--------------------------------------------------------------+---------------------------| | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| ^ | | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| linter.model_shape.states_checked | | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
^ | | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ |roperties.derives_from +--------------------------------------------------------------+ | | |
| linter.referential_integrity.index_fail | -+-----------------------> | linter.referential_integrity.index_failed | -+-----------------------> | | <+........................ | linter.referential_integrity.index_failure_label_asserted | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ | | |
| | | | | | | |
|---------------------------+ | | linter.referential_integrity.index_failure | | +---------------------------------------------------------------+ | |
| v | | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | | | | | |
| linter.referential_integrity.build_index | -+-----------------------> | linter.referential_integrity.indexing | | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | | |
| | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+ | | | | | | | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| linter.referential_integrity.unindexed | | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | v | | | |
| | +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ |roperties.derives_from +--------------------------------------------------------------+ | | |
| | | linter.referential_integrity.index_ok | -+- - - - - - - - - - - -> | linter.referential_integrity.unique_across_repo | -+........................ | linter.referential_integrity.cross_file_collision | | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| v | | | | | | |
+-------------------------------------------------------------+ |ransitions.from +------------------------------------------------------------+ | | | | | | |
| linter.referential_integrity.resolve_refs | -+-----------------------> | linter.referential_integrity.indexed | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | | |
| transitions.to | +-------------------------------------------------------------+ | | | | | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| linter.referential_integrity.resolving | | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
^ | | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | | | | | |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | | |
| linter.referential_integrity.duplicate_id_rejected | .+.......................> | linter.referential_integrity.unique_within_file | -+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+---------------------------+ |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ +---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| | | | | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ |tates.emits +--------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------+ | |
| | | linter.referential_integrity.reject | -+-----------------------> | linter.referential_integrity.resolution_failed | -+-----------------------> | | <+........................ | linter.referential_integrity.resolution_failure_label_asserted | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | | +----------------------------------------------------------------+ | |
| | | | | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | linter.referential_integrity.resolution_failure | | +-----------------------------------------------------------------+ |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| linter.schema_shape.diff_fail | -+-----------------------> | linter.schema_shape.diff_failed | -+-----------------------> | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | linter.schema_shape.diff_failure | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |ransitions.from +------------------------------------------------------------+ | | | | | |
| linter.schema_shape.diff_skip | -+-----------------------> | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+ | linter.schema_shape.diffing | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| linter.schema_shape.parser_audited | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | v | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| linter.schema_shape.silent_narrowing_rejected | .+.......................> | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | | |
| | | | | | | | | |
+--------------------------------------------------------------+ | linter.schema_shape.id_set_grows_only | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | linter.schema_shape | | |
| | +------------------------------------------------------------+ | | | | | | |
| | ^ | | | | | | |
| | +-------------------------------------------------------------+ | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | | | | | ^ | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | +-|-----------------------------------------------------------+ | | | |
| linter.schema_shape.reordered_id_set_rejected | .+.......................> | linter.schema_shape.id_set_order_stable | -| | | linter.schema_shape.diff_failure_label_asserted | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-|-----------------------------------------------------------+ | | | |
| ^ | | | | | |
| +-------------------------------------------------------------| +--------------------------------------------------------------| | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-|-----------------------------------------------------------+ | | | |
| linter.schema_shape.kind_fail | -+-----------------------> | linter.schema_shape.kind_failed | -+-----------------------> | | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | linter.schema_shape.kind_check_failure | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | +-|-----------------------------------------------------------+ | | | |
| | | | ^ | | | |
| | | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| |---------------------------+-------------------------------------------------------------+---------------------------+ +---------------------------+---------------------------------------------------------------+ | |
| | | | v | | |
| | +------------------------------------------------------------+ |ransitions.guard +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| | | linter.schema_shape.kind_ok | -+- - - - - - - - - - - -> | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------| | linter.schema_shape.constraint_kind_closed | | | |
| v | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | | | |
| linter.schema_shape.begin | -+-----------------------> | linter.schema_shape.kind_checking | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| | | ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+---------------------------+ |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | | | | | |-----------------------------------------------------------------+ |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | +----------------------------------------------------------------+ | |
| linter.schema_shape.unchecked | | | | | | | | linter.schema_shape.constraint_kind_advisory_passes | | |
+-------------------------------------------------------------+ | | | | | | +----------------------------------------------------------------+ | |
| | | | | | | |
|---------------------------+-------------------------------------------------------------+ | | | +-----------------------------------------------------------------+ |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.schema_shape.property_kind_invalid_rejected | .+.......................> | linter.schema_shape.property_kind_closed | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------| | +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ |roperties.derives_from +--------------------------------------------------------------+ | | |
| linter.schema_shape.reject | -+-----------------------> | linter.schema_shape.parser_audit_failed | -+-----------------------> | | <+........................ | linter.schema_shape.parser_audit_failure_label_asserted | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ | | |
| | | | | | | |
|---------------------------+ | | linter.schema_shape.parser_audit_failure | | +---------------------------------------------------------------+ | |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | | |
| linter.schema_shape.parser_ast_prose_free | .+.......................> | linter.schema_shape.no_prose_field_parsed | -| | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| ^ | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| linter.schema_shape.prose_roundtrips | .+.......................> | linter.schema_shape.prose_field_passthrough | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
| +-------------------------------------------------------------| +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ |roperties.derives_from +--------------------------------------------------------------+ | | |
| merge.collision_fail | -+-----------------------> | merge.collision_failed | -+-----------------------> | | <+........................ | merge.collision_failure_label_asserted | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
| transitions.from | +-------------------------------------------------------------+ | merge.collision_failure | | +---------------------------------------------------------------+ | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| merge.diverged | | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
^ | | | | | | |
+--------------------------------------------------------------+ | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | v | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| merge.reachability_from_graph_artifact_only | .+.......................> | merge.graph_reused_not_rederived | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| ^ | | | | | | |
| | | | | | | | |
| | | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +-|----------------------------------------------------------+ | | | | | | |
| merge.textually_clean_semantic_conflict_flagged | .+.......................> | | merge.semantic_conflict_iff_blast_radius_intersects | -+---------------------------+--------------------------------------------------------------| | | | |
+-------------------------------------------------------------+ | +-|----------------------------------------------------------+ | | | | | | |
| ^ | | | | | | |
|---------------------------|-------------------------------------------------------------| | | | merge | | |
| | | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +-|----------------------------------------------------------+ | | | | | | |
| merge.inherited_id_not_falsely_flagged | .+.......................> | | | -+---------------------------+--------------------------------------------------------------| | | | |
+-------------------------------------------------------------+ | | | | | | | | | | |
| | | | | | | | | | |
+--------------------------------------------------------------+ | | merge.no_new_id_collision | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
| | +-|----------------------------------------------------------+ | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | | | | | ^ | |
| | +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+---------------------------| | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| merge.reject | -+-----------------------> | merge.merge_failed | -+-----------------------> | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | merge.merge_aborted | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
+--------------------------------------------------------------+ | | +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| merge.ref_integrity_reverified | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------|---------------------------+-------------------------------------------------------------+ | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| merge.duplicate_sequential_number_renumbered | .+.......................> | merge.sequential_number_reassigned_on_conflict | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
|---------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| merge.reverify_fail | -+-----------------------> | merge.reverification_failed | -+-----------------------> | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | merge.reverification_failure | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
| | | | +--------------------------------------------------------------+---------------------------| | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | | +---------------------------------------------------------------+ | |
| | v | | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| merge.reverify | -+- - - - - - - - - - - -> | merge.post_merge_relint_required | -+---------------------------+--------------------------------------------------------------| | merge.reverification_failure_label_asserted | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| | ^ | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | +---------------------------------------------------------------+ | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| merge.rename_replayed | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ |roperties.derives_from +--------------------------------------------------------------+ | | |
| orchestrate.compile_fail | -+-----------------------> | orchestrate.compile_failed | -+-----------------------> | | <+........................ | orchestrate.compile_stage_failure_label_asserted | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | orchestrate.compile_stage_failure | | +---------------------------------------------------------------+ | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| orchestrate.compile_stage | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| | | | | v |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| orchestrate.verify_ok | -+- - - - - - - - - - - -> | orchestrate.stage_order_fixed | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| orchestrate.succeeded | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
+--------------------------------------------------------------| | | | | | orchestrate | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| orchestrate.verify_stage | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+ | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | | ^ |
| | | |---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| orchestrate.lint_fail | -+-----------------------> | orchestrate.lint_failed | -+-----------------------> | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | orchestrate.lint_stage_failure | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
+--------------------------------------------------------------+ | | +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| orchestrate.lint_stage | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+ | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ | | | |
| orchestrate.verify_fail | -+-----------------------> | orchestrate.verify_failed | -+-----------------------> | | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | orchestrate.verify_stage_failure | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
| | | | +--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | orchestrate.lint_stage_failure_label_asserted | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------| +---------------------------------------------------------------+ |-----------------------------------------------------------------+ |
| | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ |roperties.derives_from +--------------------------------------------------------------+ | +----------------------------------------------------------------+ | |
| orchestrate.model_check_fail | -+-----------------------> | orchestrate.model_check_failed | -+-----------------------> | | <+........................ | orchestrate.model_check_stage_failure_label_asserted | | | orchestrate.verify_stage_failure_label_asserted | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ | +----------------------------------------------------------------+ | |
| | | | | | | | |
| transitions.from | +-------------------------------------------------------------+ | orchestrate.model_check_stage_failure | | +---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| orchestrate.model_check_stage | | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
^ | | | | | | |
+--------------------------------------------------------------+ | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ |roperties.derives_from +--------------------------------------------------------------+ | | |
| | -+-----------------------> | rename.apply_failed | -+-----------------------> | | <+........................ | rename.apply_failure_label_asserted | | | |
| | | +------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ | | |
| | | | | | | | | |
| rename.apply_fail | | +-------------------------------------------------------------+ | rename.apply_failure | | +---------------------------------------------------------------+ | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
| | | | +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| rename.applying | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------|---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | v | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| rename.kind_preserved_by_rename | .+.......................> | rename.kind_unchanged | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| ^ | | | | | | |
| +-------------------------------------------------------------| | | | | | |
| | | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | | | | | |
| rename.validate | -+- - - - - - - - - - - -> | rename.new_id_available | -+---------------------------+--------------------------------------------------------------| | rename | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | | |
| | ^ | | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| rename.checked | | | | | | | | | |
+-------------------------------------------------------------+ | | | | | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
^ | | | | | ^ | |
+--------------------------------------------------------------+ | |---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| rename.filename_mismatch_rejected | .+.......................> | rename.new_id_matches_filename | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| rename.requested | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| v | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| rename.stray_ref_caught_by_verify | .+.......................> | | -+........................ | rename.clean_rename_passes | | | | |
+-------------------------------------------------------------+ | | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
+--------------------------------------------------------------+ | rename.old_id_fully_replaced | | +--------------------------------------------------------------+ | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------| | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| rename.prose_mention_left_alone | .+.......................> | rename.prose_untouched_by_rename | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ +---------------------------------------------------------------+---------------------------+---------------------------------------------------------------------------------------------+ |
| | | | | v | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ |tates.emits +--------------------------------------------------------------+ | | +-----------------------------------------------+ |
| | | rename.reject | -+-----------------------> | rename.check_failed | -+-----------------------> | | | | | rename.post_check_failure_label_asserted | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | | | +-----------------------------------------------+ |
| | | | | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | rename.post_check_failure | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------| +-----------------------------------------------------------------+ |
| | | | | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ |tates.emits +--------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------+ | |
| | | rename.validate_fail | -+-----------------------> | rename.validate_failed | -+-----------------------> | | <+........................ | rename.validation_failure_label_asserted | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | | +----------------------------------------------------------------+ | |
| | | | | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | rename.validation_failure | | +-----------------------------------------------------------------+ |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ |--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | |
+-------------------------------------------------------------------------------------------------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | | | |
| | -+-----------------------> | verify.failed | | | | |
| | | +-------------------------------------------------------------+ | | | |
| | | | | | |
| verify.reject | | +--------------------------------------------------------------+ | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
+-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------+ +---------------------------+---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| | v | | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ |ransitions.guard +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |ransitions.to +----------------------------------------------------------------+ | |
| verify.single_gate_insufficient | .+.......................> | | -+ - - - - - - - - - - - - | | -+-----------------------> | verify.verified | | |
+-------------------------------------------------------------+ | | | | | | | +----------------------------------------------------------------+ | |
| | | | | | | | |
+--------------------------------------------------------------+ | verify.both_gates_required | | | verify.accept | | +-----------------------------------------------------------------+ |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| | ^ | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+ +---------------------------+---------------------------------------------------------------+ | |
| v | | | | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | | | | |
| verify.swapped_scope_report_rejected | .+.......................> | | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | verify.evidence_scope_bound | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------| | | | |
| v | | | | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | | | | |
| verify.partial_law_case_rejected | .+.......................> | verify.law_cases_unexecuted | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| +-------------------------------------------------------------+---------------------------| | | | |
| v | | | | | |
+-------------------------------------------------------------+ transitions.to +------------------------------------------------------------+ | | | | | |
| verify.evaluate | -+-----------------------> | verify.properties_evaluated | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
|-transitions.from---------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ +---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ transitions.guard +--------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------+ | |
| verify.running | | | | | verify.begin | -+- - - - - - - - - - - -> | verify.blocks_run_to_completion | <+........................ | verify.partial_run_rejected | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | +----------------------------------------------------------------+ | |
| | | | | | | |
| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ +---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| v | | | | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | | | | |
| verify.cached_result_rejected | .+.......................> | verify.properties_pass_reflects_latest_run | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| +-------------------------------------------------------------+---------------------------| | | | |
| v | | | | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | | | | |
| verify.malformed_records_rejected | .+.......................> | | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | verify.required_claims_govern_acceptance | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | -+-----------------------> | acset.writer.apply_failed | -+-----------------------> | acset.writer.apply_failure | .+.......................> | | |
| | | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
| acset.writer.apply_fail | | +-------------------------------------------------------------+ +--------------------------------------------------------------| | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | | | | | acset.writer | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | | | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| acset.writer.spanned | | | | | | | | |
+-------------------------------------------------------------+ | | | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
^ | | | | | ^ | |
| |---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| | v | | | | | |
+-|-----------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| | acset.writer.intent_rename_moves_file | .+.......................> | acset.writer.filename_follows_intent_id | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-|-----------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| | | | | | | |
+-|-----------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | acset.writer.writer_edit_law | .+.......................> | acset.writer.edits_compose | -| | acset.writer.apply_failure_label_asserted | | | | |
+-|-----------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | ^ | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| v | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| acset.writer.prose_survives_edit | .+.......................> | | -+........................ | acset.writer.crlf_survives_edit | | | | |
+-------------------------------------------------------------+ | | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
+--------------------------------------------------------------+ | acset.writer.untouched_bytes_preserved | | +--------------------------------------------------------------+ | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------| | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.writer.padding_not_realigned | .+.......................> | acset.writer.width_padding_policy | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
|---------------------------+-------------------------------------------------------------| | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.writer.writer_does_no_io | .+.......................> | acset.writer.write_set_atomic | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
| +-------------------------------------------------------------| +--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | -+-----------------------> | compile.extract_failed | -+-----------------------> | | -+.......................> | | |
| | | +------------------------------------------------------------+ | | | | | | |
| | | | | | | | | |
| compile.extract_fail | | +-------------------------------------------------------------+ | compile.extraction_failure | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| | | | ^ | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ +--------------------------------------------------------------| | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.unknown_tag_labeled | .+.......................> | compile.unknown_tag_rejected | -+---------------------------+--------------------------------------------------------------| | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| compile.extracting | | | | | | | compile | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
|--------------------------------------------------------------|---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------| | | |
| | v | | | | | |
+-|-----------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | compile.tla_disjunct_count_matches | .+.......................> | | -| | compile.extraction_failure_label_asserted | | | | |
+-|-----------------------------------------------------------+ | | | | +-------------------------------------------------------------+ | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | compile.model_to_tla | | +--------------------------------------------------------------| | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
| | +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | | ^ | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| | v | | | | | |
+-|-----------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| | compile.toml_roundtrips | .+.......................> | compile.constraint_table_to_toml | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-|-----------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------| | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.proptest_block_per_property | .+.......................> | | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | compile.properties_to_proptest | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ |onstraints.traces_to +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| linter.coverage.coverage_naturality | .+.......................> | specodelic.rename_naturality | .+.......................> | specodelic.unique_id | .+.......................> | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| ^ | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------| | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | specodelic | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| specodelic.failing_proptest_blocks_verify | .+.......................> | specodelic.properties_pass | -+---------------------------+--------------------------------------------------------------| | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| ^ | | | ^ |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+ |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | linter.referential_integrity.rename_naturality | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------| | +--------------------------------------------------------------+ | | |
| | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| specodelic.verify | -+- - - - - - - - - - - -> | specodelic.no_counterexample | -+........................ | specodelic.invariant_violation_blocks_verify | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| specodelic.model_checked | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+ | | | | | | |
| | | | | | | | |
| | | | | | | | |
|--------------------------------------------------------------+ | | | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| specodelic.verified | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | linter.external_completeness.mapping_naturality | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | linter.schema_shape.kind_check_failure_label_asserted | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
| | | | +--------------------------------------------------------------| | | |
| | | | | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | merge.merge_aborted_label_asserted | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +---------------------------+ +-----------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |tates.emits +-------------------------------------------------------------+ |onstraints.traces_to +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| refactor.flag | -+-----------------------> | refactor.found | -+-----------------------> | refactor.advisory_finding_emitted | .+.......................> | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | | |
| | ^ | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------| | refactor | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | | | | | ^ | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | +---------------------------+ +-----------------------------------------------------------------+ |
| v | | | | | |
+-------------------------------------------------------------+ |ransitions.from +------------------------------------------------------------+ | | | | | |
| refactor.clear | -+-----------------------> | refactor.analyzing | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
| transitions.to | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| refactor.clean | | | | | refactor.emitted_finding_shape | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| | | | | | |
|---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| refactor.narrow_diff_flagged_low_fan_in | .+.......................> | refactor.narrow_diff_heuristic | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
|---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ | |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| refactor.unrelated_fan_in_flagged | .+.......................> | refactor.unrelated_fan_in_defined | -+---------------------------+--------------------------------------------------------------+ | refactor.related_fan_in_not_flagged | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | | | |
| | | acset.core.archive | -+-----------------------> | acset.core.archived | | | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| | v | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | acset.core.implemented | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.attributes_untouched | .+.......................> | acset.core.cells_carried | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.blast_radius_parity | .+.......................> | acset.core.blast_radius_defined | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| acset.core.closure_laws_hold | .+.......................> | acset.core.single_traversal_primitive | -+........................ | acset.core.no_private_adjacency | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.closure_on_cycle_terminates | .+.......................> | acset.core.closure_terminates | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.dangling_is_a_value | .+.......................> | acset.core.morphisms_partial | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.dangling_not_followed_in_traversal | .+.......................> | acset.core.dangling_not_followed | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| acset.core.duplicate_id_first_wins_parity | .+.......................> | acset.core.ids_interned | <+........................ | acset.core.index_roundtrip | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.duplicate_morphism_rejected | .+.......................> | acset.core.morphisms_typed | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.emits_refinement_enforced | .+.......................> | acset.core.refinement_declared | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| acset.core.endo_cycle_detected | .+.......................> | acset.core.endo_acyclicity_flagged | <+........................ | acset.core.unflagged_endo_cycle_tolerated | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.fan_in_matches_preimage | .+.......................> | acset.core.fan_in_is_preimage_size | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.forbidden_edge_not_stored | .+.......................> | acset.core.typing_enforced_at_build | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.graph_parity | .+.......................> | acset.core.adapter_graph_equivalent | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.no_link_dropped | .+.......................> | acset.core.adapter_total | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.order_is_canonical | .+.......................> | acset.core.canonical_order | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.rebuild_is_byte_stable | .+.......................> | acset.core.build_deterministic | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.results_sorted_by_id | .+.......................> | acset.core.results_ordered | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.schema_drift_detected | .+.......................> | acset.core.schema_matches_typing_table | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.sixth_object_rejected | .+.......................> | acset.core.objects_closed | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.supersedes_scope_exact | .+.......................> | acset.core.scope_by_morphism_set | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | +-------------------------------------------------------------+ |ransitions.to +--------------------------------------------------------------+ |tates.emits +----------------------------------------------------------------+ |roperties.derives_from +-----------------------------------------------+ |
| acset.core.typing_read_from_schema | .+.......................> | acset.core.typing_table_is_data | -| | acset.writer.reparse_fail | -+-----------------------> | acset.writer.roundtrip_failed | -+-----------------------> | acset.writer.roundtrip_failure | <+........................ | acset.writer.roundtrip_failure_label_asserted | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | +----------------------------------------------------------------+ | +-----------------------------------------------+ |
| | | | | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.core.unknown_seed_rejected | .+.......................> | acset.core.seeds_exist | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| | | | v | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | | | |
| | | | -+-----------------------> | acset.writer.applied | | | | |
| | | | | +-------------------------------------------------------------+ | | | |
| | | | | ^ | | | |
| | | acset.writer.apply | | +--------------------------------------------------------------+ | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| v | | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ |ransitions.from +----------------------------------------------------------------+ | |
| acset.writer.identity_emission_exact | .+.......................> | acset.writer.emit_identity | -+---------------------------+--------------------------------------------------------------| | acset.writer.record_fail | -+-----------------------> | acset.writer.requested | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ | +----------------------------------------------------------------+ | |
| ^ | | | | | ^ | |
| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------| +-----------------------------------------------------------------+ |
| v | | | v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | +-------------------------------------------------------------+ |ransitions.guard +--------------------------------------------------------------+ | | |
| acset.writer.span_covers_every_link | .+.......................> | acset.writer.source_spans_recorded | -| | acset.writer.reparse | -+- - - - - - - - - - - -> | acset.writer.edit_application_faithful | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| ^ | | | ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------| +-----------------------------------------------------------------+ |
| | | | | | v | | | |
| | | | | | +--------------------------------------------------------------+ |tates.emits +----------------------------------------------------------------+ |roperties.derives_from +-----------------------------------------------+ |
| | | | | | | acset.writer.span_failed | -+-----------------------> | acset.writer.span_failure | <+........................ | acset.writer.span_failure_label_asserted | |
| | | | | | +--------------------------------------------------------------+ | +----------------------------------------------------------------+ | +-----------------------------------------------+ |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | +---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| acset.writer.process_lifecycle_checked | .+.......................> | acset.writer.process_lifecycle | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | acset.writer.record | -+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------| |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
| | +-------------------------------------------------------------+ | | +---------------------------------------------------------------+ | |
| | | | | | | | | |
| | | | | | +--------------------------------------------------------------+ | | |
| | | | | | | acset.writer.text_realizes_instance | | | |
| | | | | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
| | | |---------------------------+--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | v | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | acset.writer.verified | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+------------------------------------------------------------------------------------------+ +-------------------------------------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| archive.companion.ambiguous_dir_reported | .+.......................> | archive.companion.newest_archive_wins | .+.......................> | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
| | | | | |
| | | | | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | |
| archive.companion.shape_assertion_enforced | .+.......................> | archive.companion.attach_shape_asserted | -+------------------------>| | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
| ^ | | | | |
| +-------------------------------------------------------------| | archive.companion | | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | |
| archive.companion.converged_rerun_skipped | .+.......................> | archive.companion.converged_sidecarless_recognized | -+------------------------>| | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
| | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | | | ^ | | |
| | | +---------------------------+--------------------------------------------------------------+ +-------------------------------------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ | | |
| | | | v | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ |ransitions.from +-------------------------------------------------------------+ | | | |
| archive.companion.locate | | | | -+-----------------------> | archive.companion.archived | | | | |
+-------------------------------------------------------------+ | | | | +-------------------------------------------------------------+ | | | |
| | | | | ^ | | | |
+--------------------------------------------------------------+ | archive.companion.deploy | | +--------------------------------------------------------------+ | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | |---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| | | | | | | | | |
| | | | +-------------------------------------------------------------+ |ransitions.guard +--------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------+ | |
| | | | | archive.companion.reject | -+- - - - - - - - - - - -> | archive.companion.fail_closed_unverifiable | <+........................ | archive.companion.plain_delta_refused | | |
| | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | +----------------------------------------------------------------+ | |
| | | | | | | | |
| | | | | transitions.to | +---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| | | | v | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | archive.companion.refused | | | | |
| | | | +-------------------------------------------------------------+ | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| archive.companion.deployed_assembly_matches | .+.......................> | archive.companion.layer_preserved | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | v | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | archive.companion.restored | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| archive.companion.docs_only_change_succeeds | .+.......................> | archive.companion.empty_restore_valid | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| archive.companion.dry_run_writes_nothing | .+.......................> | archive.companion.dry_run_never_mutates | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
| archive.companion.openspec_not_forked | -+---------------------------+-------------------------------------------------------------| | archive.companion.missing_openspec_labeled | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | | | |
^ | | | | | | |
+--------------------------------------------------------------+ | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| archive.companion.no_bespoke_merge | | | bioimage.data.pack.orphan_names_bioimage_pack | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| archive.companion.resolved | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
| +-------------------------------------------------------------+ | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| archive.companion.process_lifecycle_checked | .+.......................> | archive.companion.process_lifecycle | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| archive.companion.rerun_byte_identical | .+.......................> | archive.companion.idempotent_rerun | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| +-------------------------------------------------------------|---------------------------+--------------------------------------------------------------+ +-------------------------------------------------------------------------------------------+-----------------------------------------------------------------+ |
| v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| bioimage.data.pack.consumer_activates_advisory | .+.......................> | bioimage.data.pack.axes_section_shape | .+.......................> | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
| ^ | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | |
| | | | | | | |
| | | | | | | |
| | | | | bioimage.data.pack | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | | | ^ | |
| | | +---------------------------+--------------------------------------------------------------+ +-------------------------------------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | | |
| | +------------------------------------------------------------+ |ransitions.from +-------------------------------------------------------------+ | | | |
| | | bioimage.data.pack.publish | -+-----------------------> | bioimage.data.pack.draft | | | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| bioimage.data.pack.pack_shape_clean_all_states | .+.......................> | | -+---------------------------| | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | bioimage.data.pack.pack_file_declared | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| bioimage.data.pack.transform_floor_labels | .+.......................> | bioimage.data.pack.transform_floor_kind_dependent | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | v | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | bioimage.data.pack.published | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
| | +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | | | |
| | | bioimage.data.pack.deprecate | -+-----------------------> | bioimage.data.pack.deprecated | | | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | bioimage.data.pack.discovery_finds_bioimage_pack | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| bioimage.data.pack.kind_column_accepts_fiber_kinds | .+.......................> | bioimage.data.pack.kind_column_typeable | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | bioimage.data.pack.law_floor_unchanged | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| bioimage.data.pack.no_bare_word_vocabulary | .+.......................> | bioimage.data.pack.vocabulary_prose_safe | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| bioimage.data.pack.predicates_honest_empty | .+.......................> | bioimage.data.pack.predicates_closed_named | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| bioimage.data.pack.process_lifecycle_checked | .+.......................> | bioimage.data.pack.process_lifecycle | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| bioimage.data.pack.requires_deps_resolve | .+.......................> | bioimage.data.pack.requires_declares_pack_deps | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| bioimage.data.pack.same_shape_as_resolves | .+.......................> | bioimage.data.pack.same_shape_as_cross_pack | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+ +-----------------------------------------------------------------+ |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ |ransitions.to +--------------------------------------------------------------+ |tates.emits +----------------------------------------------------------------+ |roperties.derives_from +-----------------------------------------------+ |
| compile.accept | -+- - - - - - - - - - - -> | compile.compile_preserves_ids | <+ - - - - - - - - - - - - | compile.reject | -+-----------------------> | compile.emit_failed | -+-----------------------> | | <+........................ | compile.emission_failure_label_asserted | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | | | +-----------------------------------------------+ |
| | ^ | | | | | | | |
|-transitions.from---------------------------------------------| +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | compile.emission_failure | | |
v | | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | | |
| compile.emitting | | | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | +----------------------------------------------------------------+ | |
^ | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------| | | +-----------------------------------------------------------------+ |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------| | | | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.roundtrip_stable | .+.......................> | compile.no_semantic_drift | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| compile.compiled | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| compile.begin | -+- - - - - - - - - - - -> | compile.precondition_satisfied | <+........................ | compile.precondition_violation_rejected | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | | |
| transitions.from | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| compile.not_started | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| compile.binding_text_surfaces_verbatim | .+.......................> | compile.kernel_binding_opaque | -+........................ | compile.claim_path_is_flags | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.compile_completes_or_fails_cleanly | .+.......................> | compile.compile_is_total | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | compile.extract_ok | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| compile.fragment_body_emitted_verbatim | .+.......................> | compile.predicate_fragment_opt_in | <+........................ | compile.prose_predicate_stays_placeholder | | | compile.fragment_values_bind_generators | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.guard_fragment_labeled | .+.......................> | compile.fragment_guard_rejected | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.hygiene_violation_labeled | .+.......................> | compile.fragment_hygiene | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | compile.id_preservation_holds | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| compile.kernel_expr_extracts | .+.......................> | compile.kernel_expr_opt_in | <+........................ | compile.kernel_nonmember_labeled | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.law_fragment_labeled | .+.......................> | compile.fragment_law_rejected | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | compile.law_property_compiles_required_cases | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.missing_emitter_labeled | .+.......................> | compile.no_emitter_labeled_failure | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.non_fragment_tag_is_mention | .+.......................> | compile.mention_not_extraction | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.non_invariant_fragment_labeled | .+.......................> | compile.invariant_fragment_opt_in | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.one_tag_extracts_per_cell | .+.......................> | compile.fragment_language_closed | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | compile.output_function_covers_emitting_states_only | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| compile.rust_fragment_semantics_unchanged | .+.......................> | compile.rust_back_compat | -+---------------------------+--------------------------------------------------------------+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| |---------------------------+--------------------------------------------------------------+ +-------------------------------------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |onstraints.traces_to +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| conform.classify_claims | -+- - - - - - - - - - - -> | conform.prose_never_checked | .+.......................> | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
| | ^ | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | |
v | | | | | | |
+-------------------------------------------------------------+ | | | | | | |
| conform.artifacts_gated | | | | | conform | | |
+-------------------------------------------------------------+ | | | | | | |
^ | | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | |
v | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
| conform.claims_classified | | | conform.unsupported_kind_named | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
^ | | ^ | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ +-------------------------------------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | | | | |
| conform.classify_traces | -+- - - - - - - - - - - -> | conform.verdict_taxonomy_distinct | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
| transitions.to | +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| conform.traces_classified | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | conform.taxonomy_is_total_and_distinct | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| conform.closed_world_forbidden_recorded | .+.......................> | conform.closed_world_gated | <+........................ | conform.uncovered_trace_never_forbidden | | | conform.contradiction_forbidden_any_mode | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| conform.consumes_never_writes | .+.......................> | conform.read_only_evaluation | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| conform.digest_binds_scenarios | .+.......................> | | -+........................ | conform.rerun_byte_identical | | | conform.report_schema_roundtrip | | | |
+-------------------------------------------------------------+ | | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | | | |
+--------------------------------------------------------------+ | conform.verdict_report_schema | | +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------+---------------------------+ | | | | | |
| | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | | | |
| conform.emit_report | -+-----------------------> | conform.reported | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| conform.exit_codes_match_verdicts | .+.......................> | conform.exit_code_verdict_mapping | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------| | +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| conform.gate_artifacts | -+- - - - - - - - - - - -> | conform.artifact_gate | <+........................ | conform.stale_artifacts_refused | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | ^ | | | | |
| transitions.from | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| conform.input_read | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | conform.lint_dirty_refused | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| conform.outside_lifecycle | .+.......................> | conform.lifecycle_outside | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | conform.prose_yields_unknown | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ |---------------------------+ +---------------------------+---------------------------------------------------------------+ | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| data.lineage.pack.binding_opaque_to_lint | .+.......................> | data.lineage.pack.binding_stays_external | .+.......................> | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | data.lineage.pack | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| | | | ^ | | |
| | | +---------------------------+ +---------------------------+---------------------------------------------------------------+ | |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | data.lineage.pack.no_base_set_absorption | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | | | | |
| data.lineage.pack.publish | -+- - - - - - - - - - - -> | data.lineage.pack.data_section_row_shape | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| data.lineage.pack.draft | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
|---------------------------+-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| v | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ |ransitions.to +--------------------------------------------------------------+ | | |
| data.lineage.pack.pack_shape_clean_all_states | .+.......................> | | -+ - - - - - - - - - - - - | data.lineage.pack.deprecate | -+-----------------------> | data.lineage.pack.deprecated | | | |
+-------------------------------------------------------------+ | | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | | | | |
+--------------------------------------------------------------| | data.lineage.pack.pack_file_declared | | +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| data.lineage.pack.published | | | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
^ | ^ | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------| | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| data.lineage.pack.closure_honest_empty | .+.......................> | data.lineage.pack.lineage_edges_outbound | <+........................ | data.lineage.pack.lineage_reference_resolves | | | data.lineage.pack.discovery_finds_lineage_pack | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | data.lineage.pack.consumer_activates_advisory | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
| | +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | data.lineage.pack.orphan_names_lineage_pack | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| data.lineage.pack.process_lifecycle_checked | .+.......................> | data.lineage.pack.process_lifecycle | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| |---------------------------+ +---------------------------+---------------------------------------------------------------+ | |
| | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| docs.site.docs_build | -+- - - - - - - - - - - -> | docs.site.site_present | .+.......................> | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | ^ | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | docs.site | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| docs.site.authored | | | | | | | | |
+-------------------------------------------------------------+ | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| | | ^ | | |
+--------------------------------------------------------------+ | +---------------------------+ +---------------------------+---------------------------------------------------------------+ | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| docs.site.built | | | docs.site.p_site | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
^ | | | | | | |
+--------------------------------------------------------------| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| docs.site.process_lifecycle_checked | .+.......................> | docs.site.process_lifecycle | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| docs.site.docs_deploy | -+- - - - - - - - - - - -> | docs.site.pages_deployed | -+........................ | docs.site.p_deploy | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | | |
| transitions.to | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| docs.site.deployed | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
| +-------------------------------------------------------------+ | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| docs.site.p_llms | .+.......................> | docs.site.llms_txt_served | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| docs.site.p_specs | .+.......................> | docs.site.specs_in_book | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |onstraints.traces_to +-------------------------------------------------------------+ | | | |
| doctor.classify | -+- - - - - - - - - - - -> | doctor.mode_detection | .+.......................> | doctor | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | ^ | ^ | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| doctor.classified | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | doctor.p_mode | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| doctor.process_lifecycle_checked | .+.......................> | doctor.process_lifecycle | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| doctor.probed | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| doctor.currency | -+- - - - - - - - - - - -> | doctor.currency_warning | <+........................ | doctor.p_currency | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | | |
| transitions.to | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| doctor.currency_checked | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
| +-------------------------------------------------------------+---------------------------+ +---------------------------+---------------------------------------------------------------+ | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| embedded.guide.p_drift | .+.......................> | embedded.guide.drift_guard | .+.......................> | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | embedded.guide | | | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | | | | |
| embedded.guide.resolve | -+- - - - - - - - - - - -> | embedded.guide.explain_topics | -+------------------------>| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| | ^ | ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+ +---------------------------+---------------------------------------------------------------+ | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ | | | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| embedded.guide.queried | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
+--------------------------------------------------------------| | | | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| embedded.guide.resolved | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
^ | | | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| embedded.guide.p_revision | .+.......................> | embedded.guide.revision_pinned | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | | | |
| embedded.guide.render | -+-----------------------> | embedded.guide.rendered | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| embedded.guide.p_scaffold | .+.......................> | embedded.guide.scaffold_teaches | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | embedded.guide.p_topics | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| embedded.guide.process_lifecycle_checked | .+.......................> | embedded.guide.process_lifecycle | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| |---------------------------+ +---------------------------+---------------------------------------------------------------+ | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| empirical.registry.pack.closure_honest_empty | .+.......................> | empirical.registry.pack.tested_by_outbound | .+.......................> | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | empirical.registry.pack | | | |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | empirical.registry.pack.tested_by_resolves | | | | | | |
| | +------------------------------------------------------------+ | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| | | ^ | | |
| | +-------------------------------------------------------------+---------------------------+ +---------------------------+---------------------------------------------------------------+ | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| empirical.registry.pack.consumer_activates_advisory | .+.......................> | empirical.registry.pack.stat_test_section_shape | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------| | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | empirical.registry.pack.orphan_names_empirical_pack | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ |ransitions.from +--------------------------------------------------------------+ | | |
| empirical.registry.pack.deprecate | -+- - - - - - - - - - - -> | | <+ - - - - - - - - - - - - | empirical.registry.pack.publish | -+-----------------------> | empirical.registry.pack.draft | | | |
+-------------------------------------------------------------+ | | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | | | | | |
+--------------------------------------------------------------| | empirical.registry.pack.pack_file_declared | | +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | | |
| empirical.registry.pack.deprecated | | | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------| | |
| | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
v | | | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| empirical.registry.pack.published | | | | | empirical.registry.pack.discovery_finds_empirical_pack | | | empirical.registry.pack.pack_shape_clean_all_states | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| empirical.registry.pack.kind_column_accepts_fiber_kinds | .+.......................> | empirical.registry.pack.fiber_kinds_typeable | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| empirical.registry.pack.law_floor_unchanged | .+.......................> | empirical.registry.pack.floors_are_additive | <+........................ | empirical.registry.pack.statistic_floor_labels | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| empirical.registry.pack.no_bare_word_vocabulary | .+.......................> | empirical.registry.pack.vocabulary_prose_safe | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| empirical.registry.pack.process_lifecycle_checked | .+.......................> | empirical.registry.pack.process_lifecycle | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| +---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |onstraints.traces_to +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| error.contract.approve | -+- - - - - - - - - - - -> | | .+.......................> | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | | |
+--------------------------------------------------------------| | error.contract.process_lifecycle | | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| error.contract.approved | | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
^ | ^ | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | error.contract | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | | |
+-------------------------------------------------------------+ | | | | | | |
| error.contract.implement | -+---------------------------| | | | | |
+-------------------------------------------------------------+ | | | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | | ^ | |
+--------------------------------------------------------------+ | +---------------------------+--------------------------------------------------------------+---------------------------+ +----------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | error.contract.process_lifecycle_checked | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| error.contract.zero_citation_failure_flagged | .+.......................> | error.contract.guard_negation_typed | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | | | |
v | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| error.contract.proposed | | | | | | | | |
+-------------------------------------------------------------+ | | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | | | |
| | | error.contract.archive | -+-----------------------> | error.contract.archived | | | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ | | |
| | v | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | error.contract.implemented | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
| | +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | error.contract.carved_out_guard_not_flagged | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| error.contract.contract_satisfied_from_consumer | .+.......................> | error.contract.contract_published | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| error.contract.emits_wrong_kind_rejected | .+.......................> | error.contract.failure_state_emits | <+........................ | error.contract.mute_failure_state_detected | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | |
| | +--------------------------------------------------------------+ | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| error.contract.envelope_kind_wrong_rejected | .+.......................> | error.contract.envelope_error_kind | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| error.contract.exit_code_mismatch_rejected | .+.......................> | error.contract.exit_code_mapping | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| error.contract.hint_missing_rejected | .+.......................> | error.contract.remediation_hint_present | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| error.contract.label_property_asserts_exact_label | .+.......................> | error.contract.error_property_names_label | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| error.contract.labeled_failure_is_single | .+.......................> | error.contract.single_labeled_failure | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| error.contract.multiclass_single_state_detected | .+.......................> | error.contract.failure_class_is_state | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| error.contract.routing_matches_reality | .+.......................> | error.contract.enforcement_routed | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| error.contract.unnamespaced_label_rejected | .+.......................> | error.contract.error_expr_shape | -+---------------------------| | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | | |
| | | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | error.contract.untyped_negation_detected | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+ | |
| v | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| errors.carved_out_guard_not_flagged | .+.......................> | errors.guard_negation_typed | <+........................ | errors.zero_citation_failure_flagged | | | errors.untyped_negation_detected | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| errors.contract_satisfied_from_consumer | .+.......................> | errors.contract_published | -+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| ^ | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ | | |
| | | | | | | |
| | +------------------------------------------------------------+ |ransitions.from +-------------------------------------------------------------+ | | | |
| | | errors.publish | -+-----------------------> | errors.draft | | | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | | | |
| | | transitions.to | +--------------------------------------------------------------+ | | |
| | v | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | errors.published | | | | | | |
| | +------------------------------------------------------------+ | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ | | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | | |
| errors.emits_wrong_kind_rejected | .+.......................> | errors.failure_state_emits | <+........................ | errors.mute_failure_state_detected | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+ | |
| | | | | | | | | |
| | | | | | +--------------------------------------------------------------+ | | |
| | | | | | | errors.envelope_kind_wrong_rejected | | | |
| | | | | | +--------------------------------------------------------------+ | | |
| | | | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | +---------------------------------------------------------------+ | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| errors.label_property_asserts_exact_label | .+.......................> | errors.error_property_names_label | -+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| errors.labeled_failure_is_single | .+.......................> | errors.single_labeled_failure | -+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| errors.multiclass_single_state_detected | .+.......................> | errors.failure_class_is_state | -+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| errors.routing_matches_reality | .+.......................> | errors.enforcement_routed | -+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| errors.unnamespaced_label_rejected | .+.......................> | errors.error_expr_shape | -+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
| | | | | |
| | +--------------------------------------------------------------+ | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | | |
| graph.blast_radius_bidirectional | .+.......................> | graph.blast_radius_is_transitive_closure | <+........................ | graph.blast_radius_transitive | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | |
| | | | |
| | +--------------------------------------------------------------+ | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | |
| graph.boundary_tracks_extension_points | .+.......................> | graph.external_boundary_derived | -+---------------------------+--------------------------------------------------------------+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
| | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | | |
| | | | | | | |
| | +------------------------------------------------------------+ | | | | |
| | | graph.every_edge_extracted | | | | | |
| | +------------------------------------------------------------+ | | | | |
| | | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ | |
| | | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | | |
| | | graph.extract_ok | -+-----------------------> | graph.indexed | | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | |
| | | ^ | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ | |
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | |
| graph.hand_authored_edge_rejected | .+.......................> | graph.graph_is_derived_not_authored | -+---------------------------+--------------------------------------------------------------+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
| ^ | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | +-------------------------------------------------------------+ | | | |
| | | | | | | |
| | +------------------------------------------------------------+ | | | | |
| | | graph.publish | -+---------------------------| | | |
| | +------------------------------------------------------------+ | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | transitions.guard | | | | |
| v | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | |
| graph.rerun_deterministic | .+.......................> | graph.deterministic_derivation | -+---------------------------+--------------------------------------------------------------+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | |
| | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ +---------------------------------------------------------------+ |
| | | | v | | | |
| | +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ |ransitions.from +--------------------------------------------------------------+ |ransitions.guard +----------------------------------------------------------------+ properties.derives_from +-----------------------------------------------+
| | | refactor.analyze | -+- - - - - - - - - - - -> | graph.queryable | <+------------------------ | graph.invalidate | -+- - - - - - - - - - - -> | graph.stale_graph_detected | <......................... | graph.stale_detected_on_edit |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | +----------------------------------------------------------------+ +-----------------------------------------------+
| | | | | |
| | | transitions.from | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | v | | | |
| | +------------------------------------------------------------+ | | | |
| | | refactor.idle | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ |---------------------------+--------------------------------------------------------------+ +-------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |onstraints.traces_to +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| graph.views.approve | -+- - - - - - - - - - - -> | | .+.......................> | | |
+-------------------------------------------------------------+ | | | | | | |
| | | | | | | |
+--------------------------------------------------------------| | graph.views.process_lifecycle | | | | |
v | | | | | | |
+-------------------------------------------------------------+ | | | | | | |
| graph.views.approved | | | | | | graph.views | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
^ | ^ | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | ^ |
| | | +---------------------------+--------------------------------------------------------------+ +-------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+--------------------------------------------------------------+ | | | | |
| | | | | | |
+-------------------------------------------------------------+ | | | | | |
| graph.views.implement | -+---------------------------| | | | |
+-------------------------------------------------------------+ | | | | | |
| | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | graph.views.process_lifecycle_checked | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ | | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| graph.views.violations_survive_projection | .+.......................> | graph.views.violations_annotated | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| ^ | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | |
v | | | | | |
+-------------------------------------------------------------+ | | | | | |
| graph.views.proposed | | | | | | |
+-------------------------------------------------------------+ | | | | | |
| | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | graph.views.archive | -+-----------------------> | graph.views.archived | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | v | | | |
| | +------------------------------------------------------------+ | | | |
| | | graph.views.implemented | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ | | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| graph.views.build_output_not_committed | .+.......................> | graph.views.build_time_generation | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | graph.views.clean_corpus_no_annotations | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| graph.views.empty_corpus_projection | .+.......................> | graph.views.empty_corpus_valid | <+........................ | graph.views.single_intent_sane | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| | +--------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| graph.views.ids_never_labels | .+.......................> | graph.views.canonical_ids | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
| | | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| graph.views.out_of_scope_refused | .+.......................> | graph.views.corpus_scope_operational | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
| | | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| graph.views.rerun_byte_identical | .+.......................> | graph.views.deterministic_projection | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
| |---------------------------+--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| graph.views.schema_view_revision_labeled | .+.......................> | graph.views.derived_views_only | <+........................ | graph.views.views_from_artifact_only | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| | +---------------------------+---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| hooks.additive_wiring | .+.......................> | hooks.chain_preserved | .+.......................> | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| ^ | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | hooks | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | ^ |
| | | |---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+
| | | | | | |
| | | | | | |
| | +-------------------------------------------------------------+---------------------------+ +---------------------------+---------------------------------------------------------------+
| | | | |
| | +------------------------------------------------------------+ |ransitions.to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | hooks.uninstall_ok | -+-----------------------> | | |
| | +------------------------------------------------------------+ | | | |
| | | | | | |
| | +-------------------------------------------------------------+ | hooks.unwired | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | ^ |
| | +-------------------------------------------------------------+---------------------------+ +---------------------------+---------------------------------------------------------------+
| | v | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | hooks.wired | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | +------------------------------------------------------------+ | | | |
| | ^ | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------|
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| hooks.unsupported_framework | .+.......................> | hooks.honest_anchor | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| ^ | | | |
| +-------------------------------------------------------------| | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| hooks.clean_uninstall | .+.......................> | | -+---------------------------| | |
+-------------------------------------------------------------+ | | | | | | |
| | | | | | |
+--------------------------------------------------------------+ | hooks.idempotent_lifecycle | | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | | | | | |
| | | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | | <+------------------------ | hooks.uninstall_notwired | | | hooks.reinstall_ok | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | ^ | | | |
| | +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+
| | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| hooks.foreign_refused | .+.......................> | hooks.no_foreign_writes | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | +-------------------------------------------------------------+ | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | hooks.idempotent_install | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | hooks.install_ok | -+---------------------------| | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ | | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| hooks.process_lifecycle_checked | .+.......................> | hooks.process_lifecycle | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------|---------------------------+--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | hooks.refuse | -+-----------------------> | hooks.refused | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
+--------------------------------------------------------------+ | +---------------------------+---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| kernel.acyclic_decides | .+.......................> | kernel.kernel_grounded_in_machinery | .+.......................> | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
| | | kernel | |
| | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | | |
| kernel.widen | -+- - - - - - - - - - - -> | kernel.kernel_decidable | -+------------------------>| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | ^ | ^ | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+
| | | | | | |
| | | | | | |
+--------------------------------------------------------------+ | | | | |
v | | | | | |
+-------------------------------------------------------------+ | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| kernel.expr_evaluated | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
+-------------------------------------------------------------+ | | | | | |
^ | | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------| | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| kernel.binding_never_interpreted | .+.......................> | kernel.kernel_binding_opaque | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | | | | |
| | | | +-------------------------------------------------------------+ |ransitions.guard +--------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------+
| | | | | kernel.discharge | -+- - - - - - - - - - - -> | kernel.kernel_status_three_valued | <+........................ | kernel.unknown_propagates |
| | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | +----------------------------------------------------------------+
| | | | | | |
| | | | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| kernel.declare | -+- - - - - - - - - - - -> | kernel.kernel_grammar_closed | <+........................ | kernel.unknown_atomic_fails | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
| transitions.from | +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
v | | | | | |
+-------------------------------------------------------------+ | | | | | |
| kernel.expr_absent | | | | | | |
+-------------------------------------------------------------+ | | | | | |
| | | | | |
+--------------------------------------------------------------+ | | +--------------------------------------------------------------+ |
| | | | v | |
| | | | +-------------------------------------------------------------+ | |
| | | | | kernel.expr_unknown | | |
| | | | +-------------------------------------------------------------+ | |
| | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| kernel.process_lifecycle_checked | .+.......................> | kernel.process_lifecycle | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | kernel.quantifier_bounded | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------| +---------------------------------------------------------------+
| | | | |
| | +-------------------------------------------------------------------------------------------------------------------------------------------------------+ |ransitions.to +--------------------------------------------------------------+ |
| | | | -+-----------------------> | kinds.passed | |
| | | | | +--------------------------------------------------------------+ |
| | | | | |
| | | kinds.accept | | +---------------------------------------------------------------+
| | | | | |
| | | | | |
| | | | | |
| | +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | | |
| |---------------------------+ +---------------------------+--------------------------------------------------------------+ |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | kinds.assign_ok | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | kinds.constraint_row_advisory_accepted | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | kinds.constraint_row_effect_accepted | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | kinds.constraint_row_extension_point_accepted | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | kinds.kind_shape_naturality | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | kinds.state_row_emits_accepted | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | kinds.state_row_emits_wrong_kind_rejected | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | kinds.state_row_with_extra_field_rejected | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ | | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| kinds.subkind_addition_without_revision_rejected | .+.......................> | kinds.kind_field_extensible | -+---------------------------+--------------------------------------------------------------+---------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | kinds.unknown_kind_rejected | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |onstraints.traces_to +-------------------------------------------------------------+ | |
| lint.findings.emit | -+- - - - - - - - - - - -> | lint.findings.self_describing | .+.......................> | lint.findings | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | ^ | ^ | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
v | | | | | |
+-------------------------------------------------------------+ | | | | | |
| lint.findings.emitted | | | | | | |
+-------------------------------------------------------------+ | | | | | |
| | | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | | |
v | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| lint.findings.found | | | lint.findings.p_finding | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
| | | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| lint.findings.process_lifecycle_checked | .+.......................> | lint.findings.process_lifecycle | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | linter.coverage.accept | -+-----------------------> | linter.coverage.passed | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| linter.coverage.computed_derives_from_rejected | .+.......................> | linter.coverage.coverage_is_computable | -+---------------------------+--------------------------------------------------------------+---------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.coverage.extra_case_is_declaration | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.coverage.full_coverage_passes | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.coverage.incomplete_law_rejected | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ | | | |
| | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | |
| linter.ears_syntax.accept | -+-----------------------> | linter.ears_syntax.passed | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.ears_syntax.non_ears_rejected | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.ears_syntax.pattern_ok | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | linter.external_completeness.accept | -+-----------------------> | linter.external_completeness.passed | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.external_completeness.check_mapping | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.external_completeness.fully_mapped_checklist_passes | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.external_completeness.load | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.external_completeness.no_checklist_not_applicable | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | | -+-----------------------> | linter.failure_shape.passed | | |
| | | | | +-------------------------------------------------------------+ | |
| | | | | | |
| | | linter.failure_shape.accept | | +--------------------------------------------------------------+ |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.failure_shape.clean_repo_passes | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.failure_shape.negation_set_mismatch_flagged | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | | -+-----------------------> | linter.frontmatter.passed | | |
| | | | | +-------------------------------------------------------------+ | |
| | | | | | |
| | | linter.frontmatter.accept | | +--------------------------------------------------------------+ |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.frontmatter.missing_id_rejected | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | | -+-----------------------> | linter.graph_shape.passed | | |
| | | | | +-------------------------------------------------------------+ | |
| | | | | | |
| | | linter.graph_shape.accept | | +--------------------------------------------------------------+ |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.graph_shape.cycle_rejected | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.graph_shape.dag_passes | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.graph_shape.derives_from_edges_property_sourced | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.graph_shape.topo_sort_naturality | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | linter.model_shape.accept | -+-----------------------> | linter.model_shape.passed | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.model_shape.bad_edge_rejected | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| | | |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | linter.model_shape.pairing_ok | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ |---------------------------+ +---------------------------+---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| linter.observability.dangling_observes_not_doubled | .+.......................> | linter.observability.dangling_observes_is_referential | .+.......................> | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | linter.observability | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | | ^ |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+ +---------------------------+---------------------------------------------------------------+
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| linter.observability.warning_never_gates | .+.......................> | linter.observability.advisory_severity | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
| | | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| linter.observability.mutual_observation_passes | .+.......................> | | -+---------------------------| | |
+-------------------------------------------------------------+ | | | | | | |
| | | | | | |
+--------------------------------------------------------------+ | linter.observability.observation_universe | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | +------------------------------------------------------------+ | | | |
| | ^ | | | |
| | +-------------------------------------------------------------+---------------------------| | |
| | | | | | |
| | | | | | |
+--------------------------------------------------------------+ | | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ |ransitions.from +--------------------------------------------------------------+ |ransitions.from +----------------------------------------------------------------+ transitions.to +-----------------------------------------------+
| linter.observability.unobserved_effect_warned | -+---------------------------| | | linter.observability.warn_unobserved | -+-----------------------> | linter.observability.scanning | <+------------------------ | linter.observability.scan_effects | -------------------------> | linter.observability.observed |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | +----------------------------------------------------------------+ +-----------------------------------------------+
| | | | | |
+--------------------------------------------------------------+ | | | transitions.to | +---------------------------------------------------------------+
| | | | v | |
| | | | +-------------------------------------------------------------+ | |
| | | | | linter.observability.unobserved_warned | | |
| | | | +-------------------------------------------------------------+ | |
| | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.observability.observed_effect_silent | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ | | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| linter.observability.self_observation_still_warned | .+.......................> | linter.observability.self_observation_not_counted | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
| | | | |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | |
| linter.observability.waiver_input_ignored | .+.......................> | linter.observability.no_waivers_in_v1 | -+---------------------------| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | linter.referential_integrity.accept | -+-----------------------> | linter.referential_integrity.passed | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.referential_integrity.clean_repo_passes | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | linter.schema_shape.accept | -+-----------------------> | linter.schema_shape.passed | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.schema_shape.appended_id_set_member_passes | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | | | |
| | | | +-------------------------------------------------------------+ | |
| | | | | linter.schema_shape.constraint_kind_effect_passes | | |
| | | | +-------------------------------------------------------------+ | |
| | | | | |
| | | | +--------------------------------------------------------------| |
| | | | | | |
| | | | +-------------------------------------------------------------+ | |
| | | | | linter.schema_shape.constraint_kind_extension_point_passes | | |
| | | | +-------------------------------------------------------------+ | |
| | | | | |
| | | | +--------------------------------------------------------------| |
| | | | | | |
| | | | +-------------------------------------------------------------+ | |
| | | | | linter.schema_shape.constraint_kind_invalid_rejected | | |
| | | | +-------------------------------------------------------------+ | |
| | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.schema_shape.diff_ok | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | | | |
| | | | +-------------------------------------------------------------+ | |
| | | | | linter.schema_shape.kind_closed_passes | | |
| | | | +-------------------------------------------------------------+ | |
| | | | | |
| | +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | | | |
| | +------------------------------------------------------------+ | | | |
| | | linter.schema_shape.shrunk_id_set_rejected | | | | |
| | +------------------------------------------------------------+ | | | |
| | | | | |
+--------------------------------------------------------------+ | | | |
| | | | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | | |
| merge.accept | -+-----------------------> | merge.merged | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+
| | v | | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ |
| merge.auto_proceed | -+- - - - - - - - - - - -> | merge.rename_replayed_onto_foreign_edits | -+---------------------------+--------------------------------------------------------------+ | merge.rename_replay_repairs_dangling_ref | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | +--------------------------------------------------------------+ |
| | ^ | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ +---------------------------------------------------------------+
v | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | |
| merge.collision_checked | | | | | merge.check_collisions | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ | |
^ | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |ransitions.from +------------------------------------------------------------+ | | |
| merge.resolved | -+-----------------------> | merge.needs_review | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ | |
| merge.blast_radius_recorded_before_apply | .+.......................> | merge.blast_radii_recorded_pre_merge | <+ - - - - - - - - - - - - | merge.flag_review | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ | | |
| | merge.collision_across_branches_rejected | | | |
| +------------------------------------------------------------+ | | |
| | | |
| +-------------------------------------------------------------| | |
| | | | |
| +------------------------------------------------------------+ | | |
| | merge.disjoint_blast_radii_auto_merge | | | |
| +------------------------------------------------------------+ | | |
| | | |
| +-------------------------------------------------------------| | |
| | | | |
| +------------------------------------------------------------+ | | |
| | merge.relint_gates_merge | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ |---------------------------+ +-------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |onstraints.traces_to +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| migrate.begin_wrap | -+- - - - - - - - - - - -> | migrate.delta_text_preserved | .+.......................> | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | ^ | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | |
| | | | | |
| | | | | |
| | | | migrate | |
| | | | | |
| | | | | |
| +-------------------------------------------------------------+ | | |
| | | | | |
| +------------------------------------------------------------+ | | | |
| | migrate.p_delta_text_preserved | | | | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | ^ | |
| +-------------------------------------------------------------+---------------------------+ +-------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | |
| | | |
+-------------------------------------------------------------+ transitions.guard +------------------------------------------------------------+ | | |
| migrate.write_result | -+- - - - - - - - - - - -> | migrate.sidecar_generated | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | ^ | | |
+--------------------------------------------------------------+-----------------------------------------------------------------------------------------| | |
v | | |
+-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
| | | | |
| | | | |
| | | | |
| migrate.wrapping | | | |
| | | | |
| | | | |
| | | | |
+-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | |
^ | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------| |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| migrate.written | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ | | | | |
^ | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ +---------------------------------------------------------------+
v | | | | | | |
+-------------------------------------------------------------+ | | | +-------------------------------------------------------------+ |ransitions.guard +--------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------+
| | | | | | migrate.dry_run_pass | -+- - - - - - - - - - - -> | migrate.dry_run_writes_nothing | <+........................ | migrate.p_dry_run_writes_nothing |
| | | | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | +----------------------------------------------------------------+
| | | | | | |
| migrate.unwrapping | | | | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ | | | | |
^ | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------+------------------------------------------------------------------------------------------|
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| migrate.merge_only | -+- - - - - - - - - - - -> | migrate.merge_adds_only_missing | <+........................ | migrate.p_merge_adds_only_missing | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| migrate.p_refuse_already_migrated | .+.......................> | migrate.refuse_already_migrated | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | | |
| +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ |ransitions.to +--------------------------------------------------------------+ |
| | migrate.refuse | -+-----------------------> | migrate.refused | <+------------------------ | migrate.rekey_refuse | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| migrate.p_rekey_derives_real_id | .+.......................> | migrate.rekey_derives_real_id | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | migrate.rekey_result | -+-----------------------> | migrate.rekeyed | | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------|
| v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| migrate.p_rekey_refusals | .+.......................> | migrate.rekey_refusals | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | +--------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ | |
| migrate.p_scaffold_lints_clean | .+.......................> | migrate.scaffold_lints_clean | <+ - - - - - - - - - - - - | migrate.verify_green | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ | | |
| | migrate.p_sidecar_generated | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| migrate.process_lifecycle_checked | .+.......................> | migrate.process_lifecycle | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| |---------------------------+ +---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| model.check.activate | -+- - - - - - - - - - - -> | model.check.backend_agreement_ci | .+.......................> | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | ^ | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | model.check | |
v | | | | | |
+-------------------------------------------------------------+ | | | | | |
| model.check.agreement_active | | | | | | |
+-------------------------------------------------------------+ | | | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | ^ |
+--------------------------------------------------------------+ | +---------------------------+ +---------------------------------------------------------------+
| | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | model.check.fixture_status_agrees | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | |
| model.check.upgrade | -+- - - - - - - - - - - -> | model.check.guard_citation_evaluated | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | ^ | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| model.check.citation_evaluated | | | | | |
+-------------------------------------------------------------+ | | | | |
| | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | |
v | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| model.check.citation_inert | | | model.check.citation_composes | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| model.check.command_claims_checked | .+.......................> | model.check.command_claims | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| model.check.guard_fragment_still_rejected | .+.......................> | model.check.fragment_guard_rejected_stands | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| model.check.process_lifecycle_checked | .+.......................> | model.check.process_lifecycle | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| model.check.report_names_status | .+.......................> | model.check.run_report_status | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | |
| | | | |
| +------------------------------------------------------------+ | | |
| | model.check.undischargable_is_unknown | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ +---------------------------+------------------------------------------------------------------------------------------+
| |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| model_check.backend_named_in_report | .+.......................> | model_check.backend_identified | .+.......................> | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| model_check.violated_invariant_named | .+.......................> | model_check.counterexample_names_violated_invariant | -+------------------------>| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
| +-------------------------------------------------------------| | |
| | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | |
| model_check.begin | -+- - - - - - - - - - - -> | model_check.checker_invoked | -+------------------------>| model_check |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | +-------------------------------------------------------------| | |
| | | | | |
| | +------------------------------------------------------------+ | | |
| | | model_check.stale_claim_rejected | | | |
| | +------------------------------------------------------------+ | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | | ^
| | +-------------------------------------------------------------+---------------------------+------------------------------------------------------------------------------------------+
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
+--------------------------------------------------------------+ | | | |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| model_check.not_run | | | | | |
+-------------------------------------------------------------+ | | | | |
| | | | |
+---------------------------+ +---------------------------+--------------------------------------------------------------+ |
| v | |
+-------------------------------------------------------------+ |ransitions.from +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| model_check.finish_timeout | -+-----------------------> | | | |
+-------------------------------------------------------------+ | | | | |
| | | | | |
+--------------------------------------------------------------| | model_check.running | | |
| | | | | |
| | | | | |
| | | | | |
| | +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | ^ | |
+--------------------------------------------------------------+---------------------------+ +---------------------------+--------------------------------------------------------------+ |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| model_check.timed_out | | | | | |
+-------------------------------------------------------------+ | | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| model_check.bound_must_be_stated | .+.......................> | model_check.exhaustive_within_bound | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | model_check.finish_exploration | -+-----------------------> | model_check.exploration_only | | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ | | |
| | model_check.clean_model_passes | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| model_check.clean_run_satisfies_verify_gate | .+.......................> | model_check.no_counterexample_feeds_verify | -+........................ | model_check.exploration_run_is_not_a_clean_verdict | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| model_check.combined_same_identity_scope_refused | .+.......................> | model_check.corpus_identity_scope | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | model_check.distinct_ids_compose_and_lint_together | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| model_check.exhausted_bound_stays_timed_out | .+.......................> | | <+........................ | model_check.unknown_or_missing_claim_blocks_clean | | |
+-------------------------------------------------------------+ | | | | +-------------------------------------------------------------+ | |
| | | | | |
+--------------------------------------------------------------+ | model_check.claim_aggregate_governs | | +--------------------------------------------------------------+ |
| | | | | |
| | | | | |
| | | | | |
| +------------------------------------------------------------+ | | |
| ^ | | |
| +-------------------------------------------------------------+---------------------------| |
| | | | |
| | | | |
| | | +--------------------------------------------------------------+ |
| | | | | |
| | | +-------------------------------------------------------------+ | |
| | | | model_check.false_claim_is_counterexample_found | | |
| | | +-------------------------------------------------------------+ | |
| | | | |
| | | +--------------------------------------------------------------+ |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | model_check.finish_clean | -+-----------------------> | model_check.clean | | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| +-------------------------------------------------------------| +--------------------------------------------------------------| +---------------------------------------------------------------+
| | | | | |
| +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ |roperties.derives_from +--------------------------------------------------------------+ |
| | model_check.finish_violation | -+- - - - - - - - - - - -> | model_check.counterexample_is_minimal | <+........................ | model_check.shortest_counterexample_reported | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | | |
| | transitions.to | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| v | | |
| +------------------------------------------------------------+ | | |
| | model_check.counterexample_found | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| model_check.fragment_clean_run_is_no_counterexample | .+.......................> | model_check.executable_invariants_execute | <+........................ | model_check.fragment_invariants_checked | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ | | |
| | model_check.fragment_violation_traces | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| model_check.panicking_fragment_violates | .+.......................> | model_check.invariant_totality | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| model_check.prose_only_is_unchecked_not_required | .+.......................> | model_check.required_claims_classified | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| model_check.reordered_paths_and_prose_preserve_scope | .+.......................> | model_check.claim_report_schema | <+........................ | model_check.swapped_reports_rejected_unrewritten | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | model_check.report_binds_claims_and_scope_digest | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| model_check.stale_result_invalidated_by_edit | .+.......................> | model_check.rerun_on_model_change | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| |---------------------------+ +---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| numeric.predicates.pack.closure_honest_empty | .+.......................> | numeric.predicates.pack.measured_by_outbound | .+.......................> | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| ^ | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | numeric.predicates.pack | |
| | | | | |
| | | | | |
| | | | | |
| | | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | ^ |
| | +---------------------------+ +---------------------------------------------------------------+
| | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | numeric.predicates.pack.measured_by_resolves | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| numeric.predicates.pack.units_opaque_to_lint | .+.......................> | numeric.predicates.pack.units_stay_opaque | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
| +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| numeric.predicates.pack.consumer_activates_advisory | .+.......................> | numeric.predicates.pack.quantity_section_shape | -+........................ | numeric.predicates.pack.orphan_names_numeric_pack | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| ^ | | |
| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | |
| numeric.predicates.pack.deprecate | -+- - - - - - - - - - - -> | | -+---------------------------| |
+-------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------| | numeric.predicates.pack.pack_file_declared | | +--------------------------------------------------------------+ +---------------------------------------------------------------+
v | | | | | | |
+-------------------------------------------------------------+ | | | | +-------------------------------------------------------------+ |ransitions.from +--------------------------------------------------------------+ |
| numeric.predicates.pack.deprecated | | | | <+------------------------ | numeric.predicates.pack.publish | -+-----------------------> | numeric.predicates.pack.draft | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| ^ | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | |
| | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------| |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| numeric.predicates.pack.published | | | | | |
+-------------------------------------------------------------+ | | | | |
| | | | |
+--------------------------------------------------------------+ | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | numeric.predicates.pack.discovery_finds_numeric_pack | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| numeric.predicates.pack.no_bare_word_vocabulary | .+.......................> | numeric.predicates.pack.vocabulary_prose_safe | -+........................ | numeric.predicates.pack.tolerance_floor_labels | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ | | |
| | numeric.predicates.pack.no_base_set_absorption | | | |
| +------------------------------------------------------------+ | | |
| | | |
| +-------------------------------------------------------------| | |
| | | | |
| +------------------------------------------------------------+ | | |
| | numeric.predicates.pack.pack_shape_clean_all_states | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| numeric.predicates.pack.process_lifecycle_checked | .+.......................> | numeric.predicates.pack.process_lifecycle | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| |---------------------------+ +---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| observability.approve | -+- - - - - - - - - - - -> | | .+.......................> | | |
+-------------------------------------------------------------+ | | | | | | |
| | | | | | | |
+--------------------------------------------------------------| | observability.process_lifecycle | | | observability | |
v | | | | | | |
+-------------------------------------------------------------+ | | | | | | |
| observability.approved | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
^ | ^ | ^ |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+ +---------------------------------------------------------------+
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
+--------------------------------------------------------------+ | | | |
| | | | | |
+-------------------------------------------------------------+ | | | | |
| observability.implement | -+---------------------------| | | |
+-------------------------------------------------------------+ | | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | observability.process_lifecycle_checked | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| observability.unobserved_effect_finding | .+.......................> | observability.unobserved_effect_reported | -+........................ | observability.observed_effect_silent | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| observability.proposed | | | | | |
+-------------------------------------------------------------+ | | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | observability.archive | -+-----------------------> | observability.archived | | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
| +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| v | | |
| +------------------------------------------------------------+ | | |
| | observability.implemented | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+
| v | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| observability.boundary_from_extension_points | .+.......................> | observability.boundary_derived | <+........................ | observability.stale_boundary_impossible | | | observability.plain_file_not_boundary | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| observability.gates_untouched | .+.......................> | observability.no_gate_change | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| observability.observes_cross_file_resolves | .+.......................> | observability.observes_typing | <+........................ | observability.observes_wrong_target_rejected | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| observability.observes_cycle_not_flagged | .+.......................> | observability.acyclic_edge_set_stable | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| orchestrate.clean_repo_succeeds | .+.......................> | orchestrate.lint_gate_matches_checker_ownership | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | |
| | | | |
| +------------------------------------------------------------+ | | |
| | orchestrate.compile_blocked_on_partial_lint | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| orchestrate.compile_ok | -+- - - - - - - - - - - -> | orchestrate.compile_gate_matches_coverage | <+........................ | orchestrate.coverage_failure_holds_compile | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| orchestrate.external_completeness_failure_ignored | .+.......................> | orchestrate.external_completeness_never_gating | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| orchestrate.independent_branch_failure_isolated | .+.......................> | orchestrate.independent_branches_run_regardless | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | |
| | | | | |
+-------------------------------------------------------------+ | | | | |
| | -+---------------------------| | | |
| | | | | | |
| | | | | | |
| orchestrate.lint_ok | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ | | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| orchestrate.upstream_failure_skips_dependents | .+.......................> | orchestrate.dependency_respecting_skip | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | |
| | | | | |
+-------------------------------------------------------------+ | | | | |
| orchestrate.model_check_ok | -+---------------------------| | | |
+-------------------------------------------------------------+ | | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| orchestrate.rerun_idempotent | .+.......................> | orchestrate.deterministic_rerun | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| +---------------------------+------------------------------------------------------------------------------------------+
| |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| packs.base_sets_unchanged_by_packs | .+.......................> | packs.base_closed_sets_frozen | .+.......................> | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| packs.uses_edge_typing | .+.......................> | packs.uses_is_outbound_leaf | -+------------------------>| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ | | |
| packs.deprecate | -+- - - - - - - - - - - -> | packs.lifecycle_labeled | -+------------------------>| packs |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | ^ | | |
+--------------------------------------------------------------| +-------------------------------------------------------------| | |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| packs.deprecated | | | | | |
+-------------------------------------------------------------+ | | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | |
| | | | |
| +------------------------------------------------------------+ | | |
| | packs.lifecycle_states_parsed | | | |
| +------------------------------------------------------------+ | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | ^
| +-------------------------------------------------------------+---------------------------+------------------------------------------------------------------------------------------+
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
+--------------------------------------------------------------+ | | | |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| packs.published | | | | | |
+-------------------------------------------------------------+ | | | | |
^ | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+ | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| packs.discovery_finds_all_packs | .+.......................> | packs.discovery_corpus_scan | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | packs.subdir_lint_discovers_workspace_packs | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| packs.empty_vocabulary_honest | .+.......................> | packs.honest_empty | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| packs.fiber_kinds_typeable | .+.......................> | packs.namespaced_vocabulary | <+........................ | packs.pack_vocabulary_qualified | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| packs.manifest_round_trips | .+.......................> | packs.manifest_six_tables | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ |ransitions.from +-------------------------------------------------------------+ | |
| | packs.publish | -+-----------------------> | packs.draft | | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
+--------------------------------------------------------------+ | transitions.guard | +--------------------------------------------------------------+ |
| v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| packs.profile_file_is_four_layer | .+.......................> | packs.pack_is_profile_file | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| packs.narrowing_rejected | .+.......................> | packs.append_only_packs | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| packs.no_pack_no_change | .+.......................> | packs.advisory_first_opt_in | <+........................ | packs.overlapping_vocabulary_activates_both | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| packs.orphan_finding_names_pack | .+.......................> | packs.orphan_vocabulary_labeled | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| packs.pack_self_activation_absent | .+.......................> | packs.pack_self_exempt | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| packs.skew_is_warning_not_failure | .+.......................> | packs.skew_advisory | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+
| | | | v
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ | +------------------------------------------------------------------------------------------------------------------------------------------------------------+
| parse.P-envelope | .+.......................> | parse.C-parse-envelope | <+ - - - - - - - - - - - - | parse.t-emit | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | |
| | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------| | parse |
| | | v | | |
| | | +-------------------------------------------------------------+ | | |
| | | | parse.emitted | | | |
| | | +-------------------------------------------------------------+ | +------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | | | ^
| | | +--------------------------------------------------------------+---------------------------+
| | | | | |
| | | | | |
| | | +--------------------------------------------------------------+ |
| | | v | |
| | | +-------------------------------------------------------------+ | |
| | | | parse.parsed | | |
| | | +-------------------------------------------------------------+ | |
| | | ^ | |
| | | +--------------------------------------------------------------+ |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | | | |
| | | +-------------------------------------------------------------+ |ransitions.guard +--------------------------------------------------------------+ |roperties.derives_from +----------------------------------------------------------------+
| | | | parse.t-parse | -+- - - - - - - - - - - -> | parse.C-parse-ir | <+........................ | parse.P-ir |
| | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | +----------------------------------------------------------------+
| | | | | ^ |
| | | | transitions.from | +---------------------------------------------------------------+
| | | v | |
| | | +-------------------------------------------------------------+ | |
| | | | parse.reading | | |
| | | +-------------------------------------------------------------+ | |
| | | ^ | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+
| | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | +--------------------------------------------------------------+ |
| parse.P-error | .+.......................> | parse.C-parse-error | -+---------------------------+--------------------------------------------------------------+ | parse.t-fail | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | +--------------------------------------------------------------+ |
| ^ | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | +---------------------------------------------------------------+
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+---------------------------+------------------------------------------------------------------------------------------|
| v | | |
| +------------------------------------------------------------+ | | |
| | parse.failed | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| parse.P-lintindep | .+.......................> | parse.C-parse-lint-independent | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| parse.P-single | .+.......................> | parse.C-parse-single-file | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| parse.process_lifecycle_checked | .+.......................> | parse.process_lifecycle | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| |---------------------------+ +---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| quant.finance.pack.deprecate | -+- - - - - - - - - - - -> | | .+.......................> | | |
+-------------------------------------------------------------+ | | | | | | |
| | | | | | | |
+--------------------------------------------------------------| | quant.finance.pack.pack_file_declared | | | quant.finance.pack | |
v | | | | | | |
+-------------------------------------------------------------+ | | | | | | |
| quant.finance.pack.deprecated | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| ^ | ^ |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+ +---------------------------------------------------------------+
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | quant.finance.pack.no_pack_no_change | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| quant.finance.pack.vocabulary_prose_safe_verified | .+.......................> | quant.finance.pack.vocabulary_prose_safe | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| quant.finance.pack.published | | | | | |
+-------------------------------------------------------------+ | | | | |
^ | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------| |
| | | | |
| +------------------------------------------------------------+ | | |
| | quant.finance.pack.discovery_finds_quant_pack | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| quant.finance.pack.fixtures_are_data_rows | .+.......................> | quant.finance.pack.fixtures_stay_data | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| quant.finance.pack.honest_empty | .+.......................> | quant.finance.pack.capped_by_outbound | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ |ransitions.from +--------------------------------------------------------------+ |
| quant.finance.pack.limits_rows_namespaced | .+.......................> | quant.finance.pack.limits_section_shape | <+ - - - - - - - - - - - - | quant.finance.pack.publish | -+-----------------------> | quant.finance.pack.draft | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| refactor.fan_in_graph_sourced | .+.......................> | refactor.fan_in_read_from_graph | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| refactor.threshold_read_from_config | .+.......................> | refactor.threshold_is_per_repo_setting | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------| | +--------------------------------------------------------------+ |
| | | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| rename.apply | -+- - - - - - - - - - - -> | rename.atomic_operation | <+........................ | rename.partial_failure_rolls_back | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | | |
+-------------------------------------------------------------+ | | | | |
| | -+---------------------------| | | |
| | | | | | |
| | | | | | |
| rename.apply_ok | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-------------------------------------------------------------+ | | | | |
| | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------+ | |
| | v | | |
+-------------------------------------------------------------+ |ransitions.from +------------------------------------------------------------+ | | |
| rename.verify | -+-----------------------> | rename.applied | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | |
| | | | |
| +------------------------------------------------------------+ | | |
| | rename.collision_rejected | | | |
| +------------------------------------------------------------+ | | |
| | | |
| +-------------------------------------------------------------| | |
| | | | |
| +------------------------------------------------------------+ | | |
| | rename.rename_naturality | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ |---------------------------+ +---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| skill.pack.absent_pack_hinted | .+.......................> | skill.pack.doctor_flags_staleness | .+.......................> | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| ^ | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | | |
| | | | | |
| | | | | |
| | | | skill.pack | |
| | | | | |
| | | | | |
| +-------------------------------------------------------------+ | | |
| | | | | |
| +------------------------------------------------------------+ | | | |
| | skill.pack.stale_install_flagged | | | | |
| +------------------------------------------------------------+ | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | ^ |
| +-------------------------------------------------------------+---------------------------+ +---------------------------------------------------------------+
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| skill.pack.book_stays_thin | .+.......................> | skill.pack.docs_stay_thin_pointers | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| skill.pack.dangling_mention_fails | .+.......................> | skill.pack.mentions_membership_tested | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ |ransitions.from +-------------------------------------------------------------+ | |
| | skill.pack.drift | -+-----------------------> | skill.pack.installed | | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | ^ | |
| | transitions.to | +--------------------------------------------------------------+---------------------------|
| v | | |
| +------------------------------------------------------------+ | | |
| | skill.pack.stale | | | |
| +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ |ransitions.to +--------------------------------------------------------------+ |ransitions.from +----------------------------------------------------------------+ transitions.to +-----------------------------------------------+
| skill.pack.example_lints_zero | .+.......................> | skill.pack.example_lints_clean | <+ - - - - - - - - - - - - | | -+-----------------------> | skill.pack.generated | <+------------------------ | skill.pack.refuse | -------------------------> | skill.pack.refused |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ | +----------------------------------------------------------------+ +-----------------------------------------------+
| | | | | ^ | |
| | | skill.pack.generate | | +---------------------------------------------------------------+ |
| | | | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | | | | |
| skill.pack.generation_is_deterministic | .+.......................> | skill.pack.embedded_single_source |<-+------------------------ | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | | |
| | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ | |
| | | | | |
| | | | | |
| | | +--------------------------------------------------------------+ +---------------------------------------------------------------+ |
| | | v | | | |
| | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | |
| | | | skill.pack.tracked | | | skill.pack.install | | |
| | | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ | |
| | | | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+---------------------------------------------------------------+---------------------------+
| v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| skill.pack.foreign_file_refused | .+.......................> | | -+---------------------------| |
+-------------------------------------------------------------+ | | | | | |
| | | | | |
+--------------------------------------------------------------+ | skill.pack.install_writes_and_reports | | +--------------------------------------------------------------+ |
| | | | | |
| | | | +-------------------------------------------------------------+ | |
| | | <+------------------------ | skill.pack.fresh_install_enveloped | | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| ^ | | |
| +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | |
| | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+------------------------------------------------------------------------------------------|
| v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| skill.pack.marker_survives_embedding | .+.......................> | skill.pack.marker_marks_every_install | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | skill.pack.refresh | -+---------------------------| |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ |---------------------------+ +---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| spec.integration.both_parsers_green | .+.......................> | spec.integration.both_parsers_accept | .+.......................> | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| ^ | | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | spec.integration | |
| | | | | |
| | | | | |
| | | | | |
| | | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | | ^ |
| | +---------------------------+ +---------------------------------------------------------------+
| | | | |
| | | | |
| +---------------------------+ | +--------------------------------------------------------------+ |
| | | | | |
| | +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | | spec.integration.deploy_pair | -+-----------------------> | spec.integration.deployed | | |
| | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | ^ | |
| | +-------------------------------------------------------------| +--------------------------------------------------------------+ |
| | v | | |
| | +------------------------------------------------------------+ | | |
| | | spec.integration.plain_delta | | | |
| | +------------------------------------------------------------+ | | |
| | | | |
+--------------------------------------------------------------| +-------------------------------------------------------------| +--------------------------------------------------------------+ +---------------------------------------------------------------+
| v | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | +-------------------------------------------------------------+ |ransitions.to +--------------------------------------------------------------+ |
| spec.integration.plain_delta_gate_green | .+.......................> | spec.integration.plain_deltas_carried | -| | spec.integration.gate | -+-----------------------> | spec.integration.gated | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | | |
| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ +---------------------------------------------------------------+
| v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| spec.integration.same_id_pair_refused | .+.......................> | spec.integration.duplicate_corpus_identity | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| +-------------------------------------------------------------+---------------------------| |
| v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| spec.integration.deployed_shape_enforced | .+.......................> | spec.integration.deployed_shape_closed | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| spec.integration.process_lifecycle_checked | .+.......................> | spec.integration.process_lifecycle | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| specodelic.acyclicity | .+.......................> | specodelic.acyclic_traces | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------------------------------------+
| v | | |
+-------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | | +----------------------------------------------------------------+
| specodelic.advisory_cannot_gate | .+.......................> | | -+---------------------------| | specodelic.emits_cannot_target_non_effect |
+-------------------------------------------------------------+ | | | | | +----------------------------------------------------------------+
| | | | |
+--------------------------------------------------------------| | specodelic.ref_kind_compatible | | |
| | | | | |
| | | | | |
| | | | | |
| | +-------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
| | ^ | |
| +---------------------------+-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+---------------------------|
| | | | |
| | | | |
+--------------------------------------------------------------+ | | | +---------------------------------------------------------------+
| | | | | |
+-------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ |
| specodelic.constraint_derives_from_rejected | -+---------------------------| | | | specodelic.satisfies_wrong_kind_rejected | |
+-------------------------------------------------------------+ | | | | +--------------------------------------------------------------+ |
| | | | |
+--------------------------------------------------------------+ | | | +---------------------------------------------------------------+
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | specodelic.typing_enforced | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |ransitions.guard +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| specodelic.compile | -+- - - - - - - - - - - -> | specodelic.coverage | <+........................ | specodelic.coverage_gap_flagged | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
| transitions.to | +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| specodelic.compiled | | | | | |
+-------------------------------------------------------------+ | | | | |
^ | | | | |
+--------------------------------------------------------------|---------------------------+-------------------------------------------------------------+ | |
| v | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| specodelic.law_missing_case_rejected | .+.......................> | specodelic.law_requires_cases | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------|---------------------------+ | | |
| | v | | |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ | | |
| specodelic.lint | -+-----------------------> | specodelic.linted | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| specodelic.constraint_kind_out_of_set_rejected | .+.......................> | specodelic.constraint_kind_closed | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |roperties.derives_from +-------------------------------------------------------------+ | |
| specodelic.extension_point_needs_no_reachability_carveout | .+.......................> | specodelic.single_root_reachable | -+........................ | specodelic.own_intent_reachable | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | |
| | +--------------------------------------------------------------+ |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| specodelic.filename_convention | .+.......................> | specodelic.id_matches_file | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
| +-------------------------------------------------------------+---------------------------+--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ |ransitions.from +--------------------------------------------------------------+ |
| specodelic.frontmatter_missing_field_rejected | .+.......................> | specodelic.frontmatter_valid | <+ - - - - - - - - - - - - | specodelic.parse | -+-----------------------> | specodelic.draft | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| specodelic.guardless_transition_rejected | .+.......................> | specodelic.guard_required | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| specodelic.missing_model_section_rejected | .+.......................> | specodelic.model_present | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | | |
+-------------------------------------------------------------+ | | | |
| specodelic.model_check | -+---------------------------| | |
+-------------------------------------------------------------+ | | | |
| | | |
| +-------------------------------------------------------------+ | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| specodelic.model_well_formed | .+.......................> | specodelic.every_transition_valid | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| specodelic.multi_intent_join_rejected | .+.......................> | specodelic.one_capability_per_row | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| specodelic.no_stored_superseded_flag | .+.......................> | specodelic.no_boolean_columns | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| specodelic.non_ears_statement_rejected | .+.......................> | specodelic.ears_statement | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| specodelic.orphan_state_rejected | .+.......................> | specodelic.every_state_used | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| specodelic.property_kind_out_of_set_rejected | .+.......................> | specodelic.property_kind_closed | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| specodelic.prose_does_not_affect_lint | .+.......................> | specodelic.prose_untouched | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| specodelic.referential_integrity | .+.......................> | specodelic.total_refs | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| specodelic.silently_shrunk_id_set_rejected | .+.......................> | specodelic.append_only_variants | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | |
| | | | |
| +------------------------------------------------------------+ | | |
| | specodelic.state_guard_citation_accepted | | | |
| +------------------------------------------------------------+ | | |
| | | |
| +-------------------------------------------------------------| | |
| | | | |
| +------------------------------------------------------------+ | | |
| | specodelic.supersedes_cross_kind_rejected | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| specodelic.supersedes_cycle_rejected | .+.......................> | specodelic.supersedes_acyclic | -+---------------------------+------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| |---------------------------+ +---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ transitions.guard +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| svc.contracts.pack.deprecate | -+- - - - - - - - - - - -> | | .+.......................> | | |
+-------------------------------------------------------------+ | | | | | | |
| | | | | | | |
+--------------------------------------------------------------+ | svc.contracts.pack.pack_file_declared | | | svc.contracts.pack | |
v | | | | | | |
+-------------------------------------------------------------+ | | | | | | |
| svc.contracts.pack.deprecated | | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| ^ | ^ |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+ +---------------------------------------------------------------+
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| +-------------------------------------------------------------+ | |
| | | | |
| +------------------------------------------------------------+ | | |
| | svc.contracts.pack.no_pack_no_change | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| svc.contracts.pack.vocabulary_audit_holds | .+.......................> | svc.contracts.pack.vocabulary_prose_safe | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
v | | | | |
+-------------------------------------------------------------+ | | | | |
| svc.contracts.pack.published | | | | | |
+-------------------------------------------------------------+ | | | | |
^ | | | | |
+--------------------------------------------------------------+---------------------------+-------------------------------------------------------------+---------------------------| |
| | | | |
| +------------------------------------------------------------+ | | |
| | svc.contracts.pack.discovery_finds_svc_pack | | | |
| +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| svc.contracts.pack.floor_names_cases | .+.......................> | svc.contracts.pack.compat_floor_declared | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| svc.contracts.pack.honest_empty | .+.......................> | svc.contracts.pack.published_as_outbound | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ |ransitions.guard +-------------------------------------------------------------+ |ransitions.from +--------------------------------------------------------------+ |
| svc.contracts.pack.interfaces_shape_lints_clean | .+.......................> | svc.contracts.pack.interfaces_section_shape | <+ - - - - - - - - - - - - | svc.contracts.pack.publish | -+-----------------------> | svc.contracts.pack.draft | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +-------------------------------------------------------------+ | +--------------------------------------------------------------+ |
| | | |
| | +--------------------------------------------------------------+ +---------------------------------------------------------------+
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| svc.contracts.pack.no_consumer_field_declared | .+.......................> | svc.contracts.pack.no_new_cross_repo_edge | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
| |---------------------------+ +---------------------------------------------------------------+
| | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ |onstraints.traces_to +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| verification.claims.aggregate_checked | .+.......................> | verification.claims.aggregate | .+.......................> | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | | |
| | | | |
| | | verification.claims | |
| | | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | | |
| verification.claims.visible_scope_checked | .+.......................> | verification.claims.visible_scope | -+------------------------>| | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | +---------------------------------------------------------------------------------------------------------------------------------------------------------+ |
| | ^ |
+--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------+ +---------------------------------------------------------------+
| | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | |
+-------------------------------------------------------------+ transitions.guard +------------------------------------------------------------+ | | |
| verification.claims.approve | -+- - - - - - - - - - - -> | | -+---------------------------| |
+-------------------------------------------------------------+ | | | | | |
| | | | | | |
+--------------------------------------------------------------+ | verification.claims.process_lifecycle | | | |
v | | | | | |
+-------------------------------------------------------------+ | | | | | |
| verification.claims.approved | | | | | | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
^ | ^ | | |
|--------------------------------------------------------------+ +-------------------------------------------------------------+---------------------------| |
| | | |
| | | |
| | +--------------------------------------------------------------+ |
| | | | |
| | +-------------------------------------------------------------+ | |
| | | verification.claims.process_lifecycle_checked | | |
| | +-------------------------------------------------------------+ | |
| | | |
+--------------------------------------------------------------+ | +--------------------------------------------------------------+ |
v | | | |
+-------------------------------------------------------------+ | | | |
| verification.claims.proposed | | | | |
+-------------------------------------------------------------+ | | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ +--------------------------------------------------------------+ |
| | | | |
| +------------------------------------------------------------+ |ransitions.to +-------------------------------------------------------------+ | |
| | verification.claims.deploy | -+-----------------------> | verification.claims.deployed | | |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+ | |
| | | | |
| | transitions.from | +--------------------------------------------------------------+ |
| v | | |
| +------------------------------------------------------------+ | | |
| | verification.claims.implemented | | | |
| +------------------------------------------------------------+ | | |
| ^ | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| | |
| | | |
+-------------------------------------------------------------+ |roperties.derives_from +------------------------------------------------------------+ | | |
| verification.claims.freshness_checked | .+.......................> | verification.claims.freshness | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | | | |
+-------------------------------------------------------------+ | | | |
| verification.claims.implement | -+---------------------------| | |
+-------------------------------------------------------------+ | | | |
| | | |
| +-------------------------------------------------------------+ | |
| | | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | | |
| verification.claims.required_set_checked | .+.......................> | verification.claims.required_set | -+---------------------------| |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | | |
| | | |
+--------------------------------------------------------------+ +-------------------------------------------------------------+ | |
| | v |
| | +-------------------------------------------------------------+ |
| | | verify.not_run | |
| | +-------------------------------------------------------------+ |
| | |
| +-------------------------------------------------------------+ |
| | | |
| +------------------------------------------------------------+ | |
| | verify.both_gates_clean_passes | | |
| +------------------------------------------------------------+ | |
| | |
| +-------------------------------------------------------------| |
| | | |
| +------------------------------------------------------------+ | |
| | verify.claim_complete_evidence_verifies | | |
| +------------------------------------------------------------+ | |
| | |
| +-------------------------------------------------------------| |
| | | |
| +------------------------------------------------------------+ | |
| | verify.false_claim_blocks_verified | | |
| +------------------------------------------------------------+ | |
| | |
| +-------------------------------------------------------------| |
| | | |
| +------------------------------------------------------------+ | |
| | verify.fragments_reach_verified | | |
| +------------------------------------------------------------+ | |
| | |
+--------------------------------------------------------------+ | |
| | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | |
| verify.hang_reported_as_labeled_timeout | .+.......................> | verify.bounded_wall_clock | -+----------------------------------------------------------------------------------------------------------------------|
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | |
| | |
+--------------------------------------------------------------+ +-------------------------------------------------------------| |
| | | |
| +------------------------------------------------------------+ | |
| | verify.release_docs_match_implemented_capabilities | | |
| +------------------------------------------------------------+ | |
| | |
+--------------------------------------------------------------+ | |
| | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | |
| verify.rerun_matches_prior_outcome | .+.......................> | verify.verify_is_idempotent | -+----------------------------------------------------------------------------------------------------------------------+
+-------------------------------------------------------------+ | +------------------------------------------------------------+ | |
| | |
| | |
| | |
+-------------------------------------------------------------+ properties.derives_from +------------------------------------------------------------+ | |
| verify.shrunk_counterexample_reported | .+.......................> | verify.failure_reports_shrunk_counterexample | -+----------------------------------------------------------------------------------------------------------------------+
+-------------------------------------------------------------+ | +------------------------------------------------------------+ |
| |
+--------------------------------------------------------------+ +-------------------------------------------------------------+
| | |
| +------------------------------------------------------------+ |
| | verify.stale_report_rejected_with_rerun_hint | |
| +------------------------------------------------------------+ |
| |
| +-------------------------------------------------------------|
| | |
| +------------------------------------------------------------+ |ransitions.from +-------------------------------------------------------------+
| | linter.coverage.begin | -+-----------------------> | linter.coverage.unchecked |
| +------------------------------------------------------------+ | +-------------------------------------------------------------+
| |
+--------------------------------------------------------------+ |
| |
+-------------------------------------------------------------+ |ransitions.to +------------------------------------------------------------+ |
| linter.graph_shape.build_graph | -+-----------------------> | linter.graph_shape.built | |
+-------------------------------------------------------------+ | +------------------------------------------------------------+ |
| | ^ |
+--------------------------------------------------------------+ +-------------------------------------------------------------+
| |
| |
| |
| |
| |
| |
| |
| +------------------------------------------------------------+
| | linter.graph_shape.traverse |
| +------------------------------------------------------------+
|
+--------------------------------------------------------------+
v |
+-------------------------------------------------------------+ |
| linter.graph_shape.unbuilt | |
+-------------------------------------------------------------+ |
|
+--------------------------------------------------------------+
|
+-------------------------------------------------------------+ transitions.from +------------------------------------------------------------+
| orchestrate.start_lint | -------------------------> | orchestrate.idle |
+-------------------------------------------------------------+ +------------------------------------------------------------+
+-------------------------------------------------------------+
| node |
+-------------------------------------------------------------+
Rebuilding the views
The views live in docs/src/views/ (gitignored — build-time artifacts,
never committed; if they disagree with the corpus, the corpus wins and
they get regenerated). just docs-graphs regenerates all four — plus
the two ASCII renderings (wiring.ascii, graph.ascii; a missing
gently degrades to a labeled note, never a silently clean artifact) —
before the docs build; the same commands by hand:
spk graph specs --format edges > edges.tsv
spk graph specs --json > graph.json
spk guide --schema --json > schema.json
python3 scripts/graph_views.py schema schema.json
python3 scripts/graph_views.py states edges.tsv --graph graph.json
python3 scripts/graph_views.py trace edges.tsv --graph graph.json
spk graph specs --view wiring --format mermaid > wiring.md
The DOT projections render externally (add-graph-views D8) — into Graphviz, or into the family’s native Graph::Easy port:
spk graph openspec/specs --format dot | dot -Tsvg > graph.svg
spk graph openspec/specs --format dot | gently --as ascii # in-terminal diagram
spk graph openspec/specs --format dot | gently --as html # styled table markup
gently parses the DOT projection natively (--as picks the format);
rendering stays a user-chosen pipe — spk never shells out to a
renderer (decision record: openspec/decisions/2026-10-16-gently-render-boundary.md).
For your own corpora, the one-pipe render recipes are in
spk explain graph-views.
linter.frontmatter
id: linter.frontmatter kind: intent statement: “WHEN a spec file is parsed, THE linter SHALL reject it unless its frontmatter declares id, kind, and statement, with kind equal to intent.”
Linter: Frontmatter Check
The first gate every spec file passes through. Every other linter check
assumes this one already succeeded — a file with no valid id has nothing
for later checks to key off of.
Purpose
The first gate every spec file passes through. Every other linter check
assumes this one already succeeded — a file with no valid id has nothing
for later checks to key off of.
Constraints
| id | kind | expr | traces_to | satisfies |
|——————|———–|–––––––––––––––––––––––––––––––––––|————————————|
| has_id | invariant | frontmatter.id != null and matches(id, /^[a-z][a-z0-9_.]*$/) | [[linter.frontmatter]] | |
| has_kind | invariant | frontmatter.kind == "intent" | [[linter.frontmatter]] | |
| has_statement | invariant | frontmatter.statement != null and len(statement) > 0 | [[linter.frontmatter]] | |
| id_matches_file | invariant | frontmatter.id == expected_id_from_path(path) — spec.md derives from its parent directory, any other file from its stem (Revision 18) | [[linter.frontmatter]] | |
| check_failure | effect | linter.frontmatter.check_failure(detail) | [[linter.frontmatter]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
uncheckedcheckingpassedfailed(emits:[[linter.frontmatter.check_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| begin | unchecked | checking | file exists and is UTF-8 markdown |
| accept | checking | passed | [[linter.frontmatter.has_id]] ∧ [[linter.frontmatter.has_kind]] ∧ [[linter.frontmatter.has_statement]] ∧ [[linter.frontmatter.id_matches_file]] |
| reject | checking | failed | ¬([[linter.frontmatter.has_id]] ∧ [[linter.frontmatter.has_kind]] ∧ [[linter.frontmatter.has_statement]] ∧ [[linter.frontmatter.id_matches_file]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| missing_id_rejected | unit | [[linter.frontmatter.has_id]] | frontmatter_without("id") | check(frontmatter) == failed |
| wrong_kind_rejected | unit | [[linter.frontmatter.has_kind]] | frontmatter_with_kind(≠ "intent") | check(frontmatter) == failed |
| valid_passes | unit | [[linter.frontmatter.has_id]] | arbitrary_valid_frontmatter() | check(frontmatter) == passed |
| filename_mismatch | unit | [[linter.frontmatter.id_matches_file]] | (id, path) pairs where id ≠ expected_id_from_path(path) (stem files, spec.md files under their capability directory, and bare spec.md all covered) | check(id, path) == failed |
| missing_statement_rejected | unit | [[linter.frontmatter.has_statement]] | frontmatter_without("statement") and frontmatter_with(statement: "") | check(frontmatter) == failed — absent and empty are both rejections |
| check_failure_label_asserted | unit | [[linter.frontmatter.check_failure]] | check_failure_raised() | error_label == "linter.frontmatter.check_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
Reference-typing reconciliation (2026-09-30, specodelic-cxq): the
Constraints table’s traces_to cells previously pointed at the
specodelic.md rows these checks re-own — a Constraint→Constraint target,
which the Reference Typing table forbids (traces_to resolves to Intent
only). Each now traces to this file’s own intent: specodelic.md keeps
the corpus-wide statement of record, this file owns the checkable one
(the “same claim, two altitudes” pattern the Notes below already use).
Revision 18 (specodelic-mcy, 2026-10-08): id_matches_file’s
expected_id_from_filename(path) became expected_id_from_path(path) —
a file named spec.md (the openspec-mandated filename) derives its
expected id from its PARENT DIRECTORY name, so a single-tree
openspec/specs/<cap>/spec.md layout carries real ids and id: spec
retires (it survives only as the stem-fallback for a bare spec.md
with no parent directory). The gap flagged below is long resolved:
specodelic.md’s Constraints table has carried the invariant since
Revision 2, and it applies to every spec file, top-level or not.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: missing_id_rejected
- WHEN
frontmatter_without("id") - THEN
check(frontmatter) == failed - VERIFIES [[linter.frontmatter.missing_id_rejected]]
Scenario: wrong_kind_rejected
- WHEN
frontmatter_with_kind(≠ "intent") - THEN
check(frontmatter) == failed - VERIFIES [[linter.frontmatter.wrong_kind_rejected]]
Scenario: valid_passes
- WHEN
arbitrary_valid_frontmatter() - THEN
check(frontmatter) == passed - VERIFIES [[linter.frontmatter.valid_passes]]
Scenario: filename_mismatch
- WHEN
(id, path)pairs whereid ≠ expected_id_from_path(path)(stem files, spec.md files under their capability directory, and bare spec.md all covered) - THEN
check(id, path) == failed - VERIFIES [[linter.frontmatter.filename_mismatch]]
Scenario: missing_statement_rejected
- WHEN
frontmatter_without("statement")andfrontmatter_with(statement: "") - THEN
check(frontmatter) == failed— absent and empty are both rejections - VERIFIES [[linter.frontmatter.missing_statement_rejected]]
Scenario: check_failure_label_asserted
- WHEN
check_failure_raised() - THEN
error_label == "linter.frontmatter.check_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.frontmatter.check_failure_label_asserted]]
linter.schema_shape
id: linter.schema_shape kind: intent statement: “WHEN a spec file’s frontmatter has passed, THE linter SHALL reject it if any 𝒦-governed id-set (a variant table, a Constraint/Property kind value-set, or the Reference Typing table’s field set) shrinks, reorders, or silently retypes an existing member across revisions, if a Constraint or Property row’s own kind value falls outside its closed set, or if the parser has touched content inside a rationale/description field.”
Linter: Schema Shape Check
Runs after linter.frontmatter only, in parallel with
linter.ears_syntax and the referential/graph/model branch — per
specodelic.md’s Checker Ownership table, this checker and
linter.ears_syntax are independent terminal nodes that both feed into
linted, not a sequential continuation of one another.
Note this checker’s scope narrowed relative to its first mention in the
decomposition plan: no_boolean_columns ended up owned by
linter-model_shape.md instead, scoped specifically to state/transition
rows where a boolean column would actually manifest as an ad-hoc state
machine. What’s left here is schema evolution discipline
(append_only_variants), the closed-set discipline on the kind column
that Constraint and Property rows each separately carry (see kinds.md),
and the parser’s own restraint around prose (prose_untouched) — three
properties about how the tooling behaves across time, across the
kind column specifically, and across fields generally, rather than
about any single file’s current shape.
Purpose
Runs after linter.frontmatter only, in parallel with
linter.ears_syntax and the referential/graph/model branch — per
specodelic.md’s Checker Ownership table, this checker and
linter.ears_syntax are independent terminal nodes that both feed into
linted, not a sequential continuation of one another.
Note this checker’s scope narrowed relative to its first mention in the
decomposition plan: no_boolean_columns ended up owned by
linter-model_shape.md instead, scoped specifically to state/transition
rows where a boolean column would actually manifest as an ad-hoc state
machine. What’s left here is schema evolution discipline
(append_only_variants), the closed-set discipline on the kind column
that Constraint and Property rows each separately carry (see kinds.md),
and the parser’s own restraint around prose (prose_untouched) — three
properties about how the tooling behaves across time, across the
kind column specifically, and across fields generally, rather than
about any single file’s current shape.
Constraints
| id | kind | expr | traces_to | satisfies |
|–––––––––––––|———–|–––––––––––––––––––––––––––––––––––––––––––––––––|—————————————————–|
| id_set_grows_only | invariant | ∀ 𝒦-governed id-set S (a variant-table's ids, a Constraint/Property row's own kind value-set, or the Reference Typing table's field set), revision r < r': S(r) ⊆ S(r'), appended only under a new Revision heading — no member removed or renumbered; a member's typing may narrow only in the same Revision that introduces the kind-split it depends on, and only if it invalidates nothing valid at r | [[linter.schema_shape]] | |
| id_set_order_stable | invariant | ∀ 𝒦-governed id-set S, revision r < r': the relative order of members present in both r and r' is unchanged | [[linter.schema_shape]] | |
| constraint_kind_closed | invariant | ∀ Constraint row: row.kind ∈ {invariant, advisory, effect, extension_point}, extended per-file by the active packs' declared kinds (openspec/specs/packs/spec.md fiber kinds — base ∪ active-pack-fiber, never narrower; without the pack active the token stays outside the closed set and the labeled finding fires) — see [[kinds.constraint_row_shape]] | [[linter.schema_shape]] | |
| property_kind_closed | invariant | ∀ Property row: row.kind ∈ {unit, law}, extended per-file by the active packs' declared kinds (openspec/specs/packs/spec.md fiber kinds — base ∪ active-pack-fiber, never narrower; without the pack active the token stays outside the closed set and the labeled finding fires) — see [[kinds.property_row_shape]] | [[linter.schema_shape]] | |
| no_prose_field_parsed | invariant | the parser's AST never branches on the text content of a rationale/description field | [[linter.schema_shape]] | |
| prose_field_passthrough | invariant | rationale/description content is stored verbatim and emitted verbatim in the compiled TOML | [[linter.schema_shape]] | |
| kind_check_failure | effect | linter.schema_shape.kind_check_failure(detail) | [[linter.schema_shape]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| diff_failure | effect | linter.schema_shape.diff_failure(detail) | [[linter.schema_shape]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| parser_audit_failure | effect | linter.schema_shape.parser_audit_failure(detail) | [[linter.schema_shape]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
uncheckedkind_checkingdiffingparser_auditedpassedkind_failed(emits:[[linter.schema_shape.kind_check_failure]])diff_failed(emits:[[linter.schema_shape.diff_failure]])parser_audit_failed(emits:[[linter.schema_shape.parser_audit_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| begin | unchecked | kind_checking | file passed linter.frontmatter |
| kind_ok | kind_checking | diffing | [[linter.schema_shape.constraint_kind_closed]] ∧ [[linter.schema_shape.property_kind_closed]] |
| kind_fail | kind_checking | kind_failed | ¬([[linter.schema_shape.constraint_kind_closed]] ∧ [[linter.schema_shape.property_kind_closed]]) |
| diff_ok | diffing | parser_audited | [[linter.schema_shape.id_set_grows_only]] ∧ [[linter.schema_shape.id_set_order_stable]] |
| diff_skip | diffing | parser_audited | no prior revision exists in repo history — nothing to diff against yet |
| diff_fail | diffing | diff_failed | ¬([[linter.schema_shape.id_set_grows_only]] ∧ [[linter.schema_shape.id_set_order_stable]]) |
| accept | parser_audited | passed | [[linter.schema_shape.no_prose_field_parsed]] ∧ [[linter.schema_shape.prose_field_passthrough]] |
| reject | parser_audited | parser_audit_failed | ¬([[linter.schema_shape.no_prose_field_parsed]] ∧ [[linter.schema_shape.prose_field_passthrough]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| shrunk_id_set_rejected | unit | [[linter.schema_shape.id_set_grows_only]] | (revision_n, revision_n_plus_1) where a member was removed from a variant table, a kind value-set, or the Reference Typing table | check(revisions) == failed — one generator, parametrized over which of the three id-sets it targets |
| reordered_id_set_rejected | unit | [[linter.schema_shape.id_set_order_stable]] | (revision_n, revision_n_plus_1) where two existing members of any 𝒦-governed id-set swapped order | check(revisions) == failed |
| silent_narrowing_rejected | unit | [[linter.schema_shape.id_set_grows_only]] | (revision_n, revision_n_plus_1) where a Reference Typing field’s target narrowed with no accompanying kind-split cited | check(revisions) == failed |
| constraint_kind_invalid_rejected | unit | [[linter.schema_shape.constraint_kind_closed]] | constraint_row_with(kind: "made_up") | check(file) == failed |
| property_kind_invalid_rejected | unit | [[linter.schema_shape.property_kind_closed]] | property_row_with(kind: "audit") | check(file) == failed |
| kind_closed_passes | unit | [[linter.schema_shape.constraint_kind_closed]] | arbitrary_file_with(only_closed_kind_values: true) | check(file) == passed |
| prose_roundtrips | unit | [[linter.schema_shape.prose_field_passthrough]] | arbitrary_unicode_string() as rationale content | compile(parse(s)).rationale == s — no normalization, no truncation |
| appended_id_set_member_passes | unit | [[linter.schema_shape.id_set_grows_only]] | (revision_n, revision_n_plus_1) where only new members were added, to any of the three id-sets | check(revisions) == passed |
| constraint_kind_advisory_passes | unit | [[linter.schema_shape.constraint_kind_closed]] | constraint_row_with(kind: "advisory") | check(file) == passed |
| constraint_kind_effect_passes | unit | [[linter.schema_shape.constraint_kind_closed]] | constraint_row_with(kind: "effect") | check(file) == passed |
| constraint_kind_extension_point_passes | unit | [[linter.schema_shape.constraint_kind_closed]] | constraint_row_with(kind: "extension_point") | check(file) == passed |
| parser_ast_prose_free | unit | [[linter.schema_shape.no_prose_field_parsed]] | spec_file_with(prose_in_rationale_and_description: arbitrary_unicode_string()) | ∀ parsed row: the row's parsed fields contain no fragment of the planted prose — a planted-prose differential parse (see Notes) |
| kind_check_failure_label_asserted | unit | [[linter.schema_shape.kind_check_failure]] | kind_check_failure_raised() | error_label == "linter.schema_shape.kind_check_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| diff_failure_label_asserted | unit | [[linter.schema_shape.diff_failure]] | diff_failure_raised() | error_label == "linter.schema_shape.diff_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| parser_audit_failure_label_asserted | unit | [[linter.schema_shape.parser_audit_failure]] | parser_audit_failure_raised() | error_label == "linter.schema_shape.parser_audit_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
Fiber-kind acceptance (2026-10-03, beads specodelic-ung): the two
closed-set walkers above walk the static base sets — a pack-declared
fiber kind (e.g. empirical.statistic, the empirical-registry pack’s
property kind, which targets the base Properties kind column
directly) fired the finding even with the pack discovered. The
walkers’ effective set is now base ∪ active-pack-fiber: the ## Kinds
tokens of every pack active for the file (declared uses edge or
vocabulary match, the advisory-first activation rule in
openspec/specs/packs/spec.md). Never narrower than the base set — a base-set member
is accepted unconditionally, the base sets themselves do not grow
(id_set_grows_only untouched, no format Revision), and a fiber kind
with no active declaring pack stays outside the closed set, firing the
labeled finding — never a silent pass. The existing acceptance rows
(constraint_kind_invalid_rejected, property_kind_invalid_rejected,
kind_closed_passes) keep their meaning: made_up/audit are still
outside any effective set, declared or not.
Reference-typing reconciliation (2026-09-30, specodelic-cxq): the
Constraints table’s traces_to cells previously pointed at the
specodelic.md rows these checks re-own — a Constraint→Constraint target,
which the Reference Typing table forbids (traces_to resolves to Intent
only). Each now traces to this file’s own intent: specodelic.md keeps
the corpus-wide statement of record, this file owns the checkable one
(the “same claim, two altitudes” pattern the Notes below already use).
Resolves the follow-up kinds.md recorded when it landed: that file
defined property_row_shape’s kind ∈ {unit, law} and
constraint_row_shape’s kind ∈ {invariant}, and specodelic.md
Revision 4 added the corresponding top-level invariants
(constraint_kind_closed, property_kind_closed), but no checker
enforced either yet. This file now does, as the first gate after
linter.frontmatter and before the existing revision-diffing — a Property
row with an invalid kind should fail here regardless of whether the file
has any revision history to diff against at all, which is why
kind_checking sits ahead of diffing rather than folded into it: the
two checks have genuinely different preconditions (kind_checking needs
only the current file; diffing needs a prior revision, hence the new
diff_skip edge for a file with none).
no_prose_field_parsed was originally framed as a property of the
parser’s implementation, not of any spec file’s content — “no generator
can produce a bad spec file to test this against, only an audit of whether
the parser code path for rationale/description ever appears on the
left side of a conditional.” The parser_ast_prose_free row above
reflects the pre-decision audit framing and predates the decision below.
Decision of record (2026-09-30, user-approved; HITL ticket
specodelic-mp1 row 5, EXCL-001): the audit framing was wrong — the
claim is mechanically testable by a planted-prose differential parse
(generate a spec with prose in rationale/description, parse, assert
no parsed row contains it). The property is to be rewritten as a runnable
unit row on that basis (lands with specodelic-cxq); no audit kind
was added — kinds.md Revision 6 records the dissolution and {unit, law} stays closed. This row’s Needs Human Review flag is retired.
Landed here with specodelic-cxq: parser_ast_prose_free is now
the runnable planted-prose differential parse (generate a spec with prose
in rationale/description, parse, assert no parsed row contains it) —
no generator-preclusion claim remains.
Checker Ownership table in specodelic.md is now fully accounted for
except linter-coverage.md, the last remaining file.
Previous pass (resolving specodelic.md Revision 5’s two pending
items): constraint_kind_closed widened to {invariant, advisory} in
step with kinds.md Revision 2, and a reference_table_grows_only
constraint was added for the Reference Typing table specifically.
This pass (specodelic.md Revision 6): that separate
reference_table_grows_only constraint, along with the older
variant_set_grows_only/variant_order_stable pair, is retired and
replaced by the two rows above — id_set_grows_only and
id_set_order_stable — stated once, over any 𝒦-governed id-set, rather
than three times over three id-sets that all follow the identical rule.
No enforcement was lost: the same three id-sets (variant tables, kind
value-sets, the Reference Typing table) are still covered, by the same
diffing state and the same diff_skip fallback for a file with no prior
revision to diff against — only the naming collapsed from three
constraints to two. constraint_kind_closed widens again, to
{invariant, advisory, effect}, in step with kinds.md Revision 3.
no_prose_field_parsed remains the one row here still marked Needs Human Review; nothing in this pass touches it. (Historical note: the flag was
retired 2026-09-30 — see the decision of record above; this paragraph
predates that.)
This pass (specodelic.md Revision 7): constraint_kind_closed
widens a third time, to {invariant, advisory, effect, extension_point},
in step with kinds.md Revision 4. constraint_kind_extension_point_passes
added as the matching acceptance case, same shape as the advisory/
effect rows above it. Nothing else in this file changes: id_set_grows_only
already covers any Constraint-kind value-set widening generically, so an
extension_point addition is diffed by the same diffing state as any
other, with no new branch.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: shrunk_id_set_rejected
- WHEN
(revision_n, revision_n_plus_1)where a member was removed from a variant table, a kind value-set, or the Reference Typing table - THEN
check(revisions) == failed— one generator, parametrized over which of the three id-sets it targets - VERIFIES [[linter.schema_shape.shrunk_id_set_rejected]]
Scenario: reordered_id_set_rejected
- WHEN
(revision_n, revision_n_plus_1)where two existing members of any 𝒦-governed id-set swapped order - THEN
check(revisions) == failed - VERIFIES [[linter.schema_shape.reordered_id_set_rejected]]
Scenario: silent_narrowing_rejected
- WHEN
(revision_n, revision_n_plus_1)where a Reference Typing field’s target narrowed with no accompanying kind-split cited - THEN
check(revisions) == failed - VERIFIES [[linter.schema_shape.silent_narrowing_rejected]]
Scenario: constraint_kind_invalid_rejected
- WHEN
constraint_row_with(kind: "made_up") - THEN
check(file) == failed - VERIFIES [[linter.schema_shape.constraint_kind_invalid_rejected]]
Scenario: property_kind_invalid_rejected
- WHEN
property_row_with(kind: "audit") - THEN
check(file) == failed - VERIFIES [[linter.schema_shape.property_kind_invalid_rejected]]
Scenario: kind_closed_passes
- WHEN
arbitrary_file_with(only_closed_kind_values: true) - THEN
check(file) == passed - VERIFIES [[linter.schema_shape.kind_closed_passes]]
Scenario: prose_roundtrips
- WHEN
arbitrary_unicode_string()as rationale content - THEN
compile(parse(s)).rationale == s— no normalization, no truncation - VERIFIES [[linter.schema_shape.prose_roundtrips]]
Scenario: appended_id_set_member_passes
- WHEN
(revision_n, revision_n_plus_1)where only new members were added, to any of the three id-sets - THEN
check(revisions) == passed - VERIFIES [[linter.schema_shape.appended_id_set_member_passes]]
Scenario: constraint_kind_advisory_passes
- WHEN
constraint_row_with(kind: "advisory") - THEN
check(file) == passed - VERIFIES [[linter.schema_shape.constraint_kind_advisory_passes]]
Scenario: constraint_kind_effect_passes
- WHEN
constraint_row_with(kind: "effect") - THEN
check(file) == passed - VERIFIES [[linter.schema_shape.constraint_kind_effect_passes]]
Scenario: constraint_kind_extension_point_passes
- WHEN
constraint_row_with(kind: "extension_point") - THEN
check(file) == passed - VERIFIES [[linter.schema_shape.constraint_kind_extension_point_passes]]
Scenario: parser_ast_prose_free
- WHEN
spec_file_with(prose_in_rationale_and_description: arbitrary_unicode_string()) - THEN
∀ parsed row: the row's parsed fields contain no fragment of the planted prose— a planted-prose differential parse (see Notes) - VERIFIES [[linter.schema_shape.parser_ast_prose_free]]
Scenario: kind_check_failure_label_asserted
- WHEN
kind_check_failure_raised() - THEN
error_label == "linter.schema_shape.kind_check_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.schema_shape.kind_check_failure_label_asserted]]
Scenario: diff_failure_label_asserted
- WHEN
diff_failure_raised() - THEN
error_label == "linter.schema_shape.diff_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.schema_shape.diff_failure_label_asserted]]
Scenario: parser_audit_failure_label_asserted
- WHEN
parser_audit_failure_raised() - THEN
error_label == "linter.schema_shape.parser_audit_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.schema_shape.parser_audit_failure_label_asserted]]
linter.ears_syntax
id: linter.ears_syntax kind: intent statement: “WHEN a spec file’s frontmatter has passed, THE linter SHALL reject it if the intent statement does not match one of the five EARS patterns or if any row id joins two capabilities.”
Linter: EARS Syntax Check
Runs after linter.frontmatter only — unlike the other checks, it doesn’t
need the reference graph or model, since it operates purely on the
statement string and the shape of id cells. Corresponds to
testability-implementability-evaluator’s EARS-compliance and
NLP-anti-pattern steps, narrowed to what can be checked structurally rather
than by judging prose quality.
Purpose
Runs after linter.frontmatter only — unlike the other checks, it doesn’t
need the reference graph or model, since it operates purely on the
statement string and the shape of id cells. Corresponds to
testability-implementability-evaluator’s EARS-compliance and
NLP-anti-pattern steps, narrowed to what can be checked structurally rather
than by judging prose quality.
Constraints
| id | kind | expr | traces_to | satisfies |
|–––––––––––|———–|–––––––––––––––––––––––––––––––––––––––––––––––––––––––|––––––––––––––––––––––––––|
| ears_pattern_match | invariant | statement matches one of: Ubiquitous / Event-Driven / State-Driven / Unwanted-Behavior / Optional-Feature | [[linter.ears_syntax]] | |
| has_shall | invariant | statement contains an imperative "SHALL" (or "SHALL NOT") | [[linter.ears_syntax]] | |
| no_conjoined_id | invariant | ∀ row.id: id does not encode two capabilities joined by "and"/"or" (checked on the id token, not prose) — the top-level rule this row re-owns from specodelic.md’s higher-altitude one_capability_per_row restatement | [[linter.ears_syntax]]| |
| no_universal_in_id | invariant | ∀ row.id: id does not contain "all"/"every"/"any"/"always"/"never" as a token — applies to every table row id (Intent, Constraint, State, Transition, Property), not only Constraint rows; the top-level rule this row re-owns from specodelic.md’s higher-altitude one_capability_per_row restatement | [[linter.ears_syntax]]| |
| pattern_failure | effect | linter.ears_syntax.pattern_failure(detail) | [[linter.ears_syntax]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| id_check_failure | effect | linter.ears_syntax.id_check_failure(detail) | [[linter.ears_syntax]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
uncheckedpattern_matchingid_checkingpassedpattern_failed(emits:[[linter.ears_syntax.pattern_failure]])id_check_failed(emits:[[linter.ears_syntax.id_check_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| begin | unchecked | pattern_matching | file passed linter.frontmatter |
| pattern_ok | pattern_matching | id_checking | [[linter.ears_syntax.ears_pattern_match]] ∧ [[linter.ears_syntax.has_shall]] |
| pattern_fail | pattern_matching | pattern_failed | ¬([[linter.ears_syntax.ears_pattern_match]] ∧ [[linter.ears_syntax.has_shall]]) |
| accept | id_checking | passed | [[linter.ears_syntax.no_conjoined_id]] ∧ [[linter.ears_syntax.no_universal_in_id]] |
| reject | id_checking | id_check_failed | ¬([[linter.ears_syntax.no_conjoined_id]] ∧ [[linter.ears_syntax.no_universal_in_id]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| non_ears_rejected | unit | [[linter.ears_syntax.ears_pattern_match]] | statement_not_matching_any_ears_pattern() | check(file) == failed |
| missing_shall_rejected | unit | [[linter.ears_syntax.has_shall]] | statement_matching_pattern_but_missing("SHALL") | check(file) == failed |
| conjoined_id_rejected | unit | [[linter.ears_syntax.no_conjoined_id]] | row_with(id: "order.cancel_and_refund") | check(file) == failed |
| universal_token_rejected | unit | [[linter.ears_syntax.no_universal_in_id]] | row_with(id: "order.always_validate") | check(file) == failed |
| valid_ears_passes | unit | [[linter.ears_syntax.ears_pattern_match]] | arbitrary_ears_compliant_statement() | check(file) == passed |
| pattern_failure_label_asserted | unit | [[linter.ears_syntax.pattern_failure]] | pattern_failure_raised() | error_label == "linter.ears_syntax.pattern_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| id_check_failure_label_asserted | unit | [[linter.ears_syntax.id_check_failure]] | id_check_failure_raised() | error_label == "linter.ears_syntax.id_check_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
This check deliberately does not attempt weak-phrase detection
(“adequate,” “as appropriate”), passive-voice detection, or non-specific
temporals (“immediately,” “quickly”) — those are exactly the prose-quality
judgments specodelic.md’s prose_untouched invariant puts out of the
linter’s scope, and are correctly testability-implementability-evaluator’s
job to run separately over the compiled statement field, not this
schema’s job to structurally forbid. The line drawn here: EARS pattern
conformance is a grammar fact (does the sentence have the right shape),
weak-phrase/temporal/passive-voice detection is a judgment about word
choice within a grammatically valid sentence — the former belongs in the
linter, the latter doesn’t.
no_conjoined_id/no_universal_in_id check the id token, not the
prose statement — e.g. rejecting id: "cancel_and_refund" structurally,
while a compound statement in prose still isn’t caught (correctly, since
that would require judging prose). Scope decision of record (2026-09-30,
specodelic-cxq): the check covers every table row id — Intent,
Constraint, State, Transition, and Property rows alike. A property named
law_cases_unexecuted or a transition named scan_effects carries the same
compound/universal-token problem a constraint id does, so the corpus’s
six such rows were renamed in the same change rather than carving the
row kinds out of the rule. This is narrower than
specification-evaluation-diagnostician’s “Compound Requirements” finding,
which also reads sentence structure — worth flagging as a known gap this
checker doesn’t close, rather than one it silently claims to.
No new specodelic.md gaps surfaced this time — this is the first check
whose constraints all trace cleanly to invariants already on the list.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: non_ears_rejected
- WHEN
statement_not_matching_any_ears_pattern() - THEN
check(file) == failed - VERIFIES [[linter.ears_syntax.non_ears_rejected]]
Scenario: missing_shall_rejected
- WHEN
statement_matching_pattern_but_missing("SHALL") - THEN
check(file) == failed - VERIFIES [[linter.ears_syntax.missing_shall_rejected]]
Scenario: conjoined_id_rejected
- WHEN
row_with(id: "order.cancel_and_refund") - THEN
check(file) == failed - VERIFIES [[linter.ears_syntax.conjoined_id_rejected]]
Scenario: universal_token_rejected
- WHEN
row_with(id: "order.always_validate") - THEN
check(file) == failed - VERIFIES [[linter.ears_syntax.universal_token_rejected]]
Scenario: valid_ears_passes
- WHEN
arbitrary_ears_compliant_statement() - THEN
check(file) == passed - VERIFIES [[linter.ears_syntax.valid_ears_passes]]
Scenario: pattern_failure_label_asserted
- WHEN
pattern_failure_raised() - THEN
error_label == "linter.ears_syntax.pattern_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.ears_syntax.pattern_failure_label_asserted]]
Scenario: id_check_failure_label_asserted
- WHEN
id_check_failure_raised() - THEN
error_label == "linter.ears_syntax.id_check_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.ears_syntax.id_check_failure_label_asserted]]
linter.coverage
id: linter.coverage
kind: intent
statement: “WHEN a spec file’s graph shape and model shape have both passed, THE linter SHALL reject it if any constraint has no property deriving from it, or if any law-kind property fails to enumerate its required cases as machine-findable **name:** labels with the identity and associativity floor present.”
Linter: Coverage Check
The last checker in the Checker Ownership table — it needs graph_shape’s
acyclicity result to know the derives_from edges it’s counting form a
well-defined graph, and model_shape’s result to know every constraint it’s
checking coverage for is actually attached to a real transition, not a
dangling row that would make “100% coverage” a vacuous claim.
This is the check most directly aimed at the gap this whole exercise kept
surfacing — every prior checker file found something missing from
specodelic.md itself. Coverage is what would have caught those
mechanically instead of by manual inspection: id_matches_file,
ref_kind_compatible, single_root_reachable, and the model
well-formedness pair were all constraints with no corresponding property
until Revision 2 added both together. Had this checker existed first, the
gap and its fix would have been a single failing/passing pair, not two
separate passes.
Purpose
The last checker in the Checker Ownership table — it needs graph_shape’s
acyclicity result to know the derives_from edges it’s counting form a
well-defined graph, and model_shape’s result to know every constraint it’s
checking coverage for is actually attached to a real transition, not a
dangling row that would make “100% coverage” a vacuous claim.
This is the check most directly aimed at the gap this whole exercise kept
surfacing — every prior checker file found something missing from
specodelic.md itself. Coverage is what would have caught those
mechanically instead of by manual inspection: id_matches_file,
ref_kind_compatible, single_root_reachable, and the model
well-formedness pair were all constraints with no corresponding property
until Revision 2 added both together. Had this checker existed first, the
gap and its fix would have been a single failing/passing pair, not two
separate passes.
Constraints
| id | kind | expr | traces_to | satisfies |
|—————————|———–|———————————————————————————————–|———————————————–|
| every_constraint_covered | invariant | ∀ constraint c: ∃ property p. p.derives_from == c.id — the rule this row re-owns from specodelic.md’s higher-altitude restatement (see Notes) | [[linter.coverage]] | |
| every_law_has_cases | invariant | ∀ property p where p.kind == "law": p's predicate enumerates its required cases as **name:** case labels, and the label set includes identity and associativity — the rule this row re-owns from specodelic.md’s higher-altitude restatement (see Notes; machine form ratified in specodelic.md Revision 13) | [[linter.coverage]] | |
| no_orphan_property | invariant | ∀ property p: p.derives_from resolves to a real constraint (restates total_refs, scoped to this edge) | [[linter.coverage]] | |
| coverage_is_computable | invariant | the derives_from multiplicity per constraint is countable in finite time from the parsed AST alone | [[linter.coverage]] | |
| count_failure | effect | linter.coverage.count_failure(detail) | [[linter.coverage]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| law_case_failure | effect | linter.coverage.law_case_failure(detail) | [[linter.coverage]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
uncheckedcountinglaw_checkingpassedcount_failed(emits:[[linter.coverage.count_failure]])law_check_failed(emits:[[linter.coverage.law_case_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| begin | unchecked | counting | file passed linter.graph_shape and linter.model_shape |
| count_ok | counting | law_checking | [[linter.coverage.every_constraint_covered]] ∧ [[linter.coverage.no_orphan_property]] |
| count_fail | counting | count_failed | ¬([[linter.coverage.every_constraint_covered]] ∧ [[linter.coverage.no_orphan_property]]) |
| accept | law_checking | passed | [[linter.coverage.every_law_has_cases]] |
| reject | law_checking | law_check_failed | ¬([[linter.coverage.every_law_has_cases]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| uncovered_constraint_rejected | unit | [[linter.coverage.every_constraint_covered]] | spec_file_with(constraint_with_no_deriving_property: true) | check(file) == failed |
| orphan_property_rejected | unit | [[linter.coverage.no_orphan_property]] | spec_file_with(property.derives_from_pointing_at_nonexistent_id: true) | check(file) == failed |
| incomplete_law_rejected | unit | [[linter.coverage.every_law_has_cases]] | law_property_missing("identity") | check(file) == failed |
| full_coverage_passes | unit | [[linter.coverage.every_constraint_covered]] | arbitrary_fully_covered_spec_file() | check(file) == passed |
| coverage_naturality | law | [[specodelic.rename_naturality]] | arbitrary_spec_file(), arbitrary_id_rename() | identity: coverage_ratio(coverage_ratio_placeholder_renamed_to_itself) == coverage_ratio(placeholder) — the rename identity case, instantiated at the coverage_ratio observation point associativity: coverage_ratio(rename(rename(I, a, b), b, c)) == coverage_ratio(rename(I, a, c)) — the rename associativity case at the same observation point naturality: coverage_ratio(rename(I)) == coverage_ratio(I) — renaming a constraint doesn’t change whether it’s covered |
| computed_derives_from_rejected | unit | [[linter.coverage.coverage_is_computable]] | spec_file_with(computed_or_templated_derives_from_id: true) | check(file) == failed — a non-literal id cannot be counted from the parsed AST alone |
| count_failure_label_asserted | unit | [[linter.coverage.count_failure]] | count_failure_raised() | error_label == "linter.coverage.count_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| law_case_failure_label_asserted | unit | [[linter.coverage.law_case_failure]] | law_case_failure_raised() | error_label == "linter.coverage.law_case_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| unlabeled_law_rejected | unit | [[linter.coverage.every_law_has_cases]] | law_predicate_mentioning_cases_without_labels() | check(file) == failed — a prose mention of a case name is not an enumeration (Revision 13 machine form) |
| extra_case_is_declaration | unit | [[linter.coverage.every_law_has_cases]] | law_property_with_extra_case("commutativity") | compile(file) emits one block per enumerated case — an extra named case is a first-class checkable declaration, never prose |
Notes
Machine-form ratification (2026-10-01, specodelic-9qw):
every_law_has_cases now executes specodelic.md Revision 13: a law
row’s required cases are whatever its predicate enumerates as
**name:** case labels — the exact form compile’s
required_law_cases has always parsed — with the identity and
associativity floor mandatory. A predicate that only mentions a case
name in prose is not an enumeration and fails the check; the label set
may exceed the floor freely (see extra_case_is_declaration), which
is what makes extra cases (commutativity, idempotence, …) checkable
declarations that compile one proptest block each.
Reference-typing reconciliation (2026-09-30, specodelic-cxq):
every_constraint_covered, every_law_has_cases, no_orphan_property,
and coverage_is_computable previously pointed their traces_to at the
specodelic.md rows they re-own ([[specodelic.coverage]],
[[specodelic.law_requires_cases]]) — a Constraint→Constraint target,
which the Reference Typing table forbids (traces_to resolves to Intent
only). Each now traces to [[linter.coverage]], its own file’s intent —
the same re-anchoring linter-referential_integrity.md’s
rename_naturality Notes call the “same claim, two altitudes” pattern:
specodelic.md keeps the corpus-wide statement of record, this file owns
the checkable one.
Decision of record (2026-09-30, specodelic-cxq): coverage_naturality
was one of the three law rows lacking the identity/associativity cases
[[specodelic.law_requires_cases]] requires. Its identity case is the
trivial instantiations rename(I, a, a) == I at the coverage_ratio
observation point; the naturality case remains the substantive one —
renaming a constraint doesn’t change whether it’s covered.
coverage_is_computable is worth stating explicitly even though it looks
tautological: it’s the thing that fails if expr fields were ever allowed
to contain arbitrary logic requiring evaluation to determine
derives_from targets dynamically (e.g. a templated or generated id).
specodelic.md’s decision to keep id values as static string literals
in every table row — never computed — is what makes this checker decidable
at all. If that ever changes, this constraint is the one that breaks first
and should be treated as a tripwire, not just a check.
No new gaps in specodelic.md surfaced this time — this is the third
checker of seven to close clean, and notably the one built specifically to
catch what the first four checkers found by hand. That its own pass here
is clean is a weaker signal than it looks: it confirms specodelic.md’s
constraint list is self-consistent as of Revision 2, not that Revision 2
is complete — a coverage check can only tell you every constraint present
has a property; it cannot tell you a constraint is missing, which is
exactly the class of gap the first four checkers caught by inspection
rather than by any mechanism this framework has yet.
All eight gating checker files in the Checker Ownership table now
exist (linter-frontmatter, linter-referential_integrity,
linter-graph_shape, linter-model_shape, linter-failure_shape,
linter-ears_syntax, linter-schema_shape, and linter-coverage.md
itself — this checker joined the table as a gating row once its checks
landed) — linted in specodelic.md is fully specified as the join
over all eight.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: uncovered_constraint_rejected
- WHEN
spec_file_with(constraint_with_no_deriving_property: true) - THEN
check(file) == failed - VERIFIES [[linter.coverage.uncovered_constraint_rejected]]
Scenario: orphan_property_rejected
- WHEN
spec_file_with(property.derives_from_pointing_at_nonexistent_id: true) - THEN
check(file) == failed - VERIFIES [[linter.coverage.orphan_property_rejected]]
Scenario: incomplete_law_rejected
- WHEN
law_property_missing("identity") - THEN
check(file) == failed - VERIFIES [[linter.coverage.incomplete_law_rejected]]
Scenario: full_coverage_passes
- WHEN
arbitrary_fully_covered_spec_file() - THEN
check(file) == passed - VERIFIES [[linter.coverage.full_coverage_passes]]
Scenario: coverage_naturality
- WHEN
arbitrary_spec_file(), arbitrary_id_rename() - THEN identity:
coverage_ratio(coverage_ratio_placeholder_renamed_to_itself) == coverage_ratio(placeholder)— the rename identity case, instantiated at the coverage_ratio observation point associativity:coverage_ratio(rename(rename(I, a, b), b, c)) == coverage_ratio(rename(I, a, c))— the rename associativity case at the same observation point naturality:coverage_ratio(rename(I)) == coverage_ratio(I)— renaming a constraint doesn’t change whether it’s covered - VERIFIES [[linter.coverage.coverage_naturality]]
Scenario: computed_derives_from_rejected
- WHEN
spec_file_with(computed_or_templated_derives_from_id: true) - THEN
check(file) == failed— a non-literal id cannot be counted from the parsed AST alone - VERIFIES [[linter.coverage.computed_derives_from_rejected]]
Scenario: count_failure_label_asserted
- WHEN
count_failure_raised() - THEN
error_label == "linter.coverage.count_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.coverage.count_failure_label_asserted]]
Scenario: law_case_failure_label_asserted
- WHEN
law_case_failure_raised() - THEN
error_label == "linter.coverage.law_case_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.coverage.law_case_failure_label_asserted]]
Scenario: unlabeled_law_rejected
- WHEN
law_predicate_mentioning_cases_without_labels() - THEN
check(file) == failed— a prose mention of a case name is not an enumeration (Revision 13 machine form) - VERIFIES [[linter.coverage.unlabeled_law_rejected]]
Scenario: extra_case_is_declaration
- WHEN
law_property_with_extra_case("commutativity") - THEN
compile(file) emits one block per enumerated case— an extra named case is a first-class checkable declaration, never prose - VERIFIES [[linter.coverage.extra_case_is_declaration]]
linter.graph_shape
id: linter.graph_shape kind: intent statement: “WHEN a spec repo has passed referential integrity, THE linter SHALL reject it if the traces_to/derives_from reference graph contains a cycle, or if the supersedes reference graph contains a cycle.”
Linter: Graph Shape Check
Runs after linter.referential_integrity — it needs every reference already
proven to resolve before it can walk the graph those references form.
Corresponds to the modularity-diagnostician’s “dependency graph must form a
DAG” check, applied to specs instead of code modules.
Purpose
Runs after linter.referential_integrity — it needs every reference already
proven to resolve before it can walk the graph those references form.
Corresponds to the modularity-diagnostician’s “dependency graph must form a
DAG” check, applied to specs instead of code modules.
Constraints
| id | kind | expr | traces_to | satisfies |
|———————–|———–|——————————————————————————————|––––––––––––––––––––––|
| acyclic | invariant | the directed graph formed by constraint-traces_to ∪ property-derives_from ∪ guard-as-edge has no cycle — the edge set is closed and property-sourced: derives_from edges are taken from Property rows only, because the Reference Typing table’s Appears-on column is normative (a Constraint-row derives_from is out-of-format — ref_kind_compatible’s beat, never an edge here; decided of record 2026-10-01, specodelic-huf) | [[linter.graph_shape]] | |
| single_root_reachable | invariant | ∀ constraint/property/state/transition row: reachable(row, the file's own intent row through own-file primary linkage) — same claim as [[specodelic.single_root_reachable]] at checker altitude | [[linter.graph_shape]] | |
| no_self_ref | invariant | ∀ row: row.traces_to != row.id and row.derives_from != row.id | [[linter.graph_shape]] | |
| supersedes_dag | invariant | the directed graph formed by supersedes edges alone (Constraint→Constraint, Property→Property) has no cycle | [[linter.graph_shape]] | |
| check_failure | effect | linter.graph_shape.check_failure(detail) | [[linter.graph_shape]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
unbuiltbuilttraversingpassedfailed(emits:[[linter.graph_shape.check_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| build_graph | unbuilt | built | repo passed linter.referential_integrity |
| traverse | built | traversing | graph built successfully |
| accept | traversing | passed | [[linter.graph_shape.acyclic]] ∧ [[linter.graph_shape.single_root_reachable]] ∧ [[linter.graph_shape.no_self_ref]] ∧ [[linter.graph_shape.supersedes_dag]] |
| reject | traversing | failed | ¬([[linter.graph_shape.acyclic]] ∧ [[linter.graph_shape.single_root_reachable]] ∧ [[linter.graph_shape.no_self_ref]] ∧ [[linter.graph_shape.supersedes_dag]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| cycle_rejected | unit | [[linter.graph_shape.acyclic]] | spec_repo_with(traces_to_cycle: length ≥ 2) | check(repo) == failed |
| self_ref_rejected | unit | [[linter.graph_shape.no_self_ref]] | spec_row_with(traces_to == self.id) | check(repo) == failed |
| orphan_subgraph_rejected | unit | [[linter.graph_shape.single_root_reachable]] | spec_repo_with(disconnected_constraint_cluster: true) | check(repo) == failed |
| dag_passes | unit | [[linter.graph_shape.acyclic]] | arbitrary_dag_shaped_repo() | check(repo) == passed |
| supersedes_cycle_rejected | unit | [[linter.graph_shape.supersedes_dag]] | spec_repo_with(supersedes_cycle: length ≥ 2) | check(repo) == failed |
| supersedes_dag_ignores_traces_cycle | unit | [[linter.graph_shape.supersedes_dag]] | spec_repo_with(traces_to_cycle: true, supersedes: acyclic) | check(repo).supersedes_dag == passed — the two graphs are checked independently; a cycle in one must not be attributed to the other |
| derives_from_edges_property_sourced | unit | [[linter.graph_shape.acyclic]] | spec_repo_with(constraint_row_carrying_derives_from: true) | check(repo) == failed — the malformed edge is typing’s finding (the graph layer reports it and records no edge), so it joins no acyclic edge set; the invariant and the checker now agree on the same closed edge set (specodelic-huf) |
| topo_sort_naturality | law | [[linter.graph_shape.acyclic]] | arbitrary_dag_repo(), arbitrary_id_rename() | identity: topo_sort(rename(I, a, a)) == topo_sort(I) — the rename identity case at the topo_sort observation point associativity: topo_sort(rename(rename(I, a, b), b, c)) == topo_sort(rename(I, a, c)) — the rename associativity case at the same point naturality: topo_sort(rename(I)) == rename(topo_sort(I)) — renaming a node doesn’t change relative order of unrelated nodes |
| check_failure_label_asserted | unit | [[linter.graph_shape.check_failure]] | check_failure_raised() | error_label == "linter.graph_shape.check_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
Reference-typing reconciliation (2026-09-30, specodelic-cxq): the
Constraints table’s traces_to cells previously pointed at the
specodelic.md rows these checks re-own — a Constraint→Constraint target,
which the Reference Typing table forbids (traces_to resolves to Intent
only). Each now traces to this file’s own intent: specodelic.md keeps
the corpus-wide statement of record, this file owns the checkable one
(the “same claim, two altitudes” pattern the Notes below already use).
single_root_reachable is new relative to specodelic.md’s original
acyclic_traces invariant — acyclicity alone permits an orphaned island of
constraints/properties that trace only to each other and never up to a real
intent. That’s not a cycle, but it is the spec-level equivalent of
modularity-diagnostician’s “missing boundary”: a cluster with no owning
purpose. The gap was folded back into specodelic.md’s constraint list,
and the open question this file raised (any intent vs the file’s own) was
decided of record 2026-09-30 (HITL specodelic-mp1 row 8): reachability
lands on the file’s OWN intent through own-file primary linkage, with
cross-file typed edges (guard citations of foreign constraints,
satisfies, observes) counted as outbound leaves, never reachability
paths — see specodelic.md Revision 10. This file’s constraint expr
reflects the decided reading.
topo_sort_naturality is included because the model-checking and
PBT-generation pipeline both need a stable topological order to compile
against — if renaming perturbed that order, generated artifacts (TLA+
modules, proptest! blocks) would churn on every unrelated rename, which
defeats the “refactor-safe end to end” property specodelic.md promises.
supersedes_dag (added per specodelic.md’s Revision 5) is kept as its
own constraint and its own conjunct in accept’s guard, rather than
unioned into acyclic’s edge set. Lineage (supersedes) and provenance
(traces_to/derives_from/guard) answer different questions about a
row, and a cycle in one says nothing about the other —
supersedes_dag_ignores_traces_cycle exists specifically to pin that
independence down, so a future edit can’t accidentally merge the two
graphs back into one God-check.
acyclic’s edge set is qualified (2026-10-01, specodelic-huf). The
invariant previously read “traces_to ∪ derives_from ∪ guard-as-edge has
no cycle” — unqualified, while the checker built the edge set from
(constraints, traces_to), (properties, derives_from) and
(transitions, guard) only. A constraint↔constraint derives_from cycle
therefore escaped both the acyclic check and edge typing (the typing
check read only the Must-resolve-to column). The decision of record:
constraint-level derives_from is out-of-format — a constraint is
derived FROM by properties; it does not derive. The Reference Typing
table’s Appears on: Property column is normative, and
ref_kind_compatible now reads it source-side (a Constraint-row
derives_from is a labeled typing violation, recorded as no edge),
making the cycle unrepresentable rather than merely uncycled. The
invariant text above is amended to name the exact implemented edge set
— same discipline as the observes exclusion note below: the union is
closed, and it grows only under a new Revision of this file.
observes (specodelic.md Revision 9) is deliberately absent from
acyclic’s edge set. The union above is closed — satisfies and
emits never joined it either — and an observation claim is not a
dependency: two files mutually observing each other’s effects are
well-formed. The edge set grows only under a new Revision of this file
(same discipline as supersedes_dag’s independence, pinned by
supersedes_dag_ignores_traces_cycle).
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: cycle_rejected
- WHEN
spec_repo_with(traces_to_cycle: length ≥ 2) - THEN
check(repo) == failed - VERIFIES [[linter.graph_shape.cycle_rejected]]
Scenario: self_ref_rejected
- WHEN
spec_row_with(traces_to == self.id) - THEN
check(repo) == failed - VERIFIES [[linter.graph_shape.self_ref_rejected]]
Scenario: orphan_subgraph_rejected
- WHEN
spec_repo_with(disconnected_constraint_cluster: true) - THEN
check(repo) == failed - VERIFIES [[linter.graph_shape.orphan_subgraph_rejected]]
Scenario: dag_passes
- WHEN
arbitrary_dag_shaped_repo() - THEN
check(repo) == passed - VERIFIES [[linter.graph_shape.dag_passes]]
Scenario: supersedes_cycle_rejected
- WHEN
spec_repo_with(supersedes_cycle: length ≥ 2) - THEN
check(repo) == failed - VERIFIES [[linter.graph_shape.supersedes_cycle_rejected]]
Scenario: supersedes_dag_ignores_traces_cycle
- WHEN
spec_repo_with(traces_to_cycle: true, supersedes: acyclic) - THEN
check(repo).supersedes_dag == passed— the two graphs are checked independently; a cycle in one must not be attributed to the other - VERIFIES [[linter.graph_shape.supersedes_dag_ignores_traces_cycle]]
Scenario: derives_from_edges_property_sourced
- WHEN
spec_repo_with(constraint_row_carrying_derives_from: true) - THEN
check(repo) == failed— the malformed edge is typing’s finding (the graph layer reports it and records no edge), so it joins no acyclic edge set; the invariant and the checker now agree on the same closed edge set (specodelic-huf) - VERIFIES [[linter.graph_shape.derives_from_edges_property_sourced]]
Scenario: topo_sort_naturality
- WHEN
arbitrary_dag_repo(), arbitrary_id_rename() - THEN identity:
topo_sort(rename(I, a, a)) == topo_sort(I)— the rename identity case at the topo_sort observation point associativity:topo_sort(rename(rename(I, a, b), b, c)) == topo_sort(rename(I, a, c))— the rename associativity case at the same point naturality:topo_sort(rename(I)) == rename(topo_sort(I))— renaming a node doesn’t change relative order of unrelated nodes - VERIFIES [[linter.graph_shape.topo_sort_naturality]]
Scenario: check_failure_label_asserted
- WHEN
check_failure_raised() - THEN
error_label == "linter.graph_shape.check_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.graph_shape.check_failure_label_asserted]]
linter.model_shape
id: linter.model_shape kind: intent statement: “WHEN a spec file’s graph shape has passed, THE linter SHALL reject it if any transition lacks a guard or if the file declares a model without both a states section and a transitions section.”
Linter: Model Shape Check
Runs after linter.graph_shape — it needs the reference graph already
proven acyclic before it can trust that a guard field pointing at a
constraint isn’t secretly part of a cycle back through the model itself.
Corresponds to invalid-states-diagnostician’s “unconstrained optionality”
and “ad-hoc state machine” signals, made structural rather than advisory.
Purpose
Runs after linter.graph_shape — it needs the reference graph already
proven acyclic before it can trust that a guard field pointing at a
constraint isn’t secretly part of a cycle back through the model itself.
Corresponds to invalid-states-diagnostician’s “unconstrained optionality”
and “ad-hoc state machine” signals, made structural rather than advisory.
Constraints
| id | kind | expr | traces_to | satisfies |
|————————|———–|———————————————————————————————–|————————————————|
| guard_present | invariant | ∀ transition row: guard field is non-empty | [[linter.model_shape]] | |
| model_sections_paired | invariant | if [[model.state]] exists then [[model.transition]] exists, and vice versa | [[linter.model_shape]] | |
| every_state_used | invariant | ∀ state: state appears as from or to in ≥ 1 transition | [[linter.model_shape]] | |
| every_transition_valid | invariant | ∀ transition: from ∈ states and to ∈ states | [[linter.model_shape]] | |
| no_bool_state_field | invariant | no state or transition row has a column of boolean type | [[linter.model_shape]]| |
| pairing_failure | effect | linter.model_shape.pairing_failure(detail) | [[linter.model_shape]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| field_check_failure | effect | linter.model_shape.field_check_failure(detail) | [[linter.model_shape]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
ungroupedpairing_checkedstates_checkedpassedpairing_failed(emits:[[linter.model_shape.pairing_failure]])field_check_failed(emits:[[linter.model_shape.field_check_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| check_pairing | ungrouped | pairing_checked | file passed linter.graph_shape |
| pairing_ok | pairing_checked | states_checked | [[linter.model_shape.model_sections_paired]] ∧ [[linter.model_shape.every_state_used]] ∧ [[linter.model_shape.every_transition_valid]] |
| pairing_fail | pairing_checked | pairing_failed | ¬([[linter.model_shape.model_sections_paired]] ∧ [[linter.model_shape.every_state_used]] ∧ [[linter.model_shape.every_transition_valid]]) |
| accept | states_checked | passed | [[linter.model_shape.guard_present]] ∧ [[linter.model_shape.no_bool_state_field]] |
| reject | states_checked | field_check_failed | ¬([[linter.model_shape.guard_present]] ∧ [[linter.model_shape.no_bool_state_field]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| missing_guard_rejected | unit | [[linter.model_shape.guard_present]] | spec_file_with(transition_missing_guard: true) | check(file) == failed |
| unpaired_states_rejected | unit | [[linter.model_shape.model_sections_paired]] | spec_file_with(states_section_but_no_transitions: true) | check(file) == failed |
| unreachable_state_rejected | unit | [[linter.model_shape.every_state_used]] | spec_file_with(declared_state_never_referenced: true) | check(file) == failed |
| bad_edge_rejected | unit | [[linter.model_shape.every_transition_valid]] | spec_file_with(transition.to_not_in_states: true) | check(file) == failed |
| bool_column_rejected | unit | [[linter.model_shape.no_bool_state_field]] | spec_file_with(state_row_having_bool_typed_column: true) | check(file) == failed |
| well_formed_model_passes | unit | [[linter.model_shape.every_transition_valid]] | arbitrary_well_formed_state_machine() | check(file) == passed |
| pairing_failure_label_asserted | unit | [[linter.model_shape.pairing_failure]] | pairing_failure_raised() | error_label == "linter.model_shape.pairing_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| field_check_failure_label_asserted | unit | [[linter.model_shape.field_check_failure]] | field_check_failure_raised() | error_label == "linter.model_shape.field_check_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
Reference-typing reconciliation (2026-09-30, specodelic-cxq): the
Constraints table’s traces_to cells previously pointed at the
specodelic.md rows these checks re-own — a Constraint→Constraint target,
which the Reference Typing table forbids (traces_to resolves to Intent
only). Each now traces to this file’s own intent: specodelic.md keeps
the corpus-wide statement of record, this file owns the checkable one
(the “same claim, two altitudes” pattern the Notes below already use).
every_state_used and every_transition_valid are new relative to
specodelic.md’s original list — guard_required only checked that a
guard exists, not that the state machine around it is internally
consistent (an unreferenced state, or a transition pointing at a state
that was never declared). Both are structural prerequisites for the
model_check transition in specodelic.md to mean anything: a TLA+
generator handed a dangling to field would either crash or silently
model-check a different graph than the author wrote. Fourth candidate to
fold back into specodelic.md’s constraint table.
no_bool_state_field restates specodelic.md’s no_boolean_columns
scoped specifically to the model section, since that’s where a boolean
column would actually manifest as an ad-hoc state machine
(invalid-states-diagnostician’s central pathology) rather than merely a
schema-hygiene issue elsewhere in the file.
Running tally of gaps found so far, all pending a pass back into
specodelic.md: id_matches_file, ref_kind_compatible,
single_root_reachable, every_state_used + every_transition_valid.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: missing_guard_rejected
- WHEN
spec_file_with(transition_missing_guard: true) - THEN
check(file) == failed - VERIFIES [[linter.model_shape.missing_guard_rejected]]
Scenario: unpaired_states_rejected
- WHEN
spec_file_with(states_section_but_no_transitions: true) - THEN
check(file) == failed - VERIFIES [[linter.model_shape.unpaired_states_rejected]]
Scenario: unreachable_state_rejected
- WHEN
spec_file_with(declared_state_never_referenced: true) - THEN
check(file) == failed - VERIFIES [[linter.model_shape.unreachable_state_rejected]]
Scenario: bad_edge_rejected
- WHEN
spec_file_with(transition.to_not_in_states: true) - THEN
check(file) == failed - VERIFIES [[linter.model_shape.bad_edge_rejected]]
Scenario: bool_column_rejected
- WHEN
spec_file_with(state_row_having_bool_typed_column: true) - THEN
check(file) == failed - VERIFIES [[linter.model_shape.bool_column_rejected]]
Scenario: well_formed_model_passes
- WHEN
arbitrary_well_formed_state_machine() - THEN
check(file) == passed - VERIFIES [[linter.model_shape.well_formed_model_passes]]
Scenario: pairing_failure_label_asserted
- WHEN
pairing_failure_raised() - THEN
error_label == "linter.model_shape.pairing_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.model_shape.pairing_failure_label_asserted]]
Scenario: field_check_failure_label_asserted
- WHEN
field_check_failure_raised() - THEN
error_label == "linter.model_shape.field_check_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.model_shape.field_check_failure_label_asserted]]
linter.referential_integrity
id: linter.referential_integrity kind: intent statement: “WHEN a spec repo is parsed, THE linter SHALL reject it if any row id is duplicated within a file or any wiki-link reference fails to resolve to a defining row.”
Linter: Referential Integrity Check
Runs after linter.frontmatter passes for every file in the repo — it needs
every file’s declared id to already be trustworthy before it can build the
id→row index this check relies on.
Purpose
Runs after linter.frontmatter passes for every file in the repo — it needs
every file’s declared id to already be trustworthy before it can build the
id→row index this check relies on.
Constraints
| id | kind | expr | traces_to | satisfies |
|–––––––––––|———–|—————————————————————————————––|–––––––––––––––––––––––––|
| unique_within_file | invariant | ∀ file: no two rows in file share the same local id | [[linter.referential_integrity]] | |
| unique_across_repo | invariant | ∀ repo: no two files declare the same fully-qualified id | [[linter.referential_integrity]] | |
| ref_resolves | invariant | ∀ [[ref]] in file: qualify(ref) ∈ index(repo) | [[linter.referential_integrity]] | |
| ref_kind_compatible | invariant | ∀ [[ref]] in any reference field defined by specodelic.md's Reference Typing table: target row's kind ∈ allowed_targets(field) — read from that table, not hardcoded per field name, so a Revision adding a field (supersedes, emits, …) needs no change here | [[linter.referential_integrity]] | |
| index_failure | effect | linter.referential_integrity.index_failure(detail) | [[linter.referential_integrity]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| resolution_failure | effect | linter.referential_integrity.resolution_failure(detail) | [[linter.referential_integrity]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
unindexedindexingindexedresolvingpassedindex_failed(emits:[[linter.referential_integrity.index_failure]])resolution_failed(emits:[[linter.referential_integrity.resolution_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| build_index | unindexed | indexing | all files in repo passed linter.frontmatter |
| index_ok | indexing | indexed | [[linter.referential_integrity.unique_within_file]] ∧ [[linter.referential_integrity.unique_across_repo]] |
| index_fail | indexing | index_failed | ¬([[linter.referential_integrity.unique_within_file]] ∧ [[linter.referential_integrity.unique_across_repo]]) |
| resolve_refs | indexed | resolving | index built successfully |
| accept | resolving | passed | [[linter.referential_integrity.ref_resolves]] ∧ [[linter.referential_integrity.ref_kind_compatible]] |
| reject | resolving | resolution_failed | ¬([[linter.referential_integrity.ref_resolves]] ∧ [[linter.referential_integrity.ref_kind_compatible]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| duplicate_id_rejected | unit | [[linter.referential_integrity.unique_within_file]] | spec_file_with(duplicate_row_id: true) | check(file) == failed |
| cross_file_collision | unit | [[linter.referential_integrity.unique_across_repo]] | two_spec_files_sharing_id() | check(repo) == failed |
| dangling_ref_rejected | unit | [[linter.referential_integrity.ref_resolves]] | spec_file_with(ref_to_nonexistent_id: true) | check(file) == failed |
| wrong_target_kind | unit | [[linter.referential_integrity.ref_kind_compatible]] | guard_field_pointing_at_a_property_row() | check(file) == failed |
| rename_naturality | law | [[specodelic.rename_naturality]] | arbitrary_spec_repo(), arbitrary_id_rename() | identity: resolve(rename(I,a,a)) == resolve(I) associativity: resolve(rename(rename(I,a,b),b,c)) == resolve(rename(I,a,c)) naturality: lookup_all(rename(I)) == rename(lookup_all(I)) — every [[ref]] pointing at a renamed id is updated with it, the naturality case this checker is the enforcement layer for |
| clean_repo_passes | unit | [[linter.referential_integrity.ref_resolves]] | arbitrary_well_formed_repo() | check(repo) == passed |
| index_failure_label_asserted | unit | [[linter.referential_integrity.index_failure]] | index_failure_raised() | error_label == "linter.referential_integrity.index_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| resolution_failure_label_asserted | unit | [[linter.referential_integrity.resolution_failure]] | resolution_failure_raised() | error_label == "linter.referential_integrity.resolution_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Resolution algorithm
qualify(ref) (the ref_resolves expr) and spk graph share one
algorithm, pinned by unit tests and documented here because it lives
nowhere user-facing otherwise (gh#1: “the resolution algorithm is
undocumented”):
- Exact file id — the whole target names a file; file id wins over any split interpretation.
- Bare-local row — a dotless target naming one of the SOURCE
file’s own rows resolves to that row. In practice this arm is masked
by the metasyntactic skip (a dotless target naming no file id is
format prose, not a reference) — it is the safety net that keeps a
dotless spelling meaningful when a file id shadows it. Revision 18
note: the old “only self-contained
id: specdeltas reach this arm” reading retired withid: specitself; every file resolves corpus-wide and authors spell rows file-qualified. - Every dot split, last to first —
prefixmust be a known file id and the remainder must be one of: a row of that file, themodel.state/model.transitionsection anchor, orrow.memberwhere the row is the single segment right after the split and the member path may itself be dotted. Last-dot wins because dotted file ids (linter.frontmatter) are the common case; the first-dot fallback covers[[specodelic.model.state]](filespecodelic, anchormodel.state) and multi-segment member paths ([[a.b.c.d]]= filea, rowb, memberc.d).
Row ids are single-segment: a dotted tail is always read as
row.member, never as a dotted row id, so [[a.b.c.d]] does not
resolve when file a declares only a row literally named b.c.
Notes
Reference-typing reconciliation (2026-09-30, specodelic-cxq): the
Constraints table’s traces_to cells previously pointed at the
specodelic.md rows these checks re-own — a Constraint→Constraint target,
which the Reference Typing table forbids (traces_to resolves to Intent
only). Each now traces to this file’s own intent: specodelic.md keeps
the corpus-wide statement of record, this file owns the checkable one
(the “same claim, two altitudes” pattern the Notes below already use).
Decision of record (2026-09-30, specodelic-cxq): this file’s
rename_naturality law previously carried only identity + associativity;
its naturality case (the cross-artifact commutation that gives the law its
name, lookup_all(rename(I)) == rename(lookup_all(I))) was implicit in the
Notes prose. It is now an explicit case column entry — the law the Notes
describe as “the layer that enforces it” states all three cases
[[specodelic.law_requires_cases]] requires.
rename_naturality is restated here rather than only living in
specodelic.md, because this is the check that would actually break if
a naive rename tool patched an id field without updating every [[ref]]
pointing at it — the property belongs at the layer that enforces it, with
specodelic.md keeping the higher-level restatement as its own
traceability anchor. This is the same “same claim, two altitudes” pattern
as ah reading dont’s events: one tool states the law, another is where
violating it actually fails.
The open question this file’s Notes used to raise — that ref_kind_compatible
assumes a fixed “which kind may fill which field” table that wasn’t
specified anywhere — was resolved in specodelic.md Revision 2, which
added the Reference Typing table for exactly this reason. What was never
fixed, until this pass (specodelic.md Revision 6), is that this file’s
own expr still named the three original fields by hand
(traces_to/derives_from/guard) instead of pointing at that table
generically — so it silently fell behind twice, first when supersedes
was added (Revision 5) and again when emits was (Revision 6), despite
specodelic.md’s own Revision 5 notes claiming this check needed zero
code changes for either addition. That claim was true of the
implementation (which already reads allowed_targets(field) from the
table) but not of this file’s wording, which had drifted out of sync
with what the implementation actually does. Reworded above to describe
the table-driven behavior directly, so the next Reference Typing addition
requires no follow-up here either in code or in wording.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: duplicate_id_rejected
- WHEN
spec_file_with(duplicate_row_id: true) - THEN
check(file) == failed - VERIFIES [[linter.referential_integrity.duplicate_id_rejected]]
Scenario: cross_file_collision
- WHEN
two_spec_files_sharing_id() - THEN
check(repo) == failed - VERIFIES [[linter.referential_integrity.cross_file_collision]]
Scenario: dangling_ref_rejected
- WHEN
spec_file_with(ref_to_nonexistent_id: true) - THEN
check(file) == failed - VERIFIES [[linter.referential_integrity.dangling_ref_rejected]]
Scenario: wrong_target_kind
- WHEN
guard_field_pointing_at_a_property_row() - THEN
check(file) == failed - VERIFIES [[linter.referential_integrity.wrong_target_kind]]
Scenario: rename_naturality
- WHEN
arbitrary_spec_repo(), arbitrary_id_rename() - THEN identity:
resolve(rename(I,a,a)) == resolve(I)associativity:resolve(rename(rename(I,a,b),b,c)) == resolve(rename(I,a,c))naturality:lookup_all(rename(I)) == rename(lookup_all(I))— every[[ref]]pointing at a renamed id is updated with it, the naturality case this checker is the enforcement layer for - VERIFIES [[linter.referential_integrity.rename_naturality]]
Scenario: clean_repo_passes
- WHEN
arbitrary_well_formed_repo() - THEN
check(repo) == passed - VERIFIES [[linter.referential_integrity.clean_repo_passes]]
Scenario: index_failure_label_asserted
- WHEN
index_failure_raised() - THEN
error_label == "linter.referential_integrity.index_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.referential_integrity.index_failure_label_asserted]]
Scenario: resolution_failure_label_asserted
- WHEN
resolution_failure_raised() - THEN
error_label == "linter.referential_integrity.resolution_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.referential_integrity.resolution_failure_label_asserted]]
linter.external_completeness
id: linter.external_completeness kind: intent checked_against_core: clear statement: “WHEN a spec repo declares an external checklist, THE linter SHALL reject it if any checklist item lacks an explicit mapping to a resolved constraint/property id or an explicit waiver with a stated rationale.”
Linter: External Completeness Check
STATUS.md §4’s long-standing unsolved problem: every gap folded into
specodelic.md’s Revision 2 — id_matches_file, ref_kind_compatible,
single_root_reachable, the model well-formedness pair — was found by a
human comparing a new checker file against the existing constraint list by
eye. linter.coverage mechanizes internal consistency (every present
constraint has a deriving property) but says so explicitly in its own
Notes: it can tell you a present constraint lacks a test, never that a
constraint is missing entirely. This file is the structurally different
thing STATUS.md called for — a checker that diffs against an external
reference (a domain checklist, a list of known past incidents, a
stakeholder sign-off list) instead of checking the repo against itself.
Unlike the eight gating checkers in the Checker Ownership table, this one is
optional per repo: it only runs when a repo declares a checklist to
check against. A repo with no checklist has nothing external to be
incomplete relative to — that’s a repo this checker doesn’t apply to, not
one it passes vacuously. No file’s linted state depends on this checker;
see Notes for what it does gate instead.
Purpose
STATUS.md §4’s long-standing unsolved problem: every gap folded into
specodelic.md’s Revision 2 — id_matches_file, ref_kind_compatible,
single_root_reachable, the model well-formedness pair — was found by a
human comparing a new checker file against the existing constraint list by
eye. linter.coverage mechanizes internal consistency (every present
constraint has a deriving property) but says so explicitly in its own
Notes: it can tell you a present constraint lacks a test, never that a
constraint is missing entirely. This file is the structurally different
thing STATUS.md called for — a checker that diffs against an external
reference (a domain checklist, a list of known past incidents, a
stakeholder sign-off list) instead of checking the repo against itself.
Unlike the eight gating checkers in the Checker Ownership table, this one is
optional per repo: it only runs when a repo declares a checklist to
check against. A repo with no checklist has nothing external to be
incomplete relative to — that’s a repo this checker doesn’t apply to, not
one it passes vacuously. No file’s linted state depends on this checker;
see Notes for what it does gate instead.
Constraints
| id | kind | expr | traces_to | satisfies |
|––––––––––––––|———–|——————————————————————————————————————————————|–––––––––––|
| checklist_well_formed | invariant | a declared checklist is a flat list of items, each with a stable id and a plain-text description | [[linter.external_completeness]] | |
| every_item_accounted | invariant | ∀ checklist item c: ∃ exactly one mapping row m with m.item == c.id and m.status ∈ {covered, waived} | [[linter.external_completeness]] | |
| covered_maps_resolve | invariant | ∀ mapping row m where m.status == "covered": m.mapped_ids is non-empty, and every id in it resolves to a real constraint or property row | [[linter.external_completeness]] | |
| waiver_has_rationale | invariant | ∀ mapping row m where m.status == "waived": m.rationale is non-empty prose | [[linter.external_completeness]] | |
| no_duplicate_claim | invariant | ∀ checklist item c: no two mapping rows both target c.id | [[linter.external_completeness]] | |
| manifest_failure | effect | linter.external_completeness.manifest_failure(detail) | [[linter.external_completeness]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| mapping_failure | effect | linter.external_completeness.mapping_failure(detail) | [[linter.external_completeness]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
| resolution_failure | effect | linter.external_completeness.resolution_failure(detail) | [[linter.external_completeness]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
not_applicableloadedmapping_checkedpassedload_failed(emits:[[linter.external_completeness.manifest_failure]])mapping_failed(emits:[[linter.external_completeness.mapping_failure]])resolution_failed(emits:[[linter.external_completeness.resolution_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| load | not_applicable | loaded | repo declares a checklist ∧ [[linter.external_completeness.checklist_well_formed]] |
| load_fail | not_applicable | load_failed | repo declares a checklist ∧ ¬([[linter.external_completeness.checklist_well_formed]]) |
| check_mapping | loaded | mapping_checked | [[linter.external_completeness.every_item_accounted]] ∧ [[linter.external_completeness.no_duplicate_claim]] |
| check_fail | loaded | mapping_failed | ¬([[linter.external_completeness.every_item_accounted]] ∧ [[linter.external_completeness.no_duplicate_claim]]) |
| accept | mapping_checked | passed | [[linter.external_completeness.covered_maps_resolve]] ∧ [[linter.external_completeness.waiver_has_rationale]] |
| reject | mapping_checked | resolution_failed | ¬([[linter.external_completeness.covered_maps_resolve]] ∧ [[linter.external_completeness.waiver_has_rationale]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| unmapped_item_rejected | unit | [[linter.external_completeness.every_item_accounted]] | checklist_item_with_no_mapping_row() | check(repo) == failed |
| dangling_mapped_id_rejected | unit | [[linter.external_completeness.covered_maps_resolve]] | mapping_row_with(status: "covered", mapped_ids: ["nonexistent.id"]) | check(repo) == failed |
| unrationalized_waiver_rejected | unit | [[linter.external_completeness.waiver_has_rationale]] | mapping_row_with(status: "waived", rationale: "") | check(repo) == failed |
| duplicate_claim_rejected | unit | [[linter.external_completeness.no_duplicate_claim]] | two_mapping_rows_targeting_the_same_checklist_item() | check(repo) == failed |
| fully_mapped_checklist_passes | unit | [[linter.external_completeness.every_item_accounted]] | checklist_where_every_item_has_a_covered_or_waived_mapping() | check(repo) == passed |
| no_checklist_not_applicable | unit | [[linter.external_completeness.every_item_accounted]] | repo_with_no_declared_checklist() | check(repo) == not_applicable — distinct from passed, see Notes |
| mapping_naturality | law | [[specodelic.rename_naturality]] | arbitrary_repo_with_checklist(), arbitrary_id_rename() | identity: mapped(repo renamed to itself) == mapped(repo) — the rename identity case instantiated at the mapped-ids observation point associativity: mapped(rename(rename(I, a, b), b, c)) == mapped(rename(I, a, c)) — the rename associativity case at the same observation point naturality: mapped(rename(I)) == rename(mapped(I)) — renaming a constraint or property id updates every mapped_ids cell claiming it, the same as any other reference |
| malformed_checklist_rejected | unit | [[linter.external_completeness.checklist_well_formed]] | declared_checklist_with(a_nested_item, an_item_missing_its_id) | check(repo) == failed |
| manifest_failure_label_asserted | unit | [[linter.external_completeness.manifest_failure]] | manifest_failure_raised() | error_label == "linter.external_completeness.manifest_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| mapping_failure_label_asserted | unit | [[linter.external_completeness.mapping_failure]] | mapping_failure_raised() | error_label == "linter.external_completeness.mapping_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
| resolution_failure_label_asserted | unit | [[linter.external_completeness.resolution_failure]] | resolution_failure_raised() | error_label == "linter.external_completeness.resolution_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
What this checker can and can’t prove — read carefully, this is the
whole point of the file. every_item_accounted, covered_maps_resolve,
and waiver_has_rationale together prove that a human explicitly asserted,
for every checklist item, either “constraint X covers this” (checked
structurally: X exists) or “this doesn’t apply, because ___” (checked
structurally: a rationale was written). What none of this proves is that
the assertion is true — that constraint X actually, semantically,
addresses the checklist item it’s mapped to, or that the waiver’s stated
reason is a good one. That gap is irreducible from inside this framework:
whether a mapping is semantically correct is a judgment call about meaning,
the same category of thing STATUS.md §2 keeps out of scope for prose
quality. What this checker mechanizes isn’t “the repo is complete” — it’s
“someone was forced to look at every item and commit to a claim about it,
on the record, instead of the checklist silently going unconsulted.” That
is a real, checkable weakening of the original problem, not a full
solution to it — the same honest half-measure linter.coverage makes about
internal completeness, one level out.
Why this doesn’t gate linted, compiled, or anything else in
specodelic.md’s lifecycle. The six Checker Ownership table checkers
and linter.coverage all check something every spec file necessarily has
(frontmatter, refs, a model, constraints). A checklist is an artifact nothing
requires a repo to declare — bolting a mandatory gate onto an optional
input would make every repo without a checklist unable to reach verified,
which is wrong. Instead this stands as an independent, addressable gate: a
repo may declare a checklist and run this against it, and CI or an
orchestrator (STATUS.md §4’s remaining P1 item) can choose to require it
for release the same way a real org’s ship gate might require a security
sign-off — a policy decision layered on top of specodelic, not a fact
specodelic.md itself asserts about every repo.
Decision of record (mp1 row 10, 2026-09-29): the checklist is a
dedicated *.checklist.md artifact OUTSIDE 𝒦. A checklist manifest
is not a spec file — no frontmatter, no four-layer shape, exempt like
AGENTS.md/STATUS.md. Its format: a ## Items section holding a flat
list of - **<id>**: <plain-text description> bullets (ids are stable
row-id tokens: alphanumeric segments joined by ./_/-), plus a
## Mapping section holding exactly one table with the columns
item/status/mapped_ids/rationale. Presence of the file IS the
declaration — a repo with no *.checklist.md has nothing external to be
incomplete relative to (not_applicable, not a vacuous pass). Mapping
rows must reference declared items; statuses outside {covered, waived}
leave the item unaccounted-for (every_item_accounted’s beat); a
duplicated item claim is no_duplicate_claim’s beat, never double-
reported as unaccounted. A manifest that declares zero items is itself
a checklist_well_formed defect — a checklist nothing can be consulted
against is not a checklist, and a silent pass would be a false green.
The linter tolerates a UTF-8 BOM and ### -depth headers inside a
section (prose, never section switches). The degenerate-spec-file option (empty
Model/Constraints, frontmatter id: spec) was rejected: it would grow
𝒦 after all, drag the naming law and dual-format machinery onto an
artifact that has no intent to state, for no benefit this checker needs.
mapping_naturality is enforced by the rename tool. The checklist
sits outside 𝒦, so linter.referential_integrity itself never parses
the manifest — but rename.md’s old_id_fully_replaced reaches into
mapped_ids cells (bare and [[…]] spellings alike, children
following their parent), and rename’s verify gate re-runs this
checker’s resolution (covered_maps_resolve, plus manifest
well-formedness) over the post-rename corpus. A rename that misses a
cell is rejected before any byte is written, and a rename that would
dangle a cell is equally refused.
checked_against_core: clear (see AGENTS.md’s convention). This file’s
constraints are local to what an external-completeness check
specifically needs, the same way compile.md/model_check.md/
verify.md/rename.md each carry lifecycle-step-specific invariants
without requiring new top-level entries.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: unmapped_item_rejected
- WHEN
checklist_item_with_no_mapping_row() - THEN
check(repo) == failed - VERIFIES [[linter.external_completeness.unmapped_item_rejected]]
Scenario: dangling_mapped_id_rejected
- WHEN
mapping_row_with(status: "covered", mapped_ids: ["nonexistent.id"]) - THEN
check(repo) == failed - VERIFIES [[linter.external_completeness.dangling_mapped_id_rejected]]
Scenario: unrationalized_waiver_rejected
- WHEN
mapping_row_with(status: "waived", rationale: "") - THEN
check(repo) == failed - VERIFIES [[linter.external_completeness.unrationalized_waiver_rejected]]
Scenario: duplicate_claim_rejected
- WHEN
two_mapping_rows_targeting_the_same_checklist_item() - THEN
check(repo) == failed - VERIFIES [[linter.external_completeness.duplicate_claim_rejected]]
Scenario: fully_mapped_checklist_passes
- WHEN
checklist_where_every_item_has_a_covered_or_waived_mapping() - THEN
check(repo) == passed - VERIFIES [[linter.external_completeness.fully_mapped_checklist_passes]]
Scenario: no_checklist_not_applicable
- WHEN
repo_with_no_declared_checklist() - THEN
check(repo) == not_applicable— distinct frompassed, see Notes - VERIFIES [[linter.external_completeness.no_checklist_not_applicable]]
Scenario: mapping_naturality
- WHEN
arbitrary_repo_with_checklist(), arbitrary_id_rename() - THEN identity:
mapped(repo renamed to itself) == mapped(repo)— the rename identity case instantiated at the mapped-ids observation point associativity:mapped(rename(rename(I, a, b), b, c)) == mapped(rename(I, a, c))— the rename associativity case at the same observation point naturality:mapped(rename(I)) == rename(mapped(I))— renaming a constraint or property id updates everymapped_idscell claiming it, the same as any other reference - VERIFIES [[linter.external_completeness.mapping_naturality]]
Scenario: malformed_checklist_rejected
- WHEN
declared_checklist_with(a_nested_item, an_item_missing_its_id) - THEN
check(repo) == failed - VERIFIES [[linter.external_completeness.malformed_checklist_rejected]]
Scenario: manifest_failure_label_asserted
- WHEN
manifest_failure_raised() - THEN
error_label == "linter.external_completeness.manifest_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.external_completeness.manifest_failure_label_asserted]]
Scenario: mapping_failure_label_asserted
- WHEN
mapping_failure_raised() - THEN
error_label == "linter.external_completeness.mapping_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.external_completeness.mapping_failure_label_asserted]]
Scenario: resolution_failure_label_asserted
- WHEN
resolution_failure_raised() - THEN
error_label == "linter.external_completeness.resolution_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.external_completeness.resolution_failure_label_asserted]]
linter.failure_shape
id: linter.failure_shape kind: intent checked_against_core: clear statement: “WHEN a spec repo’s tool files are linted, THE failure-shape checker SHALL reject a tool file whose failure terminal states emit nothing, whose error labels collide within a file, or whose failure guards neither cite their negated transition’s citation set nor appear on the recorded carve-out list.”
Linter: Failure Shape Check
The checker half of the error contract (errors.md): it turns the
contract’s tier-2 rules — mute failure terminals, label collisions, and
typed-negation guard totals — from graph-observable facts into lint
findings. Everything it checks is decidable from the reference graph the
graph tool already derives; nothing here reads prose, so the checker
cannot over-reject a file whose prose happens to argue for failure
classes the graph already separates (the D2a decision: classes ⟺
distinct guard citation sets, never a prose judgment).
v1 scope is failure terminals only. model_check.md’s timed_out and
exploration_only are real terminal outcomes but are a stated
non-goal of this version: typing them raises the exit-code mapping
question (does an exhausted bound map to 1?) that errors.md’s
exit_code_mapping row defers to a later Revision — the gap is stated
here so it is visible rather than forgotten.
This file shipped spec-only (design D6): no Checker Ownership row
lands until the checker exists — the table’s invariant is that every
listed checker exists. The checker is implemented in spk lint
(specodelic-ct5): the three constraint rules below are enforced per file
on every lint pass, so they are claims about today’s linter, not norms
for a future one. Resolved of record (2026-10-01, specodelic-hhp,
decision a): the checker has its Checker Ownership row and rides the
orchestrator’s lint stage, dependent on linter.model_shape — the
failure-shape walk reads the Model’s states, transitions and emits
edges, so it runs only once the model is proven well-formed
(dependency_respecting_skip). The earlier note about AGENTS.md’s
stale specodelic-6pi citation belongs to governance, not to this file.
Purpose
The checker half of the error contract (errors.md): it turns the
contract’s tier-2 rules — mute failure terminals, label collisions, and
typed-negation guard totals — from graph-observable facts into lint
findings. Everything it checks is decidable from the reference graph the
graph tool already derives; nothing here reads prose, so the checker
cannot over-reject a file whose prose happens to argue for failure
classes the graph already separates (the D2a decision: classes ⟺
distinct guard citation sets, never a prose judgment).
v1 scope is failure terminals only. model_check.md’s timed_out and
exploration_only are real terminal outcomes but are a stated
non-goal of this version: typing them raises the exit-code mapping
question (does an exhausted bound map to 1?) that errors.md’s
exit_code_mapping row defers to a later Revision — the gap is stated
here so it is visible rather than forgotten.
This file shipped spec-only (design D6): no Checker Ownership row
lands until the checker exists — the table’s invariant is that every
listed checker exists. The checker is implemented in spk lint
(specodelic-ct5): the three constraint rules below are enforced per file
on every lint pass, so they are claims about today’s linter, not norms
for a future one. Resolved of record (2026-10-01, specodelic-hhp,
decision a): the checker has its Checker Ownership row and rides the
orchestrator’s lint stage, dependent on linter.model_shape — the
failure-shape walk reads the Model’s states, transitions and emits
edges, so it runs only once the model is proven well-formed
(dependency_respecting_skip). The earlier note about AGENTS.md’s
stale specodelic-6pi citation belongs to governance, not to this file.
Constraints
| id | kind | expr | traces_to | satisfies |
|---|---|---|---|---|
| terminal_states_emit | invariant | every failure terminal state in a tool file's Model (a state with inbound transitions, no outbound transitions, carrying an emits edge obligation per errors.md's failure_state_emits) emits exactly one effect Constraint owned by that file — a mute failure terminal is a finding (v1 scope: failure terminals only; timed_out / exploration_only are the stated non-goal above) | [[linter.failure_shape]] | |
| error_labels_unique | invariant | within one file, no two error Constraints carry the same variant head — cross-file collisions are structurally impossible by errors.md's error_expr_shape (the label is file-id-namespaced), so this check is per-file and trivially green corpus-wide today; it exists so a future format change cannot silently drop the namespacing law | [[linter.failure_shape]] | |
| guard_negation_total | invariant | every failure transition either cites exactly the union of its success siblings' citation sets (the negated disjunction, errors.md's guard_negation_typed), or is on the recorded carve-out list — which is exactly orchestrate.md's stage-fail transitions (lint_fail, compile_fail, model_check_fail, verify_fail), per that file's own Notes' don't-restate discipline; a zero-citation failure guard off the list is a finding, never a silent pass | [[linter.failure_shape]] | |
| check_failure | effect | linter.failure_shape.check_failure(detail) | [[linter.failure_shape]] | [[errors.envelope_error_kind]] ∧ [[errors.exit_code_mapping]] ∧ [[errors.remediation_hint_present]] |
Model
States
uncheckedcheckingpassedfailed(emits:[[linter.failure_shape.check_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| begin | unchecked | checking | the repo declares a specs corpus to check |
| accept | checking | passed | [[linter.failure_shape.terminal_states_emit]] ∧ [[linter.failure_shape.error_labels_unique]] ∧ [[linter.failure_shape.guard_negation_total]] |
| reject | checking | failed | ¬([[linter.failure_shape.terminal_states_emit]] ∧ [[linter.failure_shape.error_labels_unique]] ∧ [[linter.failure_shape.guard_negation_total]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| mute_terminal_rejected | unit | [[linter.failure_shape.terminal_states_emit]] | tool_file_with_failure_terminal_and_no_emits_edge() | check(file) == failed — the corpus’s pre-add-error-contract shape, kept as the negative fixture |
| label_collision_rejected | unit | [[linter.failure_shape.error_labels_unique]] | file_with_two_error_constraints_sharing_a_variant_head() | check(file) == failed |
| zero_citation_flagged | unit | [[linter.failure_shape.guard_negation_total]] | failure_transition_citing_nothing_off_the_carveout_list() | check(file) == failed — the carve-out is a checked list, not an escape hatch |
| carved_out_guard_passes | unit | [[linter.failure_shape.guard_negation_total]] | orchestrate_stage_fail_transition() | check(file) == passed — the carve-out is checked, not assumed |
| negation_set_mismatch_flagged | unit | [[linter.failure_shape.guard_negation_total]] | failure_transition_whose_citations_differ_from_its_siblings() | check(file) == failed — citation-set inequality is graph-decidable |
| clean_repo_passes | unit | [[linter.failure_shape.terminal_states_emit]] | the_repo_itself() | check(repo) == passed — the corpus dogfoods this checker’s contract |
| check_failure_label_asserted | unit | [[linter.failure_shape.check_failure]] | failure_shape_violation_found() | error_label == "linter.failure_shape.check_failure" — renaming the label touches the error Constraint, this property, and its note together (EDGE-002) |
Notes
The class rule is the graph-decidable form. errors.md’s
failure_class_is_state owns the corpus-side rule (a state with ≥2
inbound failure transitions whose guard citation sets differ is
malformed); this checker never re-derives classes from prose —
“classes ⟺ distinct citation sets” is the only reading it implements,
so a single-class file (one failure state, one inbound failure
transition, however many prose paragraphs argue about it) cannot be
over-rejected.
Carve-out list, restated. The only recorded carve-out is
orchestrate.md‘s stage-fail transitions. That file’s Notes decline to
restate upstream files’ logic as guard-citable rows; retyping their
guards would reintroduce exactly the duplication it argues against. The
list is data to this checker (checked membership), not an assumption —
a new zero-citation failure guard anywhere else is a finding.
Ownership was deliberately absent, then granted. This file originally
shipped no Checker Ownership row — its findings rode spk lint only
after the checker was implemented (specodelic-ct5), and orchestrator
wiring waited for an ownership decision. That decision landed
(specodelic-hhp): the file now owns terminal_states_emit,
error_labels_unique, and guard_negation_total as a gating row in
specodelic.md’s Checker Ownership table (depending on
linter-model_shape.md), and its findings gate spk lint. The
AGENTS.md staleness it once flagged is governance’s to refresh.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: mute_terminal_rejected
- WHEN
tool_file_with_failure_terminal_and_no_emits_edge() - THEN
check(file) == failed— the corpus’s pre-add-error-contract shape, kept as the negative fixture - VERIFIES [[linter.failure_shape.mute_terminal_rejected]]
Scenario: label_collision_rejected
- WHEN
file_with_two_error_constraints_sharing_a_variant_head() - THEN
check(file) == failed - VERIFIES [[linter.failure_shape.label_collision_rejected]]
Scenario: zero_citation_flagged
- WHEN
failure_transition_citing_nothing_off_the_carveout_list() - THEN
check(file) == failed— the carve-out is a checked list, not an escape hatch - VERIFIES [[linter.failure_shape.zero_citation_flagged]]
Scenario: carved_out_guard_passes
- WHEN
orchestrate_stage_fail_transition() - THEN
check(file) == passed— the carve-out is checked, not assumed - VERIFIES [[linter.failure_shape.carved_out_guard_passes]]
Scenario: negation_set_mismatch_flagged
- WHEN
failure_transition_whose_citations_differ_from_its_siblings() - THEN
check(file) == failed— citation-set inequality is graph-decidable - VERIFIES [[linter.failure_shape.negation_set_mismatch_flagged]]
Scenario: clean_repo_passes
- WHEN
the_repo_itself() - THEN
check(repo) == passed— the corpus dogfoods this checker’s contract - VERIFIES [[linter.failure_shape.clean_repo_passes]]
Scenario: check_failure_label_asserted
- WHEN
failure_shape_violation_found() - THEN
error_label == "linter.failure_shape.check_failure"— renaming the label touches the error Constraint, this property, and its note together (EDGE-002) - VERIFIES [[linter.failure_shape.check_failure_label_asserted]]
linter.observability
id: linter.observability
kind: intent
statement: “WHEN a spec declares an effect Constraint, THE linter SHALL warn — advisory, exit 0 — on every effect within the lint invocation’s file set that no observes reference targets, and SHALL never gate a lifecycle transition on this warning.”
Linter: Observability
specodelic.md Revision 9 added the observes typed reference (a
Constraint row’s outbound pointer to an effect Constraint, any file) —
the vocabulary for “this output must be observable”. Vocabulary alone
does not make observability a checked fact: nothing distinguishes an
effect that something watches from one nobody declared an interest in.
This file specifies the check that closes that gap, shaped on the
Revision 7 precedent (satisfies) for typing and on
linter-external_completeness.md for cross-file check shape.
Purpose
specodelic.md Revision 9 added the observes typed reference (a
Constraint row’s outbound pointer to an effect Constraint, any file) —
the vocabulary for “this output must be observable”. Vocabulary alone
does not make observability a checked fact: nothing distinguishes an
effect that something watches from one nobody declared an interest in.
This file specifies the check that closes that gap, shaped on the
Revision 7 precedent (satisfies) for typing and on
linter-external_completeness.md for cross-file check shape.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| observation_universe | invariant | the check's universe is the lint invocation's file set — corpus-wide lint (just lint-specs) is the canonical run; every effect Constraint in that set is either the target of ≥1 observes edge resolved within the same set, or is reported as an advisory warning naming the unobserved row | [[linter.observability]] |
| advisory_severity | invariant | the unobserved-effect warning is emitted on the success envelope's warnings channel with exit 0 — never an Issue, never a nonzero exit; a warning is not a failure and never gates a lifecycle transition (whether it ever gates is a follow-up change, decided on dogfood friction evidence) | [[linter.observability]] |
| self_observation_not_counted | invariant | an effect row does not observe itself — an observation counts only when the observes edge is sourced at a different row (intra-file or cross-file); a row pointing observes at its own output is vacuous and is still warned | [[linter.observability]] |
| dangling_observes_is_referential | invariant | an observes edge whose target does not resolve is a linter.total_refs finding, never an observability warning — the two checks compose without double-reporting the same row | [[linter.observability]] |
| no_waivers_in_v1 | invariant | no waiver machinery exists in this Revision — when the need is demonstrated, linter-external_completeness.md's covered/waived claim structure is the candidate, decided in a follow-up change | [[linter.observability]] |
Model
States
scanningobservedunobserved_warned
Transitions
| id | from | to | guard |
|---|---|---|---|
| scan_effects | scanning | observed | every effect in the invocation's file set has ≥1 observer |
| warn_unobserved | scanning | unobserved_warned | [[linter.observability.observation_universe]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| unobserved_effect_warned | unit | [[linter.observability.observation_universe]] | effect_with_zero_observes_edges() | exit 0 ∧ a warning naming the row id and the rule id linter.observability |
| observed_effect_silent | unit | [[linter.observability.observation_universe]] | effect_with_at_least_one_observes_edge() | no observability warning |
| warning_never_gates | unit | [[linter.observability.advisory_severity]] | corpus_with_unobserved_effects() | exit code 0 ∧ zero Issue findings attributable to this check |
| mutual_observation_passes | unit | [[linter.observability.observation_universe]] | two_files_mutually_observing_each_others_effects() | both effects counted as observed — no warning |
| self_observation_still_warned | unit | [[linter.observability.self_observation_not_counted]] | effect_row_pointing_observes_at_itself() | warning fires — vacuous self-observation is not an observer |
| dangling_observes_not_doubled | unit | [[linter.observability.dangling_observes_is_referential]] | observes_pointing_at_an_absent_row() | exactly one total_refs finding; zero observability warnings |
| waiver_input_ignored | unit | [[linter.observability.no_waivers_in_v1]] | corpus_with_unobserved_effects() | the warning fires regardless of any claimed-waiver text — no waiver channel exists |
Notes
checked_against_core: clear (see AGENTS.md’s convention). The
self-observation decision above resolves the question left open in the
change design’s Open Questions: intra-file observation by a different
row counts; a row observing itself does not. Whether this check ever
becomes a gate (and joins orchestrate.md’s stage guards) is deliberately
undecided — the advisory-vs-gating decision needs dogfood friction
evidence first, per the Revision 9 text.
Requirements
Requirement: Property coverage mirror
Every property row SHALL be verified by exactly one dedicated scenario;
ah sync SHALL derive one contract per VERIFIES link.
Scenario: unobserved_effect_warned
- WHEN
effect_with_zero_observes_edges() - THEN
exit 0 ∧ a warning naming the row id and the rule id linter.observability - VERIFIES [[linter.observability.unobserved_effect_warned]]
Scenario: observed_effect_silent
- WHEN
effect_with_at_least_one_observes_edge() - THEN
no observability warning - VERIFIES [[linter.observability.observed_effect_silent]]
Scenario: warning_never_gates
- WHEN
corpus_with_unobserved_effects() - THEN
exit code 0 ∧ zero Issue findings attributable to this check - VERIFIES [[linter.observability.warning_never_gates]]
Scenario: mutual_observation_passes
- WHEN
two_files_mutually_observing_each_others_effects() - THEN
both effects counted as observed — no warning - VERIFIES [[linter.observability.mutual_observation_passes]]
Scenario: self_observation_still_warned
- WHEN
effect_row_pointing_observes_at_itself() - THEN
warning fires — vacuous self-observation is not an observer - VERIFIES [[linter.observability.self_observation_still_warned]]
Scenario: dangling_observes_not_doubled
- WHEN
observes_pointing_at_an_absent_row() - THEN
exactly one total_refs finding; zero observability warnings - VERIFIES [[linter.observability.dangling_observes_not_doubled]]
Scenario: waiver_input_ignored
- WHEN
corpus_with_unobserved_effects() - THEN
the warning fires regardless of any claimed-waiver text — no waiver channel exists - VERIFIES [[linter.observability.waiver_input_ignored]]
Architecture — the four layers and the lint pipeline
Every static diagram on this page is hand-authored: it exists to explain
the format and the linter’s pass structure — it is not a projection of a
live corpus. Where a diagram and the format disagree, the format wins:
spk explain format, spk explain references, and spk explain lint-rules
are the machine-generated truth these diagrams compress. The linter emits
self-describing findings and the rule catalog is generated from the same
table the binary uses, so a stale catalog here will out-argue this page.
For diagrams derived from a live corpus at build time — wiring, per-file state machines, traceability, the revision-labeled typing table — see Graph views. Never restate those by hand: the corpus changes, and a hand-drawn view of it becomes an unconsulted claim.
Four-layer relationship — one file, one spec
A spec file is one markdown document carrying four layers. The Intent (YAML frontmatter) is the machine-linted top claim; Constraints are machine-checkable claims that trace to it; the Model reads a lifecycle whose guards cite those claims; Properties derive from Constraints and give them their verifying behavior. The wiki-links in the table cells are typed foreign keys — the rows link across layers with these references:
flowchart TB
FM["<b>Intent</b><br>frontmatter: id · kind · statement<br><i>EARS-patterned SHALL claim</i>"]
subgraph CONS ["<b>Constraints</b> — machine-checkable claims"]
CI["invariant<br><i>lifecycle claim</i>"]
CE["effect<br><i>file-owned observable</i>"]
CX["extension_point<br><i>published contract</i>"]
end
subgraph MODEL ["<b>Model</b> — States + Transitions table"]
ST["<b>State</b> s"]
TI["<b>Transition</b> t1<br><i>success path</i>"]
TF["<b>Transition</b> t2<br><i>failure path</i>"]
end
subgraph PROPS ["<b>Properties</b> — verifying behavior"]
PU["<b>unit property</b>"]
PL["<b>law property</b><br><i>enumerated cases</i>"]
end
CI -- "traces_to" --> FM
CE -- "traces_to" --> FM
CX -- "traces_to<br><i>(satisfies is an outbound leaf, not an edge)</i>" --> FM
TI -- "guard cites invariant" --> CI
TI -- "from/to" --> ST
TF -- "guard cites the siblings' union<br><i>(guard_negation_total)</i>" --> CI
ST -- "emits effect" --> CE
CE -- "observes<br><i>(consuming row at the declared observable)</i>" --> CX
PU -- "derives_from" --> CI
PL -- "derives_from" --> CI
style FM fill:#e8eaf6,stroke:#3f51b5
style MODEL fill:#e8f5e9,stroke:#2e7d32
style CONS fill:#fff8e1,stroke:#f9a825
style PROPS fill:#fce4ec,stroke:#c2185b
Reading the diagram: traces_to anchors every Constraint in the intent
(coverage-style guarantees run the other way through derives_from);
a guard cites an invariant Constraint or a State (the “has reached
state X” pattern); a state’s emits cites only effect Constraints;
observes points from a consuming row at an effect Constraint — a
declared observable. The full typing table (which field may appear on
which row kind, and what it must resolve to) is served by
spk explain references, and the resolution rules for file-qualified
[[<file-id>.<row-id>]] links (file-id-namespaced) live there too. The
coverage guarantee is mandatory: every Constraint needs a deriving
Property (linter.coverage), and every Property must derive from at
least one Constraint (linter.no_orphan_property) — that bidirectional
floor is what keeps the four layers one spec, not four documents.
For the same four layers rendered from a live corpus, see the state-machine view in Graph views — never restate it here.
Lint checker pipeline — families and the advisory/gating split
The linter’s pass structure is a join point, not a dependency DAG: each
checker family reads the structured layers (frontmatter, table rows, the
model, the corpus graph) independently, and findings accumulate until
lint emits. There is no machine-readable family-dependency order served
by spk explain lint-rules, so the diagram below is deliberately a
layered pipeline, not an invented dependency DAG — checker families
are grouped by what they read, and the advisory/gating distinction is
marked on the rules that carry it. The rule catalog is generated from the
same table the binary uses; the 34 rule ids served by
spk explain lint-rules are the authoritative enumeration. One
stated dependency exists: the failure-shape checker rides the
orchestrator’s lint stage “dependent on linter.model_shape” — its walk
reads the Model’s states, transitions and emits edges, so it runs only
once the model is proven well-formed (dependency_respecting_skip,
openspec/specs/linter-failure_shape/spec.md).
Three rules are advisory: linter.observability and
linter.skew_advisory warn on the warnings channel with exit 0, and
linter.single_root_reachable is tiered — its cross-file-only half warns
(rows with no path to ANY intent still hard-fail). Every other rule in
the catalog gates: exit 1, and the lint pass fails.
flowchart TB
TARGET["<b>Corpus</b><br>one spec file is one spec — structured fields only"]
subgraph FMF ["<b>frontmatter family</b> — reads the intent layer"]
R1["linter.frontmatter_valid<br>linter.id_matches_file<br>linter.unique_id<br>linter.no_conjoined_id<br>linter.no_universal_in_id<br>linter.ears_syntax"]
end
subgraph SHAPE ["<b>shape families</b> — read the rows"]
R2["<b>schema_shape</b><br>linter.model_present<br>linter.constraint_kind_closed<br>linter.property_kind_closed<br>linter.schema_matches_typing_table"]
R3["<b>graph_shape</b> — reads the model's edges<br>linter.guard_required<br>linter.no_self_ref<br>linter.acyclic"]
end
subgraph MODEL ["<b>model family</b> — reads the Model section"]
R4["linter.every_state_used<br>linter.every_transition_valid"]
end
subgraph REACH ["<b>reachability family</b> — reads the linkage graph"]
R5["linter.single_root_reachable<br><i>tiered: cross-file-only rows</i><br><i>warn (exit 0); no path to ANY intent</i><br><i>hard-fails</i>"]
end
subgraph INT ["<b>integrity family</b> — reads the corpus graph"]
R6["linter.total_refs<br>linter.coverage<br>linter.no_orphan_property<br>linter.law_cases"]
end
subgraph DUAL ["<b>dual-format family</b> — reads the openspec mirror"]
R7["linter.requirement_drift<br>linter.dual_format_valid"]
end
subgraph FAIL ["<b>failure-shape family</b>"]
R8["linter.terminal_states_emit<br>linter.error_labels_unique<br>linter.guard_negation_total"]
end
subgraph CHECK ["<b>external-completeness family</b> — reads the checklist"]
R9["linter.checklist_well_formed<br>linter.every_item_accounted<br>linter.covered_maps_resolve<br>linter.waiver_has_rationale<br>linter.no_duplicate_claim"]
end
subgraph PACK ["<b>pack family</b> — reads kind: profile files"]
R10["linter.pack_shape<br>linter.orphan_vocabulary"]
end
ADV1["<b>linter.observability</b><br><i>advisory — warnings channel, exit 0</i>"]
ADV2["<b>linter.skew_advisory</b><br><i>advisory — warnings channel, exit 0</i>"]
TARGET --> FMF
TARGET --> SHAPE
TARGET --> MODEL
TARGET --> REACH
TARGET --> INT
TARGET --> DUAL
TARGET --> FAIL
TARGET --> CHECK
TARGET --> PACK
MODEL -. "stated dependency:<br>failure_shape runs only once<br>the model is well-formed" .-> FAIL
R1 & R2 & R3 & R4 & REACH & R6 & R7 & R8 & R9 & R10 --> JOIN{{"<b>lint</b> — the join point<br><i>all gating rules pass, or exit 1</i>"}}
R5 -- "warn-half<br><i>(exit 0)</i>" --> JOIN
ADV1 --> JOIN
ADV2 --> JOIN
style JOIN fill:#e3f2fd,stroke:#1565c0
style R5 fill:#fff3e0,stroke:#ef6c00
style ADV1 fill:#fff3e0,stroke:#ef6c00
style ADV2 fill:#fff3e0,stroke:#ef6c00
Reading the diagram: the families are join-point checkers, not a
dependency DAG — the linter’s actual pass structure accumulates
findings per family and emits at the join point, so no invented edges
between families — the one dashed edge below is the dependency the
failure-shape spec itself declares. Only the orange boxes are advisory; every other rule
in the catalog gates (exit 1). The per-rule semantics (one line each,
generated from the same table the binary uses) are served by
spk explain lint-rules, and the per-family spec files live under the
Lint Rules section. Dogfood: just lint-specs
runs this linter over the corpus itself.
Verify workflow — the canonical lifecycle
The artifact lifecycle is the pipeline’s own Model, declared and machine-linted like any other spec’s. Stage gates are enforced by the guards the lifecycle spec itself declares:
flowchart LR
DR["draft"] --> PA["parsed"]
PA -- "frontmatter_valid ·<br>id_matches_file" --> LI["linted"]
LI -- "every owned checker<br>passes (the join point)" --> CO["compiled"]
CO -- "coverage ·<br>law_cases" --> MC["model_checked"]
MC -- "model_present" --> VE["verified"]
VE -- "no_counterexample ·<br>properties_pass ·<br>opted-in invariants<br>verified" --> DONE(["complete"])
style DONE fill:#e8f5e9,stroke:#2e7d32
Reading the diagram: verify is bounded model checking of the claims
the spec opts into — prose-only invariants stay explicitly unchecked
and are never counted as verified. Verification is not a substitute for
the application’s own test suite. The lifecycle’s guards carry rule ids
served by spk explain lifecycle; the stage gates are enforced by the
linter’s guard rules, and the guard_required rule makes every
transition’s guard non-null. The same lifecycle is what the linter’s
own Model section declares — the pipeline self-hosts. Full recipes for
running the pipeline by hand are in Command Reference.
Verification boundaries — what a green check actually buys you
Every specodelic command can exit 0. An exit 0 from spk lint and an exit
0 from spk verify are not the same guarantee, and the toolchain never
collapses them into one PASS label: each stage attests something
different, and each stage’s verdict is honest about what it did not
check. This page names the ladder rung by rung, then states the known
limitations up front — each one backed by a command you can run right now.
The canonical one-line statement of the widest boundary — verification is not application testing — lives in the repo README; this page applies that discipline stage by stage and does not restate it.
The guarantee ladder
Read the ladder from the bottom up. Each rung presupposes every rung
below it — a file cannot be matches-oracle without first being
checked-within-bounds, and so on. A file’s honest status is the
highest rung it has evidence for, never a single undifferentiated PASS.
| # | Rung | Gate | What passing assures |
|---|---|---|---|
| 1 | Lint-clean | spk lint specs | The file is format-valid: all eight gating checkers pass |
| 2 | Well-modeled | lint’s model checkers + a non-empty Model section | The behavior is stated as a state machine that is present and internally coherent |
| 3 | Checked-within-bounds | spk compile, then spk model-check | Bounded exploration of the compiled model found no violation within the stated bound — or honestly reported it could not finish |
| 4 | Matches-oracle | spk verify | The compiled property blocks executed and passed, and every invariant claim the spec opted into is verified |
| 5 | Better-design | a counterexample_found from spk model-check | The checker refuted a claim — and the counterexample is often the design lesson, not a tool failure |
Rung 1 — lint-clean
spk lint specs exits 0: the file passes the join of the eight gating
checkers — frontmatter, EARS syntax, schema shape, coverage wiring,
graph shape, model shape, referential integrity, external completeness
(each has its own page under Lint Rules). spk explain lifecycle
defines this rung: “lint is the join point of the linter checkers —
it requires every owned checker to pass.”
Does not assure: that the constraints are true, or that the constraint list is complete. See the coverage blind spot below — this is the rung where a MISSING constraint hides most comfortably.
Rung 2 — well-modeled
The file carries a Model section that is present and coherent: a States
list and a Transitions table (linter.model_present — “empty-but-present
beats absent”), every transition guarded (guard_present), every state
used, every endpoint declared (linter.model_shape). This is the
precondition spk model-check imposes — “model_check requires a
complete Model section (model_present)” (spk explain lifecycle).
Does not assure: that the model says anything — an empty-but-present model lints. And a coherent model can still be the wrong model; only rung 3 puts it under exploration.
Rung 3 — checked-within-bounds
spk compile (which “only runs on a file that has passed both lint and coverage” — openspec/specs/compile/spec.md, precondition_satisfied) emits the TOML,
TLA+, and proptest artifacts; spk model-check explores the compiled
model’s reachable state space within a stated depth bound. A clean
verdict means no counterexample exists within the bound;
spk model-check --help is explicit that exhaustive runs report
exploration_only, never no_counterexample — a completed exploration
is evidence the space ends within the bound, never that the model holds
(openspec/specs/model_check/spec.md).
Does not assure: anything beyond the stated bound, and — because the program-counter model carries no data binding — nothing about values, only about state names and transitions.
Rung 4 — matches-oracle
spk verify executes the compiled proptest blocks and gates on the
model-check outcome. spk explain lifecycle states the full gate: “verify
requires no_counterexample, properties_pass, and every opted-in
invariant claim verified — prose-only invariants stay explicitly unchecked
and are never counted as verified.” A verified verdict is the top of
what the toolchain can attest about a spec.
Does not assure: application correctness. The claims verified are
exactly the ones the spec opted into; prose invariants are not checked at
all. See the README’s Verification is not application testing — a
verified spec is an audited specification, not a tested system.
Rung 5 — better-design
When spk model-check returns counterexample_found, the ladder’s
highest-value rung may not be “fix the model” but “fix the claim”. The
worked example shows the precedent: the
author’s executable invariant swore a rejection state was unreachable,
and the checker found a two-step trace reaching it — the rejection was
reachable by design, so the over-claiming row was the bug. As that
example puts it: “The checker was the design critic; the counterexample
was the design lesson.”
Does not assure: that every counterexample is a design lesson — but a refuted claim always names the violated invariant, so the diagnosis starts from the named row, not from a diff.
Known limitations
Each limitation below cites a runnable command; run it yourself — the outputs shown were captured at v0.8.0.
Coverage cannot detect a missing constraint
spk explain lint-rules prints the checker’s one-directional semantics:
| `linter.coverage` | every constraint must have a deriving property (`∃ property.derives_from == <constraint>`) |
Every constraint present must have a property — but nothing can tell you
a constraint should exist and doesn’t. openspec/specs/linter-coverage/spec.md says
this in so many words: “a coverage check can only tell you every
constraint present has a property; it cannot tell you a constraint is
missing, which is exactly the class of gap the first four checkers caught
by inspection rather than by any mechanism this framework has yet.”
Coverage is a completeness check over what you wrote, not over what the
domain needs.
**py:** and **ts:** fragments are not executable
The format grammar accepts both tags (openspec/specs/compile/spec.md,
fragment_language_closed — the closed set is {rust, py, ts}), and a
**py:** fragment in a predicate cell lints clean. But no emitter has
shipped: at HEAD, **rust:** is the only executable tag. Save this
minimal spec (the whole file — same shape as the worked example):
---
id: demo.py_spec
kind: intent
statement: "THE demo spec SHALL compile under py emission"
---
## Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| demo_py_c1 | invariant | `the demo spec compiles under py emission` | [[demo.py_spec]] |
## Model
### States
- `Idle`
- `Done`
### Transitions
| id | from | to | guard |
|---|---|---|---|
| t1 | Idle | Done | [[demo.py_spec.demo_py_c1]] |
## Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| demo_py_p1 | unit | [[demo.py_spec.demo_py_c1]] | `proptest::collection::vec(any::<u8>(), 0..8)` | **py:** len(xs) <= 8 |
spk lint demo.py_spec.md passes clean — and then spk compile demo.py_spec.md fails labeled, never a silent fall-through to Rust
emission:
{"ok":false,"envelope_kind":"error","data":{"failed":[{"file":"demo.py_spec.md",
"message":"row `demo_py_p1`: no emitter for **py:** fragments — executable
emission for py is deferred of record (follow-up change:
add-py-fragment-emission); **rust:** is the only executable tag at this
Revision (no_emitter_labeled_failure, specodelic.md Revision 16)",
"stage":"compile.emission_failure"}]}}
The tag set is closed on purpose — an accepted tag without an emitter
would silently fall through to Rust, the vacuous outcome the closed set
exists to prevent (openspec/specs/compile/spec.md, no_emitter_labeled_failure).
Pipeline verbs vary in implementation maturity
Every specced command ships (spk --help — the README keeps the count
honest), but “ships” is not “uniform”. What the binary actually does at
HEAD:
spk refactoradvises — it never edits.spk refactor --helpopens: “Advise on tidy-first splits for high unrelated fan-in nodes.” It flags split candidates via graph queries (openspec/specs/refactor/spec.md); the split itself is yours.spk model-checkis bounded, and honest about it. Exhaustive runs reportexploration_only, never a fabricated clean (spk model-check --help, backend description); a backend that cannot execute Rust fragments reportsexploration_only, never a fabricated pass (openspec/specs/model_check/spec.md,executable_invariants_execute). TLC cannot execute Rust fragments at all and never claims clean.spk verifynever re-compiles. It consumes the artifacts a priorspk compilewrote to the same out-dir (spk verify --help); a stale compile is a rerun, never an acceptance.spk compilegates on lint. A file with lint findings fails atprecondition_satisfiedwith the fired rule ids named (spk compileon any unlinted file; lint is corpus-wide, so a file referencing other specs only compiles in whole-corpus context).spk lint,spk graph,spk rename,spk merge,spk orchestraterun as specced: lint is the corpus-wide join over the eight gating checkers, rename is atomic and verified against the linters (spk rename --help), merge is a pre-merge detector, orchestrate gates each pipeline stage in Checker Ownership order (spk orchestrate --help).
When in doubt about a verb’s exact contract, the corpus file of the same
name under openspec/specs/ — also served in
this book under Pipeline — is the source of truth, and spk explain
works offline.
Tool relationships — specodelic, OpenSpec, and the ah companion
specodelic does not replace the tools a repo already uses for change
management — it plugs into them. Two relationships matter, and this
page names both honestly: the dual-format protocol that binds
specodelic files to the OpenSpec change
workflow, and the ah companion CLI
(espectacular) that
checks spec↔test correspondence around those files without ever
editing them.
| Tool | What it does | Who owns what |
|---|---|---|
| OpenSpec | proposal → tasks → archive change workflow | owns the openspec/ directory structure |
specodelic (spk) | the format + linter; every change delta is also a lint-clean specodelic file | owns the content of the dual-format layer |
espectacular (ah) | spec↔test correspondence checking | read-only over openspec/ — never edits a spec file |
The OpenSpec dual-format relationship
This repo manages its engineering changes in openspec: a change lives
in openspec/changes/<id>/ as a proposal plus task list, and when it
ships, openspec archive moves its deltas into the capability specs
under openspec/specs/. The dual-format protocol makes every one of
those files a specodelic file at the same time: one markdown file,
two parsers, requirement text authored once.
A dual-format file carries both grammars:
- the specodelic half — YAML frontmatter (a real
idfrom the naming law,kind: intent, one EARSstatement) plus the## Constraints,## Model, and## Propertiestables; - the openspec half —
## Purpose, a delta section such as## ADDED Requirements, and a sibling## Requirementssection with identical text.
Two rules keep the halves honest:
- Naming law: openspec hard-requires the filename
spec.md, so the id law derives aspec.mdfile’s id from its parent directory name —openspec/specs/ge-cli/spec.mddeclaresid: ge.cli. - No drift:
linter.requirement_driftfails when the delta requirements and the mirrored## Requirementssection drift apart, andlinter.dual_format_validrequires a capability spec underopenspec/specs/to be dual-format at all.
The full protocol — the migration recipe, the delta-mirror rules, and the exact gate commands — is served from inside the binary:
spk explain dual-format
That topic is the single source of truth for the protocol; this page
only orients you. When the protocol text and spk explain dual-format
disagree, trust the command — it ships with the linter that enforces
it.
Why not plain openspec archive? The stock archive command
regenerates spec files and destroys the specodelic layer (frontmatter
and the three tables). specodelic ships spk archive-companion — it
runs openspec archive with default spec application (plain deltas
are openspec’s native merge), then re-attaches the dual-format layer
from the change’s archived layer.md sidecar; a change whose deltas
lack restorable sidecars is refused before the destructive archive.
spk hooks install wires the dual-format gate (spk lint openspec)
into the repo’s pre-commit chain, so an unwrapped delta is caught
before commit, not after archive.
The ah companion (espectacular)
espectacular is a
sibling tool in the charly-vibes ecosystem — a separate crate whose
binary is ah (see its upstream
README for install and
the full command table). It solves a problem specodelic deliberately
does not: spec↔test correspondence. A spec scenario is only as
good as the test that actually runs it, and ah keeps those two
honestly wired together.
How it works, in this repo:
- Each deployed scenario in
openspec/specs/<capability>/spec.mdis backed by a contract TOML at.espectacular/<capability>/<scenario-id>.toml, which declares the tests that verify that scenario. ah checkvalidates every deployed scenario against its contract — and with--run-testsexecutes the declared tests.ah doctordiagnoses the setup;ah explain <topic>serves playbook guidance for a finding or workflow question.
The boundary is one sentence and it is a hard constraint, recorded in this repo’s AGENTS.md sibling-tool rules:
espectacular is read-only over
openspec/specs/andopenspec/changes/: it reads the#### Scenario:blocks, checks correspondence, and reports — it never edits, regenerates, or archives spec files.
That is why the companion archive recipe above exists as the only
sanctioned write path for capability specs: the two tools split the
work cleanly, with specodelic owning content and espectacular owning
the correspondence check. Nothing in ah’s enforcement is
specodelic-owned, and no specodelic command changes ah’s behavior —
if a docs page or tool output ever claims otherwise, it is wrong; the
guards in this repo’s CI (scripts/guards/sibling-blockers.sh) treat
boundary violations as bugs, not preferences.
Reading order for an OpenSpec user
- Run
spk explain dual-format— the protocol, from the binary that enforces it. - Skim the core format — the four specodelic layers the dual-format layer adds to an openspec delta.
- Read verification boundaries — what a
lint-clean or verified file does not assure; the same honesty
discipline
ah checkapplies to spec↔test claims.
Worked example — a reservation/order spec, failures included
This page builds one small spec from nothing: a reservation service for a
web shop. You will see the whole four-layer journey — Intent →
Constraints → Model → Properties — and then the part most tutorials
skip: the mistakes. Every lint finding below is real output from
specodelic 0.10.0 on the file as it stood at that moment; the fixes are
the fixes that were actually made. The finished spec lives at
examples/reservation-order.md and lints clean —
you can check that yourself with spk lint docs/src/examples/reservation-order.md
from the repo root.
The machine truth behind every rule quoted here is the tool itself:
spk explain format, spk explain ears, spk explain lint-rules serve
the distilled format guide from inside the binary. This page teaches the
format through one example; the commands appear only where they earn
their place.
On a TTY spk prints human-readable text (shown throughout); piped, it
emits a JSON envelope instead.
The story
A customer submits an order for several items. The service holds stock for every line item and accepts the order — or rejects it outright, holding nothing, charging nothing. Accepted orders are charged exactly once, fulfilled, or released by cancellation or expiry. Deliberately out of scope: pricing, the payment gateway itself, and multi-order races for the last unit (see What the spec does not decide).
One capability, one file. The file is named reservation-order.md;
the format’s naming law says - maps to the namespace dot, so the
frontmatter id is reservation.order (_ would be literal).
Layer 1 — Intent
The frontmatter carries the whole intent as one EARS sentence; the opening prose is free-form and is never read by the linter.
---
id: reservation.order
kind: intent
statement: "WHEN a customer submits an order THE reservation service SHALL hold stock for every line item and accept the order, or reject it with nothing held and nothing charged."
---
# Reservation & Order
A small order pipeline: submitting an order holds stock for every line
item or rejects the order outright; accepted orders are charged exactly
once, fulfilled, or released by cancellation or expiry.
The statement is Event-Driven (WHEN <trigger> THE <system> SHALL <response>) — one of the five EARS patterns; a statement with no SHALL
or no pattern match fails linter.ears_syntax. spk explain ears has
the full list.
Now lint the file as it stands:
spk lint reservation-order.md
lint: 1 file(s) linted, 1 finding(s), 0 advisory warning(s)
reservation-order.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.model_present fires because the Model section does not exist
yet — a spec with an intent but no model is incomplete by construction,
and the checker says so from the first pass. Keep the finding in mind;
the next two layers exist to resolve it.
Layer 2 — Constraints
The Constraints table holds the invariants the design actually rests on.
Each row is id | kind | expr | traces_to; traces_to is a typed
wiki-link pointing at this file’s own intent row — bare text does not
resolve. Here is the first draft, warts included: the author wrote the
last constraint’s traces_to against a misremembered id
(reservation.orders, plural).
| id | kind | expr | traces_to |
|--------------------------------|-----------|----------------------------------------------------------------------------------------------------------------------|---------------------|
| all_or_nothing | invariant | `the order is accepted only if every line item's requested quantity is available; otherwise no unit is held and nothing is charged` | [[reservation.orders]] |
| held_or_converted | invariant | `every unit held for an accepted order is either converted to a fulfillment or released; no held unit outlives both` | [[reservation.order]] |
| release_is_total | invariant | `when a hold is released, no unit remains held for that order; releasing an already-released hold changes nothing` | [[reservation.order]] |
| charge_iff_fulfilled | invariant | `the customer is charged if and only if the order reaches fulfilled` | [[reservation.order]] |
| cancel_refunds_exactly_charged | invariant | `IF a charged order is cancelled, THEN the refund equals exactly the amount charged — never more, never less` | [[reservation.order]] |
| expiry_releases_holds | invariant | `an unfulfilled reservation expires after its TTL; expiry releases the hold without charging the customer` | [[reservation.order]] |
spk lint reservation-order.md
lint: 1 file(s) linted, 9 finding(s), 0 advisory warning(s)
reservation-order.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
reservation-order.md [linter.total_refs] dangling reference `[[reservation.orders]]` from constraints.traces_to — target not defined in any spec file
reservation.order [linter.coverage] constraint `all_or_nothing` has no deriving property — ∃ property.derives_from == `reservation.order.all_or_nothing` is required — write `[[reservation.order.all_or_nothing]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `cancel_refunds_exactly_charged` has no deriving property — ∃ property.derives_from == `reservation.order.cancel_refunds_exactly_charged` is required — write `[[reservation.order.cancel_refunds_exactly_charged]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `charge_iff_fulfilled` has no deriving property — ∃ property.derives_from == `reservation.order.charge_iff_fulfilled` is required — write `[[reservation.order.charge_iff_fulfilled]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `expiry_releases_holds` has no deriving property — ∃ property.derives_from == `reservation.order.expiry_releases_holds` is required — write `[[reservation.order.expiry_releases_holds]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `held_or_converted` has no deriving property — ∃ property.derives_from == `reservation.order.held_or_converted` is required — write `[[reservation.order.held_or_converted]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `release_is_total` has no deriving property — ∃ property.derives_from == `reservation.order.release_is_total` is required — write `[[reservation.order.release_is_total]]` in the property's derives_from cell
reservation.order [linter.single_root_reachable] 1 row(s) unreachable from any intent row — an orphaned island (traces_to/derives_from/guard/from-to/emits): reservation.order.all_or_nothing
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
Stop and read what one typo bought. Three different rules fired on one wrong cell, and they say different things:
linter.total_refs— the reference dangles: nothing definesreservation.orders. The target id isreservation.order.linter.coverage— every constraint must have a deriving property (the fourth layer’s job; six of these findings stay until then).linter.single_root_reachable—all_or_nothingis now an orphaned island: with its only link broken, it cannot reach the file’s own intent row, and neither can anything hanging off it.
That last one is the format’s structural conscience. Fix the typo:
| all_or_nothing | invariant | `the order is accepted only if every line item's requested quantity is available; otherwise no unit is held and nothing is charged` | [[reservation.order]] |
and the dangling-reference and orphaned-island findings clear — one
cell, two findings. (The coverage finding for all_or_nothing itself
stays: it fires because nothing derives from the constraint yet, not
because of the typo — all six coverage rows stand until the next layer.)
What remains is linter.model_present plus the six coverage rows; those
are next — the count dropped from 9 to 7, all of it the typo’s:
lint: 1 file(s) linted, 7 finding(s), 0 advisory warning(s)
reservation-order.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
reservation.order [linter.coverage] constraint `all_or_nothing` has no deriving property — ∃ property.derives_from == `reservation.order.all_or_nothing` is required — write `[[reservation.order.all_or_nothing]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `cancel_refunds_exactly_charged` has no deriving property — ∃ property.derives_from == `reservation.order.cancel_refunds_exactly_charged` is required — write `[[reservation.order.cancel_refunds_exactly_charged]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `charge_iff_fulfilled` has no deriving property — ∃ property.derives_from == `reservation.order.charge_iff_fulfilled` is required — write `[[reservation.order.charge_iff_fulfilled]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `expiry_releases_holds` has no deriving property — ∃ property.derives_from == `reservation.order.expiry_releases_holds` is required — write `[[reservation.order.expiry_releases_holds]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `held_or_converted` has no deriving property — ∃ property.derives_from == `reservation.order.held_or_converted` is required — write `[[reservation.order.held_or_converted]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `release_is_total` has no deriving property — ∃ property.derives_from == `reservation.order.release_is_total` is required — write `[[reservation.order.release_is_total]]` in the property's derives_from cell
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
Layer 3 — Model
The Model section is a finite state machine: a ### States list and a
### Transitions table (id | from | to | guard). The first draft most
people write is wrong in instructive ways. This draft has a missing
guard, a misspelled state name, a state declared but never used — and it
draws the happy path while leaving the failure path mute:
### States
- `submitted`
- `reserved`
- `charged`
- `fulfilled`
- `cancelled`
- `released`
- `expired`
- `reserve_failed`
### Transitions
| id | from | to | guard |
|---------------|-----------|----------------|-----------------------------------------------------------------------------------------------------|
| accept | submitted | reserved | [[reservation.order.all_or_nothing]] |
| reject_order | submitted | reserve_failed | stock unavailable at acceptance time |
| charge | reserved | charged | |
| fulfill | charged | shipped | payment settled |
| cancel_paid | charged | cancelled | `customer cancelled ∧ ¬payment settled ∧ [[reservation.order.cancel_refunds_exactly_charged]]` |
| cancel_unpaid | reserved | cancelled | `customer cancelled ∧ [[reservation.order.held_or_converted]]` |
| expire | reserved | released | `reservation TTL elapsed ∧ [[reservation.order.expiry_releases_holds]]` |
spk lint reservation-order.md
lint: 1 file(s) linted, 13 finding(s), 0 advisory warning(s)
reservation-order.md [linter.guard_required] transition `charge` has an empty guard — every guard must be non-null
reservation-order.md [linter.every_state_used] state `fulfilled` is declared but no transition enters or leaves it — every state must appear as from or to in at least one transition
reservation-order.md [linter.every_state_used] state `expired` is declared but no transition enters or leaves it — every state must appear as from or to in at least one transition
reservation-order.md [linter.every_transition_valid] transition `fulfill` has to `shipped` — not a declared state (states: ["cancelled", "charged", "expired", "fulfilled", "released", "reserve_failed", "reserved", "submitted"])
reservation-order.md [linter.terminal_states_emit] failure terminal `reserve_failed` emits nothing — a mute failure terminal is a finding; add an emits edge to a file-owned effect Constraint: `- reserve_failed (emits: [[reservation.order.<label>]])`
reservation-order.md [linter.guard_negation_total] failure transition `reject_order` cites nothing and is not on the carve-out list (orchestrate's stage-fails) — cite the union of its success siblings' citation sets, or record a carve-out in linter-failure_shape.md
reservation.order [linter.coverage] constraint `all_or_nothing` has no deriving property — ∃ property.derives_from == `reservation.order.all_or_nothing` is required — write `[[reservation.order.all_or_nothing]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `cancel_refunds_exactly_charged` has no deriving property — ∃ property.derives_from == `reservation.order.cancel_refunds_exactly_charged` is required — write `[[reservation.order.cancel_refunds_exactly_charged]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `charge_iff_fulfilled` has no deriving property — ∃ property.derives_from == `reservation.order.charge_iff_fulfilled` is required — write `[[reservation.order.charge_iff_fulfilled]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `expiry_releases_holds` has no deriving property — ∃ property.derives_from == `reservation.order.expiry_releases_holds` is required — write `[[reservation.order.expiry_releases_holds]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `held_or_converted` has no deriving property — ∃ property.derives_from == `reservation.order.held_or_converted` is required — write `[[reservation.order.held_or_converted]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `release_is_total` has no deriving property — ∃ property.derives_from == `reservation.order.release_is_total` is required — write `[[reservation.order.release_is_total]]` in the property's derives_from cell
reservation.order [linter.single_root_reachable] 2 row(s) unreachable from any intent row — an orphaned island (traces_to/derives_from/guard/from-to/emits): reservation.order.expired, reservation.order.fulfilled
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
Thirteen findings, four distinct lessons:
Shape mistakes. charge has an empty guard
(linter.guard_required — a transition without a guard is not a
decision, it is an accident). fulfill points at shipped, which is
not declared (linter.every_transition_valid). And expired was
declared but never wired into any transition
(linter.every_state_used).
A typo cascades. Notice that fulfilled — a state the author fully
intended to reach — is also reported unused. That is the shipped
typo’s doing: with fulfill pointing elsewhere, nothing enters
fulfilled, so the state became an unreachable island, dragging itself
and expired into one linter.single_root_reachable finding. Fixing
the typo clears both.
The failure path is not optional. The two most interesting findings name the failure path:
linter.terminal_states_emit:reserve_failedis a terminal state with inbound transitions and nothing to say — a mute failure terminal. In this format a failure may not be silent: it must emit exactly one labeled effect Constraint owned by this file.linter.guard_negation_total:reject_order’s guard cites nothing. Its success sibling (accept) cites[[reservation.order.all_or_nothing]], so the failure transition must cite exactly the negated disjunction of that citation set. The rule makes “rejection happens for the same reason acceptance does not” a machine-checked shape, not a hope.
Fixing all of this touches four places: delete the redundant expired
state (TTL expiry releases the hold — released is that state), give
charge a guard, point fulfill at fulfilled, and give the failure
terminal something to emit. Emitting needs a new effect row — the
fourth constraint kind the file meets for the first time:
| stock_exhausted | effect | `reservation.order.stock_exhausted(detail)` | [[reservation.order]] |
and the states list gains the emit edge:
- `reserve_failed` (emits: `[[reservation.order.stock_exhausted]]`)
while the transitions become:
| id | from | to | guard |
|---------------|-----------|----------------|-----------------------------------------------------------------------------------------------------|
| accept | submitted | reserved | [[reservation.order.all_or_nothing]] |
| reject_order | submitted | reserve_failed | `¬[[reservation.order.all_or_nothing]]` |
| charge | reserved | charged | `payment authorized ∧ [[reservation.order.charge_iff_fulfilled]]` |
| fulfill | charged | fulfilled | `payment settled ∧ [[reservation.order.charge_iff_fulfilled]]` |
| cancel_paid | charged | cancelled | `customer cancelled ∧ ¬payment settled ∧ [[reservation.order.cancel_refunds_exactly_charged]]` |
| cancel_unpaid | reserved | cancelled | `customer cancelled ∧ [[reservation.order.held_or_converted]]` |
| expire | reserved | released | `reservation TTL elapsed ∧ [[reservation.order.expiry_releases_holds]]` |
spk lint reservation-order.md
lint: 1 file(s) linted, 7 finding(s), 1 advisory warning(s)
reservation.order [linter.coverage] constraint `all_or_nothing` has no deriving property — ∃ property.derives_from == `reservation.order.all_or_nothing` is required — write `[[reservation.order.all_or_nothing]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `cancel_refunds_exactly_charged` has no deriving property — ∃ property.derives_from == `reservation.order.cancel_refunds_exactly_charged` is required — write `[[reservation.order.cancel_refunds_exactly_charged]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `charge_iff_fulfilled` has no deriving property — ∃ property.derives_from == `reservation.order.charge_iff_fulfilled` is required — write `[[reservation.order.charge_iff_fulfilled]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `expiry_releases_holds` has no deriving property — ∃ property.derives_from == `reservation.order.expiry_releases_holds` is required — write `[[reservation.order.expiry_releases_holds]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `held_or_converted` has no deriving property — ∃ property.derives_from == `reservation.order.held_or_converted` is required — write `[[reservation.order.held_or_converted]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `release_is_total` has no deriving property — ∃ property.derives_from == `reservation.order.release_is_total` is required — write `[[reservation.order.release_is_total]]` in the property's derives_from cell
reservation.order [linter.coverage] constraint `stock_exhausted` has no deriving property — ∃ property.derives_from == `reservation.order.stock_exhausted` is required — write `[[reservation.order.stock_exhausted]]` in the property's derives_from cell
advisory reservation.order [linter.observability] effect `reservation.order.stock_exhausted` has no `observes` reference targeting it — a declared output nobody observes (advisory, exit 0) [hint: add an `observes` column on the row that consumes this output, or remove the effect if it is unintentional]
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
The model shape is clean now; what remains is coverage (next layer) — plus one advisory warning: the new effect is a firehose nobody attached a hose to. The warning asks, in the linter’s own words, “who is this output for?” It never gates (exit 0) — but a rejection reason that is part of the contract deserves an observer, so the finished spec adds one. An observer is a second column on the row that consumes the output:
| reject_reason_surfaced | invariant | `every rejection reaches the caller with the exhausted line items named` | [[reservation.order]] | [[reservation.order.stock_exhausted]] |
Layer 4 — Properties
Every constraint — including the effect row — needs a deriving
property. The Properties table is the evidence plan: unit rows pair a
generator with a predicate; law rows would additionally require
**identity:** and **associativity:** case labels (linter.law_cases)
— none of this file’s claims are algebraic, so it needs none.
## Properties
| id | kind | derives_from | generator | predicate |
|---------------------------|------|--------------------------------------------------|---------------------------------------------------|--------------------------------------------------------------------|
| no_charge_on_rejection | unit | [[reservation.order.all_or_nothing]] | `orders_with_mixed_availability()` | `nothing is charged for any rejected order` |
| holds_all_converted | unit | [[reservation.order.held_or_converted]] | `arbitrary_closed_orders()` | `every held unit is converted or released when the order closes` |
| double_release_noop | unit | [[reservation.order.release_is_total]] | `order_released_twice()` | `the second release changes nothing` |
| fulfilled_implies_charged | unit | [[reservation.order.charge_iff_fulfilled]] | `arbitrary_accepted_orders()` | `charged(order) == (outcome(order) == fulfilled)` |
| refund_bounded | unit | [[reservation.order.cancel_refunds_exactly_charged]] | `charged_orders_cancelled_before_settlement()` | `refund(order) == amount_charged(order)` |
| expiry_free | unit | [[reservation.order.expiry_releases_holds]] | `orders_past_ttl()` | `nothing is charged and no unit remains held after expiry` |
| rejection_labels_emitted | unit | [[reservation.order.reject_reason_surfaced]] | `orders_with_exhausted_items()` | `the rejection message names exactly the exhausted line items` |
| stock_exhausted_emitted | unit | [[reservation.order.stock_exhausted]] | `rejections_over_sold_out_items()` | `the rejection carries the stock_exhausted label with the item detail` |
With the observer row and the property table in place:
spk lint reservation-order.md
lint: 1 file(s) linted, 0 finding(s), 0 advisory warning(s)
→ Run: specodelic graph
Zero findings, zero warnings. The spec is structurally complete. Now walk the rest of the pipeline (the exact commands and their shape are in Installation & Quick Start); the essentials:
spk graph reservation-order.md
graph: 1 file(s), 31 node(s), 39 edge(s), 0 dangling, 0 typing violation(s), 0 supersedes cycle(s), 0 external boundary(ies)
→ Run: specodelic lint
spk compile reservation-order.md --out-dir specodelic/
compile: 1 compiled, 0 failed
reservation-order.md [reservation.order] → specodelic/reservation-order.toml, specodelic/reservation-order_props.rs, specodelic/reservation-order.tla
→ Run: specodelic verify (consumes the *_props.rs artifacts)
Compile emits three artifacts byte-stably: the constraints as TOML, the properties as proptest scaffolding, the model as a TLA+ module.
spk model-check reservation-order.md
model-check: 1 checked, 0 failed
reservation-order.md [reservation.order]: exploration_only (7 states explored) → specodelic/reservation-order.check.json
claims: 0 evaluated, 7 unchecked, 0 blocking
unchecked: all_or_nothing, cancel_refunds_exactly_charged, charge_iff_fulfilled, expiry_releases_holds, held_or_converted, reject_reason_surfaced, release_is_total
→ Run: specodelic verify (consumes the *.check.json reports)
Read that verdict carefully: exploration_only is not a clean bill of
health. All seven states were explored and no executable invariant was
violated — but none of the seven claims was evaluated, because
prose-predicate claims cannot be executed. The tool refuses to dress an
unexplored claim as a verified one.
A counterexample — the checker catches the author
That honesty has a payoff. Suppose the author, wanting a stronger verdict, hardens one claim into an executable invariant — and while at it, over-claims. Working on a scratch copy of the finished file, they add:
| reserve_never_fails | invariant | `**rust:** state != "reserve_failed"` — surely the reject path never fires once the flow is written down | [[reservation.order]] | |
plus its coverage property:
| reserve_never_fails_holds | unit | [[reservation.order.reserve_never_fails]] | `arbitrary_orders()` | `no order ever reaches reserve_failed` |
The **rust:** marker makes the invariant executable: the model-check
backend evaluates it as a Rust boolean over the program counter, the
machine’s current state name. Lint is happy — coverage holds, shape
holds:
cp reservation-order.md /tmp/reservation/
cd /tmp/reservation
spk lint reservation-order.md
lint: 1 file(s) linted, 0 finding(s), 0 advisory warning(s)
→ Run: specodelic graph
Lint cannot catch this mistake — it is structural, and this file is structurally impeccable. The claim itself is simply false, and the next stage proves it:
spk compile reservation-order.md --out-dir specodelic/
spk model-check reservation-order.md
compile: 1 compiled, 0 failed
reservation-order.md [reservation.order] → specodelic/reservation-order.toml, specodelic/reservation-order_props.rs, specodelic/reservation-order.tla
→ Run: specodelic verify (consumes the *_props.rs artifacts)
model-check: 1 checked, 0 failed
reservation-order.md [reservation.order]: counterexample_found (3 states explored) → specodelic/reservation-order.check.json
claims: 1 evaluated, 7 unchecked, 1 blocking
blocking: reserve_never_fails (rust, counterexample)
unchecked: all_or_nothing, cancel_refunds_exactly_charged, charge_iff_fulfilled, expiry_releases_holds, held_or_converted, reject_reason_surfaced, release_is_total
model-check findings: the claim aggregate is not clean (see .data.checked)
→ Run: fix the refuted claim(s) named in .data.checked — verify's model gate accepts only a no_counterexample aggregate
counterexample_found — and the report names the violated invariant and
the shortest trace that refutes it (from the emitted
reservation-order.check.json):
{
"outcome": "counterexample_found",
"violated_invariant_id": "reserve_never_fails",
"trace": ["submitted", "reserve_failed"],
"states_explored": 3,
"invariants_checked": ["reserve_never_fails"]
}
The model checker explored the reachable states — states_explored: 3,
the two successors of submitted and the root — and reported the
two-step trace ending in the very state the author swore was
unreachable. Rejection is reachable by design —
all_or_nothing can fail; that is what the failure terminal is for. The
executable invariant claimed otherwise, so the invariant was the bug.
The fix is to delete the over-claiming row and its property (the true
claim — why rejection happens — is already carried by the transition’s
guard). Re-running spk model-check on the restored file returns
exactly the exploration_only verdict shown above. The checker was the
design critic; the counterexample was the design lesson.
This is also where the format’s honesty boundary shows: the program
counter model carries no data binding — an executable invariant sees
only the machine’s current state name, never the order’s contents or its
history. Claims like “no charge without fulfillment” live in the
Constraints table as prose invariants and their Properties plans; they
get executed only when someone writes a real **rust:** fragment in a
backend that can evaluate them.
What verify refuses to pretend
The last stage executes the property scaffolding:
spk verify reservation-order.md
verify: 0 verified, 1 blocked
reservation-order.md [reservation.order]: properties_failed — 8 of 8 compiled blocks failed — a partial pass is a failure of the property
unchecked: all_or_nothing, cancel_refunds_exactly_charged, charge_iff_fulfilled, expiry_releases_holds, held_or_converted, reject_reason_surfaced, release_is_total
verify blocked — the blocking gate, message and hint ride .data.blocked
→ Run: fix the failing predicates (see each block's detail for the shrunk failing input)
Eight of eight blocks fail — because every predicate in this file is
still prose. Compile emitted them as todo_predicate! placeholders that
compile fine and fail the moment verify executes them. That is the
format refusing to pretend: a claim that was never executed is never
reported as passing, and a partial pass is a failure. The next rung on
the progressive-formalization ladder is opting individual predicates
into **rust:** fragments — one property at a time, only where a real
assertion can be written.
Unresolved behaviors
A finished spec is not a finished design. This file deliberately leaves open:
- Payment semantics.
payment authorizedandpayment settledare prose guards; the compiled TLA+ module carries them as comments and stutters at terminals rather than inventing semantics. The spec does not model the gateway. - The value of the TTL.
expirefires “after its TTL” — no duration is decided, and the schema has no column that could carry one. - Refund timing.
cancel_refunds_exactly_chargedbounds the amount; when the refund lands is not decided. - Concurrent orders. The model is one order’s program counter. Two orders racing for the last unit is a different capability — its own file, its own intent — not a row bolted onto this one.
- Partial fulfillment.
all_or_nothingwas a design decision, not an accident of scope: no partial accept is modeled, deliberately. - Unexecuted predicates. All eight predicates are prose. The claims
are as strong as their evidence plan, and the evidence has not been
written yet — see
spk verifyabove.
Each of these would land in a named place if decided: a constraint row, a property row, a transition guard, or a new file — the format refuses to let them remain ambient.
The finished file
The result of every fix above is
examples/reservation-order.md — the standalone
spec exactly as it lints at 0 findings, 0 warnings. For a second worked
example that goes deeper on one mechanism (how the accumulator’s algebra
licenses parallel architectures), see
batch-resume — derived parallelism, worked end to end.
Worked example — the data.lineage pack, failures included
The generic worked example builds a spec with the
core format alone. This page does the same job for a domain pack:
data.lineage,
the pack that adds a typed ## Data section, dataset/artifact/environment
kinds, and lineage reference fields — without a format revision and
without growing any base closed set. You will see a small spec use the
pack’s vocabulary end to end, the pack-activation advisory that proves
the pack actually engaged, two genuinely failing variants with their real
labeled findings, and — just as important — the failures the linter does
not catch yet, shown honestly.
How packs are enabled and what a pack adds mechanically is the job of
using packs — the practical guide; read that for the
mechanism, this page for the hands-on loop. Every lint and graph block
below is real output from specodelic 0.10.0 on the file as it stood at
that moment. On a TTY spk prints the human-readable text shown here;
piped, it emits a JSON envelope instead.
The story
A sequencing lab runs an ETL pipeline: raw reads land in object storage,
a count table and a QC report are derived from them, and every dataset
names the environment (instrument, manifest) it was produced in. The
spec’s job is to record that lineage — one capability, one file, named
lab-etl.md; the format’s naming law maps - to the namespace dot, so
the frontmatter id is lab.etl.
Deliberately out of scope: where the data physically lives and which external standard governs each artifact. That is the binding column’s job, and it stays opaque — the pack bridges external data standards (OME/NGFF, Parquet, …), it never absorbs them.
The setup
Packs are discovered by scanning the workspace, not by any config file:
every kind: profile file in the git repository toplevel of the linted
spec is a candidate pack. So the demo workspace needs two files — the
pack definition (copied from this repo’s packs/ directory) and the
spec under test:
mkdir demo && cd demo && git init
cp /path/to/specodelic/packs/data-lineage.md .
That is the whole enablement story. The discovery and activation rules (corpus-scan anchors, word-boundary vocabulary matching, the byte-identical guarantee) are in the guide; this page just runs the loop.
Layer 1 — Intent
---
id: lab.etl
kind: intent
statement: "WHEN the lab's ETL pipeline runs THE system SHALL record every dataset, artifact, and environment it touches together with the lineage edges between them."
---
# Lab ETL lineage
A small data-lineage record for one sequencing ETL run: the pipeline
reads raw reads, produces a count table and a QC report, and every
dataset names the environment it ran in.
Lint it as it stands:
spk lint lab-etl.md
lint: 1 file(s) linted, 1 finding(s), 0 advisory warning(s)
lab-etl.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.model_present — a spec with an intent but no Model section is
incomplete by construction, same as in the generic worked example. Note
what is missing from this output: no pack advisory. The file uses none
of the pack’s vocabulary yet, so the byte-identical guarantee holds —
the pack machinery is invisible. That changes with the next section.
Layer 2 — the ## Data section
This is the pack’s namespaced section, declared in the pack’s
## Sections manifest with the fixed row shape
| name | kind | dtype | units | binding |:
## Data
| name | kind | dtype | units | binding |
|-----------|------------------|----------|--------|----------------------|
| raw_reads | data.dataset | fastq | reads | s3://lab/raw-reads |
| counts | data.artifact | tsv | count | parquet://lab/counts |
| qc_report | data.artifact | html | report | file://lab/qc.html |
| sequencer | data.environment | manifest | run | equipment://seq/7 |
Each column, defined for a cold read:
- name — the row’s id inside this file; lineage edges point at it.
- kind — the pack’s own closed set:
data.dataset(a bounded collection of measurements),data.artifact(a derived object a pipeline produces or consumes),data.environment(the execution or measurement context of a dataset or artifact). These are fiber kinds — pack-declared vocabulary, never a growth of the base closed sets. - dtype / units — free-form descriptors of the data’s shape and unit of measure. The pack does not constrain their values.
- binding — the opaque pointer to the external system of record
(
s3://…,parquet://…). The pack declares no syntax for it and the linter never interprets it: external data standards stay authoritative.
Lineage edges are the pack’s reference fields, declared in its
## References manifest — produced_by and consumed_by, each
resolving to a ## Data row of the same file. They ride as columns on a
second table under the same section:
| row | produced_by | consumed_by |
|-----------|-------------|-------------|
| raw_reads | | counts |
| counts | raw_reads | qc_report |
Read the edges aloud: raw_reads is consumed by counts; counts is
produced by raw_reads and consumed by qc_report. The pack also
declares a checker — data.lineage.closure, every lineage reference
name resolves to a ## Data row declared in the same file — and a
case-label floor: law-kind properties about data.artifact rows must
carry a provenance: case. (This spec’s properties are all unit kind,
so the floor does not bite; see the guide for what
floors demand.)
Layer 3 — Constraints, Model, Properties
The rest of the file is the ordinary four-layer shape — the pack adds
vocabulary, it does not change the layers. Two invariants state the
lineage claims, an effect constraint labels the failure output, an
observes edge ties the observer to the effect, and the model is a
five-state run lifecycle whose failure terminal emits:
## Constraints
| id | kind | expr | traces_to | observes |
|----------------|-----------|-----------------------------------------------------------------------------------|-------------|------------------------|
| lineage_closed | invariant | `every produced_by or consumed_by edge names a ## Data row declared in this file` | [[lab.etl]] | |
| env_binds_run | invariant | `every dataset names exactly one environment as its execution context` | [[lab.etl]] | |
| lineage_gap | effect | `lab.etl.lineage_gap(detail)` | [[lab.etl]] | |
| gap_surfaced | invariant | `every lineage gap reaches the operator with the offending edge named` | [[lab.etl]] | [[lab.etl.lineage_gap]] |
## Model
### States
- `started`
- `ingested`
- `counted`
- `reported`
- `failed` (emits: `[[lab.etl.lineage_gap]]`)
### Transitions
| id | from | to | guard |
|--------|----------|----------|-----------------------------------------------------------|
| begin | started | ingested | `sequencer manifest present ∧ [[lab.etl.env_binds_run]]` |
| count | ingested | counted | `[[lab.etl.lineage_closed]]` |
| report | counted | reported | `qc_report binding written` |
| fail | started | failed | `¬[[lab.etl.env_binds_run]]` |
## Properties
| id | kind | derives_from | generator | predicate |
|---------------|------|-----------------------------|------------------------------|---------------------------------------------------------|
| edges_resolve | unit | [[lab.etl.lineage_closed]] | `runs_with_lineage_edges()` | `every lineage edge resolves to a declared ## Data row` |
| env_recorded | unit | [[lab.etl.env_binds_run]] | `runs_with_datasets()` | `every dataset carries its environment binding` |
| gap_emitted | unit | [[lab.etl.lineage_gap]] | `runs_with_undefined_rows()` | `the failure carries the lineage_gap label with detail` |
| gap_named | unit | [[lab.etl.gap_surfaced]] | `runs_with_undefined_rows()` | `the operator message names the offending lineage edge` |
Nothing pack-specific here — traces_to, guards, emits, coverage:
all core-format machinery, taught properly in the
generic worked example.
First full lint — a dangling reference, then the fix
The author’s first full draft carries one typo: the lineage_closed
constraint traces to [[lab.ets]] — plural, misremembered, the same
classic as the reservation example. Lint the file:
spk lint lab-etl.md
lint: 1 file(s) linted, 1 finding(s), 1 advisory warning(s)
lab-etl.md [linter.total_refs] dangling reference `[[lab.ets]]` from constraints.traces_to — target not defined in any spec file
advisory lab.etl [linter.observability] pack `data.lineage` activated for `lab.etl` (vocabulary match: data.dataset, data.artifact, data.environment, Data, produced_by, consumed_by) — empty checked-set (no violations to report)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
Exit 1 — a finding, never a silent pass. Two things to read here:
- The finding and the advisory ride different channels. The pack activated (its vocabulary is all over the file — note the match list names every token that triggered it), but activation lives on the warnings channel and never gates. The dangling reference lives on the findings channel and does.
empty checked-set (no violations to report)— the pack’s declareddata.lineage.closurechecker ran and honestly reports an empty violation set. More on what that does and does not mean below.
Fix the typo — [[lab.ets]] → [[lab.etl]] — and lint again:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory lab.etl [linter.observability] pack `data.lineage` activated for `lab.etl` (vocabulary match: data.dataset, data.artifact, data.environment, Data, produced_by, consumed_by) — empty checked-set (no violations to report)
→ Run: specodelic graph
Zero findings, exit 0 — and now the advisory is the whole output.
Read it as the meter it is: an exit-0 lint alone proves nothing (a spec
using none of the pack vocabulary also exits 0, silently); the
activation line naming data.lineage is the observable that the pack
was discovered, matched, and engaged. The pipeline’s next stage runs
pack-aware too:
spk graph lab-etl.md
graph: 1 file(s), 18 node(s), 21 edge(s), 0 dangling, 0 typing violation(s), 0 supersedes cycle(s), 0 external boundary(ies)
→ Run: specodelic lint
What a missing pack looks like
The demo depends on the pack file being discovered. Delete the setup
step — lint the same finished spec in a directory with no
kind: profile file in it — and the machinery goes inert:
lint: 1 file(s) linted, 0 finding(s), 0 advisory warning(s)
→ Run: specodelic graph
No advisory, no findings — the byte-identical guarantee in action. The
## Data table is now just markdown the linter does not interpret, and
that is by design: a pack can never gate a file that did not opt in.
But inert cuts both ways, and the linter tells you when vocabulary
expects a pack. Say the author, pack-enthused, writes a base-table
kind cell as data.dataset instead of invariant, in that same
pack-less directory:
lint: 1 file(s) linted, 2 finding(s), 0 advisory warning(s)
lab.etl [linter.constraint_kind_closed] constraint `lineage_closed` has kind `data.dataset` — outside the closed set {invariant, advisory, effect, extension_point} (kinds.md constraint_row_shape)
lab.etl [linter.orphan_vocabulary] orphan vocabulary: pack-qualified token(s) `data.dataset` used by `lab.etl`, but no `kind: profile` pack in namespace `data` is discovered — candidate pack `data.*` (prefix-derived: only the namespace is known); remediations: add/enable a `kind: profile` pack file declaring the vocabulary, or fix the vocabulary (un-qualify or retype the token)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.orphan_vocabulary — the finding always names the candidate
(namespace-derived, data.* here) and states both remediations:
enable a pack declaring the vocabulary, or fix the token. Copy the pack
file back into the workspace and the orphan finding clears — with the
pack active, the walkers accept a fiber kind in a kind column
(base ∪ active-pack-fiber), so that particular mis-typing only surfaces
when the pack is missing. The reliable diagnostic is the one this
section started with: a spec you meant to be pack-enabled but lint
with no activation advisory is a spec whose pack was not discovered.
What the linter does not catch (yet)
Honesty check — the failure this page has not shown is the one you
might expect first: a lineage edge that dangles. Change qc_report to
ghost_row in the consumed_by column and lint:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory lab.etl [linter.observability] pack `data.lineage` activated for `lab.etl` (vocabulary match: data.dataset, data.artifact, data.environment, Data, produced_by, consumed_by) — empty checked-set (no violations to report)
→ Run: specodelic graph
Byte-for-byte the clean output — exit 0, activation advisory, nothing
else. That is not the linter missing the dangle by accident: data.lineage.closure
ships as a declaration. In v1, pack checkers are honest-empty —
activation and the empty checked-set are reported truthfully, violation
evaluation is not implemented yet (the
guide documents this, and
verification boundaries places it on
the guarantee ladder). The format’s answer to the gap is the one this
spec already embodies: the dangling-edge claim lives where the evidence
plan lives — the lineage_closed invariant, its edges_resolve
property, and eventually a real executable predicate. When the checker
gains evaluation, the declaration is already in place; the spec does not
change.
The finished spec
Every block above assembles into this one file, lab-etl.md — exactly
as it lints at 0 findings, 1 activation advisory:
---
id: lab.etl
kind: intent
statement: "WHEN the lab's ETL pipeline runs THE system SHALL record every dataset, artifact, and environment it touches together with the lineage edges between them."
---
# Lab ETL lineage
A small data-lineage record for one sequencing ETL run: the pipeline
reads raw reads, produces a count table and a QC report, and every
dataset names the environment it ran in.
## Constraints
| id | kind | expr | traces_to | observes |
|----------------|-----------|-----------------------------------------------------------------------------------|-------------|------------------------|
| lineage_closed | invariant | `every produced_by or consumed_by edge names a ## Data row declared in this file` | [[lab.etl]] | |
| env_binds_run | invariant | `every dataset names exactly one environment as its execution context` | [[lab.etl]] | |
| lineage_gap | effect | `lab.etl.lineage_gap(detail)` | [[lab.etl]] | |
| gap_surfaced | invariant | `every lineage gap reaches the operator with the offending edge named` | [[lab.etl]] | [[lab.etl.lineage_gap]] |
## Data
| name | kind | dtype | units | binding |
|-----------|------------------|----------|--------|----------------------|
| raw_reads | data.dataset | fastq | reads | s3://lab/raw-reads |
| counts | data.artifact | tsv | count | parquet://lab/counts |
| qc_report | data.artifact | html | report | file://lab/qc.html |
| sequencer | data.environment | manifest | run | equipment://seq/7 |
| row | produced_by | consumed_by |
|-----------|-------------|-------------|
| raw_reads | | counts |
| counts | raw_reads | qc_report |
## Model
### States
- `started`
- `ingested`
- `counted`
- `reported`
- `failed` (emits: `[[lab.etl.lineage_gap]]`)
### Transitions
| id | from | to | guard |
|--------|----------|----------|-----------------------------------------------------------|
| begin | started | ingested | `sequencer manifest present ∧ [[lab.etl.env_binds_run]]` |
| count | ingested | counted | `[[lab.etl.lineage_closed]]` |
| report | counted | reported | `qc_report binding written` |
| fail | started | failed | `¬[[lab.etl.env_binds_run]]` |
## Properties
| id | kind | derives_from | generator | predicate |
|---------------|------|-----------------------------|------------------------------|---------------------------------------------------------|
| edges_resolve | unit | [[lab.etl.lineage_closed]] | `runs_with_lineage_edges()` | `every lineage edge resolves to a declared ## Data row` |
| env_recorded | unit | [[lab.etl.env_binds_run]] | `runs_with_datasets()` | `every dataset carries its environment binding` |
| gap_emitted | unit | [[lab.etl.lineage_gap]] | `runs_with_undefined_rows()` | `the failure carries the lineage_gap label with detail` |
| gap_named | unit | [[lab.etl.gap_surfaced]] | `runs_with_undefined_rows()` | `the operator message names the offending lineage edge` |
To reproduce every block on this page: copy
packs/data-lineage.md from the repo into a fresh git workspace, save
the file above as lab-etl.md, and run spk lint lab-etl.md.
Where to go next
- The mechanism, end to end: using packs — the practical guide — discovery, activation, honest-empty semantics, the full CLI loop.
- The pack’s normative home: packs — domain-pack
mechanism, and the pack definition itself —
packs/data-lineage.mdis the source of truth for the vocabulary this page used. - The capability spec behind the pack: data-lineage-pack.
- The same journey without packs: worked example — reservation/order.
- Rule semantics offline:
spk explain packsandspk explain lint-rules.
Worked example — the numeric.predicates pack, failures included
The generic worked example builds a spec with the
core format alone, and the data.lineage page does the
same for the first domain pack. This page runs the identical journey for
numeric.predicates
— the pack that adds a typed ## Quantities section, the
numeric.quantity / numeric.bound / numeric.tolerance kinds, a
measured_by reference field, and a case-label floor for tolerance laws
— without a format revision and without growing any base closed set.
You will see a small spec use the pack’s vocabulary end to end, the
pack-activation advisory that proves the pack actually engaged, two
genuinely failing variants with their real labeled findings, and — just
as important — the failures the linter does not catch yet, shown
honestly.
How packs are enabled and what a pack adds mechanically is the job of
using packs — the practical guide; read that for the
mechanism, this page for the hands-on loop. Every lint and graph block
below is real output from specodelic 0.10.0 on the file as it stood at
that moment. On a TTY spk prints the human-readable text shown here;
piped, it emits a JSON envelope instead.
The story
A brewery runs a fermentation line: every batch measures mash
temperature and pH, derives an ABV yield from a gas-chromatography
assay, and must respect a regulatory ethanol cap and a pH window. The
spec’s job is to record those quantities and the limits that govern
them — one capability, one file, named batch-yield.md; the format’s
naming law maps - to the namespace dot, so the frontmatter id is
batch.yield.
Deliberately out of scope: which unit system the numbers are written
in and where each domain of validity is defined. Those are the
unit and domain columns’ job, and they stay opaque — the pack
bridges external unit systems (UCUM, ISO 4217, microscopy units, …), it
never absorbs them. Lint behavior is byte-identical no matter what
prose the columns carry.
The setup
Packs are discovered by scanning the workspace, not by any config file:
every kind: profile file in the git repository toplevel of the linted
spec is a candidate pack. So the demo workspace needs two files — the
pack definition (copied from this repo’s packs/ directory) and the
spec under test:
mkdir demo && cd demo && git init
cp /path/to/specodelic/packs/numeric-predicates.md .
That is the whole enablement story. The discovery and activation rules (corpus-scan anchors, word-boundary vocabulary matching, the byte-identical guarantee) are in the guide; this page just runs the loop.
Layer 1 — Intent
---
id: batch.yield
kind: intent
statement: "WHEN a fermentation batch is processed THE system SHALL record every quantity it measures or derives together with the bounds and tolerances that govern them."
---
# Batch yield quantities
A small numeric record for one fermentation line: the process measures
mash temperature and pH, derives an ABV yield from a gas-chromatography
assay, and every stated limit is a bound or a tolerance with a unit and
a domain of validity.
Lint it as it stands:
spk lint batch-yield.md
lint: 1 file(s) linted, 1 finding(s), 0 advisory warning(s)
batch-yield.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.model_present — a spec with an intent but no Model section is
incomplete by construction, same as in the generic worked example. Note
what is missing from this output: no pack advisory. The file uses none
of the pack’s vocabulary yet, so the byte-identical guarantee holds —
the pack machinery is invisible. That changes with the next section.
Layer 2 — the ## Quantities section
This is the pack’s namespaced section, declared in the pack’s
## Sections manifest with the fixed row shape
| name | kind | unit | domain |:
## Quantities
| name | kind | unit | domain |
|---------------|-------------------|--------------------------|------------|
| mash_temp | numeric.quantity | degC (UCUM Cel) | process |
| ph | numeric.quantity | pH (dimensionless) | process |
| ethanol_assay | numeric.quantity | percent v/v (UCUM [pct]V)| product |
| abv_yield | numeric.quantity | percent v/v (UCUM [pct]V)| product |
| ethanol_cap | numeric.bound | percent v/v (UCUM [pct]V)| regulatory |
| ph_window | numeric.tolerance | pH | process |
Each column, defined for a cold read:
- name — the row’s id inside this file;
measured_byedges point at it. - kind — the pack’s own closed set:
numeric.quantity(a measured or derived real-valued quantity),numeric.bound(a stated real-valued limit on a quantity),numeric.tolerance(an empirically held tolerance on a quantity). These are fiber kinds — pack-declared vocabulary, never a growth of the base closed sets. - unit / domain — typed prose pointers to the unit system and the domain of validity. The pack declares no syntax for either and the linter never interprets them: external unit systems stay authoritative, and lint behavior is identical regardless of what the columns say.
Edges come from the pack’s single reference field, declared in its
## References manifest — measured_by, resolving to a ## Quantities
row of the same file. It rides as a column on a second table under the
same section:
| row | measured_by |
|-----------|--------------|
| abv_yield | ethanol_assay|
Read the edge aloud: the derived abv_yield is measured by the
ethanol_assay quantity — the assay is the quantity’s source of
evidence, and it is itself a declared row.
The pack also declares two checkers — numeric.quantity_closed, every
measured_by name resolves to a ## Quantities row declared in the
same file, and numeric.tolerance_labels, numeric.tolerance laws
enumerate their bound and against case labels per the pack’s floor
— and one floor: law-kind properties about numeric.tolerance rows
must carry bound: and against: case labels. The Properties
layer below pays that floor in full.
Layer 3 — Constraints, Model, Properties
The rest of the file is the ordinary four-layer shape — the pack adds
vocabulary, it does not change the layers. Two invariants state the
quantity claims, an effect constraint labels the failure output, an
observes edge ties the observer to the effect, and the model is a
five-state batch lifecycle whose failure terminal emits. The Properties
table includes one law row, window_cases, which enumerates four
case labels: the base format’s mandatory identity: and
associativity: floor (linter.law_cases demands those of every
law) plus the pack floor’s bound: and against::
## Constraints
| id | kind | expr | traces_to | observes |
|-------------------|-----------|-------------------------------------------------------------------------------------------|------------------|-----------------------------|
| rows_declared | invariant | `every quantity row is declared in the ## Quantities table of this file` | [[batch.yield]] | |
| yield_measured | invariant | `every derived quantity names exactly one assay quantity as its measured_by source` | [[batch.yield]] | |
| cap_breach | effect | `batch.yield.cap_breach(detail)` | [[batch.yield]] | |
| breach_surfaced | invariant | `every bound breach reaches the operator with the quantity and the offending bound named` | [[batch.yield]] | [[batch.yield.cap_breach]] |
## Model
### States
- `mashed`
- `fermented`
- `assayed`
- `released`
- `failed` (emits: `[[batch.yield.cap_breach]]`)
### Transitions
| id | from | to | guard |
|---------|-----------|-----------|-----------------------------------------------------------|
| ferment | mashed | fermented | `[[batch.yield.rows_declared]]` |
| assay | fermented | assayed | `[[batch.yield.yield_measured]]` |
| release | assayed | released | `abv_yield within ethanol_cap` |
| fail | mashed | failed | `¬[[batch.yield.rows_declared]]` |
## Properties
| id | kind | derives_from | generator | predicate |
|------------------|------|-----------------------------------|--------------------------------|-----------------------------------------------------------------|
| rows_resolvable | unit | [[batch.yield.rows_declared]] | `batches_with_quantity_rows()` | `every measured_by name resolves to a declared ## Quantities row`|
| assay_recorded | unit | [[batch.yield.yield_measured]] | `batches_with_derived_quantities()` | `every derived quantity carries its assay source` |
| breach_emitted | unit | [[batch.yield.cap_breach]] | `batches_with_bound_breaches()`| `the failure carries the cap_breach label with detail` |
| breach_named | unit | [[batch.yield.breach_surfaced]] | `batches_with_bound_breaches()`| `the operator message names the quantity and the bound` |
| window_cases | law | [[batch.yield.rows_declared]] | `batches_with_tolerance_rows()` | `the tolerance holds in every declared case — **identity:** a quantity measured against itself is within any window; **associativity:** composing assays is order-independent; **bound:** the lower and upper pH bounds hold; **against:** readings outside the window fail against the stated tolerance` |
Nothing pack-specific in the layers themselves — traces_to, guards,
emits, coverage: all core-format machinery, taught properly in the
generic worked example. Omit either pair of case
labels from window_cases and linter.law_cases names the missing
floor cases exactly.
First full lint — a dangling reference, then the fix
The author’s first full draft carries one typo: the rows_declared
constraint traces to [[batch.yiled]] — plural, misremembered, the
same classic as the reservation example. Lint the file:
spk lint batch-yield.md
lint: 1 file(s) linted, 1 finding(s), 1 advisory warning(s)
batch-yield.md [linter.total_refs] dangling reference `[[batch.yiled]]` from constraints.traces_to — target not defined in any spec file
advisory batch.yield [linter.observability] pack `numeric.predicates` activated for `batch.yield` (vocabulary match: numeric.quantity, numeric.bound, numeric.tolerance, Quantities, measured_by) — empty checked-set (no violations to report)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
Exit 1 — a finding, never a silent pass. Two things to read here:
- The finding and the advisory ride different channels. The pack activated (its vocabulary is all over the file — note the match list names every token that triggered it), but activation lives on the warnings channel and never gates. The dangling reference lives on the findings channel and does.
empty checked-set (no violations to report)— the pack’s declared checkers ran and honestly report an empty violation set. More on what that does and does not mean below.
Fix the typo — [[batch.yiled]] → [[batch.yield]] — and lint again:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory batch.yield [linter.observability] pack `numeric.predicates` activated for `batch.yield` (vocabulary match: numeric.quantity, numeric.bound, numeric.tolerance, Quantities, measured_by) — empty checked-set (no violations to report)
→ Run: specodelic graph
Zero findings, exit 0 — and now the advisory is the whole output.
Read it as the meter it is: an exit-0 lint alone proves nothing (a spec
using none of the pack vocabulary also exits 0, silently); the
activation line naming numeric.predicates is the observable that
the pack was discovered, matched, and engaged. The pipeline’s next
stage runs pack-aware too:
spk graph batch-yield.md
graph: 1 file(s), 18 node(s), 21 edge(s), 0 dangling, 0 typing violation(s), 0 supersedes cycle(s), 0 external boundary(ies)
→ Run: specodelic lint
What a missing pack looks like
The demo depends on the pack file being discovered. Copy the finished
spec into a directory with no kind: profile file in it — the
machinery goes inert:
lint: 1 file(s) linted, 0 finding(s), 0 advisory warning(s)
→ Run: specodelic graph
No advisory, no findings — the byte-identical guarantee in action. The
## Quantities table is now just markdown the linter does not
interpret, and that is by design: a pack can never gate a file that did
not opt in. But inert cuts both ways, and the linter tells you when
vocabulary expects a pack. Say the author, pack-enthused, writes a
base-table kind cell as numeric.quantity instead of invariant,
in that same pack-less directory:
lint: 1 file(s) linted, 2 finding(s), 0 advisory warning(s)
batch.yield [linter.constraint_kind_closed] constraint `rows_declared` has kind `numeric.quantity` — outside the closed set {invariant, advisory, effect, extension_point} (kinds.md constraint_row_shape)
batch.yield [linter.orphan_vocabulary] orphan vocabulary: pack-qualified token(s) `numeric.quantity` used by `batch.yield`, but no `kind: profile` pack in namespace `numeric` is discovered — candidate pack `numeric.*` (prefix-derived: only the namespace is known); remediations: add/enable a `kind: profile` pack file declaring the vocabulary, or fix the vocabulary (un-qualify or retype the token)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.orphan_vocabulary — the finding always names the candidate
(namespace-derived, numeric.* here) and states both remediations:
enable a pack declaring the vocabulary, or fix the token. Copy the pack
file back into the workspace and the orphan finding clears — with the
pack active, the walkers accept a fiber kind in a kind column (base ∪
active-pack-fiber), so that particular mis-typing only surfaces when
the pack is missing. The reliable diagnostic is the one this section
started with: a spec you meant to be pack-enabled but lint with no
activation advisory is a spec whose pack was not discovered.
What the linter does not catch (yet)
Honesty check — the failure this page has not shown is the one you
might expect first: a measured_by edge that dangles. Change
ethanol_assay to ghost_assay in the measured_by column and lint:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory batch.yield [linter.observability] pack `numeric.predicates` activated for `batch.yield` (vocabulary match: numeric.quantity, numeric.bound, numeric.tolerance, Quantities, measured_by) — empty checked-set (no violations to report)
→ Run: specodelic graph
Byte-for-byte the clean output — exit 0, activation advisory, nothing
else. That is not the linter missing the dangle by accident:
numeric.quantity_closed ships as a declaration. In v1, pack
checkers are honest-empty — activation and the empty checked-set are
reported truthfully, violation evaluation is not implemented yet (the
guide documents this, and
verification boundaries places it on
the guarantee ladder). The format’s answer to the gap is the one this
spec already embodies: the dangling-edge claim lives where the evidence
plan lives — the rows_declared invariant, its rows_resolvable
property, and eventually a real executable predicate. When the checker
gains evaluation, the declaration is already in place; the spec does not
change.
The finished spec
Every block above assembles into this one file, batch-yield.md —
exactly as it lints at 0 findings, 1 activation advisory:
---
id: batch.yield
kind: intent
statement: "WHEN a fermentation batch is processed THE system SHALL record every quantity it measures or derives together with the bounds and tolerances that govern them."
---
# Batch yield quantities
A small numeric record for one fermentation line: the process measures
mash temperature and pH, derives an ABV yield from a gas-chromatography
assay, and every stated limit is a bound or a tolerance with a unit and
a domain of validity.
## Quantities
| name | kind | unit | domain |
|---------------|-------------------|--------------------------|------------|
| mash_temp | numeric.quantity | degC (UCUM Cel) | process |
| ph | numeric.quantity | pH (dimensionless) | process |
| ethanol_assay | numeric.quantity | percent v/v (UCUM [pct]V)| product |
| abv_yield | numeric.quantity | percent v/v (UCUM [pct]V)| product |
| ethanol_cap | numeric.bound | percent v/v (UCUM [pct]V)| regulatory |
| ph_window | numeric.tolerance | pH | process |
| row | measured_by |
|-----------|--------------|
| abv_yield | ethanol_assay|
## Constraints
| id | kind | expr | traces_to | observes |
|-------------------|-----------|-------------------------------------------------------------------------------------------|------------------|-----------------------------|
| rows_declared | invariant | `every quantity row is declared in the ## Quantities table of this file` | [[batch.yield]] | |
| yield_measured | invariant | `every derived quantity names exactly one assay quantity as its measured_by source` | [[batch.yield]] | |
| cap_breach | effect | `batch.yield.cap_breach(detail)` | [[batch.yield]] | |
| breach_surfaced | invariant | `every bound breach reaches the operator with the quantity and the offending bound named` | [[batch.yield]] | [[batch.yield.cap_breach]] |
## Model
### States
- `mashed`
- `fermented`
- `assayed`
- `released`
- `failed` (emits: `[[batch.yield.cap_breach]]`)
### Transitions
| id | from | to | guard |
|---------|-----------|-----------|-----------------------------------------------------------|
| ferment | mashed | fermented | `[[batch.yield.rows_declared]]` |
| assay | fermented | assayed | `[[batch.yield.yield_measured]]` |
| release | assayed | released | `abv_yield within ethanol_cap` |
| fail | mashed | failed | `¬[[batch.yield.rows_declared]]` |
## Properties
| id | kind | derives_from | generator | predicate |
|------------------|------|-----------------------------------|--------------------------------|-----------------------------------------------------------------|
| rows_resolvable | unit | [[batch.yield.rows_declared]] | `batches_with_quantity_rows()` | `every measured_by name resolves to a declared ## Quantities row`|
| assay_recorded | unit | [[batch.yield.yield_measured]] | `batches_with_derived_quantities()` | `every derived quantity carries its assay source` |
| breach_emitted | unit | [[batch.yield.cap_breach]] | `batches_with_bound_breaches()`| `the failure carries the cap_breach label with detail` |
| breach_named | unit | [[batch.yield.breach_surfaced]] | `batches_with_bound_breaches()`| `the operator message names the quantity and the bound` |
| window_cases | law | [[batch.yield.rows_declared]] | `batches_with_tolerance_rows()` | `the tolerance holds in every declared case — **identity:** a quantity measured against itself is within any window; **associativity:** composing assays is order-independent; **bound:** the lower and upper pH bounds hold; **against:** readings outside the window fail against the stated tolerance` |
To reproduce every block on this page: copy
packs/numeric-predicates.md from the repo into a fresh git workspace,
save the file above as batch-yield.md, and run
spk lint batch-yield.md.
Where to go next
- The mechanism, end to end: using packs — the practical guide — discovery, activation, honest-empty semantics, the full CLI loop.
- The pack’s normative home: packs — domain-pack
mechanism, and the pack definition itself —
packs/numeric-predicates.mdis the source of truth for the vocabulary this page used. - The capability spec behind the pack: numeric-predicates-pack.
- The same journey without packs: worked example — reservation/order, and with the sibling pack: the data.lineage pack.
- Rule semantics offline:
spk explain packsandspk explain lint-rules.
Worked example — the empirical.registry pack, failures included
The generic worked example builds a spec with the
core format alone, and the data.lineage and
numeric.predicates pages run the same journey
for the first domain packs. This page does it for
empirical.registry
— the pack that adds a typed ## StatTests registry, the
empirical.statistic fiber property kind, a tested_by reference
field, and a per-kind case-label floor enumerating alpha: and
window: on statistic properties — without a format revision and
without growing any base closed set. It is also another Floors
consumer: the floor here is driven by statistical practice (significance
level plus observation window), not algebra — unlike the sibling packs’
floors, whose case labels are shape-flavored (provenance, bound/against,
added/removed). You will see
a small spec use the pack’s vocabulary end to end, the pack-activation
advisory that proves the pack actually engaged, genuinely failing
variants with their real labeled findings, and — just as important —
the failures the linter does not catch yet, shown honestly.
How packs are enabled and what a pack adds mechanically is the job of
using packs — the practical guide; read that for the
mechanism, this page for the hands-on loop. Every lint and graph block
below is real output from specodelic 0.10.0 on the file as it stood at
that moment. On a TTY spk prints the human-readable text shown here;
piped, it emits a JSON envelope instead.
The story
A growth team runs A/B experiments: each experiment measures
click-through rate and conversion, and when one concludes, the team
records a lift claim — an empirically held statement that variant B
beat variant A, supported by a registered significance test evaluated
at a declared alpha over a declared observation window. The spec’s job
is to record those claims and the tests that back them — one
capability, one file, named claim-lift.md; the format’s naming law
maps - to the namespace dot, so the frontmatter id is claim.lift.
Deliberately out of scope: how each test computes significance and
what the observation window means in wall-clock time. Those are the
alpha and window columns’ job, and they stay opaque prose — the
pack declares the registry, statistical methodology stays authoritative
outside the format. Lint behavior is byte-identical no matter what the
columns carry.
The setup
Packs are discovered by scanning the workspace, not by any config file:
every kind: profile file in the git repository toplevel of the linted
spec is a candidate pack. So the demo workspace needs two files — the
pack definition (copied from this repo’s packs/ directory) and the
spec under test:
mkdir demo && cd demo && git init
cp /path/to/specodelic/packs/empirical-registry.md .
That is the whole enablement story. The discovery and activation rules (corpus-scan anchors, word-boundary vocabulary matching, the byte-identical guarantee) are in the guide; this page just runs the loop.
Layer 1 — Intent
---
id: claim.lift
kind: intent
statement: "WHEN an experiment concludes THE system SHALL record every lift claim it holds together with the stat test and evidence window that supports it."
---
# Experiment lift claims
A small empirical registry for one growth team: each experiment measures
click-through and conversion, and every stated lift claim is an
empirically held statistic backed by a registered significance test over
a declared observation window.
Lint it as it stands:
spk lint claim-lift.md
lint: 1 file(s) linted, 1 finding(s), 0 advisory warning(s)
claim-lift.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.model_present — a spec with an intent but no Model section is
incomplete by construction, same as in the generic worked example. Note
what is missing from this output: no pack advisory. The file uses none
of the pack’s vocabulary yet, so the byte-identical guarantee holds —
the pack machinery is invisible. That changes with the next section.
Layer 2 — the ## StatTests registry
This is the pack’s namespaced section, declared in the pack’s
## Sections manifest with the fixed row shape
| name | metric | alpha | window |:
## StatTests
| name | metric | alpha | window |
|--------|------------|-------|--------|
| ctr_t | ctr | 0.05 | 14d |
| conv_t | conversion | 0.01 | 30d |
Each column, defined for a cold read:
- name — the row’s id inside this file;
tested_byedges point at it. - metric — free-form prose naming what the test measures (
ctr,conversion). The pack does not constrain its values. - alpha — opaque prose recording the test’s significance level. The format never parses it.
- window — opaque prose recording the observation window the claim holds over. Same story: never interpreted.
The registry is deliberately thin: the pack adds structure (a place tests live, names claims can point at), not statistics.
The pack’s other two vocabulary pieces ride on the ordinary Properties
table. First, the fiber kind — type one property row’s kind cell as
empirical.statistic (an empirically held statistical claim, stated
with a declared stat test, per the pack’s ## Kinds manifest). Base
kind columns accept pack-qualified fiber kinds when the declaring pack
is active; the same cell without the pack is a labeled finding, shown
below. Second, the reference field — tested_by, declared in the
pack’s ## References manifest, resolving to a ## StatTests row of
the same file. It rides as a column on the Properties table, because a
claim is exactly the thing a stat test tests:
| id | kind | derives_from | generator | tested_by | predicate |
|-----------------|------|-----------------------------------|------------------------------|-----------|-----------------------------------------------------------------|
| claims_resolvable | unit | [[claim.lift.tests_registered]] | `experiments_with_registered_tests()` | ctr_t | `every tested_by name resolves to a declared ## StatTests row` |
| ctr_claim | empirical.statistic | [[claim.lift.lift_tested]] | `experiments_with_lift_claims()` | ctr_t | `the CTR lift claim stands at the registered significance — **alpha:** the claim is evaluated at the test's declared alpha; **window:** the claim holds over the declared observation window` |
Read the rows aloud: every tested_by name points at a ## StatTests
row declared above; the ctr_claim row is the empirically held lift
claim, typed with the pack’s fiber kind and paying the pack’s floor —
its predicate enumerates the two pack-required case labels,
alpha: and window:, on top of nothing else (it is not a
law, so the base algebra floor does not bite it).
The pack also declares two checkers — empirical.stat_test_closed,
every tested_by name resolves to a ## StatTests row declared in
the same file, and empirical.statistic_labels, statistic properties
enumerate their alpha: and window: case labels per the floor
declaration — and one floor, in its ## Floors manifest:
empirical.statistic properties owe alpha: and window:
case labels. What “declaring” buys you today is the next section’s
honesty check, not enforcement — keep reading.
Layer 3 — Constraints, Model, Properties
The rest of the file is the ordinary four-layer shape — the pack adds
vocabulary, it does not change the layers. Two invariants state the
registry claims, an effect constraint labels the failure output, an
observes edge ties the observer to the effect, and the model is a
five-state experiment lifecycle whose failure terminal emits. One
Properties row is a law, significance_cases, which enumerates
four case labels: the base format’s mandatory identity: and
associativity: floor (linter.law_cases demands those of every
law) plus the pack floor’s alpha: and window::
## Constraints
| id | kind | expr | traces_to | observes |
|-----------------|-----------|-------------------------------------------------------------------------------------------|----------------|-----------------------------|
| tests_registered| invariant | `every stat test named by a claim is declared in the ## StatTests table of this file` | [[claim.lift]] | |
| lift_tested | invariant | `every empirically held lift claim names exactly one registered test as its tested_by source` | [[claim.lift]] | |
| lift_retracted | effect | `claim.lift.lift_retracted(detail)` | [[claim.lift]] | |
| retraction_surfaced | invariant | `every retracted claim reaches the operator with the offending test and window named` | [[claim.lift]] | [[claim.lift.lift_retracted]] |
## Model
### States
- `ramped`
- `measured`
- `tested`
- `recorded`
- `failed` (emits: `[[claim.lift.lift_retracted]]`)
### Transitions
| id | from | to | guard |
|----------|----------|----------|---------------------------------------------|
| measure | ramped | measured | `[[claim.lift.tests_registered]]` |
| test | measured | tested | `[[claim.lift.lift_tested]]` |
| record | tested | recorded | `window elapsed without retraction` |
| fail | ramped | failed | `¬[[claim.lift.tests_registered]]` |
## Properties
| id | kind | derives_from | generator | tested_by | predicate |
|-----------------|------|-----------------------------------|------------------------------|-----------|-----------------------------------------------------------------|
| claims_resolvable | unit | [[claim.lift.tests_registered]] | `experiments_with_registered_tests()` | ctr_t | `every tested_by name resolves to a declared ## StatTests row` |
| ctr_claim | empirical.statistic | [[claim.lift.lift_tested]] | `experiments_with_lift_claims()` | ctr_t | `the CTR lift claim stands at the registered significance — **alpha:** the claim is evaluated at the test's declared alpha; **window:** the claim holds over the declared observation window` |
| lift_recorded | unit | [[claim.lift.lift_tested]] | `experiments_with_lift_claims()` | conv_t | `every lift claim carries its registered test source` |
| retraction_emitted | unit | [[claim.lift.lift_retracted]] | `experiments_with_failing_tests()` | ctr_t | `the failure carries the lift_retracted label with detail` |
| retraction_named | unit | [[claim.lift.retraction_surfaced]] | `experiments_with_failing_tests()` | conv_t | `the operator message names the offending test and window` |
| significance_cases | law | [[claim.lift.tests_registered]] | `experiments_with_statistic_claims()` | ctr_t | `the lift claim holds in every declared case — **identity:** a claim tested against itself stands; **associativity:** composing tests is order-independent; **alpha:** significance is evaluated at the test's declared alpha; **window:** the claim holds only over the declared observation window` |
Nothing pack-specific in the layers themselves — traces_to, guards,
emits, coverage: all core-format machinery, taught properly in the
generic worked example.
First full lint — a dangling reference, then the fix
The author’s first full draft carries one typo: the lift_tested
constraint traces to [[claim.lifst]] — letters transposed, the same
classic as the reservation example. Lint the file:
spk lint claim-lift.md
lint: 1 file(s) linted, 1 finding(s), 1 advisory warning(s)
claim-lift.md [linter.total_refs] dangling reference `[[claim.lifst]]` from constraints.traces_to — target not defined in any spec file
advisory claim.lift [linter.observability] pack `empirical.registry` activated for `claim.lift` (vocabulary match: empirical.statistic, StatTests, tested_by) — empty checked-set (no violations to report)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
Exit 1 — a finding, never a silent pass. Two things to read here:
- The finding and the advisory ride different channels. The pack activated (its vocabulary is all over the file — note the match list names every token that triggered it: the fiber kind, the section name, the reference field), but activation lives on the warnings channel and never gates. The dangling reference lives on the findings channel and does.
empty checked-set (no violations to report)— the pack’s declared checkers ran and honestly report an empty violation set. More on what that does and does not mean below.
Fix the typo — [[claim.lifst]] → [[claim.lift]] — and lint again:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory claim.lift [linter.observability] pack `empirical.registry` activated for `claim.lift` (vocabulary match: empirical.statistic, StatTests, tested_by) — empty checked-set (no violations to report)
→ Run: specodelic graph
Zero findings, exit 0 — and now the advisory is the whole output.
Read it as the meter it is: an exit-0 lint alone proves nothing (a spec
using none of the pack vocabulary also exits 0, silently); the
activation line naming empirical.registry is the observable that
the pack was discovered, matched, and engaged. The pipeline’s next
stage runs pack-aware too:
spk graph claim-lift.md
graph: 1 file(s), 20 node(s), 23 edge(s), 0 dangling, 0 typing violation(s), 0 supersedes cycle(s), 0 external boundary(ies)
→ Run: specodelic lint
What a missing pack looks like
The demo depends on the pack file being discovered. Copy the finished
spec into a directory with no kind: profile file in it — the
machinery goes inert:
lint: 1 file(s) linted, 2 finding(s), 0 advisory warning(s)
claim.lift [linter.property_kind_closed] property `ctr_claim` has kind `empirical.statistic` — outside the closed set {unit, law} (kinds.md property_row_shape)
claim.lift [linter.orphan_vocabulary] orphan vocabulary: pack-qualified token(s) `empirical.statistic` used by `claim.lift`, but no `kind: profile` pack in namespace `empirical` is discovered — candidate pack `empirical.*` (prefix-derived: only the namespace is known); remediations: add/enable a `kind: profile` pack file declaring the vocabulary, or fix the vocabulary (un-qualify or retype the token)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
Different from the sibling packs’ story, and worth pausing on: this
spec does not lint silently without its pack, because it uses the
pack’s fiber kind in a base-table kind column. A fiber kind’s
typeability is the pack — with no active declaring pack, the
empirical.statistic cell is outside the base closed set
(linter.property_kind_closed) and the token itself is orphan
vocabulary (linter.orphan_vocabulary, naming the candidate
empirical.* and both remediations: enable a pack declaring the
vocabulary, or fix the token). The byte-identical guarantee still holds
exactly as specified — it covers files using none of the pack
vocabulary. Retype the one fiber-kind cell back to unit in that same
pack-less directory and the file goes quiet again:
lint: 1 file(s) linted, 0 finding(s), 0 advisory warning(s)
→ Run: specodelic graph
No advisory, no findings — a spec with no pack vocabulary is markdown
the pack machinery never interprets, by design: a pack can never gate
a file that did not opt in. Note what quiet means here: with the pack
gone, the tested_by column, the ## StatTests section, and the
alpha:/window: case labels are all inert decoration —
linted as ordinary markdown, interpreted by nothing. A pack-less lint
never errors on pack vocabulary alone; it only errors once a
pack-qualified kind lands in a closed-set column (the fiber kind
cell, as above). The reliable diagnostic either way: a spec
you meant to be pack-enabled but lint with no activation advisory is
a spec whose pack was not discovered.
What the linter does not catch (yet)
Honesty check — the failures this page has not shown are the two you might expect first, because the pack declares a checker for each.
A tested_by edge that dangles. Change ctr_t to ghost_t in the
tested_by column and lint the pack-enabled workspace:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory claim.lift [linter.observability] pack `empirical.registry` activated for `claim.lift` (vocabulary match: empirical.statistic, StatTests, tested_by) — empty checked-set (no violations to report)
→ Run: specodelic graph
Byte-for-byte the clean output — exit 0, activation advisory, nothing
else. That is not the linter missing the dangle by accident:
empirical.stat_test_closed ships as a declaration.
Floor labels dropped. Strip the alpha: and window: case
labels out of ctr_claim’s predicate and lint:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory claim.lift [linter.observability] pack `empirical.registry` activated for `claim.lift` (vocabulary match: empirical.statistic, StatTests, tested_by) — empty checked-set (no violations to report)
→ Run: specodelic graph
Identical again. empirical.statistic_labels is the same kind of
declaration.
In v1, pack checkers are honest-empty — activation and the empty
checked-set are reported truthfully, violation evaluation is not
implemented yet (the guide documents this, and
verification boundaries places it on
the guarantee ladder). The format’s answer to the gap is the one this
spec already embodies: each unenforced claim lives where the evidence
plan lives — the dangling-edge claim in the tests_registered
invariant and its claims_resolvable property, the floor in the
ctr_claim and significance_cases predicates’ enumerated cases —
each waiting on a real executable predicate. When the checkers gain
evaluation, the declarations are already in place; the spec does not
change.
The finished spec
Every block above assembles into this one file, claim-lift.md —
exactly as it lints at 0 findings, 1 activation advisory:
---
id: claim.lift
kind: intent
statement: "WHEN an experiment concludes THE system SHALL record every lift claim it holds together with the stat test and evidence window that supports it."
---
# Experiment lift claims
A small empirical registry for one growth team: each experiment measures
click-through and conversion, and every stated lift claim is an
empirically held statistic backed by a registered significance test over
a declared observation window.
## StatTests
| name | metric | alpha | window |
|--------|------------|-------|--------|
| ctr_t | ctr | 0.05 | 14d |
| conv_t | conversion | 0.01 | 30d |
## Constraints
| id | kind | expr | traces_to | observes |
|-----------------|-----------|-------------------------------------------------------------------------------------------|----------------|-----------------------------|
| tests_registered| invariant | `every stat test named by a claim is declared in the ## StatTests table of this file` | [[claim.lift]] | |
| lift_tested | invariant | `every empirically held lift claim names exactly one registered test as its tested_by source` | [[claim.lift]] | |
| lift_retracted | effect | `claim.lift.lift_retracted(detail)` | [[claim.lift]] | |
| retraction_surfaced | invariant | `every retracted claim reaches the operator with the offending test and window named` | [[claim.lift]] | [[claim.lift.lift_retracted]] |
## Model
### States
- `ramped`
- `measured`
- `tested`
- `recorded`
- `failed` (emits: `[[claim.lift.lift_retracted]]`)
### Transitions
| id | from | to | guard |
|----------|----------|----------|---------------------------------------------|
| measure | ramped | measured | `[[claim.lift.tests_registered]]` |
| test | measured | tested | `[[claim.lift.lift_tested]]` |
| record | tested | recorded | `window elapsed without retraction` |
| fail | ramped | failed | `¬[[claim.lift.tests_registered]]` |
## Properties
| id | kind | derives_from | generator | tested_by | predicate |
|-----------------|------|-----------------------------------|------------------------------|-----------|-----------------------------------------------------------------|
| claims_resolvable | unit | [[claim.lift.tests_registered]] | `experiments_with_registered_tests()` | ctr_t | `every tested_by name resolves to a declared ## StatTests row` |
| ctr_claim | empirical.statistic | [[claim.lift.lift_tested]] | `experiments_with_lift_claims()` | ctr_t | `the CTR lift claim stands at the registered significance — **alpha:** the claim is evaluated at the test's declared alpha; **window:** the claim holds over the declared observation window` |
| lift_recorded | unit | [[claim.lift.lift_tested]] | `experiments_with_lift_claims()` | conv_t | `every lift claim carries its registered test source` |
| retraction_emitted | unit | [[claim.lift.lift_retracted]] | `experiments_with_failing_tests()` | ctr_t | `the failure carries the lift_retracted label with detail` |
| retraction_named | unit | [[claim.lift.retraction_surfaced]] | `experiments_with_failing_tests()` | conv_t | `the operator message names the offending test and window` |
| significance_cases | law | [[claim.lift.tests_registered]] | `experiments_with_statistic_claims()` | ctr_t | `the lift claim holds in every declared case — **identity:** a claim tested against itself stands; **associativity:** composing tests is order-independent; **alpha:** significance is evaluated at the test's declared alpha; **window:** the claim holds only over the declared observation window` |
To reproduce every block on this page: copy
packs/empirical-registry.md from the repo into a fresh git workspace,
save the file above as claim-lift.md, and run
spk lint claim-lift.md.
Where to go next
- The mechanism, end to end: using packs — the practical guide — discovery, activation, honest-empty semantics, the full CLI loop.
- The pack’s normative home: packs — domain-pack
mechanism, and the pack definition itself —
packs/empirical-registry.mdis the source of truth for the vocabulary this page used. - The capability spec behind the pack: empirical-registry-pack.
- The same journey without packs: worked example — reservation/order, and with the sibling packs: data.lineage and numeric.predicates.
- Rule semantics offline:
spk explain packsandspk explain lint-rules.
Worked example — the bioimage.data pack, failures included
The generic worked example builds a spec with the
core format alone, and the data.lineage,
numeric.predicates, and
empirical.registry pages run the same journey
pack by pack. This page does it for
bioimage.data
— the pack mechanism’s pilot: the thin instance that stress-tests what
the packs it consumes could not exercise alone, and the mechanism’s
first cross-pack ## Requires consumer. It adds a typed ## Axes
section (image axes with OME-NGFF-style semantics), the
bioimage.transform fiber property kind, a same_shape_as reference
field resolving to ## Data rows, a named data-shaped predicate
grammar (bioimage.dtype_is, bioimage.shape_eq,
bioimage.units_convertible), and a kind-dependent case-label floor
enumerating preserves: and dtype: — all without a format
revision, without growing any base closed set, and while OME/NGFF and
BioImage.IO stay authoritative external standards. You will see a small
spec use the pack’s vocabulary end to end, the pack-activation advisory
that proves the pack actually engaged, genuinely failing variants with
their real labeled findings, and — just as important — the failures the
linter does not catch yet, shown honestly.
How packs are enabled and what a pack adds mechanically is the job of
using packs — the practical guide; read that for
the mechanism, this page for the hands-on loop. Every lint and graph
block below is real output from specodelic 0.10.0 on the file as it
stood at that moment. On a TTY spk prints the human-readable text
shown here; piped, it emits a JSON envelope instead.
The story
An imaging facility runs a light-sheet pipeline: raw microscopy frames
land from the microscope, a flat-field correction is applied, and the
corrected volume is written as an OME/NGFF zarr store. The spec’s job
is to record that conversion — the image axes every dataset lives on,
which datasets share a voxel geometry, and the law each transform step
asserts about how it reshapes image data. One capability, one file,
named plate7-ngff.md; the format’s naming law maps - to the
namespace dot, so the frontmatter id is plate7.ngff.
Deliberately out of scope: how OME/NGFF structures the zarr store and
what BioImage.IO says about the acquisition metadata. Those are the
opaque columns’ job — scale, unit, and binding are prose the
format never parses, keeping OME/NGFF and BioImage.IO authoritative
outside the format. The pack bridges them; it never absorbs them. Lint
behavior is byte-identical no matter what those columns carry.
The setup
Packs are discovered by scanning the workspace, not by any config file:
every kind: profile file in the git repository toplevel of the linted
spec is a candidate pack. bioimage.data is a consumer pack — its
## Requires table pins the base format revision plus the packs whose
vocabulary it reuses (data.lineage for the ## Data section,
numeric.predicates for the opaque unit domain its
bioimage.units_convertible checker bridges, empirical.registry for
empirically held transform claims). So the demo workspace copies the
pack under test and the three it consumes:
mkdir demo && cd demo && git init
cp /path/to/specodelic/packs/{bioimage-data,data-lineage,numeric-predicates,empirical-registry}.md .
That is the whole enablement story. The discovery and activation rules (corpus-scan anchors, word-boundary vocabulary matching, the byte-identical guarantee) are in the guide; this page just runs the loop.
Layer 1 — Intent
---
id: plate7.ngff
kind: intent
statement: "WHEN the imaging pipeline processes a light-sheet plate THE system SHALL record the image axes of every dataset it writes together with the transform laws each pipeline step asserts about how it reshapes image data."
---
# Plate 7 NGFF conversion
A small bioimage record for one light-sheet run: the pipeline acquires
raw microscopy frames, applies flat-field correction, writes an
OME/NGFF zarr store, and every transform step states the law it obeys —
what it preserves and in which dtype.
Lint it as it stands:
spk lint plate7-ngff.md
lint: 1 file(s) linted, 1 finding(s), 0 advisory warning(s)
plate7-ngff.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.model_present — a spec with an intent but no Model section is
incomplete by construction, same as in the generic worked example. Note
what is missing from this output: no pack advisory. The file uses none
of the pack vocabulary yet, so the byte-identical guarantee holds —
the pack machinery is invisible. That changes with the next section.
Layer 2 — the ## Data and ## Axes registries
Two registries carry the pack’s data claims. The first is
data.lineage’s ## Data section (the pack consumes it rather than
redeclaring it — that is what the ## Requires table records), with
its fixed row shape | name | kind | dtype | units | binding |:
## Data
| name | kind | dtype | units | binding |
|-------------|------------------|---------|-----------|----------------------|
| raw_frames | data.dataset | uint16 | intensity | file://scope/raw-p7 |
| ome_zarr | data.artifact | uint16 | intensity | zarr://ngff/plate7 |
| flat_field | data.artifact | float32 | gain | file://calib/p7 |
| lightsheet | data.environment | manifest| run | equipment://lsm9 |
Each column, defined for a cold read:
- name — the row’s id inside this file; the pack’s
same_shape_asedges point at it. - kind —
data.lineage’s closed set:data.dataset(a bounded collection of measurements),data.artifact(a derived object a pipeline produces or consumes),data.environment(the execution or measurement context). These are fiber kinds — pack-declared vocabulary, never a growth of the base closed sets. - dtype — free-form descriptor of the data’s element type
(
uint16,float32). The pack does not constrain its values — though thedtype:floor case below makes the law state it. - units / binding — opaque prose for the unit of measure and
the pointer to the external system of record (
zarr://…,file://…). The linter never interprets either: OME/NGFF and BioImage.IO stay authoritative.
The second registry is the pack’s own namespaced section, declared in
its ## Sections manifest with the fixed row shape
| name | axis | scale | unit |:
## Axes
| name | axis | scale | unit |
|------|------|-------|---------|
| x | x | 0.65 | µm |
| y | y | 0.65 | µm |
| z | z | 2.00 | µm |
| c | c | 1 | channel |
| t | t | 300 | s |
- name — the axis row’s id inside this file.
- axis — the dimensional label with OME-NGFF-style semantics
(
x,y,z,cfor channel,tfor time). Free prose; the pack does not constrain it. - scale / unit — opaque prose recording voxel size and its unit
(
0.65µm). The format never parses these columns — that is exactly what keeps OME/NGFF external.
Shape claims are the pack’s reference field, declared in its
## References manifest — same_shape_as, resolving to a ## Data
row of the same file. It rides as a column on a second table under
## Data:
| row | same_shape_as |
|-----------|---------------|
| ome_zarr | raw_frames |
Read the edge aloud: the written ome_zarr store keeps the voxel
geometry of the acquired raw_frames — same x/y/z/c/t extents, per the
## Axes rows above.
The pack declares four named checkers in its ## Checkers manifest —
bioimage.dtype_is, bioimage.shape_eq, and
bioimage.units_convertible (the data-shaped predicate grammar,
resolving against declared ## Data rows — the last one bridging
numeric.predicates’ opaque unit domain), and bioimage.same_shape_closed
(every same_shape_as name resolves to a ## Data row declared in
the same file). It also declares a kind-dependent case-label floor in
its ## Floors manifest: bioimage.transform properties must carry
preserves: and dtype: case labels. The Properties layer
below pays that floor in full.
Layer 3 — Constraints, Model, Properties
The rest of the file is the ordinary four-layer shape — the pack adds
vocabulary, it does not change the layers. Two invariants state the
record’s claims, an effect constraint labels the failure output, an
observes edge ties the observer to the effect, and the model is a
five-state run lifecycle whose failure terminal emits. The Properties
table includes one row with the pack’s fiber kind, norm_cases —
kind: bioimage.transform, typeable in the base-table kind column
because the pack is active — which enumerates the pack floor’s
preserves: and dtype: case labels:
## Constraints
| id | kind | expr | traces_to | observes |
|----------------|-----------|-------------------------------------------------------------------------------------------|------------------|---------------------------|
| axes_declared | invariant | `every dataset's voxel geometry is declared in the ## Axes table of this file` | [[plate7.ngff]] | |
| shapes_paired | invariant | `every ome_zarr names its raw dataset as its same_shape_as source` | [[plate7.ngff]] | |
| norm_fail | effect | `plate7.ngff.norm_fail(detail)` | [[plate7.ngff]] | |
| fail_surfaced | invariant | `every normalization failure reaches the operator with the offending dataset named` | [[plate7.ngff]] | [[plate7.ngff.norm_fail]] |
## Model
### States
- `acquired`
- `corrected`
- `written`
- `published`
- `failed` (emits: `[[plate7.ngff.norm_fail]]`)
### Transitions
| id | from | to | guard |
|----------|----------|-----------|-----------------------------------------------------------|
| correct | acquired | corrected | `[[plate7.ngff.axes_declared]]` |
| write | corrected| written | `[[plate7.ngff.shapes_paired]]` |
| publish | written | published | `ome_zarr binding written` |
| fail | acquired | failed | `¬[[plate7.ngff.axes_declared]]` |
## Properties
| id | kind | derives_from | generator | predicate |
|------------------|---------------------|----------------------------------|----------------------------------|-----------------------------------------------------------------|
| axes_resolvable | unit | [[plate7.ngff.axes_declared]] | `plates_with_axis_rows()` | `every dataset's axes resolve to declared ## Axes rows` |
| shapes_resolvable| unit | [[plate7.ngff.shapes_paired]] | `plates_with_paired_shapes()` | `every same_shape_as name resolves to a declared ## Data row` |
| fail_emitted | unit | [[plate7.ngff.norm_fail]] | `plates_with_norm_failures()` | `the failure carries the norm_fail label with detail` |
| fail_named | unit | [[plate7.ngff.fail_surfaced]] | `plates_with_norm_failures()` | `the operator message names the offending dataset` |
| norm_cases | bioimage.transform | [[plate7.ngff.axes_declared]] | `plates_with_flatfield_steps()` | `the flat-field step transforms image data in every declared case — **preserves:** the corrected volume keeps the acquired voxel geometry; **dtype:** the corrected volume stays in the acquired dtype, uint16` |
Nothing pack-specific in the layers themselves — traces_to, guards,
emits, coverage: all core-format machinery, taught properly in the
generic worked example. Note what the norm_cases
row does not carry: the base law floor’s identity: and
associativity: cases are not demanded here, because the floor is
kind-dependent — the base law floor stays untouched, and a kind with no
declared floor inherits none.
First full lint — a dangling reference, then the fix
The author’s first full draft carries one typo: the axes_declared
constraint traces to [[plate7.nff]] — plural, misremembered, the same
classic as the reservation example. Lint the file:
spk lint plate7-ngff.md
lint: 1 file(s) linted, 1 finding(s), 2 advisory warning(s)
plate7-ngff.md [linter.total_refs] dangling reference `[[plate7.nff]]` from constraints.traces_to — target not defined in any spec file
advisory plate7.ngff [linter.observability] pack `bioimage.data` activated for `plate7.ngff` (vocabulary match: bioimage.transform, Axes, same_shape_as) — empty checked-set (no violations to report)
advisory plate7.ngff [linter.observability] pack `data.lineage` activated for `plate7.ngff` (vocabulary match: data.dataset, data.artifact, data.environment, Data) — empty checked-set (no violations to report)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
Exit 1 — a finding, never a silent pass. Three things to read here:
- The finding and the advisories ride different channels. Both packs activated (their vocabulary is all over the file — the match list names every token that triggered each), but activation lives on the warnings channel and never gates. The dangling reference lives on the findings channel and does.
- Two advisories, two packs.
bioimage.dataactivated on its own vocabulary (bioimage.transform,Axes,same_shape_as);data.lineageactivated on the## Datarows the consumer pack reuses. The two packs the file uses no vocabulary of —numeric.predicates,empirical.registry— stay silent: activation is per-vocabulary-use, not per-## Requiresrow. empty checked-set (no violations to report)— the packs’ declared checkers ran and honestly report an empty violation set. More on what that does and does not mean below.
Fix the typo — [[plate7.nff]] → [[plate7.ngff]] — and lint again:
lint: 1 file(s) linted, 0 finding(s), 2 advisory warning(s)
advisory plate7.ngff [linter.observability] pack `bioimage.data` activated for `plate7.ngff` (vocabulary match: bioimage.transform, Axes, same_shape_as) — empty checked-set (no violations to report)
advisory plate7.ngff [linter.observability] pack `data.lineage` activated for `plate7.ngff` (vocabulary match: data.dataset, data.artifact, data.environment, Data) — empty checked-set (no violations to report)
→ Run: specodelic graph
Zero findings, exit 0 — and now the advisories are the whole output.
Read them as the meter they are: an exit-0 lint alone proves nothing (a
spec using none of the pack vocabulary also exits 0, silently); the
activation line naming bioimage.data is the observable that the
pack was discovered, matched, and engaged. The pipeline’s next stage
runs pack-aware too:
spk graph plate7-ngff.md
graph: 1 file(s), 19 node(s), 22 edge(s), 0 dangling, 0 typing violation(s), 0 supersedes cycle(s), 0 external boundary(ies)
→ Run: specodelic lint
What a missing pack looks like
The demo depends on the pack file being discovered. Copy the finished
spec into a directory with no kind: profile file in it — the
machinery goes inert, and now that inertness bites, because
norm_cases uses the pack’s fiber kind in a base-table kind column:
lint: 1 file(s) linted, 2 finding(s), 0 advisory warning(s)
plate7.ngff [linter.property_kind_closed] property `norm_cases` has kind `bioimage.transform` — outside the closed set {unit, law} (kinds.md property_row_shape)
plate7.ngff [linter.orphan_vocabulary] orphan vocabulary: pack-qualified token(s) `bioimage.transform` used by `plate7.ngff`, but no `kind: profile` pack in namespace `bioimage` is discovered — candidate pack `bioimage.*` (prefix-derived: only the namespace is known); remediations: add/enable a `kind: profile` pack file declaring the vocabulary, or fix the vocabulary (un-qualify or retype the token)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.orphan_vocabulary — the finding always names the candidate
(namespace-derived, bioimage.* here) and states both
remediations: enable a pack declaring the vocabulary, or fix the token.
linter.property_kind_closed fires alongside it: with no pack active,
the walkers accept only the base kinds in the kind column (base ∪
active-pack-fiber, never narrower). Copy the pack files back into the
workspace and both findings clear — the same file, byte-identical,
lints clean. The reliable diagnostic is the one this section started
with: a spec you meant to be pack-enabled but lint with no activation
advisory is a spec whose pack was not discovered.
What the linter does not catch (yet)
Honesty check — the failures this page has not shown are the two you might expect first, because the pack declares a checker and a floor for each.
A same_shape_as edge that dangles. Change raw_frames to
ghost_row in the same_shape_as column and lint the pack-enabled
workspace:
lint: 1 file(s) linted, 0 finding(s), 2 advisory warning(s)
advisory plate7.ngff [linter.observability] pack `bioimage.data` activated for `plate7.ngff` (vocabulary match: bioimage.transform, Axes, same_shape_as) — empty checked-set (no violations to report)
advisory plate7.ngff [linter.observability] pack `data.lineage` activated for `plate7.ngff` (vocabulary match: data.dataset, data.artifact, data.environment, Data) — empty checked-set (no violations to report)
→ Run: specodelic graph
Byte-for-byte the clean output — exit 0, activation advisories, nothing
else. That is not the linter missing the dangle by accident:
bioimage.same_shape_closed ships as a declaration.
Floor labels dropped. Strip the preserves: and dtype:
case labels out of norm_cases’s predicate and lint:
lint: 1 file(s) linted, 0 finding(s), 2 advisory warning(s)
advisory plate7.ngff [linter.observability] pack `bioimage.data` activated for `plate7.ngff` (vocabulary match: bioimage.transform, Axes, same_shape_as) — empty checked-set (no violations to report)
advisory plate7.ngff [linter.observability] pack `data.lineage` activated for `plate7.ngff` (vocabulary match: data.dataset, data.artifact, data.environment, Data) — empty checked-set (no violations to report)
→ Run: specodelic graph
Identical again. The pack’s floor is the same kind of declaration: the
manifest requires the labels, but in v1 pack checkers are
honest-empty — activation and the empty checked-set are reported
truthfully, violation evaluation is not implemented yet (the
guide documents this, and
verification boundaries places it on
the guarantee ladder). The format’s answer to the gap is the one this
spec already embodies: each unenforced claim lives where the evidence
plan lives — the dangling-edge claim in the shapes_paired invariant
and its shapes_resolvable property, the floor in the norm_cases
predicate’s enumerated cases — each waiting on a real executable
predicate. When the checkers gain evaluation, the declarations are
already in place; the spec does not change.
The finished spec
Every block above assembles into this one file, plate7-ngff.md —
exactly as it lints at 0 findings, 2 activation advisories:
---
id: plate7.ngff
kind: intent
statement: "WHEN the imaging pipeline processes a light-sheet plate THE system SHALL record the image axes of every dataset it writes together with the transform laws each pipeline step asserts about how it reshapes image data."
---
# Plate 7 NGFF conversion
A small bioimage record for one light-sheet run: the pipeline acquires
raw microscopy frames, applies flat-field correction, writes an
OME/NGFF zarr store, and every transform step states the law it obeys —
what it preserves and in which dtype.
## Data
| name | kind | dtype | units | binding |
|-------------|------------------|---------|-----------|----------------------|
| raw_frames | data.dataset | uint16 | intensity | file://scope/raw-p7 |
| ome_zarr | data.artifact | uint16 | intensity | zarr://ngff/plate7 |
| flat_field | data.artifact | float32 | gain | file://calib/p7 |
| lightsheet | data.environment | manifest| run | equipment://lsm9 |
| row | same_shape_as |
|-----------|---------------|
| ome_zarr | raw_frames |
## Axes
| name | axis | scale | unit |
|------|------|-------|---------|
| x | x | 0.65 | µm |
| y | y | 0.65 | µm |
| z | z | 2.00 | µm |
| c | c | 1 | channel |
| t | t | 300 | s |
## Constraints
| id | kind | expr | traces_to | observes |
|----------------|-----------|-------------------------------------------------------------------------------------------|------------------|---------------------------|
| axes_declared | invariant | `every dataset's voxel geometry is declared in the ## Axes table of this file` | [[plate7.ngff]] | |
| shapes_paired | invariant | `every ome_zarr names its raw dataset as its same_shape_as source` | [[plate7.ngff]] | |
| norm_fail | effect | `plate7.ngff.norm_fail(detail)` | [[plate7.ngff]] | |
| fail_surfaced | invariant | `every normalization failure reaches the operator with the offending dataset named` | [[plate7.ngff]] | [[plate7.ngff.norm_fail]] |
## Model
### States
- `acquired`
- `corrected`
- `written`
- `published`
- `failed` (emits: `[[plate7.ngff.norm_fail]]`)
### Transitions
| id | from | to | guard |
|----------|----------|-----------|-----------------------------------------------------------|
| correct | acquired | corrected | `[[plate7.ngff.axes_declared]]` |
| write | corrected| written | `[[plate7.ngff.shapes_paired]]` |
| publish | written | published | `ome_zarr binding written` |
| fail | acquired | failed | `¬[[plate7.ngff.axes_declared]]` |
## Properties
| id | kind | derives_from | generator | predicate |
|------------------|---------------------|----------------------------------|----------------------------------|-----------------------------------------------------------------|
| axes_resolvable | unit | [[plate7.ngff.axes_declared]] | `plates_with_axis_rows()` | `every dataset's axes resolve to declared ## Axes rows` |
| shapes_resolvable| unit | [[plate7.ngff.shapes_paired]] | `plates_with_paired_shapes()` | `every same_shape_as name resolves to a declared ## Data row` |
| fail_emitted | unit | [[plate7.ngff.norm_fail]] | `plates_with_norm_failures()` | `the failure carries the norm_fail label with detail` |
| fail_named | unit | [[plate7.ngff.fail_surfaced]] | `plates_with_norm_failures()` | `the operator message names the offending dataset` |
| norm_cases | bioimage.transform | [[plate7.ngff.axes_declared]] | `plates_with_flatfield_steps()` | `the flat-field step transforms image data in every declared case — **preserves:** the corrected volume keeps the acquired voxel geometry; **dtype:** the corrected volume stays in the acquired dtype, uint16` |
To reproduce every block on this page: copy
packs/bioimage-data.md and the three pack files it requires
(packs/data-lineage.md, packs/numeric-predicates.md,
packs/empirical-registry.md) from the repo into a fresh git
workspace, save the file above as plate7-ngff.md, and run
spk lint plate7-ngff.md.
Where to go next
- The mechanism, end to end: using packs — the practical guide — discovery, activation, honest-empty semantics, the full CLI loop.
- The pack’s normative home: packs — domain-pack
mechanism, and the pack definition itself —
packs/bioimage-data.mdis the source of truth for the vocabulary this page used. - The capability spec behind the pack: bioimage-data-pack.
- The same journey for the packs this one consumes: the data.lineage pack, the numeric.predicates pack, and the empirical.registry pack — or without packs at all: worked example — reservation/order.
- Rule semantics offline:
spk explain packsandspk explain lint-rules.
Worked example — the quant.finance pack, failures included
The generic worked example builds a spec with the
core format alone, and the data.lineage,
numeric.predicates,
empirical.registry, and
bioimage.data pages run the same journey pack by
pack. This page does it for
quant.finance
— the mechanism’s restraint instance: most of what a quant desk would
ask for already ships in the packs it requires (tolerance semantics in
numeric.predicates, statistics in empirical.registry, backtest
fixtures and datasets in data.lineage), so this pack declares only the
vocabulary no other pack carries — a typed ## Limits section for
bounded risk measures and pricing invariants, the quant.risk and
quant.pricing kinds, and the capped_by outbound-leaf edge. Unit
systems stay outside: ISO4217 currency codes and market-data conventions
remain authoritative external standards, bridged through the opaque
unit column, never absorbed into any closed set. You will see a small
spec use the pack’s vocabulary end to end, the pack-activation advisory
that proves the pack actually engaged, genuinely failing variants with
their real labeled findings, and — just as important — the failures the
linter does not catch yet, shown honestly.
How packs are enabled and what a pack adds mechanically is the job of
using packs — the practical guide; read that for
the mechanism, this page for the hands-on loop. Every lint and graph
block below is real output from specodelic 0.10.0 on the file as it
stood at that moment. On a TTY spk prints the human-readable text
shown here; piped, it emits a JSON envelope instead.
The story
A prop trading desk keeps a risk register: an overnight VaR limit, the
expected-shortfall limit that reconciles against it, and a NAV
consistency bound. The spec’s job is to record that register — every
limit with its unit and bound, and the cap edge between the two risk
limits. One capability, one file, named desk-risks.md; the format’s
naming law maps - to the namespace dot, so the frontmatter id is
desk.risks.
Deliberately out of scope: which external standard each unit comes
from and where the market data pricing the bounds lives. USD is an
ISO4217 code, bps is a market convention — the pack treats both as
opaque strings in the unit column and never interprets them.
The setup
Packs are discovered by scanning the workspace, not by any config file:
every kind: profile file in the git repository toplevel of the linted
spec is a candidate pack. So the demo workspace needs two files — the
pack definition (copied from this repo’s packs/ directory) and the
spec under test:
mkdir demo && cd demo && git init
cp /path/to/specodelic/packs/quant-finance.md .
That is the whole enablement story. The pack’s ## Requires table also
declares numeric.predicates and data.lineage — but those are
complementary vocabulary, not prerequisites for this example: backtest
fixtures and datasets are ## Data rows (data.lineage), tolerance
semantics live in numeric.predicates, and the spec below uses neither.
Copying just the pack above is enough; the discovery and activation
rules (corpus-scan anchors, word-boundary vocabulary matching, the
byte-identical guarantee) are in the guide.
Layer 1 — Intent
---
id: desk.risks
kind: intent
statement: "WHEN the desk books trades against its risk limits THE system SHALL record every bounded risk measure and pricing invariant together with the cap edges between them."
---
# Desk risk register
A small risk register for one prop trading desk: overnight VaR, its
expected-shortfall companion, and the NAV consistency bound — each
stated with its unit and bound, and the cap edges between them.
Lint it as it stands:
spk lint desk-risks.md
lint: 1 file(s) linted, 1 finding(s), 0 advisory warning(s)
desk-risks.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.model_present — a spec with an intent but no Model section is
incomplete by construction, same as in the generic worked example. Note
what is missing from this output: no pack advisory. The file uses none
of the pack’s vocabulary yet, so the byte-identical guarantee holds —
the pack machinery is invisible. That changes with the next section.
Layer 2 — the ## Limits section
This is the pack’s namespaced section, declared in the pack’s
## Sections manifest with the fixed row shape
| name | kind | unit | bound |:
## Limits
| name | kind | unit | bound |
|-----------|---------------|------|---------|
| var_1d | quant.risk | USD | 250_000 |
| es_1d | quant.risk | USD | 400_000 |
| nav_drift | quant.pricing | bps | 25 |
Each column, defined for a cold read:
- name — the row’s id inside this file; cap edges point at it.
- kind — a short label closed per pack to
riskandpricing(a risk measure vs. a valuation invariant). These are the row-level form of the pack’squant.risk/quant.pricingkinds — and note the boundary: the mechanism never checks row-kind cells. The closure is the pack’s own prose law; what the linter enforces is the pack-qualified kind tokens in the Properties layer, below. - unit — opaque.
USDis an ISO4217 code,bpsa market convention; the pack declares no syntax for either and the linter never interprets the column. The external standards stay authoritative — the pack bridges them, it does not absorb them. - bound — the limit’s numeric value. Also opaque prose; the mechanism never parses it.
Cap edges are the pack’s reference field, declared in its
## References manifest — capped_by, resolving to a ## Limits row
of the same file. It rides as a column on a second table under the same
section:
| row | capped_by |
|--------|-----------|
| es_1d | var_1d |
Read the edge aloud: es_1d is capped by var_1d — the
expected-shortfall limit names the VaR limit it reconciles against.
The pack also declares two checkers in its ## Checkers manifest —
quant.limit_closed (every capped_by name resolves to a ## Limits
row declared in the same file) and quant.risk_labels (quant.risk
laws enumerate their horizon and confidence case labels per the pack’s
floor). Both ship as declarations: the mechanism names them, it does
not yet execute them — keep reading, the page shows exactly what that
means. The same manifest style declares the pack’s floor: law-kind
properties about quant.risk rows must carry horizon: and
confidence: case labels. The base law floor is untouched — the
mandatory identity:/associativity: labels ride base law rows (see
the numeric.predicates page); this floor is
kind-dependent and rides the fiber kind, as the Properties layer
below pays it.
Lint the file with the section in place, Model still missing:
lint: 1 file(s) linted, 1 finding(s), 1 advisory warning(s)
desk-risks.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
advisory desk.risks [linter.observability] pack `quant.finance` activated for `desk.risks` (vocabulary match: quant.risk, quant.pricing, Limits, capped_by) — empty checked-set (no violations to report)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
There is the activation advisory, and the match list names every token that triggered it: both fiber kinds, the section name, and the reference field.
Layer 3 — Constraints, Model, Properties
The rest of the file is the ordinary four-layer shape — the pack adds
vocabulary, it does not change the layers. Two invariants state the
register’s claims, an effect constraint labels the failure output, an
observes edge ties the observer to the effect, and the model is a
four-state day lifecycle whose failure terminal emits:
## Constraints
| id | kind | expr | traces_to | observes |
|-----------------|-----------|----------------------------------------------------------------------------------------|----------------|------------------------------|
| caps_closed | invariant | `every capped_by name resolves to a ## Limits row declared in this file` | [[desk.risks]] | |
| limit_breach | effect | `desk.risks.limit_breach(detail)` | [[desk.risks]] | |
| breach_surfaced | invariant | `every limit breach reaches the risk officer with the offending row and its bound named` | [[desk.risks]] | [[desk.risks.limit_breach]] |
## Model
### States
- `booked`
- `checked`
- `published`
- `failed` (emits: `[[desk.risks.limit_breach]]`)
### Transitions
| id | from | to | guard |
|---------|---------|-----------|------------------------------------|
| check | booked | checked | `[[desk.risks.caps_closed]]` |
| publish | checked | published | `end-of-day run written` |
| fail | booked | failed | `¬[[desk.risks.caps_closed]]` |
## Properties
| id | kind | derives_from | generator | predicate |
|----------------|------------|--------------------------------|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| caps_resolve | unit | [[desk.risks.caps_closed]] | `registers_with_cap_edges()` | `every capped_by name resolves to a declared ## Limits row` |
| breach_emitted | unit | [[desk.risks.limit_breach]] | `registers_with_breaches()` | `the failure carries the desk.risks.limit_breach label with detail` |
| breach_named | unit | [[desk.risks.breach_surfaced]] | `registers_with_breaches()` | `the risk-officer message names the offending ## Limits row and its bound` |
| es_reconciles | quant.risk | [[desk.risks.caps_closed]] | `registers_with_es_limits()` | `the ES limit reconciles against the VaR limit in every declared case — **horizon:** both limits are stated over the same 1-day horizon; **confidence:** both limits assume the same 99% confidence level` |
One pack-specific row: es_reconciles is a law whose kind is the fiber
kind quant.risk — the Properties layer paying the pack’s floor, its
predicate enumerating horizon: and confidence: as named
cases. Everything else — traces_to, guards, emits, coverage — is
core-format machinery, taught properly in the
generic worked example.
First full lint — a dangling reference, then the fix
The author’s first full draft carries one typo: the caps_closed
constraint traces to [[desk.riss]] — misremembered, the same classic
as the reservation example. Lint the file:
spk lint desk-risks.md
lint: 1 file(s) linted, 1 finding(s), 1 advisory warning(s)
desk-risks.md [linter.total_refs] dangling reference `[[desk.riss]]` from constraints.traces_to — target not defined in any spec file
advisory desk.risks [linter.observability] pack `quant.finance` activated for `desk.risks` (vocabulary match: quant.risk, quant.pricing, Limits, capped_by) — empty checked-set (no violations to report)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
Exit 1 — a finding, never a silent pass. Two things to read here:
- The finding and the advisory ride different channels. The pack activated (its vocabulary is all over the file — note the match list names every token that triggered it), but activation lives on the warnings channel and never gates. The dangling reference lives on the findings channel and does.
empty checked-set (no violations to report)— the pack’s declaredquant.limit_closedchecker ran and honestly reports an empty violation set. More on what that does and does not mean below.
Fix the typo — [[desk.riss]] → [[desk.risks]] — and lint again:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory desk.risks [linter.observability] pack `quant.finance` activated for `desk.risks` (vocabulary match: quant.risk, quant.pricing, Limits, capped_by) — empty checked-set (no violations to report)
→ Run: specodelic graph
Zero findings, exit 0 — and now the advisory is the whole output.
Read it as the meter it is: an exit-0 lint alone proves nothing (a spec
using none of the pack vocabulary also exits 0, silently); the
activation line naming quant.finance is the observable that the
pack was discovered, matched, and engaged. The pipeline’s next stage
runs pack-aware too:
spk graph desk-risks.md
graph: 1 file(s), 15 node(s), 17 edge(s), 0 dangling, 0 typing violation(s), 0 supersedes cycle(s), 0 external boundary(ies)
→ Run: specodelic lint
What a missing pack looks like
The demo depends on the pack file being discovered. Lint the same
finished spec in a fresh git workspace with no kind: profile file
in it — not a subdirectory of the first workspace, whose repo toplevel
the discovery scan still anchors to — and the machinery goes inert…
almost. First, the truly silent case: drop the one fiber-kind row (the
es_reconciles law, whose kind cell is pack vocabulary) and lint:
lint: 1 file(s) linted, 0 finding(s), 0 advisory warning(s)
→ Run: specodelic graph
No advisory, no findings — the byte-identical guarantee in action. The
## Limits table is now just markdown the linter does not interpret,
and that is by design: a pack can never gate a file that did not opt
in. But inert cuts both ways, and the linter tells you when vocabulary
expects a pack. Restore the law row and lint in that same pack-less
workspace:
lint: 1 file(s) linted, 2 finding(s), 0 advisory warning(s)
desk.risks [linter.property_kind_closed] property `es_reconciles` has kind `quant.risk` — outside the closed set {unit, law} (kinds.md property_row_shape)
desk.risks [linter.orphan_vocabulary] orphan vocabulary: pack-qualified token(s) `quant.risk` used by `desk.risks`, but no `kind: profile` pack in namespace `quant` is discovered — candidate pack `quant.*` (prefix-derived: only the namespace is known); remediations: add/enable a `kind: profile` pack file declaring the vocabulary, or fix the vocabulary (un-qualify or retype the token)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.orphan_vocabulary — the finding always names the candidate
(namespace-derived, quant.* here) and states both remediations:
enable a pack declaring the vocabulary, or fix the token. Copy the pack
file back into the workspace and the pair clears — with the pack
active, the walkers accept a fiber kind in a kind column
(base ∪ active-pack-fiber), so that mis-typing only surfaces when the
pack is missing. The reliable diagnostic is the one this section
started with: a spec you meant to be pack-enabled but lint with no
activation advisory is a spec whose pack was not discovered.
What the linter does not catch (yet)
Honesty check — the failures this page has not shown are the ones you
might expect first. Change var_1d to ghost_row in the capped_by
column and lint:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory desk.risks [linter.observability] pack `quant.finance` activated for `desk.risks` (vocabulary match: quant.risk, quant.pricing, Limits, capped_by) — empty checked-set (no violations to report)
→ Run: specodelic graph
Byte-for-byte the clean output — exit 0, activation advisory, nothing
else. That is not the linter missing the dangle by accident:
quant.limit_closed ships as a declaration. In v1, pack checkers
are honest-empty — activation and the empty checked-set are reported
truthfully, violation evaluation is not implemented yet (the
guide documents this, and
verification boundaries places it on
the guarantee ladder). The same applies to the floor: drop the
horizon: and confidence: case labels from es_reconciles
and the lint output is identical — quant.risk_labels is also named,
not executed.
The format’s answer to the gap is the one this spec already embodies:
the dangling-edge claim lives where the evidence plan lives — the
caps_closed invariant, its caps_resolve property, and eventually a
real executable predicate. When the checkers gain evaluation, the
declarations are already in place; the spec does not change.
The finished spec
Every block above assembles into this one file, desk-risks.md —
exactly as it lints at 0 findings, 1 activation advisory:
---
id: desk.risks
kind: intent
statement: "WHEN the desk books trades against its risk limits THE system SHALL record every bounded risk measure and pricing invariant together with the cap edges between them."
---
# Desk risk register
A small risk register for one prop trading desk: overnight VaR, its
expected-shortfall companion, and the NAV consistency bound — each
stated with its unit and bound, and the cap edges between them.
## Limits
| name | kind | unit | bound |
|-----------|---------------|------|---------|
| var_1d | quant.risk | USD | 250_000 |
| es_1d | quant.risk | USD | 400_000 |
| nav_drift | quant.pricing | bps | 25 |
| row | capped_by |
|--------|-----------|
| es_1d | var_1d |
## Constraints
| id | kind | expr | traces_to | observes |
|-----------------|-----------|----------------------------------------------------------------------------------------|----------------|------------------------------|
| caps_closed | invariant | `every capped_by name resolves to a ## Limits row declared in this file` | [[desk.risks]] | |
| limit_breach | effect | `desk.risks.limit_breach(detail)` | [[desk.risks]] | |
| breach_surfaced | invariant | `every limit breach reaches the risk officer with the offending row and its bound named` | [[desk.risks]] | [[desk.risks.limit_breach]] |
## Model
### States
- `booked`
- `checked`
- `published`
- `failed` (emits: `[[desk.risks.limit_breach]]`)
### Transitions
| id | from | to | guard |
|---------|---------|-----------|------------------------------------|
| check | booked | checked | `[[desk.risks.caps_closed]]` |
| publish | checked | published | `end-of-day run written` |
| fail | booked | failed | `¬[[desk.risks.caps_closed]]` |
## Properties
| id | kind | derives_from | generator | predicate |
|----------------|------------|--------------------------------|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| caps_resolve | unit | [[desk.risks.caps_closed]] | `registers_with_cap_edges()` | `every capped_by name resolves to a declared ## Limits row` |
| breach_emitted | unit | [[desk.risks.limit_breach]] | `registers_with_breaches()` | `the failure carries the desk.risks.limit_breach label with detail` |
| breach_named | unit | [[desk.risks.breach_surfaced]] | `registers_with_breaches()` | `the risk-officer message names the offending ## Limits row and its bound` |
| es_reconciles | quant.risk | [[desk.risks.caps_closed]] | `registers_with_es_limits()` | `the ES limit reconciles against the VaR limit in every declared case — **horizon:** both limits are stated over the same 1-day horizon; **confidence:** both limits assume the same 99% confidence level` |
To reproduce every block on this page: copy
packs/quant-finance.md from the repo into a fresh git workspace, save
the file above as desk-risks.md, and run spk lint desk-risks.md.
Where to go next
- The mechanism, end to end: using packs — the practical guide — discovery, activation, honest-empty semantics, the full CLI loop.
- The pack’s normative home: packs — domain-pack
mechanism, and the pack definition itself —
packs/quant-finance.mdis the source of truth for the vocabulary this page used. - The capability spec behind the pack: quant-finance-pack.
- The packs this one requires for the rest of a quant desk’s needs: the numeric.predicates pack (tolerance semantics) and the data.lineage pack (backtest fixtures and datasets) — or the journey without packs at all: worked example — reservation/order.
- Rule semantics offline:
spk explain packsandspk explain lint-rules.
Worked example — the svc.contracts pack, failures included
The generic worked example builds a spec with the
core format alone, and the data.lineage,
numeric.predicates,
empirical.registry,
bioimage.data, and
quant.finance pages run the same journey pack by
pack. This page does it for
svc.contracts
— the pack for services whose published contracts live in different
repositories: a typed ## Interfaces registry section (name, kind,
repo, binding, evolution), the svc.compat compatibility law kind, and
the published_as outbound-leaf reference field. It adds no cross-file
edge and no consumer listing: the base format already carries those
(extension_point + satisfies for request contracts, effect +
observes for events), and USAGE.md §2.6 forbids a publisher from
enumerating its conformers. External contracts — OpenAPI, AsyncAPI,
protobuf, JSON Schema — stay authoritative through an opaque binding
column the pack never parses or absorbs.
Lifecycle: DRAFT.
svc.contractsis the one standard pack still at draft — its pack file’s Model names the single statedraft(pack definition, proposed by the archivedadd-svc-contracts-packchange). What that means for you today: the pack activates by vocabulary match only, everything it produces rides the advisory (warnings) channel with exit 0, and the activation advisory itself carries the suffix— draft status: findings are advisory-first and name it. Nothing a draft pack does can block a lint. Its vocabulary already parses (the fiber kinds extend the closed kind sets), while its checkers are honest-empty declarations — the same v1 semantics every pack ships. Every lint block below shows where the draft status appears; this page is a draft preview, not a promise of published-pack behavior, and promotion is a maintainer decision gated on a second independent consumer.
You will see a small spec use the pack’s vocabulary end to end, the pack-activation advisory that proves the pack actually engaged (draft suffix included), genuinely failing variants with their real labeled findings, and — just as important — the failures the linter does not catch yet, shown honestly.
How packs are enabled and what a pack adds mechanically is the job of
using packs — the practical guide; read that for
the mechanism, this page for the hands-on loop. Every lint and graph
block below is real output from specodelic 0.10.0 on the file as it
stood at that moment. On a TTY spk prints the human-readable text
shown here; piped, it emits a JSON envelope instead.
The story
A checkout team publishes three contracts, and they do not live in one
place: the cart request contract and the order-placed event are owned
by the backend API repo, while the order-summary schema is owned by the
web repo. The spec’s job is to register those three contracts — which
repo owns each, which authoritative external artifact each binds to,
how each is allowed to evolve — and to state each change’s consumer
compatibility as a law. One capability, one file, named
checkout-api.md; the format’s naming law maps - to the namespace
dot, so the frontmatter id is checkout.api.
Deliberately out of scope: what any contract’s consumers look like
and what the OpenAPI/AsyncAPI/JSON Schema files say inside. The
publisher never lists its conformers (the base satisfies/observes
edges carry that direction, derived from the graph), and the binding
column stays opaque — openapi://…, asyncapi://…,
json-schema://… are pointers the linter never interprets.
The setup
Packs are discovered by scanning the workspace, not by any config file:
every kind: profile file in the git repository toplevel of the linted
spec is a candidate pack. So the demo workspace needs two files — the
pack definition (copied from this repo’s packs/ directory) and the
spec under test:
mkdir demo && cd demo && git init
cp /path/to/specodelic/packs/svc-contracts.md .
That is the whole enablement story. The discovery and activation rules (corpus-scan anchors, word-boundary vocabulary matching, the byte-identical guarantee) are in the guide; this page just runs the loop.
Layer 1 — Intent
---
id: checkout.api
kind: intent
statement: "WHEN the checkout team publishes or changes a service contract THE system SHALL register every published contract with its owning repo, its authoritative external binding, its declared evolution mode, and the consumer compatibility law each change must satisfy."
---
# Checkout service contracts
A contract registry for one checkout team: a cart request contract, an
order-placed event, and an order-summary schema — each registered with
the repo that owns it and the authoritative external artifact it binds
to.
Lint it as it stands:
spk lint checkout-api.md
lint: 1 file(s) linted, 1 finding(s), 0 advisory warning(s)
checkout-api.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.model_present — a spec with an intent but no Model section is
incomplete by construction, same as in the generic worked example.
Note what is missing from this output: no pack advisory. The file
uses none of the pack’s vocabulary yet, so the byte-identical guarantee
holds — the pack machinery is invisible. That changes with the next
section.
Layer 2 — the ## Interfaces section
This is the pack’s namespaced section, declared in the pack’s
## Sections manifest with the fixed row shape
| name | kind | repo | binding | evolution |:
## Interfaces
| name | kind | repo | binding | evolution |
|---------------|---------|-------------------|---------------------------------------|-----------|
| cart_api | request | acme/checkout-api | openapi://specs/cart-v2.yaml | additive |
| order_placed | event | acme/checkout-api | asyncapi://specs/order-placed-v1.yaml | versioned |
| order_summary | schema | acme/checkout-web | json-schema://order-summary-v3.json | frozen |
Each column, defined for a cold read:
- name — the row’s id inside this file; contract rows point at it
through
published_as(Layer 3). - kind — a short label closed per pack to
request(a consumer conforms to it via the basesatisfiesedge),event(consumers watch it via the baseobservesedge), andschema(a shared data shape). Boundary note, same as every pack: the mechanism never checks row-kind cells — the closure is the pack’s own prose law. - repo — opaque. The repo that owns and publishes the contract; the pack declares no syntax for it and the linter never parses it.
- binding — opaque. The pointer to the authoritative external
artifact — an OpenAPI or AsyncAPI document, a protobuf definition, a
JSON Schema file. The pack bridges to those standards, it never
absorbs them: the
.yaml/.proto/.jsonfile stays the system of record, and nothing in this spec is allowed to contradict it. - evolution — closed per pack to
additive(only compatible additions),versioned(breaking changes ship as new versions), andfrozen(no changes at all). Also unchecked by the mechanism — the closure is prose.
The pack declares one reference field in its ## References manifest —
published_as, resolving to a ## Interfaces row of the declaring
file — and two checkers, svc.interface_closed (every published_as
name resolves to an ## Interfaces row declared in the same file) and
svc.compat_cases (svc.compat laws enumerate their added and
removed case labels per the pack’s floor). Both ship as declarations:
the mechanism names them, it does not yet execute them — keep reading,
the page shows exactly what that means. The same manifest style
declares the pack’s floor: law-kind properties of kind svc.compat
must carry added: and removed: case labels — the two cases
that decide whether existing consumers keep working. The base law floor
is untouched — the mandatory identity:/associativity: labels ride
base law rows (see the numeric.predicates page);
this floor is kind-dependent and rides the fiber kind, as the
Properties layer below pays it.
Lint with the section in place, Model still missing:
lint: 1 file(s) linted, 1 finding(s), 1 advisory warning(s)
checkout-api.md [linter.model_present] Model section incomplete: 0 states, 0 transitions — both sections must exist (empty-but-present beats absent)
advisory checkout.api [linter.observability] pack `svc.contracts` activated for `checkout.api` (vocabulary match: Interfaces) — empty checked-set (no violations to report) — draft status: findings are advisory-first and name it
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
There is the activation advisory, draft suffix and all: — draft status: findings are advisory-first and name it is the pack’s
lifecycle speaking. The match list names every vocabulary token that
triggered it — so far just the section name. The finding on the
findings channel and the advisory on the warnings channel are
independent: the draft status cannot change the exit code, ever.
Layer 3 — Constraints, Model, Properties
The rest of the file is the ordinary four-layer shape — the pack adds
vocabulary, it does not change the layers. The two contract rows are
base-format rows: a request contract is an extension_point (consumers
satisfies it, from their own files), the event is an effect
(consumers observes it). The pack’s published_as field rides as a
column on those rows, tying each contract to the ## Interfaces row
that registers it. Two invariants state the registry’s claims, a
failure effect labels the breach output, and the model is a
four-state publish lifecycle whose failure terminal emits:
## Constraints
| id | kind | expr | traces_to | observes | published_as |
|--------------------|-----------------|------------------------------------------------|------------------|--------------------------------|--------------|
| cart_contract | extension_point | `a consumer conforms to the cart request contract` | [[checkout.api]] | | cart_api |
| contracts_closed | invariant | `every published_as name on a contract row resolves to a ## Interfaces row declared in this file` | [[checkout.api]] | | |
| order_placed_event | effect | `checkout.api.order_placed_event(payload)` | [[checkout.api]] | | order_placed |
| compat_breach | effect | `checkout.api.compat_breach(detail)` | [[checkout.api]] | | |
| breach_surfaced | invariant | `every compatibility breach reaches the consumer team with the interface and change case named` | [[checkout.api]] | [[checkout.api.compat_breach]] | |
| event_on_bus | invariant | `every order event reaches the message bus topic its binding declares` | [[checkout.api]] | [[checkout.api.order_placed_event]] | |
## Model
### States
- `registered`
- `checked`
- `published`
- `failed` (emits: `[[checkout.api.compat_breach]]`)
### Transitions
| id | from | to | guard |
|-------|------------|-----------|-----------------------------------|
| check | registered | checked | `[[checkout.api.contracts_closed]]` |
| ship | checked | published | `all published_as bindings written` |
| fail | registered | failed | `¬[[checkout.api.contracts_closed]]` |
## Properties
| id | kind | derives_from | generator | predicate |
|----------------|------------|-------------------------------------|----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| order_emitted | unit | [[checkout.api.order_placed_event]] | `checkout_runs_with_orders()` | `every order publication carries the order_placed payload` |
| bus_written | unit | [[checkout.api.event_on_bus]] | `checkout_runs_with_orders()` | `every order event is written to the topic its binding declares` |
| contracts_resolve | unit | [[checkout.api.contracts_closed]] | `contract_rows_with_published_as()` | `every published_as name resolves to a declared ## Interfaces row` |
| breach_emitted | unit | [[checkout.api.compat_breach]] | `contract_changes_with_breaches()` | `the failure carries the checkout.api.compat_breach label with detail` |
| breach_named | unit | [[checkout.api.breach_surfaced]] | `contract_changes_with_breaches()` | `the consumer-team message names the interface and the change case` |
| cart_compat | svc.compat | [[checkout.api.cart_contract]] | `cart_contracts_with_changes()` | `every cart contract change keeps existing consumers working in every declared case — **added:** optional fields and new endpoints keep existing clients valid; **removed:** a removal never ships inside an additive binding — it ships as a new versioned binding` |
One pack-specific row: cart_compat is a law whose kind is the fiber
kind svc.compat — the Properties layer paying the pack’s floor, its
predicate enumerating added: and removed: as named cases
(the two directions a published contract can break in). Everything
else — traces_to, guards, emits, coverage, the observes edges
that keep every effect somebody’s watched output — is core-format
machinery, taught properly in the generic worked
example. And note what the Constraints table
cannot grow: a consumers column. The pack declares no
provider-to-consumer field — that direction is the graph’s job
(satisfies from the consumer’s file, observes likewise), never the
publisher’s authored list.
First full lint — a dangling reference, then the fix
The author’s first full draft carries one typo: cart_contract’s
traces_to cell says [[checkout.apt]] — misremembered, the same
classic as the reservation example. Lint the file:
spk lint checkout-api.md
lint: 1 file(s) linted, 2 finding(s), 1 advisory warning(s)
checkout-api.md [linter.total_refs] dangling reference `[[checkout.apt]]` from constraints.traces_to — target not defined in any spec file
checkout.api [linter.single_root_reachable] 2 row(s) unreachable from any intent row — an orphaned island (traces_to/derives_from/guard/from-to/emits): checkout.api.cart_compat, checkout.api.cart_contract
advisory checkout.api [linter.observability] pack `svc.contracts` activated for `checkout.api` (vocabulary match: svc.compat, Interfaces, published_as) — empty checked-set (no violations to report) — draft status: findings are advisory-first and name it
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
Exit 1 — findings, never a silent pass. Three things to read here:
- One typo, two labeled findings. The dangling
traces_tois the direct hit;linter.single_root_reachableis the cascade — with the edge broken,cart_contractand the law that derives from it (cart_compat) become an island no intent can reach. Fixing the typo clears both. - The finding and the advisory ride different channels. The pack activated (its vocabulary is all over the file — the match list names all three tokens now: the kind, the section, the field), but activation lives on the warnings channel and never gates, draft status or not. The dangling reference lives on the findings channel and does.
empty checked-set (no violations to report)— the pack’s declaredsvc.interface_closedchecker ran and honestly reports an empty violation set. More on what that does and does not mean below.
Fix the typo — [[checkout.apt]] → [[checkout.api]] — and lint
again:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory checkout.api [linter.observability] pack `svc.contracts` activated for `checkout.api` (vocabulary match: svc.compat, Interfaces, published_as) — empty checked-set (no violations to report) — draft status: findings are advisory-first and name it
→ Run: specodelic graph
Zero findings, exit 0 — and now the advisory is the whole output.
Read it as the meter it is: an exit-0 lint alone proves nothing (a spec
using none of the pack vocabulary also exits 0, silently); the
activation line naming svc.contracts is the observable that the
pack was discovered, matched, and engaged. The pipeline’s next stage
runs pack-aware too:
spk graph checkout-api.md
graph: 1 file(s), 20 node(s), 23 edge(s), 0 dangling, 0 typing violation(s), 0 supersedes cycle(s), 1 external boundary(ies)
external boundary: checkout.api
→ Run: specodelic lint
The 1 external boundary is not a defect — it is this pack’s whole
point, derived for free. A file hosting an extension_point row is
an external boundary: the checkout team’s spec is the seam between
their repo and every consumer’s repo, and the graph says so without the
publisher ever listing a consumer.
What a missing pack looks like
The demo depends on the pack file being discovered. Lint the same
finished spec in a fresh git workspace with no kind: profile file
in it — not a subdirectory of the first workspace, whose repo toplevel
the discovery scan still anchors to — and the machinery turns from
advisory into findings:
lint: 1 file(s) linted, 2 finding(s), 0 advisory warning(s)
checkout.api [linter.property_kind_closed] property `cart_compat` has kind `svc.compat` — outside the closed set {unit, law} (kinds.md property_row_shape)
checkout.api [linter.orphan_vocabulary] orphan vocabulary: pack-qualified token(s) `svc.compat` used by `checkout.api`, but no `kind: profile` pack in namespace `svc` is discovered — candidate pack `svc.*` (prefix-derived: only the namespace is known); remediations: add/enable a `kind: profile` pack file declaring the vocabulary, or fix the vocabulary (un-qualify or retype the token)
→ Run: fix the reported invariants — each rule's semantics: spk explain lint-rules
linter.orphan_vocabulary — the finding always names the candidate
(namespace-derived, svc.* here) and states both remediations:
enable a pack declaring the vocabulary, or fix the token. Copy the pack
file back into the workspace and the pair clears — with the pack
active, the walkers accept a fiber kind in a kind column
(base ∪ active-pack-fiber), so that mis-typing only surfaces when the
pack is missing. The reliable diagnostic is the one this section
started with: a spec you meant to be pack-enabled but lint with no
activation advisory is a spec whose pack was not discovered.
For the other direction — proof the guarantee still runs toward
silence — strip every pack-vocabulary token from the same file (drop
the ## Interfaces section, the published_as column and the prose
mentions of it, and the svc.compat law row), then lint in that same
pack-less workspace:
lint: 1 file(s) linted, 0 finding(s), 0 advisory warning(s)
→ Run: specodelic graph
No advisory, no findings — the byte-identical guarantee in action. What is left is an ordinary base-format spec, and a pack can never gate a file that did not opt in.
What the linter does not catch (yet)
Honesty check — the failures this page has not shown are the ones you
might expect first. Change cart_api to ghost_row in the
published_as column of the finished spec and lint:
lint: 1 file(s) linted, 0 finding(s), 1 advisory warning(s)
advisory checkout.api [linter.observability] pack `svc.contracts` activated for `checkout.api` (vocabulary match: svc.compat, Interfaces, published_as) — empty checked-set (no violations to report) — draft status: findings are advisory-first and name it
→ Run: specodelic graph
Byte-for-byte the clean output — exit 0, activation advisory, nothing
else. That is not the linter missing the dangle by accident:
svc.interface_closed ships as a declaration. In v1, pack checkers
are honest-empty — activation and the empty checked-set are reported
truthfully, violation evaluation is not implemented yet (the
guide documents this, and
verification boundaries places it on
the guarantee ladder). The same applies to the floor: drop the
added: and removed: case labels from cart_compat’s
predicate and the lint output is identical — svc.compat_cases is also
named, not executed.
The format’s answer to the gap is the one this spec already embodies:
the dangling-edge claim lives where the evidence plan lives — the
contracts_closed invariant, its contracts_resolve property, and
eventually a real executable predicate. When the checkers gain
evaluation, the declarations are already in place; the spec does not
change. Until then, remember what draft means here twice over: the
pack’s findings are advisory-first, and its checkers are
declarations — this page is a preview of a mechanism still being
filled in, not a compatibility guarantee you can test today.
The finished spec
Every block above assembles into this one file, checkout-api.md —
exactly as it lints at 0 findings, 1 activation advisory:
---
id: checkout.api
kind: intent
statement: "WHEN the checkout team publishes or changes a service contract THE system SHALL register every published contract with its owning repo, its authoritative external binding, its declared evolution mode, and the consumer compatibility law each change must satisfy."
---
# Checkout service contracts
A contract registry for one checkout team: a cart request contract, an
order-placed event, and an order-summary schema — each registered with
the repo that owns it and the authoritative external artifact it binds
to.
## Interfaces
| name | kind | repo | binding | evolution |
|---------------|---------|-------------------|---------------------------------------|-----------|
| cart_api | request | acme/checkout-api | openapi://specs/cart-v2.yaml | additive |
| order_placed | event | acme/checkout-api | asyncapi://specs/order-placed-v1.yaml | versioned |
| order_summary | schema | acme/checkout-web | json-schema://order-summary-v3.json | frozen |
## Constraints
| id | kind | expr | traces_to | observes | published_as |
|--------------------|-----------------|------------------------------------------------|------------------|--------------------------------|--------------|
| cart_contract | extension_point | `a consumer conforms to the cart request contract` | [[checkout.api]] | | cart_api |
| contracts_closed | invariant | `every published_as name on a contract row resolves to a ## Interfaces row declared in this file` | [[checkout.api]] | | |
| order_placed_event | effect | `checkout.api.order_placed_event(payload)` | [[checkout.api]] | | order_placed |
| compat_breach | effect | `checkout.api.compat_breach(detail)` | [[checkout.api]] | | |
| breach_surfaced | invariant | `every compatibility breach reaches the consumer team with the interface and change case named` | [[checkout.api]] | [[checkout.api.compat_breach]] | |
| event_on_bus | invariant | `every order event reaches the message bus topic its binding declares` | [[checkout.api]] | [[checkout.api.order_placed_event]] | |
## Model
### States
- `registered`
- `checked`
- `published`
- `failed` (emits: `[[checkout.api.compat_breach]]`)
### Transitions
| id | from | to | guard |
|-------|------------|-----------|-----------------------------------|
| check | registered | checked | `[[checkout.api.contracts_closed]]` |
| ship | checked | published | `all published_as bindings written` |
| fail | registered | failed | `¬[[checkout.api.contracts_closed]]` |
## Properties
| id | kind | derives_from | generator | predicate |
|----------------|------------|-------------------------------------|----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| order_emitted | unit | [[checkout.api.order_placed_event]] | `checkout_runs_with_orders()` | `every order publication carries the order_placed payload` |
| bus_written | unit | [[checkout.api.event_on_bus]] | `checkout_runs_with_orders()` | `every order event is written to the topic its binding declares` |
| contracts_resolve | unit | [[checkout.api.contracts_closed]] | `contract_rows_with_published_as()` | `every published_as name resolves to a declared ## Interfaces row` |
| breach_emitted | unit | [[checkout.api.compat_breach]] | `contract_changes_with_breaches()` | `the failure carries the checkout.api.compat_breach label with detail` |
| breach_named | unit | [[checkout.api.breach_surfaced]] | `contract_changes_with_breaches()` | `the consumer-team message names the interface and the change case` |
| cart_compat | svc.compat | [[checkout.api.cart_contract]] | `cart_contracts_with_changes()` | `every cart contract change keeps existing consumers working in every declared case — **added:** optional fields and new endpoints keep existing clients valid; **removed:** a removal never ships inside an additive binding — it ships as a new versioned binding` |
To reproduce every block on this page: copy
packs/svc-contracts.md from the repo into a fresh git workspace, save
the file above as checkout-api.md, and run
spk lint checkout-api.md.
Where to go next
- The mechanism, end to end: using packs — the practical guide — discovery, activation, lifecycle and draft-status semantics, honest-empty checkers, the full CLI loop.
- The pack’s normative home: packs — domain-pack
mechanism, and the pack definition itself —
packs/svc-contracts.mdis the source of truth for the vocabulary this page used. - The capability spec behind the pack:
svc-contracts-pack — including
the design decision that keeps
published_asan intra-file outbound leaf. - The published sibling packs, for the stable versions of the same journey: the data.lineage pack (registry rows for datasets) or the quant.finance pack (registry rows for limits) — or the journey without packs at all: worked example — reservation/order.
- Rule semantics offline:
spk explain packsandspk explain lint-rules.
reservation-order — the finished spec
id: reservation.order kind: intent statement: “WHEN a customer submits an order THE reservation service SHALL hold stock for every line item and accept the order, or reject it with nothing held and nothing charged.”
Reservation & Order
A small order pipeline: submitting an order holds stock for every line item or rejects the order outright; accepted orders are charged exactly once, fulfilled, or released by cancellation or expiry. The subject of this spec is the reservation service itself — nothing here constrains or mentions any checking tool.
Constraints
| id | kind | expr | traces_to | observes |
|---|---|---|---|---|
| all_or_nothing | invariant | the order is accepted only if every line item's requested quantity is available; otherwise no unit is held and nothing is charged | [[reservation.order]] | |
| held_or_converted | invariant | every unit held for an accepted order is either converted to a fulfillment or released; no held unit outlives both | [[reservation.order]] | |
| release_is_total | invariant | when a hold is released, no unit remains held for that order; releasing an already-released hold changes nothing | [[reservation.order]] | |
| charge_iff_fulfilled | invariant | the customer is charged if and only if the order reaches fulfilled | [[reservation.order]] | |
| cancel_refunds_exactly_charged | invariant | IF a charged order is cancelled, THEN the refund equals exactly the amount charged — never more, never less | [[reservation.order]] | |
| expiry_releases_holds | invariant | an unfulfilled reservation expires after its TTL; expiry releases the hold without charging the customer | [[reservation.order]] | |
| stock_exhausted | effect | reservation.order.stock_exhausted(detail) | [[reservation.order]] | |
| reject_reason_surfaced | invariant | every rejection reaches the caller with the exhausted line items named | [[reservation.order]] | [[reservation.order.stock_exhausted]] |
Model
States
submittedreservedchargedfulfilledcancelledreleasedreserve_failed(emits:[[reservation.order.stock_exhausted]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| accept | submitted | reserved | [[reservation.order.all_or_nothing]] |
| reject_order | submitted | reserve_failed | ¬[[reservation.order.all_or_nothing]] |
| charge | reserved | charged | payment authorized ∧ [[reservation.order.charge_iff_fulfilled]] |
| fulfill | charged | fulfilled | payment settled ∧ [[reservation.order.charge_iff_fulfilled]] |
| cancel_paid | charged | cancelled | customer cancelled ∧ ¬payment settled ∧ [[reservation.order.cancel_refunds_exactly_charged]] |
| cancel_unpaid | reserved | cancelled | customer cancelled ∧ [[reservation.order.held_or_converted]] |
| expire | reserved | released | reservation TTL elapsed ∧ [[reservation.order.expiry_releases_holds]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| no_charge_on_rejection | unit | [[reservation.order.all_or_nothing]] | orders_with_mixed_availability() | nothing is charged for any rejected order |
| holds_all_converted | unit | [[reservation.order.held_or_converted]] | arbitrary_closed_orders() | every held unit is converted or released when the order closes |
| double_release_noop | unit | [[reservation.order.release_is_total]] | order_released_twice() | the second release changes nothing |
| fulfilled_implies_charged | unit | [[reservation.order.charge_iff_fulfilled]] | arbitrary_accepted_orders() | charged(order) == (outcome(order) == fulfilled) |
| refund_bounded | unit | [[reservation.order.cancel_refunds_exactly_charged]] | charged_orders_cancelled_before_settlement() | refund(order) == amount_charged(order) |
| expiry_free | unit | [[reservation.order.expiry_releases_holds]] | orders_past_ttl() | nothing is charged and no unit remains held after expiry |
| rejection_labels_emitted | unit | [[reservation.order.reject_reason_surfaced]] | orders_with_exhausted_items() | the rejection message names exactly the exhausted line items |
| stock_exhausted_emitted | unit | [[reservation.order.stock_exhausted]] | rejections_over_sold_out_items() | the rejection carries the stock_exhausted label with the item detail |
batch-resume — derived parallelism, worked end to end
id: batch.resume kind: intent statement: “WHEN a batch run starts and the manifest marks some items done, THE processor SHALL invoke processing only on items whose content fingerprint differs from the manifest’s record.”
Batch Resume
A processor consumes a collection of files and records per-item progress in a sidecar manifest, so an interrupted run resumes without reprocessing unchanged items. The subject of this spec is the batch processor itself — per USAGE.md §0, nothing here constrains or mentions any checking tool.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| done_iff_output_durable | invariant | manifest[item].status == done → output(item) was durably written before the marker was written | [[batch.resume]] |
| resume_touches_only_unprocessed | invariant | a resumed run invokes process(item) only if fingerprint(item) ≠ manifest[item].fingerprint | [[batch.resume]] |
| changed_input_invalidates_marker | invariant | fingerprint(item) ≠ manifest[item].fingerprint → the item is treated as pending regardless of its recorded status | [[batch.resume]] |
| failed_is_not_done | invariant | manifest[item].status == failed → item is reprocessed on resume; status == partial → processing resumes from manifest[item].offset | [[batch.resume]] |
| manifest_is_sidecar | invariant | the manifest lives outside every output artifact; no output contains manifest rows | [[batch.resume]] |
| offset_version_contract | invariant | a recorded offset is valid only for the processor version that wrote it; a version mismatch demotes the item to pending | [[batch.resume]] |
| resumed_equivalent_to_fresh | invariant | result(resume(run interrupted at t)) == result(uninterrupted rerun over the same inputs) | [[batch.resume]] |
| map_is_parallel_unconditional | invariant | deterministic per-item processing runs over any partition without coordination — map parallelism is licensed by determinism alone, never gated on merge algebra | [[batch.resume]] |
| reduce_sound_iff_monoid | invariant | partitioned results may be combined by merge iff merge is associative with an identity; partition order is free iff merge is additionally commutative — no reduce-parallelism claim exists apart from these laws | [[batch.resume]] |
| process_is_deterministic | invariant | process(item) is a pure function of (item content, offset) — no reads of wall-clock, randomness, or unrecorded external state | [[batch.resume]] |
| emits_survive_replay | invariant | every emitted artifact is a pure function of (item, offset), so reprocessing after a crash rewrites identical bytes; emissions that cannot be rewritten (notifications, appends) are deduplicated by (item fingerprint, offset) before taking effect | [[batch.resume]] |
| manifest_merges_idempotently | invariant | concurrent workers checkpoint into per-worker manifests combined by union of per-item markers; marker ∪ marker is associative, commutative, and idempotent, so any merge order and any checkpoint frequency yields the same manifest | [[batch.resume]] |
Model
States
pendingin_progresspartialdonefailed
Transitions
| id | from | to | guard |
|---|---|---|---|
| start | pending | in_progress | item unmarked ∨ fingerprint(item) ≠ manifest[item].fingerprint |
| complete | in_progress | done | [[batch.resume.done_iff_output_durable]] |
| checkpoint | in_progress | partial | output-so-far written ∧ offset recorded |
| fail | in_progress | failed | ¬[[batch.resume.done_iff_output_durable]] ∧ ¬(output-so-far written ∧ offset recorded) — cites the union of success siblings per guard_negation_total |
| resume_partial | partial | in_progress | recorded offset version matches the current processor |
| requeue_failed | failed | pending | [[batch.resume.failed_is_not_done]] |
| invalidate | done | pending | [[batch.resume.changed_input_invalidates_marker]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| done_marker_implies_output_readable | unit | [[batch.resume.done_iff_output_durable]] | crash_between(write_output(item), write_marker(item)) | no resumed run reports done for an item whose output is missing |
| resumed_run_invocation_count | unit | [[batch.resume.resume_touches_only_unprocessed]] | manifest with k done items, n−k changed items | invocation_count(process) == n − k |
| edited_item_reprocessed | unit | [[batch.resume.changed_input_invalidates_marker]] | edit an item after its marker was written | the item is processed on the next resume |
| failed_item_reprocessed | unit | [[batch.resume.failed_is_not_done]] | mark an item failed, then resume | invocation_count(process(item)) == 1 |
| outputs_contain_no_manifest_rows | unit | [[batch.resume.manifest_is_sidecar]] | arbitrary completed run | manifest rows appear in no output artifact |
| version_mismatch_invalidates_partial | unit | [[batch.resume.offset_version_contract]] | bump processor version with stale partial offsets recorded | partial items reprocessed from 0, never from the stale offset |
| crash_resume_equivalent | unit | [[batch.resume.resumed_equivalent_to_fresh]] | arbitrary crash-injection schedule over the run | result(resumed_run) == result(uninterrupted_run) |
| resume_composition | law | [[batch.resume.resumed_equivalent_to_fresh]] | arbitrary manifests and crash schedules | **associativity:** resume(resume(m, a), b) == resume(m, a ++ b) **identity:** resume(empty_manifest, items) == process_all(items) |
| results_merge_monoid | law | [[batch.resume.reduce_sound_iff_monoid]] | arbitrary triples of partial results (a, b, c) | **associativity:** merge(merge(a, b), c) == merge(a, merge(b, c)) **identity:** merge(e, a) == a == merge(a, e) — fails ⇒ reduce is inherently sequential, chunked execution unsound (map stays parallel per map_is_parallel_unconditional) |
| results_merge_commutative | unit | [[batch.resume.reduce_sound_iff_monoid]] | arbitrary pairs of partial results | merge(a, b) == merge(b, a) — optional case, feeds the tier table below: holds ⇒ workers need no ordering; fails ⇒ merge is a non-commutative monoid (e.g. ordered append), reduce still parallel-sound, merge order fixed |
| manifest_merge_semilattice | law | [[batch.resume.manifest_merges_idempotently]] | arbitrary triples of per-worker manifests | **associativity:** (m ∪ n) ∪ o == m ∪ (n ∪ o) **identity:** ∅ ∪ m == m **idempotence:** m ∪ m == m — idempotence is what makes double-checkpointing harmless |
| map_chunks_uncoordinated | unit | [[batch.resume.map_is_parallel_unconditional]] | arbitrary item partition across workers | per-item results match the sequential run regardless of partition boundaries |
| deterministic_replay | unit | [[batch.resume.process_is_deterministic]] | same item processed twice under any interleaving | outputs are byte-identical |
| replay_dedupes_emissions | unit | [[batch.resume.emits_survive_replay]] | crash after emit, before marker; resume | output artifact state equals an uninterrupted run's — no duplicated appends, no replayed notifications |
Notes
Transducer framing. process is a transducer: a step function over
(accumulator, item) → (accumulator, emits). The Model section is that
transducer per item. Transducers compose; chunkability is a property of
the accumulator’s algebra, not of transducer-hood — a transducer whose
step is associative parallelizes like a scan (parallel prefix); one whose
state entangles order does not. So the design-raising questions this
spec forces are exactly three:
is the accumulator a monoid — and is the process deterministic, with replay-safe emissions?
Three answers, three architectures, all derived rather than asserted (map parallelism needs none of these — only determinism; the algebra gates the reduce):
| accumulator algebra | architecture the law row licenses |
|---|---|
| deterministic, emissions replayable | parallel map over any partition — no algebra required (map_is_parallel_unconditional, emits_survive_replay) |
results form a monoid (results_merge_monoid) | + chunked parallel reduce; merge in tree order |
+ commutative (results_merge_commutative) | + workers need no ordering at all; any shuffle, any reduce tree |
+ idempotent (semilattice, manifest_merge_semilattice) | + concurrent workers checkpoint freely; duplicates vanish |
| results not a monoid (e.g. running average, ordered state machine) | reduce is inherently sequential — still streamable, never chunkable; say so here rather than discovering it in production |
The resume design of the Constraints table is itself an instance: the
manifest is a semilattice, which is why resume composes
(resume_composition) and why concurrent checkpointing is safe.
The three decisions the constraints force, in dependency order:
- Marker placement (
manifest_is_sidecar): the manifest is a sidecar (e.g..state.jsonl), never embedded in outputs — half-written markers must not be readable by downstream consumers. - Fingerprints, not paths (
changed_input_invalidates_marker): items are keyed by content fingerprint; renames between runs keep their progress, edits discard it. - Partial is a contract, not a bookmark (
offset_version_contract): once offsets are recorded, the processor version is part of their meaning; alternatively, promote sub-steps to items and keep markers boolean-shaped (seeno_boolean_columnsinspecodelic.mdfor why the status vocabulary is named variants, not flags).
done_iff_output_durable exists to make the worst interleaving — crash
after marker, before output — unreachable; emits_survive_replay closes
the mirror-image hole (crash after emit, before marker: at-least-once
replay must not duplicate side effects — the semilattice protects
manifests, not outputs). crash_resume_equivalent is the property a
test suite must actually check to prove resume is just a cache in front
of a deterministic rerun — which is why process_is_deterministic is a
constraint in its own right, not an implicit assumption. Failure effects (a
kind = "effect" row per error, per the corpus’s error contract) are
omitted here; add them if the processor exposes an error surface.
acset-core — capability
id: acset.core kind: intent statement: “THE acset-core capability SHALL hold the format’s fixed finite schema — five objects and typed reference morphisms — as one data value, SHALL build each parsed corpus into a typed instance whose dangling references are representable values rather than lint-time afterthoughts, and SHALL answer reachability questions through a single closure primitive shared by graph, merge, and refactor — all internal-only, with every migration gated by parity against the existing walks.”
acset-core Specification
Purpose
One place for the format’s schema and one way to walk it. Today the
Reference Typing table lives as NodeKind/kind_index and match arms in
src/graph.rs; src/merge.rs builds a private adjacency map despite
openspec/specs/merge/spec.md mandating reuse of graph.md’s blast-radius query; and
fan_in/fan_out are built in two duplicated edge loops. This capability
replaces those with a Schema value, a typed instance builder, and
closure primitives — with edge-for-edge parity gates so no behaviour
changes. The rigorous reading (attributed C-sets over the small fixed
category) ships with the later pushout change; nothing here needs it.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[acset.core]] |
| objects_closed | invariant | the schema's objects are exactly {Intent, Constraint, State, Transition, Property}; a sixth object is added only under a new Revision heading of openspec/specs/specodelic/spec.md (append-only discipline) | [[acset.core]] |
| morphisms_typed | invariant | every morphism has a name, exactly one source object, and exactly one target object; the pair (source, name) is unique across the schema | [[acset.core]] |
| typing_table_is_data | invariant | allowed_targets(m) is read from the Schema value for every morphism m — never decided by a per-field match arm; adding a reference field is adding one row | [[acset.core]] |
| refinement_declared | invariant | a typing rule that depends on a row's own kind column (emits targets effect Constraints only; satisfies targets extension_point only; observes targets effect only) is declared as a refinement predicate on the morphism's target, never inlined into the checker | [[acset.core]] |
| endo_acyclicity_flagged | invariant | an endo-morphism (supersedes; derives_from between laws) carries an explicit flag saying whether cycles through it are forbidden; acyclicity is checked generically for flagged morphisms only | [[acset.core]] |
| schema_matches_typing_table | invariant | when the lint target carries the format doc, the Schema value equals its Reference Typing table (openspec/specs/specodelic/spec.md) row for row, reported as a lint finding on divergence — the document and the code cannot drift; a corpus without the format doc is out of the gate's scope (no-op, never fabricated expected rows) | [[acset.core]] |
| canonical_order | invariant | objects and morphisms are enumerated in a canonical sorted order, so every artifact derived from the Schema is byte-stable | [[acset.core]] |
| ids_interned | invariant | each object's id-set is a dense index 0..n-1 with a bidirectional map to the file-qualified string id; a duplicate file-qualified id is resolved first-wins and recorded in the builder's collision report — identical to the existing graph builder's or_insert behavior — and a lint-clean corpus carries none (no_duplicate_claim fires upstream) | [[acset.core]] |
| morphisms_partial | invariant | each schema morphism is stored as a vector of optional indices, None meaning an unresolved reference; the builder never drops an unresolved reference at parse time | [[acset.core]] |
| typing_enforced_at_build | invariant | a resolved reference whose target object or sub-kind violates the Schema's allowed_targets is recorded as a typing violation and is not stored as a morphism value — the behaviour of openspec/specs/graph/spec.md's edge_kind_matches_typing | [[acset.core]] |
| adapter_total | invariant | from_specs is total on parsed corpora: every Link of every spec is stored as a morphism value, recorded as dangling, or recorded as a typing violation — none is silently dropped | [[acset.core]] |
| adapter_graph_equivalent | invariant | for every corpus the existing graph builder accepts, the edge set derived from the instance equals the edges of the existing graph builder, edge for edge — the parity gate that retires the old path without a flag day | [[acset.core]] |
| build_deterministic | invariant | index assignment follows sorted file-qualified id order; two builds of the same corpus serialize byte-identically regardless of input file order | [[acset.core]] |
| cells_carried | invariant | non-reference cells (expr, statement, generator, predicate, kind cells) are carried as attribute columns keyed by the same indices and are never inspected by the builder — consistent with openspec/specs/specodelic/spec.md's prose_untouched | [[acset.core]] |
| seeds_exist | invariant | every seed id is an id of the instance; an unknown seed is rejected, not ignored | [[acset.core]] |
| scope_by_morphism_set | invariant | a query names the set M of schema morphisms to follow; restricting M to supersedes yields exactly the supersedes subgraph, on which cycle detection is closure-based (openspec/specs/specodelic/spec.md's supersedes_acyclic) | [[acset.core]] |
| single_traversal_primitive | invariant | forward_closure and backward_closure over a seed set and a morphism set M are the only traversal primitives; graph, merge, and refactor are expressed through them with no private adjacency map | [[acset.core]] |
| closure_terminates | invariant | closure on a finite instance terminates and visits each node at most once, including when M contains a cycle | [[acset.core]] |
| dangling_not_followed | invariant | an unresolved reference is not an edge: traversal skips it, the dangling report lists it, and it contributes to no reachability result | [[acset.core]] |
| blast_radius_defined | invariant | blast_radius(I, touched) == the mixed transitive closure — from every reached node, both the morphism values are followed forward and the references backward, i.e. the fixpoint of X ↦ backward_closure(forward_closure(X)); the backward closure of the touched ids alone, plus the targets reached forward from them, is NOT the definition (a backward-reached dependent's own forward reach joins the radius) — the definition in openspec/specs/graph/spec.md's blast_radius_is_transitive_closure, pinned by parity against merge.rs's pre-migration walk | [[acset.core]] |
| fan_in_is_preimage_size | invariant | fan_in(x) == the number of pairs (s, m) with the morphism value at s defined and equal to x | [[acset.core]] |
| results_ordered | invariant | query results are sorted by file-qualified id, so output is deterministic | [[acset.core]] |
| parity_with_existing | invariant | for every corpus, query-derived blast_radius, fan_in, and supersedes cycles equal the values computed by the pre-migration walks in graph.rs and merge.rs — the parity gate that lets the private adjacency maps be deleted | [[acset.core]] |
Model
States
proposedapprovedimplementedarchived
Transitions
| id | from | to | guard |
|---|---|---|---|
| approve | proposed | approved | [[acset.core.process_lifecycle]] ∧ proposal reviewed and approved by the maintainer |
| implement | approved | implemented | [[acset.core.process_lifecycle]] ∧ all tasks.md items complete; parity gates hold over the corpus; just ci and openspec validate --strict pass |
| archive | implemented | archived | [[acset.core.process_lifecycle]] ∧ just archive-change id=add-acset-core ran with the dual-format recipe |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[acset.core.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| sixth_object_rejected | unit | [[acset.core.objects_closed]] | schema_with_extra_object("Action") | check(schema) == failed |
| duplicate_morphism_rejected | unit | [[acset.core.morphisms_typed]] | schema_with_two_morphisms_same_source_and_name() | check(schema) == failed |
| duplicate_id_first_wins_parity | unit | [[acset.core.ids_interned]] | corpus_with_two_rows_same_qualified_id() | node_kinds(builder(corpus)) == node_kinds(existing kind_index(corpus)) — first-wins, and the collision report names the id |
| typing_read_from_schema | unit | [[acset.core.typing_table_is_data]] | schema_with_one_added_reference_field() | typing_violation(edge_over_new_field) is decided with no change to checker code |
| endo_cycle_detected | unit | [[acset.core.endo_acyclicity_flagged]] | supersedes_cycle_a_b_a() | check(instance) == failed |
| unflagged_endo_cycle_tolerated | unit | [[acset.core.endo_acyclicity_flagged]] | law_derives_from_law_self_edge() | check(instance) == passed — only flagged morphisms are cycle-checked |
| schema_drift_detected | unit | [[acset.core.schema_matches_typing_table]] | schema_missing_one_row_of_the_typing_table() | check(schema, typing_table) == failed |
| index_roundtrip | unit | [[acset.core.ids_interned]] | arbitrary_corpus() | name_of(object, index_of(object, id)) == id |
| emits_refinement_enforced | unit | [[acset.core.refinement_declared]] | emits_edge_to_invariant_constraint() | check(edge) == failed — the effect-only rule comes from the declared refinement |
| order_is_canonical | unit | [[acset.core.canonical_order]] | arbitrary_schema_built_from_two_declaration_orders() | schemas_equal_as_values() — enumeration order never depends on declaration order |
| dangling_is_a_value | unit | [[acset.core.morphisms_partial]] | corpus_with_one_unresolved_link() | morphism_value(source) == None and the build reports no error |
| no_link_dropped | unit | [[acset.core.adapter_total]] | arbitrary_corpus() | card(stored) + card(dangling) + card(violations) == card(links) |
| graph_parity | unit | [[acset.core.adapter_graph_equivalent]] | arbitrary_corpus_accepted_by_graph_build() | edges(from_specs(c)) == the pre-migration builder's edges |
| forbidden_edge_not_stored | unit | [[acset.core.typing_enforced_at_build]] | traces_to_pointing_at_a_state() | morphism_value(source) == None and card(typing_violations) == 1 |
| rebuild_is_byte_stable | unit | [[acset.core.build_deterministic]] | arbitrary_corpus(), arbitrary_file_order() | serialize(from_specs(shuffle(c))) == serialize(from_specs(c)) |
| attributes_untouched | unit | [[acset.core.cells_carried]] | row_whose_expr_contains_wiki_link_syntax_in_prose() | attribute(row, "expr") == original_cell |
| unknown_seed_rejected | unit | [[acset.core.seeds_exist]] | closure_over_unknown_seed() | check(query) == failed |
| supersedes_scope_exact | unit | [[acset.core.scope_by_morphism_set]] | corpus_with_supersedes_and_traces_edges() | closure(M == supersedes) contains no traces edge |
| no_private_adjacency | unit | [[acset.core.single_traversal_primitive]] | grep_for_adjacency_map_construction() | no adjacency-map construction exists outside acset::query |
| closure_on_cycle_terminates | unit | [[acset.core.closure_terminates]] | cyclic_morphism_set() | closure(seed) terminates and visits each node at most once |
| closure_laws_hold | unit | [[acset.core.single_traversal_primitive]] | arbitrary_instance(), arbitrary_seed_set(), arbitrary_morphism_set() | the closure over a seed set satisfies its three named cases — contains: S ⊆ closure(S), idempotence: closure(closure(S)) == closure(S), monotonicity: S ⊆ T implies closure(S) ⊆ closure(T) |
| dangling_not_followed_in_traversal | unit | [[acset.core.dangling_not_followed]] | corpus_with_one_dangling_link() | closure(seed) skips the dangling endpoint and the dangling report lists it |
| blast_radius_parity | unit | [[acset.core.blast_radius_defined]] | arbitrary_corpus_with_two_branch_tips() | blast_radius(query) == blast_radius(pre-migration merge walk) |
| fan_in_matches_preimage | unit | [[acset.core.fan_in_is_preimage_size]] | corpus_with_one_shared_target() | fan_in(shared_target) == 2 |
| results_sorted_by_id | unit | [[acset.core.results_ordered]] | query_over_reverse_insertion_order() | output == sorted(output) |
| walk_parity_fan_and_cycles | unit | [[acset.core.parity_with_existing]] | arbitrary_corpus() | closure-derived fan_in, fan_out, and supersedes cycles == the pre-migration walks' values |
Requirements
Requirement: Schema-as-data typing
The acset-core SHALL hold the format’s fixed finite schema — the five
objects (Intent, Constraint, State, Transition, Property) and their typed
reference morphisms — as a single Schema value read by one generic
checker, so the Reference Typing table of openspec/specs/specodelic/spec.md is
evaluated from data instead of per-field match arms in src/graph.rs;
adding a reference field is adding one row.
Scenario: Typing read from schema
- WHEN a new reference field is added as one schema row
- THEN typing decisions for that field come from the Schema value with no change to checker code
Scenario: Schema drift detected at lint time
- WHEN the Schema value diverges from openspec/specs/specodelic/spec.md’s Reference Typing table on any row
- THEN lint reports the divergence naming the mismatched row
Scenario: Endo-acyclicity only where flagged
- WHEN a cycle passes through an unflagged endo-morphism such as derives_from between laws
- THEN it is not reported, while cycles through flagged endo-morphisms such as supersedes are reported
Requirement: Typed instance builder
WHEN a parsed corpus is loaded, the acset-core SHALL build a typed instance — an interned, dense id-set per schema object and a partial function per morphism — in which an unresolved reference is a representable value (None) that is never dropped and never a parse error.
Scenario: Dangling is a value
- WHEN a corpus contains one link that resolves to no id
- THEN the instance stores that morphism value as None and the build succeeds
Scenario: No link dropped
- WHEN a parsed corpus is built into an instance
- THEN every link is stored, recorded as dangling, or recorded as a typing violation — the three counts sum to the link count
Scenario: Forbidden edge not stored
- WHEN a corpus contains a reference whose target object is not in the morphism’s allowed targets
- THEN the instance records a typing violation and stores no morphism value for it
Scenario: Graph parity edge for edge
- WHEN a corpus accepted by the existing graph builder is built into an instance
- THEN the derived edge set equals the existing builder’s edges exactly
Scenario: Byte-stable rebuild
- WHEN the same corpus is built with input files in two different orders
- THEN both serializations are byte-identical
Scenario: Duplicate id parity
- WHEN a corpus contains two rows with the same file-qualified id (a corpus the existing builder accepts first-wins)
- THEN the builder resolves the same way and records the collision in its collision report — never a hard failure the old path did not emit
Scenario: Empty corpus builds an empty instance
- WHEN the input directory contains zero spec files
- THEN the instance is empty and well-formed, closures over it are empty, and parity with the existing builder holds
Requirement: Single traversal primitive
The acset-core SHALL expose forward and backward closure over a seed set
and a chosen set of schema morphisms as its only traversal primitives,
through which graph fan-in, merge blast radius (openspec/specs/merge/spec.md), and
the refactor advisor (openspec/specs/refactor/spec.md) are expressed with no private
adjacency map — and query-derived results SHALL equal the pre-migration
walks for every corpus.
Scenario: Parity with existing walks
- WHEN closure-derived blast radius, fan-in, and supersedes cycles are computed over any corpus
- THEN they equal the values the pre-migration graph.rs and merge.rs walks compute
Scenario: Unknown seed rejected
- WHEN a closure is requested over a seed id that is not in the instance
- THEN the query fails with a remediation hint rather than ignoring the seed
Scenario: Dangling not followed
- WHEN traversal reaches an unresolved reference
- THEN it is skipped for reachability, listed in the dangling report, and contributes to no result
Scenario: Supersedes-scoped cycles
- WHEN M is restricted to supersedes and a cycle exists in the supersedes subgraph
- THEN closure-based cycle detection over that scope reports it, matching the existing supersedes_acyclic behaviour
acset-writer — capability
id: acset.writer kind: intent statement: “WHEN an instance edit is realized as text, THE acset-writer capability SHALL emit markdown that differs from the source only in the cells and links the edit touches, so that the typed core never costs the format its byte-stable round trip.”
acset-writer Specification
Purpose
The one module allowed to turn an instance edit back into text. It works
from recorded source spans and returns a write-set, never re-serializing
a table and never doing I/O itself. Without it, the typed acset core
(acset-core) can read and query the corpus but every edit still flows
through hand-wired rewriting in src/rename.rs — unspecified, unparityed,
and drifting from the format’s byte-stability promises
(rename.prose_untouched_by_rename). This capability makes emission a
specified, identity-gated seam: emit(parse(x), no edit) == x byte for
byte over every file the parser accepts, and every edit touches only the
spans the edit names.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[acset.writer]] |
| source_spans_recorded | invariant | parsing records the byte span of every id cell, every [[link]] occurrence, and every state bullet; the writer edits only recorded spans and never re-serializes a table | [[acset.writer]] |
| emit_identity | invariant | emit(parse(x), no edit) == x byte for byte, for every spec file the parser accepts | [[acset.writer]] |
| untouched_bytes_preserved | invariant | bytes outside the rewritten spans of edited rows are identical to the source text — including prose, CRLF terminators, trailing whitespace, and table padding (the mechanical realization of rename.md's prose_untouched_by_rename) | [[acset.writer]] |
| width_padding_policy | invariant | when an id edit changes a cell's width the writer leaves surrounding padding unchanged, so a column may become unaligned; realignment is a separate explicit formatting operation and never a side effect of an edit | [[acset.writer]] |
| filename_follows_intent_id | invariant | an edit to a file's own Intent id also produces the file rename implied by specodelic.md's id_matches_file rule | [[acset.writer]] |
| write_set_atomic | invariant | the writer returns a write-set of (path, full new contents) plus optional removals and performs no I/O; applying it is the caller's single transaction (the shape of rename.md's atomic_operation write-set) | [[acset.writer]] |
| edit_application_faithful | invariant | for a morphism f : I → I' and source text x, from_specs(apply(f, x)) equals I' — the emitted text realizes exactly the instance the edit denotes | [[acset.writer]] |
| edits_compose | invariant | apply(g, apply(f, x)) == apply(compose(g, f), x) | [[acset.writer]] |
| span_failure | effect | spec.span_failure(detail) — the writer could not extract a recorded span because detail; the error envelope satisfies the fleet error contract (error-kind envelope, labeled, remediation hint present, non-zero exit) | [[acset.writer]] |
| apply_failure | effect | spec.apply_failure(detail) — an edit names an id no recorded span can realize because detail; the error envelope satisfies the fleet error contract (error-kind envelope, labeled, remediation hint present, non-zero exit) | [[acset.writer]] |
| roundtrip_failure | effect | spec.roundtrip_failure(detail) — the emitted text reparses to an instance other than the edit's target because detail; the error envelope satisfies the fleet error contract (error-kind envelope, labeled, remediation hint present, non-zero exit) | [[acset.writer]] |
Model
States
requestedspannedappliedverifiedspan_failed(emits:[[acset.writer.span_failure]])apply_failed(emits:[[acset.writer.apply_failure]])roundtrip_failed(emits:[[acset.writer.roundtrip_failure]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| record | requested | spanned | [[acset.writer.source_spans_recorded]] ∧ [[acset.writer.emit_identity]] |
| record_fail | requested | span_failed | ¬([[acset.writer.source_spans_recorded]] ∧ [[acset.writer.emit_identity]]) |
| apply | spanned | applied | [[acset.writer.untouched_bytes_preserved]] ∧ [[acset.writer.width_padding_policy]] ∧ [[acset.writer.filename_follows_intent_id]] ∧ [[acset.writer.write_set_atomic]] |
| apply_fail | spanned | apply_failed | ¬([[acset.writer.untouched_bytes_preserved]] ∧ [[acset.writer.width_padding_policy]] ∧ [[acset.writer.filename_follows_intent_id]] ∧ [[acset.writer.write_set_atomic]]) |
| reparse | applied | verified | [[acset.writer.edit_application_faithful]] ∧ [[acset.writer.edits_compose]] |
| reparse_fail | applied | roundtrip_failed | ¬([[acset.writer.edit_application_faithful]] ∧ [[acset.writer.edits_compose]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[acset.writer.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| span_covers_every_link | unit | [[acset.writer.source_spans_recorded]] | arbitrary_spec_file() | card(link_spans) == card(links(parse(x))) |
| identity_emission_exact | unit | [[acset.writer.emit_identity]] | arbitrary_accepted_spec_file() | emit(parse(x)) == x |
| crlf_survives_edit | unit | [[acset.writer.untouched_bytes_preserved]] | crlf_file_with_one_renamed_id() | line_terminators(apply(f,x)) == line_terminators(x) |
| prose_survives_edit | unit | [[acset.writer.untouched_bytes_preserved]] | row_whose_prose_mentions_the_old_id_as_a_word() | prose_bytes(apply(f,x)) == prose_bytes(x) |
| padding_not_realigned | unit | [[acset.writer.width_padding_policy]] | rename_that_lengthens_an_id_in_an_aligned_table() | padding_bytes(apply(f,x)) == padding_bytes(x) |
| intent_rename_moves_file | unit | [[acset.writer.filename_follows_intent_id]] | rename_of_a_files_own_intent_id() | path(apply(f,x)) == mapped_filename(new_id) and one removal is returned |
| writer_does_no_io | unit | [[acset.writer.write_set_atomic]] | apply_with_io_calls_instrumented() | io_calls == 0 |
| text_realizes_instance | unit | [[acset.writer.edit_application_faithful]] | arbitrary_spec_file(), arbitrary_id_rename() | from_specs(apply(f,x)) == rename(from_specs(x),a,b) |
| writer_edit_law | law | [[acset.writer.edits_compose]] | arbitrary_spec_file(), arbitrary_morphism_pair() | identity: apply(id, x) == x associativity: apply(g, apply(f, x)) == apply(compose(g, f), x) |
| span_failure_label_asserted | unit | [[acset.writer.span_failure]] | span_failure_raised() | error_label == "spec.span_failure" |
| apply_failure_label_asserted | unit | [[acset.writer.apply_failure]] | apply_failure_raised() | error_label == "spec.apply_failure" |
| roundtrip_failure_label_asserted | unit | [[acset.writer.roundtrip_failure]] | roundtrip_failure_raised() | error_label == "spec.roundtrip_failure" |
Requirements
Requirement: Span-preserving emission
The acset-writer SHALL emit markdown for an instance from recorded byte spans alone — never re-serializing a table — such that emission without an edit reproduces the source byte for byte, and an edit changes only the cells and links the edit names.
Scenario: Identity emission is byte-exact
- WHEN the writer emits text for a parsed spec file with no edit applied
- THEN the emitted text equals the source byte for byte, including prose, line endings, and table padding
Scenario: Spans cover every editable element
- WHEN a spec file is parsed
- THEN every id cell, every
[[link]]occurrence, and every state bullet carries a byte span the writer can rewrite
Scenario: Untouched bytes survive an edit
- WHEN an edit rewrites one id cell of a file whose prose mentions the old id as a word
- THEN the prose bytes, line terminators, and table padding outside the rewritten span are identical to the source
Requirement: Edits realize typed morphisms
The acset-writer SHALL apply an edit (a morphism between instances) to
source text by returning a write-set of (path, full new contents) plus
optional removals, performing no I/O itself — applying the write-set is
the caller’s single transaction.
Scenario: Text realizes the renamed instance
- WHEN a rename morphism f : I → I’ is applied to source text x
- THEN parsing the emitted text yields exactly I’
Scenario: Edits compose
- WHEN two edits f and g are applied in sequence to x
- THEN the result equals applying their composition in one step
Scenario: Intent rename moves the file
- WHEN an edit renames a file’s own Intent id
- THEN the write-set maps the file to the filename implied by the new id and returns one removal for the old path
Scenario: No realignment side effect
- WHEN an id edit changes a cell’s width in an aligned table
- THEN surrounding padding is left unchanged — the column may become unaligned, and realignment is a separate explicit operation
Scenario: Writer performs no I/O
- WHEN the writer applies an edit
- THEN it returns a write-set and performs no file operations itself
Requirement: Writer error contract
The acset-writer SHALL report its three failure modes — span extraction, edit application, and roundtrip verification — as labeled errors carrying remediation hints, surfaced through the consuming command’s envelope per the error contract.
Scenario: Span failure is labeled
- WHEN the writer cannot extract a recorded span it needs (e.g. the source changed under the parse)
- THEN the failure is labeled
acset.writer.span_failurewith a remediation hint
Scenario: Apply failure is labeled
- WHEN an edit names an id no recorded span can realize
- THEN the failure is labeled
acset.writer.apply_failurewith a remediation hint
Scenario: Roundtrip failure is labeled
- WHEN the emitted text reparses to an instance other than the edit’s target
- THEN the failure is labeled
acset.writer.roundtrip_failurewith a remediation hint
doctor — capability
id: doctor kind: intent statement: “THE doctor command SHALL classify the workspace mode and warn — never fail — when the binary’s embedded format knowledge lags the local corpus.”
doctor Specification
Purpose
Diagnose a workspace’s specodelic setup from inside the binary: classify the workspace as self-hosting or consumer, check the local corpus against the embedded format knowledge, and report findings over the standard envelope without failing on informational conditions.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[doctor]] |
| mode_detection | invariant | workspace classifies as self_hosting when openspec/specs/specodelic/spec.md exists and consumer otherwise, with the mode reported in envelope data | [[doctor]] |
| currency_warning | invariant | whenever a local corpus exists, the latest Revision N heading is compared numerically against FORMAT_REVISION; a lagging binary warns without failing; unreadable or revision-less corpora skip with an informational note | [[doctor]] |
Model
States
probedclassifiedcurrency_checked
Transitions
| id | from | to | guard |
|---|---|---|---|
| classify | probed | classified | [[doctor.mode_detection]] |
| currency | classified | currency_checked | [[doctor.currency_warning]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[doctor.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| p_mode | unit | [[doctor.mode_detection]] | arbitrary_workspace() | mode == expected(ws) ∧ mode ∈ envelope_data |
| p_currency | unit | [[doctor.currency_warning]] | arbitrary_corpus_state() | lag ⇒ warning ∧ ¬failure ∧ skip ⇒ note |
Requirements
Requirement: Workspace Mode Detection
spk doctor SHALL classify the workspace as self_hosting when
openspec/specs/specodelic/spec.md exists, and consumer otherwise, and SHALL
report the detected mode in its envelope data.
Scenario: Self-hosting corpus
- WHEN
spk doctorruns in a workspace containingopenspec/specs/specodelic/spec.md - THEN the envelope reports mode
self_hostingand keeps today’s checks (specs directory, core format spec, beads)
Scenario: Consumer workspace without the core spec
- WHEN
spk doctorruns in a workspace with aspecs/directory but noopenspec/specs/specodelic/spec.md - THEN the envelope reports mode
consumer, the check for the core spec does not fail the command, and the envelope reports the embedded guide’sformat_revisionas ok
Scenario: Empty workspace
- WHEN
spk doctorruns in a directory with nospecs/at all - THEN the envelope reports mode
consumerwith an informational note suggestingspk newto start a corpus, and exits 0
Requirement: Doctor Reports Knowledge Currency
spk doctor SHALL — in any mode, whenever a local corpus is present
(vendored consumer corpora included) — extract
the latest Revision N heading (numerically largest trailing integer)
from the local openspec/specs/specodelic/spec.md and SHALL emit a warning — not a
failure — when that revision is newer than the binary’s embedded
FORMAT_REVISION. A corpus with no Revision heading skips the
currency check with an informational note.
Scenario: Corpus file is unreadable
- WHEN the local
openspec/specs/specodelic/spec.mdexists but cannot be read (permissions, non-UTF-8 content) - THEN the currency check is skipped with an informational warning naming the read error, and the command neither fails nor claims the corpus is current
Scenario: Binary lags local corpus
- WHEN
spk doctorruns in a workspace whose vendoredopenspec/specs/specodelic/spec.mddeclaresRevision 8while the binary embedsRevision 7 - THEN the envelope contains a warning naming both revisions and exits 0
Scenario: Binary current
- WHEN
spk doctorruns in a workspace whose corpus revision equals the embeddedFORMAT_REVISION - THEN no knowledge-currency warning is emitted
Scenario: Corpus carries no revision headings
- WHEN
spk doctorruns in a workspace whoseopenspec/specs/specodelic/spec.mdcontains noRevision Nheading - THEN the currency check is skipped with an informational note and the command neither warns nor fails
embedded-guide — capability
id: embedded.guide kind: intent statement: “THE CLI SHALL serve an embedded, repository-free guide to the format through spk explain and keep that knowledge pinned to a corpus revision.”
embedded-guide Specification
Purpose
Make the format’s knowledge travel with the binary: spk explain
serves a distilled machine-facing guide embedded via include_str!,
the embedded knowledge is pinned to a corpus revision and guarded
against drift, and spk new scaffolds files that teach their own
tables.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[embedded.guide]] |
| explain_topics | invariant | spk explain serves exactly six topics — format, ears, kinds, references, lifecycle, lint-rules — over the standard envelope; unknown topics fail with a hint listing valid ones | [[embedded.guide]] |
| revision_pinned | invariant | FORMAT_REVISION names the mirrored corpus revision and appears in spk --version --json and every explain payload | [[embedded.guide]] |
| drift_guard | invariant | the test suite fails when FORMAT_REVISION lags the corpus's latest Revision N heading, compared numerically | [[embedded.guide]] |
| scaffold_teaches | invariant | spk new generates per-layer guidance comments that the linter ignores | [[embedded.guide]] |
Model
States
queriedresolvedrendered
Transitions
| id | from | to | guard |
|---|---|---|---|
| resolve | queried | resolved | [[embedded.guide.explain_topics]] |
| render | resolved | rendered | [[embedded.guide.revision_pinned]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[embedded.guide.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| p_topics | unit | [[embedded.guide.explain_topics]] | arbitrary_topic() | known ⇒ body_nonempty ∧ unknown ⇒ hint |
| p_revision | unit | [[embedded.guide.revision_pinned]] | arbitrary_version() | payload.contains(format_revision) |
| p_drift | unit | [[embedded.guide.drift_guard]] | stale_constant() | numeric(latest) > numeric(FORMAT_REVISION) ⇒ test_fails |
| p_scaffold | unit | [[embedded.guide.scaffold_teaches]] | arbitrary_new_file() | guidance_present ∧ lint(f) == zero_issues |
Requirements
Requirement: Embedded Format Guide Command
The CLI SHALL provide an explain subcommand that serves a distilled,
machine-facing guide to the Specodelic format from content embedded in
the binary (include_str!), requiring no repository access, and SHALL
emit it through the standard genesis JSON envelope (human-readable under
--human).
Scenario: Agent in a consumer repo asks for the format
- WHEN
spk explain formatruns in a directory with noopenspec/specs/specodelic/spec.mdon disk - THEN it exits 0 and the envelope
data.bodycontains the four-layer table and the lifecycle description
Scenario: Topic listing
- WHEN
spk explainruns with no topic argument - THEN it exits 0 and the envelope
data.topicslists exactly the topics enumerated in the Guide Topics requirement
Scenario: Unknown topic
- WHEN
spk explain noperuns - THEN it exits non-zero with an envelope failure whose hint lists the valid topics
Requirement: Guide Topics
The guide SHALL expose exactly six topics: format (the four layers
and the lifecycle), ears (the five EARS patterns), kinds (the closed
kind value sets for all five schema objects), references (the
Reference Typing table), lifecycle (the stage gates and checker
ownership), and lint-rules (every rule id the binary can emit, each
with a one-line semantics string).
Scenario: Closed kind sets rendered from enforced constants
- WHEN
spk explain kindsruns - THEN the rendered constraint/property
kindsets equal the constants the linter enforces, because both render from the samepub constsource inguide.rs
Scenario: Lint rule catalog is complete
- WHEN
spk explain lint-rulesruns - THEN every rule id the linter can emit appears in the catalog with a non-empty semantics string (unit-tested by iterating the linter’s rule table)
Requirement: Embedded Format Revision Pinning
The binary SHALL declare a FORMAT_REVISION constant naming the corpus
revision it mirrors (e.g. specodelic.md Revision 8); spk --version --json SHALL include it as format_revision and every explain
payload SHALL include it.
Scenario: Version reports format knowledge
- WHEN
spk --version --jsonruns - THEN the envelope contains
format_revision
Requirement: Guide-Drift Guard
The repo’s test suite SHALL fail if FORMAT_REVISION is stale relative
to the latest Revision N heading in openspec/specs/specodelic/spec.md, comparing
the trailing integers numerically, so binary knowledge cannot silently
lag the corpus.
Scenario: Corpus revision bumped without updating the constant
- WHEN
openspec/specs/specodelic/spec.mdgains a newerRevision Nmarker thanFORMAT_REVISIONand the test suite runs - THEN a unit test fails naming both revisions
Scenario: Double-digit revision ordering
- WHEN the corpus’s latest heading is
Revision 10andFORMAT_REVISIONnamesRevision 9 - THEN the comparison reports the corpus as newer (numeric, not lexicographic, comparison)
Requirement: Scaffold Teaches the Format
spk new SHALL generate a template whose each of the four layers
carries an HTML-comment guidance note (valid kinds for that layer’s
rows, what a guard may cite, law-case requirements for kind = "law"
properties), and the guidance SHALL be deletable without affecting lint
results, because the linter never parses prose.
Scenario: Scaffolded file explains its own tables
- WHEN
spk new order.cancelruns - THEN the generated file contains comment guidance under the Constraints, Model, and Properties sections
Scenario: Guidance does not affect linting
- WHEN a fully filled-in spec containing the scaffold guidance comments is linted
- THEN the guidance produces no findings
docs-site — capability
id: docs.site kind: intent statement: “THE docs system SHALL render the spec corpus and the command surface as an mdBook site deployed to GitHub Pages on every push to main.”
docs-site Specification
Purpose
Give specodelic the family-standard public docs: an mdBook that renders
the introduction, command reference, and the spec corpus itself (domain
specs plus dual-format capability specs), served at
charly-vibes.github.io/specodelic with an llms.txt for agents.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[docs.site]] |
| site_present | invariant | the repo carries an mdBook under docs/ with book.toml at the root, an index, and a command reference | [[docs.site]] |
| specs_in_book | invariant | SUMMARY.md links every domain spec file under specs/ and every dual-format capability spec under openspec/specs/; specs are copied at build time, never vendored | [[docs.site]] |
| pages_deployed | invariant | pushing to main builds the book and deploys it to GitHub Pages at /specodelic/ via docs.yml (upload-pages-artifact → deploy-pages) | [[docs.site]] |
| llms_txt_served | invariant | the site serves an llms.txt tool summary at its root, copied into the built book | [[docs.site]] |
Model
States
authoredbuiltdeployed
Transitions
| id | from | to | guard |
|---|---|---|---|
| docs_build | authored | built | [[docs.site.site_present]] |
| docs_deploy | built | deployed | [[docs.site.pages_deployed]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[docs.site.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| p_site | unit | [[docs.site.site_present]] | fresh_checkout() | docs-build() == ok ∧ index_html_exists |
| p_specs | unit | [[docs.site.specs_in_book]] | arbitrary_spec_file() | SUMMARY.md links f ⇒ book page for f resolves |
| p_deploy | unit | [[docs.site.pages_deployed]] | push_to_main() | pages_url == /specodelic/ ∧ book deployed |
| p_llms | unit | [[docs.site.llms_txt_served]] | site_fetch() | GET /llms.txt == 200 ∧ body non-empty |
Requirements
Requirement: mdBook docs site
The repo SHALL carry an mdBook site under docs/ — book.toml at the
repo root, SUMMARY.md, an introduction, and a command reference —
buildable locally via just docs-build, which copies the spec trees
into the book source, runs mdbook build docs, and places llms.txt
in the built book.
Scenario: Local build succeeds
- WHEN
just docs-buildruns in a fresh checkout with mdBook installed - THEN the build exits 0 and
docs/book/index.htmlexists
Requirement: Spec corpus rendered
The book SHALL render one page per domain spec file under specs/ and
one page per dual-format capability spec under openspec/specs/, with
SUMMARY.md linking every such page; the spec trees are copied at
build time and never vendored into docs/src/.
Scenario: Every corpus page reachable
- WHEN the book is built
- THEN every
specs/*.mdfile and everyopenspec/specs/*/spec.mdfile is linked fromSUMMARY.mdand renders as a book page
Requirement: GitHub Pages deployment
Pushing to main SHALL build the book and deploy it to GitHub Pages at
/specodelic/ through the docs.yml workflow — mdBook installed via
peaceiris/actions-mdbook@v2, artifact uploaded via
actions/upload-pages-artifact@v3, deployed via actions/deploy-pages@v4.
Scenario: Push deploys the site
- WHEN a commit is pushed to
main - THEN the workflow builds the book and deploys it, and the site is
served at
https://charly-vibes.github.io/specodelic/
Requirement: Agent summary served
The site SHALL serve an llms.txt summarizing the tool — what it is,
core concepts, command surface — at the site root, copied into the
built book by both the local recipe and the workflow.
Scenario: Agent fetches the summary
- WHEN
GET /llms.txtis issued against the deployed site - THEN the response is 200 with a non-empty body
hooks — capability
id: hooks kind: intent statement: “THE hooks step SHALL wire the specodelic dual-format gate into a repository’s hook chain as a marker-guarded managed block in its lefthook config, additively and idempotently, without ever claiming core.hooksPath or writing foreign hook files.”
hooks Specification
Purpose
Let spk hooks install wire spk lint openspec as a pre-commit
command so dual-format drift fails before the commit lands, while the
repo’s existing hook ownership (beads’ core.hooksPath, chained to
lefthook) keeps flowing untouched.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[hooks]] |
| chain_preserved | invariant | wiring is purely additive: existing commands, entries, and their order in the lefthook config are never dropped, reordered, or wrapped | [[hooks]] |
| no_foreign_writes | invariant | no install or uninstall path ever writes .git/hooks hook files, .beads/hooks hook files, or the core.hooksPath config | [[hooks]] |
| honest_anchor | invariant | a config whose stage or commands structure cannot be anchored is refused without modification, with a labeled error and a hint | [[hooks]] |
| idempotent_lifecycle | invariant | install on a wired repo and uninstall on an unwired repo are successful no-ops that leave the config byte-identical | [[hooks]] |
Model
States
unwiredwiredrefused
Transitions
| id | from | to | guard |
|---|---|---|---|
| install_ok | unwired | wired | lefthook config exists and the stage is anchorable — [[hooks.chain_preserved]] |
| reinstall_ok | wired | wired | the managed block is already present — [[hooks.idempotent_lifecycle]] |
| refuse | unwired | refused | no lefthook config, unsupported framework, or no anchor — [[hooks.honest_anchor]] |
| uninstall_ok | wired | unwired | the managed block markers are present — [[hooks.chain_preserved]] |
| uninstall_notwired | unwired | unwired | no managed block present — reported, not an error — [[hooks.idempotent_lifecycle]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[hooks.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| additive_wiring | unit | [[hooks.chain_preserved]] | fixture config with an existing pre-commit commands mapping | existing entries unchanged and lefthook parses the config |
| idempotent_install | unit | [[hooks.idempotent_lifecycle]] | install run twice on a fixture | config byte-identical after the second install |
| foreign_refused | unit | [[hooks.no_foreign_writes]] | fixture with a bd-owned pre-commit hook | .git/hooks and .beads/hooks hook files byte-identical after install |
| clean_uninstall | unit | [[hooks.idempotent_lifecycle]] | wired fixture, then uninstall | rest of the config byte-identical and the gate entry gone |
| unsupported_framework | unit | [[hooks.honest_anchor]] | fixture with a husky-sigil pre-commit | install errors labeling the framework and the tree is untouched |
Requirements
Requirement: spk hooks install wires the gate additively into the lefthook config
The system SHALL provide spk hooks install that injects a
marker-guarded managed block wiring the command spk lint openspec
into the pre-commit: stage of the repository’s lefthook config
(lefthook.yml or lefthook.yaml) — and when the stage already has a
commands: mapping, the entry SHALL be inserted inside that mapping
rather than as a second commands: key, with indentation inferred from
the stage’s own child entries (2 spaces for an empty stage), never
assumed.
Scenario: entry added inside an existing commands mapping
- WHEN the repo’s
lefthook.ymlhas apre-commit:stage whose section already contains acommands:mapping with other entries - AND
spk hooks installruns - THEN the gate entry is inserted inside the existing
commands:mapping as a marker-guarded block - AND the entry’s indentation matches the stage’s existing child entries
- AND the pre-existing entries and their order are unchanged
- AND lefthook still parses the config (no duplicate
commands:key is created)
Scenario: full wrapper injected when the stage has no commands
- WHEN the repo’s lefthook config has no
pre-commit:section, or the section has nocommands:mapping - AND
spk hooks installruns - THEN the full
commands:wrapper containing the marker-guarded gate entry is injected into the stage, formatted at the stage’s own children indentation
Requirement: spk hooks install is idempotent with a dry-run gate report
The install SHALL be idempotent, and it SHALL run the wired gate command once as a dry-run and report its outcome over the envelope — warning, never failing, when the gate would fail immediately.
Scenario: install is idempotent
- WHEN
spk hooks installruns on a repo whose config already contains the managed block - THEN the command succeeds reporting the already-wired outcome and the config is byte-identical
Scenario: gate that would fail immediately is a warning, not a trap
- WHEN
spk hooks installruns in a repo whose openspec tree does not yet pass the gate (for example, a change’s deltas whose sidecars are not yet written) - THEN the install succeeds and the envelope reports the dry-run outcome on the warnings channel
- AND the warning carries the failure summary and an
spk hooks uninstallescape hint
Requirement: hook wiring never claims foreign ownership
The system SHALL never claim core.hooksPath, never write hook files
under .git/hooks/ or .beads/hooks/, and never modify a hook file
owned by another tool — spk hooks install wires only the lefthook
config, so an existing chain such as beads’ .beads/hooks shims →
lefthook keeps flowing unchanged.
Scenario: beads-owned hook chain is left untouched
- WHEN
spk hooks installruns in a repo wherecore.hooksPathpoints at beads-owned hook shims that chain to lefthook - THEN the hook files under
.beads/hooks/and.git/hooks/are byte-identical after the run - AND the wiring landed only in the lefthook config
Requirement: unsupported framework or missing config is a labeled error with a hint
The system SHALL refuse to wire, with a labeled error and a
remediation hint and no file modification, when the repository has no
lefthook config (never creating one), when the detected hook framework
is unsupported (husky, prek), or when the target stage or its
commands: key cannot be anchored (quoted, indented, unrecognized
structure, or unascertainable children indentation).
Scenario: no lefthook config
- WHEN
spk hooks installruns in a repo with neitherlefthook.ymlnorlefthook.yaml - THEN the command fails with an error naming the missing config and a hint to create one first — no config is created
Scenario: unsupported framework
- WHEN
spk hooks installruns in a repo whose pre-commit hook carries the husky sigil and no lefthook config exists - THEN the command fails with an error naming the detected framework and a manual-wiring hint, and no file is modified
Scenario: unanchorable structure
- WHEN the lefthook config’s
commands:key inside the target stage is quoted or otherwise not anchorable, or the stage’s children indentation cannot be ascertained - THEN the command fails with an anchor error and the config file is unchanged
Requirement: spk hooks uninstall strips only the managed block
The system SHALL provide spk hooks uninstall that removes exactly
the lines between the managed-block markers (inclusive) from the
lefthook config — leaving the rest of the file byte-identical — and
SHALL report a successful no-op (not an error) when no block is
present. A stage section that install itself appended is left behind
as an empty stage section rather than removed.
Scenario: block removed, rest intact
- WHEN
spk hooks uninstallruns on a wired repo - THEN the gate entry and its markers are removed
- AND every other byte of the config is unchanged
- AND a stage section that install itself appended remains as an empty stage section (residue is documented, not hidden)
Scenario: unwired repo is a no-op
- WHEN
spk hooks uninstallruns on a repo with no managed block - THEN the command succeeds reporting the not-wired outcome and the config is unchanged
kernel — capability
id: kernel kind: intent statement: “WHEN a corpus declares kernel expressions, THE toolchain SHALL evaluate them as a decidable bounded logic over the acset instances I(k) with every v0 atomic grounded in existing proven machinery, SHALL report every evaluation with an explicit three-valued status whose unknown propagates honestly and never coerces to pass, and SHALL register no new atomic that is not decidable over finite instances.”
kernel Specification
Purpose
Give the corpus’s data-dependent cells — the equational and
bounded-quantified ones that are prose-only today — defined, decidable
semantics: a closed kernel grammar over the finite instances I(k)
supplied by the deployed acset substrate, a Kleene three-valued status
chain that keeps unknown honest, per-atomic grounding in proven
machinery so kernel semantics never outruns what is proven, a widening
law that gates future atomics (including pack-defined predicates) on
decidability, and an opaque binding column that lets external checkers
claim constraints without specodelic learning their language.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[kernel]] |
| kernel_grammar_closed | invariant | a kernel expression's atomics come from the closed set — equality, comparisons, bounded universal and existential quantification over I(k), conjunction, negation, and the reference-typed atomics resolves, unique, acyclic, reachable — an expression using an atomic outside the closed set is a labeled failure naming the atomic and the closed set, never prose and never silently ignored | [[kernel]] |
| kernel_decidable | invariant | every kernel expression is decidable over the finite instances I(k) — evaluation is total, terminates, and yields exactly one of verified, counterexample, unknown — an addition to the closed set that fails decidability over finite instances cannot register | [[kernel]] |
| kernel_status_three_valued | invariant | every kernel evaluation reports an explicit three-valued status — verified, counterexample, unknown — unknown is honest (the claim could not be discharged) and propagates under Kleene rules; no evaluation path coerces unknown to pass or to counterexample | [[kernel]] |
| kernel_grounded_in_machinery | invariant | each v0 atomic's semantics is grounded in existing proven machinery — acyclic and reachable on graph traversal, unique and resolves on acset traversal, equality, comparisons, and bounded quantifiers on model_check bounded evaluation — an atomic without a grounding entry cannot ship | [[kernel]] |
| kernel_binding_opaque | invariant | the per-constraint kernel.binding cell is an opaque claim carrier — the toolchain extracts and surfaces its contents verbatim and never interprets them; external checkers claim constraints through contract-TOML flags without the format learning their language; no registry is built | [[kernel]] |
Model
States
expr_absentexpr_evaluatedexpr_unknown
Transitions
| id | from | to | guard |
|---|---|---|---|
| declare | expr_absent | expr_evaluated | an expr cell opts into kernel translation per [[kernel.kernel_grammar_closed]] |
| discharge | expr_evaluated | expr_unknown | an evaluation cannot discharge its claim per [[kernel.kernel_status_three_valued]] |
| widen | expr_evaluated | expr_evaluated | a new atomic registers per [[kernel.kernel_decidable]] with a grounding entry |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[kernel.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| unknown_propagates | unit | [[kernel.kernel_status_three_valued]] | expression_with_undischargable_subclaim() | composite status is unknown and no coercion to pass or counterexample occurs |
| acyclic_decides | unit | [[kernel.kernel_grounded_in_machinery]] | instance_with_cycle_fixtures() | acyclic atomic returns counterexample on a cyclic instance, verified on an acyclic one |
| quantifier_bounded | unit | [[kernel.kernel_decidable]] | large_finite_instance() | bounded quantifier evaluation terminates with exactly one status |
| unknown_atomic_fails | unit | [[kernel.kernel_grammar_closed]] | expression_with_nonmember_atomic() | grammar rejects labeled, naming the atomic and the closed set |
| binding_never_interpreted | unit | [[kernel.kernel_binding_opaque]] | constraint_with_arbitrary_binding_text() | binding cell contents surface verbatim; no parse, no interpretation |
Requirements
Requirement: Decidable bounded kernel grammar
The kernel SHALL accept expressions built from the closed atomic set —
equality, comparisons, bounded ∀/∃ over the finite instances I(k),
∧/¬, and the reference-typed atomics resolves, unique, acyclic,
reachable — SHALL reject an expression using an atomic outside the
closed set with a labeled failure naming the atomic and the closed set,
and SHALL evaluate every accepted expression totally over finite
instances.
Scenario: Closed atomic set accepts and rejects
-
WHEN an expr cell carries a kernel expression over the closed atomic set
-
THEN it parses and evaluates to exactly one status
-
WHEN an expr cell carries an expression using a non-member atomic
-
THEN the grammar rejects labeled, naming the atomic and the closed set — never prose, never silently ignored
Scenario: Evaluation is total and terminating
- WHEN a kernel expression evaluates over any finite instance
I(k) - THEN evaluation terminates with exactly one of verified, counterexample, unknown
Requirement: Three-valued evaluation status
Every kernel evaluation SHALL report an explicit three-valued status —
verified, counterexample, unknown — where unknown is honest
(the claim could not be discharged) and propagates under Kleene rules;
no evaluation path SHALL coerce unknown to pass or to
counterexample.
Scenario: Honest unknown propagates
- WHEN a composite claim contains an undischargable subclaim
- THEN the composite status is unknown and no coercion to pass or counterexample occurs
Scenario: Verified and counterexample remain first-class
- WHEN a claim is discharged or refuted over the finite instances
- THEN the status is verified or counterexample respectively, persisted in run output
Requirement: Semantics grounded in proven machinery
Each v0 atomic SHALL name its grounding machinery — acyclic and
reachable on graph traversal, unique and resolves on acset
traversal, equality, comparisons, and bounded quantifiers on
model_check bounded evaluation — and an atomic without a grounding
entry SHALL NOT ship, so kernel semantics never outruns proven
machinery.
Scenario: Per-atomic grounding holds
- WHEN the v0 atomics evaluate against fixtures derived from their grounding machinery
- THEN each atomic’s result agrees with the machinery’s result on the same instance
Scenario: Ungrounded atomic cannot ship
- WHEN a proposed atomic has no grounding entry
- THEN it is not part of the closed set and its use fails labeled
Requirement: Widening law with a decidability gate
A new atomic SHALL register only if it is decidable over finite instances, and pack-defined predicates SHALL register under the same gate; a predicate that fails the gate stays pack-side, checked by contract-TOML runners rather than by the kernel.
Scenario: Decidable addition widens purely
- WHEN a new decidable atomic registers with a grounding entry
- THEN every expression valid before the registration evaluates identically after it
Scenario: Undecidable predicate stays pack-side
- WHEN a pack defines a predicate that is not decidable over finite instances
- THEN it does not register in the closed set and remains checked by contract-TOML runners
Requirement: Opaque binding column
The per-constraint kernel.binding cell SHALL be an opaque claim
carrier — extracted and surfaced verbatim, never interpreted by the
toolchain — and external checkers SHALL claim constraints through
contract-TOML flags without the format learning their language; no
registry SHALL be built.
Scenario: Binding surfaces verbatim
- WHEN a constraint carries an arbitrary
kernel.bindingtext - THEN the contents surface verbatim; no parse, no interpretation, no validation of their internals
Scenario: External checkers claim through flags
- WHEN an external checker claims a constraint
- THEN the claim is carried by contract-TOML
flagsbinding (node id,-k, or[[tests.shell]]) and the format learns nothing of the checker’s language
lint-findings — capability
id: lint.findings kind: intent statement: “THE linter SHALL make every finding self-describing by carrying its stable rule id and a one-line rule semantics string in both JSON and human output.”
lint-findings Specification
Purpose
Turn each lint finding into its own documentation: a stable
linter.<name> rule id plus a one-line semantics string, present in
the JSON envelope and the human-readable output, so agents and humans
can act on a finding without repository access.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[lint.findings]] |
| self_describing | invariant | every finding carries rule_id linter.<name> and a non-empty one-line rule_semantics sourced from the rule table, in JSON and human output | [[lint.findings]] |
Model
States
foundemitted
Transitions
| id | from | to | guard |
|---|---|---|---|
| emit | found | emitted | [[lint.findings.self_describing]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[lint.findings.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| p_finding | unit | [[lint.findings.self_describing]] | arbitrary_finding() | finding.rule_id stable ∧ semantics non-empty in both output modes |
Requirements
Requirement: Self-Describing Lint Findings
Every lint finding SHALL carry a rule_id (the stable
linter.<name> identifier) and a one-line rule_semantics string
stating what the rule requires, in both the JSON envelope and the
human-readable output, so the finding is documentation for itself.
Skipped-file notices (e.g. non-spec markdown without frontmatter) are
emitted on the envelope’s warnings channel and are out of scope for
this requirement.
Scenario: Agent reads a finding without repo access
- WHEN
spk lint bad-spec.md --format jsonruns on a file whosestatementmatches no EARS pattern - THEN the finding’s
rule_idislinter.ears_syntaxandrule_semanticsis a non-empty one-line description of the EARS requirement
Scenario: Human output shows the rule id
- WHEN
spk lint bad-spec.md --humanruns on an invalid file - THEN each printed finding includes its
rule_id
model-check — capability
id: model.check kind: intent statement: “WHEN a compiled artifact carries guard citations or kernel expressions, THE model-check backend SHALL evaluate guard citations under the citation algebra with an explicit three-valued status, SHALL keep executable guard fragments rejected of record, and SHALL persist run reports naming backend, bound, and per-invariant status with unknown persisted honestly.”
model-check Specification
Purpose
Upgrade guard citations from inert TLA+ annotations to kernel-evaluated claims — slice 1, the one-green-where-today-is-red increment — while keeping the fragment_guard_rejected decision of record intact (executable guard fragments stay rejected; the deferral’s data-carrying-state-space condition is not met by instance-grounded expressions). Run reports persist backend, bound, and per-invariant three-valued status, with the backend-agreement property running as a CI property test over shared fixtures until the py backend lands.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[model.check]] |
| guard_citation_evaluated | invariant | an invariant-kind Constraint's guard citations resolve against same-run invariant outcomes and compose under the citation algebra — conjunction and negation with an explicit three-valued status — the evaluated citation is an annotation upgrade and never a sole verdict unless executed | [[model.check]] |
| fragment_guard_rejected_stands | invariant | a Transition's guard cell carrying a rust: fragment still fails labeled — the deferral of record (deferred to a future Revision alongside a data-carrying state space) is not met by instance-grounded expressions, so the decision of record stands unchanged | [[model.check]] |
| run_report_status | invariant | a model-check run persists backend, bound, and per-invariant status — verified, counterexample, or unknown — unknown is persisted honestly and never coerced, and a run whose invariants were not executed remains exploration_only and explicitly not clean | [[model.check]] |
| backend_agreement_ci | invariant | the same fixture corpus evaluates through every deployed backend with identical three-valued status per cell, asserted by a CI property test — the rust-only interim asserts rust status equals the fixture's expected status, and the cross-backend assertion activates with the py backend without being disabled | [[model.check]] |
| command_claims | invariant | every accepted kernel or citation invariant has one canonical status in CLI output and persisted reports over the explicit corpus | [[model.check]] |
Model
States
citation_inertcitation_evaluatedagreement_active
Transitions
| id | from | to | guard |
|---|---|---|---|
| upgrade | citation_inert | citation_evaluated | citation algebra lands per [[model.check.guard_citation_evaluated]]: citations resolve and compose with three-valued status |
| activate | citation_evaluated | agreement_active | the py backend lands (l8l): the cross-backend assertion activates per [[model.check.backend_agreement_ci]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[model.check.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| citation_composes | unit | [[model.check.guard_citation_evaluated]] | artifact_with_composite_citation() | citation resolves and composes with an explicit three-valued status |
| undischargable_is_unknown | unit | [[model.check.guard_citation_evaluated]] | artifact_with_undischargable_citation() | citation reports unknown, never pass |
| guard_fragment_still_rejected | unit | [[model.check.fragment_guard_rejected_stands]] | transition_row_with_fragment_guard() | compile fails labeled (fragment_extraction stage), decision of record intact |
| report_names_status | unit | [[model.check.run_report_status]] | run_with_mixed_invariant_status() | report persists backend, bound, and per-invariant status with unknown honest |
| fixture_status_agrees | unit | [[model.check.backend_agreement_ci]] | shared_fixture_corpus() | every deployed backend yields identical status per cell on shared fixtures |
| command_claims_checked | unit | [[model.check.command_claims]] | mixed_kernel_and_citation_cli_corpus() | reported claim IDs and statuses equal expected values in CLI and persisted JSON |
Requirements
Requirement: Guard citation semantics evaluated
Guard citations in invariant-kind Constraints SHALL resolve against
same-run invariant outcomes and compose under the citation algebra (conjunction
and negation) with an explicit three-valued status; the evaluated
citation remains an annotation upgrade and never a sole verdict unless
executed. Executable guard fragments SHALL remain rejected of record —
the fragment_guard_rejected deferral’s condition (a data-carrying
state space) is not met by instance-grounded expressions.
Scenario: Citation algebra evaluates
- WHEN an artifact’s invariant Constraint cites
[[a]] ∧ [[b]]or a negated citation - THEN the citation resolves against same-run invariant outcomes and composes with an explicit three-valued status
Scenario: Undischargable citation is honest
- WHEN a citation cannot be discharged over the artifact’s instances
- THEN it reports unknown, never pass
Scenario: Executable guard fragments stay rejected
- WHEN a Transition’s guard cell carries a
rust:fragment - THEN compile fails labeled (fragment_extraction stage) — the decision of record stands unchanged
Requirement: Backend agreement property
The same fixture corpus SHALL evaluate through every deployed backend with identical three-valued status per cell, asserted by a CI property test: the rust-only interim asserts rust status equals the fixture’s expected status, and the cross-backend assertion activates with the py backend without being disabled or commented out.
Scenario: Rust-only interim holds
- WHEN the shared fixture corpus evaluates through the rust backend
- THEN every cell’s status equals the fixture’s expected status
Scenario: Cross-backend agreement activates with the py backend
- WHEN the py backend lands (l8l) and the shared fixtures evaluate through both backends
- THEN the CI property asserts identical status per cell across backends
Requirement: Status-bearing run reports
A model-check run SHALL persist backend, bound, and per-invariant
status — verified, counterexample, or unknown — with unknown
persisted honestly and never coerced; a run whose invariants were not
executed remains exploration_only and is explicitly not clean.
Scenario: Report names its status
- WHEN a run evaluates invariants with mixed outcomes
- THEN the report persists backend, bound, and per-invariant status, including honest unknown
Scenario: Unexecuted invariants are not clean
- WHEN a run compiles invariants but executes none
- THEN the outcome is exploration_only and explicitly not clean
Requirement: Complete command-path claim evaluation
The model-check command and orchestrator SHALL evaluate every accepted kernel invariant over the complete explicit input corpus and SHALL publish one status per opted-in kernel or citation invariant in both CLI output and persisted reports. This requirement supplies statuses; aggregate acceptance is specified by the separate verification-claims capability. Report freshness follows verification-claims.
Scenario: Mixed inputs cannot hide a false kernel claim
- WHEN a two-state corpus contains a kernel claim asserting 999 states and a passing Rust invariant
- THEN both claim IDs appear in CLI and persisted output and the kernel claim is counterexample
Scenario: Property execution is not invented
- WHEN a citation targets a Property row without same-run invariant evidence
- THEN its status is unknown and model-check does not invoke a property runner
Requirement: Citation resolution is file-scoped and dependency-ordered
Qualified citations SHALL resolve exact invariant IDs; bare citations SHALL resolve within the citing file. Citation chains SHALL resolve in dependency order. Cycles, missing targets, and property rows without same-run evidence SHALL report unknown with a reason. An incapable backend SHALL report labeled unsupported claims, never omit them. Single-file spec.row citations SHALL resolve locally; multi-file dual-format lint SHALL remain valid.
Scenario: Qualified and bare citations agree locally
- WHEN a local passing invariant is cited by its local name and its qualified name
- THEN both citations evaluate to verified, without confusing an identically named invariant in another file
Scenario: Scope and dependency order are explicit
- WHEN the same multi-file corpus with a citation chain is supplied in either file order
- THEN the same statuses result from the full corpus and a cycle or absent target remains unknown with a reason
Requirement: Dual-format files run under an isolated scope
Ordinary corpus intent IDs SHALL be unique; duplicates SHALL fail as duplicate_corpus_identity. Dual-format id: spec files SHALL retain local identity and SHALL run only as the sole parsed input to model-check, verify or orchestrate. Combined scopes SHALL fail as isolated_scope_required before compilation, evaluation or report writes, with a hint to run each file separately using separate artifact/report directories.
Scenario: File-local identities do not merge into a corpus
- WHEN two parsed inputs claim the same intent id (the transitional pair: an active change’s delta and its deployed capability spec), or distinct real-id files compose
- THEN the same-identity combined command evaluation refuses the scope as duplicate_corpus_identity before writes; files with distinct ids run independently with their own outcomes and multi-file lint keeps accepting them
Requirement: Model check runs a real backend against the compiled artifact
The system SHALL provide a spk model-check <files> step that runs a
model-check backend — stateright (embedded, default) — against the
compiled model artifact of each file, without re-compiling, and SHALL
report the outcome over the envelope (data.outcome), where a missing
compiled artifact is a labeled ERROR with a spk compile hint, never a
silent success.
Scenario: Clean spec within bound
- WHEN
spk model-check <file> --jsonruns on a linted, covered, compiled spec whose model explores exhaustively within the stated bound, at least one executable invariant is checked, and none is violated - THEN the envelope reports
.data.outcome == "no_counterexample"with the backend engine and version and the stated bound restated
Scenario: Missing compiled artifact is an error
- WHEN
spk model-check <file>runs on a spec with no compiled artifact present - THEN the command fails with a labeled error naming the missing
artifact and a
spk compileremediation hint, and never reportsno_counterexample
Requirement: The run names its backend, bound, and checked invariants
The system SHALL include in every run report the backend engine and
version, the applied bound (max depth, and optional state count and time
budget), and the set of invariants actually checked — so a report where
no user invariant was executable states invariants_checked as empty
rather than implying a semantic check that never ran.
Scenario: Prose invariants are not fabricated
- WHEN a spec’s Constraints table contains invariant rows whose expressions are prose with no executable opt-in
- THEN a native-backend run reports
exploration_onlyafter exhaustive automaton exploration within the bound, withinvariants_checkedempty — never a fabricated counterexample or an implied semantic check
Scenario: Budget exhaustion is a distinct outcome
- WHEN a run’s state/time budget expires before exhaustive exploration completes
- THEN the report outcome is
timed_out— never collapsed intono_counterexampleorcounterexample_found
Requirement: Run reports persist with artifact provenance
The system SHALL write each run’s report as <stem>.check.json next to
the compiled artifacts, carrying the SHA-256 of the compiled .tla
module the run consumed, so downstream verify can determine whether a
stored clean result still applies to the current compiled artifact.
Scenario: Stale clean result is detectable
- WHEN a stored clean report’s artifact hash differs from the hash
of the current compiled
.tla(the model changed since the run) - THEN the stored result no longer satisfies
no_counterexample— the report is stale until a re-run against the current artifact
spec-integration — capability
id: spec.integration kind: intent statement: “WHEN a specodelic corpus lives under openspec, THE spec-integration capability SHALL keep change deltas plain openspec files with their specodelic layer in a sibling layer.md sidecar, SHALL keep deployed capability specs simultaneously openspec-valid and lint-clean with no delta sections and no mirror, and SHALL refuse a true same-id pair on command paths — so a repo needs exactly one spec vocabulary.”
spec-integration Specification
Purpose
Widen the dual-format mirror rules to ## MODIFIED Requirements so the
repo’s first MODIFIED-carrying delta is gated exactly like an
ADDED-carrying one — the widening is additive; no ADDED-side behavior
changes.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[spec.integration]] |
| plain_deltas_carried | invariant | a change's delta file under openspec/changes/ is a plain openspec delta — openspec validate is its gate; it carries no frontmatter, no specodelic tables, and no ## Requirements mirror — with the specodelic layer in a sibling layer.md sidecar (Revision 19: a linted specodelic file whose id derives from the parent directory plus a .layer suffix) | [[spec.integration]] |
| deployed_shape_closed | invariant | a deployed capability spec carries no delta sections and no ## Requirements mirror — the retired dual shape is unwritable: openspec 1.x strict validation fails it and the companion's post-attach shape assertion refuses the deploy, so no per-file lint pairing check remains | [[spec.integration]] |
| both_parsers_accept | invariant | every deployed capability spec is simultaneously a valid openspec capability spec and a lint-clean specodelic spec file (frontmatter plus Constraints, Model, and Properties sections) carrying its real naming-law id — a spec.md file takes its id from its parent directory name (-⇔.) | [[spec.integration]] |
| duplicate_corpus_identity | invariant | the intent ids of a command's parsed inputs are unique — command paths (model-check, verify, orchestrate) refuse a true same-id pair with duplicate_corpus_identity before any write; lint accepts files with distinct ids together (uniqueness is per-file) | [[spec.integration]] |
Model
States
plain_deltadeployedgated
Transitions
| id | from | to | guard |
|---|---|---|---|
| deploy_pair | plain_delta | deployed | [[spec.integration.plain_deltas_carried]] ∧ [[spec.integration.both_parsers_accept]] |
| gate | deployed | gated | [[spec.integration.deployed_shape_closed]] ∧ [[spec.integration.duplicate_corpus_identity]] |
Properties
| id | kind | derives_from | generator | predicate |
|————————|——|———————————–|–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|
| process_lifecycle_checked | unit | [[spec.integration.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| plain_delta_gate_green | unit | [[spec.integration.plain_deltas_carried]] | plain_delta_with_sidecar_pair() | openspec validate passes the delta; spk lint passes the sidecar; the delta carries no frontmatter or tables |
| deployed_shape_enforced | unit | [[spec.integration.deployed_shape_closed]]| assembly_with_delta_header() | openspec 1.x strict fails the retired shape; the companion refuses it pre-write |
| both_parsers_green | unit | [[spec.integration.both_parsers_accept]] | deployed_capability_spec() | openspec validate --specs --strict and spk lint both exit 0 with zero issues |
| same_id_pair_refused | unit | [[spec.integration.duplicate_corpus_identity]] | sidecar_and_deployed_pair() | command paths refuse a true same-id pair with duplicate_corpus_identity; lint accepts the distinct pair |
Requirements
Requirement: Dual-format delta
Every deployed capability spec under openspec/specs/<cap>/spec.md
SHALL be simultaneously a valid openspec capability spec and a lint-
clean specodelic spec file (frontmatter plus Constraints, Model, and
Properties sections) carrying its real naming-law id — a spec.md file
takes its id from its parent directory name — and SHALL carry no delta
sections and no ## Requirements mirror: with change deltas plain
(Revision 19), the mirror pairing rules retire by construction.
Scenario: Both parsers accept
- WHEN
openspec validate --specs --strictandspk lint <file>run on any deployed capability spec - THEN both exit 0 with zero issues
Scenario: No delta sections reach the deployed spec
- WHEN the archive-companion deploys a change for capability
c - THEN
openspec/specs/c/spec.mdcarries no## ADDED/MODIFIED/REMOVED Requirementsheaders and no## Requirementsmirror — only the single## Requirementssection openspec applied
Scenario: Modified delta is dual-format
- WHEN a deployed capability file carries a delta section or a
## Requirementsmirror (the retired dual shape) - THEN openspec 1.x strict validation fails and the companion’s post-attach shape assertion refuses the deploy — the shape is unwritable, so no per-file lint pairing check remains
Requirement: Change deltas are plain openspec deltas
A change’s delta file under openspec/changes/ SHALL be a plain openspec
delta — openspec validate is its gate; it SHALL carry no frontmatter and
no specodelic tables, and no ## Requirements mirror — with the
specodelic layer carried by a sibling layer.md sidecar (Revision 19: a
linted specodelic file whose id derives from the parent directory plus a
.layer suffix), so a repo needs exactly one spec vocabulary.
Scenario: Coexisting identical ids
- WHEN two specodelic files claiming the same intent id (the pair: an
active change’s sidecar and its deployed capability spec — distinct via
the
.layersuffix) are linted together - THEN the lint reports no unique-id violation (uniqueness is per-file);
command paths (model-check, verify, orchestrate) refuse a true same-id
pair with
duplicate_corpus_identity
Scenario: Sidecar rides with the delta
- WHEN a change delta under
openspec/changes/<id>/specs/<cap>/is authored - THEN the delta carries
## ADDED/MODIFIED/REMOVED Requirementsonly and the siblinglayer.mdcarries the frontmatter and tables;spk lint openspeclints the sidecar and skips the frontmatterless delta
verification-claims — capability
id: verification.claims kind: intent statement: “THE verifier SHALL accept only complete current evidence for every required invariant claim.”
Verification claims
Purpose
THE verifier SHALL accept only complete current evidence for every required invariant claim.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[verification.claims]] |
| required_set | invariant | required claims come from explicit executable or kernel or citation invariant opt-ins | [[verification.claims]] |
| aggregate | invariant | nonempty required claims must all verify and property blocks must all pass | [[verification.claims]] |
| freshness | invariant | reports bind a versioned claim set to current structured scope and artifacts | [[verification.claims]] |
| visible_scope | invariant | all report views and release docs state the same assurance scope | [[verification.claims]] |
Model
States
- proposed
- approved
- implemented
- deployed
Transitions
| id | from | to | guard |
|---|---|---|---|
| approve | proposed | approved | [[verification.claims.process_lifecycle]] ∧ maintainer approves this proposal |
| implement | approved | implemented | [[verification.claims.process_lifecycle]] ∧ every constraint has passing behavioral evidence |
| deploy | implemented | deployed | [[verification.claims.process_lifecycle]] ∧ required gates pass and the companion re-attachment preserves every requirement |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[verification.claims.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| required_set_checked | unit | [[verification.claims.required_set]] | mixed_claim_corpus() | required and unchecked sets equal the expected IDs |
| aggregate_checked | unit | [[verification.claims.aggregate]] | mixed_claim_statuses() | false and unknown claims cannot yield verified |
| freshness_checked | unit | [[verification.claims.freshness]] | changed_scope_or_legacy_report() | verify rejects stale or incomplete evidence with a rerun hint |
| visible_scope_checked | unit | [[verification.claims.visible_scope]] | report_and_docs_fixture() | claim counts agree and pending features are not advertised as implemented |
Requirements
Requirement: Explicit required claim set
The toolchain SHALL derive the required claim set from invariant Constraints opted into Rust, kernel, or citation evaluation. Prose-only invariant claims SHALL be listed as unchecked and SHALL NOT be counted as verified.
Scenario: Prose is not executable evidence
- WHEN a file contains only prose invariant Constraints
- THEN its required set is empty, its unchecked set lists those Constraints, and verify cannot report verified
Requirement: All required claims govern acceptance
A nonempty complete required set SHALL contain only verified claims before model-check reports no_counterexample. A refuted claim SHALL yield counterexample_found; absent refutation, exhausted bounds SHALL yield timed_out; otherwise unknown, unsupported, or missing evidence SHALL prevent clean acceptance. Verify SHALL also require every generated property block to complete and pass, and SHALL reject invalid or duplicate claim records.
Scenario: Passing Rust does not hide a false kernel invariant
- WHEN one required Rust claim is verified and a kernel claim is counterexample
- THEN the aggregate is counterexample_found and verify exits 1 with the false claim ID
Scenario: Unknown and false citations block success
- WHEN a required citation is unknown or counterexample alongside a passing executable claim
- THEN verify exits 1 with the citation ID and reason, never verified
Scenario: Complete evidence passes both gates
- WHEN every required claim is verified within the bound and every generated property block passes
- THEN verify reports verified with the exact required and unchecked claim sets
Requirement: Evidence is versioned and scope bound
A stored report SHALL declare claim schema version 1, canonical expected claim IDs, actual statuses, and a deterministic digest of the structured corpus and consumed artifacts. Verify SHALL recompute scope and expected claims from current inputs. The digest SHALL bind structured content, not filesystem paths, so opposite claims with identical names cannot reuse each other’s evidence.
Scenario: Cross-file changes invalidate a clean report
- WHEN a referenced file changes structurally or is omitted from the supplied corpus after the report was produced
- THEN verify rejects the report as stale or scope-mismatched and names the rerun command
Requirement: Stale evidence is refused with a rerun hint
Stale evidence SHALL be refused with a rerun hint: unsupported or old reports, omitted or duplicate records, changed contributing inputs, or artifact mismatch SHALL fail with a rerun hint. Reordering CLI paths or changing prose alone SHALL preserve scope.
Scenario: Old reports cannot acquire new evidence implicitly
- WHEN a stored report has no claim schema version or required records
- THEN verify rejects it with a model-check rerun hint without modifying the report
Requirement: Intent ids of parsed inputs are unique
The intent IDs of a command’s parsed inputs SHALL be unique: a combined
invocation whose inputs claim the same intent id SHALL fail with
duplicate_corpus_identity and a rename hint before compilation,
evaluation or report writes (Revision 18: dual-format files carry real
ids and compose like any corpus — the former id: spec isolated-scope
rule retired with id: spec itself).
Scenario: A same-id pair is refused before any writes
- WHEN two parsed inputs claim the same intent id (the transitional pair: an active change’s delta and its deployed capability spec)
- THEN model-check, verify and orchestrate refuse the combined scope as duplicate_corpus_identity before writing artifacts or reports, with rename guidance; independent runs in separate directories retain each claim’s own status, produce different scope digests and reject swapped reports, while lint accepts files with distinct ids together
Requirement: Assurance scope is visible
CLI JSON, human output, and persisted reports SHALL agree on evaluated, unchecked, and blocking claims. Release documentation SHALL distinguish structural lint, bounded model exploration, verified declared claims, and external application-test binding, and SHALL identify pending capabilities without presenting them as implemented.
Scenario: Report views agree
- WHEN a run contains a required unknown claim and a prose-only invariant
- THEN JSON, human output, and the saved report identify the same blocker and unchecked claim
Scenario: Capability and release metadata stay current
- WHEN the documentation is built for a release
- THEN its version derives from Cargo metadata and its examples distinguish implemented behavior from pending proposals
error-contract — capability
id: error.contract kind: intent statement: “WHEN a specodelic tool stage fails, THE error-contract capability SHALL ensure the failure is reported as a single file-id-namespaced labeled error with a remediation hint, backed by an emitting failure state and a falsifying property asserting the exact label.”
error-contract Specification
Purpose
Make errors in the specodelic corpus typed, namespaced, and falsifiable
facts instead of CHANGELOG lore and mute failed states: the cross-cutting
output contract (envelope kind, exit codes, one labeled failure per stage,
remediation hints) becomes a published extension_point contract, every
tool file’s failure terminal emits a file-owned labeled error Constraint,
and every label carries a unit property asserting the exact label — the
same red→green discipline every other corpus fact already has.
Scope note: this capability spans two altitudes, kept distinct throughout
— lint-time corpus rules (failure states, guards: about the content of
specs/*.md files) and the runtime output contract (envelope, exit
codes: about tool behavior). “Emits” names the format mechanism; runtime
reporting is “reports”. Enforcement routing is itself specced:
enforcement_routed below states which tier enforces each rule today,
which waits for linter-failure_shape, and which rides the compile/verify
pipeline.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[error.contract]] |
| error_expr_shape | invariant | every error Constraint's expr is a file-id-namespaced variant head: <owning-file-id>.<variant_head>(field, …) — the label names its owning file, so label uniqueness is a per-file property and cross-file collisions are structurally impossible (pattern-checking the structured expr field follows the ears_statement precedent — expr is not rationale; prose_untouched is untouched) | [[error.contract]] |
| failure_state_emits | invariant | every failure terminal state in a tool file's Model carries an emits edge to an effect Constraint owned by that file, whose expr satisfies error_expr_shape — a failure state emits nothing is a malformed model (phase-2 terminals timed_out / exploration_only are excluded in v1) | [[error.contract]] |
| failure_class_is_state | invariant | a state with ≥2 inbound failure transitions whose guard citation sets differ is malformed — each distinct failure class is its own failure state; decidable from the graph alone (compile.md's two-class prose argument is the rationale, never the check — prose is not read) | [[error.contract]] |
| guard_negation_typed | invariant | a failure transition whose guard cites ≥1 [[id]] must cite exactly the citation set of the success transition(s) it negates — a negated disjunction cites the union of its branches' citation sets; a failure transition citing no ids at all is either on the recorded carve-out list (orchestrate.md's stage pipeline, per its own Notes) or malformed — the carve-out is a checked list in linter-failure_shape.md, never a silent assumption | [[error.contract]] |
| single_labeled_failure | invariant | a failing tool stage reports exactly one labeled error naming the failing stage and class — never a silent partial result and never an unlabeled failure (restates compile_is_total at contract altitude) | [[error.contract]] |
| envelope_error_kind | invariant | a failing stage's report is an error-kind envelope with ok == false — the ok:false ↔ "error" correspondence is the published contract row, not CHANGELOG lore | [[error.contract]] |
| exit_code_mapping | invariant | the output contract maps outcomes to exit codes: 0 for a clean run, 1 for findings-or-failure, 2 for invocation error — the mapping is part of the published contract, not per-tool convention | [[error.contract]] |
| remediation_hint_present | invariant | every error report carries a non-empty remediation hint (one row per concern: envelope kind, exit codes, and hint are separate rows, resolving design.md’s Open Question) | [[error.contract]] |
| error_property_names_label | invariant | every error Constraint has ≥1 unit property whose predicate asserts the exact label string — a label with no falsifying property is not specced, only named | [[error.contract]] |
| contract_published | invariant | the output contract rows live in openspec/specs/errors/spec.md as kind == extension_point, and exist only there — this capability's constraints state requirements about them, never duplicate them; tool files point satisfies at the errors.md rows from their own files, per the extension_point mechanism | [[error.contract]] |
| enforcement_routed | invariant | every rule in this capability states its enforcement tier and no scenario overclaims the present tool: tier 1 — enforced today by existing checkers (emits→effect typing via ref_kind_compatible, every_state_used, graph citation-set comparison); tier 2 — enforced by linter-failure_shape once implemented (label-shape pattern check, mute failure states, carve-out list, negation-set equality), its scenarios are normative for that checker, not claims about today's spk lint; tier 3 — runtime rows (single labeled failure, remediation hint, exit codes) enforced via the compile/verify pipeline fixtures | [[error.contract]] |
Model
States
proposedapprovedimplementedarchived
Transitions
| id | from | to | guard |
|---|---|---|---|
| approve | proposed | approved | [[error.contract.process_lifecycle]] ∧ proposal reviewed and approved by the maintainer |
| implement | approved | implemented | [[error.contract.process_lifecycle]] ∧ all tasks.md items complete; spk lint and openspec validate --strict pass over the change |
| archive | implemented | archived | [[error.contract.process_lifecycle]] ∧ just archive-change id=add-error-contract ran with the dual-format recipe |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[error.contract.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| unnamespaced_label_rejected | unit | [[error.contract.error_expr_shape]] | error_constraint_with_label_lacking_file_id_prefix() | check(file) == failed — a bare extraction_failure(...) head is not a valid label (tier 2) |
| mute_failure_state_detected | unit | [[error.contract.failure_state_emits]] | tool_file_with_failure_terminal_and_no_emits_edge() | check(file) == failed — the corpus’s current shape, asserting the red is real (tier 2; the emits-typing half is tier 1) |
| emits_wrong_kind_rejected | unit | [[error.contract.failure_state_emits]] | failure_state_emitting_an_invariant_constraint() | check(file) == failed — existing ref_kind_compatible typing, restated as the contract’s edge (tier 1) |
| multiclass_single_state_detected | unit | [[error.contract.failure_class_is_state]] | failed_state_with_inbound_failure_transitions_citing_different_constraint_sets() | check(file) == failed — compile.md’s pre-change shape, detected from the graph (tier 2) |
| untyped_negation_detected | unit | [[error.contract.guard_negation_typed]] | failure_transition_whose_citation_set_differs_from_its_success_transition() | check(file) == failed — citation-set inequality is graph-decidable (tier 2) |
| zero_citation_failure_flagged | unit | [[error.contract.guard_negation_typed]] | failure_transition_citing_zero_intrafile_constraints_not_on_carveout_list() | check(file) == failed — the carve-out is a checked list, not an escape hatch (tier 2) |
| carved_out_guard_not_flagged | unit | [[error.contract.guard_negation_typed]] | orchestrate_stage_fail_transition() | check(file) == passed — the D2 carve-out is checked, not assumed (tier 2) |
| labeled_failure_is_single | unit | [[error.contract.single_labeled_failure]] | failing_stage_with_two_extractable_errors() | report(stage) == one labeled error naming stage and class — never a partial bundle (tier 3) |
| envelope_kind_wrong_rejected | unit | [[error.contract.envelope_error_kind]] | failing_stage_reported_as_ok_envelope() | check(report) == failed — a failure must never ride a success-shaped envelope (tier 3) |
| exit_code_mismatch_rejected | unit | [[error.contract.exit_code_mapping]] | failure_stage_with_wrong_exit_code() | check(report) == failed — exit codes are contract, not convention (tier 3) |
| hint_missing_rejected | unit | [[error.contract.remediation_hint_present]] | error_report_with_empty_remediation_hint() | check(report) == failed (tier 3) |
| label_property_asserts_exact_label | unit | [[error.contract.error_property_names_label]] | error_constraint_with_property_predicating_a_different_label() | check(file) == failed — coverage alone cannot catch this; the property must name the label |
| routing_matches_reality | unit | [[error.contract.enforcement_routed]] | each_rule_checked_against_its_stated_tier() | every scenario's tool claim agrees with its tier — no scenario claims today's spk lint for a tier-2 rule |
| contract_satisfied_from_consumer | unit | [[error.contract.contract_published]] | tool_file_with_satisfies_pointing_at_contract_row() | extraction yields exactly one satisfies edge and lint passes — the satisfies precedent’s shape |
Requirements
Requirement: Namespaced labeled errors
The system SHALL require every error Constraint in the corpus to carry a file-id-namespaced variant-head label (<owning-file-id>.<variant_head>(field, …)), and SHALL reject any error Constraint whose label lacks the owning file’s id prefix or whose variant head collides within one file.
Scenario: Bare label rejected
- WHEN an error Constraint’s expr is
extraction_failure(row_id)without the owning file’s id prefix - THEN the tier-2 check fails the file, naming the label and the expected
<file-id>.prefix — untillinter-failure_shapelands, enforcement is the property fixture plus review, never a claim about today’sspk lint
Scenario: Namespaced label accepted
- WHEN an error Constraint in
compile.mddeclares exprcompile.extraction_failure(row_id, reason) - THEN the file lints clean and the label is unique corpus-wide by construction
Requirement: Failure states emit labeled errors
The system SHALL require every failure terminal state in a tool file’s Model to carry an emits edge to that file’s labeled error Constraint, and SHALL require a distinct failure state per distinct failure class, where classes are distinguished by differing guard citation sets.
Scenario: Mute failure state rejected
- WHEN a tool file’s Model contains a failure terminal state with no
emitsedge - THEN the tier-2 check fails the file with a remediation hint pointing at the missing emits target; the emits-typing half (wrong target kind) is enforced today by
ref_kind_compatible
Scenario: Split failure states carry distinct labels
- WHEN
compile.md’s Model distinguishes extraction and emission failure classes asextract_failedandemit_failed - THEN each state emits its own labeled error Constraint and
every_state_usedpasses
Requirement: Typed failure guards
The system SHALL require a failure transition whose guard cites ≥1 [[id]] to cite exactly the citation set of the success transition(s) it negates — a negated disjunction cites the union of its branches’ sets — and SHALL require failure transitions citing no ids at all to be on a recorded carve-out list.
Scenario: Citation-set mismatch rejected
- WHEN a failure transition’s guard cites a citation set different from the success transition(s) it negates — or cites nothing at all while absent from the carve-out list
- THEN the tier-2 check fails the file, since the citation sets are graph-decidable and the carve-out is a checked list
Scenario: Carve-out file passes
- WHEN
orchestrate.md’s stage-fail transitions keep their prose guards per that file’s own Notes - THEN the tier-2 check passes —
orchestrate.mdis on the recorded carve-out list inlinter-failure_shape.md, not silent
Requirement: Output contract published and satisfied
The system SHALL publish the cross-cutting output contract — envelope kind ok:false ↔ "error", exit codes 0/1/2, exactly one labeled failure per failing stage, and a non-empty remediation hint — as extension_point rows in openspec/specs/errors/spec.md, with each tool file pointing satisfies at them from its own file.
Scenario: Consumer satisfies the contract
- WHEN a tool file carries a
satisfiesedge pointing at a contract row inopenspec/specs/errors/spec.md - THEN extraction yields exactly one cross-file edge and lint passes — no edit to the contract file
Scenario: Property-less constraint rejected
- WHEN an error Constraint has no unit property at all
- THEN the tier-1 coverage check fails the file — coverage over every constraint is enforced today; a named-but-unfalsifiable label is not a specced error
Scenario: Mismatched label predicate rejected
- WHEN an error Constraint’s unit property predicate asserts a different label string than the constraint’s own
- THEN the tier-2 check fails the file — exact-label assertion waits for
linter-failure_shape; today’s coverage cannot see the predicate text, so no tier-1 claim is made
observability — capability
id: observability kind: intent statement: “THE observability capability SHALL let Constraint rows declare observability of effect Constraints via a typed one-directional observes reference, SHALL report every unobserved effect as an advisory finding, and SHALL derive external-boundary classification from published extension_point rows alone.”
observability Specification
Purpose
Make declared observability and external-boundary structure checkable
facts from the start of a system’s life: a spec author declares which
behaviors must be observable via observes references, the linter reports
every declared effect that nothing observes (advisory in v1, never a
silent pass or a fabricated gate), and spk graph derives which files are
external boundaries from published extension_point contracts alone —
no hand-maintained tags that can drift from the edges.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[observability]] |
| observes_typing | invariant | every observes reference is a one-directional outbound pointer from a Constraint row to a Constraint row with kind == effect (intra-file or cross-file) — same shape as the satisfies precedent, per the Reference Typing row for observes in openspec/specs/specodelic/spec.md | [[observability]] |
| acyclic_edge_set_stable | invariant | the acyclic invariant's edge set (openspec/specs/linter-graph_shape/spec.md) grows only under a new Revision of that file; observes is deliberately absent from it — an observation claim is not a dependency, so mutual observation across files is well-formed | [[observability]] |
| unobserved_effect_reported | invariant | within the lint invocation's file set (corpus-wide lint via just lint-specs is the canonical run), every effect Constraint is either the target of ≥1 observes edge or is reported as an advisory warning on the lint output's warnings channel — exit 0, never counted as a lint failure (design D5) — naming the unobserved row; no waiver machinery exists in v1 (openspec/specs/linter-external_completeness/spec.md's covered/waived pattern is the candidate when the need is demonstrated) | [[observability]] |
| no_gate_change | invariant | whether linter.observability ever gates a lifecycle transition (openspec/specs/orchestrate/spec.md) is decided only after dogfooding produces real friction evidence — until then it is advisory by severity and the stage guards are untouched | [[observability]] |
| boundary_derived | invariant | external-boundary classification is derived from published extension_point Constraints (a file hosting ≥1 is an external boundary); no authored boundary tag exists anywhere — hand-maintained tags drift from edges, against openspec/specs/graph/spec.md's graph_is_derived_not_authored philosophy | [[observability]] |
Model
States
proposedapprovedimplementedarchived
Transitions
| id | from | to | guard |
|---|---|---|---|
| approve | proposed | approved | [[observability.process_lifecycle]] ∧ proposal reviewed and approved by the maintainer |
| implement | approved | implemented | [[observability.process_lifecycle]] ∧ all tasks.md items complete; spk lint and openspec validate --strict pass over the change |
| archive | implemented | archived | [[observability.process_lifecycle]] ∧ just archive-change id=add-observability-contracts ran with the dual-format recipe |
Properties
| id | kind | derives_from | generator | predicate |
|––––––––––––––––|——|–––––––––––––––––|–––––––––––––––––––––––––––––––––––––––––––––––|
| process_lifecycle_checked | unit | [[observability.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| observes_wrong_target_rejected | unit | [[observability.observes_typing]] | observes_field_pointing_at_an_invariant_constraint() | check(file) == failed — same shape as emits_cannot_target_non_effect |
| observes_cross_file_resolves | unit | [[observability.observes_typing]] | observes_from_consumer_file_to_published_effect() | extraction yields exactly one cross-file edge and lint passes |
| observes_cycle_not_flagged | unit | [[observability.acyclic_edge_set_stable]] | two_files_mutually_observing_each_others_effects() | acyclic check passes — no cycle finding emitted |
| unobserved_effect_finding | unit | [[observability.unobserved_effect_reported]] | effect_with_zero_observes_edges() | advisory finding linter.observability emitted naming the row id |
| observed_effect_silent | unit | [[observability.unobserved_effect_reported]] | effect_with_at_least_one_observes_edge() | no linter.observability finding emitted |
| gates_untouched | unit | [[observability.no_gate_change]] | corpus_with_unobserved_effects_through_full_pipeline() | linted/compiled/model_checked/verified outcomes identical to before |
| boundary_from_extension_points | unit | [[observability.boundary_derived]] | file_hosting_extension_point_row() | graph classifies the file as an external boundary |
| plain_file_not_boundary | unit | [[observability.boundary_derived]] | file_with_no_extension_point_rows() | graph does not classify the file as an external boundary |
| stale_boundary_impossible | unit | [[observability.boundary_derived]] | file_whose_extension_points_are_all_removed() | boundary classification disappears — no authored tag left to clean |
Requirements
Requirement: Typed one-directional observes reference
The system SHALL accept an optional observes reference on Constraint rows, typed as a one-directional outbound pointer from any Constraint to a Constraint with kind == effect on any file — mirroring the satisfies precedent — and SHALL reject any observes pointing at a non-effect row.
Scenario: Cross-file observation resolves
- WHEN a Constraint row in file A carries
observespointing at an effect Constraint in file B - THEN graph extraction yields exactly one cross-file edge and
spk lintreports no typing violation
Scenario: Wrong target kind rejected
- WHEN a Constraint row carries
observespointing at a Constraint with kind ==invariant - THEN
spk lintfails the file with a remediation hint naming the target’s actual kind
Scenario: Mutual observation is not a cycle violation
- WHEN two files’ Constraint rows each
observesan effect in the other file - THEN the acyclic check passes —
observesis a claim, not a dependency edge
Requirement: Unobserved effects are reported, never silent
The system SHALL report, with rule id linter.observability, on the lint output’s warnings channel (advisory severity — exit 0, never counted as a lint failure; design D5), every effect Constraint within the lint invocation’s file set that is the target of no observes edge (corpus-wide lint is the canonical run) — and SHALL NOT gate any lifecycle transition on this finding in v1.
Scenario: Effect with no observer reported
- WHEN a linted spec declares an effect Constraint that no
observesreference targets - THEN the lint output carries an advisory
linter.observabilitywarning on the warnings channel, naming the unobserved row id, with exit 0
Scenario: Observed effect produces no finding
- WHEN every effect Constraint in the corpus is targeted by at least one
observesedge - THEN no
linter.observabilityfinding is emitted and the lifecycle gate outcomes are unchanged
Requirement: External boundaries are derived, not tagged
The system SHALL classify a file as an external boundary in spk graph output iff it hosts at least one extension_point Constraint, deriving the classification from existing reference structure — and SHALL NOT introduce any authored boundary tag.
Scenario: Boundary derived from published contract
- WHEN
spk graph --jsonruns on a corpus where file B hosts anextension_pointConstraint - THEN B is classified as an external boundary without any authored tag
Scenario: No tag drift
- WHEN a file’s
extension_pointrows are all removed in a revision - THEN the file’s external-boundary classification disappears — there is no stale tag to clean up
migrate — capability
id: migrate kind: intent statement: “WHEN an openspec delta file is migrated, THE migrate capability SHALL wrap it in place into the Revision 19 pair — leaving the plain delta byte-untouched and creating the sibling layer.md sidecar with generated frontmatter and wired scaffold layers — while refusing any file that is not a plain delta in need of a sidecar and supporting –rekey for id: spec legacy files.”
migrate Specification
Purpose
Give adopters a tool surface for the dual-format migration recipe: wrap an existing openspec delta in place — frontmatter, wired scaffold layers, and the byte-identical Requirements mirror — instead of hand-editing (sed one-liners on the mirror were the top onboarding cost, gh#6 item 1). The scaffold is a marked skeleton, never derived prose; finishing it is the author’s job.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[migrate]] |
| delta_text_preserved | invariant | ∀ migrated pair: the given delta file's text is preserved byte-for-byte — the plain delta stays untouched; the wrap creates the sibling layer.md sidecar rather than rewriting the delta (Revision 19: the dual-format wrap and its ## Requirements mirror are retired) | [[migrate]] |
| sidecar_generated | invariant | the wrap produces the pair — the byte-unchanged plain delta plus the generated sibling layer.md carrying frontmatter (the parent-directory-derived id with a .layer suffix, kind intent, an EARS scaffold statement) and the wired scaffold layers; no mirror exists to maintain | [[migrate]] |
| refuse_already_migrated | invariant | a file that is not a plain delta in need of a sidecar is refused (exit 2), never rewritten: a file carrying frontmatter (already a specodelic file), a file whose only Requirements heading is a deployed spec's ## Requirements section, and a file carrying no Requirements heading at all | [[migrate]] |
| merge_adds_only_missing | invariant | only missing pieces are inserted when migrating a partially migrated pair — an existing sidecar passes through untouched and no scaffold statement is inserted; only missing sidecar pieces are generated (Revision 19: the mirror is no longer a piece that can be missing) | [[migrate]] |
| scaffold_lints_clean | invariant | the generated sidecar (frontmatter + wired scaffold_* placeholder rows + one-state model) exits spk lint 0 with zero findings as written — the author starts from a green baseline | [[migrate]] |
| dry_run_writes_nothing | invariant | --dry-run emits the resulting pair content as data and never writes a file; the on-disk bytes are unchanged | [[migrate]] |
| rekey_derives_real_id | invariant | --rekey derives the real id by the naming law — the parent directory name for a spec.md file (- ⇔ ., _ literal) — and replaces the frontmatter id: spec line with it; every [[spec]] ref re-keys to [[<derived-id>]] and every [[spec. prefix to [[<derived-id>.; every other byte stays untouched | [[migrate]] |
| rekey_refusals | invariant | --rekey on a file without frontmatter is refused (a plain delta takes the wrap path); on a spec.md with no derivable id (bare, or parent dir named spec) it is refused; on a file whose id is already not spec it is an idempotent no-op with a warning, never rewritten | [[migrate]] |
Model
States
unwrappingwrappingwrittenrefusedrekeyed
Transitions
| id | from | to | guard |
|---|---|---|---|
| begin_wrap | unwrapping | wrapping | [[migrate.delta_text_preserved]] |
| write_result | wrapping | written | [[migrate.sidecar_generated]] |
| dry_run_pass | wrapping | wrapping | [[migrate.dry_run_writes_nothing]] |
| refuse | unwrapping | refused | [[migrate.refuse_already_migrated]] |
| merge_only | wrapping | written | [[migrate.merge_adds_only_missing]] |
| verify_green | written | written | [[migrate.scaffold_lints_clean]] |
| rekey_result | unwrapping | rekeyed | [[migrate.rekey_derives_real_id]] |
| rekey_refuse | unwrapping | refused | [[migrate.rekey_refusals]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[migrate.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| p_delta_text_preserved | unit | [[migrate.delta_text_preserved]] | delta_with(frontmatter: any) | migrate(file).delta_body == original.delta_body |
| p_sidecar_generated | unit | [[migrate.sidecar_generated]] | delta_with(sections: any) | result.sidecar present with .layer-derived frontmatter ∧ result.delta byte-unchanged |
| p_refuse_already_migrated | unit | [[migrate.refuse_already_migrated]] | file_with_frontmatter_or_non_delta() | migrate(file) == refused ∧ disk_unchanged |
| p_merge_adds_only_missing | unit | [[migrate.merge_adds_only_missing]] | pair_with(partial_sidecar: any) | existing_pieces(result) == existing_pieces(original) |
| p_scaffold_lints_clean | unit | [[migrate.scaffold_lints_clean]] | any_plain_delta() | lint(migrate(file).sidecar) == zero_findings |
| p_dry_run_writes_nothing | unit | [[migrate.dry_run_writes_nothing]] | any_plain_delta() | dry_run(file).disk_bytes == original.disk_bytes |
| p_rekey_derives_real_id | unit | [[migrate.rekey_derives_real_id]] | legacy_id_spec_dual_format_file() | rekey(file).id == parent_dir_derived ∧ refs re-keyed ∧ rest byte-identical |
| p_rekey_refusals | unit | [[migrate.rekey_refusals]] | plain_delta_or_bare_spec_md() | rekey(file) == refused ∧ disk_unchanged ∧ second_run_is_noop |
Requirements
Requirement: In-place wrap of a plain delta
The system SHALL rewrite the given delta file pair in place — leaving the
plain delta untouched and creating the sibling layer.md sidecar with
generated frontmatter (the parent-directory-derived id with a .layer
suffix, kind intent, an EARS scaffold statement) and the wired scaffold
layers — while preserving the delta text byte-for-byte (Revision 19: the
dual-format wrap and its ## Requirements mirror are retired).
Scenario: Plain delta becomes dual-format
- WHEN
spk migrate delta.mdruns on a file with an ADDED Requirements section and no frontmatter - THEN the delta file is byte-unchanged, a sibling
layer.mdis created with frontmatter and scaffold layers (the retired dual wrap is gone — the delta itself stays plain), andspk lintover the pair exits 0 with zero findings
Scenario: Plain delta gains a sidecar
- WHEN the wrap completes on a plain delta
- THEN the change directory holds the pair — the byte-unchanged delta plus the generated sidecar — and no mirror exists to maintain
Scenario: Scaffold lints clean
- WHEN the migration completes
- THEN
spk lint <layer.md>exits 0 with zero findings as written — the author starts from a green baseline
Requirement: Merge adds only missing pieces
The system SHALL keep every already-present piece verbatim when migrating a partially migrated pair — an existing sidecar passes through untouched and no scaffold statement is inserted — generating only the missing sidecar pieces (Revision 19: the mirror is no longer a piece that can be missing).
Scenario: Frontmatter kept verbatim
- WHEN the pair already carries a
layer.mdwith frontmatter - THEN it appears unchanged in the result and no scaffold statement is inserted
Scenario: Existing sidecar kept verbatim
- WHEN the merge runs over a partially migrated pair
- THEN every already-present piece (delta text, sidecar tables) passes through verbatim and only missing sidecar pieces are generated
Requirement: Refuse already migrated or non-delta files
The system SHALL refuse (exit 2) any file that is not a plain delta in
need of a sidecar — a file carrying frontmatter (already a specodelic
file), a file whose only Requirements heading is a deployed spec’s
## Requirements section, and a file carrying no Requirements heading at
all.
Scenario: Frontmatter-carrying file is a refusal
- WHEN
spk migrateruns on a file that already carries frontmatter - THEN the command fails with an already-migrated message and no file is changed
Scenario: Second run is a refusal
- WHEN
spk migrateruns twice over the same change directory - THEN the second run fails with an already-migrated message and both files are unchanged
Requirement: Rekey an id:spec dual-format file to its real id
The system SHALL support --rekey on the migrate command: a file
carrying frontmatter with id: spec SHALL be rewritten in place with
the real id derived by the naming law — the parent directory name for a
spec.md file (- ⇔ ., _ literal) — every [[spec]] reference
re-keyed to [[<derived-id>]] and every [[spec. prefix re-keyed to
[[<derived-id>., and every other byte of the file left untouched.
Scenario: Legacy dual-format file rekeys
- WHEN
spk migrate openspec/specs/ge-cli/spec.md --rekeyruns on a file declaringid: specwith[[spec.c1]]refs - THEN the file declares
id: ge.cli, its refs read[[ge.cli]]/[[ge.cli.c1]], and everything else is byte-identical
Scenario: Dry-run rekey writes nothing
- WHEN
spk migrate <file> --rekey --dry-runruns - THEN the on-disk bytes are unchanged and the envelope data carries the would-be content
Requirement: Rekey refusals keep the sweep idempotent
A file without frontmatter SHALL be refused (it is a plain delta — the
wrap path’s job); a bare spec.md with no parent directory SHALL be
refused (no derivable id); a file whose id is already not spec SHALL
be a no-op with a warning, never rewritten, so a directory-wide rekey
sweep is idempotent.
Scenario: Refusals never rewrite
- WHEN
--rekeyruns on a plain delta (no frontmatter) or a barespec.mdwith no parent directory - THEN the command fails (exit 2) with a labeled refusal naming the right path (the wrap path, or an unguessable id) and the file is unchanged
Scenario: Rekey sweep is idempotent
- WHEN
--rekeyruns twice, or on a file already carrying its real id - THEN the second run is a success with a no-op warning and the file is unchanged
Requirement: Dry-run writes nothing
The system SHALL support --dry-run, emitting the resulting content as
data without writing the file.
Scenario: Dry-run leaves disk untouched
- WHEN
spk migrate --dry-run delta.mdruns - THEN the on-disk bytes are unchanged and the envelope data carries the would-be content
archive-companion — capability
id: archive.companion kind: intent statement: “WHEN an openspec change is archived, THE archive-companion capability SHALL invoke openspec’s own archiver with default spec application, SHALL refuse any delta whose layer is unrestorable before the destructive archive, and SHALL re-attach each archived sidecar’s dual-format layer onto the applied deployed spec so every deployed spec ends up frontmatter-plus-tables again while the archived delta stays plain.”
archive-companion Specification
Purpose
Make the dual-format corpus survive the openspec archive round-trip at
the tool level: openspec archive regenerates deployed specs from
parsed deltas and drops frontmatter + the Constraints/Model/Properties
tables; the companion performs the archive with spec application skipped
and deploys the archived deltas verbatim, so the deployed spec and its
archived delta are byte-identical dual-format files. Fail-closed on any
delta lacking the layer — the tool must never be the thing that deploys
a stripped spec.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[archive.companion]] |
| layer_preserved | invariant | after the command completes, every deployed openspec/specs/<cap>/spec.md touched by the change is the layer-preserving re-assembly of the archived pair — openspec's applied ## Requirements section plus the archived sidecar's frontmatter and Constraints/Model/Properties tables — while the archived delta stays byte-plain and the sidecar stays archived beside it | [[archive.companion]] |
| openspec_not_forked | invariant | archive semantics come from the openspec CLI itself (invoked with DEFAULT spec application — plain deltas are openspec's native merge); the companion never re-implements delta merging, requirement parsing, or directory moves — it only re-attaches the layer afterwards | [[archive.companion]] |
| fail_closed_unverifiable | invariant | a delta whose layer is unrestorable — no sibling layer.md sidecar, or a sidecar that fails parse or lacks one of the three layer tables — is refused BEFORE the destructive openspec archive; the whole command fails listing every refused delta with the spk migrate wrap remediation hint, and nothing moves and nothing is written | [[archive.companion]] |
| attach_shape_asserted | invariant | the assembled deployed spec is asserted pre-write: no delta headers, frontmatter plus all three tables present — a shape openspec 1.x strict rejects is refused and reverted; when the run invoked openspec the change dir is moved back out of the archive; the deployed-corpus lint runs post-write and reverts on findings, labeled with a remediation hint | [[archive.companion]] |
| converged_sidecarless_recognized | invariant | a sidecar-less ARCHIVED delta whose deployed target already carries the dual-format layer is recognized as converged and skipped, never refused — Stage-1-era archives stay idempotent under rerun; anything else without a sidecar is refused | [[archive.companion]] |
| idempotent_rerun | invariant | re-running the command for an already-archived change skips the openspec invocation and re-executes the re-attachment from the newest archive; the result converges to identical deployed bytes | [[archive.companion]] |
| newest_archive_wins | invariant | when multiple archive directories match the change id, the lexicographically greatest name is used and is named in the envelope — ambiguity is observable, never silent | [[archive.companion]] |
| dry_run_never_mutates | invariant | --dry-run resolves the restore plan (archive directory, delta list, sidecar verification) and emits the envelope without invoking openspec and without writing any file | [[archive.companion]] |
| empty_restore_valid | invariant | a change with no spec deltas completes successfully with an empty restored list — nothing to restore is a stated outcome, not an error | [[archive.companion]] |
Model
States
resolvedarchivedrestoredrefused
Transitions
| id | from | to | guard |
|---|---|---|---|
| locate | resolved | archived | [[archive.companion.openspec_not_forked]] |
| deploy | archived | restored | [[archive.companion.layer_preserved]] ∧ [[archive.companion.fail_closed_unverifiable]] ∧ [[archive.companion.attach_shape_asserted]] |
| reject | archived | refused | ¬[[archive.companion.fail_closed_unverifiable]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[archive.companion.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| deployed_assembly_matches | unit | [[archive.companion.layer_preserved]] | change_archived_then_compared() | each deployed spec carries the applied Requirements section plus the sidecar's frontmatter and tables; the archived delta stays plain |
| no_bespoke_merge | unit | [[archive.companion.openspec_not_forked]] | module_inspected_for_merge_logic() | the module contains no delta-merge code path; only the external invocation and the layer re-attachment remain |
| plain_delta_refused | unit | [[archive.companion.fail_closed_unverifiable]] | active_change_with_sidecarless_delta() | command fails before the openspec invocation, lists the delta, writes nothing |
| shape_assertion_enforced | unit | [[archive.companion.attach_shape_asserted]] | assembly_with_delta_header() | write refused and reverted; the run fails naming the capability; an openspec-invoking run moves the change dir back |
| converged_rerun_skipped | unit | [[archive.companion.converged_sidecarless_recognized]] | sidecarless_archive_with_deployed_layer() | rerun skips the pair as converged, not refused |
| rerun_byte_identical | unit | [[archive.companion.idempotent_rerun]] | companion_run_twice() | second run's envelope matches the first's restored set; deployed files converge to identical bytes |
| ambiguous_dir_reported | unit | [[archive.companion.newest_archive_wins]] | two_archive_dirs_same_change_id() | lexicographically greatest dir used and named in the envelope |
| dry_run_writes_nothing | unit | [[archive.companion.dry_run_never_mutates]] | dry_run_over_populated_archive() | envelope emitted; no file mtimes changed; openspec not invoked |
| docs_only_change_succeeds | unit | [[archive.companion.empty_restore_valid]] | archived_change_without_spec_deltas() | exit 0, restored: [] |
| missing_openspec_labeled | unit | [[archive.companion.openspec_not_forked]] | path_without_openspec_binary() | labeled error with an install/PATH remediation hint |
Requirements
Requirement: Archive with dual-format preservation
The system SHALL provide spk archive-companion <CHANGE_ID> that
archives the named openspec change via the openspec CLI with spec
application ON (plain deltas are openspec’s native merge — Revision 19),
then re-attaches the dual-format layer from the change’s archived
layer.md sidecar, so every deployed openspec/specs/<cap>/spec.md
ends up frontmatter-plus-tables again: the archived delta stays plain
and the deployed spec is the layer-preserving re-assembly of the two.
Scenario: Dual-format delta survives archiving
- WHEN an active change carries a plain delta plus a
layer.mdsidecar for capabilityc - THEN after
spk archive-companion <id>, the archived delta remains a plain openspec file, the sidecar is archived beside it, andopenspec/specs/c/spec.mdis the dual-format re-assembly of the two
Scenario: Plain delta deploys a dual-format spec
- WHEN an active change carries a plain delta plus a
layer.mdsidecar for capabilityc - THEN after
spk archive-companion <id>,openspec/specs/c/spec.mdcarries the applied## Requirementssection plus the sidecar’s frontmatter and Constraints/Model/Properties tables, and the archived delta remains a plain openspec file
Scenario: Already-archived change re-runs cleanly
- WHEN the change directory no longer exists but its archive does
- THEN the openspec invocation is skipped and the layer re-attachment re-runs to a byte-identical deployed result
Requirement: Observable resolution and preview
The system SHALL emit an envelope naming the archive directory used,
whether the openspec invocation ran, and the restored paths — and SHALL
support --dry-run producing that same resolution without invoking
openspec or writing any file.
Scenario: Dry-run reports the plan
- WHEN
spk archive-companion <id> --dry-runruns over a resolvable change - THEN the envelope lists the planned restores and no file is written and no openspec invocation occurs
Requirement: Fail closed on sidecar-less deltas
The system SHALL refuse to archive any change whose delta lacks a sidecar
capable of restoring the deployed dual-format layer — no layer.md
beside the delta, or a sidecar that fails parse or spk lint — failing
the whole command before the destructive openspec archive runs, with a
listing of every refused delta and a remediation hint naming the spk migrate wrap path, and writing no deployed spec.
Scenario: Sidecar-less delta is refused
- WHEN an active change carries a delta with no sibling
layer.md - THEN the command fails before
openspec archiveis invoked, the delta is listed, and noopenspec/specs/<cap>/spec.mdis modified
Scenario: Frontmatterless delta is refused
- WHEN an active change carries a delta with no sibling
layer.md - THEN the command fails, the delta is listed, and no
openspec/specs/<cap>/spec.mdis modified
Scenario: Unlintable sidecar is refused
- WHEN an active change carries a
layer.mdsidecar that failsspk lint - THEN the command fails before
openspec archiveis invoked, the delta is listed with the lint findings, and no deployed spec is written
Scenario: Post-attach shape assertion
- WHEN the layer re-attachment would write a deployed spec carrying a delta header, or lacking frontmatter or any of the three tables
- THEN the write is refused and reverted and the command fails naming the capability — the deployed tree never gains a shape 1.x strict rejects
Scenario: New capability deploys from scratch
- WHEN a change introduces capability
cwith no existing deployedopenspec/specs/c/spec.md - THEN the companion assembles the first deployed spec from the delta’s applied requirements plus the sidecar’s frontmatter and tables — same shape assertion, same fail-closed refusal
parse — capability
id: parse kind: intent statement: “WHEN a user runs spk parse on one spec file THE parse capability SHALL emit the full parsed Spec IR as a json envelope, SHALL succeed independently of lint status, and SHALL fail with a labeled error envelope on unparseable input.”
structured parse export
The structured layer (Properties rows included) is fully modeled in the lib
crate but unreachable from outside it: spk compile --json embeds generated
artifact text and exposes no Properties rows as structure. Fleet consumers
(espectacular’s ah sync) must not reimplement the markdown-table parser —
they consume the typed IR through the versioned-binary boundary instead.
spk parse is the minimal exposure: same parse path as lint and compile, the
Serialize-derived Spec emitted directly, envelope-conformant, syntax-only.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[parse]] |
| C-parse-ir | invariant | spk parse emits the full parsed Spec IR for a well-formed file — intent, constraints rows, states, transitions, properties rows, and structured links — with no structured field omitted | [[parse]] |
| C-parse-lint-independent | invariant | parse succeeds on a file that fails spk lint and the parse envelope embeds no lint status | [[parse]] |
| C-parse-envelope | invariant | parse output conforms to the shared json envelope discipline — ok, envelope_version, cli_version, envelope_kind, data, warnings, hints, meta — with hints suggesting spk lint on the parsed file | [[parse]] |
| C-parse-error | effect | spec.parse_failure(detail) — the file could not be parsed or located because detail; the error envelope satisfies the fleet error contract (error-kind envelope, labeled, remediation hint present, non-zero exit) | [[parse]] |
| C-parse-single-file | advisory | parse accepts exactly one file path argument per invocation, without globbing or bulk export | [[parse]] |
Model
States
readingparsedemittedfailed(emits:[[parse.C-parse-error]])
Transitions
| id | from | to | guard |
|---|---|---|---|
| t-parse | reading | parsed | [[parse.C-parse-ir]] |
| t-emit | parsed | emitted | [[parse.C-parse-envelope]] |
| t-fail | reading | failed | ¬([[parse.C-parse-ir]]) |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[parse.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| P-ir | unit | [[parse.C-parse-ir]] | well_formed_four_layer_file() | data.carries(intent, constraints, states, transitions, properties, links) ∧ every_row_present |
| P-lintindep | unit | [[parse.C-parse-lint-independent]] | a file that parses but fails spk lint | parse exits zero with the full IR and no lint status anywhere in the envelope |
| P-envelope | unit | [[parse.C-parse-envelope]] | parse output inspected field by field | every shared envelope field is present and hints suggest spk lint |
| P-error | unit | [[parse.C-parse-error]] | unparseable_file() and nonexistent_path() | envelope.has_label ∧ envelope.has_hint ∧ exit != 0 in both cases |
| P-single | unit | [[parse.C-parse-single-file]] | parse invoked with zero or two or more paths | the invocation is rejected with usage guidance |
Purpose
Expose the typed Spec IR through the CLI so external fleet tools consume the structured layer without reimplementing the format parser, keeping the versioned binary as the single integration boundary.
Requirements
Requirement: Structured Parse Export
spk parse <file> SHALL emit the parsed Spec IR — intent, constraints rows, states, transitions, properties rows, and structured links — as a --json-conformant envelope, SHALL succeed independently of lint status without embedding it, and SHALL fail with a labeled error envelope and non-zero exit on unparseable or missing input.
Scenario: Export full IR
- GIVEN a well-formed four-layer spec file with three Properties rows
- WHEN
spk parse <file> --jsonruns - THEN the envelope is
okanddatacarries intent, constraints, states, transitions, all three property rows, and the structured links
Scenario: Lint-dirty file still parses
- GIVEN a file that parses but fails
spk lint - WHEN
spk parse <file> --jsonruns - THEN it exits zero with the full IR and the envelope embeds no lint status
Scenario: Unparseable input errors with label
- GIVEN a file that violates the format’s parse rules, and separately a nonexistent path
- WHEN
spk parseruns on each - THEN each yields a labeled error envelope with a remediation hint and non-zero exit
Scenario: Single file per invocation
- GIVEN zero or two or more path arguments
- WHEN
spk parseruns - THEN the invocation is rejected with usage guidance
data-lineage-pack — capability
id: data.lineage.pack kind: intent statement: “WHEN a workspace enables the data/lineage standard pack, THE format SHALL provide the pack as a declared kind: profile artifact — a namespaced ## Data section, data.dataset/data.artifact/data.environment kinds, produced_by/consumed_by/produces lineage reference fields resolving to declared ## Data rows, and a binding column that bridges external data standards without absorbing them into any base closed set — while files not using the pack’s vocabulary lint byte-identically.”
data-lineage-pack Specification
Purpose
Ship the first standard pack on the pack mechanism: a data/lineage pack
declaring a typed ## Data table, namespaced dataset/artifact/environment
kinds, and lineage edges (produced_by/consumed_by/produces) whose
external binding is typed but opaque. External data standards stay
authoritative — the pack bridges, never absorbs. It is the mechanism’s
first real instance and the prerequisite for the bioimage D6 pilot.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[data.lineage.pack]] |
| pack_file_declared | invariant | the data/lineage standard pack is a kind: profile spec file at packs/data-lineage.md (id data.lineage) whose six manifest tables declare the ## Data section, the data.dataset/data.artifact/data.environment kinds, the produces/produced_by/consumed_by reference fields, the data.lineage.closure checker, the artifact provenance floor, and the Revision 14 base pin | [[data.lineage.pack]] |
| data_section_row_shape | invariant | ## Data rows carry exactly | name | kind | dtype | units | binding | with kind closed to dataset, artifact, environment — a per-pack closed set; no base closed set grows and no existing entry narrows | [[data.lineage.pack]] |
| lineage_edges_outbound | invariant | produces, produced_by, and consumed_by are pack-added reference fields resolving to a ## Data row of the declaring file — outbound leaves joining no reachability path and no acyclic edge set; a dangling lineage reference is a labeled finding naming the row id and both remediations, never a generic dangling message | [[data.lineage.pack]] |
| binding_stays_external | invariant | the binding column carries a typed pointer to an external data standard and the format never parses it — no dtype enum, shape convention, or URI scheme joins a base closed set, and lint behavior is identical regardless of the binding target | [[data.lineage.pack]] |
Model
States
draftpublisheddeprecated
Transitions
| id | from | to | guard |
|---|---|---|---|
| publish | draft | published | [[data.lineage.pack.pack_file_declared]] ∧ [[data.lineage.pack.data_section_row_shape]] — pack_shape reports zero findings over the pack's declared vocabulary |
| deprecate | published | deprecated | [[data.lineage.pack.pack_file_declared]] — maintainer marks the pack deprecated; findings name the deprecation, vocabulary still checks |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[data.lineage.pack.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| discovery_finds_lineage_pack | unit | [[data.lineage.pack.pack_file_declared]] | lint_workspace_with_pack_present() | the pack is discovered by corpus scan alone, no config file, no registry |
| pack_shape_clean_all_states | unit | [[data.lineage.pack.pack_file_declared]] | pack_parsed_in_every_lifecycle_state() | pack_shape reports zero findings over the declared vocabulary in draft, published, and deprecated |
| consumer_activates_advisory | unit | [[data.lineage.pack.data_section_row_shape]] | corpus_spec_uses_data_dataset_vocabulary() | the pack's checkers activate advisory-first naming the pack; findings attributed per pack |
| orphan_names_lineage_pack | unit | [[data.lineage.pack.data_section_row_shape]] | workspace_uses_data_vocabulary_without_pack() | the orphan finding names the candidate pack data.lineage and both remediations, exit non-zero |
| lineage_reference_resolves | unit | [[data.lineage.pack.lineage_edges_outbound]] | constraint_row_produced_by_declared_data_row() | the lineage reference resolves to the declared ## Data row; a dangling reference is a labeled finding naming the row id and both remediations |
| closure_honest_empty | unit | [[data.lineage.pack.lineage_edges_outbound]] | pack_active_without_data_rows() | the closure checker reports an empty checked-set; no fabricated findings |
| no_base_set_absorption | unit | [[data.lineage.pack.binding_stays_external]] | guide_sets_compared_with_and_without_data_pack() | INTENT_KINDS and Reference Typing sets identical with the pack discovered or not |
| binding_opaque_to_lint | unit | [[data.lineage.pack.binding_stays_external]] | data_row_binds_to_ome_zarr_vs_parquet() | lint findings byte-identical across different binding targets |
Requirements
Requirement: The data/lineage standard pack is a declared profile file
The format SHALL ship the data/lineage standard pack as an in-repo
kind: profile spec file (packs/data-lineage.md, id data.lineage)
whose manifest declares the ## Data section, the data.dataset,
data.artifact, and data.environment kinds, the produces,
produced_by, and consumed_by reference fields, the
data.lineage.closure checker, and the artifact provenance floor.
Scenario: Pack lints clean under pack_shape in every lifecycle state
- WHEN the pack file is parsed in draft, published, or deprecated state
- THEN pack_shape reports zero findings over the pack’s declared vocabulary
Requirement: The data/lineage pack is discovered by corpus scan with a pinned base
The pack’s manifest SHALL pin the specodelic.md Revision 14 base, and
the pack SHALL be discovered by corpus scan alone — with no config file
and no registry.
Scenario: Corpus scan discovers the pack
- WHEN
spk lintruns over any workspace subdirectory of a repository containingpacks/data-lineage.md - THEN the pack is discovered without configuration and reported in the lint envelope
- AND files using none of the pack’s vocabulary keep byte-identical lint findings
Requirement: Data vocabulary is namespaced and per-pack closed
The pack SHALL declare a ## Data section with row shape
| name | kind | dtype | units | binding |, kind closed to dataset,
artifact, environment, and namespaced kinds data.dataset,
data.artifact, data.environment — pack-fiber-relative vocabulary that
grows no base closed set and narrows no existing base set entry.
Scenario: Consumer activates the pack advisory-first
- WHEN a corpus spec uses
data.datasetvocabulary without a declaredusesedge - THEN the pack’s declared checking activates advisory-first for that file, naming the pack
Scenario: Orphan vocabulary names the lineage pack
- WHEN a workspace uses
data.datasetvocabulary with no data/lineage pack discovered or declared - THEN the orphan finding names the candidate pack
data.lineageand both remediations, with non-zero exit
Requirement: Lineage edges are typed outbound leaves resolving to declared rows
The pack SHALL add produces, produced_by, and consumed_by reference
fields, each resolving to a ## Data row of the declaring file and
joining no reachability path and no acyclic edge set, and SHALL declare a
data.lineage.closure checker that reports an empty checked-set honestly
when no ## Data rows exist.
Scenario: Dangling lineage reference is labeled
- WHEN a constraint row references a
## Datarow id viaproduced_bythat no## Datarow in the file declares - THEN the finding is labeled, names the row id and both remediations, and is never a generic dangling message
Scenario: Honest-empty closure with no data rows
- WHEN the pack is active in a workspace whose specs declare no
## Datarows - THEN the closure checker reports an empty checked-set with no fabricated findings
Requirement: External binding stays external
The pack SHALL bridge, never absorb, external data standards: the
binding column carries a typed pointer to a schema, URI, or standard
reference that the format never parses, and no dtype enum, shape
convention, or URI scheme ever joins a base closed set — lint behavior
is identical regardless of the binding target.
Scenario: Any external standard binds without lint change
- WHEN a
## Datarow binds to OME-Zarr, Parquet, or any other external standard - THEN lint findings are byte-identical across the different binding targets
numeric-predicates-pack — capability
id: numeric.predicates.pack kind: intent statement: “WHEN a workspace enables the numeric-predicates standard pack, THE format SHALL provide a namespaced ## Quantities section whose rows carry name, kind, unit, and domain; numeric.quantity, numeric.bound, and numeric.tolerance kinds; a measured_by reference field resolving to declared ## Quantities rows of the same file; and a tolerance case-label floor — while unit and domain columns stay opaque and files using none of this vocabulary lint byte-identically.”
numeric-predicates-pack Specification
Purpose
Ship the numeric-predicates standard pack: a typed ## Quantities
table, namespaced numeric kinds, tolerance laws with their own case-label
floor, and a measured_by outbound-leaf edge. Unit systems stay
authoritative outside the format — the pack bridges, never absorbs. It
is the second standard pack and, with the empirical-registry pack,
completes what the bioimage D6 pilot consumes.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[numeric.predicates.pack]] |
| pack_file_declared | invariant | the numeric-predicates standard pack is a kind: profile spec file at packs/numeric-predicates.md (id numeric.predicates) whose six manifest tables declare the ## Quantities section, the numeric.quantity/numeric.bound/numeric.tolerance kinds, the measured_by reference field, the numeric.quantity_closed and numeric.tolerance_labels checkers, the tolerance case-label floor, and the Revision 14 base pin | [[numeric.predicates.pack]] |
| quantity_section_shape | invariant | ## Quantities rows carry exactly | name | kind | unit | domain | with kind closed to quantity, bound, tolerance — a per-pack closed set; no base closed set grows and no existing entry narrows | [[numeric.predicates.pack]] |
| measured_by_outbound | invariant | measured_by is a pack-added reference field resolving to a ## Quantities row of the declaring file — an outbound leaf joining no reachability path and no acyclic edge set; a dangling reference is a labeled finding naming the row id and both remediations, never a generic dangling message | [[numeric.predicates.pack]] |
| units_stay_opaque | invariant | the unit and domain columns carry typed prose pointers (unit systems, domains of validity) that the format never parses — no unit system, conversion rule, or domain vocabulary joins a base closed set, and lint behavior is identical regardless of the unit system named | [[numeric.predicates.pack]] |
| vocabulary_prose_safe | invariant | no pack vocabulary token is a bare English word that appears in corpus prose — the within/bound/against/unit/domain audit excluded them from vocabulary-carrying facets; bound and against survive only as floor case labels, which the mechanism never scans | [[numeric.predicates.pack]] |
Model
States
draftpublisheddeprecated
Transitions
| id | from | to | guard |
|---|---|---|---|
| publish | draft | published | [[numeric.predicates.pack.pack_file_declared]] ∧ [[numeric.predicates.pack.quantity_section_shape]] — pack_shape reports zero findings over the pack's declared vocabulary |
| deprecate | published | deprecated | [[numeric.predicates.pack.pack_file_declared]] — maintainer marks the pack deprecated; findings name the deprecation, vocabulary still checks |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[numeric.predicates.pack.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| discovery_finds_numeric_pack | unit | [[numeric.predicates.pack.pack_file_declared]] | lint_workspace_with_numeric_pack_present() | the pack is discovered by corpus scan alone, no config file, no registry |
| pack_shape_clean_all_states | unit | [[numeric.predicates.pack.pack_file_declared]] | pack_parsed_in_every_lifecycle_state() | pack_shape reports zero findings over the declared vocabulary in draft, published, and deprecated |
| consumer_activates_advisory | unit | [[numeric.predicates.pack.quantity_section_shape]] | corpus_spec_uses_numeric_quantity_vocabulary() | the pack's checkers activate advisory-first naming the pack; findings attributed per pack |
| orphan_names_numeric_pack | unit | [[numeric.predicates.pack.quantity_section_shape]] | workspace_uses_numeric_vocabulary_without_pack() | the orphan finding names the candidate pack numeric.predicates and both remediations, exit non-zero |
| measured_by_resolves | unit | [[numeric.predicates.pack.measured_by_outbound]] | constraint_row_measured_by_declared_quantity_row() | the measured_by reference resolves to the declared ## Quantities row; a dangling reference is a labeled finding naming the row id and both remediations |
| closure_honest_empty | unit | [[numeric.predicates.pack.measured_by_outbound]] | pack_active_without_quantity_rows() | the closure checkers report an empty checked-set; no fabricated findings |
| no_base_set_absorption | unit | [[numeric.predicates.pack.units_stay_opaque]] | guide_sets_compared_with_and_without_numeric_pack() | INTENT_KINDS and Reference Typing sets identical with the pack discovered or not |
| units_opaque_to_lint | unit | [[numeric.predicates.pack.units_stay_opaque]] | quantity_row_names_ucum_vs_iso4217() | lint findings byte-identical across different unit systems |
| no_bare_word_vocabulary | unit | [[numeric.predicates.pack.vocabulary_prose_safe]] | corpus_linted_with_and_without_numeric_pack() | lint warnings byte-identical for every corpus file — no vocabulary-match activations from prose words |
| tolerance_floor_labels | unit | [[numeric.predicates.pack.vocabulary_prose_safe]] | tolerance_law_missing_case_label() | a numeric.tolerance law owes **bound:** and **against:** case labels per the floor declaration |
Requirements
Requirement: The numeric-predicates standard pack is a declared profile file
The format SHALL ship the numeric-predicates standard pack as an in-repo
kind: profile spec file (packs/numeric-predicates.md, id
numeric.predicates) whose manifest declares the ## Quantities
section, the numeric.quantity, numeric.bound, and
numeric.tolerance kinds, the measured_by reference field, the
numeric.quantity_closed and numeric.tolerance_labels checkers, and
the tolerance case-label floor.
Scenario: Pack lints clean under pack_shape in every lifecycle state
- WHEN the pack file is parsed in draft, published, or deprecated state
- THEN pack_shape reports zero findings over the pack’s declared vocabulary
Requirement: The numeric-predicates pack is discovered by corpus scan with a pinned base
The pack’s manifest SHALL pin the specodelic.md Revision 14 base, and
the pack SHALL be discovered by corpus scan alone — with no config file
and no registry.
Scenario: Corpus scan discovers the pack
- WHEN
spk lintruns over any workspace subdirectory of a repository containingpacks/numeric-predicates.md - THEN the pack is discovered without configuration and reported in the lint envelope
- AND files using none of the pack’s vocabulary keep byte-identical lint findings
Requirement: Quantity vocabulary is namespaced and per-pack closed
The pack SHALL declare a ## Quantities section with row shape
| name | kind | unit | domain |, kind closed to quantity, bound,
tolerance, and namespaced kinds numeric.quantity, numeric.bound,
numeric.tolerance — pack-fiber-relative vocabulary that grows no base
closed set and narrows no existing base set entry.
Scenario: Consumer activates the pack advisory-first
- WHEN a corpus spec uses
numeric.quantityvocabulary without a declaredusesedge - THEN the pack’s declared checking activates advisory-first for that file, naming the pack
Scenario: Orphan vocabulary names the numeric pack
- WHEN a workspace uses
numeric.quantityvocabulary with no numeric-predicates pack discovered or declared - THEN the orphan finding names the candidate pack
numeric.predicatesand both remediations, with non-zero exit
Requirement: measured_by is a typed outbound leaf resolving to declared rows
The pack SHALL add the measured_by reference field, resolving to a
## Quantities row of the declaring file and joining no reachability
path and no acyclic edge set, and SHALL declare the
numeric.quantity_closed checker that reports an empty checked-set
honestly when no ## Quantities rows exist.
Scenario: Dangling measured_by reference is labeled
- WHEN a constraint row references a
## Quantitiesrow id viameasured_bythat no## Quantitiesrow in the file declares - THEN the finding is labeled, names the row id and both remediations, and is never a generic dangling message
Scenario: Honest-empty closure with no quantity rows
- WHEN the pack is active in a workspace whose specs declare no
## Quantitiesrows - THEN the closure checker reports an empty checked-set with no fabricated findings
Requirement: Unit systems stay external
The pack SHALL bridge, never absorb, unit systems: the unit and
domain columns carry typed prose pointers (UCUM codes, ISO4217
currencies, domains of validity) that the format never parses, and no
unit system or conversion rule ever joins a base closed set — lint
behavior is identical regardless of the unit system named.
Scenario: Any unit system binds without lint change
- WHEN a
## Quantitiesrow names UCUM units, ISO4217 currencies, or any other unit system - THEN lint findings are byte-identical across the different unit systems
Requirement: Pack vocabulary is prose-safe
The pack SHALL NOT declare any vocabulary token that is a bare English
word appearing in corpus prose: the within, bound, against,
unit, and domain audit excludes them from the Sections, Kinds, and
References facets, and bound/against survive only as the tolerance
floor’s case labels, which the activation scanner never reads — corpus
lint warnings stay byte-identical with the pack discovered.
Scenario: Corpus prose does not falsely activate the pack
- WHEN the corpus lints with the pack discovered and again with it removed
- THEN issues and warnings are byte-identical — no vocabulary-match activations from prose words
Scenario: Tolerance floor names its case labels
- WHEN a
numeric.tolerancelaw property is checked against the pack’s floor declaration - THEN the floor names
boundandagainstas the required case labels
empirical-registry-pack — capability
id: empirical.registry.pack kind: intent statement: “WHEN a workspace enables the empirical-registry standard pack, THE format SHALL provide a namespaced ## StatTests section whose rows carry name, metric, alpha, and window; an empirical.statistic property kind; a tested_by reference field resolving to declared ## StatTests rows of the same file; and a per-kind case-label floor enumerating alpha and window on statistic properties — while the base law floor stays untouched and files using none of this vocabulary lint byte-identically.”
empirical-registry-pack Specification
Purpose
Ship the empirical-registry standard pack: the Floors facet’s first real
consumer — a namespaced ## StatTests registry table, the
empirical.statistic property kind, and a per-kind case-label floor
(alpha + window) reusing the **name:** enumeration machinery with
a different required label set than the base law floor. Statistical
tests stay authoritative outside the format — the pack declares the
registry, the mechanism interprets it. It is the third standard pack
and, with data/lineage and numeric predicates, completes what the
bioimage D6 pilot consumes.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[empirical.registry.pack]] |
| pack_file_declared | invariant | the empirical-registry standard pack is a kind: profile spec file at packs/empirical-registry.md (id empirical.registry) whose six manifest tables declare the ## StatTests section, the empirical.statistic kind, the tested_by reference field, the empirical.statistic_labels and empirical.stat_test_closed checkers, the statistic case-label floor, and the Revision 14 base pin | [[empirical.registry.pack]] |
| stat_test_section_shape | invariant | ## StatTests rows carry exactly | name | metric | alpha | window | — the registry of the workspace's statistical tests; alpha and window columns are opaque prose the format never parses, and no base closed set grows and no existing entry narrows | [[empirical.registry.pack]] |
| tested_by_outbound | invariant | tested_by is a pack-added reference field resolving to a ## StatTests row of the declaring file — an outbound leaf joining no reachability path and no acyclic edge set; a dangling reference is a labeled finding naming the row id and both remediations, never a generic dangling message | [[empirical.registry.pack]] |
| floors_are_additive | invariant | the per-kind floor requires statistic properties to enumerate their alpha and window case labels; the base law floor (identity/associativity) is byte-identical with the pack discovered, a kind with no declared floor inherits none, the base floor is a floor not a ceiling (a law-kind row using the pack's vocabulary may carry the labels additively), and floor labels are never activation vocabulary | [[empirical.registry.pack]] |
| fiber_kinds_typeable | invariant | when the pack is active, base-table kind columns accept its pack-qualified fiber kinds — property_kind_closed's effective set is base ∪ active-pack-fiber, never narrower; without the pack active the token stays outside the closed set and the labeled finding fires — never a silent pass | [[empirical.registry.pack]] |
| vocabulary_prose_safe | invariant | no pack vocabulary token is a bare English word that appears in corpus prose — window appears in corpus prose and is excluded from every vocabulary-carrying facet, surviving only as a floor case label and a table column; the kind is dotted; StatTests and tested_by are verified absent from the corpus | [[empirical.registry.pack]] |
Model
States
draftpublisheddeprecated
Transitions
| id | from | to | guard |
|---|---|---|---|
| publish | draft | published | [[empirical.registry.pack.pack_file_declared]] ∧ [[empirical.registry.pack.stat_test_section_shape]] — pack_shape reports zero findings over the pack's declared vocabulary |
| deprecate | published | deprecated | [[empirical.registry.pack.pack_file_declared]] — maintainer marks the pack deprecated; findings name the deprecation, vocabulary still checks |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[empirical.registry.pack.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| discovery_finds_empirical_pack | unit | [[empirical.registry.pack.pack_file_declared]] | lint_workspace_with_empirical_pack_present() | the pack is discovered by corpus scan alone, no config file, no registry |
| pack_shape_clean_all_states | unit | [[empirical.registry.pack.pack_file_declared]] | pack_parsed_in_every_lifecycle_state() | pack_shape reports zero findings over the declared vocabulary in draft, published, and deprecated |
| consumer_activates_advisory | unit | [[empirical.registry.pack.stat_test_section_shape]] | corpus_spec_uses_empirical_statistic_vocabulary() | the pack's checkers activate advisory-first naming the pack; findings attributed per pack |
| orphan_names_empirical_pack | unit | [[empirical.registry.pack.stat_test_section_shape]] | workspace_uses_empirical_vocabulary_without_pack() | the orphan finding names the candidate pack empirical.registry and both remediations, exit non-zero |
| tested_by_resolves | unit | [[empirical.registry.pack.tested_by_outbound]] | property_row_tested_by_declared_stat_test_row() | the tested_by reference resolves to the declared ## StatTests row; a dangling reference is a labeled finding naming the row id and both remediations |
| closure_honest_empty | unit | [[empirical.registry.pack.tested_by_outbound]] | pack_active_without_stat_test_rows() | the closure checkers report an empty checked-set; no fabricated findings |
| law_floor_unchanged | unit | [[empirical.registry.pack.floors_are_additive]] | law_property_linted_with_and_without_pack() | law floor findings byte-identical with the pack discovered; a kind with no floor inherits none |
| statistic_floor_labels | unit | [[empirical.registry.pack.floors_are_additive]] | statistic_property_missing_case_label() | an empirical.statistic property owes **alpha:** and **window:** case labels per the floor declaration |
| kind_column_accepts_fiber_kinds | unit | [[empirical.registry.pack.fiber_kinds_typeable]] | property_row_typed_empirical_statistic_with_pack_active() | property_kind_closed accepts the fiber kind when the pack is active; the same row without the pack fires the labeled finding |
| no_bare_word_vocabulary | unit | [[empirical.registry.pack.vocabulary_prose_safe]] | corpus_linted_with_and_without_empirical_pack() | lint warnings byte-identical for every corpus file — no vocabulary-match activations from prose words |
Requirements
Requirement: The empirical-registry standard pack is a declared profile file
The format SHALL ship the empirical-registry standard pack as an in-repo
kind: profile spec file (packs/empirical-registry.md, id
empirical.registry) whose manifest declares the ## StatTests
section, the empirical.statistic kind, the tested_by reference
field, the empirical.statistic_labels and empirical.stat_test_closed
checkers, the statistic case-label floor, and the
specodelic.md Revision 14 base pin — discovered by corpus scan alone,
with no config file and no registry.
Scenario: Corpus scan discovers the pack
- WHEN
spk lintruns over any workspace subdirectory of a repository containingpacks/empirical-registry.md - THEN the pack is discovered without configuration and reported in the lint envelope
- AND files using none of the pack’s vocabulary keep byte-identical lint findings
Scenario: Pack lints clean under pack_shape in every lifecycle state
- WHEN the pack file is parsed in draft, published, or deprecated state
- THEN pack_shape reports zero findings over the pack’s declared vocabulary
Requirement: The StatTests registry is namespaced and per-pack closed
The pack SHALL declare a ## StatTests section with row shape
| name | metric | alpha | window | — the workspace’s declared
statistical tests — and the namespaced kind empirical.statistic,
pack-fiber-relative vocabulary that grows no base closed set and
narrows no existing base set entry; the alpha and window columns
stay opaque prose the format never parses.
Scenario: Consumer activates the pack advisory-first
- WHEN a corpus spec uses
empirical.statisticvocabulary without a declaredusesedge - THEN the pack’s declared checking activates advisory-first for that file, naming the pack
Scenario: Orphan vocabulary names the empirical pack
- WHEN a workspace uses
empirical.statisticvocabulary with no empirical-registry pack discovered or declared - THEN the orphan finding names the candidate pack
empirical.registryand both remediations, with non-zero exit
Requirement: tested_by is a typed outbound leaf resolving to declared rows
The pack SHALL add the tested_by reference field, resolving to a
## StatTests row of the declaring file and joining no reachability
path and no acyclic edge set, and SHALL declare the
empirical.stat_test_closed checker that reports an empty checked-set
honestly when no ## StatTests rows exist.
Scenario: Dangling tested_by reference is labeled
- WHEN a property row references a
## StatTestsrow id viatested_bythat no## StatTestsrow in the file declares - THEN the finding is labeled, names the row id and both remediations, and is never a generic dangling message
Scenario: Honest-empty closure with no stat-test rows
- WHEN the pack is active in a workspace whose specs declare no
## StatTestsrows - THEN the closure checker reports an empty checked-set with no fabricated findings
Requirement: The per-kind floor is additive enumeration
The pack SHALL declare the floor requiring empirical.statistic
properties to enumerate their **alpha:** and **window:** case
labels, reusing the **name:** enumeration machinery with a different
required label set — while the base law floor stays byte-identical
with the pack discovered and a kind with no declared floor inherits
none; floor labels never join the activation vocabulary.
Scenario: Law properties keep their base floor
- WHEN a base
lawproperty is linted with the empirical-registry pack discovered - THEN its floor findings are byte-identical to the lint without the pack
Scenario: Statistic floor names its case labels
- WHEN an
empirical.statisticproperty is checked against the pack’s floor declaration - THEN the floor names
alphaandwindowas the required case labels
Requirement: Pack-fiber kinds are typeable in base-table kind columns
When the pack is active, the format SHALL accept its pack-qualified
fiber kinds in base-table kind columns — property_kind_closed‘s
effective set is the base set extended with active packs’ fiber
vocabulary, never narrower than the base set — and without the pack
active the token stays outside the closed set, firing the labeled
finding, never passing silently.
Scenario: Active pack makes the fiber kind typeable
- WHEN a Properties row carries
kind = empirical.statisticand the empirical-registry pack is discovered/declared - THEN
property_kind_closedaccepts the row — no finding
Scenario: Inactive pack keeps the closed set closed
- WHEN a Properties row carries
kind = empirical.statisticwith no empirical-registry pack discovered or declared - THEN the labeled
property_kind_closedfinding fires naming the closed set
Requirement: Pack vocabulary is prose-safe
The pack SHALL NOT declare any vocabulary token that is a bare English
word appearing in corpus prose: window appears in corpus prose and is
excluded from the Sections, Kinds, and References facets, surviving only
as the statistic floor’s case label and a table column, which the
activation scanner never reads; the kind is dotted
(empirical.statistic); StatTests and tested_by are verified absent
from the corpus — corpus lint warnings stay byte-identical with the pack
discovered.
Scenario: Corpus prose does not falsely activate the pack
- WHEN the corpus lints with the pack discovered and again with it removed
- THEN issues and warnings are byte-identical — no vocabulary-match activations from prose words
Scenario: Prose window mentions stay inert
- WHEN a corpus spec’s prose contains the English word
window(the rate-limit window pattern) - THEN the pack is not activated by that word — no warning, no finding
bioimage-data-pack — capability
id: bioimage.data.pack kind: intent statement: “WHEN a workspace enables the bioimage-data domain pack, THE format SHALL provide a namespaced ## Axes section whose rows carry name, axis, scale, and unit; a bioimage.transform property kind typeable in base-table kind columns when the pack is active; a same_shape_as reference field resolving to a ## Data row of the declaring file; the R2 data-shaped predicate grammar declared as named pack-qualified checkers (bioimage.dtype_is, bioimage.shape_eq, bioimage.units_convertible) resolving against declared ## Data rows; and a kind-dependent transform floor enumerating preserves and dtype — while OME/NGFF and BioImage.IO stay authoritative external standards, no base closed set grows, and files using none of this vocabulary lint byte-identically.”
bioimage-data-pack Specification
Purpose
Ship the bioimage-data domain pack — the D6 pilot for the domain-pack
mechanism: the thin instance that stress-tests what the three standard
packs could not exercise alone. It consumes the data/lineage pack’s
## Data section (same_shape_as resolution, the dtype_is predicate
class), the numeric-predicates pack’s opaque unit domain
(units_convertible bridging), and the empirical-registry pack’s
stat-test rows (empirically held transform claims) via the mechanism’s
first cross-pack ## Requires table. OME/NGFF and BioImage.IO stay
authoritative external standards — the pack bridges them through opaque
scale/unit columns, it never absorbs them. Checkers ship as
declarations (honest-empty): this pack declares vocabulary, the
mechanism (packs.md) interprets it.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[bioimage.data.pack]] |
| pack_file_declared | invariant | the bioimage-data domain pack is a kind: profile spec file at packs/bioimage-data.md (id bioimage.data) whose six manifest tables declare the ## Axes section, the bioimage.transform kind, the same_shape_as reference field, the bioimage.dtype_is, bioimage.shape_eq, and bioimage.units_convertible checkers, the transform case-label floor, and the Requires table pinning base specodelic.md Revision 14 plus the data.lineage, numeric.predicates, and empirical.registry pack deps | [[bioimage.data.pack]] |
| axes_section_shape | invariant | ## Axes rows carry exactly | name | axis | scale | unit | — the workspace's declared image axes (OME-NGFF-style semantics); the scale and unit columns are opaque prose the format never parses, and no base closed set grows and no existing entry narrows | [[bioimage.data.pack]] |
| same_shape_as_cross_pack | invariant | same_shape_as is a pack-added reference field resolving to a ## Data row of the declaring file — the data-lineage pack's section, the pack's cross-pack-ness living in its Requires pack deps while the resolution stays intra-file, an outbound leaf joining no reachability path and no acyclic edge set; a dangling reference is a labeled finding naming the row id and both remediations, never a generic dangling message | [[bioimage.data.pack]] |
| predicates_closed_named | invariant | the R2 data-shaped predicate grammar is declared as the named pack-qualified checkers bioimage.dtype_is, bioimage.shape_eq, and bioimage.units_convertible — a closed set over declared ## Data rows, declaration-only (the mechanism names them, never executes them), honest-empty with no ## Data rows, and the bare predicate tokens are never activation vocabulary | [[bioimage.data.pack]] |
| transform_floor_kind_dependent | invariant | the per-kind floor requires bioimage.transform properties to enumerate their preserves and dtype case labels — the kind-dependent floor the base law floor's non-universality critique motivated; the base law floor is byte-identical with the pack discovered, a kind with no declared floor inherits none, the floor is a floor not a ceiling (labels from consumed packs ride along additively), and floor labels are never activation vocabulary | [[bioimage.data.pack]] |
| kind_column_typeable | invariant | when the pack is active, base-table kind columns accept its pack-qualified fiber kinds — property_kind_closed's effective set is base ∪ active-pack-fiber, never narrower; without the pack active the token stays outside the closed set and the labeled finding fires — never a silent pass | [[bioimage.data.pack]] |
| requires_declares_pack_deps | invariant | the pack-authored ## Requires table pins the base format_revision and names the data.lineage, numeric.predicates, and empirical.registry pack deps with the base corpus revision each was verified against — the mechanism's first cross-pack Requires consumer; a dep with no discovered pack is a labeled advisory, never silent | [[bioimage.data.pack]] |
| vocabulary_prose_safe | invariant | no pack vocabulary token is a bare English word that appears in corpus prose — bare bioimage appears in corpus prose as documented asymmetry (dotted tokens cannot match prose words), pipeline is prose-heavy and rejected, the predicate names ride pack-qualified checker rule names, Axes and same_shape_as and preserves are verified absent from the corpus, and dtype survives only as an opaque column header and floor case label — corpus lint warnings stay byte-identical with the pack discovered | [[bioimage.data.pack]] |
Model
States
draftpublisheddeprecated
Transitions
| id | from | to | guard |
|---|---|---|---|
| publish | draft | published | [[bioimage.data.pack.pack_file_declared]] ∧ [[bioimage.data.pack.axes_section_shape]] — pack_shape reports zero findings over the pack's declared vocabulary |
| deprecate | published | deprecated | [[bioimage.data.pack.pack_file_declared]] — maintainer marks the pack deprecated; findings name the deprecation, vocabulary still checks |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| process_lifecycle_checked | unit | [[bioimage.data.pack.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| discovery_finds_bioimage_pack | unit | [[bioimage.data.pack.pack_file_declared]] | lint_workspace_with_bioimage_pack_present() | the pack is discovered by corpus scan alone, no config file, no registry |
| pack_shape_clean_all_states | unit | [[bioimage.data.pack.pack_file_declared]] | pack_parsed_in_every_lifecycle_state() | pack_shape reports zero findings over the declared vocabulary in draft, published, and deprecated |
| consumer_activates_advisory | unit | [[bioimage.data.pack.axes_section_shape]] | corpus_spec_uses_bioimage_vocabulary() | the pack's checkers activate advisory-first naming the pack; findings attributed per pack |
| orphan_names_bioimage_pack | unit | [[bioimage.data.pack.axes_section_shape]] | workspace_uses_bioimage_vocabulary_without_pack() | the orphan finding names the candidate pack bioimage.data and both remediations, exit non-zero |
| same_shape_as_resolves | unit | [[bioimage.data.pack.same_shape_as_cross_pack]] | constraint_row_same_shape_as_declared_data_row() | the same_shape_as reference resolves to the declared ## Data row of the same file; a dangling reference is a labeled finding naming the row id and both remediations |
| requires_deps_resolve | unit | [[bioimage.data.pack.requires_declares_pack_deps]] | bioimage_pack_requires_table_parsed_with_deps() | the pack deps resolve to discovered pack ids; a dep with no discovered pack is a labeled advisory naming it, never silent |
| predicates_honest_empty | unit | [[bioimage.data.pack.predicates_closed_named]] | pack_active_without_data_rows() | the predicate checkers report an empty checked-set with no ## Data rows; no fabricated findings; the bare predicate tokens are never declared |
| transform_floor_labels | unit | [[bioimage.data.pack.transform_floor_kind_dependent]] | transform_property_missing_case_label() | a bioimage.transform property owes **preserves:** and **dtype:** case labels per the floor declaration |
| law_floor_unchanged | unit | [[bioimage.data.pack.transform_floor_kind_dependent]] | law_property_linted_with_and_without_pack() | law floor findings byte-identical with the pack discovered; a kind with no floor inherits none |
| kind_column_accepts_fiber_kinds | unit | [[bioimage.data.pack.kind_column_typeable]] | property_row_typed_bioimage_transform_with_pack_active() | property_kind_closed accepts the fiber kind when the pack is active; the same row without the pack fires the labeled finding |
| no_bare_word_vocabulary | unit | [[bioimage.data.pack.vocabulary_prose_safe]] | corpus_linted_with_and_without_bioimage_pack() | lint warnings byte-identical for every corpus file — no vocabulary-match activations from prose words |
Requirements
Requirement: The bioimage-data domain pack is a declared profile file
The format SHALL ship the bioimage-data domain pack as an in-repo
kind: profile spec file (packs/bioimage-data.md, id bioimage.data)
whose manifest declares the ## Axes section, the bioimage.transform
kind, the same_shape_as reference field, the bioimage.dtype_is,
bioimage.shape_eq, and bioimage.units_convertible checkers, and the
transform case-label floor.
Scenario: Pack lints clean under pack_shape in every lifecycle state
- WHEN the pack file is parsed in draft, published, or deprecated state
- THEN pack_shape reports zero findings over the pack’s declared vocabulary
Requirement: The bioimage-data pack is discovered by corpus scan with a pinned Requires table
The pack’s manifest SHALL declare a ## Requires table pinning the
specodelic.md Revision 14 base plus the data.lineage,
numeric.predicates, and empirical.registry pack deps, and the pack
SHALL be discovered by corpus scan alone — with no config file and no
registry.
Scenario: Corpus scan discovers the pack
- WHEN
spk lintruns over any workspace subdirectory of a repository containingpacks/bioimage-data.md - THEN the pack is discovered without configuration and reported in the lint envelope
- AND files using none of the pack’s vocabulary keep byte-identical lint findings
Requirement: The Axes registry is namespaced with opaque scale and unit
The pack SHALL declare a ## Axes section with row shape
| name | axis | scale | unit | — the workspace’s declared image axes
with OME-NGFF-style semantics — and the namespaced kind
bioimage.transform, pack-fiber-relative vocabulary that grows no base
closed set and narrows no existing base set entry; the scale and
unit columns stay opaque prose the format never parses, keeping OME/NGFF
and BioImage.IO authoritative as external standards.
Scenario: Consumer activates the pack advisory-first
- WHEN a corpus spec uses
bioimage.transformor## Axesvocabulary without a declaredusesedge - THEN the pack’s declared checking activates advisory-first for that file, naming the pack
Scenario: Orphan vocabulary names the bioimage pack
- WHEN a workspace uses
bioimage.transformvocabulary with no bioimage-data pack discovered or declared - THEN the orphan finding names the candidate pack
bioimage.dataand both remediations, with non-zero exit
Requirement: same_shape_as is a typed outbound leaf resolving cross-pack, file-locally
The pack SHALL add the same_shape_as reference field, resolving to a
## Data row of the declaring file — the data-lineage pack’s section —
with the pack dependency living in the pack’s ## Requires table while
the resolution itself stays intra-file, joining no reachability path and
no acyclic edge set; a dangling reference is a labeled finding naming the
row id and both remediations.
Scenario: Cross-pack section resolution stays file-local
- WHEN a constraint row declares
same_shape_aspointing at a## Datarow declared in the same file, with the data-lineage pack active for the file - THEN the reference resolves with no corpus-wide pass and no reachability join
Scenario: Dangling same_shape_as reference is labeled
- WHEN a row references a
## Datarow id viasame_shape_asthat no## Datarow in the file declares - THEN the finding is labeled, names the row id and both remediations, and is never a generic dangling message
Requirement: The R2 predicate grammar is a declared closed set of named checkers
The pack SHALL declare the data-shaped predicate grammar as the named
pack-qualified checkers bioimage.dtype_is, bioimage.shape_eq, and
bioimage.units_convertible — a closed set resolving against declared
## Data rows — declaration-only, with the bare predicate tokens never
declared as activation vocabulary and the checkers reporting an empty
checked-set honestly when no ## Data rows exist.
Scenario: Predicates resolve against declared Data rows
- WHEN a law predicate uses a declared predicate name over
## Datarows in a file with the pack active - THEN the corresponding named checker covers the predicate occurrence against the declared
## Datarows
Scenario: Honest-empty predicates with no data rows
- WHEN the pack is active in a workspace whose specs declare no
## Datarows - THEN the predicate checkers report an empty checked-set with no fabricated findings
Requirement: The transform floor is kind-dependent and additive
The pack SHALL declare the floor requiring bioimage.transform
properties to enumerate their **preserves:** and **dtype:** case
labels — the kind-dependent floor making the base law floor’s
non-universality critique concrete — while the base law floor stays
byte-identical with the pack discovered, a kind with no declared floor
inherits none, case labels from the consumed packs ride along
additively, and floor labels never join the activation vocabulary.
Scenario: Law properties keep their base floor
- WHEN a base
lawproperty is linted with the bioimage-data pack discovered - THEN its floor findings are byte-identical to the lint without the pack
Scenario: Transform floor names its case labels
- WHEN a
bioimage.transformproperty is checked against the pack’s floor declaration - THEN the floor names
preservesanddtypeas the required case labels
Requirement: Pack-fiber kinds are typeable in base-table kind columns
When the pack is active, the format SHALL accept its pack-qualified
fiber kinds in base-table kind columns — property_kind_closed‘s
effective set is the base set extended with active packs’ fiber
vocabulary, never narrower than the base set — and without the pack
active the token stays outside the closed set, firing the labeled
finding, never passing silently.
Scenario: Active pack makes the fiber kind typeable
- WHEN a Properties row carries
kind = bioimage.transformand the bioimage-data pack is discovered/declared - THEN
property_kind_closedaccepts the row — no finding
Scenario: Inactive pack keeps the closed set closed
- WHEN a Properties row carries
kind = bioimage.transformwith no bioimage-data pack discovered or declared - THEN the labeled
property_kind_closedfinding fires naming the closed set
Requirement: The Requires table names its pack dependencies
The pack SHALL author a ## Requires table pinning the
specodelic.md Revision 14 base and naming the data.lineage,
numeric.predicates, and empirical.registry pack deps with the base
corpus revision each was verified against — the mechanism’s first
cross-pack ## Requires consumer — where a dep naming no discovered
pack is a labeled advisory, never silent.
Scenario: Pack deps resolve to discovered packs
- WHEN the bioimage-data pack is discovered in a workspace that also carries the three standard packs
- THEN each
## Requiresdep row resolves to a discovered pack id with its verified-at revision recorded
Scenario: Missing pack dep is a labeled advisory
- WHEN a
## Requiresdep row names a pack id that no discovered pack in the workspace declares - THEN the advisory names the missing dep on the warnings channel and lint does not fail silently
Requirement: Pack vocabulary is prose-safe
The pack SHALL NOT declare any vocabulary token that is a bare English
word appearing in corpus prose: bare bioimage appears in corpus prose
as documented asymmetry (dotted tokens cannot match prose words),
pipeline is prose-heavy and rejected from this release, and Axes,
same_shape_as, and preserves are verified absent from the corpus —
corpus lint warnings stay byte-identical with the pack discovered.
Scenario: Prose bioimage mentions stay inert
- WHEN a corpus doc’s prose mentions the word
bioimage(the packs paragraph in USAGE) - THEN the pack is not activated by that word — no warning, no finding
Requirement: Pack-only vocabulary rides opaque scanner-invisible positions
The pack’s remaining vocabulary SHALL survive only in positions the
activation scanner never reads: the predicate names ride pack-qualified
checker rule names, and dtype survives only as the ## Axes row’s
opaque column header and the transform floor’s case label.
Scenario: Corpus prose does not falsely activate the pack
- WHEN the corpus lints with the pack discovered and again with it removed
- THEN issues and warnings are byte-identical — no vocabulary-match activations from prose words
quant-finance-pack — capability
id: quant.finance.pack kind: intent statement: “WHEN a workspace enables the quant-finance standard pack, THE format SHALL provide a namespaced ## Limits section whose rows carry name, kind, unit, and bound; quant.risk and quant.pricing kinds; a capped_by reference field resolving to declared ## Limits rows of the same file; the limit-closure and risk-label pack-qualified checkers (quant.limit_closed, quant.risk_labels); and a kind-dependent case-label floor enumerating horizon and confidence on risk laws — while ISO4217 and market-data conventions stay authoritative external standards, no base closed set grows, and files using none of this vocabulary lint byte-identically.”
quant-finance-pack Specification
Purpose
Ship the quant-finance standard pack: a typed ## Limits section for
bounded risk measures and pricing invariants, namespaced quant kinds, a
kind-dependent risk case-label floor, and a capped_by outbound-leaf
edge. Most of the vendor convergence already ships in the standard
packs — this pack declares only the vocabulary no standard pack
carries, and requires numeric.predicates + data.lineage for the
rest. It is the D6 second domain pack.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| pack_file_declared | invariant | the quant-finance standard pack is a kind: profile spec file at packs/quant-finance.md (id quant.finance) whose six manifest tables declare the ## Limits section, the quant.risk and quant.pricing kinds, the capped_by reference field, the quant.limit_closed and quant.risk_labels checkers, the risk case-label floor, and the Revision 18 base pin plus the numeric.predicates and data.lineage pack deps | [[quant.finance.pack]] |
| limits_section_shape | invariant | ## Limits rows carry exactly | name | kind | unit | bound | with kind closed to risk, pricing — a per-pack closed set; no base closed set grows and no existing entry narrows | [[quant.finance.pack]] |
| capped_by_outbound | invariant | capped_by is a pack-added reference field resolving to a ## Limits row of the declaring file — an outbound leaf joining no reachability path and no acyclic edge set; a dangling reference is a labeled finding naming the row id and both remediations, never a generic dangling message | [[quant.finance.pack]] |
| fixtures_stay_data | invariant | backtest fixtures and datasets are data.lineage ## Data rows — the pack declares no fixtures section and no second dataset vocabulary; the vendor Fixtures demand is satisfied by the required data.lineage pack | [[quant.finance.pack]] |
| vocabulary_prose_safe | invariant | no pack vocabulary token is a bare English word that appears in corpus prose — the limit/confidence/Fixtures audit excluded them from vocabulary-carrying facets; horizon and confidence survive only as floor case labels, which the mechanism never scans | [[quant.finance.pack]] |
Model
States
draftpublisheddeprecated
Transitions
| id | from | to | guard |
|---|---|---|---|
| publish | draft | published | [[quant.finance.pack.pack_file_declared]] ∧ [[quant.finance.pack.limits_section_shape]] — pack_shape reports zero findings over the pack's declared vocabulary |
| deprecate | published | deprecated | [[quant.finance.pack.pack_file_declared]] — maintainer marks the pack deprecated; findings name the deprecation, vocabulary still checks |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| discovery_finds_quant_pack | unit | [[quant.finance.pack.pack_file_declared]] | lint_workspace_with_quant_pack_present() | the pack is discovered by corpus scan alone, no config file, no registry |
| no_pack_no_change | unit | [[quant.finance.pack.pack_file_declared]] | corpus_linted_before_and_after_quant_pack() | lint findings byte-identical for a corpus using no quant vocabulary |
| honest_empty | unit | [[quant.finance.pack.capped_by_outbound]] | quant_pack_active_with_no_limits_rows() | quant.limit_closed reports an empty checked-set with no fabricated findings |
| limits_rows_namespaced | unit | [[quant.finance.pack.limits_section_shape]] | limits_row_shape_checked_by_pack_shape() | ## Limits rows carry exactly | name | kind | unit | bound |; the row-kind labels stay closed to risk, pricing — a per-pack closed set that never grows a base set |
| fixtures_are_data_rows | unit | [[quant.finance.pack.fixtures_stay_data]] | corpus_declares_no_fixtures_section() | the pack declares no Fixtures section; backtest fixtures and datasets are data.lineage ## Data rows via the required data.lineage pack dep |
| vocabulary_prose_safe_verified | unit | [[quant.finance.pack.vocabulary_prose_safe]] | vocabulary_surface_equals_declared_tokens() | the pack's vocabulary() is exactly [quant.risk, quant.pricing, Limits, capped_by]; floor case labels never join it; corpus prose using none of it lints byte-identically |
Requirements
Requirement: The quant-finance standard pack is a declared profile file
The format SHALL ship the quant-finance standard pack as an in-repo
kind: profile spec file (packs/quant-finance.md, id
quant.finance) whose manifest declares the ## Limits section, the
quant.risk and quant.pricing kinds, the capped_by reference
field, the quant.limit_closed and quant.risk_labels checkers, and
the risk case-label floor.
Scenario: Pack lints clean under pack_shape in every lifecycle state
- WHEN the pack file is parsed in draft, published, or deprecated state
- THEN pack_shape reports zero findings over the pack’s declared vocabulary
Requirement: The quant-finance pack is discovered by corpus scan with pinned deps
The pack’s manifest SHALL pin the specodelic.md Revision 18 base plus
the numeric.predicates and data.lineage pack deps in a ## Requires
table, and the pack SHALL be discovered by corpus scan alone — with no
config file and no registry.
Scenario: Corpus scan discovers the pack
- WHEN
spk lintruns over any workspace subdirectory of a repository containingpacks/quant-finance.md - THEN the pack is discovered without configuration and reported in the lint envelope
- AND files using none of the pack’s vocabulary keep byte-identical lint findings
Requirement: Limits vocabulary is namespaced and per-pack closed
The pack SHALL declare a ## Limits section with row shape
| name | kind | unit | bound |, kind closed to risk, pricing,
and namespaced kinds quant.risk, quant.pricing —
pack-fiber-relative vocabulary that grows no base closed set and
narrows no existing base set entry.
Scenario: Consumer activates the pack advisory-first
- WHEN a corpus spec uses
quant.riskvocabulary without a declaredusesedge - THEN the pack’s declared checking activates advisory-first for that file, naming the pack
Scenario: Orphan vocabulary names the quant pack
- WHEN a workspace uses
quant.riskvocabulary with no quant-finance pack discovered or declared - THEN the orphan finding names the candidate pack
quant.financeand both remediations, with non-zero exit
Requirement: capped_by is a typed outbound leaf resolving to declared rows
The pack SHALL add the capped_by reference field, resolving to a
## Limits row of the declaring file and joining no reachability path
and no acyclic edge set, and SHALL declare the quant.limit_closed
checker that reports an empty checked-set honestly when no ## Limits
rows exist.
Scenario: Dangling capped_by reference is labeled
- WHEN a constraint row references a
## Limitsrow id viacapped_bythat no## Limitsrow in the file declares - THEN the finding is labeled, names the row id and both remediations, and is never a generic dangling message
Scenario: Honest-empty closure with no limits rows
- WHEN the pack is active in a workspace whose specs declare no
## Limitsrows - THEN the closure checker reports an empty checked-set with no fabricated findings
Requirement: The risk floor names its case labels
The pack SHALL declare a kind-dependent floor: quant.risk laws and
limits enumerate their **horizon:** and **confidence:** case labels
per the floor declaration, reusing the machine-findable **name:**
case-label enumeration mechanism with a different required label set —
and SHALL NOT re-declare a second tolerance kind (the numeric pack’s
numeric.tolerance and its bound, against floor remain the
tolerance vocabulary).
Scenario: Risk floor names its case labels
- WHEN a
quant.risklaw property is checked against the pack’s floor declaration - THEN the floor names
horizonandconfidenceas the required case labels
Scenario: Tolerance semantics reuse the numeric pack
- WHEN a quant spec file states a tolerance claim on a quantity
- THEN the claim is declared as a
numeric.tolerancerow under the requirednumeric.predicatespack, with no second tolerance kind from this pack
Requirement: Fixtures stay in the data-lineage pack
The pack SHALL NOT declare a fixtures section: backtest fixtures and
datasets are the required data.lineage pack’s ## Data rows, and the
Fixtures audit excludes them from the pack’s Sections, Kinds, and
References facets.
Scenario: Backtest fixtures are Data rows
- WHEN a quant spec file declares a backtest fixture or dataset
- THEN it is a
data.datasetordata.artifactrow of the declaring file’s## Datatable under the requireddata.lineagepack
Requirement: Pack vocabulary is prose-safe
The pack SHALL NOT declare any vocabulary token that is a bare English
word appearing in corpus prose: limit and confidence are prose-heavy
and excluded from the Kinds and References facets, while horizon and
confidence survive only as the risk floor’s case labels, which the
activation scanner never reads — corpus lint warnings stay
byte-identical with the pack discovered.
Scenario: Corpus prose does not falsely activate the pack
- WHEN the corpus lints with the pack discovered and again with it removed
- THEN issues and warnings are byte-identical — no vocabulary-match activations from prose words
skill-pack — capability
id: skill.pack kind: intent statement: “WHEN the specodelic binary ships its first-party design skill, THE format SHALL generate the specodelic-design skill pack from single tracked sources embedded via include_str! — membership-testing every explain topic, lint rule, and EARS pattern mention against the in-binary tables, embedding the worked example verbatim and linting it clean with the shipped linter, marking every installed file as managed with the regenerate command, refusing to overwrite non-marker files while writing the pack and its content hash under the requested target directory, and flagging stale installs in spk doctor without failing the run — while the docs point at the procedure without duplicating it.”
skill-pack Specification
Purpose
Ship the specodelic-design skill from this repo so the skill and the
binary evolve in the same commit: the pack is generated from single
tracked sources (include_str!), rendered through the same tables that
serve spk explain, installed by a fail-closed verb, and staleness-
checked by the doctor. Distribution is the verb (design D7) — no zip in
git, and the docs stay thin pointers to the single authoring surfaces.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| embedded_single_source | invariant | the pack is generated from content compiled in via include_str! over tracked repo files — skill-packs/specodelic-design/SKILL.md for the protocol prose and docs/src/examples/reservation-order.md for the worked example — with the format reference rendered through the same machinery that serves spk explain; no hand-copied explain table, rule list, or worked example exists in the pack, and two generations without an intervening source change are byte-identical | [[skill.pack]] |
| mentions_membership_tested | invariant | every spk explain topic name, lint rule id, and EARS pattern name mentioned in a generated pack file resolves against the in-binary table that serves it, enforced by an in-binary test that fails the build naming the unresolvable identifier — a hand-maintained list may be stale, a generated one cannot | [[skill.pack]] |
| example_lints_clean | invariant | references/example.md embeds docs/src/examples/reservation-order.md verbatim and passes spk lint with zero findings and zero warnings through the same entry point the corpus gate runs, so a corpus change that would make the embedded example lint-dirty fails the test before release | [[skill.pack]] |
| marker_marks_every_install | invariant | every file spk skill install writes begins with a managed marker block naming the owning tool and the exact regenerate command (Regenerate: spk skill install), and the marker is present even when that file is a verbatim embedding of a repo source that lacks it | [[skill.pack]] |
| install_writes_and_reports | invariant | spk skill install writes the generated pack files under the given directory (default ~/.claude/skills, creating parent directories), refuses to overwrite pre-existing non-marker-bearing files at a target path instead of silently replacing them, emits a JSON envelope (human-readable on TTYs) listing the written paths and a deterministic pack_content_hash computed as SHA-256 over the pack files sorted by relative path each hashed as path + NUL + content, and exits 0 installed, 1 write failure, 2 invalid invocation or overwrite refusal | [[skill.pack]] |
| doctor_flags_staleness | invariant | spk doctor includes a managed-skill-pack check that recomputes the generated pack hash and compares it against the on-disk installation under each configured target directory — pass when the hashes match, stale when they differ (naming the differing hash pair and the regenerate command), missing when no pack is installed — and the check is optional: a missing pack is reported with a regenerate hint and never fails the doctor run | [[skill.pack]] |
| docs_stay_thin_pointers | invariant | the repository docs reference the skill pack by pointer only — a thin book page linking the conversation procedure to its tracked source and the install command, the README command surface and SUMMARY.md and llms.txt naming the verb, and no page duplicating the protocol prose, the self-check list, or the worked example beyond the thin page's quoted one-line summary | [[skill.pack]] |
Model
States
trackedgeneratedinstalledstalerefused
Transitions
| id | from | to | guard |
|---|---|---|---|
| generate | tracked | generated | [[skill.pack.embedded_single_source]] ∧ [[skill.pack.mentions_membership_tested]] ∧ [[skill.pack.example_lints_clean]] |
| install | generated | installed | [[skill.pack.marker_marks_every_install]] ∧ [[skill.pack.install_writes_and_reports]] |
| refuse | generated | refused | a target path holds a file without the managed marker — [[skill.pack.install_writes_and_reports]] refuses before writing anything |
| drift | installed | stale | the installed pack hash differs from the regenerated pack hash — [[skill.pack.doctor_flags_staleness]] names both hashes and the regenerate command |
| refresh | stale | installed | [[skill.pack.install_writes_and_reports]] |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| generation_is_deterministic | unit | [[skill.pack.embedded_single_source]] | generate_pack_called_twice() | two generations are byte-identical, including the embedded example and the rendered topic and rule-name lists |
| dangling_mention_fails | unit | [[skill.pack.mentions_membership_tested]] | dangling_mention_in_fixture() | the in-binary membership test fails naming the unresolvable identifier, and the binary cannot ship with the dangling mention |
| example_lints_zero | unit | [[skill.pack.example_lints_clean]] | generated_example_over_shipped_linter() | zero issues and zero warnings over the generated example, and a dirtying corpus change fails the test |
| marker_survives_embedding | unit | [[skill.pack.marker_marks_every_install]] | installed_example_checked_for_marker() | the installed verbatim embedding starts with the managed marker while the repository source remains marker-free |
| fresh_install_enveloped | unit | [[skill.pack.install_writes_and_reports]] | install_into_an_empty_directory() | the pack files are written, the envelope lists exactly those paths with their hashes, and the exit code is 0 |
| foreign_file_refused | unit | [[skill.pack.install_writes_and_reports]] | install_over_a_non_marker_file() | the verb refuses with exit 2 and a remediation hint naming the conflicting path, leaving the file untouched |
| stale_install_flagged | unit | [[skill.pack.doctor_flags_staleness]] | doctor_over_an_older_binary_install() | the report contains a stale entry naming the target directory, the installed hash, the expected hash, and the regenerate command spk skill install |
| absent_pack_hinted | unit | [[skill.pack.doctor_flags_staleness]] | doctor_without_any_install() | the report contains a missing entry with the install hint, and the doctor exit code is unchanged by the check's absence |
| book_stays_thin | unit | [[skill.pack.docs_stay_thin_pointers]] | docs_capability_checks_over_the_book() | no book page contains a copy of the pack's protocol prose or worked example beyond the thin-pointer page's quoted one-line summary |
Requirements
Requirement: Pack content is embedded from single tracked sources
The specodelic-design skill pack SHALL be generated from
binary-embedded include_str! content over tracked files in this
repository (skill-packs/specodelic-design/SKILL.md for the protocol
prose, docs/src/examples/reservation-order.md for the worked-example
reference), with the format reference rendered by the same in-binary
machinery that serves the spk explain topics it summarizes. It SHALL NOT
contain a hand-copied copy of any explain table, lint rule list, or
worked example.
Scenario: No drift class in the pack
- WHEN the served
spk explaintopic list or lint rule catalog changes in the binary - THEN the next generated pack reflects the new list without any edit to the skill source, because the list is rendered from the served tables rather than restated in them
Requirement: Pack generation is deterministic
Pack generation SHALL be deterministic: two generations without an intervening source change produce byte-identical files.
Scenario: Deterministic regeneration
- WHEN the pack generator runs twice without an intervening source or binary change
- THEN the two generated file sets are byte-identical, including the embedded example and the rendered topic and rule-name lists
Requirement: Mentioned identifiers are membership-tested
Every identifier mentioned in a generated pack file SHALL resolve against
the in-binary tables the spk explain command serves: every spk explain
topic name, lint rule id, and EARS pattern name (topic list, lint rule
catalog, EARS grammar). This SHALL be enforced by an in-binary test
that fails the build when a generated mention does not resolve — a
hand-maintained list may be stale, a generated one cannot.
Scenario: Unresolvable rule mention fails the build
- WHEN the skill source names a lint rule id that is not present in the linter rule catalog table
- THEN the in-binary membership test fails, naming the unresolvable identifier, and the binary cannot ship with the dangling mention
Requirement: The embedded example lints clean
The references/example.md file of a generated pack SHALL embed the
worked example from docs/src/examples/reservation-order.md verbatim and
SHALL pass spk lint over that example with zero findings and zero
warnings. This SHALL be enforced by an in-binary test that lints the
generated example file with the shipped linter.
Scenario: Generated example passes the shipped linter
- WHEN the in-binary lint test runs over the generated
references/example.md - THEN the linter reports zero issues and zero warnings, and any corpus change that would make the embedded example lint-dirty fails the test before release
Requirement: Installed files carry the managed marker
Every file written by spk skill install SHALL begin with a managed
marker block that names the owning tool and the exact regenerate command
(<!-- MANAGED BY specodelic — DO NOT EDIT MANUALLY. Regenerate: spk skill install -->),
and the marker SHALL be present on every generated file even when that
file is a verbatim embedding of a repo source that lacks it.
Scenario: Marker survives verbatim embedding
- WHEN the generator writes the example reference that is a verbatim copy of a repository source file
- THEN the installed file starts with the managed marker block while the repository source remains marker-free
Requirement: Install verb writes the pack and refuses overwrites
spk skill install [--dir <dir>] SHALL write the generated pack files
under the given directory (default ~/.claude/skills, creating parent
directories), and SHALL refuse to overwrite pre-existing
non-marker-bearing files at a target path instead of silently replacing
them. Exit codes follow the error contract: 0 installed, 1 write
failure, 2 invalid invocation or overwrite refusal.
Scenario: Overwrite refusal
- WHEN a target path already holds a file without the managed marker
- THEN the verb refuses with exit 2 and a remediation hint naming the conflicting path, leaving the file untouched
Requirement: Install reports a deterministic pack hash
The install SHALL emit a JSON envelope (human-readable on TTYs) listing
the written paths and a deterministic pack_content_hash computed as
SHA-256 over the pack files sorted by relative path, each hashed as
path + NUL + content.
Scenario: Install into an empty directory
- WHEN
spk skill install --dir <dir>runs against a directory that does not exist or contains no conflicting files - THEN the pack files are written, the envelope lists exactly those paths with the managed marker files’ hashes, and the exit code is 0
Requirement: Doctor reports managed-pack staleness
spk doctor SHALL include a managed-skill-pack check that recomputes the
generated pack hash and compares it against the on-disk installation under
each configured target directory: pass when the hashes match, stale
when an installed pack’s hash differs from the generated one, and
missing when no pack is installed.
Scenario: Stale installed pack is flagged
- WHEN a pack installed from an older binary is checked by the current binary’s doctor
- THEN the report contains a
staleentry naming the target directory, the installed hash, the expected hash, and the regenerate commandspk skill install
Requirement: Managed-pack staleness is advisory
The managed-skill-pack check SHALL be optional: a missing pack is
reported missing with a regenerate hint and SHALL NOT fail the doctor
run, while a stale pack SHALL be reported with the regenerate command
and the differing hash pair.
Scenario: Absent pack is not a failure
- WHEN no managed pack exists at any target directory
- THEN the doctor report contains a
missingentry with the install hint and the doctor exit code is unchanged by the check’s absence
Requirement: Docs wire the pack as a thin pointer
The repository docs SHALL reference the skill pack by pointer only: a
thin book page (docs/src/design-conversations.md) links the conversation
procedure to its tracked source and the install command, and the README
command surface, docs/src/SUMMARY.md, and llms.txt SHALL name the
spk skill install verb — none of these pages SHALL duplicate the
protocol prose, the self-check list, or the example.
Scenario: No duplicate protocol in the book
- WHEN the docs-site capability checks run over the book
- THEN no book page contains a copy of the pack’s protocol prose or worked example beyond the thin-pointer page’s quoted one-line summary, keeping a single authoring surface for the procedure
svc-contracts-pack — capability
id: svc.contracts.pack kind: intent statement: “WHEN a workspace enables the svc-contracts standard pack, THE format SHALL provide a namespaced ## Interfaces section whose rows carry name, kind, repo, binding, and evolution; a svc.compat kind with a compatibility case-label floor; and a published_as outbound-leaf reference field resolving to declared ## Interfaces rows of the same file — adding no cross-file edge and no consumer-listing field, with the binding and repo columns staying opaque and files using none of this vocabulary linting byte-identically.”
svc-contracts-pack Specification
Purpose
Ship the svc-contracts standard pack (proposed, draft): vocabulary for
services whose published contracts live in different repositories. The
base format already carries the cross-file edges (extension_point +
satisfies, effect + observes), so this pack adds none; it declares
only a typed ## Interfaces registry row (name, kind, repo, opaque
binding to the authoritative external artifact, evolution mode), a
svc.compat law kind whose floor forces the added and removed
cases, and the published_as outbound leaf tying a contract row to its
registry row. Checkers ship as declarations (honest-empty), as in the
existing standard packs.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| pack_file_declared | invariant | the svc-contracts standard pack is a kind: profile spec file at packs/svc-contracts.md (id svc.contracts) whose six manifest tables declare the Interfaces section, the svc.compat kind, the published_as reference field, two declaration-only checkers, the compat floor, and the specodelic.md Revision 18 base pin — discovered by corpus scan alone, no config file, no registry | [[svc.contracts.pack]] |
| interfaces_section_shape | invariant | Interfaces rows carry exactly name, kind, repo, binding, evolution; kind is closed per-pack to request, event, schema and evolution to additive, versioned, frozen — per-pack closed sets the mechanism does not check; repo and binding are opaque strings never parsed; no base closed set grows and no existing entry narrows | [[svc.contracts.pack]] |
| published_as_outbound | invariant | published_as is a pack-added reference field resolving to an Interfaces row of the declaring file — an outbound leaf joining no reachability path and no acyclic edge set; its closure checker is declared, and reports an empty checked-set honestly when no Interfaces rows exist | [[svc.contracts.pack]] |
| compat_floor_declared | invariant | svc.compat laws enumerate the added and removed case labels per the pack floor, reusing the machine-findable bold-name case-label mechanism with a different required label set; the floor is a declaration the mechanism names and never executes | [[svc.contracts.pack]] |
| no_new_cross_repo_edge | invariant | the pack declares no cross-file edge field and no field or section listing a contract's consumers: cross-repo edges stay the base satisfies and observes fields, and the provider-to-consumer direction stays derived from the graph, never authored (USAGE.md section 2.6) | [[svc.contracts.pack]] |
| vocabulary_prose_safe | invariant | no pack vocabulary token is a bare word that appears with exact case in the corpus prose of specs/ or openspec/specs/ — the token audit is recorded in the change design and re-run at landing | [[svc.contracts.pack]] |
Model
States
draftpublisheddeprecated
Transitions
| id | from | to | guard |
|---|---|---|---|
| publish | draft | published | [[svc.contracts.pack.pack_file_declared]] ∧ [[svc.contracts.pack.interfaces_section_shape]] — pack_shape reports zero findings over the pack's declared vocabulary |
| deprecate | published | deprecated | [[svc.contracts.pack.pack_file_declared]] — maintainer marks the pack deprecated; findings name the deprecation, vocabulary still checks |
Properties
| id | kind | derives_from | generator | predicate |
|---|---|---|---|---|
| discovery_finds_svc_pack | unit | [[svc.contracts.pack.pack_file_declared]] | lint_workspace_with_svc_pack_present() | the pack is discovered by corpus scan alone and the activation advisory names its draft status |
| no_pack_no_change | unit | [[svc.contracts.pack.pack_file_declared]] | corpus_linted_before_and_after_svc_pack() | lint output byte-identical for a corpus using no svc vocabulary |
| interfaces_shape_lints_clean | unit | [[svc.contracts.pack.interfaces_section_shape]] | pack_file_linted_alone() | pack_shape reports zero findings |
| honest_empty | unit | [[svc.contracts.pack.published_as_outbound]] | svc_pack_active_with_no_interfaces_rows() | the closure checker reports an empty checked-set with no fabricated findings |
| floor_names_cases | unit | [[svc.contracts.pack.compat_floor_declared]] | read_floor_row_for_svc_compat() | the floor names added and removed as the required case labels |
| no_consumer_field_declared | unit | [[svc.contracts.pack.no_new_cross_repo_edge]] | scan_manifest_for_consumer_listing_fields() | no declared section, kind, or reference field lists consumers or adds a cross-file edge |
| vocabulary_audit_holds | unit | [[svc.contracts.pack.vocabulary_prose_safe]] | audit_pack_tokens_against_corpus_prose() | zero exact-case hits for every vocabulary token in specs/ and openspec/specs/ |
Requirements
Requirement: The svc-contracts standard pack is a declared profile file
The format SHALL ship the svc-contracts standard pack as an in-repo
kind: profile spec file (packs/svc-contracts.md, id svc.contracts)
whose manifest declares the ## Interfaces section, the svc.compat
kind, the published_as reference field, the svc.interface_closed and
svc.compat_cases checkers, the compatibility floor, and the
specodelic.md Revision 18 base pin — discovered by corpus scan alone,
with no config file and no registry.
Scenario: Corpus scan discovers the pack
- WHEN
spk lintruns over a workspace subdirectory of a repository containingpacks/svc-contracts.md - THEN the pack is discovered without configuration
- AND files using none of the pack’s vocabulary keep byte-identical lint findings
Scenario: Pack lints clean under pack_shape
- WHEN the pack file is parsed in its landing lifecycle state
- THEN pack_shape reports zero findings over the pack’s declared vocabulary
Requirement: Interfaces vocabulary is namespaced and per-pack closed
The pack SHALL declare an ## Interfaces section with row shape
| name | kind | repo | binding | evolution |, kind closed per-pack to
request, event, schema, evolution closed per-pack to additive,
versioned, frozen, and the namespaced kind svc.compat — pack-fiber-
relative vocabulary that grows no base closed set and narrows no
existing base set entry. The repo and binding columns SHALL stay
opaque strings that the pack never parses.
Scenario: Consumer activates the pack advisory-first
- WHEN a corpus spec uses
svc.compat,Interfaces, orpublished_aswithout a declaredusesedge - THEN the pack’s declared checking activates advisory-first for that file, naming the pack and its draft status
Scenario: Orphan vocabulary names the svc pack
- WHEN a workspace uses
svc.compatwith no svc-contracts pack discovered or declared - THEN the orphan finding names the candidate namespace
svcand both remediations, with non-zero exit
Requirement: published_as is a typed outbound leaf resolving to declared rows
The pack SHALL add the published_as reference field, resolving to an
## Interfaces row of the declaring file and joining no reachability
path and no acyclic edge set, and SHALL declare the svc.interface_closed
checker that reports an empty checked-set honestly when no ## Interfaces
rows exist.
Scenario: Honest-empty closure with no interfaces rows
- WHEN the pack is active in a workspace whose specs declare no
## Interfacesrows - THEN the closure checker reports an empty checked-set with no fabricated findings
Scenario: Closure is a declaration, not an executed check
- WHEN a constraint row names an
## Interfacesrow id viapublished_asthat no row in the file declares - THEN the pack’s Checkers facet names the closure rule and the mechanism reports its checked-set honestly — the pack never claims an executed check the mechanism does not run
Requirement: The compatibility floor names its case labels
The pack SHALL declare a kind-dependent floor: svc.compat laws
enumerate their **added:** and **removed:** case labels per the floor
declaration, reusing the machine-findable **name:** case-label
enumeration mechanism with a different required label set, and SHALL
NOT re-declare any base or other-pack kind.
Scenario: Compat floor names its case labels
- WHEN a
svc.compatproperty is read against the pack’s floor declaration - THEN the floor names
addedandremovedas the required case labels
Scenario: Floor is declaration-only
- WHEN a
svc.compatproperty omits the floor’s case labels - THEN the pack’s declared floor is the contract and the mechanism does not fabricate a finding the checker declaration does not execute
Requirement: The pack adds no cross-file edge and no consumer listing
The pack SHALL NOT declare any section, kind, or reference field that
lists a contract’s consumers or that adds a cross-file edge: cross-repo
edges SHALL remain the base satisfies (to an extension_point) and
observes (to an effect) fields, and the provider-to-consumer
direction SHALL stay derived from the reference graph.
Scenario: Cross-repo contract uses only base edges
- WHEN a consumer repo’s constraint row cites a provider repo’s
extension_pointthroughsatisfiesand aneffectthroughobserves - THEN both resolve corpus-wide with no pack involved, and the provider’s file names no consumer
Scenario: Manifest declares no consumer field
- WHEN the pack’s
## Sections,## Kinds, and## Referencesfacets are read - THEN none declares a consumers or consumed-by listing or a second cross-file edge
graph-views — capability
id: graph.views kind: intent statement: “THE graph-views capability SHALL emit a deterministic edge-list projection with canonical ids and annotated typing violations from any specodelic-compliant corpus, and SHALL derive per-file state-machine, file-level traceability, and revision-labeled schema views from that artifact plus the lint-gated acset Schema value and the format revision marker alone — never from prose.”
graph-views Specification
Purpose
Make the typed reference graph of any specodelic-compliant corpus thinkable-with rather than merely machine-readable: a deterministic, pipeline-friendly edge list as the single projection boundary, and human-readable views (per-file state machines, file-level traceability, the schema’s own shape) rendered from that boundary at build time. Views are derived facts — never authored, never committed, never cleaner than the artifact they came from.
Constraints
| id | kind | expr | traces_to |
|---|---|---|---|
| process_lifecycle | invariant | the capability advances through its declared lifecycle states under the repo's change process — each stage transition fires only when its stage gate holds | [[graph.views]] |
| canonical_ids | invariant | every edge-list endpoint is a canonical node ID (intent ID or qualified row ID) value; display labels (e.g. refactor (intent)-style label-qualified nodes) never appear in the projection — normalization happens at or before projection | [[graph.views]] |
| deterministic_projection | invariant | re-running the projection over an unchanged corpus produces a byte-identical edge list — rows sorted, no timestamps, no iteration-order leakage | [[graph.views]] |
| violations_annotated | invariant | every violation in the graph artifact appears as an annotation row in the edge list; a view rendered from a corpus with violations is never silently clean — dashed/annotated rendering is mandatory, omission forbidden | [[graph.views]] |
| derived_views_only | invariant | corpus views consume only graph artifacts; the schema view consumes only a versioned export of the lint-gated acset Schema value and format revision; no view parses prose, re-walks markdown, or embeds hand-authored structure | [[graph.views]] |
| build_time_generation | invariant | rendered views are generated into the docs build at build time and never committed; no hand-edit path exists, so graph_is_derived_not_authored holds by construction and no staleness check is needed | [[graph.views]] |
| empty_corpus_valid | invariant | spk graph --format edges over any parseable corpus — including a directory with zero spec files, or a single intent with no cross-file edges — exits 0 and emits a well-formed (possibly empty) row set: graph extraction requires parsed, not linted (openspec/specs/graph/spec.md's scope note); rendered views are transform-level and additionally gated by corpus_scope_operational — an intentless corpus is refused there with a remediation hint, never silently rendered | [[graph.views]] |
| corpus_scope_operational | invariant | a corpus is in scope iff spk lint over it reports no invariant-rule findings and ≥1 intent file parses; corpora failing this (e.g. openspec-layout repositories) are out of scope until a parse-boundary adapter change lands — no view special-cases them | [[graph.views]] |
Model
States
proposedapprovedimplementedarchived
Transitions
| id | from | to | guard |
|---|---|---|---|
| approve | proposed | approved | [[graph.views.process_lifecycle]] ∧ proposal reviewed and approved by the maintainer |
| implement | approved | implemented | [[graph.views.process_lifecycle]] ∧ all tasks.md items complete; spk lint, transform tests, and openspec validate --strict pass |
| archive | implemented | archived | [[graph.views.process_lifecycle]] ∧ just archive-change id=add-graph-views ran with the dual-format recipe |
Properties
| id | kind | derives_from | generator | predicate |
|—————————––|——|———————————–|—————————————————————————————————–|
| process_lifecycle_checked | unit | [[graph.views.process_lifecycle]] | lifecycle_model_present() | check(file) == passed |
| ids_never_labels | unit | [[graph.views.canonical_ids]] | corpus_parsed_then_projected() | no TSV field matches a label-qualified node pattern |
| rerun_byte_identical | unit | [[graph.views.deterministic_projection]] | same_corpus_projected_twice() | both runs byte-identical; also holds across invocation order of files |
| violations_survive_projection | unit | [[graph.views.violations_annotated]] | corpus_with_known_typing_violations() | edge list contains one annotation row per violation, none missing |
| clean_corpus_no_annotations | unit | [[graph.views.violations_annotated]] | lint_clean_corpus() | edge list contains zero annotation rows |
| views_from_artifact_only | unit | [[graph.views.derived_views_only]] | view_rendered_with_prose_perturbation() | rendered view byte-identical after perturbing prose blocks |
| schema_view_revision_labeled | unit | [[graph.views.derived_views_only]] | schema_value_rendered() | schema view names the format revision it was derived from |
| build_output_not_committed | unit | [[graph.views.build_time_generation]] | docs_build_completed() | git status clean — no rendered artifact tracked or staged |
| empty_corpus_projection | unit | [[graph.views.empty_corpus_valid]] | directory_with_zero_spec_files() | projection exits 0 with empty output — the view half is governed by corpus_scope_operational (an intentless corpus is refused: out_of_scope_refused) |
| single_intent_sane | unit | [[graph.views.empty_corpus_valid]] | corpus_with_one_intent_no_cross_file_edges() | file-level view renders one node; no crash |
| out_of_scope_refused | unit | [[graph.views.corpus_scope_operational]] | corpus_with_invariant_findings_or_no_intents() | transform exits non-zero with a remediation hint naming the failed gate |
Requirements
Requirement: Deterministic edge-list projection
The system SHALL provide spk graph --format edges emitting a sorted TSV
edge list (source id, source kind, typed reference field, target kind,
target id, annotation) with exactly one row per recorded edge — plus
annotation rows per the violations requirement below — using canonical node IDs (intent IDs and qualified row IDs), byte-identical across re-runs on an unchanged corpus. When
--format edges is given, raw TSV goes to stdout, overriding envelope
formatting.
Scenario: Projection over a compliant corpus
- WHEN
spk graph --format edgesruns over a corpus with ≥1 lint-clean intent - THEN the output is a sorted TSV whose every endpoint is a canonical node ID (intent ID or qualified row ID)
Scenario: Re-run determinism
- WHEN the projection runs twice over an unchanged corpus
- THEN both outputs are byte-identical
Scenario: Display labels excluded
- WHEN the corpus contains label-qualified pseudo-nodes in the raw graph output
- THEN the projection contains only canonical ids — no label-qualified endpoints
Requirement: Violations never silently hidden
The edge-list projection SHALL include one annotation row per violation in the graph artifact, and views SHALL render violations as annotated (not omitted, not silently clean) elements.
Scenario: Violation-bearing corpus projects annotated rows
- WHEN the graph artifact reports N typing violations
- THEN the edge list contains exactly N annotation rows
Scenario: Views annotate rather than omit
- WHEN a view is rendered from an artifact carrying violations
- THEN each violation appears rendered as a dashed/annotated element
Requirement: Derived per-file state-machine view
The system SHALL derive, per corpus file whose artifact contains
transition edges, a state-machine view from transitions.from,
transitions.to, and transitions.guard edges alone — no re-walking of
markdown.
Scenario: State machine from edges only
- WHEN a file’s spec defines states and guarded transitions
- THEN the derived view shows the same states, transitions, and guard annotations as the file’s Model tables
Scenario: File without transitions skipped cleanly
- WHEN a file has no transition edges
- THEN no state-machine view is emitted for it and the build succeeds
Requirement: Derived file-level traceability view
The system SHALL derive a corpus-level view collapsing all edges to intent (file) granularity, annotating fan-in per intent, from the edge list alone.
Scenario: Collapse to intents
- WHEN the projection is derived from the specodelic corpus
- THEN the view’s node set equals the corpus’s intent set and edges connect only intents
Scenario: Fan-in annotation
- WHEN an intent is the target of edges from k distinct intents
- THEN the view annotates that intent with fan-in k
Requirement: Derived revision-labeled schema view
The system SHALL derive the schema view (kinds as nodes, typed reference
fields as typed edges with allowed targets) from the acset Schema
value (acset::schema::canonical() — the Reference Typing table as
data, lint-gated against openspec/specs/specodelic/spec.md by
schema_matches_typing_table) and the format revision marker, labeled
with the revision it was derived from.
Scenario: Schema from the Schema value
- WHEN the schema view is rendered
- THEN its nodes and typed edges match the
Schemavalue’s objects and morphisms, and the format revision is named in the output
Requirement: The schema export is a typed genesis envelope
spk guide --schema --json SHALL provide the schema view through a
genesis envelope whose data contains schema_version 1, format_revision,
sorted object names, and morphisms sorted by source/name. Each morphism
SHALL carry name, column, source, target, refinements (side/kind pairs
sorted by side/kind), source_rule (unchecked, appears_on or same_kind),
and endo_acyclic (boolean or null).
Scenario: Schema export connects producer and renderer
- WHEN the production exporter serializes canonical Schema and two valid constructed schemas differing in one morphism
- THEN its canonical payload matches the source rows and rendering the two constructed exports changes the corresponding edge without a renderer edit
Requirement: The schema renderer validates the export
The schema renderer SHALL consume the serialized export, display refinement labels and reject unsuccessful envelopes, missing fields, unknown versions, duplicate identities and absent endpoints with schema_export_invalid and a regeneration hint before writing output. It SHALL neither parse Rust source nor reconstruct schema structure from corpus edges.
Scenario: Invalid schema export is refused
- WHEN the renderer receives an unsuccessful envelope or schema data with a missing field, unknown version, duplicate identity or absent endpoint
- THEN it fails as schema_export_invalid with a regeneration hint before writing a diagram
Requirement: The schema export derives from canonical Schema alone
The export SHALL derive directly from canonical Schema; guide typing constants SHALL NOT supply its rows. Ordinary guide –json SHALL expose kinds, row shapes and revision independently.
Scenario: Revision bump changes the view
- WHEN a new Revision adds a Reference Typing field (Schema + corpus doc updated together under the lint gate)
- THEN the regenerated schema view includes the new field without any renderer change
Requirement: Build-time generation, never committed
Rendered views SHALL be generated into the docs build at build time via a
just docs-graphs recipe and SHALL NOT be committed to version control;
no hand-edit path exists.
Scenario: Build leaves the worktree clean
- WHEN
just docs-graphsruns and the docs build completes - THEN
git statusreports no new or modified tracked files from view generation
Scenario: Empty corpus is refused at the transform, clean at the projection
- WHEN views are generated over a directory with zero spec files
- THEN the scope gate exits non-zero with a remediation hint (
out_of_scope_refused), while the tool-level projection itself exits 0 with empty output (empty_corpus_valid)
Requirement: Row identity survives raw projection
Raw edge projection SHALL retain canonical intent IDs and qualified row IDs without collapsing rows to their file. File-level traceability and wiring views MAY collapse rows to their owning intent; state-machine views SHALL retain distinct state and transition identities.
Scenario: Two states in one file remain distinct
- WHEN one file defines transition order.t from order.s1 to order.s2
- THEN raw projection retains both distinct state IDs and both edges, and the state-machine view renders two states