Method · End-to-End Practice

The Three Axes.

How an open-source project travels from a business sentence to requirements, design, real users, support — and back into new requirements. MRRC Modern as the worked example, with SDD, Harness and FDE as the method, and evidence at every station.

75 days · 67 SDD version rows 22 machine-readable constraints 262 → 1,299 tests v1.0.0 → v1.18.1 · 3 platforms

By BG1SB  ·   ·  ~22 min read

Most engineering write-ups start halfway: a requirement appears, a design follows, tests pass. This one starts where projects actually start — with a business sentence a person said out loud — and follows MRRC Modern for its first 75 days (2026-07-06 → 2026-09-19): one remote-control server for amateur radio that grew from a single-radio experiment into a three-platform, ten-model-key product with a public support chain. The method on display is the same three axes used across this ecosystem — a living SDD (the contract), an engineering harness (the machinery that enforces it), and FDE (the field loops that keep correcting both). Every station below is anchored to a commit, an SDD row, a test, a release artifact, or a public answer card.

TL;DR — the journey in six findings

  • FactOne sentence became a contract on day one. The Echo output — "a browser-based FT-710 remote control that replaces SCU-LAN10 with zero additional hardware, one USB cable" — drove SDD V1.0: all 15 chapters, written concurrently with the first commit.
  • FactThe field wrote the roadmap. 12 git-verifiable FDE cycles in 16 days, each triggered by exactly one real field signal — 16 kHz TX crackling → AD-011; a ~20 Hz frequency drift → the DN; ruling and constraint; filter races → stale-read discard.
  • FactKnowledge became machinery. 22 machine-readable constraints (block / warn / info) distilled from real incidents, checked before edits (exit code 2); the test suite grew 262 → 1,299; release_check holds offline release rules; PROJECT_MAP names the single source of truth per topic.
  • InferenceSupport became the sensor network. Built as four slices in three days: bundle → triage + answers → update check → board + unattended implement. Classification is the decision; the loop now ends at a reviewed branch, never at main.
  • InferenceNew requirements arrive through the same door as old ones. Recording silence (field log), macOS "damaged" and silent RX (release week), PTT release latency (field feel) — each became an SDD row, a guard, a release, and a public answer card.
  • ThesisThe method transfers; the counts do not. Definition before implementation, every incident becomes a rule, instrument the field, automate the doing and never the verdict. §07 turns this into eight rules and a three-stage adoption ladder.

One project, two ways the chain breaks

Business intent → requirements → design → field → support → new requirements. Every project walks it; the failures are what differ.

The chain looks linear on a slide and behaves like a loop in reality: a requirement is only real when a field signal confirms it mattered, and a field signal is only useful when it can change the design. When the loop closes, the project compounds. It breaks in two recognizable ways instead.

The vision never lands

A business sentence is repeated in pitch decks while requirements drift into feature lists. Nobody can point at the artifact where the sentence became a measurable obligation — so every argument restarts from opinion.

The field never returns

Iteration is fast, but what the field taught lives in chat logs and a maintainer's memory. The same incident is rediscovered each quarter; a version number tells you what shipped but not what it learned.

MRRC Modern was built to make both failures expensive to commit. It began on 2026-07-06 with the initial commit of a single-radio experiment (FT-710 over its own USB cable), and by 2026-09-19 had produced 435 commits, 67 SDD version rows, 22 architecture decisions, 22 machine-readable constraints, 37 changelog entries, releases from v1.0.0 to v1.18.1 across Windows, macOS and a Raspberry Pi image — and a public support chain with answers, a board and an unattended implementer. None of the numbers are the point; what follows is the machinery that produced them.

Author's thesis

The three axes are one system. The SDD settles judgment into a contract; the harness makes forgetting structurally impossible; FDE keeps letting the field correct both. Remove any one and you can name the failure that follows — a beautiful document nobody obeys, a fast loop with no memory, or a contract that has stopped listening. It is the umbrella's thesis — execution can be delegated, judgment cannot — applied to a whole lifecycle; the chain that page promises (field incident → SDD ruling → constraint → blocked re-introduction) is exactly what §02–§05 walk (thesis).

The contract that outlives the conversation

A living design document in three harness levels: business (what), technical (how), product (deliverable).

2.1 · From a sentence to measurable requirements

The project's Echo phase produced one paragraph before any architecture:

Fact · the demo definition that drove SDD V1.0

"A browser-based FT-710 remote control that replaces SCU-LAN10 with zero additional hardware. One USB cable. Real-time waterfall via FT4222 SPI. Bidirectional 48 kHz Opus audio. Mobile-first UI. Four WebSocket channels (control, audio RX, audio TX, spectrum)."

