usedesign. Operation Card — SPEC v1.7 · usedesign 1.7.1 1 / 23

A format for describing what a system can do

usedesign.

One description per operation. Every screen, contract, test and migration points back to it.

SPEC v1.7 · usedesign 1.7.1 on npm · 31 rounds Apache 2.0 YAML front matter + Markdown github.com/nistoc/usedesign
→Space next ← back HomeEnd first / last T theme L language · or swipe ·

01 — the question

“What can this system actually do, and what state is each of those things in?”


Ask a mature system that question and there is usually no answer.

Not because nobody knows — because the knowledge is split across genres that never agree with each other. Each one is complete inside its own boundary and silent outside it.

02 — where the answer is scattered

Four genres that never agree

Four islands. Each knows one thing well and cannot be asked about anything else.

API specification

OpenAPI, and friends

AnswersWhich endpoints exist

Cannot answerWhich screens use them, what is tested, why it exists

Decision records

ADRs

AnswersWhy a decision was made

Cannot answerWhat implements the decision

Test suite

CI, green or red

AnswersWhat currently passes

Cannot answerWhich behaviour is not covered

Tribal knowledge

and code comments

AnswersEverything else

Cannot answerAnything, to anyone who has not read that file

None of them is a catalogue of operations. And none of them lets an artifact — a test, a screen, a contract, a migration — declare what it implements and which stage of life it belongs to.

03 — the bill

What that costs

The screen drifts from the API

Nothing in either artifact says which screen depends on which behaviour, so nothing notices when they part ways.

The suite is green while a whole branch is unproven

A test suite reports what it ran. It has no vocabulary for the branch nobody wrote a test for.

A deliberate decision survives only as a code comment

One person will ever read it — the one who is already in that file, and who therefore did not need it.

An Operation Card is the missing unit: the place where those four genres meet and where an artifact can point at what it implements.

04 — prior art

We tried to buy this before building it

A description of an operation has to serve six consumers at once — the UI, the data model, the external contract, the service-to-service contract, the tests, and the database migrations. Here is how much of that six each existing family already covers.

FamilyExamplesWhat it does wellWhy it is not enough here
API contracts OpenAPI, AsyncAPI, gRPC / protobuf, Smithy, TypeSpec The interface, precisely — plus strong code generation Describes endpoints, not operations. One operation often spans several endpoints; one endpoint serves several operations. Nothing about screens, tests, migrations or maturity
Behaviour & contract testing Gherkin / Cucumber, Pact Executable behaviour; a test that states what it checks No data model, no contracts, no migrations. Pact is a check, not a description — it verifies agreement, it does not say what exists
Service catalogues Backstage Software Catalog Ownership, discovery, a real lifecycle field Catalogues components, not operations, and lifecycle is a single field. It stops one level above the thing we needed to describe
Architecture notations C4 / Structurizr, arc42, ArchiMate Structure and boundaries; ArchiMate can express almost anything Describes the shape of the system, not its operations. ArchiMate is expressive but GUI-bound, hard to review in diffs, and generates nothing
Decision records ADR Why a decision was made — irreplaceable A decision is not an operation. Nothing points from an ADR to the code that implements it
Requirements traceability Jira + Xray / Zephyr, DOORS The closest existing thing: real test-to-requirement links Lives in a tracker, not the repository — outside diffs, outside review, outside the pull request. The requirement itself is prose
Formal methods TLA+, Alloy Proves properties of a model Proves the model, and says nothing about its implementations. The wrong cost for CRUD-shaped systems
Collaborative modelling Event Storming, Domain Storytelling The best way there is to agree what the operations are — and it takes the same unit we do: the thing that happens Produces a wall of stickies, then a photograph in a wiki. Truth is the agreement in the room, and nothing afterwards compares it with the running system

Each family closes one or two of the six consumers. None of them closes the link between them.

Event Storming is the closest neighbour, and it is not a competitor

