<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>工程正确性 - category - 沐木</title><link>https://oldletter.cn/categories/%E5%B7%A5%E7%A8%8B%E6%AD%A3%E7%A1%AE%E6%80%A7/</link><description>工程正确性 - category - 沐木</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 11 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://oldletter.cn/categories/%E5%B7%A5%E7%A8%8B%E6%AD%A3%E7%A1%AE%E6%80%A7/" rel="self" type="application/rss+xml"/><item><title>工程正确性（二）：Java、TypeScript 与 Native 的密码互操作</title><link>https://oldletter.cn/posts/engineering-correctness-02-crypto-interoperability/</link><pubDate>Sat, 11 Jul 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/engineering-correctness-02-crypto-interoperability/</guid><description><![CDATA[<blockquote>
<p>系列导航：<a href="/posts/engineering-correctness-01-green-tests-can-be-wrong/" rel="">11</a> | <a href="/posts/engineering-correctness-02-crypto-interoperability/" rel="">12</a> | <a href="/posts/engineering-correctness-03-database-migrations/" rel="">13</a> | <a href="/posts/engineering-correctness-04-coordinate-precision/" rel="">14</a></p></blockquote>
<p>Java、TypeScript 和 Native 各自完成“加密后能解密”，不能证明它们能互相解密。真正的差异经常出现在模式、填充、IV、Tag、Base64、ASN.1、签名输入和错误处理。GMKit 的跨语言验证把 <code>vectors/interop.json</code> 作为共享输入，再用双向矩阵检查每个实现的字节结果。</p>
<h2 id="向量文件是协议边界">向量文件是协议边界</h2>
<p>每条向量至少写明算法套件、密钥字节、明文字节、AAD、IV 或 nonce、密文、Tag、签名和编码方式。算法名不能只写 <code>AES</code>，要写模式和参数，例如 GCM 的 Tag 长度、CBC 的填充规则或椭圆曲线名称。JSON 的 UTF-8、字段顺序和 Base64 变体也属于协议。</p>
<div class="mermaid" id="id-2" data-mermaid-definition="Zmxvd2NoYXJ0IFRECiAgICBWW3ZlY3RvcnMvaW50ZXJvcC5qc29uXSAtLT4gSmF2YVtKYXZhIOWunueOsF0KICAgIFYgLS0&#43;IFRTW1R5cGVTY3JpcHQg5a6e546wXQogICAgViAtLT4gTmF0aXZlW05hdGl2ZSDlrp7njrBdCiAgICBKYXZhIC0tPiBNYXRyaXhb6Leo56uv55&#43;p6Zi1XQogICAgVFMgLS0&#43;IE1hdHJpeAogICAgTmF0aXZlIC0tPiBNYXRyaXgKICAgIE1hdHJpeCAtLT4gTmVnYXRpdmVb56&#43;h5pS55LiO5qC85byP6LSf5L6LXQogICAgTmVnYXRpdmUgLS0&#43;IEdhdGVbUGFyaXR5IOS4juWPkeW4g&#43;mXqOemgV0=">flowchart TD
    V[vectors/interop.json] --&gt; Java[Java 实现]
    V --&gt; TS[TypeScript 实现]
    V --&gt; Native[Native 实现]
    Java --&gt; Matrix[跨端矩阵]
    TS --&gt; Matrix
    Native --&gt; Matrix
    Matrix --&gt; Negative[篡改与格式负例]
    Negative --&gt; Gate[Parity 与发布门禁]</div><p>项目生成的向量只能证明实现之间采用了同一套错误或正确的规则，不能自动证明符合外部标准。标准向量、独立库结果和固定边界样本要在来源字段中区分。</p>
