方法 · 端到端实践

板斧

一个开源项目如何从一句业务话走到需求、设计、真实用户、支持——再走回新需求。以 MRRC Modern 为完整样本,以 SDD、Harness、FDE 为方法,每一站都有证据。

75 天 · 67 行 SDD 版本记录 22 条机器可读约束 测试 262 → 1,299 v1.0.0 → v1.18.1 · 三平台

BG1SB  ·   ·  约 22 分钟

多数工程文章从半截讲起:需求出现,设计跟进,测试通过。这篇从项目真正开始的地方讲起——一个人说出的一句业务话——并跟着 MRRC Modern 走过它的前 75 天(2026-07-06 → 2026-09-19):一台业余无线电的远程控制服务器,从单电台实验长成三平台、十个机型键的产品,并带着一条公开的支持链。要用到的方法正是这套生态里反复使用的三板斧——活体 SDD(契约)、工程 Harness(把契约变成机器执行力的装置)、FDE(不断修正两者的现场回路)。下文每一站都锚定到某个提交、某行 SDD、某个测试、某个发布产物或某张公开答复卡。

TL;DR — 六条发现里的旅程

  • 事实一句话在第一天变成了契约。Echo 的产出——「用浏览器遥控 FT-710,用一根 USB 线替代 SCU-LAN10,不加任何硬件」——直接驱动 SDD V1.0:全部 15 章与第一个提交同时写就。
  • 事实路线图是现场写的。16 天里 12 个 git 可查的 FDE 循环,每个都只由一个真实现场信号触发——16 kHz TX 爆裂声 → AD-011;约 20 Hz 的频率漂移 → DN; 裁决与约束;滤波器竞态 → 丢弃过期读。
  • 事实知识变成了机器。22 条机器可读约束(block / warn / info)从真实事故中提炼,编辑前检查(退出码 2);测试套件 262 → 1,299;release_check 持离线发布规则;PROJECT_MAP 为每个主题指定唯一真相源。
  • 推断支持变成了传感网络。三天里分四片建成:诊断包 → 分诊 + 答复页 → 更新检查 → 看板 + 无人值守实施。分类即决策;回路现在止于一处已审查分支,永不落在 main
  • 推断新需求与旧需求走同一扇门。录音静音(现场日志)、macOS「已损坏」与无声 RX(发布周)、PTT 释放延迟(现场体感)——每一个都变成了 SDD 行、守卫、发布和一张公开答复卡。
  • 论点方法可迁移,数字不可。定义先于实现、每次事故变成规则、先给现场装仪器、把「做」自动化而永不自动化「裁决」。§07 把它落成八条规则和三级采纳阶梯。

一个项目,链条断掉的两种方式

业务意图 → 需求 → 设计 → 现场 → 支持 → 新需求。每个项目都走这条链;区别在于断在哪里。

链条在幻灯片上是线性的,在现实里是回环的:需求只有在现场信号确认它重要之后才算成立,而现场信号只有在能改变设计时才有用。回环闭上,项目就复利;断掉则有两种典型形态。

愿景从不落地

业务话在路演里反复出现,需求却漂成一张功能清单。没人能指出「这句话在哪个产物里变成了可度量的义务」——于是每次争论都从观点重新开始。

现场从不回来

迭代很快,但现场教会的东西留在聊天记录和维护者的记忆里。同一个事故每季度被重新发现;版本号能告诉你发布了什么,却告诉不了它学到了什么。

MRRC Modern 的设计就是让这两种失败变得昂贵。它始于 2026-07-06——一个单电台(FT-710 走自己的 USB 线)实验的初始提交;到 2026-09-19,它已经产出 435 个提交、67 行 SDD 版本记录、22 条架构决策、22 条机器可读约束、37 条 changelog 记录,从 v1.0.0 走到横跨 Windows、macOS 与树莓派镜像的 v1.18.1——以及一条带答复页、看板和无人值守实施者的公开支持链。数字不是重点;下面是产出这些数字的装置。

作者论点