It is the half that comes before — a room agreeing on what happens. This format is the half that comes after: the same agreement written down so that it cannot go stale quietly.

Three differences do the work. When: a workshop runs before the code, a card lives against it. What counts as true: there, the nod of everyone present; here, what the system states about itself — its own route list, its own test report, the keys of its own live database. What survives: a sticky wall ages silently in a wiki; a card that has drifted is printed by the next build.

They chain without strain: the workshop yields the vocabulary and the boundaries, the cards turn that result into files that cannot lie for long. A sticky reading “request rejected” becomes an outcome named rejected — and from then on, if the code stops producing it or the screen stops showing it, somebody learns the same day rather than the following year.

05 — the gap

So what is actually missing?

JSON Schema is not a competitor — it is the material

Whatever format you choose, you validate it with JSON Schema. usedesign uses it too.

The gap is not another way to describe an API

It is a unit with a stable identifier that the other artifacts can point at. Once that unit exists, everything else keeps doing its own job: OpenAPI is generated from it, Gherkin scenarios attach to its steps, Pact verifies its contracts, ADRs justify it, a service catalogue links to it.

Said plainly

usedesign does not replace any of the tools above. It is the missing centre they were all pointing near, but never at.

OpenAPI · AsyncAPI Gherkin · Pact decision records service catalogue C4 · arc42 tracker traceability Operation Card a stable id to point at

Six good tools, all aimed at the same empty spot.

06 — the idea

Describe it once. Let everything point back.

One operation, one file that both a human and a machine can read. Tests attach to steps. Screens declare which steps they cover. Migrations attach to the version axis. Nothing is left to be inferred.

screen · covers_steps contract · rest.path test · covers migration · since worker · job_states Operation Card library.loan.checkout

The question “what proves this behaviour, and where is it shown to the user?” now has an answer that fits on one screen.

07 — structure

Three levels

Humans read the top. Tests and contracts attach to the bottom.

Scenario library.borrowing.member-borrows-a-book

A user goal, told in human terms — “a member borrows a book”.

└─ Operation library.loan.checkout

One atomic action with a stable id. The id never changes — tests and code point at it.

└─ Step s3-member-standing

One stage inside that action, including the failure branches. Step ids are never renumbered or reused.

One file, one operation

The file name equals the operation id. Only the operation is a file: a scenario is a name shared by several cards, a step is a section inside one.

No orphan endpoints

An operation that belongs to no scenario must declare whom it serves (serves_step) — otherwise nobody knows who calls it, and it is probably forgotten.

08 — the heart of the format

The axes of life

Stages of life are not a single field. Six different things change at six different speeds, and each is owned by different artifacts.

A
Maturitymaturity
How far the operation is built: conceived → designed → implemented → tested → in production → deprecated
Weekly, as work lands
B
Stepssteps[]
What happens during execution, including the failure branches
Only when behaviour changes
C
Data lifecycledata_transition
What the operation does to the state of the record itself
Almost never — it is the domain model
D
Versionsince
Since which migration or release the operation behaves this way
Every release
E
Async jobasync_execution.job_states
The life of the background work the operation starts
Only for async operations
F
Continuationcontinuation
The operation stopped and cannot proceed until a person decides — nothing to observe, nobody watching moves it
Only for operations that can suspend

Squashing six clocks into one stage field is why traceability schemes rot. F arrived in round 7 — the format was published with five.

09 — the classic mistakes

Why the axes must never merge

A test moves two axes at once

A test attaches to a step (axis B) and, by existing, moves maturity (axis A). A screen covers a set of steps.

test→step · axis B⇢maturity · axis A

A migration attaches to neither

It belongs to axis D — which is exactly why migrations end up orphaned in every traceability scheme that has only one stage field.

migration→since · axis D

Axis B is not axis C

Steps are states of executing the operation. The data transition is the state of the record. A publish operation has its own steps and moves a record from draft to published. Conflating the two is the second classic mistake.

