Requirement / Scenario grammar for normative .kmd
Gramática opcional, mas normativa quando usada, para expressar requisitos testáveis dentro de uma spec `.kmd`. Um bloco `### Requirement:` declara uma obrigação em RFC 2119 (SHALL / SHALL NOT / SHOULD / MAY); cada `#### Scenario:` filho descreve o critério observável em Given / When / Then. Cada Scenario é a unidade testável rastreável: o `/k-test-gen` emite exatamente um TDD por Scenario, ligado de volta à cláusula da spec pelo seu id estável. Internaliza o que a OpenSpec faz de melhor (spec-driven, requisito antes do código, cenário = teste) dentro da infra de spec já existente da Koder Stack, sem adotar a ferramenta externa (ver stack-RFC-013).
This grammar is an opt-in extension of
specs/kmd/format.kmd. A spec is free to keep using prose +triggers:/gates. But once a document uses a### Requirement:heading, every rule below is normative for that document:kmd lintandkoder-spec-auditreject malformed blocks, and/k-test-genis entitled to treat each#### Scenario:as a test case.
KMD already gives us frontmatter, validated cross-refs and triggers:. What it lacks — and what OpenSpec's RFC-2119 + GivenWhenThen format gives — is a structured, machine-consumable unit of obligation that maps 1:1 to a test. This spec adds exactly that unit, reusing KMD's heading/list syntax verbatim (no new tokens), so the existing parser, linter and renderer keep working.
1. Grammar
1.1 Requirement block
### Requirement: User authentication {#req-auth}
The Identity service SHALL issue a Koder ID session token on a successful
password+passkey login, and SHALL NOT issue one on a failed factor.
#### Scenario: Valid credentials
- GIVEN a registered user with a valid password and an enrolled passkey
- WHEN the user completes both factors
- THEN a session token is returned
- AND the token's `aud` claim equals the requesting client_id
#### Scenario: Wrong password
- GIVEN a registered user
- WHEN the user submits an incorrect password
- THEN no token is issued
- AND the failure is logged with reason `bad_factor`1.2 Shape
| Element | Form | Level |
|---|---|---|
| Requirement | ### Requirement: <Title> (optional {#anchor}) |
H3 |
| Obligation prose | one or more paragraphs containing ≥1 RFC-2119 keyword | — |
| Scenario | #### Scenario: <Name> |
H4 |
| Steps | a single bullet list, each item opening with a step keyword | — |
Step keywords (uppercase, verbatim): GIVEN, WHEN, THEN, AND, BUT. ANDBUT continue the kind of the previous step (GivenWhen/Then).
RFC-2119 keywords (uppercase, verbatim): SHALL, SHALL NOT, MUST, MUST NOT, SHOULD, SHOULD NOT, MAY. MUST≡SHALL and MUST NOT≡SHALL
NOT (alias; prefer SHALL in new specs for parity with the imported grammar).
2. Rules (normative)
Requirement: A Requirement block is well-formed
The grammar SHALL be enforced by kmd lint and koder-spec-audit so that a malformed requirement fails the audit rather than silently degrading.
Scenario: Requirement carries an obligation
- GIVEN a
### Requirement:heading - WHEN its prose body contains zero RFC-2119 keywords
- THEN lint emits error
req-no-obligation
Scenario: Requirement title is unique
- GIVEN two
### Requirement:headings in the same document with the same title and no distinguishing{#anchor} - WHEN the document is linted
- THEN lint emits error
req-duplicate-id
Scenario: Scenario has an action and an outcome
- GIVEN a
#### Scenario:block - WHEN its step list lacks a
WHENstep or lacks aTHENstep - THEN lint emits error
scenario-incomplete
Scenario: Steps use known keywords
- GIVEN a
#### Scenario:step list - WHEN a bullet does not begin with one of GIVENWHENTHENANDBUT
- THEN lint emits error
scenario-bad-step
Requirement: Every Scenario has a stable id
Each Scenario SHALL have a deterministic, content-stable id of the form <spec-slug>#<requirement-slug>/<scenario-slug>, where spec-slug is the spec's path slug, requirement-slug is the requirement {#anchor} (or the title slugified), and scenario-slug is the scenario name slugified. This id is the traceability key shared by koder.manifest.json and the regression-test registry.
Scenario: Id is stable across prose edits
- GIVEN a Scenario whose id was recorded in
koder.manifest.json - WHEN the Requirement's obligation prose is reworded but the headings are unchanged
- THEN the computed Scenario id is unchanged
- AND the previously generated test is still matched to it
Scenario: Renaming a Scenario is a tracked change
- GIVEN a Scenario with an existing generated test
- WHEN the Scenario heading text changes
- THEN its id changes
- AND
/k-testreports the old id as orphaned rather than silently dropping coverage
Requirement: A Requirement without Scenarios is advisory
A Requirement MAY exist with zero Scenarios, but it then carries no test obligation and SHALL be reported so the gap is visible (it cannot masquerade as covered).
Scenario: Scenario-less requirement is flagged
- GIVEN a
### Requirement:with a SHALL clause and no#### Scenario: - WHEN the document is audited
- THEN audit emits warning
req-no-scenario - AND the requirement is excluded from the test-gen coverage denominator until a Scenario is added
3. Test-generation contract
Requirement: One Scenario yields one traceable test
/k-test-gen (and its category subagents) SHALL emit exactly one TDD per #### Scenario:, named and cross-referenced by the Scenario id (req-scenario-id). GIVEN steps map to test setuparrange, WHEN to the act, THENAND-after-THEN to the assertions.
Scenario: Scenario becomes a generated test
- GIVEN a spec with a SHALL Requirement and one Scenario
- WHEN
/k-test-genruns for the implementing module - THEN a TDD is emitted whose id references the Scenario id
- AND its assertions cover every THEN/BUT step
Scenario: Missing coverage fails the gate
- GIVEN a SHALL Requirement with ≥1 Scenario and no corresponding generated test
- WHEN
/k-testruns - THEN the run fails with the uncovered Scenario id named
- AND (per
regression-tests.kmd§7) a generator-coverage backfill ticket is suggested
Requirement: The verify triad maps to concrete gates
The OpenSpec verify triad SHALL be realised as explicit, already-owned gates rather than a separate tool: Completeness = every Requirement has ≥1 Scenario (req-advisory warning count is zero); Correctness = every Scenario's THEN is asserted by a passing generated test (req-testgen gate); Coherence = kmd lint cross-refs resolve and no two Scenarios in a document assert contradictory outcomes for the same WHEN.
Scenario: Coherence catches contradictory scenarios
- GIVEN two Scenarios in one document with identical GIVEN+WHEN and contradictory THEN
- WHEN the document is audited
- THEN audit emits error
scenario-contradiction
4. Relationship to existing mechanisms
triggers:stay as-is — they answer "when must a human read this spec?".Requirements answer "what must the system do, and how is it tested?". Both coexist in one document.
gatesremain the coarse pass/fail switches; a Requirement is the fine-grained, individually-traceable obligation underneath.
regression-tests.kmd— a fixed bug whose class was expressible as aScenario SHOULD gain that Scenario in the owning spec, closing the generator-coverage loop (§7) at the spec layer, not just the test layer.
- Spec-delta lifecycle (stack-RFC-013 phase 4) — Requirements are the unit a
spec-delta addsmodifiesremoves, which is why this grammar lands first.
5. Conversion recipe (prose → Requirement/Scenario)
Receita validada no backfill pilot (fase 6 — landing-pages/products.kmd e errors/user-facing-messages.kmd):
- Não reescreva a prosa — adicione uma seção
## Rules (normative)(ouconverta a seção de regras existente) com os blocos; a prosa original permanece como contexto. A conversão NÃO pode mudar significado normativo: cada SHALL do bloco precisa rastrear para uma frase já existente.
- 1 Requirement = 1 obrigação coesa com
{#anchor}estável (req-…);2–4 Scenarios por Requirement: o happy path observável + a violação que o gate deve pegar (o "Kodix-class" — pense no post-mortem que essa cláusula evitaria).
- THEN observável ou não é Scenario — cada THEN/AND tem que ser
verificável por um teste estático/headless (grep, exit code, render). Se só um humano avalia, fica na prosa.
- Frontmatter:
kanon: "<versão corrente>"+applies_when:(liga aspec aos módulos pro
/k-test-genFASE 1.5 descobrir os ids por dados) e, quando existir oráculo executável,kanon_bindings:(gateblockexigefp_budget, RFC-014 R3). - Validar:
kmd lint <spec>(forma) + `koder-spec-auditrequirement-scenario --doc=spec` (ids/semântica) — ambos limpos antes do commit.
Disposição do corpus restante (decisão registrada, fase 6): a conversão é oportunista — uma spec ganha a gramática quando receber edição substancial (mesma regra de migração do document-format.kmd), não por sweep rastreado. Racional: a conversão exige julgamento sobre o que é testável; um sweep mecânico produziria Scenarios de baixa qualidade em massa e o denominador de cobertura cresceria mais rápido que a capacidade do /k-test-gen de honrá-lo.