三板斧是一个系统。SDD 把判断沉淀成契约;Harness 让「忘记」在结构上不可能;FDE 持续让现场修正两者。抽掉任何一根,你都能说出随之而来的失败——没人遵守的漂亮文档、没有记忆的快速回路,或者一个已经不再倾听的契约。这也是总纲页的论点——执行可被委托,判断不能——在整条生命周期上的展开;那一页承诺的可证伪链条(现场事故 → SDD 裁决 → 约束 → 阻止重演)正是 §02–§05 走过的路(总纲)。

活得比对话更久的契约

三层 Harness 的活体设计文档:业务(为什么)、技术(怎么做)、产品(交付什么)。

2.1 · 从一句话到可度量的需求

项目的 Echo 阶段在任何架构之前只产出了一段话:

事实 · 驱动 SDD V1.0 的 Demo 定义

「用浏览器遥控 FT-710,用一根 USB 线替代 SCU-LAN10,不加任何硬件。实时瀑布(FT4222 SPI)。双向 48 kHz Opus 音频。移动优先 UI。四条 WebSocket 通道(控制、音频 RX、音频 TX、频谱)。」

Echo 的输入都是具体的:作者本人在多设备上的日常操作;对已有开源 FT-710 遥控项目(F4HTB)的评估(没有 SPI 频谱、没有双向 Opus、没有移动 UI);对照 Hamlib 记录的差距;以及那个不公平优势——从更早的 MRRC 项目继承的可复用模式(PTT 安全、多实例架构、SDD 模板本身)。这段话变成了基线:全部 15 章一次性写就,其中包含一张带目标值与验证方法的编号 NFR 表。在这个项目里,需求写成「现场测试可以裁定的义务」:

需求目标验证方法
NFR-001 · RX 音频延迟(电台喇叭 → 浏览器喇叭)端到端 < 500 ms听感测试
NFR-002 · 控制响应局域网内 UI 指令确认 < 200 msWebSocket 往返观测
NFR-008 · PTT 响应触摸到 TX 指令 < 100 ms时序日志
SC8 · 成功标准PTT 不能卡死安全架构 + 看门狗测试

要复制的模式很小、很严格:没有目标值和验证方法的需求只是意见。「PTT 不能卡死」之所以成立,是因为背后有一章(SDD 15,PTT 安全架构)和一个看门狗;它不是口号。

2.2 · 从需求到设计决策

设计决策在这里是编号的、带日期的、可追溯的:AD-001 … AD-022 一张决策登记表。每一条背后都有 Echo,其中几条背后是真实事故:

决策定下了什么被什么逼出来
AD-002FT-710 路径直接走 pyserial CAT,不用 HamlibHamlib 差距分析;线内协议更简单,还能拿到真频谱
AD-01148 kHz 编解码域 + 帧对齐的 44.1 kHz 桥现场信号:「16 kHz TX 爆裂声」
AD-014永不轮询 DN;——它每次调用把 VFO 向下步进约 20 Hz一次真实频率漂移事故
AD-017录音写入器按会话幂等重建;队列满丢最旧块2026-09-12 的静音 MP3 现场报告
AD-019未验证机型拒绝发射,除非显式放开新机型比硬件验收来得更快
AD-021 / AD-022支持诊断包、分诊,以及可自证的更新通道支持本身:第一轮邮件永远在要证据

2.3 · 版本行是学习的最小单位

SDD 的版本历史不是 changelog 的复制品,它是项目的学习台账。一次变更一段记录:现场信号或需求、证据、决策、仍未闭合的边界。75 天 67 行——有的只有一行修复,有的是一整条支持链。V2.45 行读起来像一份带结论的现场报告:静音录音的日志签名(开始后 6 ms 就出现 Recording writer is falling behind — dropping audio,22 秒里 950+ 次丢弃)、用 ffmpeg 测出的产物(-91 dB,即数字静音,时长正确)、根因、修复、三个回归测试,以及边界——「长时间连续录制与树莓派 SD 卡写入仍未验证」。

事实 · 没有自动升级

生命周期状态是显式的——plannedimplementedtestedbench-verifiedfield-verifiedreleaseddeferredknown-issue——而且没有任何状态会自己前进。一个已发布的子系统不能把成熟度借给别的客户端或机型;未验证的 Icom 与 Yaesu 机型带着发射门禁发布,并且如实写明。