Echo inputs were concrete: daily operation by the author across devices; an evaluation of the existing open-source FT-710 remote (F4HTB) that found no SPI scope, no bidirectional Opus, no mobile UI; the gaps documented against Hamlib; and — the unfair advantage — reusable patterns inherited from the older MRRC project (PTT safety, multi-instance architecture, the SDD template itself). The paragraph became the baseline: all 15 SDD chapters in one burst, including a numbered NFR table with targets and verification methods. Requirements in this project are written as obligations a field test can settle:

RequirementTargetVerification method
NFR-001 · RX audio latency (radio speaker → browser speaker)< 500 ms end-to-endListening test
NFR-002 · Control responseUI command ack within 200 ms on LANWebSocket round-trip observation
NFR-008 · PTT response< 100 ms from touch to TX commandTiming logs
SC8 · Success criterionPTT cannot stickSafety architecture + watchdog tests

The pattern to copy is small and strict: a requirement without a target and a verification method is an opinion. "PTT cannot stick" survives because a chapter (SDD 15, PTT safety architecture) and a watchdog exist; it is not a slogan.

2.2 · From requirements to design decisions

Design decisions in this project are numbered, dated, and traceable: AD-001 … AD-022, one decision register. Every one of them has an Echo behind it, and several have field incidents behind them:

DecisionWhat it settledWhat forced it
AD-002Direct CAT via pyserial instead of Hamlib for the FT-710 pathHamlib gap analysis; the in-cable protocol was simpler and gave real spectrum
AD-01148 kHz codec domain with a frame-aligned 44.1 kHz bridgeField signal: "16 kHz TX crackling"
AD-014DN; is never polled — it steps the VFO down ~20 Hz per callA live frequency-drift incident
AD-017Recording writer is per-session and idempotently rebuilt; a full queue drops the oldest blockThe 2026-09-12 silent-MP3 field report
AD-019Unverified radio models refuse transmit unless explicitly allowedNew model families arriving faster than hardware acceptance
AD-021 / AD-022Support bundle, triage and the provable update channelSupport itself: first-round emails only ever asked for evidence

2.3 · The version row is the atom of learning

The SDD's version history is not a changelog duplicate; it is the project's learning ledger. One row per change episode: the field signal or requirement, the evidence, the decision, the boundary that remains. 67 rows in 75 days — some spanning a one-line fix, some an entire support chain. Row V2.45 reads like a field report with a conclusion: the log signature of the silent recording ("Recording writer is falling behind — dropping audio" appearing 6 ms after start, 950+ drops in 22 s), the ffmpeg measurement of the output (-91 dB = digital silence, duration correct), the root cause, the fix, the three regression tests, and the boundary — "long recordings and Raspberry Pi SD-card writes remain unverified".

Fact · no automatic promotion

Lifecycle states are explicit — planned, implemented, tested, bench-verified, field-verified, released, deferred, known-issue — and nothing advances by itself. A released subsystem cannot lend its maturity to another client or radio model; unverified Icom and Yaesu profiles ship behind a transmit gate and say so.

Constraints that don't need remembering

If a lesson only lives in a document, it will be skipped. The harness is what makes skipping it fail loudly.

3.1 · Two layers, one job: make failure visible

The harness has an outer layer (business, technical and product constraints — the three SDD groups) and an inner execution layer (human + agents, repository, tools, tests + review, deployment, field telemetry). This article is about the inner layer's sharpest instrument: the constraint registry.

3.2 · The constraint registry: 22 lessons as machinery

.agents/skills/sdd-guardian/harness/constraints.json holds 22 rules distilled from the SDD and from real incidents, each with a severity, a rationale, and match patterns. An agent (or human) asking for a brief gets them at session start; a pre-edit check blocks violations with exit code 2. The registry's origin stories are the point — these are not style-guide preferences:

ConstraintSeverityWhat it encodes
cat-no-dnblockDN; steps the active VFO DOWN ~20 Hz per call — a live frequency-drift incident, now impossible to re-introduce by accident
cat-direct-serial-ioblockSerial I/O only inside radio controllers — keeps the asyncio transport from being touched from everywhere
audio-16k-rateblockAudio rates are 44.1 kHz device / 48 kHz codec — never 16 kHz in the audio path (the crackling incident); 16 kHz is a storage domain
poll-stale-guardinfoPoll loops discard stale in-flight reads (the "filter switch works 50% of the time" race)
ptt-release-no-verifyinfoTX0 is fire-and-forget — verify loops on release are how PTT gets stuck
password-constant-timewarnPassword checks use hmac.compare_digest