Axis E is neither

A job may reach succeeded while the record it produced stays draft.

Every weakening is explained

Any relaxation — no optimistic locking, no audit trail, no owning scenario — carries a rationale. A silent relaxation is a spec bug.

10 — the file

Anatomy of a card

Deliberately boring: YAML front matter for machines, Markdown body for humans. No new language, no compiler, no runtime.

---front matter · for machines
id · title · scenario | serves_step · actorsidentity
maturity · maturity_evidenceaxis A
steps[] — id · text · on_violation · rationale · sourceaxis B
data_transition · mutatesaxis C
since — migration · noteaxis D
async_execution — job_states · terminal · workeraxis E
continuation — after · resumed_by · carriesaxis F
concurrency · quota · interfaceshow it is reached
data · provenance · reversibility · sensitivitywhat it touches
tests[] · coverage_gaps[]what proves it
---markdown body · for humans
# Check out a copy to a memberwhy it works this way

The file name is the id

examples/library/
  library.loan.checkout.op.md

One file, one operation. No collections.

Claims cite source

Anything asserted about the implementation carries source: — path, path:line or path#Symbol; prefer the symbol: a line number drifts in silence. An unsourced claim is treated as unverified — and cards are written from measurement, not from memory.

Withdrawn decisions get a tombstone

On the line, not a deletion. id and step ids are immutable; renaming means a new operation plus deprecated on the old one.

11 — fields

The fields that surprised us

Each one exists because writing real cards found somewhere the format had nothing to say.

concurrency — seven modes, not a boolean

etag_requiredetag_optionalserver_read_version idempotency_by_headeridempotency_by_formula none_by_designnone_unexplained

Real systems mix them, and the reasoning usually survives only in a code comment next to one of them. Every mode except the strictest must carry a rationale.

quota — neither a role nor concurrency

A role answers who may. Concurrency answers what happens on collision. Quota answers how often. For an external consumer, the difference between “10 writes per minute” and “unlimited” is part of the promised contract — and it is frequently invisible because it depends on how the caller authenticated.

reversibility — required, never inferred

An irreversible operation needs different UX (confirmation), a different test (the guard) and often a different permission. Without an explicit field, telling a hard delete from a soft retire means reading the implementation.

sensitivity — one line, three codebases

An operation that returns a secret once states one rule for three consumers at the same time: the UI must not show it twice, the gateway must not log it, the test must not print it.

async_execution — when the response does not mean “done”

Job states, terminal states, the worker, how to observe it, how to cancel it, and what happens when a service it depends on fails. Axis E has its own life: a job can succeed while everything it produced is still a draft.

12 — the payoff

Traceability, in both directions

library.loan.checkout.op.md · one step, its test, its screen
steps:
  - id: s3-member-standing
    text: Member has no blocking holds — unpaid fines
          above the threshold, or a suspended card
    on_violation: { error: member_blocked, http: 403 }

tests:
  - { id: CheckoutTests.blocked_member_cannot_borrow,
      covers: s3-member-standing, level: integration }

interfaces:
  ui:
    screen: CirculationDesk
    covers_steps: [s1-copy-available, s3-member-standing,
                   s4-loan-limit, s5-commit]

Forward

operation→step→test+screen

What proves this behaviour, and where is it shown to the user?

And backward

test→step→screen · contract · migration

From a failing test you reach the step, the screen that covers it and the contract that exposes it — without asking anyone.

A gap is a line, not a silence

coverage_gaps:
  - step: s6-audit
    gap: no test for the rollback path
         when the audit write fails

coverage_gaps is a tool, not an admission. A missing line is invisible; a present line is reviewable.

ui.covers_steps is the mechanism that makes UI/API drift visible: if a screen claims to cover a step that does not exist, the checker fails.

13 — example · the ordinary case

library.loan.checkout