不需要被记住的约束

如果一条教训只活在文档里,它迟早被跳过。Harness 的职责是让「跳过」大声失败。

3.1 · 两层,一个目标:让失败可见

Harness 有外层(业务、技术、产品约束——即 SDD 的三组章节)与内层执行层(人 + 智能体、仓库、工具、测试 + 审查、部署、现场遥测)。本文关注内层最锋利的仪器:约束注册表。

3.2 · 约束注册表:22 条教训变成机器

.agents/skills/sdd-guardian/harness/constraints.json 保存着 22 条规则,从 SDD 与真实事故中提炼,每条带严重级、理由和匹配模式。智能体(或人)在会话开始时拿到它们;编辑前的检查会以退出码 2 阻断违规。注册表的来历才是重点——这些不是风格偏好:

约束严重级它固化了什么
cat-no-dnblockDN; 每次调用把活动 VFO 向下步进约 20 Hz——一次真实频率漂移事故,现在无法再被无意引入
cat-direct-serial-ioblock串口 I/O 只允许出现在电台控制器内部——防止 asyncio 传输层被到处直接触碰
audio-16k-rateblock音频速率是 44.1 kHz 设备 / 48 kHz 编解码——音频路径永不出现 16 kHz(爆裂声事故);16 kHz 只是存储域
poll-stale-guardinfo轮询循环丢弃过期的在途读(「滤波器切换只有 50% 生效」竞态)
ptt-release-no-verifyinfoTX0 是即发即忘——释放时的校验循环正是 PTT 卡死的来源
password-constant-timewarn口令比较必须使用 hmac.compare_digest

转换规则:事故只有变成守卫才算修完。修复是链路第一环;SDD 里的裁决是第二环;阻断下一次尝试的机器可读约束是第三环。这也是注册表小而单调的原因——每一行都付过真实的代价。

3.3 · 门禁与唯一真相源

注册表周围还有整套装置:unittest 套件(7 月第 12 个循环时 262 项 → 9 月 FDE 看板提交时 1,299 项)、离线 release_check 规则(发布记录中报 29 项通过 / 0 失败)、保存每份产物版本/大小/SHA-256 的发布产物注册表,以及强制文档一致的测试——版本历史首行的 SDD 版本必须与 README、生成页和落地页一致。给人看的入口是 docs/PROJECT_MAP.md:它回答一个问题——「我改 X,必须同步哪些东西?」——每个主题一行:

主题唯一真相源由什么强制
应用版本CHANGELOG.md 顶部条目构建脚本解析它;release_check
SDD 版本SDD/14-version-history.md 首行test_sdd_docs_consistency.py
设计决策SDD/08-architecture-decisions.md(AD-001…AD-022)release_check 的 AD 索引 + 守护注册表
工程约束constraints.jsonsdd_context.py check——block 级违规退出码 2
发布产物release-artifacts.json(版本 / 大小 / SHA-256)release_check.py + 产物测试
推断 · Harness 是让委托安全的东西

只有仓库会拒绝,你才能把仓库交给智能体。注册表把项目的伤疤变成编辑前拦截——这与产品的发射门禁是同一设计观:权限有界、拒绝显式,且每次拒绝都注明来历。

Echo → Delta → Product,以及第四项实践

血统里的现场回路:到现实里去、证明风险最高的假设、把活下来的东西抽象出来——然后让支持链对产品自己跑同一个循环。

4.1 · 十二个循环,每个只由一个现场信号触发

第一个时代被记录成一张 git 可查的循环表。每一行是一个 Echo(现场信号)、一个 Delta(最高风险的证明)、一个 Product 产出(留下来了的抽象)。节选:

