plan-verify 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +48 -0
- package/README.md +1431 -2
- package/assets/icon.png +0 -0
- package/assets/logo-mark.png +0 -0
- package/assets/logo.png +0 -0
- package/examples/checkout.ears.md +74 -0
- package/examples/order-always.ears.md +3 -0
- package/examples/order-awkward.ears.md +8 -0
- package/examples/order-bad.json +1 -0
- package/examples/order-chain.ears.md +9 -0
- package/examples/order-chosen.ears.md +7 -0
- package/examples/order-clear.ears.md +8 -0
- package/examples/order-combined.ears.md +10 -0
- package/examples/order-conflict.ears.md +8 -0
- package/examples/order-covered.ears.md +9 -0
- package/examples/order-duplicate.ears.md +8 -0
- package/examples/order-fail.json +5 -0
- package/examples/order-misuse.ears.md +7 -0
- package/examples/order-negation.ears.md +7 -0
- package/examples/order-pass.json +5 -0
- package/examples/order-quantity.ears.md +8 -0
- package/examples/order-redundant.ears.md +9 -0
- package/examples/order-repair.ears.md +9 -0
- package/examples/order-repeat.ears.md +13 -0
- package/examples/order-same.ears.md +3 -0
- package/examples/order-status.ears.md +7 -0
- package/examples/order-unanswered.ears.md +8 -0
- package/examples/order-unhandled.ears.md +8 -0
- package/examples/order-unmeasured.ears.md +10 -0
- package/examples/order-unused.ears.md +13 -0
- package/examples/order-vacuous.ears.md +9 -0
- package/examples/order-vague.ears.md +7 -0
- package/examples/order-wide.ears.md +5 -0
- package/examples/order-wording.ears.md +8 -0
- package/examples/order.ears.md +7 -0
- package/examples/payment-if.ears.md +7 -0
- package/examples/plan-to-problems/README.md +54 -0
- package/examples/plan-to-problems/expected-problems.md +25 -0
- package/examples/plan-to-problems/generate.mjs +186 -0
- package/examples/plan-to-problems/generated.ears.md +27 -0
- package/examples/plan-to-problems/lint.json +576 -0
- package/examples/plan-to-problems/plan.md +5 -0
- package/examples/plan-to-problems/planted.txt +5 -0
- package/examples/plan-to-problems/problems.md +112 -0
- package/examples/status-placed.ears.md +3 -0
- package/package.json +25 -4
- package/scripts/install-scriptc.sh +16 -0
- package/skills/plan-verify/SKILL.md +1515 -0
- package/src/check.ts +250 -0
- package/src/decide.ts +1324 -0
- package/src/ears.ts +1520 -0
- package/src/jsonvalue.ts +228 -0
- package/src/lint.ts +1245 -0
- package/src/main.ts +273 -0
- package/src/policy.ts +148 -0
- package/src/repair.ts +567 -0
- package/src/report.ts +329 -0
- package/src/scenarios.ts +149 -0
- package/src/smt.ts +97 -0
- package/src/solver.ts +868 -0
- package/src/spec.ts +307 -0
- package/src/term.ts +331 -0
- package/src/text.ts +95 -0
- package/test/fixtures/malformed.ears.md +3 -0
- package/test/fixtures/unknown-phrase.ears.md +5 -0
- package/test/golden/check-fail-json.exit +1 -0
- package/test/golden/check-fail-json.stderr +0 -0
- package/test/golden/check-fail-json.stdout +13 -0
- package/test/golden/check-fail.exit +1 -0
- package/test/golden/check-fail.stderr +0 -0
- package/test/golden/check-fail.stdout +7 -0
- package/test/golden/check-pass-json.exit +1 -0
- package/test/golden/check-pass-json.stderr +0 -0
- package/test/golden/check-pass-json.stdout +6 -0
- package/test/golden/check-pass.exit +1 -0
- package/test/golden/check-pass.stderr +0 -0
- package/test/golden/check-pass.stdout +3 -0
- package/test/golden/emit-smt-json-usage.exit +1 -0
- package/test/golden/emit-smt-json-usage.stderr +0 -0
- package/test/golden/emit-smt-json-usage.stdout +4 -0
- package/test/golden/emit-smt-json.exit +1 -0
- package/test/golden/emit-smt-json.stderr +0 -0
- package/test/golden/emit-smt-json.stdout +5 -0
- package/test/golden/emit-smt.exit +1 -0
- package/test/golden/emit-smt.stderr +0 -0
- package/test/golden/emit-smt.stdout +10 -0
- package/test/golden/help.exit +1 -0
- package/test/golden/help.stderr +0 -0
- package/test/golden/help.stdout +8 -0
- package/test/golden/lint-awkward.exit +1 -0
- package/test/golden/lint-awkward.stderr +0 -0
- package/test/golden/lint-awkward.stdout +29 -0
- package/test/golden/lint-chain.exit +1 -0
- package/test/golden/lint-chain.stderr +0 -0
- package/test/golden/lint-chain.stdout +56 -0
- package/test/golden/lint-checkout.exit +1 -0
- package/test/golden/lint-checkout.stderr +0 -0
- package/test/golden/lint-checkout.stdout +301 -0
- package/test/golden/lint-clean.exit +1 -0
- package/test/golden/lint-clean.stderr +0 -0
- package/test/golden/lint-clean.stdout +9 -0
- package/test/golden/lint-clear.exit +1 -0
- package/test/golden/lint-clear.stderr +0 -0
- package/test/golden/lint-clear.stdout +9 -0
- package/test/golden/lint-conflict-json.exit +1 -0
- package/test/golden/lint-conflict-json.stderr +0 -0
- package/test/golden/lint-conflict-json.stdout +80 -0
- package/test/golden/lint-conflict.exit +1 -0
- package/test/golden/lint-conflict.stderr +0 -0
- package/test/golden/lint-conflict.stdout +41 -0
- package/test/golden/lint-duplicate.exit +1 -0
- package/test/golden/lint-duplicate.stderr +0 -0
- package/test/golden/lint-duplicate.stdout +14 -0
- package/test/golden/lint-gap.exit +1 -0
- package/test/golden/lint-gap.stderr +0 -0
- package/test/golden/lint-gap.stdout +12 -0
- package/test/golden/lint-misuse.exit +1 -0
- package/test/golden/lint-misuse.stderr +0 -0
- package/test/golden/lint-misuse.stdout +18 -0
- package/test/golden/lint-negation.exit +1 -0
- package/test/golden/lint-negation.stderr +0 -0
- package/test/golden/lint-negation.stdout +13 -0
- package/test/golden/lint-quantity.exit +1 -0
- package/test/golden/lint-quantity.stderr +0 -0
- package/test/golden/lint-quantity.stdout +41 -0
- package/test/golden/lint-redundant.exit +1 -0
- package/test/golden/lint-redundant.stderr +0 -0
- package/test/golden/lint-redundant.stdout +14 -0
- package/test/golden/lint-repair-json.exit +1 -0
- package/test/golden/lint-repair-json.stderr +0 -0
- package/test/golden/lint-repair-json.stdout +127 -0
- package/test/golden/lint-repair.exit +1 -0
- package/test/golden/lint-repair.stderr +0 -0
- package/test/golden/lint-repair.stdout +54 -0
- package/test/golden/lint-repeat.exit +1 -0
- package/test/golden/lint-repeat.stderr +0 -0
- package/test/golden/lint-repeat.stdout +48 -0
- package/test/golden/lint-status.exit +1 -0
- package/test/golden/lint-status.stderr +0 -0
- package/test/golden/lint-status.stdout +44 -0
- package/test/golden/lint-unanswered.exit +1 -0
- package/test/golden/lint-unanswered.stderr +0 -0
- package/test/golden/lint-unanswered.stdout +12 -0
- package/test/golden/lint-unhandled.exit +1 -0
- package/test/golden/lint-unhandled.stderr +0 -0
- package/test/golden/lint-unhandled.stdout +12 -0
- package/test/golden/lint-unmeasured.exit +1 -0
- package/test/golden/lint-unmeasured.stderr +0 -0
- package/test/golden/lint-unmeasured.stdout +13 -0
- package/test/golden/lint-unused-json.exit +1 -0
- package/test/golden/lint-unused-json.stderr +0 -0
- package/test/golden/lint-unused-json.stdout +37 -0
- package/test/golden/lint-unused.exit +1 -0
- package/test/golden/lint-unused.stderr +0 -0
- package/test/golden/lint-unused.stdout +25 -0
- package/test/golden/lint-vacuous-json.exit +1 -0
- package/test/golden/lint-vacuous-json.stderr +0 -0
- package/test/golden/lint-vacuous-json.stdout +27 -0
- package/test/golden/lint-vacuous.exit +1 -0
- package/test/golden/lint-vacuous.stderr +0 -0
- package/test/golden/lint-vacuous.stdout +19 -0
- package/test/golden/lint-vague.exit +1 -0
- package/test/golden/lint-vague.stderr +0 -0
- package/test/golden/lint-vague.stdout +21 -0
- package/test/golden/lint-wide.exit +1 -0
- package/test/golden/lint-wide.stderr +0 -0
- package/test/golden/lint-wide.stdout +9 -0
- package/test/golden/lint-wording.exit +1 -0
- package/test/golden/lint-wording.stderr +0 -0
- package/test/golden/lint-wording.stdout +21 -0
- package/test/golden/malformed-scenario.exit +1 -0
- package/test/golden/malformed-scenario.stderr +1 -0
- package/test/golden/malformed-scenario.stdout +0 -0
- package/test/golden/malformed-spec.exit +1 -0
- package/test/golden/malformed-spec.stderr +1 -0
- package/test/golden/malformed-spec.stdout +0 -0
- package/test/golden/policy-counterexample.exit +1 -0
- package/test/golden/policy-counterexample.stderr +0 -0
- package/test/golden/policy-counterexample.stdout +13 -0
- package/test/golden/policy-covered.exit +1 -0
- package/test/golden/policy-covered.stderr +0 -0
- package/test/golden/policy-covered.stdout +19 -0
- package/test/golden/policy-holds.exit +1 -0
- package/test/golden/policy-holds.stderr +0 -0
- package/test/golden/policy-holds.stdout +3 -0
- package/test/golden/policy-json.exit +1 -0
- package/test/golden/policy-json.stderr +0 -0
- package/test/golden/policy-json.stdout +34 -0
- package/test/golden/policy-status.exit +1 -0
- package/test/golden/policy-status.stderr +0 -0
- package/test/golden/policy-status.stdout +16 -0
- package/test/golden/policy-vacuous.exit +1 -0
- package/test/golden/policy-vacuous.stderr +0 -0
- package/test/golden/policy-vacuous.stdout +3 -0
- package/test/golden/policy-wide.exit +1 -0
- package/test/golden/policy-wide.stderr +0 -0
- package/test/golden/policy-wide.stdout +15 -0
- package/test/golden/policy.exit +1 -0
- package/test/golden/policy.stderr +0 -0
- package/test/golden/policy.stdout +19 -0
- package/test/golden/scenarios-json.exit +1 -0
- package/test/golden/scenarios-json.stderr +0 -0
- package/test/golden/scenarios-json.stdout +75 -0
- package/test/golden/scenarios-status.exit +1 -0
- package/test/golden/scenarios-status.stderr +0 -0
- package/test/golden/scenarios-status.stdout +24 -0
- package/test/golden/scenarios.exit +1 -0
- package/test/golden/scenarios.stderr +0 -0
- package/test/golden/scenarios.stdout +26 -0
- package/test/golden/unit.exit +1 -0
- package/test/golden/unit.stderr +0 -0
- package/test/golden/unit.stdout +2 -0
- package/test/golden/unknown-phrase.exit +1 -0
- package/test/golden/unknown-phrase.stderr +1 -0
- package/test/golden/unknown-phrase.stdout +0 -0
- package/test/golden/usage.exit +1 -0
- package/test/golden/usage.stderr +9 -0
- package/test/golden/usage.stdout +0 -0
- package/test/run-all.ts +499 -0
- package/test/soundness.ts +375 -0
- package/test/unit.ts +1352 -0
- package/tsconfig.json +15 -0
package/README.md
CHANGED
|
@@ -1,3 +1,1432 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center"><img src="assets/logo.png" alt="plan-verify" width="480"></p>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# plan-verify
|
|
4
|
+
|
|
5
|
+
Formerly ears-verify.
|
|
6
|
+
|
|
7
|
+
Catch contradictions in your requirements before anyone builds them.
|
|
8
|
+
|
|
9
|
+
plan-verify is for product managers, and for the AI agents who write requirements with them. You talk through what the product should do. The agent turns that into [EARS](https://alistairmavin.com/ears/) requirements. Each name in the schema is bound to a phrase, and the requirement is written with those phrases, so a line reads the way you would say it. plan-verify then looks for requirements that cannot all be true, and explains the clash in plain language: which requirements disagree, in what situation, a concrete example, and a fix.
|
|
10
|
+
|
|
11
|
+
It will not choose which requirement wins. That question goes back to you.
|
|
12
|
+
|
|
13
|
+
The same TypeScript runs on Node 24 and compiles to a native binary with [ScriptC](https://scriptc.dev) 0.4.1. A requirement is checked by our own solver. The logic is booleans, integers in a declared range, and enums.
|
|
14
|
+
|
|
15
|
+
## Try it
|
|
16
|
+
|
|
17
|
+
You need Node 24 and clang. `npm install` installs ScriptC 0.4.1.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install
|
|
21
|
+
npm run build
|
|
22
|
+
./build/plan-verify lint examples/order-conflict.ears.md
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`node --experimental-strip-types src/main.ts` runs the same program and prints the same bytes. The tests fail if the two ever drift.
|
|
26
|
+
|
|
27
|
+
<!-- verify stdout 1
|
|
28
|
+
build/plan-verify
|
|
29
|
+
lint
|
|
30
|
+
examples/order-conflict.ears.md
|
|
31
|
+
-->
|
|
32
|
+
```text
|
|
33
|
+
spec: examples/order-conflict.ears.md
|
|
34
|
+
conflicts: 1
|
|
35
|
+
gaps: 0
|
|
36
|
+
misuse: 0
|
|
37
|
+
wording: 0
|
|
38
|
+
redundant: 0
|
|
39
|
+
duplicate: 0
|
|
40
|
+
vacuous: 0
|
|
41
|
+
unused: 0
|
|
42
|
+
|
|
43
|
+
conflict {R2, R3}
|
|
44
|
+
R2 and R3 cannot both hold.
|
|
45
|
+
|
|
46
|
+
R2 line 7
|
|
47
|
+
When an order is submitted, the Order System shall fulfill the order.
|
|
48
|
+
|
|
49
|
+
R3 line 8
|
|
50
|
+
When an order is submitted, the Order System shall not fulfill the order.
|
|
51
|
+
|
|
52
|
+
They clash when an order was submitted.
|
|
53
|
+
R2 requires that the order was fulfilled.
|
|
54
|
+
R3 requires that the order was not fulfilled.
|
|
55
|
+
|
|
56
|
+
Example scenario:
|
|
57
|
+
{
|
|
58
|
+
"order_submitted": true,
|
|
59
|
+
"order_fulfilled": true
|
|
60
|
+
}
|
|
61
|
+
This scenario keeps R2 and breaks R3.
|
|
62
|
+
|
|
63
|
+
Suggested fix: ask which requirement should win. To keep R2, remove R3. To keep R3, remove R2.
|
|
64
|
+
|
|
65
|
+
Candidate repairs follow. This report does not choose one.
|
|
66
|
+
|
|
67
|
+
repair drop R2
|
|
68
|
+
One repair is to drop R2 and keep R3 as written.
|
|
69
|
+
When an order is submitted, the Order System shall not fulfill the order.
|
|
70
|
+
|
|
71
|
+
repair drop R3
|
|
72
|
+
One repair is to drop R3 and keep R2 as written.
|
|
73
|
+
When an order is submitted, the Order System shall fulfill the order.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
R2 says the order was fulfilled. R3 says the order was not fulfilled. Both apply when the customer submits an order, so no behavior can satisfy both. The example keeps R2 only because R3 is the last requirement in the set. It is a witness, and it is not a recommendation. Ask which requirement should win, then delete or rewrite the other. The candidate repairs under the clash are the same question in sentence form: drop one requirement, or, when a single extra condition can separate them, the rewritten line. None of those repairs is chosen.
|
|
77
|
+
|
|
78
|
+
## A conversation
|
|
79
|
+
|
|
80
|
+
A product manager says: "When no order has been submitted, do not fulfill it. When an order is submitted, fulfill it. And when an order is submitted, do not fulfill it."
|
|
81
|
+
|
|
82
|
+
The agent writes that as EARS. Each declared name is bound to a phrase, and the requirements use those phrases:
|
|
83
|
+
|
|
84
|
+
```ears
|
|
85
|
+
event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
|
|
86
|
+
output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
|
|
87
|
+
|
|
88
|
+
R1 When an order is not submitted, the Order System shall not fulfill the order.
|
|
89
|
+
R2 When an order is submitted, the Order System shall fulfill the order.
|
|
90
|
+
R3 When an order is submitted, the Order System shall not fulfill the order.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
That is `examples/order-conflict.ears.md`. Lint names the clash. R2 and R3 both apply when an order was submitted, and the example keeps R2 and breaks R3.
|
|
94
|
+
|
|
95
|
+
<!-- verify stdout 1
|
|
96
|
+
build/plan-verify
|
|
97
|
+
lint
|
|
98
|
+
examples/order-conflict.ears.md
|
|
99
|
+
-->
|
|
100
|
+
```text
|
|
101
|
+
spec: examples/order-conflict.ears.md
|
|
102
|
+
conflicts: 1
|
|
103
|
+
gaps: 0
|
|
104
|
+
misuse: 0
|
|
105
|
+
wording: 0
|
|
106
|
+
redundant: 0
|
|
107
|
+
duplicate: 0
|
|
108
|
+
vacuous: 0
|
|
109
|
+
unused: 0
|
|
110
|
+
|
|
111
|
+
conflict {R2, R3}
|
|
112
|
+
R2 and R3 cannot both hold.
|
|
113
|
+
|
|
114
|
+
R2 line 7
|
|
115
|
+
When an order is submitted, the Order System shall fulfill the order.
|
|
116
|
+
|
|
117
|
+
R3 line 8
|
|
118
|
+
When an order is submitted, the Order System shall not fulfill the order.
|
|
119
|
+
|
|
120
|
+
They clash when an order was submitted.
|
|
121
|
+
R2 requires that the order was fulfilled.
|
|
122
|
+
R3 requires that the order was not fulfilled.
|
|
123
|
+
|
|
124
|
+
Example scenario:
|
|
125
|
+
{
|
|
126
|
+
"order_submitted": true,
|
|
127
|
+
"order_fulfilled": true
|
|
128
|
+
}
|
|
129
|
+
This scenario keeps R2 and breaks R3.
|
|
130
|
+
|
|
131
|
+
Suggested fix: ask which requirement should win. To keep R2, remove R3. To keep R3, remove R2.
|
|
132
|
+
|
|
133
|
+
Candidate repairs follow. This report does not choose one.
|
|
134
|
+
|
|
135
|
+
repair drop R2
|
|
136
|
+
One repair is to drop R2 and keep R3 as written.
|
|
137
|
+
When an order is submitted, the Order System shall not fulfill the order.
|
|
138
|
+
|
|
139
|
+
repair drop R3
|
|
140
|
+
One repair is to drop R3 and keep R2 as written.
|
|
141
|
+
When an order is submitted, the Order System shall fulfill the order.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The product manager chooses to keep fulfillment of a submitted order and to remove the refusal. The agent deletes R3. `examples/order-chosen.ears.md` is what remains:
|
|
145
|
+
|
|
146
|
+
```ears
|
|
147
|
+
R1 When an order is not submitted, the Order System shall not fulfill the order.
|
|
148
|
+
R2 When an order is submitted, the Order System shall fulfill the order.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Lint again, and the spec is clean:
|
|
152
|
+
|
|
153
|
+
<!-- verify stdout 0
|
|
154
|
+
build/plan-verify
|
|
155
|
+
lint
|
|
156
|
+
examples/order-chosen.ears.md
|
|
157
|
+
-->
|
|
158
|
+
```text
|
|
159
|
+
spec: examples/order-chosen.ears.md
|
|
160
|
+
conflicts: 0
|
|
161
|
+
gaps: 0
|
|
162
|
+
misuse: 0
|
|
163
|
+
wording: 0
|
|
164
|
+
redundant: 0
|
|
165
|
+
duplicate: 0
|
|
166
|
+
vacuous: 0
|
|
167
|
+
unused: 0
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## A condition that separates the clash
|
|
171
|
+
|
|
172
|
+
`examples/order-repair.ears.md` lends a copy that is in the building and refuses a copy that is out on loan. Those two states can both be true, so the requirements clash. Lint still asks which requirement should win. It also shows candidate repairs it has checked: drop either requirement, or add one existing state to one condition. Adding "and the copy is not out on loan" to R1 leaves R2 unchanged, and the patched spec no longer has that conflict. Adding "and the copy is not in the building" to R2 is the other strengthening. The report does not pick one.
|
|
173
|
+
|
|
174
|
+
<!-- verify stdout 1
|
|
175
|
+
build/plan-verify
|
|
176
|
+
lint
|
|
177
|
+
examples/order-repair.ears.md
|
|
178
|
+
-->
|
|
179
|
+
```text
|
|
180
|
+
spec: examples/order-repair.ears.md
|
|
181
|
+
conflicts: 1
|
|
182
|
+
gaps: 1
|
|
183
|
+
misuse: 0
|
|
184
|
+
wording: 0
|
|
185
|
+
redundant: 0
|
|
186
|
+
duplicate: 0
|
|
187
|
+
vacuous: 0
|
|
188
|
+
unused: 0
|
|
189
|
+
|
|
190
|
+
conflict {R1, R2}
|
|
191
|
+
R1 and R2 cannot both hold.
|
|
192
|
+
|
|
193
|
+
R1 line 8
|
|
194
|
+
While the copy is in the building, when a member asks to take a copy home, the Desk shall allow the loan.
|
|
195
|
+
|
|
196
|
+
R2 line 9
|
|
197
|
+
While the copy is out on loan, when a member asks to take a copy home, the Desk shall not allow the loan.
|
|
198
|
+
|
|
199
|
+
They clash when the copy was in the building, the copy was out on loan, and a member asks to take a copy home.
|
|
200
|
+
R1 requires that the loan was allowed.
|
|
201
|
+
R2 requires that the loan was not allowed.
|
|
202
|
+
|
|
203
|
+
Example scenario:
|
|
204
|
+
{
|
|
205
|
+
"copy_in_building": true,
|
|
206
|
+
"copy_on_loan": true,
|
|
207
|
+
"borrow_requested": true,
|
|
208
|
+
"loan_allowed": true
|
|
209
|
+
}
|
|
210
|
+
This scenario keeps R1 and breaks R2.
|
|
211
|
+
|
|
212
|
+
Suggested fix: ask which requirement should win. To keep R1, remove R2. To keep R2, remove R1.
|
|
213
|
+
|
|
214
|
+
Candidate repairs follow. This report does not choose one.
|
|
215
|
+
|
|
216
|
+
repair drop R1
|
|
217
|
+
One repair is to drop R1 and keep R2 as written.
|
|
218
|
+
While the copy is out on loan, when a member asks to take a copy home, the Desk shall not allow the loan.
|
|
219
|
+
|
|
220
|
+
repair drop R2
|
|
221
|
+
One repair is to drop R2 and keep R1 as written.
|
|
222
|
+
While the copy is in the building, when a member asks to take a copy home, the Desk shall allow the loan.
|
|
223
|
+
|
|
224
|
+
repair strengthen R1
|
|
225
|
+
One repair is to add "and the copy is not out on loan" to the condition of R1. R2 stays as written.
|
|
226
|
+
While the copy is in the building, when a member asks to take a copy home and the copy is not out on loan, the Desk shall allow the loan.
|
|
227
|
+
|
|
228
|
+
repair strengthen R2
|
|
229
|
+
One repair is to add "and the copy is not in the building" to the condition of R2. R1 stays as written.
|
|
230
|
+
While the copy is out on loan, when a member asks to take a copy home and the copy is not in the building, the Desk shall not allow the loan.
|
|
231
|
+
|
|
232
|
+
gap loan_allowed
|
|
233
|
+
Nothing says whether the loan was allowed when the copy was in the building, the copy was out on loan, and not a member asks to take a copy home. Suggested fix: add a requirement for this situation, and ask what the system shall do. Do not choose the response.
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Check one scenario
|
|
237
|
+
|
|
238
|
+
`examples/order.ears.md` is a single rule: when an order is submitted and inventory is available, the order is fulfilled. A scenario is a JSON object with one value for every declared name.
|
|
239
|
+
|
|
240
|
+
<!-- verify stdout 1
|
|
241
|
+
build/plan-verify
|
|
242
|
+
check
|
|
243
|
+
examples/order.ears.md
|
|
244
|
+
--input
|
|
245
|
+
examples/order-fail.json
|
|
246
|
+
-->
|
|
247
|
+
```text
|
|
248
|
+
spec: examples/order.ears.md
|
|
249
|
+
scenario: examples/order-fail.json
|
|
250
|
+
violations: 1
|
|
251
|
+
|
|
252
|
+
R1 line 7
|
|
253
|
+
WHEN an order is submitted AND inventory is available, THE Order System SHALL fulfill the order.
|
|
254
|
+
An order was submitted and inventory was available, but the order was not fulfilled.
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
<!-- verify stdout 0
|
|
258
|
+
build/plan-verify
|
|
259
|
+
check
|
|
260
|
+
examples/order.ears.md
|
|
261
|
+
--input
|
|
262
|
+
examples/order-pass.json
|
|
263
|
+
-->
|
|
264
|
+
```text
|
|
265
|
+
spec: examples/order.ears.md
|
|
266
|
+
scenario: examples/order-pass.json
|
|
267
|
+
violations: 0
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`check` answers a different question from `lint`. The passing scenario fits R1. Lint still finds a hole, because nothing says what fulfillment should be when the order was submitted and the inventory is not available:
|
|
271
|
+
|
|
272
|
+
<!-- verify stdout 1
|
|
273
|
+
build/plan-verify
|
|
274
|
+
lint
|
|
275
|
+
examples/order.ears.md
|
|
276
|
+
-->
|
|
277
|
+
```text
|
|
278
|
+
spec: examples/order.ears.md
|
|
279
|
+
conflicts: 0
|
|
280
|
+
gaps: 1
|
|
281
|
+
misuse: 0
|
|
282
|
+
wording: 0
|
|
283
|
+
redundant: 0
|
|
284
|
+
duplicate: 0
|
|
285
|
+
vacuous: 0
|
|
286
|
+
unused: 0
|
|
287
|
+
|
|
288
|
+
gap order_fulfilled
|
|
289
|
+
Nothing says whether the order was fulfilled when an order was submitted and inventory was not available. Suggested fix: add a requirement for this situation, and ask what the system shall do. Do not choose the response.
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
`examples/order-covered.ears.md` gives every situation a response. Lint is quiet:
|
|
293
|
+
|
|
294
|
+
<!-- verify stdout 0
|
|
295
|
+
build/plan-verify
|
|
296
|
+
lint
|
|
297
|
+
examples/order-covered.ears.md
|
|
298
|
+
-->
|
|
299
|
+
```text
|
|
300
|
+
spec: examples/order-covered.ears.md
|
|
301
|
+
conflicts: 0
|
|
302
|
+
gaps: 0
|
|
303
|
+
misuse: 0
|
|
304
|
+
wording: 0
|
|
305
|
+
redundant: 0
|
|
306
|
+
duplicate: 0
|
|
307
|
+
vacuous: 0
|
|
308
|
+
unused: 0
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## See the logic
|
|
312
|
+
|
|
313
|
+
`emit-smt` prints each requirement as one named implication. The comment above the assertion points back at the line and the sentence.
|
|
314
|
+
|
|
315
|
+
<!-- verify stdout 0
|
|
316
|
+
build/plan-verify
|
|
317
|
+
emit-smt
|
|
318
|
+
examples/order.ears.md
|
|
319
|
+
-->
|
|
320
|
+
```text
|
|
321
|
+
; spec: examples/order.ears.md
|
|
322
|
+
(set-logic ALL)
|
|
323
|
+
; event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
|
|
324
|
+
(declare-fun order_submitted () Bool)
|
|
325
|
+
; state inventory_available: Bool "Enough stock is on hand to fill the order." phrase "inventory is available"
|
|
326
|
+
(declare-fun inventory_available () Bool)
|
|
327
|
+
; output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
|
|
328
|
+
(declare-fun order_fulfilled () Bool)
|
|
329
|
+
; R1_L7 line 7: WHEN an order is submitted AND inventory is available, THE Order System SHALL fulfill the order.
|
|
330
|
+
(assert (! (=> (and order_submitted inventory_available) order_fulfilled) :named R1_L7))
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Commands
|
|
334
|
+
|
|
335
|
+
| Command | Question it answers | Status |
|
|
336
|
+
| --- | --- | --- |
|
|
337
|
+
| `check [--json] <spec> --input <scenario>` | Does this scenario fit the requirements? | Implemented |
|
|
338
|
+
| `lint [--json] <spec>` | Do any requirements clash, leave a hole, never trigger, leave schema unused, repeat each other, or use a word a test cannot pin down? | Implemented |
|
|
339
|
+
| `report <spec>` | What are the logical problems, in plain language? | Implemented |
|
|
340
|
+
| `scenarios [--json] <spec>` | What accepted and rejected examples show each requirement's trigger? | Implemented |
|
|
341
|
+
| `emit-smt [--json] <spec>` | What is the SMT-LIB for these requirements? | Implemented |
|
|
342
|
+
| `policy [--json] <spec> [--property <file>]` | Which scenarios do the requirements allow and forbid? Does any allowed scenario break a property? | Implemented |
|
|
343
|
+
| `help` | What commands are available? | Implemented |
|
|
344
|
+
|
|
345
|
+
<!-- verify stdout 0
|
|
346
|
+
build/plan-verify
|
|
347
|
+
help
|
|
348
|
+
-->
|
|
349
|
+
```text
|
|
350
|
+
usage:
|
|
351
|
+
plan-verify check [--json] <spec> --input <scenario>
|
|
352
|
+
plan-verify emit-smt [--json] <spec>
|
|
353
|
+
plan-verify lint [--json] <spec>
|
|
354
|
+
plan-verify report <spec>
|
|
355
|
+
plan-verify scenarios [--json] <spec>
|
|
356
|
+
plan-verify policy [--json] <spec> [--property <file>]
|
|
357
|
+
plan-verify help
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
With no property, `policy` prints one scenario the requirements allow and one they forbid. Both are the first assignment the search finds, false before true. A requirement whose condition is false counts as satisfied, so the allowed scenario is not a recommendation.
|
|
361
|
+
|
|
362
|
+
<!-- verify stdout 0
|
|
363
|
+
build/plan-verify
|
|
364
|
+
policy
|
|
365
|
+
examples/order.ears.md
|
|
366
|
+
-->
|
|
367
|
+
```text
|
|
368
|
+
spec: examples/order.ears.md
|
|
369
|
+
accepted:
|
|
370
|
+
{
|
|
371
|
+
"order_submitted": false,
|
|
372
|
+
"inventory_available": false,
|
|
373
|
+
"order_fulfilled": false
|
|
374
|
+
}
|
|
375
|
+
This scenario satisfies every requirement.
|
|
376
|
+
rejected:
|
|
377
|
+
{
|
|
378
|
+
"order_submitted": true,
|
|
379
|
+
"inventory_available": true,
|
|
380
|
+
"order_fulfilled": false
|
|
381
|
+
}
|
|
382
|
+
This scenario violates a requirement.
|
|
383
|
+
|
|
384
|
+
R1 line 7
|
|
385
|
+
WHEN an order is submitted AND inventory is available, THE Order System SHALL fulfill the order.
|
|
386
|
+
An order was submitted and inventory was available, but the order was not fulfilled.
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Exit status is `0` when the command is clean, `1` when a scenario violates a requirement, lint finds something, or a property has a counterexample, and `2` when the spec, the scenario, the property, or the arguments are wrong. `--json` is for the agent. The text report is for the product manager.
|
|
390
|
+
|
|
391
|
+
## JSON
|
|
392
|
+
|
|
393
|
+
`--json` prints one object on stdout and leaves stderr empty. The keys below do not change. Wrong arguments, and a file that cannot be read or parsed, are exit 2:
|
|
394
|
+
|
|
395
|
+
<!-- verify stdout 2
|
|
396
|
+
build/plan-verify
|
|
397
|
+
emit-smt
|
|
398
|
+
--json
|
|
399
|
+
-->
|
|
400
|
+
```text
|
|
401
|
+
{
|
|
402
|
+
"ok": false,
|
|
403
|
+
"error": "usage: plan-verify emit-smt [--json] <spec>"
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
`check` is `ok`, `spec`, `scenario`, and `violations`. Each violation is `id`, `line`, `sentence`, and `because`. `ok` is true when `violations` is empty.
|
|
408
|
+
|
|
409
|
+
<!-- verify stdout 1
|
|
410
|
+
build/plan-verify
|
|
411
|
+
check
|
|
412
|
+
--json
|
|
413
|
+
examples/order.ears.md
|
|
414
|
+
--input
|
|
415
|
+
examples/order-fail.json
|
|
416
|
+
-->
|
|
417
|
+
```text
|
|
418
|
+
{
|
|
419
|
+
"ok": false,
|
|
420
|
+
"spec": "examples/order.ears.md",
|
|
421
|
+
"scenario": "examples/order-fail.json",
|
|
422
|
+
"violations": [
|
|
423
|
+
{
|
|
424
|
+
"id": "R1",
|
|
425
|
+
"line": 7,
|
|
426
|
+
"sentence": "WHEN an order is submitted AND inventory is available, THE Order System SHALL fulfill the order.",
|
|
427
|
+
"because": "An order was submitted and inventory was available, but the order was not fulfilled."
|
|
428
|
+
}
|
|
429
|
+
]
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
`lint` is `ok`, `spec`, `conflicts`, `gaps`, `misuse`, `wording`, `redundant`, `duplicate`, `vacuous`, and `unused`. A conflict is `ids`, `minimal`, `everySituation`, `summary`, `clash`, `requirements` (`id`, `line`, `sentence`), `situation` and `example` (`name`, `value`), `requires` (`id`, `text`), `keeps`, `breaks`, and `fix`. A gap is `output`, `situation`, and `text`. Misuse is `id`, `line`, `symbols`, `sentence`, and `text`. Wording is `line`, `word`, `kind`, `text`, and `context`. `kind` is `vague`, `solution`, `form`, or `negation`. A redundant requirement is `id`, `line`, `sentence`, `text`, and `fix`. An exact duplicate is `id`, `line`, `sameAs`, `sentence`, `text`, and `fix`. A vacuous requirement has the same fields as a redundant requirement. An unused entry is `name`, `kind` (`state`, `input`, `output`, or `variant`), `text`, and `fix`.
|
|
434
|
+
|
|
435
|
+
<!-- verify stdout 1
|
|
436
|
+
build/plan-verify
|
|
437
|
+
lint
|
|
438
|
+
--json
|
|
439
|
+
examples/order-conflict.ears.md
|
|
440
|
+
-->
|
|
441
|
+
```text
|
|
442
|
+
{
|
|
443
|
+
"ok": false,
|
|
444
|
+
"spec": "examples/order-conflict.ears.md",
|
|
445
|
+
"conflicts": [
|
|
446
|
+
{
|
|
447
|
+
"ids": [
|
|
448
|
+
"R2",
|
|
449
|
+
"R3"
|
|
450
|
+
],
|
|
451
|
+
"minimal": true,
|
|
452
|
+
"everySituation": false,
|
|
453
|
+
"summary": "R2 and R3 cannot both hold.",
|
|
454
|
+
"clash": "They clash when an order was submitted.",
|
|
455
|
+
"requirements": [
|
|
456
|
+
{
|
|
457
|
+
"id": "R2",
|
|
458
|
+
"line": 7,
|
|
459
|
+
"sentence": "When an order is submitted, the Order System shall fulfill the order."
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"id": "R3",
|
|
463
|
+
"line": 8,
|
|
464
|
+
"sentence": "When an order is submitted, the Order System shall not fulfill the order."
|
|
465
|
+
}
|
|
466
|
+
],
|
|
467
|
+
"situation": [
|
|
468
|
+
{
|
|
469
|
+
"name": "order_submitted",
|
|
470
|
+
"value": "true"
|
|
471
|
+
}
|
|
472
|
+
],
|
|
473
|
+
"requires": [
|
|
474
|
+
{
|
|
475
|
+
"id": "R2",
|
|
476
|
+
"text": "that the order was fulfilled"
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"id": "R3",
|
|
480
|
+
"text": "that the order was not fulfilled"
|
|
481
|
+
}
|
|
482
|
+
],
|
|
483
|
+
"example": [
|
|
484
|
+
{
|
|
485
|
+
"name": "order_submitted",
|
|
486
|
+
"value": "true"
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"name": "order_fulfilled",
|
|
490
|
+
"value": "true"
|
|
491
|
+
}
|
|
492
|
+
],
|
|
493
|
+
"keeps": [
|
|
494
|
+
"R2"
|
|
495
|
+
],
|
|
496
|
+
"breaks": "R3",
|
|
497
|
+
"fix": "Suggested fix: ask which requirement should win. To keep R2, remove R3. To keep R3, remove R2.",
|
|
498
|
+
"repairs": [
|
|
499
|
+
{
|
|
500
|
+
"kind": "drop",
|
|
501
|
+
"id": "R2",
|
|
502
|
+
"text": "One repair is to drop R2 and keep R3 as written.",
|
|
503
|
+
"sentence": "When an order is submitted, the Order System shall not fulfill the order."
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
"kind": "drop",
|
|
507
|
+
"id": "R3",
|
|
508
|
+
"text": "One repair is to drop R3 and keep R2 as written.",
|
|
509
|
+
"sentence": "When an order is submitted, the Order System shall fulfill the order."
|
|
510
|
+
}
|
|
511
|
+
]
|
|
512
|
+
}
|
|
513
|
+
],
|
|
514
|
+
"gaps": [],
|
|
515
|
+
"misuse": [],
|
|
516
|
+
"wording": [],
|
|
517
|
+
"redundant": [],
|
|
518
|
+
"duplicate": [],
|
|
519
|
+
"vacuous": [],
|
|
520
|
+
"unused": []
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
`policy` is `ok`, `spec`, `property`, `holds`, `note`, `acceptedFound`, `accepted`, `rejectedFound`, `rejected`, `counterexampleFound`, `counterexample`, and `breaks`. A scenario is a list of `name` and `value`. The list is empty when the matching `Found` flag is false. A break is `id`, `line`, `sentence`, and `because`.
|
|
525
|
+
|
|
526
|
+
<!-- verify stdout 1
|
|
527
|
+
build/plan-verify
|
|
528
|
+
policy
|
|
529
|
+
--json
|
|
530
|
+
examples/order.ears.md
|
|
531
|
+
--property
|
|
532
|
+
examples/order-always.ears.md
|
|
533
|
+
-->
|
|
534
|
+
```text
|
|
535
|
+
{
|
|
536
|
+
"ok": false,
|
|
537
|
+
"spec": "examples/order.ears.md",
|
|
538
|
+
"property": "examples/order-always.ears.md",
|
|
539
|
+
"holds": false,
|
|
540
|
+
"note": "",
|
|
541
|
+
"acceptedFound": false,
|
|
542
|
+
"accepted": [],
|
|
543
|
+
"rejectedFound": false,
|
|
544
|
+
"rejected": [],
|
|
545
|
+
"counterexampleFound": true,
|
|
546
|
+
"counterexample": [
|
|
547
|
+
{
|
|
548
|
+
"name": "order_submitted",
|
|
549
|
+
"value": "false"
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
"name": "inventory_available",
|
|
553
|
+
"value": "false"
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
"name": "order_fulfilled",
|
|
557
|
+
"value": "false"
|
|
558
|
+
}
|
|
559
|
+
],
|
|
560
|
+
"breaks": [
|
|
561
|
+
{
|
|
562
|
+
"id": "P1",
|
|
563
|
+
"line": 3,
|
|
564
|
+
"sentence": "The Order System shall fulfill the order.",
|
|
565
|
+
"because": "The order was not fulfilled."
|
|
566
|
+
}
|
|
567
|
+
]
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
`emit-smt` is `ok`, `spec`, and `smt`. `smt` is the same SMT-LIB text as the plain command, with a newline between lines and none after the last line.
|
|
572
|
+
|
|
573
|
+
<!-- verify stdout 0
|
|
574
|
+
build/plan-verify
|
|
575
|
+
emit-smt
|
|
576
|
+
--json
|
|
577
|
+
examples/order.ears.md
|
|
578
|
+
-->
|
|
579
|
+
```text
|
|
580
|
+
{
|
|
581
|
+
"ok": true,
|
|
582
|
+
"spec": "examples/order.ears.md",
|
|
583
|
+
"smt": "; spec: examples/order.ears.md\n(set-logic ALL)\n; event order_submitted: Bool \"The customer submits an order.\" phrase \"an order is submitted\"\n(declare-fun order_submitted () Bool)\n; state inventory_available: Bool \"Enough stock is on hand to fill the order.\" phrase \"inventory is available\"\n(declare-fun inventory_available () Bool)\n; output order_fulfilled: Bool \"The order is fulfilled.\" phrase \"fulfill the order\" outcome \"the order was fulfilled\" / \"the order was not fulfilled\"\n(declare-fun order_fulfilled () Bool)\n; R1_L7 line 7: WHEN an order is submitted AND inventory is available, THE Order System SHALL fulfill the order.\n(assert (! (=> (and order_submitted inventory_available) order_fulfilled) :named R1_L7))"
|
|
584
|
+
}
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
## What EARS is
|
|
588
|
+
|
|
589
|
+
An EARS line is one testable response, with the condition in front of it:
|
|
590
|
+
|
|
591
|
+
```text
|
|
592
|
+
While <state>, when <trigger>, if <condition>, where <feature>, then the <system> shall <response>.
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Each piece is optional. `then` belongs in the line exactly when `if` is there. The names come from declarations at the top of the spec, so the same idea cannot be spelled two ways:
|
|
596
|
+
|
|
597
|
+
```text
|
|
598
|
+
event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
|
|
599
|
+
state inventory_available: Bool "Enough stock is on hand to fill the order." phrase "inventory is available"
|
|
600
|
+
input quantity: Int 1..10 "How many units the customer asked for." phrase "the quantity"
|
|
601
|
+
output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
|
|
602
|
+
unwanted event payment_declined: Bool "The payment was declined." phrase "payment fails"
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
The phrase is the only wording a requirement may use for that name. `not fulfill the order` is the phrase with `not` in front. When the phrase contains `is`, write `the customer is not cancelling`. `the quantity is at most 10` still parses, and lint flags `shall the quantity is 10`. Bind the response with `as`, for example `as "accept {op} {value} units"`, and write `shall accept at most 10 units`. `says "{value} units were accepted"` is the sentence a report uses. A Bool can add `action "start the subscription"` next to `phrase "the subscription is started"`. Matching is exact and case-insensitive: the words have to be the phrase you declared, and the longest phrase wins. A sentence that says `an order was submitted` is not close enough to count. The error names the line and the nearest phrases.
|
|
606
|
+
|
|
607
|
+
A Bool can also say how a report states the result, after the phrase: `outcome "the order was fulfilled" / "the order was not fulfilled"`. One string is enough. The false form inserts `not` after the first `was`, `were`, or `is`, or prefixes `not` when none of those words is there. Two strings separated by `/` are used as written. `outcome` is only for Bool, and `not` is allowed in that wording. When it is left off, a phrase that contains `is` is reported with `was` or `was not`. The violation line for the order above is `An order was submitted and inventory was available, but the order was not fulfilled.`
|
|
608
|
+
|
|
609
|
+
<!-- verify stderr 2
|
|
610
|
+
build/plan-verify
|
|
611
|
+
lint
|
|
612
|
+
test/fixtures/unknown-phrase.ears.md
|
|
613
|
+
-->
|
|
614
|
+
```text
|
|
615
|
+
error: test/fixtures/unknown-phrase.ears.md:5: unknown phrase "an order was submitted". Closest: "an order is submitted", "inventory is available", "fulfill the order".
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Scenarios and the SMT-LIB still use the declared names (`order_submitted`, `quantity`). The sentences you read, and the explanations under them, use the phrases and the outcome wording.
|
|
619
|
+
|
|
620
|
+
Events and states are booleans. Inputs and outputs are booleans, integers with an inclusive range, or enums. An unwanted event, such as a payment being declined, is written with `if` ... `then`. Writing it with `when` is a lint finding, because `when` reads like the normal trigger for the response.
|
|
621
|
+
|
|
622
|
+
A line with no condition is ubiquitous: the response is always required. `While` is a state that holds. `When` is the trigger. `If` ... `then` is the unwanted or exceptional case. `Where` is a feature or a variant, such as a region.
|
|
623
|
+
|
|
624
|
+
## How a contradiction is found
|
|
625
|
+
|
|
626
|
+
Each requirement becomes an implication: when its condition is true, its response must be true. `check` plugs in the scenario you wrote. `lint` asks whether some situation has no response that satisfies the set.
|
|
627
|
+
|
|
628
|
+
Booleans are decided by DPLL over the clauses of those implications. Integers, and enum values numbered in the order you declared them, are linear arithmetic: bounds tighten until a comparison is forced, and the search branches where a bound or a comparison can change. A model is the lexicographically least assignment. The search tries `false` before `true`, enum values in declaration order, and integers from the low end of the range to the high end. The example scenario is that first assignment. Run it twice and you get the same bytes.
|
|
629
|
+
|
|
630
|
+
A set such as `{R2, R3}` is reported when some situation has no allowed response, even if other situations are fine. The set is minimal: drop any one requirement and the rest can be satisfied. With 12 requirements or fewer, every such set is listed, smallest first. With more than 12, each set is shrunk by deleting requirements from a failing situation, and situations are tried in that same order. Two sets that share a requirement and fail in exactly the same situations can collapse to one of them. If a driver domain is wider than 64 values, or the driver combinations exceed 8192, each set is removed before the next search, so a later set that needs a requirement from an earlier one is not reported.
|
|
631
|
+
|
|
632
|
+
An integer has to declare `min..max`, inside `-1000000..1000000`. The procedure decides the range. It does not list every value, so a wide range is still checked:
|
|
633
|
+
|
|
634
|
+
<!-- verify stdout 0
|
|
635
|
+
build/plan-verify
|
|
636
|
+
lint
|
|
637
|
+
examples/order-wide.ears.md
|
|
638
|
+
-->
|
|
639
|
+
```text
|
|
640
|
+
spec: examples/order-wide.ears.md
|
|
641
|
+
conflicts: 0
|
|
642
|
+
gaps: 0
|
|
643
|
+
misuse: 0
|
|
644
|
+
wording: 0
|
|
645
|
+
redundant: 0
|
|
646
|
+
duplicate: 0
|
|
647
|
+
vacuous: 0
|
|
648
|
+
unused: 0
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
A comparison that ties two wide integers together is still decided, by splitting the range. A boolean combination of those comparisons can take longer than a single bound.
|
|
652
|
+
|
|
653
|
+
There are no strings in the logic. Descriptions and phrases stay next to the names. Vague words (`as needed`, `quickly`, `appropriate`, `user-friendly`, `fast`, `slow`, `large`, `small`, `easy`, `robust`, `flexible`, `efficient`) and solution words (`database`, `api`, `algorithm`, `microservice`, `server`, `class`, `function`) are caught in descriptions and in requirement sentences. A word that sits inside a declared phrase or action is already bound to a schema item, so that use is not a wording finding.
|
|
654
|
+
|
|
655
|
+
Lint also reports an event no requirement handles, an error condition with no response, an unwanted event written with `when`, and vague words. An integer cap can clash with a larger accepted quantity, and an enum can be pinned to two variants at once. The fix still asks which requirement should win. A condition that can never be true, a state or input no requirement mentions, an enum variant no requirement mentions, and an output no response sets are reported on their own. An output no requirement mentions at all stays a gap. An event nobody handles stays a gap. A free input that some requirement already mentions is not a finding: the environment is allowed to choose it.
|
|
656
|
+
|
|
657
|
+
<!-- verify stdout 1
|
|
658
|
+
build/plan-verify
|
|
659
|
+
lint
|
|
660
|
+
examples/order-unhandled.ears.md
|
|
661
|
+
-->
|
|
662
|
+
```text
|
|
663
|
+
spec: examples/order-unhandled.ears.md
|
|
664
|
+
conflicts: 0
|
|
665
|
+
gaps: 1
|
|
666
|
+
misuse: 0
|
|
667
|
+
wording: 0
|
|
668
|
+
redundant: 0
|
|
669
|
+
duplicate: 0
|
|
670
|
+
vacuous: 0
|
|
671
|
+
unused: 0
|
|
672
|
+
|
|
673
|
+
gap order_cancelled
|
|
674
|
+
No requirement handles "an order is cancelled". Suggested fix: add a requirement that says what the system shall do, or remove the event.
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
<!-- verify stdout 1
|
|
678
|
+
build/plan-verify
|
|
679
|
+
lint
|
|
680
|
+
examples/order-unanswered.ears.md
|
|
681
|
+
-->
|
|
682
|
+
```text
|
|
683
|
+
spec: examples/order-unanswered.ears.md
|
|
684
|
+
conflicts: 0
|
|
685
|
+
gaps: 1
|
|
686
|
+
misuse: 0
|
|
687
|
+
wording: 0
|
|
688
|
+
redundant: 0
|
|
689
|
+
duplicate: 0
|
|
690
|
+
vacuous: 0
|
|
691
|
+
unused: 0
|
|
692
|
+
|
|
693
|
+
gap payment_declined
|
|
694
|
+
No requirement states a response when "payment fails". Suggested fix: add an if ... then requirement, and ask what the system shall do. Do not choose the response.
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
<!-- verify stdout 1
|
|
698
|
+
build/plan-verify
|
|
699
|
+
lint
|
|
700
|
+
examples/order-misuse.ears.md
|
|
701
|
+
-->
|
|
702
|
+
```text
|
|
703
|
+
spec: examples/order-misuse.ears.md
|
|
704
|
+
conflicts: 0
|
|
705
|
+
gaps: 0
|
|
706
|
+
misuse: 1
|
|
707
|
+
wording: 0
|
|
708
|
+
redundant: 1
|
|
709
|
+
duplicate: 0
|
|
710
|
+
vacuous: 0
|
|
711
|
+
unused: 0
|
|
712
|
+
|
|
713
|
+
misuse R4 line 6
|
|
714
|
+
"payment fails" is an unwanted event. Write if ... then, not when.
|
|
715
|
+
When payment fails, the Order System shall reject the order.
|
|
716
|
+
|
|
717
|
+
redundant R4 line 6
|
|
718
|
+
R4 adds nothing. Whenever the other requirements hold, R4 holds too.
|
|
719
|
+
When payment fails, the Order System shall reject the order.
|
|
720
|
+
Suggested fix: remove R4, or ask whether it was meant to say something the others do not already require. Do not delete it until the PM agrees.
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
<!-- verify stdout 1
|
|
724
|
+
build/plan-verify
|
|
725
|
+
lint
|
|
726
|
+
examples/order-vague.ears.md
|
|
727
|
+
-->
|
|
728
|
+
```text
|
|
729
|
+
spec: examples/order-vague.ears.md
|
|
730
|
+
conflicts: 0
|
|
731
|
+
gaps: 0
|
|
732
|
+
misuse: 0
|
|
733
|
+
wording: 3
|
|
734
|
+
redundant: 0
|
|
735
|
+
duplicate: 0
|
|
736
|
+
vacuous: 0
|
|
737
|
+
unused: 0
|
|
738
|
+
|
|
739
|
+
wording line 4
|
|
740
|
+
"fast" is too vague to test. Replace it with something a scenario can check.
|
|
741
|
+
output order_fulfilled: Bool "The checkout is fast, appropriate, and user-friendly." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
|
|
742
|
+
|
|
743
|
+
wording line 4
|
|
744
|
+
"appropriate" is too vague to test. Replace it with something a scenario can check.
|
|
745
|
+
output order_fulfilled: Bool "The checkout is fast, appropriate, and user-friendly." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
|
|
746
|
+
|
|
747
|
+
wording line 4
|
|
748
|
+
"user-friendly" is too vague to test. Replace it with something a scenario can check.
|
|
749
|
+
output order_fulfilled: Bool "The checkout is fast, appropriate, and user-friendly." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
## A word that is not bound
|
|
753
|
+
|
|
754
|
+
`examples/order-unmeasured.ears.md` says "as needed" in a description. That phrase is not a schema item. The requirements also say "respond quickly", and that phrase is declared, so "quickly" is bound and is not a finding.
|
|
755
|
+
|
|
756
|
+
<!-- verify stdout 1
|
|
757
|
+
build/plan-verify
|
|
758
|
+
lint
|
|
759
|
+
examples/order-unmeasured.ears.md
|
|
760
|
+
-->
|
|
761
|
+
```text
|
|
762
|
+
spec: examples/order-unmeasured.ears.md
|
|
763
|
+
conflicts: 0
|
|
764
|
+
gaps: 0
|
|
765
|
+
misuse: 0
|
|
766
|
+
wording: 1
|
|
767
|
+
redundant: 0
|
|
768
|
+
duplicate: 0
|
|
769
|
+
vacuous: 0
|
|
770
|
+
unused: 0
|
|
771
|
+
|
|
772
|
+
wording line 4
|
|
773
|
+
"as needed" is too vague to test. Replace it with something a scenario can check.
|
|
774
|
+
output order_fulfilled: Bool "Handle it as needed." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
<!-- verify stdout 1
|
|
778
|
+
build/plan-verify
|
|
779
|
+
lint
|
|
780
|
+
examples/order-quantity.ears.md
|
|
781
|
+
-->
|
|
782
|
+
```text
|
|
783
|
+
spec: examples/order-quantity.ears.md
|
|
784
|
+
conflicts: 1
|
|
785
|
+
gaps: 0
|
|
786
|
+
misuse: 0
|
|
787
|
+
wording: 0
|
|
788
|
+
redundant: 0
|
|
789
|
+
duplicate: 0
|
|
790
|
+
vacuous: 0
|
|
791
|
+
unused: 0
|
|
792
|
+
|
|
793
|
+
conflict {R4, R5}
|
|
794
|
+
R4 and R5 cannot both hold.
|
|
795
|
+
|
|
796
|
+
R4 line 7
|
|
797
|
+
When an order is submitted, the Order System shall accept at most 10 units.
|
|
798
|
+
|
|
799
|
+
R5 line 8
|
|
800
|
+
When an order is submitted, the Order System shall accept 12 units.
|
|
801
|
+
|
|
802
|
+
They clash when an order was submitted.
|
|
803
|
+
R4 requires that at most 10 units were accepted.
|
|
804
|
+
R5 requires that 12 units were accepted.
|
|
805
|
+
|
|
806
|
+
Example scenario:
|
|
807
|
+
{
|
|
808
|
+
"order_submitted": true,
|
|
809
|
+
"quantity": 0
|
|
810
|
+
}
|
|
811
|
+
This scenario keeps R4 and breaks R5.
|
|
812
|
+
|
|
813
|
+
Suggested fix: ask which requirement should win. To keep R4, remove R5. To keep R5, remove R4.
|
|
814
|
+
|
|
815
|
+
Candidate repairs follow. This report does not choose one.
|
|
816
|
+
|
|
817
|
+
repair drop R4
|
|
818
|
+
One repair is to drop R4 and keep R5 as written.
|
|
819
|
+
When an order is submitted, the Order System shall accept 12 units.
|
|
820
|
+
|
|
821
|
+
repair drop R5
|
|
822
|
+
One repair is to drop R5 and keep R4 as written.
|
|
823
|
+
When an order is submitted, the Order System shall accept at most 10 units.
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
R4 says a submitted order accepts at most 10 units. R5 says that same order accepts exactly 12. The example keeps R4 with a quantity of 0 only because that is the first value that satisfies R4. It is a witness, and it is not a recommendation.
|
|
827
|
+
|
|
828
|
+
`examples/order-status.ears.md` requires the status to be shipped and cancelled in every situation. `placed` is declared on the enum and no requirement mentions it.
|
|
829
|
+
|
|
830
|
+
<!-- verify stdout 1
|
|
831
|
+
build/plan-verify
|
|
832
|
+
lint
|
|
833
|
+
examples/order-status.ears.md
|
|
834
|
+
-->
|
|
835
|
+
```text
|
|
836
|
+
spec: examples/order-status.ears.md
|
|
837
|
+
conflicts: 1
|
|
838
|
+
gaps: 0
|
|
839
|
+
misuse: 0
|
|
840
|
+
wording: 0
|
|
841
|
+
redundant: 0
|
|
842
|
+
duplicate: 0
|
|
843
|
+
vacuous: 0
|
|
844
|
+
unused: 1
|
|
845
|
+
|
|
846
|
+
conflict {R6, R7}
|
|
847
|
+
R6 and R7 cannot both hold.
|
|
848
|
+
|
|
849
|
+
R6 line 6
|
|
850
|
+
The Order System shall mark the order shipped.
|
|
851
|
+
|
|
852
|
+
R7 line 7
|
|
853
|
+
The Order System shall mark the order cancelled.
|
|
854
|
+
|
|
855
|
+
They clash in every situation.
|
|
856
|
+
R6 requires that the order was shipped.
|
|
857
|
+
R7 requires that the order was cancelled.
|
|
858
|
+
|
|
859
|
+
Example scenario:
|
|
860
|
+
{
|
|
861
|
+
"status": "shipped"
|
|
862
|
+
}
|
|
863
|
+
This scenario keeps R6 and breaks R7.
|
|
864
|
+
|
|
865
|
+
Suggested fix: ask which requirement should win. To keep R6, remove R7. To keep R7, remove R6.
|
|
866
|
+
|
|
867
|
+
Candidate repairs follow. This report does not choose one.
|
|
868
|
+
|
|
869
|
+
repair drop R6
|
|
870
|
+
One repair is to drop R6 and keep R7 as written.
|
|
871
|
+
The Order System shall mark the order cancelled.
|
|
872
|
+
|
|
873
|
+
repair drop R7
|
|
874
|
+
One repair is to drop R7 and keep R6 as written.
|
|
875
|
+
The Order System shall mark the order shipped.
|
|
876
|
+
|
|
877
|
+
unused Status.placed
|
|
878
|
+
Status.placed is never mentioned.
|
|
879
|
+
Suggested fix: use placed in a requirement, or remove it from Status.
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
## From a plan to the problems
|
|
883
|
+
|
|
884
|
+
[examples/plan-to-problems/README.md](examples/plan-to-problems/README.md) walks the whole path. A plain product plan goes in. A plain list of logical problems comes out. The live step that drafts the spec calls a model. The report step does not. The offline test uses the spec already in the folder and does not need a key.
|
|
885
|
+
|
|
886
|
+
## Where this comes from
|
|
887
|
+
|
|
888
|
+
The shape of the analysis follows Kiro's requirements analysis: separate behavior from design, and look for what the requirements refuse to allow.
|
|
889
|
+
|
|
890
|
+
- [Deep spec analysis](https://kiro.dev/blog/deep-spec-analysis/)
|
|
891
|
+
- [Analyze requirements](https://kiro.dev/docs/specs/analyze-requirements/)
|
|
892
|
+
|
|
893
|
+
## For the agent in the conversation
|
|
894
|
+
|
|
895
|
+
The skill at [`skills/plan-verify/SKILL.md`](skills/plan-verify/SKILL.md) tells an agent how to sit with a product manager. It lists the grammar the parser accepts, shows real `check`, `lint`, `report`, `scenarios`, `emit-smt`, `policy`, and `help` output, including the sentences a product manager would read, and says to ask which requirement should win.
|
|
896
|
+
|
|
897
|
+
## Questions
|
|
898
|
+
|
|
899
|
+
**Why our own solver?** The decision procedure is in this repository: DPLL for the boolean structure, and bounds for linear integer arithmetic. There is no Z3, cvc5, or yices to install, and the Node run and the native binary are tested to print the same bytes.
|
|
900
|
+
|
|
901
|
+
**Why ScriptC?** One TypeScript program is the product. ScriptC 0.4.1 compiles that program to a native binary, and `scriptc coverage` stays fully static. Node 24 runs the same files for day-to-day edits.
|
|
902
|
+
|
|
903
|
+
**Why do integers need a range?** The arithmetic is bounded. A declared range, inside `-1000000..1000000`, keeps every integer finite. The solver does not walk that range one value at a time.
|
|
904
|
+
|
|
905
|
+
**Will it pick a winner?** It will show who clashes, the situation, and an example that breaks one requirement in the set. The fix says to ask which requirement should win.
|
|
906
|
+
|
|
907
|
+
## Policy
|
|
908
|
+
|
|
909
|
+
A property is a separate file of requirement lines. It uses the spec's names and phrases, and it does not declare symbols. `P1` is the usual id.
|
|
910
|
+
|
|
911
|
+
`examples/order-always.ears.md` hopes the order is fulfilled in every scenario. The requirements do not say that. The counterexample is a scenario they allow where the order is not fulfilled:
|
|
912
|
+
|
|
913
|
+
<!-- verify stdout 1
|
|
914
|
+
build/plan-verify
|
|
915
|
+
policy
|
|
916
|
+
examples/order.ears.md
|
|
917
|
+
--property
|
|
918
|
+
examples/order-always.ears.md
|
|
919
|
+
-->
|
|
920
|
+
```text
|
|
921
|
+
spec: examples/order.ears.md
|
|
922
|
+
property: examples/order-always.ears.md
|
|
923
|
+
counterexample:
|
|
924
|
+
{
|
|
925
|
+
"order_submitted": false,
|
|
926
|
+
"inventory_available": false,
|
|
927
|
+
"order_fulfilled": false
|
|
928
|
+
}
|
|
929
|
+
This scenario satisfies every requirement and violates the property.
|
|
930
|
+
|
|
931
|
+
P1 line 3
|
|
932
|
+
The Order System shall fulfill the order.
|
|
933
|
+
The order was not fulfilled.
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
`examples/order-same.ears.md` repeats R1. Nothing the requirements allow can break it:
|
|
937
|
+
|
|
938
|
+
<!-- verify stdout 0
|
|
939
|
+
build/plan-verify
|
|
940
|
+
policy
|
|
941
|
+
examples/order.ears.md
|
|
942
|
+
--property
|
|
943
|
+
examples/order-same.ears.md
|
|
944
|
+
-->
|
|
945
|
+
```text
|
|
946
|
+
spec: examples/order.ears.md
|
|
947
|
+
property: examples/order-same.ears.md
|
|
948
|
+
No scenario satisfies every requirement and violates the property.
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
When the requirements themselves cannot all hold, there is no scenario to test the property against. `examples/order-status.ears.md` is that spec: the status would have to be shipped and cancelled.
|
|
952
|
+
|
|
953
|
+
<!-- verify stdout 0
|
|
954
|
+
build/plan-verify
|
|
955
|
+
policy
|
|
956
|
+
examples/order-status.ears.md
|
|
957
|
+
--property
|
|
958
|
+
examples/status-placed.ears.md
|
|
959
|
+
-->
|
|
960
|
+
```text
|
|
961
|
+
spec: examples/order-status.ears.md
|
|
962
|
+
property: examples/status-placed.ears.md
|
|
963
|
+
No scenario satisfies every requirement, so none violates the property.
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
## Three requirements, no pair
|
|
967
|
+
|
|
968
|
+
`examples/order-chain.ears.md` is unsatisfiable only as a set. A submitted order must be accepted, an accepted order must be shipped, and a submitted order must not be shipped. Each pair can still hold. The example keeps the first two and breaks the third. It is a witness, and it is not a recommendation. Ask which requirement should change.
|
|
969
|
+
|
|
970
|
+
<!-- verify stdout 1
|
|
971
|
+
build/plan-verify
|
|
972
|
+
lint
|
|
973
|
+
examples/order-chain.ears.md
|
|
974
|
+
-->
|
|
975
|
+
```text
|
|
976
|
+
spec: examples/order-chain.ears.md
|
|
977
|
+
conflicts: 1
|
|
978
|
+
gaps: 0
|
|
979
|
+
misuse: 0
|
|
980
|
+
wording: 0
|
|
981
|
+
redundant: 0
|
|
982
|
+
duplicate: 0
|
|
983
|
+
vacuous: 0
|
|
984
|
+
unused: 0
|
|
985
|
+
|
|
986
|
+
conflict {R1, R2, R3}
|
|
987
|
+
R1, R2, and R3 cannot all hold.
|
|
988
|
+
|
|
989
|
+
R1 line 7
|
|
990
|
+
When an order is submitted, the Order System shall accept the order.
|
|
991
|
+
|
|
992
|
+
R2 line 8
|
|
993
|
+
When the order is accepted, the Order System shall ship the order.
|
|
994
|
+
|
|
995
|
+
R3 line 9
|
|
996
|
+
When an order is submitted, the Order System shall not ship the order.
|
|
997
|
+
|
|
998
|
+
They clash when an order was submitted.
|
|
999
|
+
R1 requires that the order was accepted.
|
|
1000
|
+
R3 requires that the order was not shipped.
|
|
1001
|
+
|
|
1002
|
+
Example scenario:
|
|
1003
|
+
{
|
|
1004
|
+
"order_submitted": true,
|
|
1005
|
+
"order_accepted": true,
|
|
1006
|
+
"order_shipped": true
|
|
1007
|
+
}
|
|
1008
|
+
This scenario keeps R1 and R2 and breaks R3.
|
|
1009
|
+
|
|
1010
|
+
Suggested fix: ask which requirement should win. Removing any one of R1, R2, and R3 leaves the rest consistent.
|
|
1011
|
+
|
|
1012
|
+
Candidate repairs follow. This report does not choose one.
|
|
1013
|
+
|
|
1014
|
+
repair drop R1
|
|
1015
|
+
One repair is to drop R1 and keep R2 and R3 as written.
|
|
1016
|
+
When the order is accepted, the Order System shall ship the order.
|
|
1017
|
+
When an order is submitted, the Order System shall not ship the order.
|
|
1018
|
+
|
|
1019
|
+
repair drop R2
|
|
1020
|
+
One repair is to drop R2 and keep R1 and R3 as written.
|
|
1021
|
+
When an order is submitted, the Order System shall accept the order.
|
|
1022
|
+
When an order is submitted, the Order System shall not ship the order.
|
|
1023
|
+
|
|
1024
|
+
repair drop R3
|
|
1025
|
+
One repair is to drop R3 and keep R1 and R2 as written.
|
|
1026
|
+
When an order is submitted, the Order System shall accept the order.
|
|
1027
|
+
When the order is accepted, the Order System shall ship the order.
|
|
1028
|
+
|
|
1029
|
+
repair strengthen R2
|
|
1030
|
+
One repair is to add "and an order is not submitted" to the condition of R2. R1 and R3 stay as written.
|
|
1031
|
+
When the order is accepted and an order is not submitted, the Order System shall ship the order.
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
## A requirement that adds nothing
|
|
1035
|
+
|
|
1036
|
+
`examples/order-redundant.ears.md` says a submitted order is fulfilled, and also that a submitted order with inventory is fulfilled. The second one is already required by the first. Lint names it and asks. It does not delete it. A conflict does not hide a repeat that sits outside that conflict. Lint checks redundancy on the requirements that are not part of any conflict.
|
|
1037
|
+
|
|
1038
|
+
<!-- verify stdout 1
|
|
1039
|
+
build/plan-verify
|
|
1040
|
+
lint
|
|
1041
|
+
examples/order-redundant.ears.md
|
|
1042
|
+
-->
|
|
1043
|
+
```text
|
|
1044
|
+
spec: examples/order-redundant.ears.md
|
|
1045
|
+
conflicts: 0
|
|
1046
|
+
gaps: 0
|
|
1047
|
+
misuse: 0
|
|
1048
|
+
wording: 0
|
|
1049
|
+
redundant: 1
|
|
1050
|
+
duplicate: 0
|
|
1051
|
+
vacuous: 0
|
|
1052
|
+
unused: 0
|
|
1053
|
+
|
|
1054
|
+
redundant R2 line 8
|
|
1055
|
+
R2 adds nothing. Whenever the other requirements hold, R2 holds too.
|
|
1056
|
+
When an order is submitted and inventory is available, the Order System shall fulfill the order.
|
|
1057
|
+
Suggested fix: remove R2, or ask whether it was meant to say something the others do not already require. Do not delete it until the PM agrees.
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
## A repeat next to a conflict
|
|
1061
|
+
|
|
1062
|
+
`examples/order-repeat.ears.md` conflicts on whether a submitted order is fulfilled. Separately, closing a submitted order already requires closing it when inventory is available. `R5` is not part of the conflict, so lint still names it.
|
|
1063
|
+
|
|
1064
|
+
<!-- verify stdout 1
|
|
1065
|
+
build/plan-verify
|
|
1066
|
+
lint
|
|
1067
|
+
examples/order-repeat.ears.md
|
|
1068
|
+
-->
|
|
1069
|
+
```text
|
|
1070
|
+
spec: examples/order-repeat.ears.md
|
|
1071
|
+
conflicts: 1
|
|
1072
|
+
gaps: 0
|
|
1073
|
+
misuse: 0
|
|
1074
|
+
wording: 0
|
|
1075
|
+
redundant: 1
|
|
1076
|
+
duplicate: 0
|
|
1077
|
+
vacuous: 0
|
|
1078
|
+
unused: 0
|
|
1079
|
+
|
|
1080
|
+
conflict {R1, R2}
|
|
1081
|
+
R1 and R2 cannot both hold.
|
|
1082
|
+
|
|
1083
|
+
R1 line 8
|
|
1084
|
+
When an order is submitted, the Order System shall fulfill the order.
|
|
1085
|
+
|
|
1086
|
+
R2 line 9
|
|
1087
|
+
When an order is submitted, the Order System shall not fulfill the order.
|
|
1088
|
+
|
|
1089
|
+
They clash when an order was submitted and inventory was not available.
|
|
1090
|
+
R1 requires that the order was fulfilled.
|
|
1091
|
+
R2 requires that the order was not fulfilled.
|
|
1092
|
+
|
|
1093
|
+
Example scenario:
|
|
1094
|
+
{
|
|
1095
|
+
"order_submitted": true,
|
|
1096
|
+
"inventory_available": false,
|
|
1097
|
+
"order_fulfilled": true,
|
|
1098
|
+
"order_closed": false
|
|
1099
|
+
}
|
|
1100
|
+
This scenario keeps R1 and breaks R2.
|
|
1101
|
+
|
|
1102
|
+
Suggested fix: ask which requirement should win. To keep R1, remove R2. To keep R2, remove R1.
|
|
1103
|
+
|
|
1104
|
+
Candidate repairs follow. This report does not choose one.
|
|
1105
|
+
|
|
1106
|
+
repair drop R1
|
|
1107
|
+
One repair is to drop R1 and keep R2 as written.
|
|
1108
|
+
When an order is submitted, the Order System shall not fulfill the order.
|
|
1109
|
+
|
|
1110
|
+
repair drop R2
|
|
1111
|
+
One repair is to drop R2 and keep R1 as written.
|
|
1112
|
+
When an order is submitted, the Order System shall fulfill the order.
|
|
1113
|
+
|
|
1114
|
+
redundant R5 line 12
|
|
1115
|
+
R5 adds nothing. Whenever the requirements outside a conflict hold, R5 holds too.
|
|
1116
|
+
When an order is submitted and inventory is available, the Order System shall close the order.
|
|
1117
|
+
Suggested fix: remove R5, or ask whether it was meant to say something the others do not already require. Do not delete it until the PM agrees.
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
## The same condition and the same response
|
|
1121
|
+
|
|
1122
|
+
`examples/order-duplicate.ears.md` states the fulfilled-order rule twice. That is a duplicate, not a redundant requirement: the two lines are the same condition and the same response. Lint names the later line and the earlier id. It does not call it redundant.
|
|
1123
|
+
|
|
1124
|
+
<!-- verify stdout 1
|
|
1125
|
+
build/plan-verify
|
|
1126
|
+
lint
|
|
1127
|
+
examples/order-duplicate.ears.md
|
|
1128
|
+
-->
|
|
1129
|
+
```text
|
|
1130
|
+
spec: examples/order-duplicate.ears.md
|
|
1131
|
+
conflicts: 0
|
|
1132
|
+
gaps: 0
|
|
1133
|
+
misuse: 0
|
|
1134
|
+
wording: 0
|
|
1135
|
+
redundant: 0
|
|
1136
|
+
duplicate: 1
|
|
1137
|
+
vacuous: 0
|
|
1138
|
+
unused: 0
|
|
1139
|
+
|
|
1140
|
+
duplicate R2 line 7
|
|
1141
|
+
R2 duplicates R1. The condition and the response are the same.
|
|
1142
|
+
When an order is submitted, the Order System shall fulfill the order.
|
|
1143
|
+
Suggested fix: remove R2, or ask whether it was meant to differ from R1. Do not delete it until the PM agrees.
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
<!-- verify stdout 1
|
|
1147
|
+
build/plan-verify
|
|
1148
|
+
report
|
|
1149
|
+
examples/order-duplicate.ears.md
|
|
1150
|
+
-->
|
|
1151
|
+
```text
|
|
1152
|
+
# Logical problems
|
|
1153
|
+
|
|
1154
|
+
Spec: examples/order-duplicate.ears.md
|
|
1155
|
+
|
|
1156
|
+
These are the logical problems in the requirements. For a contradiction, which requirement should win? This report does not choose.
|
|
1157
|
+
|
|
1158
|
+
## 1. R2 duplicates R1
|
|
1159
|
+
|
|
1160
|
+
R2 line 7
|
|
1161
|
+
"When an order is submitted, the Order System shall fulfill the order."
|
|
1162
|
+
|
|
1163
|
+
R2 duplicates R1. The condition and the response are the same.
|
|
1164
|
+
|
|
1165
|
+
R1 line 6
|
|
1166
|
+
"When an order is submitted, the Order System shall fulfill the order."
|
|
1167
|
+
|
|
1168
|
+
Suggested fix: remove R2, or ask whether it was meant to differ from R1. Do not delete it until the PM agrees.
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
## A trigger that starts with not
|
|
1172
|
+
|
|
1173
|
+
`examples/order-negation.ears.md` says `When not a member asks`. The trigger is a bare negation. Lint asks for a positive condition, such as `When a non-member asks`, or a positive event. `is not` stays the normal way to negate a phrase that contains `is`.
|
|
1174
|
+
|
|
1175
|
+
<!-- verify stdout 1
|
|
1176
|
+
build/plan-verify
|
|
1177
|
+
lint
|
|
1178
|
+
examples/order-negation.ears.md
|
|
1179
|
+
-->
|
|
1180
|
+
```text
|
|
1181
|
+
spec: examples/order-negation.ears.md
|
|
1182
|
+
conflicts: 0
|
|
1183
|
+
gaps: 0
|
|
1184
|
+
misuse: 0
|
|
1185
|
+
wording: 1
|
|
1186
|
+
redundant: 0
|
|
1187
|
+
duplicate: 0
|
|
1188
|
+
vacuous: 0
|
|
1189
|
+
unused: 0
|
|
1190
|
+
|
|
1191
|
+
wording line 6
|
|
1192
|
+
The trigger is a bare negation. Write a positive condition, such as "When a non-member asks", or declare a positive event.
|
|
1193
|
+
When not a member asks to take a copy home, the Desk shall not allow the loan.
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
## A condition that can never be true
|
|
1197
|
+
|
|
1198
|
+
`examples/order-vacuous.ears.md` has a condition that contradicts itself, and a quantity comparison outside the declared range. The response is never demanded. Lint names each one as vacuous. It does not also call that requirement redundant or a duplicate, because a condition that can never be true is implied by anything. The vacuous line says to rewrite the condition. It does not pick a response.
|
|
1199
|
+
|
|
1200
|
+
<!-- verify stdout 1
|
|
1201
|
+
build/plan-verify
|
|
1202
|
+
lint
|
|
1203
|
+
examples/order-vacuous.ears.md
|
|
1204
|
+
-->
|
|
1205
|
+
```text
|
|
1206
|
+
spec: examples/order-vacuous.ears.md
|
|
1207
|
+
conflicts: 0
|
|
1208
|
+
gaps: 0
|
|
1209
|
+
misuse: 0
|
|
1210
|
+
wording: 0
|
|
1211
|
+
redundant: 0
|
|
1212
|
+
duplicate: 0
|
|
1213
|
+
vacuous: 2
|
|
1214
|
+
unused: 0
|
|
1215
|
+
|
|
1216
|
+
vacuous R1 line 7
|
|
1217
|
+
R1 can never trigger. Its condition is impossible for every allowed value.
|
|
1218
|
+
When the cart is open and the cart is not open, the Checkout shall accept the order.
|
|
1219
|
+
Suggested fix: rewrite the condition in R1 so a real situation can make it true, or remove the requirement. Do not invent a response.
|
|
1220
|
+
|
|
1221
|
+
vacuous R2 line 8
|
|
1222
|
+
R2 can never trigger. Its condition is impossible for every allowed value.
|
|
1223
|
+
When the quantity is at least 9, the Checkout shall accept the order.
|
|
1224
|
+
Suggested fix: rewrite the condition in R2 so a real situation can make it true, or remove the requirement. Do not invent a response.
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
## Schema the requirements never use
|
|
1228
|
+
|
|
1229
|
+
`examples/order-unused.ears.md` declares a state and an input that no sentence mentions, an enum variant that no sentence mentions, and an output that requirements only read. An output that no requirement mentions at all is still a gap, not a second finding. An event that nobody handles stays a gap.
|
|
1230
|
+
|
|
1231
|
+
<!-- verify stdout 1
|
|
1232
|
+
build/plan-verify
|
|
1233
|
+
lint
|
|
1234
|
+
examples/order-unused.ears.md
|
|
1235
|
+
-->
|
|
1236
|
+
```text
|
|
1237
|
+
spec: examples/order-unused.ears.md
|
|
1238
|
+
conflicts: 0
|
|
1239
|
+
gaps: 0
|
|
1240
|
+
misuse: 0
|
|
1241
|
+
wording: 0
|
|
1242
|
+
redundant: 0
|
|
1243
|
+
duplicate: 0
|
|
1244
|
+
vacuous: 0
|
|
1245
|
+
unused: 4
|
|
1246
|
+
|
|
1247
|
+
unused loyalty
|
|
1248
|
+
loyalty ("the customer is loyal") is a state and no requirement mentions it.
|
|
1249
|
+
Suggested fix: use loyalty in a requirement, or remove it from the schema.
|
|
1250
|
+
|
|
1251
|
+
unused region
|
|
1252
|
+
region ("the region") is an input and no requirement mentions it.
|
|
1253
|
+
Suggested fix: use region in a requirement, or remove it from the schema.
|
|
1254
|
+
|
|
1255
|
+
unused receipt_sent
|
|
1256
|
+
Nothing sets receipt_sent ("a receipt is sent"). Requirements mention it, but no response says what it shall be.
|
|
1257
|
+
Suggested fix: add a shall requirement that sets receipt_sent, or declare it as an input if the environment provides it. Do not choose the value.
|
|
1258
|
+
|
|
1259
|
+
unused Lane.refund
|
|
1260
|
+
Lane.refund is never mentioned.
|
|
1261
|
+
Suggested fix: use refund in a requirement, or remove it from Lane.
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
<!-- verify stdout 1
|
|
1265
|
+
build/plan-verify
|
|
1266
|
+
lint
|
|
1267
|
+
--json
|
|
1268
|
+
examples/order-unused.ears.md
|
|
1269
|
+
-->
|
|
1270
|
+
```text
|
|
1271
|
+
{
|
|
1272
|
+
"ok": false,
|
|
1273
|
+
"spec": "examples/order-unused.ears.md",
|
|
1274
|
+
"conflicts": [],
|
|
1275
|
+
"gaps": [],
|
|
1276
|
+
"misuse": [],
|
|
1277
|
+
"wording": [],
|
|
1278
|
+
"redundant": [],
|
|
1279
|
+
"duplicate": [],
|
|
1280
|
+
"vacuous": [],
|
|
1281
|
+
"unused": [
|
|
1282
|
+
{
|
|
1283
|
+
"name": "loyalty",
|
|
1284
|
+
"kind": "state",
|
|
1285
|
+
"text": "loyalty (\"the customer is loyal\") is a state and no requirement mentions it.",
|
|
1286
|
+
"fix": "Suggested fix: use loyalty in a requirement, or remove it from the schema."
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
"name": "region",
|
|
1290
|
+
"kind": "input",
|
|
1291
|
+
"text": "region (\"the region\") is an input and no requirement mentions it.",
|
|
1292
|
+
"fix": "Suggested fix: use region in a requirement, or remove it from the schema."
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
"name": "receipt_sent",
|
|
1296
|
+
"kind": "output",
|
|
1297
|
+
"text": "Nothing sets receipt_sent (\"a receipt is sent\"). Requirements mention it, but no response says what it shall be.",
|
|
1298
|
+
"fix": "Suggested fix: add a shall requirement that sets receipt_sent, or declare it as an input if the environment provides it. Do not choose the value."
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
"name": "Lane.refund",
|
|
1302
|
+
"kind": "variant",
|
|
1303
|
+
"text": "Lane.refund is never mentioned.",
|
|
1304
|
+
"fix": "Suggested fix: use refund in a requirement, or remove it from Lane."
|
|
1305
|
+
}
|
|
1306
|
+
]
|
|
1307
|
+
}
|
|
1308
|
+
```
|
|
1309
|
+
|
|
1310
|
+
## Scenarios
|
|
1311
|
+
|
|
1312
|
+
`scenarios` lists a small set of examples so a product manager can check that a requirement means what they think. For each requirement it looks for an accepted scenario with the trigger true, an accepted scenario with the trigger false, and a rejected scenario with the trigger true. Each one is the first assignment the search finds, false before true. A requirement with no trigger says so, instead of inventing a trigger-false case. The same assignment is printed once.
|
|
1313
|
+
|
|
1314
|
+
<!-- verify stdout 0
|
|
1315
|
+
build/plan-verify
|
|
1316
|
+
scenarios
|
|
1317
|
+
examples/order.ears.md
|
|
1318
|
+
-->
|
|
1319
|
+
```text
|
|
1320
|
+
spec: examples/order.ears.md
|
|
1321
|
+
scenarios: 3
|
|
1322
|
+
|
|
1323
|
+
R1 accepted, trigger true
|
|
1324
|
+
{
|
|
1325
|
+
"order_submitted": true,
|
|
1326
|
+
"inventory_available": true,
|
|
1327
|
+
"order_fulfilled": true
|
|
1328
|
+
}
|
|
1329
|
+
An order was submitted and inventory was available, and the order was fulfilled.
|
|
1330
|
+
|
|
1331
|
+
R1 accepted, trigger false
|
|
1332
|
+
{
|
|
1333
|
+
"order_submitted": false,
|
|
1334
|
+
"inventory_available": false,
|
|
1335
|
+
"order_fulfilled": false
|
|
1336
|
+
}
|
|
1337
|
+
An order was not submitted and inventory was not available, and the order was not fulfilled.
|
|
1338
|
+
|
|
1339
|
+
R1 rejected, trigger true
|
|
1340
|
+
{
|
|
1341
|
+
"order_submitted": true,
|
|
1342
|
+
"inventory_available": true,
|
|
1343
|
+
"order_fulfilled": false
|
|
1344
|
+
}
|
|
1345
|
+
An order was submitted and inventory was available, but the order was not fulfilled.
|
|
1346
|
+
```
|
|
1347
|
+
|
|
1348
|
+
`--json` is `ok`, `spec`, and `scenarios`. Each row is `id`, `line`, `kind` (`accepted` or `rejected`), `trigger` (`true` or `false`), `found`, `sameAs`, `scenario` (`name` and `value`), and `text`. `scenario` is empty when `found` is false. `sameAs` names an earlier row when the assignment was already listed.
|
|
1349
|
+
|
|
1350
|
+
<!-- verify stdout 0
|
|
1351
|
+
build/plan-verify
|
|
1352
|
+
scenarios
|
|
1353
|
+
--json
|
|
1354
|
+
examples/order.ears.md
|
|
1355
|
+
-->
|
|
1356
|
+
```text
|
|
1357
|
+
{
|
|
1358
|
+
"ok": true,
|
|
1359
|
+
"spec": "examples/order.ears.md",
|
|
1360
|
+
"scenarios": [
|
|
1361
|
+
{
|
|
1362
|
+
"id": "R1",
|
|
1363
|
+
"line": 7,
|
|
1364
|
+
"kind": "accepted",
|
|
1365
|
+
"trigger": "true",
|
|
1366
|
+
"found": true,
|
|
1367
|
+
"sameAs": "",
|
|
1368
|
+
"scenario": [
|
|
1369
|
+
{
|
|
1370
|
+
"name": "order_submitted",
|
|
1371
|
+
"value": "true"
|
|
1372
|
+
},
|
|
1373
|
+
{
|
|
1374
|
+
"name": "inventory_available",
|
|
1375
|
+
"value": "true"
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
"name": "order_fulfilled",
|
|
1379
|
+
"value": "true"
|
|
1380
|
+
}
|
|
1381
|
+
],
|
|
1382
|
+
"text": "An order was submitted and inventory was available, and the order was fulfilled."
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
"id": "R1",
|
|
1386
|
+
"line": 7,
|
|
1387
|
+
"kind": "accepted",
|
|
1388
|
+
"trigger": "false",
|
|
1389
|
+
"found": true,
|
|
1390
|
+
"sameAs": "",
|
|
1391
|
+
"scenario": [
|
|
1392
|
+
{
|
|
1393
|
+
"name": "order_submitted",
|
|
1394
|
+
"value": "false"
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
"name": "inventory_available",
|
|
1398
|
+
"value": "false"
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
"name": "order_fulfilled",
|
|
1402
|
+
"value": "false"
|
|
1403
|
+
}
|
|
1404
|
+
],
|
|
1405
|
+
"text": "An order was not submitted and inventory was not available, and the order was not fulfilled."
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
"id": "R1",
|
|
1409
|
+
"line": 7,
|
|
1410
|
+
"kind": "rejected",
|
|
1411
|
+
"trigger": "true",
|
|
1412
|
+
"found": true,
|
|
1413
|
+
"sameAs": "",
|
|
1414
|
+
"scenario": [
|
|
1415
|
+
{
|
|
1416
|
+
"name": "order_submitted",
|
|
1417
|
+
"value": "true"
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
"name": "inventory_available",
|
|
1421
|
+
"value": "true"
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
"name": "order_fulfilled",
|
|
1425
|
+
"value": "false"
|
|
1426
|
+
}
|
|
1427
|
+
],
|
|
1428
|
+
"text": "An order was submitted and inventory was available, but the order was not fulfilled."
|
|
1429
|
+
}
|
|
1430
|
+
]
|
|
1431
|
+
}
|
|
1432
|
+
```
|