A synchronous write: optimistic locking, a lifecycle transition, an audit trail, and a counterpart that undoes it. The shape everything else varies from.

library.loan.checkout.op.md · two guards that stay separate
- id: s3-member-standing
  text: Member has no blocking holds — unpaid fines
        above the threshold, or a suspended card
  on_violation: { error: member_blocked, http: 403 }
  rationale: >
    Checked before the loan limit so the member sees the
    real reason. Hitting the limit is recoverable by
    returning a book; a suspension is not …

- id: s4-loan-limit
  text: Member is below the concurrent loan limit for
        their membership tier
  on_violation: { error: loan_limit_reached, http: 409 }

Why not one “not allowed” error?

Because the two answer different questions. Standing asks may this person borrow at all; limit asks may they borrow one more. The first sends the member to the front desk; the second is fixed by returning something. A single merged error would send half of the people to the wrong desk.

The rest of the card, in one breath

  • concurrency: etag_required — a stale revision is a 412, not a lost write.
  • data_transition: { from: available, to: on_loan } — axis C, stated once.
  • since: { migration: "014" } — axis D, the place migrations stop being orphans.
  • reversible_via: library.loan.return — the undo has a name.
  • The due date is computed at commit from the tier’s loan period, so changing a policy does not rewrite open loans.

14 — example · asynchronous work

library.catalog.import

The response means accepted, not done. Everything interesting happens afterwards, in a worker with a life of its own.

axis E · the job is not the record
async_execution:
  job_states: [queued, running, succeeded, partial,
                failed, cancel_requested, cancelled]
  terminal: [succeeded, partial, failed, cancelled]
  worker: src/workers/ImportWorker.ts
  observe_via: GET /v1/imports/{jobId}
  s2s_dependency:
    service: MetadataEnrichment
    failure_mode: >
      Job ends as `partial`, not `failed` — titles are
      imported without authority links …
top level · over REST
quota:
  scheme: per_credential_per_minute
  categories:
    import: { bucket: 5, window_min: 1 }
  applies_to: api_key_callers_only
per-transport override · over RPC
interfaces:
  rpc:
    transport: json_rpc
    quota:
      categories: { write: { bucket: 2, window_min: 1 } }
      applies_to: all_rpc_callers

partial is a success

Titles still import, only authority links are missing. Calling it failed would tempt operators to re-run the whole import and create duplicates.

Same operation, two throttles

Five imports a minute over REST; a bucket of two over RPC. The kind of difference that lives in nobody’s documentation and surprises the first external integrator.

Retries join, not duplicate

idempotency_by_formula: the server derives the key from the payload, because batch scripts cannot be relied upon to send a stable one.

15 — example · irreversible, and its helper

library.member.purge

The card that justifies making reversibility a required field. Every other write here can be undone. This one cannot — and that single fact changes four things at once.

library.member.purge.op.md
reversibility: irreversible

steps:
  - id: s2-closed
    text: Membership is already closed — an active
          member cannot be purged directly
    on_violation: { error: member_still_active, http: 409 }
    rationale: >
      Forcing closure first makes erasure a two-person,
      two-step act. A single call that both closes and
      erases would turn a misclick into an
      unrecoverable loss.
library.member.settlement-check.op.md · the paired helper
serves_step:
  operation: library.member.purge
  step: s3-settled
  purpose: >
    Let the operator see the obstacle before attempting
    an irreversible action, instead of discovering it as
    a rejection. The point is not to report the conflict
    afterwards — it is to prevent the attempt.

data_transition: null
provenance: none

What irreversible buys

  • Typed confirmation. The screen requires typing the member number.
  • A prior-closure gate. An active member cannot be purged directly.
  • A settlement guard. Open loans, unpaid fines or an active hold refuse the whole operation, with the counters in the error payload.
  • Tests that prove refusals rather than the happy path.

And a receipt that carries nothing

An erasure that leaves no trace is indistinguishable from data loss. The receipt proves the erasure happened while carrying nothing the erasure was meant to destroy.