The conversion rule: an incident is only fixed when it becomes a guard. Repair is link one of three; the ruling in the SDD is link two; the machine-readable constraint that blocks the next attempt is link three. This is why the registry is small and boring — every row cost something real.

3.3 · Gates and single sources of truth

Around the registry sits the rest of the machinery: the unittest suite (262 tests at the 12th cycle in July → 1,299 by the FDE-board commits in September), offline release_check rules (reported as 29 checks / 0 failing on release rows), a release-artifacts registry holding per-file version, size and SHA-256, and tests that enforce document consistency — the SDD version in the version-history first row must match the README, generated pages and landing page. For humans, docs/PROJECT_MAP.md answers one question — "if I change X, what must move with it?" — with a table per topic:

TopicSingle source of truthEnforced by
App versionCHANGELOG.md top entryBuild scripts parse it; release_check
SDD versionSDD/14-version-history.md first rowtest_sdd_docs_consistency.py
Design decisionsSDD/08-architecture-decisions.md (AD-001…AD-022)release_check AD index + the guardian registry
Engineering constraintsconstraints.jsonsdd_context.py check — block violations exit 2
Release artifactsrelease-artifacts.json (version / size / SHA-256)release_check.py + artifact tests
Inference · the harness is what makes delegation safe

You can hand a repository to an agent only if the repository can refuse. The registry turns the project's scar tissue into pre-edit interception — which is the same design view as the product's transmit gate: authority is bounded, refusals are explicit, and every refusal cites where it came from.

Echo → Delta → Product, and the fourth practice

The lineage's field loops: deploy where reality is, prove the riskiest assumption, abstract what survived — then let support run the same loop on the product itself.

4.1 · Twelve cycles, one field signal each

The first era is documented as a git-verified cycle map. Each row is one Echo (a field signal), one Delta (the riskiest proof), one Product output (an abstraction that stays). A sample:

CycleField signal (Echo)What got built (Delta → Product)
V1.0 · Jul 6Replace SCU-LAN10, one USB cable, no extra hardwareFull 15-chapter SDD baseline + 7 core modules, in one day
V1.1 · Jul 6"16 kHz TX crackling"AD-011: 48 kHz codec domain + 44.1 kHz frame-aligned bridge
V1.2 · Jul 8"Frequency drifts ~20 Hz; TX meters blank"Active-VFO tracking, meter calibration — and the ruling that became cat-no-dn
V1.7 · Jul 19"Filter switch works 50% of the time"Stale-read discard after each poll query + 3 regression tests
V1.9 · Jul 21"USB reconnect → waterfall frozen forever"on_reconnected hook re-runs scope init after reconnect
V2.1 · Jul 22"PTT keys but no voice / no RF power"TX uplink ownership promotion + per-session TX counters + 5 regression tests

Four patterns made this era work, and all four are copyable: one cycle = one real signal (no speculative features); documentation is concurrent, not retrospective (the version-history row lands with the fix); the riskiest assumption gets the prototype (direct CAT first, 48 kHz pipeline second, multi-client ownership later); and start from leverage (six assets inherited from the older project meant cycle one began at "integrate", not "invent").

4.2 · Product means abstraction — and then a platform

The Product phase is where field hacks become assets other work can stand on: a standalone scope subprocess with a length-prefixed pipe (byte-wise resync on the FT4222 was proven impossible), a frame-aligned 44.1↔48 kHz resampler, adaptive polling with skip-on-command and stale-read discard, a tagged dual-codec audio frame, and per-PTT TX session logging. The decisive abstraction arrived in August: a pluggable radio backend. The server, polling and state layers became radio-agnostic; radio-specific behaviour moved behind profiles. That single move turned the FT-710 solution into a platform — five Icom models via a shared CI-V core and four Yaesu models via a shared ASCII-CAT core, all built on the field-proven FT-710 transport, arriving behind a transmit gate until hardware acceptance catches up.

4.3 · Practice 4: the support loop

The fourth loop practice turns the same Echo→Delta→Product discipline on the product's own support. It shipped as four slices in three days: (1) the redacted diagnostics bundle; (2) the triage autopilot and the public answers page; (3) the read-only update check with a generated manifest; (4) the public field board and the unattended implementer. The rule that organizes it: classification is the decision — noise closes as answered, bugs auto-schedule into the implementation queue, features wait for the operator, and an un-answered report never closes. The implementer runs single-flight from a clean main worktree: a read-only model proposes a unified-diff JSON contract; guards refuse protected paths and oversized patches; the patch lands only on an isolated fde/<id> branch and commits only if the full test suite is green. main is never touched by an unattended process; the merge stays human. The mechanism is specified in the mechanism volume, §Field Loop; the journal of its first week is in The Support Loop.

