Skip to content
SLCR

Concepts

Links and satisfaction

How requirements connect to each other, and how those links decide whether each requirement is satisfied.

Requirements rarely stand alone. Some break a larger requirement into parts, some need context from elsewhere in the spec, and some use terms defined elsewhere. SLCR records each relationship as an explicit link that tools can follow. One kind of link, refines, also determines how individual check results combine into a status for each requirement.

Four ways nodes connect⁠Link to this section

LinkFrom → ToMeaningAffects satisfaction?
NestingSection → section or requirement; requirement → requirement or criterion"I'm placed here"No
refinesRequirement → requirement"I help satisfy that"Yes, and it's the only one
dependsOnRequirement → requirement or section"You need that to understand or implement me"No
usesTermSection or requirement → term"I use this word"No

All four determine what goes into a slice. refines and dependsOn links can't form cycles, and every link must point to an ID that exists. slcr vet reports either problem as an error.

Nesting provides structure⁠Link to this section

Nesting determines where a node appears in the rendered document and which section bodies it inherits as background. It says nothing about whether a child helps satisfy its parent.

A child requirement counts toward its parent only when it lists the parent in refines, even when the parent is the node it's nested in. In the todo-api spec, "POST creates an item" sits inside "TodoListItem endpoint" and also refines it explicitly:

YAML
- kind: requirement
  id: REQ-001
  title: TodoListItem endpoint
  body: There MUST be an endpoint for the TodoListItem resource.
  modality: MUST
  refinement: AND
  children:
    - kind: requirement
      id: REQ-002
      title: POST creates an item
      body: The endpoint MUST accept POST to create an item.
      modality: MUST
      refinement: AND # REQ-004 refines it (see below)
      refines: [REQ-001]

A child nested without refines elaborates on its parent without contributing to its status. It is judged only by its own validators.

One home, many parents⁠Link to this section

Some requirements serve several parents. In todo-api, both POST and GET require the request to be authenticated. That requirement is written once and lists every parent it serves:

YAML
- kind: requirement
  id: REQ-004
  title: Request is authenticated
  body: The request MUST be authenticated.
  modality: MUST
  refines: [REQ-002, REQ-003]

Two copies, one under each parent, could drift apart over time. One copy with two refines links means one statement, one set of validators, and one result that counts toward both parents.

Dependencies are context⁠Link to this section

dependsOn records what an implementer needs to know. The TodoListItem controller, REQ-005, depends on the endpoint it serves (REQ-001) and the table it reads and writes (REQ-007). The controller isn't satisfied just because the endpoint is, so this isn't refinement. But the controller can't be built without knowing the endpoint's shapes and the table's columns. A dependsOn link brings that context into the controller's slice and has no effect on satisfaction.

Terms⁠Link to this section

usesTerm links a section or requirement to the glossary entries it relies on, so a slice can include their definitions.

Satisfaction⁠Link to this section

slcr check runs every validator, then assigns each requirement one of three statuses:

  • Satisfied: its validators and refinements show that it holds.
  • Unsatisfied: they show that it doesn't.
  • Unknown: nothing decides it yet.

Leaves⁠Link to this section

A requirement that nothing refines is a leaf. Its status comes from its own validators: satisfied when all of them pass, unsatisfied when any fails. A leaf with no validators is unknown, which check reports as a coverage gap. A coverage gap shows where the spec isn't verified yet. It never counts as a failure, even with --deny-warnings.

Combining refinements⁠Link to this section

A requirement that others refine states how to combine them in its refinement field:

RefinementSatisfied when
ANDEvery refiner is satisfied. MAY refiners are left out, so an optional part can't block its parent.
ORAt least one refiner is satisfied.
XORExactly one refiner is satisfied, and the rest are unsatisfied.

If every refiner of an AND is a MAY, all of them are left out and nothing remains to evaluate. The parent is then vacuously satisfied: no counted refiner is unsatisfied, because there are none. slcr vet reports this as a vacuous-and warning. Using OR, or giving at least one refiner a MUST or SHOULD modality, gives the parent something to evaluate.

If a requirement has its own validators as well as refiners, both must hold: the result of its validators is combined with the result of its refiners using AND.

The operator is part of what the spec means. The todo-api spec says the API accepts OAuth tokens or API keys:

YAML
- kind: requirement
  id: REQ-008
  title: Accepts authentication tokens
  body: The API MUST accept OAuth tokens or API keys.
  modality: MUST
  refinement: OR
  children:
    - kind: requirement
      id: REQ-009
      title: Accepts OAuth tokens
      body: The API MAY accept OAuth tokens.
      modality: MAY
      refines: [REQ-008]
    - kind: requirement
      id: REQ-010
      title: Accepts API keys
      body: The API MAY accept API keys.
      modality: MAY
      refines: [REQ-008]

If the API had to accept both, the parent would use AND over two MUST children. The two versions read almost the same in prose, but the difference is recorded in the data, so check evaluates each one correctly.

Unknowns⁠Link to this section

Unknowns combine using strong Kleene logic, a three-valued logic in which a result is decided only when every possible outcome of the unknowns would give the same answer:

  • An AND with one unsatisfied refiner is unsatisfied, whatever the unknowns turn out to be.
  • An OR with one satisfied refiner is satisfied, whatever the unknowns turn out to be.
  • An AND whose refiners are all satisfied except one unknown is unknown, because the answer depends on the unknown.

A spec that is only partly covered by validators still gets every result that can already be decided, without guessing at the rest.

Modality sets severity⁠Link to this section

Whether a requirement is satisfied and how much a failure matters are separate questions. Validators and refinements answer the first. The requirement's modality answers the second:

ModalityWhen unsatisfied
MUST, MUST NOTA violation, which fails check
SHOULD, SHOULD NOTA warning, which fails check only with --deny-warnings
MAYInformational, which never fails check

MUST NOT and SHOULD NOT need no special structure. Their validators check that the forbidden behavior is absent.

A failure is absorbed when every requirement it refines is satisfied anyway, such as one alternative of an OR failing while another passes. An absorbed failure is reported as a warning, or as informational if the requirement is a MAY.