Skip to content

v0.6.23 — 「確かめられなかった」を「変わっていない」と言っていました

Choose a tag to compare

@Driedsandwich Driedsandwich released this 15 Aug 21:22
· 11 commits to main since this release

v0.6.23 — 「確かめられなかった」を「変わっていない」と言っていました

⚠ 先に読むところ

1. 根は 1 つで、逃げ道が 6 本ありました。監査が当てたのは 4 本です

版を上げるかどうかを決める判定器(scripts/schemaLanguageDiff.mjs)が、
$ref を展開できないときに黙って元の節を返していました。

諦めた側と、確かめて同じだった側の出力が同じです。
新旧そろって同じ位置で諦めるので写しが一致し、「変わっていない」と読めてしまいます。

逃げ道                                        反例をくれた人
E1 展開の深さ上限を超えた                        **こちら**(数え直して発見)
E2 循環を見つけた                               **こちら**(同上)
E3 $ref を解決できない                          監査(55 段の連鎖)
E4 参照先が object でない(boolean schema)      監査
E5 RFC 6901 の ~0 / ~1 を復号しない              監査
E6 tuple 形式の items を素の文字列でしか比べない    監査

監査の反例 4 件は、こちらの環境でそのまま再現しました(すべて HOLD / exit 0)。
包含関係の真値も ajv で独立に出しています。

tuple_items_ref  旧invalid→新valid: [[null]]
escaped_ref      旧invalid→新valid: [null]
deep_ref         旧invalid→新valid: [null]
boolean_ref      旧invalid→新valid: [null,"x","a",true,…]  旧valid→新invalid: [1,0]
陰性対照(同一 schema 同士)  広がり証人 0 件

すべて危険側です——言語が広がっているのに「据え置き可」。
受け手は旧 schema を pin したままになり、新しい版が null を出した瞬間に検証が落ちます。

**残り 2 本は数え直して見つけました。**直す前に
「諦めたのに何も言っていない箇所」を数えたら 6 本ありました。
指摘の 4 本だけ直していたら、次の監査で同じものが返ってきます。

2. 一律に「変わった」へ倒すのは間違いでした

素直な直し方は「諦めたら BUMP」ですが、それだと
**深いだけの schema を自分自身と比べても落ちます。**条文が使えなくなります。

諦めが起きなかった  → 写しの一致がそのまま「同じ」の証拠
諦めが起きた        → root どうしを丸ごと比べる
                      まったく同じなら「同じ」/違えば決められない側へ倒す

解決できるものは解決します(E5 の復号・E4 の boolean 展開・E6 の位置ごと比較)。
諦めを減らすほど、粗い比較へ落ちる回数が減って誤検出も減ります。

3. 変異対照で 3 本が「外しても落ちない行」でした

回帰試験を先に入れ(条文 ①-c7 〜 ①-c10)、修正前に 5 件落ちることを実測しました。
そのあと直した箇所を 1 つずつ戻したところ、3 本は消しても全件通りました。

最初の変異対照(全 93 件)
  E1 深さ上限        **0 件 落ちる**
  E2 循環            **0 件 落ちる**
  E3 解決できない      1 件 落ちる
  E4 boolean 展開     1 件 落ちる
  E5 RFC 6901 復号   **0 件 落ちる**
  E6 tuple items     1 件 落ちる

消しても落ちない行は、入れた証拠がありません。
そこでその経路に実害があるかを先に測ってから試験を足しました。

  • E1 は、監査の反例(55 段)が deref の反復上限(E3)を先に踏むためでした。
    oneOf の枝の中で 60 段ネストし、その奥に $ref を置くと E1 を踏みます。
    ajv で「奥が null の値」が旧 invalid → 新 valid になることを確かめました(条文 ①-c11)。
  • E2 は反例をもらっていません。循環がある schema は保守側へ倒すという
    振る舞いそのものを固定しました(条文 ①-c12)。
  • E5 は §4 のとおりです。

足したあと、7 か所すべてが変異で落ちます(全 99 件)。

4. E5(RFC 6901 の復号)は、安全のためでなく精度のためです

#/definitions/a~1bdefinitions["a/b"] を指します(~1 = /~0 = ~)。
復号していませんでした。

**復号しなくても危険側の誤りは出ません。**解決に失敗して諦めるので、
§2 の root 比較へ落ちて BUMP になります。だから変異で消しても何も落ちません。

害は別のところに出ます。escaped $ref を使う schema は、参照先が変わっていなくても
無関係な definition が動くたびに BUMP になります。

復号して初めて「変わっていない」と言えるようになります。

その精度を固定する試験を別に書きました(条文 ①-c8 ⑤)。

