aboutsummaryrefslogtreecommitdiff
path: root/docs/process.md
blob: 9ce5fb9fb3d21561c4855caf43ec63286e1b0689 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# mojibake — Requirements and Commit Process

Software Level: **DAL-C** (DO-178C)
Project status: **early development** — see the README for what does not work yet.

This document explains the system: what the requirement artifacts are, how they
relate to each other and to the code, and what a commit must satisfy to be
accepted. It is the entry point — read this before `docs/requirements/`.

---

## 1. Why the artifacts exist

DO-178C does not ask "is the code good?" It asks a narrower, checkable
question: **can you show that every behaviour the software has was asked for,
and that everything asked for was built and verified?**

Answering it needs four things, and each one is a directory here:

| Artifact | Answers |
|---|---|
| `docs/requirements/hlr.md` | What must the software do? |
| `docs/requirements/llr.md` | How, specifically, in terms a test can check? |
| `docs/requirements/derived.md` | What did the implementation need that nobody asked for? |
| `docs/traceability/trace.md` | Where is each of those in the code, and which test proves it? |

The chain runs **HLR → LLR → source → test**, and it must hold in *both*
directions. Forward: every requirement is implemented and verified. Backward:
every piece of behaviour traces to a requirement. Backward traceability is the
one people skip, and it is the one that catches unrequested behaviour — code
that does something nobody asked for is either a missing requirement or a
defect, and you cannot tell which until you look.

---

## 2. The three requirement kinds

### High-level requirements — `MJB-HLR-nnn`

What the software does, in terms a user would recognise. One behaviour per
requirement, phrased with "shall", and stated so that it is *observable* —
if you cannot describe how to check it, it is not a requirement yet.

> **MJB-HLR-007** — The editor shall provide next-word-start,
> previous-word-start and next-word-end motions bound by default to `w`, `b`
> and `e`. Consistent with Helix and unlike Vim, each shall leave a selection
> spanning the traversed text rather than a collapsed cursor […]

HLRs must not specify implementation. Two here do — MJB-HLR-004 names a rope
and MJB-HLR-014 names TOML — because those were themselves requested. That
exception is recorded in the requirements review rather than hidden.

### Low-level requirements — `MJB-LLR-nnn`

The same behaviour decomposed until each statement maps to one identifiable
thing in the code and one test. An LLR names types, functions and exact values;
an HLR does not.

> **MJB-LLR-065** — `move_next_word_start` shall return a range whose anchor is
> the pre-motion cursor position and whose head is the start of the following
> word, so the result is a selection spanning the traversed text.

Every LLR **must** cite a parent HLR. An LLR with no parent is either a derived
requirement in disguise or scope creep.

### Derived requirements — `MJB-DR-nnn`

Requirements the implementation turned out to need that no HLR anticipated.
DO-178C §5.1.1.b requires these to be recorded **and fed back to the safety
assessment**, because by definition nobody evaluated them when the HLRs were
written.

Every entry states what forced it and carries an explicit *"Reviewer must
judge"* clause. A derived requirement is not a note-to-self; it is an open
question addressed to someone else.

Two of the seven here could not have been anticipated, and both are instructive:

- **MJB-DR-002** — choosing byte indices over char indices admits a failure
  mode char indexing cannot express: an offset landing inside a character,
  which makes ropey panic.
- **MJB-DR-007** — `encoding_rs` decodes UTF-16 but silently refuses to
  *encode* it, substituting UTF-8. Found by a round-trip test, not by reading
  the code.

---

## 3. The ID system

```
MJB-HLR-nnn     high-level requirement
MJB-LLR-nnn     low-level requirement
MJB-DR-nnn      derived requirement
```

IDs are **permanent and never reused**. Deleting a requirement means marking it
withdrawn, not freeing its number — a trace matrix that silently renumbers is
worse than no matrix, because reviews and commits reference IDs by number.

LLR numbers are allocated in blocks by subsystem so related requirements read
together:

| Block | Subsystem |
|---|---|
| 001–019 | selection |
| 020–039 | graphemes |
| 040–059 | transactions, history |
| 060–089 | movement |
| 090–109 | viewport |
| 110–129 | document, encoding, line endings |
| 130–149 | save |
| 150–179 | keymap, commands |
| 180–199 | configuration |
| 200–219 | presentation |

Blocks are a convenience, not a rule. Leave gaps.

---

## 4. How code and tests bind to requirements

Two conventions, both chosen so a machine can check them.

**Source carries a tag** immediately above the item implementing it:

```rust
/// MJB-LLR-005: the byte offset the block cursor is drawn at.
pub fn cursor(&self, text: RopeSlice) -> usize {
```

**Tests are named for what they verify**:

```rust
#[test]
fn mjb_llr_065_w_leaves_a_selection() {
```

A test name is a claim. `mjb_llr_065_w_leaves_a_selection` asserts that
MJB-LLR-065 holds; if the test does not actually establish that, the name is a
lie and the matrix is worthless. Prefer a name that states the expected
behaviour over one that describes the mechanics.

More than one test may carry the same ID. Robustness cases usually do.

---

## 5. The matrix is verified mechanically

Inspection does not scale and does not survive refactoring. Three `grep`s
answer the whole question:

```bash
grep -oE  'MJB-LLR-[0-9]+' docs/requirements/llr.md | sort -u > defined
grep -rhoE 'MJB-LLR-[0-9]+' src/                    | sort -u > tagged
grep -rhoE 'mjb_llr_[0-9]+' src/ tests/ | sed 's/mjb_llr_/MJB-LLR-/' | sort -u > tested

comm -23 defined tagged   # requirement with no implementation
comm -23 defined tested   # requirement with no test
comm -13 defined tested   # test naming a requirement that does not exist
```