<h2 id="双向矩阵怎么列">双向矩阵怎么列</h2>
<p>以信封加密为例，至少覆盖：Java 生成，TypeScript 解封；TypeScript 生成，Java 解封；Native 生成，Java 解封；Java 生成，Native 解封。签名也做同样的方向矩阵。固定向量使用固定 nonce 便于复现，生产接口通过安全随机数生成器提供 nonce，测试入口和生产入口分离。</p>
<p>矩阵中记录运行时、密码库版本和提供者。依赖升级、Native 编译选项变化或浏览器基线变化都会触发完整矩阵。失败报告保存向量 ID、字段摘要和错误枚举，不保存生产密钥、完整明文或私密内容。</p>]]></description></item><item><title>工程正确性（三）：迁移登记、Checksum 与回滚边界</title><link>https://oldletter.cn/posts/engineering-correctness-03-database-migrations/</link><pubDate>Sat, 11 Jul 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/engineering-correctness-03-database-migrations/</guid><description><![CDATA[<blockquote>
<p>系列导航：<a href="/posts/engineering-correctness-01-green-tests-can-be-wrong/" rel="">11</a> | <a href="/posts/engineering-correctness-02-crypto-interoperability/" rel="">12</a> | <a href="/posts/engineering-correctness-03-database-migrations/" rel="">13</a> | <a href="/posts/engineering-correctness-04-coordinate-precision/" rel="">14</a></p></blockquote>
<p>迁移成功不等于数据库已经可用。空库安装验证的是完整历史，线上升级还要考虑旧应用并存、DDL 锁、回填速度和权限。一次服务发布曾在代码和容器准备完成后，生产账号执行迁移却因缺少 <code>ALTER</code> 和 <code>INDEX</code> 权限失败。这个问题不在 SQL 内容，属于迁移前置条件没有进入发布检查。</p>
<h2 id="登记表是事实来源">登记表是事实来源</h2>
<p>迁移记录至少包含版本、文件名、checksum、执行序号、状态、执行人、耗时和错误摘要。已执行文件禁止原地修改，修正使用新版本。启动时发现文件 checksum 与登记值不一致，应停止自动迁移并报警，不能为了让部署继续而覆盖登记表。</p>
<div class="mermaid" id="id-2" data-mermaid-definition="Zmxvd2NoYXJ0IFRECiAgICBzdWJncmFwaCBSZWdpc3RlclvnmbvorrBdCiAgICAgICAgZGlyZWN0aW9uIExSCiAgICAgICAgTVvov4Hnp7vmlofku7ZdIC0tPiBIYXNoW&#43;iuoeeulyBjaGVja3N1bV0gLS0&#43;IFJlZ2lzdHJ5W&#43;i/geenu&#43;eZu&#43;iusOihqF0KICAgIGVuZAogICAgc3ViZ3JhcGggTWlncmF0ZVvlhbzlrrnov4Hnp7tdCiAgICAgICAgZGlyZWN0aW9uIExSCiAgICAgICAgRXhwYW5kW0V4cGFuZCDlhbzlrrnnu5PmnoRdIC0tPiBCYWNrZmlsbFvmuLjmoIflm57loatdIC0tPiBSZWNvbmNpbGVb5YWo6YeP5a&#43;56LSmXQogICAgZW5kCiAgICBzdWJncmFwaCBTaHJpbmtb5YiH5o2i5LiO5pS257ypXQogICAgICAgIGRpcmVjdGlvbiBMUgogICAgICAgIFN3aXRjaFvor7vlhpnliIfmjaJdIC0tPiBDb250cmFjdFtDb250cmFjdCDliKDpmaTml6fnu5PmnoRdCiAgICBlbmQKICAgIFJlZ2lzdHJ5IC0tPiBFeHBhbmQKICAgIFJlY29uY2lsZSAtLT4gU3dpdGNoCiAgICBSZWNvbmNpbGUgLS0&#43;fOi2hemZkHwgUGF1c2Vb5pqC5YGc5LiO6KGl5YG/XQ==">flowchart TD
    subgraph Register[登记]
        direction LR
        M[迁移文件] --&gt; Hash[计算 checksum] --&gt; Registry[迁移登记表]
    end
    subgraph Migrate[兼容迁移]
        direction LR
        Expand[Expand 兼容结构] --&gt; Backfill[游标回填] --&gt; Reconcile[全量对账]
    end
    subgraph Shrink[切换与收缩]
        direction LR
        Switch[读写切换] --&gt; Contract[Contract 删除旧结构]
    end
    Registry --&gt; Expand
    Reconcile --&gt; Switch
    Reconcile --&gt;|超限| Pause[暂停与补偿]</div><p>登记表自身也要受保护。应用账号只读，迁移执行器拥有写入权限。手工 SQL 如果改变了生产结构，要补成正式迁移或登记为已应用版本，不能让测试环境和线上环境依靠“历史操作”维持一致。</p>