安全側に倒れる修正は、変異対照では守れません。
「消しても落ちない」を見たときに、安全のためか精度のためかを分ける必要があります。

5. 試験の側も直しました。この 4 形を一度も作っていませんでした

前の版(v0.6.22)でも同じことをしています。判定器を直しても、
その形を作れない試験は自力で見つけられません。

                                v0.6.22   v0.6.23
tuple 形式の items                 0 件      12 件
指す先が boolean schema の $ref     0 件       9 件
~0 / ~1 を含む $ref                0 件      20 件
候補値のうち「要素が null の配列」     0 件      43 件

補強したうえで v0.6.22 の判定器へ当てると、自力で反例を出しました。

seed=266 … ではなく seed=299 op=type-widen verdict=HOLD 証人=[0,null]
  旧 items: [{type:'integer'}, {$ref:'#/definitions/d0', description:'ref sibling'}]
  新 同じ items                     d1.type = 'array' → ['array','null']

tuple 形式の 2 番目の枝が $ref という、監査の反例と同じ形です。
手書き fixture を一切使わない状態での実測で、種は 1〜300 で固定してあります。

6. 現行 schema にこの 4 形は 1 件もありません

tuple 形式の items          0 件
指す先が boolean の $ref     0 件
~0 / ~1 を含む $ref         0 件
循環する $ref               0 件
$ref 連鎖の最長             1 段(上限は 50)

この版の公開物への影響はありません。直したのは将来の版を決める道具です。

版数を上げていない根拠(すべて実測)

toolVersion 20 / CLI 結果契約 schemaVersion 2 / profile schemaVersion 3 のままです。

判定器は配布物か        **違う**(releaseAssets に 0 回。対照: 検算ツールは 1 回)
配る検算ツール          v0.6.21・v0.6.22 と **byte 一致**
                        da1e5558d0a4cdf38c23274c4486bbbbf94cc79bf02a28a49cd01765af325401
実 schema での判定       **197 対中 0 件が変化**(現行 26 schema × 直近 8 tag)
CLI 出力の 10 経路       基準と byte 一致
profile のモデル数値     **0 件が変化**(葉 2267 / 3130 のうち、動いたのは由来 10 件だけ)

⚠️ 「何も変わらない」ではありません。§1 の 6 つの形では判定が HOLD → BUMP へ変わります。
**それが今回直した欠陥です。**現行 schema にその形が無いので、
実 schema での判定が変わらなかっただけです。

まだやっていないこと

  • **実測が 1 件も来ていません。**接点位置は全部仮定のままで、
    導通測定も音響測定もしていません(verifiedPhysicalfalse)。
  • annotated tag への署名(v0.6.23 も署名なしです)。
  • main の branch protection / ruleset。監査の新規 P2 ですが、
    リポジトリ設定の変更なので手を付けていません。
  • Half-Plug 下流の release-lock v7 / mechanism-binding v7(別 repo)。
  • nanoid 3.3.16 の更新。踏む経路が無いと測ったまま据え置きです(SECURITY.md)。
  • BusyBox を oracle に足すこと(開発機にも CI にもありません)。
  • property-based 試験が見ているのは、いまも 1 つの性質だけです。
    v0.6.22 でその限界がそのまま当たり、この版でも同じ形で当たりました。
    補強しましたが、次に見逃す形も、いまは 0 件に見えます。

数えたもの

v0.6.22 v0.6.23
単体テスト 1433 1457(skip 0 / 失敗 0)
配る asset 31 変わらず 31
schema 26 変わらず 26
検算ツール toolVersion 20 変わらず 20(byte 一致)
CLI 結果契約 schemaVersion 2 変わらず 2
profile 契約 schemaVersion 3 変わらず 3
止め方の名前(catalog) 82 種類 変わらず 82
出力の基準(経路) 10 変わらず 10(全件 byte 一致)
検証対象(validate:profiles 14 14
記録された入力 32 32
区間 / event TRS 23/32・TRRS 30/36 変わらず

変わっていないもの

  • **モデルの数値。**接点位置も走査結果も同じです。
  • verifiedPhysicalfalse。実測記録は依然 0 件です。
  • IV02813.3–13.52 mm / ASSUMPTION / ground-open-differential /
    RETURN_OPEN_L_AND_R_ON_DISTINCT_CONDUCTORS のままです。
  • toolVersion 20 / CLI 結果契約 schemaVersion 2 / profile schemaVersion 3 / catalog 82 種類。
  • 止め方の名前の一覧・archivePolicy・status・終了コードの意味。
  • 公開済みの release 本文と asset は書き換えていません。
    過去 844 件(v0.1.0〜v0.6.22)が byte 一致であることを、再生成の段ごとに実測しました
    (対照として基準を 1 行壊すと 843 件へ落ちます)。