Why the helper is not an orphan

Nobody wakes up wanting to check settlement, so it has no scenario — it declares serves_step instead. provenance: none is stated explicitly, which keeps the checker from demanding an audit test that should not exist.

16 — enforcement

The five checks

The format only pays for itself with an automated checker. Five invariants, each measured against something the repository states about itself at run time rather than something the checker guesses — sources are never parsed:

  1. No wild endpointsEvery route the code registers is declared by some card. The repository publishes a route inventory — a runtime dump of its own route table — and the checker compares sets. It never parses your framework, which is why routes generated from a template are caught rather than missed.
  2. No unproven stepsEvery step is covered by a test that exists, ran, passed and was not skipped — or appears in coverage_gaps. Read from a JUnit report of the run being checked. A skipped test is an error, and a louder one than an absent test: it keeps its name in every report.
  3. No inflated maturityThree claims of different natures. The implemented path must exist; tested must have a named test that passes; deployed cannot be verified by anyone — so it expires instead.
  4. No imagined storageEvery store a card claims to touch exists, with the key it names. The repository publishes a storage inventory taken from the live store — and stores no card mentions are listed, not hidden.
  5. The form matches its contractThe product owner writes what a screen must show and when; a test renders the real components per state and dumps the DOM. The checker compares fields, controls, groups and visibility — including whether a control is shown exactly in the states its operation departs from.

State it plainly: without check 3 in particular, cards drift into aspiration within a couple of months. A catalogue without a checker becomes aspiration.

A card disagreeing with the code is a defect of the card until proven otherwise — even in a specification-first workflow, where the intended difference must be marked explicitly.

17 — maturity of the spec itself

How this was hardened

v0.2 was reached by writing cards for eight real operations of a production system and recording where the format broke. Since then: twenty-nine more rounds, thirty-one in all, on three stacks, eight of them driven by a pilot on a product the format had never seen — and, on 13 September 2026, v1.0.

RoundAreas exercisedFields the format was missing
1 CRUD, async ingestion, curation UI, read-only helper async_execution, concurrency, serves_step
2 RPC protocol, security administration, cross-system boundary transport, quota, reversibility, sensitivity, consumer_boundary, contract_version
3–5 Building the three checks — using the cards instead of writing more of them None. Wording, and two fields widened to accept what cards already contained
6 A second implementation, from the specification and schema — not from the first one None. One defect, and it was in the port: CRLF cards
7 Four untouched areas: a question in human language, an undo, paged search, attachments Four gaps, and a fifth found while repairing: no honest reversibility for a read, which made every read-only card say something false
8–9 Repairing round 7, then pointing the tool at a system it had never seen Round 8: five optional fields. Round 9: three, plus a normalisation bug that made the checker silently wrong and a rule fitted to its single example
10–12 A screen traced from control to pixel against a described operation; four more screens; then the layer below — cards against a live schemaless store Two optional fields — covers_outcomes, data.storage[] — and check 4; round 11 added none, it corrected round 10's own rule
13–16 The layer above, inverted: the owner writes form contracts first and the rendered screens answer; the contract's own shape and its groups against the measurement; then a pilot on a foreign stack — five issues from the first outside pilot Two artifact kinds and check 5; groups[], within; records_only, entities: [], route_not_yet_served — nothing required
17–18 The pilot's second round, four issues; the lifecycle cards of a live service Contract maturity, families, a screen-state map, from as a set, etag_optional — all optional
19–21 Dogfood on the owner's own product: a frontend gate without its sibling, a hand-kept test count, a write with no state change None. Three warnings the checker owed: no_cards_found, tested_count_mismatch, reversibility_overstated learning mutates
22–24 The pilot's backend role writes a card unaided; four reads and a create on the athlete's path — a read with one ending could name every refusal and not the success; a delete with no precondition and no reason in the code None required. Two optional words: ok for the default success, none_unexplained for a measured absence; and the spec learned the interfaces map was always open
25 Three families described whole, from both ends — the workout pass, plans, catalogs; the gaps of the first ten cards closed by 29 tests None. Fourteen candidates recorded, all inside 1.x
26–27 The first round under the v1.0 promise, taking up four of round 25's candidates; then a chain behind one button, where one control calls several operations Optional: variant_of, coverage_gaps[].kind, uncontracted_screens, calls: as a list in call order; two warnings over fields 1.0 already had
28–30 The pilot's refusals re-measured, protection the caller never sees, a contract read only after its screen, the line among the quotes No new field. Three additions to existing ones, none a break: steps[].source accepts a path alone; concurrency.mode: server_read_version; a covers_outcomes value may be a {shows, where, note} pair