<h2 id="expandbackfillcontract">Expand、Backfill、Contract</h2>
<p>Expand 先添加可空字段、兼容索引或快照表，旧应用继续可用。应用升级后双写旧字段和新字段，后台使用稳定主键游标分批回填历史数据。对账完成后切换读路径，观察窗口结束，所有旧实例和离线任务退出，才进入 Contract。</p>
<p>回填条件带上新字段为空、源版本未变化等保护，防止覆盖在线写入。每批记录起止主键、扫描数、更新数、跳过数、失败数和校验摘要。暂停后从已提交游标继续，批次重试具备幂等条件，不能依赖 offset 扫描变化中的数据。</p>
<p>对账不只比较行数。字段拆分检查空值、范围、唯一性和重组结果，表迁移检查关联完整性，编码转换检查不可映射值。允许差异和处理结果写进发布记录，抽样人工核对只作为补充。</p>
<h2 id="回滚按阶段定义">回滚按阶段定义</h2>
<p>增加字段通常可以通过应用开关回退，读切换也可以切回旧路径。已经删除列或完成不可逆数据修正时，回滚依赖备份、归档表或补偿迁移，不能承诺“一键恢复”。发布单在执行前写清每个阶段的恢复动作、数据保留时间和负责人。</p>]]></description></item><item><title>工程正确性（四）：构建成功不等于交付物可用</title><link>https://oldletter.cn/posts/engineering-correctness-04-coordinate-precision/</link><pubDate>Sat, 11 Jul 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/engineering-correctness-04-coordinate-precision/</guid><description><![CDATA[<blockquote>
<p>系列导航：<a href="/posts/engineering-correctness-01-green-tests-can-be-wrong/" rel="">11</a> | <a href="/posts/engineering-correctness-02-crypto-interoperability/" rel="">12</a> | <a href="/posts/engineering-correctness-03-database-migrations/" rel="">13</a> | <a href="/posts/engineering-correctness-04-coordinate-precision/" rel="">14</a></p></blockquote>
<p>编译器显示成功，只能证明源码在当前环境生成了产物。用户安装的是 JAR、npm 包或容器，真正的验收对象是这些文件能否被目标运行时加载，Native 资源是否随包分发，文档示例是否使用了正确入口。发布链路如果只跑单元测试，问题会在上传后才出现。</p>
<p>GMKit 的发布整理说明了这条边界：Java 的 <code>gmkit-sm9</code> 需要把多平台 Native 运行时装入同一个 JAR，TypeScript 包还要检查 tarball 内容和生产依赖。源码测试通过并不能证明打出来的包包含这些资源。</p>
<div class="mermaid" id="id-2" data-mermaid-definition="Zmxvd2NoYXJ0IFRECiAgICBzdWJncmFwaCBCdWlsZFvmupDnoIHpqozor4HkuI7mnoTlu7pdCiAgICAgICAgZGlyZWN0aW9uIExSCiAgICAgICAgU291cmNlW&#43;a6kOeggeS4juWQkemHj10gLS0&#43;IFZlcmlmeVvmtYvor5XkuI4gcGFyaXR5XSAtLT4gUGFja2FnZVvmnoTlu7rliLblk4FdCiAgICBlbmQKICAgIHN1YmdyYXBoIEF1ZGl0W&#43;WItuWTgeWuoeiuoV0KICAgICAgICBkaXJlY3Rpb24gTFIKICAgICAgICBJbnNwZWN0W&#43;WMheWGheWuueS4jiBjaGVja3N1bV0gLS0&#43;IEluc3RhbGxb5bmy5YeA546v5aKD5a6J6KOFXSAtLT4gU21va2Vb6L&#43;Q6KGM5pe2IHNtb2tlXQogICAgZW5kCiAgICBzdWJncmFwaCBSZWxlYXNlW&#43;WPkeW4g10KICAgICAgICBkaXJlY3Rpb24gTFIKICAgICAgICBQdWJsaXNoW&#43;WPkeW4g&#43;S7k&#43;W6k10KICAgIGVuZAogICAgUGFja2FnZSAtLT4gSW5zcGVjdAogICAgU21va2UgLS0&#43;IFB1Ymxpc2gKICAgIEluc3BlY3QgLS0&#43;fOe8uui1hOa6kOaIluWkmuS9meaWh&#43;S7tnwgUmVqZWN0W&#43;mYu&#43;aWreWPkeW4g10=">flowchart TD
    subgraph Build[源码验证与构建]
        direction LR
        Source[源码与向量] --&gt; Verify[测试与 parity] --&gt; Package[构建制品]
    end
    subgraph Audit[制品审计]
        direction LR
        Inspect[包内容与 checksum] --&gt; Install[干净环境安装] --&gt; Smoke[运行时 smoke]
    end
    subgraph Release[发布]
        direction LR
        Publish[发布仓库]
    end
    Package --&gt; Inspect
    Smoke --&gt; Publish
    Inspect --&gt;|缺资源或多余文件| Reject[阻断发布]</div><h2 id="先确定交付清单">先确定交付清单</h2>