循环现场信号(Echo)被建造的东西(Delta → Product)
V1.0 · 7 月 6 日替代 SCU-LAN10:一根 USB 线、不加硬件一整天内写就全部 15 章 SDD 基线 + 7 个核心模块
V1.1 · 7 月 6 日「16 kHz TX 爆裂声」AD-011:48 kHz 编解码域 + 44.1 kHz 帧对齐桥
V1.2 · 7 月 8 日「频率漂移约 20 Hz;TX 表头空白」活动 VFO 跟踪、表头校准——以及后来成为 cat-no-dn 的裁决
V1.7 · 7 月 19 日「滤波器切换只有 50% 生效」每次轮询查询后丢弃过期读 + 3 个回归测试
V1.9 · 7 月 21 日「USB 重连后瀑布永远冻结」on_reconnected 钩子在重连后重跑频谱初始化
V2.1 · 7 月 22 日「PTT 键控了但没有声音 / 没有功率」TX 上行归属提升 + 每会话 TX 计数 + 5 个回归测试

四个模式让这个时代成立,且四者都可复制:一个循环 = 一个真实信号(不做投机功能);文档是并发的,不是回溯的(版本行随修复一起落地);风险最高的假设先做原型(先直连 CAT,再 48 kHz 管线,最后多客户端归属);从既有杠杆出发(从更早的项目继承六项资产,意味着第一个循环从「集成」开始,而不是从「发明」开始)。

4.2 · Product 意味着抽象——然后成为平台

Product 阶段是现场偏方变成资产的阶段:带长度前缀管道的独立频谱子进程(FT4222 的逐字节重同步已被证明不可能)、帧对齐的 44.1↔48 kHz 重采样、带命令跳过与过期读丢弃的自适应轮询、带标签的双编解码音频帧、逐次 PTT 的 TX 会话日志。决定性的抽象出现在八月:可插拔电台后端。服务端、轮询与状态层变得与电台无关;机型差异退到 profile 之后。这一步把 FT-710 的解法变成了平台——共享 CI-V 核心的五个 Icom 机型、共享 ASCII-CAT 核心的四个 Yaesu 机型,全部建立在已现场验证的 FT-710 传输层上,并且在硬件验收追上之前带着发射门禁发布。

4.3 · 第四项实践:支持回路

第四项回路实践把同样的 Echo→Delta→Product 纪律对准产品自己的支持链。它分四片、三天上线:(1)脱敏诊断包;(2)自动分诊与公开答复页;(3)带生成式清单的只读更新检查;(4)公开现场看板与无人值守实施者。组织它的规则是:分类即决策——noise 以「已答复」闭环,bug 自动排期进实施队列,feature 等待操作员,未答复的上报永不闭环。实施者从干净的 main 工作区单飞运行:只读模型提出 unified-diff JSON 契约;护栏拒绝保护路径与超大补丁;补丁只落在隔离的 fde/<id> 分支,且只有整套测试全绿才提交。main 永不被无人值守进程触碰;合入始终是人。机制写在机制分册 · 现场回路;它第一周的日志在《支持回路》

两条线程,每一站

现场事故只有能看着它走完全程才有意思。两个真实案例:从症状到已发布修复,再到公开答复。

5.1 · 线程 A ——「录音是静音的,但文件看起来完全正常」

现场(2026-09-12 22:26)。一次真实会话产出的 MP3 时长和大小都正确——却没有声音。日志带着签名:开始后仅 6 ms 就出现 Recording writer is falling behind — dropping audio,21.9 秒里 950+ 次丢弃,最后以 Recording queue full on stop — finishing directly 收尾。ffmpeg 测得产物为 -91 dB:数字静音。

设计(V2.44/V2.45)。写入器任务按设计是「每会话一次性」的——它消费 stop 哨兵后结束——但任务只在进程启动时创建过一次。第一次录音之后,后续每个会话都没有消费者;有界队列在毫秒级填满,stop() 再按时间戳把整条时间轴补成静音。修复:幂等的 _ensure_rec_writer(),在每次音频块入队、stop 入队和每次 REC 时重建;写入器异常死亡时打 WARNING(这正是事故最缺的可观测性);队列满时丢最旧块而不是同步收尾。三个回归测试;随 v1.15.0 发布。

