diff options
| author | rottedfm <rottedfm@proton.me> | 2026-08-19 11:21:47 -0400 |
|---|---|---|
| committer | rottedfm <rottedfm@proton.me> | 2026-08-19 11:21:47 -0400 |
| commit | ea0bd36167b684c0accdb5ce2b2e21b8d84aeb25 (patch) | |
| tree | be1267972b5de2f1ae592577dfabce67f1fe6e87 /docs/process.md | |
| parent | 8c0b4c53b130555f040884c1f52b90f16b23e241 (diff) | |
feat: implement Helix-style modal buffer under DO-178C DAL-C
The repository was an unmodified ratatui component template: no editor
code, JSON5 config, and placeholder widgets. This establishes the first
working baseline — `moji <file>` opens a file into a ropey rope and edits
it with Helix selection-first semantics.
Requirements, implementation and tests land together because they must:
the traceability check rejects requirements with no implementation and
tests naming requirements that do not exist, so neither half is a valid
commit on its own.
Package renamed to mojibake-editor (mojibake was taken on crates.io);
binary is moji, library target stays mojibake.
Class: New behaviour
Requirements: MJB-HLR-001..019, MJB-LLR-001..205
Derived: MJB-DR-001..007 (DR-001 resolved, six open for review)
Verified: cargo build; clippy --all-targets -D warnings clean;
cargo test 294 passing; ./scripts/check-trace.sh 98/98/98;
cargo package clean
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'docs/process.md')
| -rw-r--r-- | docs/process.md | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/docs/process.md b/docs/process.md new file mode 100644 index 0000000..9ce5fb9 --- /dev/null +++ b/docs/process.md @@ -0,0 +1,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. |