Two threads, every station

A field incident is only interesting if you can watch it travel. Two real ones, from symptom to shipped fix to public answer.

5.1 · Thread A — "the recording was silent, but the file looked right"

Field (2026-09-12, 22:26). A live session produced an MP3 with the correct duration and size — and no audio. The logs carried the signature: Recording writer is falling behind — dropping audio as early as 6 ms after start, 950+ drops over 21.9 seconds, ending in Recording queue full on stop — finishing directly. ffmpeg measured the output at -91 dB: digital silence.

Design (V2.44/V2.45). The writer task was one-shot per session by design — it consumes the stop sentinel and finishes — but it was created only once at process start. After the first recording, every later session had no consumer; the bounded queue filled in milliseconds and stop() back-filled the timeline with silence. The fix: an idempotent _ensure_rec_writer() rebuilt on every audio block, stop enqueue and REC; a WARNING when the writer dies (the observability the incident lacked); a full queue drops the oldest block instead of synchronously finishing. Three regression tests; shipped in v1.15.0.

Support (2026-09-18). A new diagnostics bundle arrived carrying the same signature — but every matching log line was dated Sep 12, before the fix. The triage model, required to read the project's own incident history before writing a verdict, cross-referenced the CHANGELOG and the SDD version-history rows, cited server.py:348 in the shipped code, and refused both easy answers ("already fixed" / "regression"): it ran a protocol instead — record one new QSO and report back. The follow-up bundle closed the case with healthy Sep-19 logs. The public answer card states the reasoning and the raw evidence lines.

Inference · the version row is where the thread settles

Without the row, the second report starts from zero and the maintainer re-derives a two-week-old diagnosis. With it, triage is a lookup: signature → history → verdict → protocol. The SDD row is not documentation debt; it is the project's immune memory.

5.2 · Thread B — "the app is damaged and cannot be opened"

Field (release week, v1.18.0). The first macOS installer shipped; users met a Gatekeeper dialog no right-click can bypass, and a second, quieter failure — RX audio was silent while the log told them to "check the radio".

Design (V2.54, shipped as v1.18.1). Two root causes, both instances of silent failure. The signing step had been failing silently since the first macOS build (the build script treated it as non-fatal), so every bundle shipped unsigned; the fix laid the resource tree out the way codesign requires and made a failed signature abort the build. Silent RX was a missing permission key: macOS happily opened the capture stream and filled it with zeros, reporting no error, because the bundle never asked for microphone access; the fix declares the key (asserted at build time) and points the warning at System Settings instead of the radio. Verification was artifact-level, not vibes: codesign --verify valid, the designated requirement satisfied, spctl complaining only about the absent Developer ID, and the system's own permission log moving from Refusing… to AUTHREQ_PROMPTING. The documentation answered the next user before they wrote in: the macOS guide explains both dialogs and the landing page says plainly that pre-v1.18.1 downloads must be replaced.

5.3 · Thread C — "PTT release feels laggy" (a one-paragraph case)

A field feeling ("too long after release before RX returns") was decomposed from logs into ~470–870 ms: a 262–300 ms capture reopen, a ~70–130 ms drain, poll ordering, and ~120 ms of muted Opus frames queued in client jitter buffers. V2.55 shipped three independent fixes — skip the TX-status poll before the release drain, stop broadcasting muted RX frames while keyed, and remember the last-good capture device to skip a full enumeration on reopen — with the reopen later measured down from ~270 ms to ~200 ms. The boundary stayed written down: the end-to-end improvement needs real-radio verification.

Fact · what the three threads share

The same six stations: field signal → evidence in the log → SDD row (root cause + decision + boundary) → regression tests → release → public answer card. The differences are only cadence — the recording thread took a day from signal to fix and a week to the follow-up closure; the macOS thread took an evening; the latency thread took two days. The cadence is set by the slowest station, which is usually evidence, not code.

What the numbers can and cannot support

Counts are process evidence. They show the machinery ran; they do not promote a product claim by themselves.