And then the specification was checked against itself

Before publishing, this document was validated against its own schema and examples — the exercise it asks of everyone else. It failed in seven places of a different kind: a field present in the schema and in an example but described nowhere; a rule stated in prose that the schema did not enforce; one term spelled two ways across two files. All seven are fixed. That is the argument for having a checker at all.

Criterion for v1.0 — met, and declared

Not “no more breakage” — untouched areas will always break something — but a round that changes only optional fields. It held from round 8 to round 24: seventeen rounds of optional-only changes, on three stacks. v1.0 was declared on 13 September 2026: the required keys are frozen, a card clean under 1.0 stays clean under every 1.x, and new checks arrive as warnings. Rounds 26–31 have added to it without breaking it.

18 — where it stands

Where it stands — October 2026

Numbers read from the repositories and the registry, not from memory.

PartState
Specification and schemaSPEC v1.7 through 31 rounds; six schemas
ChecksFive: routes, steps, maturity, storage, forms
The CLIvalidate · check · scaffold · preview · gen openapi · conformance. OpenAPI runs both ways: gen openapi derives a contract from the cards, and the route inventory is taken from the product's OpenAPI
A second implementationA Python prototype; the same codes on 165 corpus cases
Published30 versions, 0.2.0 → 1.7.1, with provenance; publish is green only once the registry serves the box
AlsoForm contracts and check 5; a starter kit; drafts from OpenAPI; a foreign pilot

One product, two repositories

Server: 114 cards; of its 113 routes, 110 are declared by cards and 3 excluded; 619 steps proven, none unproven, read from a report of 1006 test cases — the gate reports, it does not fail. Frontend: 32 contracts against 32 rendered screens, a strict gate at zero findings, reading the server’s cards.

How complete is it?

Format: SPEC v1.7; 1.0 was declared 13 September 2026 after seventeen qualifying rounds, and rounds 26–31 added to it without breaking it. Tool: five checks, two implementations, 165 cases. First product: every served route is declared or excluded. What is still open is on the next slide.

19 — elsewhere

Does it transfer? What broke, what still can

