docs(spec): EARS-lite behavioural criteria and a context snapshot
Both skills gain EARS-lite as an option alongside prose criteria: WHEN/WHILE/IF plus the unconditional form, for behaviour that a measurable threshold cannot express. feature-spec also gains a Context snapshot section — the executor of a spec routinely cannot open the architecture document it points at, so the facts it depends on get inlined with provenance. Pressure-tested per #19, and the RED phase argued against half the issue. A subagent with no skill wrote a strong spec and then explained that it had deliberately NOT inlined architecture context, "because both are owned by the existing architecture document and duplicating them here would create two drifting sources of truth". That objection is correct, so the section is built around answering it: snapshot what the implementer cannot look up, always cite source and version, and state that the source wins on disagreement. Prose criteria came out fine unaided; EARS phrasing did not appear at all, which is where the real gap was. The GREEN run then found seven defects in the first draft, all fixed here: - The example was in the same domain as the test scenario, so it got copied rather than read — the spec inherited an extraction-confidence requirement its feature request never mentioned. The example is now deliberately off-domain, with that failure written up as the reason. - EARS-lite had no unconditional form, so a genuine invariant ("no instruction above threshold ever reaches a file") had to be bent into a WHILE that a system trivially satisfies by never entering the state. Added. - "Maps 1:1 onto a test" was false for the WHILE form, which is universally quantified. Softened to say which forms map to one test and which do not. - The provenance rule had no answer for the case it was written for: an author who also cannot read the source. Now: mark the block UNVERIFIED in those words and make confirmation the implementer's first task. - "Snapshot only what this spec's decisions rest on" was narrower than the section's own purpose; a table name underpins no decision and still blocks the migration. Widened, with the omissions stated deliberately. - "Say so in the section itself" did not say what, at what granularity. Now one opening line, not a per-fact annotation. - The template had no slot for EARS lines, so their placement was a guess. Added as a subsection with a note that they are properties, not checkboxes. Closes #12 Bump-Type: minor
This commit is contained in:
@@ -200,6 +200,56 @@ Dev: [full command]
|
||||
[Anything unresolved that needs input before implementation begins]
|
||||
```
|
||||
|
||||
## EARS-lite acceptance criteria
|
||||
|
||||
Measurable criteria answer "is it done?". They do not always answer "what should it do
|
||||
when X happens?" — and behavioural requirements written as prose are where ambiguity
|
||||
survives review. For any criterion that is conditional or event-driven, use EARS-lite
|
||||
instead of prose:
|
||||
|
||||
```
|
||||
WHEN <trigger> THE SYSTEM SHALL <observable response>
|
||||
WHILE <state> THE SYSTEM SHALL <observable response>
|
||||
IF <unwanted condition> THEN THE SYSTEM SHALL <observable response>
|
||||
```
|
||||
|
||||
There is a fourth, unconditional form. Use it for invariants — the properties that must
|
||||
hold with no trigger at all, which are usually the ones that matter most:
|
||||
|
||||
```
|
||||
THE SYSTEM SHALL <observable response>
|
||||
```
|
||||
|
||||
Worked, deliberately from a domain unlike whatever you are specifying:
|
||||
|
||||
```
|
||||
THE SYSTEM SHALL reject a firmware image whose signature does not verify
|
||||
WHEN a sensor reports a temperature above the shutdown limit
|
||||
THE SYSTEM SHALL open the contactor within 200ms
|
||||
WHILE the contactor is open
|
||||
THE SYSTEM SHALL refuse every start command
|
||||
IF a sensor reading is missing or stale by more than one interval
|
||||
THEN THE SYSTEM SHALL treat it as above the limit
|
||||
```
|
||||
|
||||
The example is off-domain on purpose. An on-domain example gets copied: a spec written
|
||||
against an invoice-flavoured example inherited a requirement about extraction confidence
|
||||
that its feature request never mentioned, and the author only noticed on review. Read
|
||||
the shapes, not the content.
|
||||
|
||||
Each conditional line names a trigger and one observable response, which usually maps
|
||||
onto a single test. The `WHILE` and unconditional forms are universally quantified —
|
||||
"every start command", "a firmware image" — so they map onto a property or a set of
|
||||
cases, not one assertion. That is still worth writing: it closes the requirements half
|
||||
of the audit trail, which is otherwise issue-body prose.
|
||||
|
||||
**This is an option, not a replacement.** Threshold criteria ("p99 < 200ms under 100
|
||||
RPS") stay as they are — EARS phrasing adds nothing to a number. Use it for conditional
|
||||
behaviour, error handling, and state-dependent rules.
|
||||
|
||||
For the executor-facing handoff, `feature-spec` also carries a **Context snapshot**
|
||||
section — inline the architecture facts the implementer cannot look up.
|
||||
|
||||
## Reframing Vague Requirements
|
||||
|
||||
When you receive a vague requirement, translate it into specific success criteria before writing any spec content:
|
||||
|
||||
Reference in New Issue
Block a user