支持(2026-09-18)。一份新的诊断包带着同样的签名到达——但所有命中日志行都落在 9 月 12 日,修复之前。分诊模型被要求在手写结论前先读项目自己的事故史,于是交叉引用了 CHANGELOG 与 SDD 版本行,指向当代码里的 server.py:348,并拒绝两个顺手答案(「早已修复」/「回归了!」):改为执行一套协议——录一段新 QSO 再回报。后续诊断包以 9 月 19 日的健康日志关闭了它。公开答复卡写明推理与原始证据行。

推断 · 版本行是线程沉淀的地方

没有这一行,第二份上报从零开始,维护者要重新推导两周前的诊断。有了它,分诊就是一次查找:签名 → 历史 → 结论 → 协议。SDD 版本行不是文档债;它是这个项目的免疫记忆。

5.2 · 线程 B ——「应用已损坏,无法打开」

现场(发布周,v1.18.0)。第一个 macOS 安装包发布;用户遇到一个右键也绕不过去的 Gatekeeper 对话框,以及第二个更安静的失败——RX 没声音,而日志让他们「检查电台」。

设计(V2.54,随 v1.18.1 发布)。两个根因,都是静默失败。签名步骤自第一个 macOS 构建起就一直在静默失败(构建脚本把它当非致命警告),于是每个包其实都没签上名;修复按 codesign 要求重排资源树布局,并让签名失败中止构建。无声 RX 是一个缺失的权限键:bundle 从未申请麦克风权限,macOS 于是正常打开采集流、往里填零,且不报任何错误;修复声明该键(构建期断言)并把警告指向系统设置而不是电台。验证到产物级:codesign --verify 有效、指定要求满足、spctl 只抱怨缺 Developer ID,系统权限日志从 Refusing… 变为 AUTHREQ_PROMPTING。文档替下一个用户先回答:macOS 指南解释两种对话框,落地页直说 v1.18.1 之前的下载必须换包。

5.3 · 线程 C ——「PTT 释放体感延迟」(一段话的案例)

一个现场体感(「松开后 RX 回来太慢」)被日志分解为约 470–870 ms:262–300 ms 的采集重开、约 70–130 ms 的排空、轮询次序,以及客户端抖动缓冲里积压的约 120 ms 静音 Opus 帧。V2.55 发了三个独立修复——释放排空与 unkey 之前跳过 TX 状态轮询、键控期间不再广播静音 RX 帧、记住上次成功的采集设备以跳过重开时的全量枚举——重开后来实测从约 270 ms 降到约 200 ms。边界照旧写下来:端到端改善仍需真机验收。

事实 · 三条线程共享的东西

同样六站:现场信号 → 日志证据 → SDD 行(根因 + 决策 + 边界)→ 回归测试 → 发布 → 公开答复卡。差别只在节拍——录音线程从信号到修复用了一天,到后续闭环用了一周;macOS 线程用了一个晚上;延迟线程用了两天。节拍由最慢的一站决定,而那一站通常是证据,不是代码。

这些数字能支撑什么、不能支撑什么

计数是过程证据。它们说明装置在运转;仅凭它们,不晋升任何产品主张。

观察数值(截至 2026-09-19)支撑什么
仓库跨度2026-07-06 → 2026-09-19,435 个提交单条产品线上持续交付
SDD15 章;67 行版本记录;AD-001…AD-022;36 条 NFR;SC1–SC9需求与决策是可追溯的工件,不是记忆
Harness22 条约束(block/warn/info);编辑前门禁退出码 2;release_check 29 项;产物注册表带 SHA-256教训被强制执行,而不是归档
测试第 12 个循环 262 项(7 月 22 日)→ 1,299 项(9 月 19 日)每次事故都买回了回归覆盖
发布37 条 changelog 记录;v1.0.0 → v1.18.1;Windows、macOS、rpi64发布是常规动作;回退目标真实存在
电台面3 个协议族、10 个机型键平台抽象经住了与其他硬件的接触
支持链3 天 4 片;Modern 已发布 2 条答复;看板在线现场反馈有一条流水线,而不是一个邮箱
事实 · 诚实的边界(写下来,不抹平)