One foreign pilot, eleven issues — five in its first round, four in its second, two in between. Three more have followed (#12–#14); all fourteen are closed. Each was a real hole, not a matter of taste.

What transferred as it is

Cards and checks 1–3 need only an OpenAPI document and a JUnit report — any stack has both. The forms producer recipe took a foreign React stack once issue #7 was closed. The corpus turned every fix into a rule, not a patch.

What every new stack will need

Check 4 needs live access to the store from CI; check 5 needs a test that renders your real components per state. Both are second-wave on purpose.

Blind spots that are still open

  • Form, not honesty. A valid card can lie: a step “proven” by a test of another route passed all five checks — twice in one day on a live service. Only review catches it.
  • Cards rot from new tests too. Two of three “unproven” steps were proven by tests written after the card; red meant “nobody told the card”.
  • The tool names, it does not decide. Silent rollbacks, a race window, a missing status check — all surface as warnings; the decisions stay with the owner.
  • Three stacks is not many. Every new stack so far broke something optional; the next one will too. That is the design, and the cost.

20 — technology

Boring on purpose

YAML front matter + Markdown

No new language, no compiler, no runtime, no lock-in. Every editor already renders it; every CI already has a YAML parser.

The choice follows from the prior art: a new language would add a seventh format to the six that already exist, instead of tying them together.

OpenAPI runs both ways

A card describes an operation, which may span several endpoints, a screen, a worker and a migration. gen openapi derives an OpenAPI contract from the cards — every card yields its contract, a conceived or designed one before any code exists, and only what the cards know goes in: method, path, response codes, path parameters, title, maturity. The checker takes the route inventory from the product’s OpenAPI document or route table. The format never parses sources; it reads only what the system states about itself.

JSON Schema, draft 2020-12

The front matter validates against a schema that also expresses conditional rules — claiming tested requires at least one recorded test; a write with no lifecycle transition must say what it changes.

What a schema cannot express belongs to the checker

Cross-card rules: step references that must resolve, coverage of every step, per-transport overrides, routes in the code with no card. A schema validates one file; these span the catalogue.

The CLI

TypeScript on Node, one box: validate, check, scaffold, preview, gen openapi, conformance. A Python prototype reads the same specification; the corpus keeps the two in step.

schema/operation-card.schema.json · a conditional rule
"$comment": "Claiming 'tested' or beyond requires at
least one recorded test and a tested note.",
"if": {
  "properties": {
    "maturity": { "enum": ["tested", "in_production"] }
  },
  "required": ["maturity"]
},
"then": {
  "properties": {
    "tests": { "minItems": 1 },
    "maturity_evidence": { "required": ["tested"] }
  },
  "required": ["tests"]
}

21 — architecture

How the pieces fit

An interactive map of the parts and of what each one compares with what — published next to this deck.

Open the map

architecture.html

Three repositories on one page: the format with its two checkers and the corpus; a server with operation cards; a frontend with form contracts. Pan, zoom, trace a relationship, switch the theme.

  • Two gates, one box. Each product repository runs npx usedesign@<pin> from the registry; nothing is installed, the version is frozen per repository.
  • Evidence is what the product says about itself. A route inventory, a JUnit report, a live-store inventory, real components rendered per state. Sources are never parsed.
  • Two readings, one corpus. The TypeScript box and the Python prototype pass the same 165 cases, and CI compares what the two report about the same repository.

22 — where this goes

Roadmap

  • ✓Specification, schema, examples, starter kit
    SPEC v1.7 through 31 rounds; six schemas; five cards over a fictional library
  • ✓Two implementations, one corpus, CI
    TypeScript on three Node versions and a Python prototype agree on 93 + 72 cases; all five checks
  • ✓usedesign validate · check · scaffold · preview · gen openapi · conformance
    Named codes; five invariants; a frontend gate sees the server’s cards
  • ✓Published, with build provenance
    30 versions, 0.2.0 → 1.7.1; the run waits for the box
  • ✓usedesign gen openapi
    The contract-first direction: every card yields its API contract, a conceived or designed one before the code exists
  • ✓v1.0 — declared 13 September 2026
    Criterion held seventeen rounds; required keys frozen, new checks arrive as warnings
  • More rounds
    The table in SPEC §8 keeps growing: round 25 recorded fourteen candidates, all inside 1.x, and round 26 took up four

The checker was the point, and it exists

A catalogue without one becomes aspiration within months. There are now two implementations, on two runtimes, and CI diffs what they report about the same repository — because two tools agreeing that something is broken while disagreeing about what is broken is how dialects start. The schema had never been run against the corpus until the second implementation existed. It agreed with it completely.

Read it, break it, tell us where

github.com/nistoc/usedesign

SPEC.md — the format and why each field exists schema/ — JSON Schema examples/ — five cards · starter-kit/ Apache 2.0 docs/architecture.html — the map

One description per operation. Every screen, contract, test and migration points back to it.