Skip to content

v0.6.21 — 判定器の欠陥を自分で見つけて直しました

Choose a tag to compare

@Driedsandwich Driedsandwich released this 15 Aug 09:33
· 28 commits to main since this release

v0.6.21 — 判定器の欠陥を、自分で見つけて直しました

⚠ 先に読むところ

1. 外部監査ではなく、自分の property-based 試験が見つけました

版を上げるかどうかを決める判定器(scripts/schemaLanguageDiff.mjs)に、
危険側の誤判定がありました。これまでの 4 件はすべて外部監査の指摘でしたが、
これは 5 件目で、初めてこちらの試験が先に見つけたものです。

ランダム生成した schema 対 961 件 → 危険側 4 件

計画(docs/V060_PLAN_20260805.md §3)が [AI]
「小さな schema をランダム生成し、ajv の真値と判定器を突き合わせ続ける」と
約束したまま、10 日間そのまま落ちていた分です。
入れた 1 回目で反例が出ました。

2. 危険側でした。受け手が拒む向きの誤りです

旧  oneOf: [{type:'string'}, {$ref:'#/definitions/d0'}]  definitions.d0.type = 'boolean'
新  同じ oneOf                                            definitions.d0.type = ['boolean','null']

ajv   null が旧 invalid → 新 valid  = **言語が広がっている**
判定  HOLD(据え置き可)             ← **上げるべきなのに上げない**

oneOf は「ちょうど 1 枝が一致」なので枝の言語について単調でなく、枝ごとの比較ができません。
そこで「変わったか」を JSON.stringify(o.oneOf) !== JSON.stringify(n.oneOf) で見ていました。
枝が $ref なら、参照先が変わっても枝の文字列は同じです。

受け手への影響。 版が据え置かれると、受け手は旧 schema を pin したままになります。
新しい版が null を出した瞬間、受け手の検証が落ちます。
「壊れたのはいつか」ではなく「使い始めたのはいつか」で止まる——
この判定器が最初から避けようとしていた形そのものです。

root が $ref の場合は入口で辿るので前から正しくoneOf の枝だけが素通りしていました。

3. 回帰テストを先に入れてから直しました

v0.5.1 / v0.5.2 と同じ順序です。

① 回帰テストを入れる(条文 ①-c3)        → 2 件落ちることを実測
② 判定器を直す(比較の前に $ref を展開)
③ 5 件すべて通ることを確認
④ 修正を戻すと 3 件落ちることを実測

直す前に落ちることを見ていなければ、その試験が欠陥を捕まえた証拠になりません。

4. 独立生成では判別力がありませんでした

最初は schema を独立に 2 枚生成する形にしました。実測すると 98% が BUMP です。

独立生成 600 対   BUMP 589 / HOLD 9 / HOLD_RECORD 2

これでは「常に BUMP と言う判定器」も同じ点数を取ります。
危険は小さな変更に潜むので、1 か所だけ変異させる形へ変えました。

変異対 961 対     BUMP 779 / HOLD_RECORD 173 / HOLD 9   ← ここで 4 件見つかった

変異が当たらなかった対は数えていません(539 件を除外)。
当たらなかった変異と素通りした変異は出力が同じなので、混ぜると意味が消えます。

5. 実装済みの道具を「未実装」と 9 日間書いていました

公開文書 2 か所が「npm run fit:contacts の 4極版は未実装」と書いていましたが、
実装済みでした(往復誤差 0 mm で動きます)。

11:21  6e151bc  docs   「4極版は未実装」と書いた
11:38  7602666  script **その 17 分後**に fitContactsTrrs.ts が入った
15:53  18abe44  docs   同じ文書を再び触ったが、直っていない

VERIFICATION_PLAN.md測ってくださる方へ向けた文書です。
在る道具を「無い」と伝え、手計算を促していました。

なぜ 9 日も残ったか。 この repo の文言検査は「在ると言ったのに無い」だけを見ていました
(指したパスが実在するか)。反対向きには検査が 1 つもありませんでした
(実測 0 件/対照は 13 ファイル)。

害は受け手の側にしか出ません。書いた本人は道具が在ることを知っているので、
読み返しても違和感がない。しかも実装が進むほど増えます。

同じ型を機械で止める検査を入れました。


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

toolVersion 20 / schemaVersion 2 のままです。

判定器は配布物か        **違う**(releaseAssets に 0 回。対照: 検算ツールは 2 回)
配る検算ツール          v0.6.20 と **byte 一致**
実 schema での判定       **179 対中 0 件が変化**(現行 schema × 直近 8 tag)
CLI 出力の 10 経路       基準と byte 一致

判定器は配布物ではありません。受け手が回すのは verifyReleaseSourceInputs.mjs で、
そちらは 1 バイトも変わっていません。判定器はこちらが版を決めるための道具
です。

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


まだやっていないこと

  • **実測が 1 件も来ていません。**ジャック内部の接点位置は全部仮定のままで、
    導通測定も音響測定もしていません(verifiedPhysicalfalse)。
  • BusyBox を oracle に足すこと。開発機にも CI にもありません。
    この機では試せないことを実測しました(Homebrew に formula が無く、
    コンテナ実行環境も無い)。ubuntu CI だけに入れる案は採りませんでした
    ——手元で再現できない欠陥を CI 往復で追うことになるためです。
  • annotated tag への署名(v0.6.21 も署名なしです)。
  • property-based 試験が見ているのは 1 つの性質だけです
    (「新だけが通す値が実在するなら BUMP」)。値は schema 対から作りますが、
    **作れない値があれば広がりを見逃します。**探索が働いていることは対照 5 つで
    確かめていますが、値の網羅性そのものは示していません。
  • 「無いと言ったのに在る」の検査は npm script 名を含む行しか拾いません。
    広げるかを検討し、現時点で偽が 0 件だったので広げていません
    (強い否定 9 件・うち検査できる 4 件はすべて本当に「無い」)。

数えたもの

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

変わっていないもの

  • **モデルの数値。**接点位置も走査結果も同じです。
  • verifiedPhysicalfalse。実測記録は依然 0 件です。
  • toolVersion 20 / CLI 結果契約 schemaVersion 2 / catalog 82 種類。
  • 止め方の名前の一覧・archivePolicy・status・終了コードの意味。