<p>Java 发布清单写明 group、artifact、版本、依赖、服务文件和 Native 路径。<code>gmkit-sm9</code> 的 JAR 中应有 <code>META-INF/gmkit/sm9-native.properties</code>、各平台运行时和摘要文件，加载逻辑按当前平台选择资源并校验 checksum。Native 库缺失时启动要给出明确错误，不能回退到一个“未实现但返回成功”的空实现。</p>]]></description></item><item><title>工程正确性（一）：测试全绿仍可能违反规格</title><link>https://oldletter.cn/posts/engineering-correctness-01-green-tests-can-be-wrong/</link><pubDate>Sat, 11 Jul 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/engineering-correctness-01-green-tests-can-be-wrong/</guid><description><![CDATA[<blockquote>
<p>系列导航：<a href="/posts/engineering-correctness-01-green-tests-can-be-wrong/" rel="">11</a> | <a href="/posts/engineering-correctness-02-crypto-interoperability/" rel="">12</a> | <a href="/posts/engineering-correctness-03-database-migrations/" rel="">13</a> | <a href="/posts/engineering-correctness-04-coordinate-precision/" rel="">14</a></p></blockquote>
<p>有一次工具链模块的 Maven 测试达到 110 个通过，构建也显示成功。随后按规格逐项检查，仍发现三个问题：启动 Java 进程时参数被拼成一个字符串，删除镜像请求带了过大的 bearer scope，注册表探测把 <code>401</code> 当成服务可用。测试覆盖了代码路径，却没有覆盖协议的精确定义。</p>
<p>这类问题很难靠继续堆单元测试解决。测试断言、实现和假服务都来自同一份理解时，三者会稳定地一起通过。正确性需要把规格、实现、独立参考和运行行为放在同一张检查表中。</p>
<div class="mermaid" id="id-2" data-mermaid-definition="Zmxvd2NoYXJ0IFRECiAgICBTcGVjW&#43;inhOagvOS4juWNj&#43;iurl0gLS0&#43;IENvbnRyYWN0W&#43;Wlkee6pua4heWNlV0KICAgIENvbnRyYWN0IC0tPiBVbml0W&#43;WNleWFg&#43;a1i&#43;ivlV0KICAgIENvbnRyYWN0IC0tPiBJbnRlZ3JhdGlvblvnnJ/lrp7mnI3liqHlpZHnuqbmtYvor5VdCiAgICBDb250cmFjdCAtLT4gUmV2aWV3W&#43;S6uuW3pei&#43;ueeVjOWuoeafpV0KICAgIFVuaXQgLS0&#43;IEV2aWRlbmNlW&#43;W3ruW8guS4juivgeaNruWMhV0KICAgIEludGVncmF0aW9uIC0tPiBFdmlkZW5jZQogICAgUmV2aWV3IC0tPiBFdmlkZW5jZQogICAgRXZpZGVuY2UgLS0&#43;IEdhdGVb5Y&#43;R5biD6Zeo56aBXQ==">flowchart TD
    Spec[规格与协议] --&gt; Contract[契约清单]
    Contract --&gt; Unit[单元测试]
    Contract --&gt; Integration[真实服务契约测试]
    Contract --&gt; Review[人工边界审查]
    Unit --&gt; Evidence[差异与证据包]
    Integration --&gt; Evidence
    Review --&gt; Evidence
    Evidence --&gt; Gate[发布门禁]</div><h2 id="绿灯的含义要写清">绿灯的含义要写清</h2>