未验证的 Icom 与 Yaesu 机型在硬件验收前拒绝发射;Yaesu 系列在此阶段只收不发;更新通道目前是只读的(下载/安装是后续分片);FDE 看板的第一条真实 bug 还在等它的第一次真实排期,第一份无人值守补丁还在等第一次人工合入;基于摘要的分诊读的是摘要,不是完整环境。跨项目可比的数字——包括当前维护中的测试计数——只在证据台账一处。

什么可以迁移,按什么顺序建

从实践中提炼的八条规则——然后是三级采纳阶梯,以及每一级的陷阱。

  1. 契约与第一个提交一起写

    一段愿景文字,然后是全部章节——包括带目标值与验证方法的 NFR 表。第一个提交是拥有契约最便宜的时刻;之后每一个时刻都是一次返工。

  2. 给每条需求配一个目标和一个方法

    「< 500 ms,听感测试」是需求;「应该快一点」是偏好。「PTT 不能卡死」这样的成功标准,背后要有安全架构章节和看门狗。

  3. 让现场选路线图

    一个循环 = 一个真实信号。不做投机功能。版本行——信号、证据、决策、边界——就是路线图日志,67 行构成的论证是任何功能清单给不出的。

  4. 把每次事故变成规则,而不是记忆

    修复是第一环,裁决是第二环,下一次编辑前就会被检查的机器可读约束是第三环——只有到这一步,事故才算永久修好。注册表要小;每一行都该付过真实代价。

  5. 先给现场装仪器,再扩功能

    脱敏诊断包把访谈变成测量。在需要数据之前先造传感器;这里的支持链分四片上线,传感器排第一。

  6. 让分类即决策

    分类问题只回答一次,然后让它调度:环境噪声闭环、缺陷排期、feature 等人类。未答复的上报永不闭环——「闭环」是一个承诺。

  7. 把「做」自动化,永不自动化「裁决」

    无人值守实施只有被围住才安全:只读提议、护栏、隔离分支、全量测试门禁、main 不被触碰、人工合入。两本账分开记——过程证据永不晋升产品主张。总纲把保留的一半命名为——意图、边界、裁决;理由是问责,而不是任务大小(分工)。

  8. 把边界写下来

    生命周期状态之间没有自动升级;未验证硬件拒绝发射;更新通道自述只读;已知问题保持可见。写下来的边界是信任资产;被用户发现的边界是事故。

采纳阶梯

阶段建什么完成判据陷阱
1 · 文档时代SDD 基线(业务/技术/产品章节)+ 每次变更一行版本记录 + changelog任何已发布行为,你都能说出它背后的需求与决策文档写完就被悄悄放弃——版本行必须随修复落地,否则永远是「回头补」
2 · 契约时代从真实事故提炼的约束注册表 + 编辑前检查 + 测试/发布规则 + 真相源地图已知失败无法再被引入而不触发红灯收集一堆没人运行的规则——强制执行(编辑路径上的退出码)才是全部意义
3 · 现场回路时代诊断包 → 分诊 → 答复 → 看板 → 无人值守实施,合入归人用户症状在无人醒着时变成分支上经测试的补丁,而裁决仍归人把手伸过护栏——回路必须失败即关闭,否则它就变成一段无人审查的 main 历史
作者论点 · 该复制什么

复制纪律,不要复制数字。没有第二个项目需要这个项目的测试数或发布节拍;可迁移的是形状:一个沉淀判断的契约、一套拒绝已知错误的装置、一个被允许修正两者的现场回路——以及一个始终握着「什么算完成」裁决权的人。

一句话,三板斧

一句愿景变成契约;契约变成带目标的需求;需求变成决策;决策撞上现实并被修正;修正变成守卫;守卫让下一次委托变得安全;支持链闭上圆环——让用户把现实报回来,附带答复、一块看板,以及一份等着人点头的、经测试的补丁。这就是整段旅程。三板斧是一句话:

一句话纪律

让判断沉淀成契约,让机器拒绝你已经付过代价的错误,让现场——经由用户——修正两者。裁决,始终归人。

深入机制:Harness、Loop 与 Living SDD · 现场回路 · 智能体工程。本案例背后的日志:七十亿 token 的全程拆解 · 支持回路。线上入口:MRRC Modern 站点答复页FDE 看板