aboutsummaryrefslogtreecommitdiff
path: root/docs/process.md
diff options
context:
space:
mode:
authorrottedfm <rottedfm@proton.me>2026-08-19 11:24:55 -0400
committerrottedfm <rottedfm@proton.me>2026-08-19 11:24:55 -0400
commit8e16347b0eb329e84892af8ece36886324c95f62 (patch)
treebe1267972b5de2f1ae592577dfabce67f1fe6e87 /docs/process.md
parentc6ae4660d1cc2414b22c492c5e819d009c8187c2 (diff)
parentea0bd36167b684c0accdb5ce2b2e21b8d84aeb25 (diff)
Merge branch 'buffer-implementation'HEADmain
Establishes the first working baseline: moji <file> opens a file into a ropey rope and edits it with Helix selection-first modal editing, under a DO-178C DAL-C requirements and traceability process. Prior to this, main tracked four files and src/main.rs was still println!("Hello, world!") — there was no buildable state to build on. Verified on a fresh clone of the branch with no untracked files: cargo build; clippy --all-targets -D warnings clean; 294 tests passing; scripts/check-trace.sh reports 98/98 requirements traced in both directions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'docs/process.md')
-rw-r--r--docs/process.md308
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.