<p>单元测试通过表示输入和断言范围内的函数行为符合预期。它不自动证明参数序列化符合命令行语义，不证明权限范围足够窄，也不证明第三方服务对状态码的解释正确。构建成功只说明编译、打包和被启用的测试任务完成，项目默认跳过测试时尤其要显式检查 <code>-DskipTests=false</code>。</p>
<p>每个关键规则写成可观察的证据：命令行参数保存为参数数组，删除操作的 scope 断言为 <code>pull,push</code> 以外的权限被拒绝，注册表探测把 <code>401</code> 分类为“服务响应但需要认证”，而非健康。断言越接近协议字段，错误越容易被定位。</p>
<h2 id="从规格拆检查项">从规格拆检查项</h2>
<p>先把规格拆成输入、输出、状态码、权限、超时和副作用。实现审查逐项标记证据来源：标准测试向量、第三方服务响应、独立实现、日志或运行截图。没有证据的项保持未验证，不用“已有测试”代替。</p>
<p>接口测试使用真实 HTTP 或接近真实的契约服务器，fake 只模拟网络异常和固定响应。fake 必须拒绝真实服务会拒绝的输入，接受范围过宽会掩盖参数错误。对 CLI、JNI、浏览器和数据库边界，至少准备一组字节或命令行级别的断言，不能只测高层对象。</p>
<h2 id="性质和变形测试">性质和变形测试</h2>
<p>算法或数据处理难以枚举期望值时，加入性质测试：排序保留元素多重集合，金额拆分总和守恒，状态转换不跳过必需阶段，签名篡改后验签失败。变形测试对输入做允许的变换，再检查输出关系，能覆盖示例测试没有触及的组合。</p>
<p>随机测试保存固定反例和生成器版本。只保存随机种子会因生成器改变而无法重现。失败样本缩减后进入回归集，回归集再由人工确认业务含义。</p>
<h2 id="代码通过后的协议复核">代码通过后的协议复核</h2>
<p>复核要跳出实现本身，逐项对照规格检查四个问题：</p>
<ol>
<li>参数是否按对方解析方式传递，尤其是字符串、数组和空值。</li>
<li>权限是否只申请当前操作必需的范围，错误码是否被正确分类。</li>
<li>成功响应是否真的产生了预期副作用，失败是否留下部分状态。</li>
<li>文档、测试、运行配置和打包产物是否描述同一行为。</li>
</ol>
<p>这一步发现的差异不应直接归类为“测试不稳定”。如果规格修正了预期，保留差异报告、旧行为、变更原因和迁移影响。零差异不是目标，未解释的差异才是发布阻断项。</p>]]></description></item></channel></rss>