**All three sets must be empty.** The third catches typos and stale references
after a requirement is renumbered or withdrawn — a check that is easy to omit
and that fails silently when omitted.

Run it before every commit. It is not optional, and it takes under a second.

---

## 6. Commit requirements

DO-178C Table A-8 (configuration management) wants changes to be controlled,
traceable, and reviewable. In practice, for this repository, that reduces to
five rules.

### Rule 1 — A commit is a complete unit

A commit must leave the tree in a state where all of the following pass:

```bash
cargo build
cargo clippy --all-targets -- -D warnings
cargo test
```

plus the traceability check from §5. Requirements, implementation, and tests
land **together**. Do not commit an LLR whose test arrives in the next commit:
between the two, the matrix is broken and the repository cannot be reviewed.

### Rule 2 — The message states which requirements are affected

```
<subject line, imperative, ≤72 chars>

<why the change is needed — the problem, not the diff>

Requirements: MJB-LLR-065, MJB-LLR-066
Derived:      MJB-DR-007            (omit if none)
Verified:     cargo test (294 passing), clippy clean, trace 98/98/98
```

The body explains *why*. The diff already shows what changed; it cannot show
what problem you were solving, and that is the part a reviewer cannot
reconstruct.

### Rule 3 — Classify the change

Every commit is exactly one of these, and the class determines what else must
be in it:

| Class | Also required in the same commit |
|---|---|
| **Implementation only** — behaviour already specified | Test naming the existing LLR |
| **New behaviour** | New LLR, its parent HLR, source tag, test |
| **Requirement change** | Updated LLR/HLR, updated tests, updated trace matrix |
| **Derived requirement discovered** | Entry in `derived.md` with a "Reviewer must judge" clause |
| **Defect fix** | Regression test named for the LLR that was violated |
| **Process/docs only** | Nothing further; state that no source changed |

If a change does not fit a class, that is the signal: either it is unrequested
behaviour needing a requirement, or it is two commits.

### Rule 4 — A defect fix explains how it was found

This is the rule most worth keeping. Recording the *detection method* tells the
next person which verification activities are actually working:

> **MJB-DR-007** — `encoding_rs::encode` silently substitutes UTF-8 for UTF-16.
> Invisible to inspection; found only by a byte-comparing round-trip test. Any
> encoding added later should be guarded by the same test shape.

Four defects in the initial buffer work were found by four different means — a
failing integration test, a clippy lint, a round-trip test, and a coverage
report showing 0% on a function nobody called. None were found by re-reading
the code. That is worth knowing.

### Rule 5 — Reviews are recorded, not implied

DAL-C permits **review without independence**: the author may review their own
work. It does not permit skipping the review. Completion is recorded in
`docs/reviews/`:

- `requirements-checklist.md` — against DO-178C Tables A-3 and A-4
- `code-checklist.md` — against Table A-5
- `library-selection.md` — rationale for non-obvious dependencies

A checklist that says only "Pass" everywhere has not been used. The value is in
the entries that say something: a conflict found, a requirement reworded, a
dependency accepted with a stated risk.

---

## 7. Definition of done

```
[ ] Requirements written or updated before the code
[ ] Every new LLR cites a parent HLR
[ ] Source tagged // MJB-LLR-nnn
[ ] Test named mjb_llr_nnn_* for every affected LLR
[ ] Robustness cases covered: empty, boundary, malformed, overflow
[ ] Trace matrix updated
[ ] Three-way grep check: all sets empty
[ ] cargo build
[ ] cargo clippy --all-targets -- -D warnings
[ ] cargo test
[ ] Statement coverage reported for changed modules
[ ] Derived requirements recorded and flagged
[ ] Review checklist updated
[ ] Commit message names affected requirements and classifies the change
```

---

## 8. What is deliberately *not* claimed

Overstating compliance is worse than not claiming it, because it removes the
reader's ability to judge. This project claims:

- **Statement coverage only.** MC/DC and decision coverage are DAL-A and DAL-B
  objectives. They are not measured and are not claimed.
- **Review without independence.** The author reviewed their own work, which is
  permitted at DAL-C and stated plainly in each checklist.
- **No qualified tools.** `cargo-llvm-cov` and `clippy` are not
  tool-qualified per DO-330. Their output is evidence, not proof.
- **Six open derived requirements.** They need a safety-assessment judgement
  that has not happened. They are listed as open, not quietly resolved.

---

## 9. Worked example

Adding `t` (find-till-char) would go:

1. **HLR** — does an existing one cover it? MJB-HLR-006 covers character and
   line motion but not character search. So a new HLR is needed.
2. **LLR** — decompose: the pending-argument state, the forward search, the
   stop-one-short semantics, the not-found case, the count interaction. Roughly
   five LLRs in the 060–089 block.
3. **Derived?** — the keymap has no way to capture "the next keystroke as a
   literal character". That is a real gap MJB-HLR-015 did not anticipate:
   record it in `derived.md` and flag it.
4. **Implement**, tagging each item.
5. **Test** each LLR, including: not found, at end of buffer, on a multi-byte
   character, with a count exceeding the number of matches.
6. **Update** the trace matrix; run the three-way grep.
7. **Commit** as class *New behaviour*, naming every new ID.

The step people skip is 3. It is the one DO-178C exists to catch.