ObservationValue (as of 2026-09-19)What it supports
Repo span2026-07-06 → 2026-09-19, 435 commitsSustained cadence on one product line
SDD15 chapters; 67 version rows; AD-001…AD-022; 36 NFRs; SC1–SC9Requirements and decisions are traceable artifacts, not memory
Harness22 constraints (block/warn/info); pre-edit gate exit 2; release_check 29 checks; artifact registry with SHA-256Lessons are enforced, not archived
Tests262 at the 12th cycle (Jul 22) → 1,299 (Sep 19)Each incident bought regression coverage
Releases37 changelog entries; v1.0.0 → v1.18.1; Windows, macOS, rpi64Shipping is routine; rollback targets exist
Radio surface10 model keys across 3 protocol familiesThe platform abstraction survived contact with other hardware
Support chain4 slices in 3 days; 2 published answers for Modern; board liveField feedback has a pipeline, not a mailbox
Fact · the honest boundaries (written down, not smoothed over)

Unverified Icom and Yaesu models refuse transmit until hardware acceptance; the Yaesu family is receive-only in this phase; the update channel ships read-only (download/install is a later slice); the FDE board's first real bug is still waiting for its first real scheduling, and the first unattended patch for its first human merge; summary-based triage reads summaries, not whole environments. Comparable cross-project numbers — including the currently maintained test counts — live in one place: the evidence ledger.

What transfers, and in what order to build it

Eight rules extracted from the practice — then a three-stage adoption ladder with the trap at each stage.

  1. Write the contract with the first commit

    One vision paragraph, then all the chapters — including the NFR table with targets and verification methods. The first commit is the cheapest moment to have a contract; every later moment costs a retrofit.

  2. Give every requirement a target and a method

    "< 500 ms, listening test" is a requirement; "should be fast" is a preference. Success criteria like "PTT cannot stick" need an architecture chapter and a watchdog behind them.

  3. Let the field pick the roadmap

    One cycle = one real signal. No speculative features. The version-history row — signal, evidence, decision, boundary — is the roadmap log, and 67 of them make an argument no feature list can.

  4. Turn every incident into a rule, not a memory

    Repair is link one. The ruling is link two. The machine-readable constraint checked before the next edit is link three — only then is an incident permanently fixed. Keep the registry small; every row should have cost something real.

  5. Instrument the field before scaling features

    A redacted diagnostics bundle converts an interview into a measurement. Build the sensor before you need the data; the support chain here shipped in four slices, sensor first.

  6. Make classification the decision

    Answer the taxonomy question once and let it dispatch: environment noise closes, defects schedule, features wait for a human. Never let an un-answered report close — "closed" is a promise.

  7. Automate the doing, never the verdict

    Unattended implementation is safe only when bounded: read-only proposal, guards, isolated branch, full test gate, main untouched, human merge. Keep the two ledgers apart — process evidence never promotes a product claim. The umbrella names the retained half — intent, boundaries, judgment — and the reason is accountability, not task size (roles).

  8. Write the boundaries down

    No automatic promotion between lifecycle states; unverified hardware refuses transmit; the update channel says it is read-only; the known issue stays visible. A boundary written down is a trust asset; a boundary discovered by a user is a breach.

The adoption ladder

StageWhat you buildYou are done when…The trap
1 · Document eraSDD baseline (business / technical / product chapters) + a version-history row per change + a changelogYou can name the requirement and the decision behind any shipped behaviorDocs written once and quietly abandoned — the row must land with the fix, or it will always be "later"
2 · Contract eraConstraint registry from real incidents + pre-edit checks + tests/release rules + a source-of-truth mapA known failure cannot be re-introduced without a red gateCollecting rules nobody runs — enforcement (exit codes in the path of edits) is the whole point
3 · Field-loop eraBundle → triage → answers → board → unattended implement, with human mergeA user symptom becomes a tested patch on a branch while the verdict stays humanAutomating past the guardrails — the loop must fail closed, or it becomes an unreviewed main history
Author's thesis · what to copy

Copy the discipline, not the counts. Nobody else needs this project's test number or release cadence; what transfers is the shape: a contract that settles judgment, machinery that refuses known mistakes, a field loop that is allowed to correct both — and a human holding the verdict on what counts as done.

One sentence, three axes

A vision sentence became a contract; the contract became requirements with targets; the requirements became decisions; the decisions met reality and were corrected; the corrections became guards; the guards made the next delegation safe; and the support chain closed the circle by letting users report reality back in — with answers, a board, and a tested patch waiting for a human yes. That is the whole journey. The three axes are one sentence:

The one-sentence discipline

Let judgment settle into a contract, let machinery refuse the mistakes you have already paid for, and let the field — through the users — correct both. The verdict stays human, always.

Go deeper on the mechanism: Harness, Loops & Living SDD · the field loop · Agentic Engineering. The journal behind this case study: Seven Billion Tokens, Dissected · The Support Loop. Live surfaces: the MRRC Modern site, its answers page, and the FDE board.