Skip to content

Releases: Driedsandwich/trs-jack-3d

v0.6.24 — JSON の項目名と JavaScript の継承を、分けていませんでした

Choose a tag to compare

@Driedsandwich Driedsandwich released this 16 Aug 08:53

v0.6.24 — JSON の項目名と JavaScript の継承を、分けていませんでした

⚠ 先に読むところ

1. 根は 2 つ。指摘は構文 4 つで、こちらは実箇所を 6 つ数えました

版を上げるかどうかを決める判定器(scripts/schemaLanguageDiff.mjs)に、
外部監査から P0 1 件・P1 1 件の指摘がありました。どちらもそのまま再現しました。

根A。 {}toStringconstructor継承しています

'toString' in {}          → true    (own property は無いのに)
({})['toString']          → 関数     (undefined ではない)
out['__proto__'] = x      → own property にならない(prototype が差し替わる)

properties の key は受け手が決める名前なので toString も来ます。
判定器はそこを分けずに集合差を取っていたので、項目の追加が見えませんでした。

旧  { type:'object', additionalProperties:false, properties:{} }
新  { …, properties:{ toString:{ type:'string' } } }

ajv  {"toString":"x"} が旧 invalid → 新 valid  = **広がっている**
判定 HOLD / exit 0                            ← 危険側

根B。 正当な再帰 schema(linked list)を比べると
RangeError: Maximum call stack size exceeded判定を返せませんでした。
打ち切りの鍵が pointer 文字列で、/properties/next/properties/next/…
伸び続けるため、同じ節へ戻っても一度も重複しません(実測: 8 段たどって重複 0 件)。

**根B は危険側ではありません。**誤った HOLD を返すのではなく、
答えを返せない——版を決める門が使えなくなる形です。だから直し方も違います。

指摘は危険な構文を 4 つ名指ししていました。直す前に実箇所を数えたところ、
根A だけで 5 か所ありました。

A1a / A1b / A1c  properties の集合差 3 か所   ← toString の反例はここ
A2               expandRefs の accumulator が {}   ← __proto__ が own にならない
A3               op[k] / np[k] で引く
A4               deref の traversal
A5               resolvePointer の traversal
B                打ち切りの鍵が pointer 文字列

hasOwn / own / bag(=Object.create(null))を 1 か所に置き、全部そこへ通しました。

2. __proto__ だけは ajv でも真値が出せません

指摘の 2 件目は oneOf の枝の中で __proto__ の型を広げる形でした。
**こちらでは ajv が広がりを認めません。**対照行列で切り分けました。

toString / constructor / hasOwnProperty / valueOf   → ajv は own property として正しく通す
__proto__                                           → **ajv 自身が新旧とも拒否**

正解器が同じ盲点を持っています。{"__proto__":"x"} を JSON.parse で作れば
own property になりますが、ajv の内部がそれを own key として扱えません。

だからこの 1 件は判定器の側だけを固定しました(条文 ①-c13 ⑦)。
「ajv が真値を出せる」という前提が崩れた初めての例です。

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

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

実害を先に測ってから、3 つに分けています。

A1c 共通集合        **精度**       戻すと同じ項目を 2 回数え、正しくは HOLD_RECORD の
                                削除が **誤って BUMP** になる
A5 resolvePointer  **正しさ**     判定は変わらない。contractMigration の
                                「その pointer は旧に在ったか」が**継承した関数を拾って true**
A3 op[k] / np[k]   **falsify 不能** `k` は `Object.keys(op)` から来るので**定義上いつも own**。
                                どの形でも判定が変わらない

**A3 は消しても落ちません。**承知のうえで残しました——A1c が将来戻ったときに効きます。
コードとテストの両方にその申告を書いています。

足したあと、A3 以外の 7 か所+生成器 3 か所が変異で落ちます。

4. P1 の最初の直し方が間違っていました

「一度でも見た節の組」を覚える形にしたところ、別の試験が 1 件落ちました
——contractMigration の記録した差分が実物から消えていたのです
/properties/nominalConfiguration/properties/windows/items)。

同じ節が 2 つの経路から来るとき、2 回目を飛ばしてその経路の差分を出さなかったからです。

「一度でも見たか」  → 別の経路から来た同じ節の結果まで消える
「いま辿っている経路上にあるか」  → 循環だけを止める(正しい)

seenスタックとして使う形へ直しました——降りるときに入れ、戻るときに外す
変異は 2 方向(鍵を pointer へ戻す/戻るときに外さない)とも落ちます。

出力が減るだけの欠陥は、落ちる試験が無ければ気づけません。
このときは既存の試験が拾ってくれました。

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

properties の key が prototype 名   0 件
循環する $ref                      0 件
(現行 26 schema を走査)

実 schema 200 対(現行 26 schema × 直近 8 tag)で判定は 1 件も変わっていません。
この版の公開物への影響はありません。直したのは将来の版を決める道具です。

**試験の側も直しました。**v0.6.23 の生成器は
prototype 名の property を 0 件、再帰する $ref0 件しか作っていません。
補強後は 9 件 / 42 件です。

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

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

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

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

まだやっていないこと

  • **実測が 1 件も来ていません。**接点位置は全部仮定のままで、
    導通測定も音響測定もしていません(verifiedPhysicalfalse)。
  • annotated tag への署名(v0.6.24 も署名なしです)。
  • main の branch protection / ruleset。監査の P2 ですが、
    リポジトリ設定の変更なので手を付けていません。
  • Half-Plug 下流の release-lock v7 / mechanism-binding v7(別 repo)。
  • nanoid 3.3.16 の更新。踏む経路が無いと測ったまま据え置きです(SECURITY.md)。
  • BusyBox を oracle に足すこと(開発機にも CI にもありません)。
  • A3 は falsify できないまま残しています(§3)。
  • property-based 試験が見ているのは、いまも 1 つの性質だけです。
    加えて §2 のとおり、正解器(ajv)にも盲点があることが分かりました。

数えたもの

v0.6.23 v0.6.24
単体テスト 1457 1474(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 は書き換えていません。
    過去 875 件(v0.1.0〜v0.6.23)が byte 一致であることを、再生成の段ごとに実測しました
    (対照として基準を 1 行壊すと 874 件へ落ち、v0.1.0 の該当ファイルを名指しします)。

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

Choose a tag to compare

@Driedsandwich Driedsandwich released this 15 Aug 21:22

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 件へ落ちます)。

v0.6.22 — 前の版で直したつもりの欠陥が、2 か所残っていました

Choose a tag to compare

@Driedsandwich Driedsandwich released this 15 Aug 12:00

v0.6.22 — 前の版で直したつもりの欠陥が、2 か所残っていました

⚠ 先に読むところ

1. 同じ根が 3 か所ありました。前の版で直したのは 1 か所です

v0.6.21 で「oneOf の枝が $ref のとき参照先の変更が見えない」を直しました。
外部監査が、同じ根の反例を 2 件返してきました。

原因は 1 つです。判定器は「節の文字列が変わっていない」を
意味が変わっていない」の証拠にしていました。$ref は参照先を指すだけなので、
指す先が変われば、文字列は同じまま意味が変わります。

その置き方が 3 か所ありました。

場所 直した版 見つけた人
oneOf の枝の比較 v0.6.21 こちらの property-based 試験
同上・枝に sibling がある形 この版 外部監査
allowlist ゲート(未対応 keyword が $ref を包む) この版 外部監査
anyOf / allOf の早期 continue この版 こちら

3 つ目は監査の指摘にありません。直す前に
「文字列の一致を意味の一致と置いた箇所」を数え直したときに出ました。
1 件目だけ直して終わりにすると、次の監査で同じものが返ってきます。

2. どちらも危険側です。受け手が拒む向きの誤りです

反例1  oneOf の枝が { $ref: '#/definitions/d0', description: 'branch' }
       旧 d0.type = 'string'  →  新 d0.type = ['string','null']

反例2  definitions.outer = { not: { $ref: '#/definitions/inner' } }   ← 新旧まったく同じ
       旧 inner.type = ['string','null']  →  新 inner.type = 'string'

どちらも ajv  {"x":null} が旧 invalid → 新 valid  = **言語が広がっている**
        判定  HOLD(据え置き可)                  ← **上げるべきなのに上げない**

反例 2 は not の中が狭まると外は広がる形です。

**受け手への影響。**版が据え置かれると、受け手は旧 schema を pin したままになります。
新しい版が null を出した瞬間、受け手の検証が落ちます。

現行 schema への影響はありません。$ref + sibling は現行 schema に 3 件
evidenceGrade が 3 版すべてでこの形)ありますが、いずれも properties の位置で、
そこは前から正しく判定していました。
実 schema 194 対(現行 25 schema × 直近 8 tag)で判定は 1 件も変わっていません。

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

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

① 回帰テストを入れる(条文 ①-c4 / ①-c5)  → 2 件とも落ちることを実測
② 判定器を直す
③ 通ることを確認
④ 直した 3 か所を 1 つずつ戻す           → A 落ちる / B 落ちる / **C 落ちない**
⑤ C 用のテスト(①-c6)を足す            → C も落ちるようになった

④ で 1 か所が「外しても落ちない行」になっていました。
anyOf/allOf の経路に実害のある反例が在ることを ajv で確かめてから、
テストを足しています。変異で落ちない修正は、入れた証拠がありません。

4. 前の版の試験が見逃した理由は 2 つで、どちらも「0 件」でした

v0.6.21 の notes に、こう書きました。

値は schema 対から作りますが、作れない値があれば広がりを見逃します。

そこがそのまま当たりました。

                                    v0.6.21   v0.6.22
枝・property 位置の $ref + sibling     0 件      20 件   ← 形を作れていなかった
候補値のうち「property が null」        0 件      85 件   ← 証人を作れていなかった

監査の反例 2 件は、どちらも証人が {"x": null} です。
当時の生成器は 300 種を作ってその形を 1 件も含めず
証人合成は 6,219 候補を作って**「1 つの key だけが null」を 1 件も作りません**でした。
どちらか片方でも 0 なら、判定器を直しても試験は自力で見つけられません。

補強したうえで v0.6.21 の判定器に対して回すと、自力で反例を出しました
(seed 266・枝が {$ref, description}・参照先の type が広がった形)。
手書きの反例を抜いた状態での実測です。

5. schemaId を名乗りながら、その schema を配っていませんでした

release-stage-attestation.v1.json(最終関門の記録)は前から
schemaId: trs-jack-3d-release-stage-attestation.v1 と名乗っていましたが、
**対応する schema を配っていませんでした。**受け手は名乗っている形を検証できません。

記録の中身は正しいものでした。欠けていたのは確かめる手段です。
検査していたのは stageRelease.mjs文字列だけで
(「exitCode: と書いてあるか」)、生成された実物は見ていませんでした。

この版で schemas/release-stage-attestation.v1.schema.json を配ります。
release:stage記録を書く前に実 object を検証し、
合わなければ記録も SHA256SUMS も出さずに止まります
(書いてから検証すると、落ちたときに「途中まで正しく見える記録」が残ります)。

testEvidenceCrossBound を必須欄へ入れました——それまで必須一覧にありませんでした。

6. tag の中の SECURITY.md が、恒久的に 1 版古くなっていました

v0.6.18 の tag → 「現時点では v0.6.17」
v0.6.19 の tag → 「現時点では v0.6.18」
v0.6.20 の tag → 「現時点では v0.6.19」
v0.6.21 の tag → 「現時点では v0.6.20」     4/4 tag で実測

**工程上どうやっても直りません。**release を作る commit の時点では、
その版はまだ公開されていないので、書ける最新は 1 つ前です。

検査は手元の控えの最大版数でこの行を縛っていたので main では常に正しく、
だからこそ誰も気づきませんでした。受け手が読むのは tag の中身です。

版数を書かず「GitHub Releases で Latest と表示されている 1 本」を指す形へ変え、
版数を書き足したら止まる検査を入れました。

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

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

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

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

配る asset は 27 件 → 28 件になりました(attestation の schema を足したため)。
足したのは schema だけで、記録そのものは索引の外のままです(自己参照を避けるため)。

依存の advisory(踏む経路つき)

npm audit が high を 1 件出します。踏む経路は無いと測りました。

nanoid 3.3.16 (<3.3.18)  GHSA-2v37-7h3g-55p8
経路    vite → postcss → nanoid(推移依存)
区分    **dev のみ**(npm ls nanoid --omit=dev が空。対照として three は解決する)
出荷物  **入っていない**(dist/assets/*.js に nanoid が 0 件。対照として three は 1 件)

**まだ上げていません。**詳しくは SECURITY.md に書きました。

まだやっていないこと

  • **実測が 1 件も来ていません。**接点位置は全部仮定のままで、
    導通測定も音響測定もしていません(verifiedPhysicalfalse)。
  • property-based 試験が見ているのは 1 つの性質だけです
    (「新だけが通す値が実在するなら BUMP」)。今回その限界がそのまま当たりました。
    補強しましたが、次に見逃す形も、いまは 0 件に見えます。
  • BusyBox を oracle に足すこと(開発機にも CI にもありません)。
  • annotated tag への署名(v0.6.22 も署名なしです)。
  • 公開済み asset の定期確認(npm run check:published-assets は在りますが、
    定期実行にはしていません)。

数えたもの

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

変わっていないもの

  • **モデルの数値。**接点位置も走査結果も同じです。
  • verifiedPhysicalfalse。実測記録は依然 0 件です。
  • IV028 は 13.30–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・終了コードの意味。

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

Choose a tag to compare

@Driedsandwich Driedsandwich released this 15 Aug 09:33

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・終了コードの意味。

v0.6.20 — io に fetch を足しました(版数は据え置き)

Choose a tag to compare

@Driedsandwich Driedsandwich released this 15 Aug 04:53

v0.6.20 — io に fetch を足しました。版数は上げていません

⚠ 先に読むところ

1. SOURCE_FETCH_* の 4 経路を、global を差し替えずに踏めるようになりました

実質の差分は 2 行です。

io へ      fetch: (url, init) => fetch(url, init)
呼び出し    await fetch(url, {…})  →  await io.fetch(url, {…})
判定ロジック **差分 0 行**

AbortSignal.timeoutTimeoutError を投げる約束はそのままで、
差し替える側が同じ約束を守ればよい形です。

SOURCE_FETCH_FAILED      fetch が throw
SOURCE_FETCH_TIMEOUT     name = 'TimeoutError' で throw
SOURCE_HTTP_ERROR        503 を返す
SOURCE_BODY_UNREADABLE   本文の reader が失敗する

変異対照。io.fetch を素の fetch へ戻すと、この 4 件だけが落ちます。
globalThis.fetch を触っていないことも試験で確かめています
——触っていたら、この試験は「global の差し替えが効いた」ことしか示しません。
io.fetch が呼ばれた回数も数えます(各 1 回)。呼ばれていなければ別の経路で止まっています。

2. toolVersionschemaVersion も上げていません

**受け手の分岐は 1 つも変わりません。**根拠(すべて実測):

既存 10 経路の出力  **byte 一致**(stdout / stderr / 終了コード)
出しうる code       **82 種類のまま**(増減 0)
schemaVersion       2 のまま・配布 schema も不変
io は出力に出るか    **0 件**(対照: stableReasonCode は 1 件)

io は道具の内側の仕組みで、受け手が読む値には現れません。

3. 既存の検査が 1 件、正しく鳴りました

gh に依存していない」の検査が、こう書かれていました。

expect(SRC).toMatch(/await fetch\(/)

性質(外部コマンドに頼っていない)を、書き方で確かめていたわけです。
io.fetch へ寄せた瞬間に落ちました。

検査を緩める前に、性質が保たれているかを実測しました。
io の既定は組み込みの fetch そのものなので、性質は変わっていません。

そのうえで実態へ合わせましたが、両方の書き方を許すだけにはしていません。

変更後  await (io.)?fetch(  を許す
追加    **io の既定が組み込みの fetch であること**も見る
        (差し替え先が別物になったら、この検査の意味が消えるため)
対照    取得の経路を消すと落ちる

落ちたのは検査が正しく鳴ったからでもあります——「取得の経路がソースに見当たらない」
という状態は、本当に外部コマンドへ逃げたときにも起きます。区別がつきません。

4. 子プロセスの route 表は残し、io に足さなかったものもあります

子プロセスの表(test/_cliRoutes.mjs)は残す
  io 注入は子プロセスへ効かない——spawnSync で起動するので、
  呼び出し側のオブジェクトは相手に届かない
  あの表は「配った 1 ファイルを、受け手と同じ起動のされ方で踏む」ためのもので、
  終了コード・stdout の byte・入口の判定まで含めて確かめられる

io に足していないもの:

入口の判定(realpathSync)
  「この道具が起動されたのか」を見るもので、**検証の入力ではない。**
  差し替えられるようにすると、起動のされ方を偽れることになる

execFileSync(git rev-parse / git archive)
  外部プロセスなので、差し替えるなら **PATH のほうが素直。**
  子プロセスの route 表が偽 git を置いてそれをやっている

io に注入できるもの(現在):

cwd / argv / stdout / stderr / exit                              v0.6.18
existsSync / readFileSync / statSync / lstatSync / readdirSync   v0.6.18
fetch                                                            **v0.6.20**

まだやっていないこと

  • **実測が 1 件も来ていません。**ジャック内部の接点位置は全部仮定のままで、
    導通測定も音響測定もしていません(verifiedPhysicalfalse)。
  • BusyBox を oracle に足すこと。開発機にも CI にもありません。
  • annotated tag への署名。
  • 検算ツールの強化は試験した範囲までです。すべての細工に耐えることは示していません。

数えたもの

v0.6.19 v0.6.20
単体テスト 1371 1376(skip 0 / 失敗 0)
検算ツール toolVersion 20 変わらず 20
CLI 結果契約 schemaVersion 2 変わらず 2
止め方の名前(catalog) 82 種類 変わらず 82
出力の基準(経路) 10 変わらず 10(全件 byte 一致)
io に注入できるもの 10 種類 11 種類(fetch を追加)
検証対象(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・終了コードの意味。

v0.6.19 — main(args, io) を抽出(版数は据え置き)

Choose a tag to compare

@Driedsandwich Driedsandwich released this 15 Aug 03:52

v0.6.19 — main(args, io) を抽出しました。版数は上げていません

⚠ 先に読むところ

1. toolVersionschemaVersion も上げていません

外部監査 P2(core / CLI 分離)の最後です。受け手の分岐は 1 つも変わりません。

toolVersion    20 のまま
schemaVersion  2 のまま(…cli-result.v2)
catalog        82 種類のまま(増減 0)

上げていない根拠(すべて実測):

出力          分離前に固定した **10 経路すべてが byte 一致**
              (stdout / stderr / 終了コード。基準は dab485c2260c 時点のもの)
出しうる code  82 種類のまま
判定ロジック   **差分 0 行**(触ったのは出口の形と設定の作り方だけ)

基準は repo に入っています(test/fixtures/cli-output-baseline.v1.json)。
npm run check:cli-output で**受け手も同じものを回せます。**CI にも段として入っています。

2. done() を「返す」形にはできませんでした。投げる形にしています

計画は「process.exit を呼ばず、投げるか返すへ変える」と書いていました。
実際に見ると、呼び出し側 6 か所のうち 5 か所が「その場で終わる」ことに依存していて、
done() の後ろにコードがそのまま続いています。返す形は選べません——
結果を出したあとも処理が進んでしまいます。

done()  CliResult を投げる
main()  受け取って CLI 側へ渡す
CLI 側  io.stdout / io.stderr / io.exit を叩くだけ(判定は一切しない)

例外ですが、異常ではありません。done() が呼ばれた時点で結果は確定していて、
あとは呼び出し元まで戻るだけです。instanceof で見分けるので、途中の catch には拾われません。

**計画のほうを結果へ寄せていません。**計画書
docs/CORE_CLI_SEPARATION_PLAN_20260815.md)の §3 は当初のまま残し、
実施記録に「どちらを選んだか・なぜか」を書いています。

3. io の注入だけで SOURCE_ARCHIVE_MISSING を踏めるようになりました

v0.6.18 の notes で、こう書きました。

io へ filesystem を足しましたが、この経路には効いていません。
残した理由は先の話です——main(args, io) を抽出したあとは、
global を差し替えずに fs を注入できるようになります。

その土台が、この版で効きます。

global を 1 つも差し替えず、io だけを渡して main() を呼ぶ
  → SOURCE_ARCHIVE_MISSING / exit 2
  対象 path の existsSync が呼ばれた回数 = 2(1 回目は通し、2 回目で false)
対照  差し替えなければ、同じ引数で別の code になる

main()io しか見ていないことも確かめています——cwd を差し替えると
manifest を見失い、引数は args から取ります(process.argv を見ていません)。

4. 子プロセスの route 表と io 注入の、両方を残しました

io 注入ができたので表を片付けたくなりますが、寄せられません。

子プロセスの表(test/_cliRoutes.mjs)
  配った 1 ファイルを、受け手と同じ起動のされ方で踏む
  → 終了コード・stdout の byte・入口の判定まで含めて確かめられる

io 注入(test/mainInjection.test.ts)
  本体が io しか見ていないことを示す
  → global を差し替えないので「差し替えが効いていただけ」を排除できる

**io 注入は子プロセスへ効きません。**あの表は spawnSync で起動するので、
呼び出し側のオブジェクトは相手に届きません。
**同じ code を、別の入口から踏みます。**どちらか一方では足りません。


まだやっていないこと

  • **実測が 1 件も来ていません。**ジャック内部の接点位置は全部仮定のままで、
    導通測定も音響測定もしていません(verifiedPhysicalfalse)。
  • **io に fetch はまだ足していません。**足せば SOURCE_FETCH_*
    global を差し替えずに踏めます。接点の追加であって抽出ではないので分けます。
  • BusyBox を oracle に足すこと。開発機にも CI にもありません。
  • annotated tag への署名。
  • 検算ツールの強化は試験した範囲までです。すべての細工に耐えることは示していません。

数えたもの

v0.6.18 v0.6.19
単体テスト 1364 1371(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・終了コードの意味。

v0.6.18 — v0.6.17 の attestation を是正し、core と CLI を分離

Choose a tag to compare

@Driedsandwich Driedsandwich released this 15 Aug 02:58

v0.6.18 — v0.6.17 の attestation に誤りがありました。core と CLI を分離しました

⚠ 先に読むところ

1. v0.6.17 の attestation は、配った索引とは別のものを指していました

公開した直後に、自分で添付を検算して見つけました。

attestation が名乗る値   e9c72e2412fa6e21…  ← **repo 側**の索引(releaseTag: null)
配布した索引の実測       a014768129b56e18…  ← 受け手が計算する値
違う欄                   releaseTag / releaseCommit(配布時に書き込む)

3 つの digest のうち 2 つは一致していました。test_counts.json
validation-results.json は配布時にそのまま写すので同じ値になります。
索引だけが写しではありません——releaseTagreleaseCommit を配布時に書き込むためです。

**「ほとんど合ってしまう」のが危ないところでした。**ずれるのは
受け手が最初に引く 1 つだけです。

公開済みの v0.6.17 の asset は上書きしません。
docs/ERRATA.md §23 に記録し、
この版で直しました(測る先を配布物の側へ)。この欠陥を捕まえる検査も入れています。

v0.6.17 での影響は attestation.releaseIndexSha256 の 1 欄だけです。
SHA256SUMS は配布物から作っているので正しく、索引の検算はそちらで行えます。

2. core と CLI を分離しました。toolVersionschemaVersion も上げていません

外部監査 P2(v0.6.15 / v0.6.16 / v0.6.17 で 3 回続けて指摘)にようやく着手しました。

新設   defaultIo()(cwd / argv / stdout / stderr / exit + filesystem 5 種)
差替   process.* と console.log の呼び出しを io 経由へ
残す   入口の判定 2 か所(この道具が起動されたのかを見るもの)

上げていない根拠を実測で示します。

出力           分離前に固定した **9 経路すべてが byte 一致**(基準は現在 10 経路。
               10 本目は分離のあとに足した SOURCE_ARCHIVE_MISSING の経路)
出しうる code   **82 種類のまま**(増減 0)
schemaVersion  2 のまま・配布 schema も不変
出力に出るか    reachability は出力に 0 件(対照: stableReasonCode は 1 件)

**受け手の分岐は変わりません。**上げたくなったら、それは分離ではありません。

分離が「何も変えていない」ことは、分離の前に取った基準で確かめています
test/fixtures/cli-output-baseline.v1.jsonnpm run check:cli-output)。
基準は repo に入っているので、受け手も同じものを回せます。

3. SOURCE_ARCHIVE_MISSING を実経路として踏みました

v0.6.17 で race-defensive(到達しうるが決定的には踏めない)と宣言した code です。
踏めました。「決定的には踏めない」は踏む試験を書いていなかっただけでした。

loadFromDir      existsSync(abs)  … 通る
                 lstatSync(abs)   … ディレクトリでない → loadFromArchive へ
loadFromArchive  existsSync(abs)  … **ここで消えていればこの code**

同じ path に対する 2 回目の existsSync だけ false にすれば踏めます。
node:fs を差し替えて注入します——道具は 1 バイトも変えません
globalThis.fetch の差し替えと同じ形)。

catalog  race-defensive → **cli-route**
         82 種類 = corpus 45 / cli-route 34 / defensive-invariant 3
両方向照合  この run で出た 79 種類・到達しないと宣言した 3 種類(実測と一致)

race-defensive は語彙表に残しました(未使用であることを試験で明示)。

4. io へ filesystem を足しましたが、この経路には効いていません

実測(2026-08-15):

同じ注入(node:fs の差し替え)を当てる
  io に fs が無い版  → SOURCE_ARCHIVE_MISSING
  io に fs を足した版 → SOURCE_ARCHIVE_MISSING

node:fs の差し替えは ESM の named import にも効くので、
io を経由してもしなくても同じように踏めます。
この経路を踏むために io へ fs を足す必要はありませんでした。

残した理由は先の話です——main(args, io) を抽出したあとは、
global を差し替えずに fs を注入できるようになります。
いまは「そのための土台」であって、いま効いているわけではありません。
効いていないものを効いていると書かないでおきます。


まだやっていないこと

  • **実測が 1 件も来ていません。**ジャック内部の接点位置は全部仮定のままで、
    導通測定も音響測定もしていません(verifiedPhysicalfalse)。
  • **main(args, io) の抽出そのものは、まだです。**この版でやったのは
    接点を 1 か所に集めたところまでで、done() はまだ io.exit を叩きます。
    戻り値だけで完結する形にするのは次です。
  • BusyBox を oracle に足すこと。開発機にも CI にもありません。
  • annotated tag への署名。
  • 検算ツールの強化は試験した範囲までです。すべての細工に耐えることは示していません。

数えたもの

v0.6.17 v0.6.18
単体テスト 1352 1364(skip 0 / 失敗 0)
検算ツール toolVersion 20 変わらず 20
CLI 結果契約 schemaVersion 2 変わらず 2
止め方の名前(catalog) 82 種類 変わらず 82
うち到達しないと宣言 3 + race-defensive 1 3race-defensive は未使用)
出力の基準(経路) (無し) 10 経路(うち 9 件は分離前に固定)
検証対象(validate:profiles 14 14
schema 25 25
記録された入力 32 32
区間 / event TRS 23/32・TRRS 30/36 変わらず

変わっていないもの

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

v0.6.17 — v0.6.16 は NOT_READY。CLI 結果の契約を v2 へ

Choose a tag to compare

@Driedsandwich Driedsandwich released this 15 Aug 01:41

v0.6.17 — v0.6.16 は NOT_READY です。CLI 結果の契約を v2 へ上げます

⚠ 先に読むところ

1. v0.6.16 は、版を据え置いたまま公開契約を広げていました

v0.6.16 の道具の出力は、1 つも v0.6.15 の schema を通りません。

同じ schemaId / schemaVersion: 1 を名乗りながら、言語が 3 か所広がっていました。
repo 自身の判定器で実測(2026-08-15・両 tag の実物):

diffSchemaObjects(v0.6.15 の v1, v0.6.16 の v1) = **BUMP**
  WIDEN  /properties/stableReasonCode/enum
           + CLI_ARGUMENTS_MISSING, SOURCE_FETCH_TIMEOUT
  WIDEN  /properties/archivePolicy/properties/coverage/properties/reasonCodeFamilies/items/enum   + usage
  WIDEN  /properties/archivePolicy/properties/reasonCodeFamilies/items/enum                        + usage
対照  同じ schema どうしなら HOLD(何にでも BUMP と言う判定器ではない)

外部監査の指摘より影響が広いことが分かりました。
監査は「新しい 2 つの code を返す結果が不適合」としていましたが、
usage 族は archivePolicy に載るのですべての出力に出ます。ajv で実測:

v0.6.15 の v1 schema に当てる
  引数不足の出力(CLI_ARGUMENTS_MISSING)  → 不適合(3 か所)
  **`OK` の正常な出力**                     → **不適合(2 か所)**

公開済みの asset は上書きしません。
docs/ERRATA.md §17 で
NOT_READY へ再分類しました。

受け手への影響は限定されます。 profile の数値・区間・event は変わりません。
使えないのは「v0.6.16 の出力を、後の版の schema で検証すること」だけです。
v0.6.16 が出した保存済みの結果は、v0.6.16 と一緒に配った v1 schema で検証してください。
v1 のファイルは変更していません。

2. schemaVersion が 2 に、toolVersion が 20 になります

                     v0.6.16                                    v0.6.17
schemaId       …source-verifier-cli-result.v1        →  …source-verifier-cli-result.v2
schemaVersion  1                                     →  2
toolVersion    19                                    →  20
配布ファイル名  source-verifier-cli-result.v1.schema.json → …v2.schema.json

ファイル名で引いている lock は、schema 検査へ到達する前に「ファイルが無い」で止まります。
対応は同梱の contract-migration.v1.jsonrenamedAssets)にあります。

止め方の名前の一覧・archivePolicy・status・終了コードの意味は変わっていません。
v1 → v2 で変わるのは、名乗る版だけです。

3. なぜ検査が通したか——母集団が 11 版のあいだ止まっていました

test/schemaVersioningPolicy.test.ts
  const LATEST_TAG = 'v0.5.1'          ← ここが母集団を決めていた

source-verifier-cli-result は v0.6.11 の新設なので、一度も母集団に入っていません。
コメントには「上げた回はここを新しい tag へ進める」と書いてありましたが、進みませんでした。

対照(2026-08-15): BUMP が実在する状態で v0.6.16 の検査を回すと **57/57 全緑**

「比較した本数 === 母集団の本数」という空振り検査は付いていました。
数えていたのは古いほうの一覧です。

v0.6.17 では、直前 release を package.json の版数から毎回その場で決め、
母集団を現行の schema 全部git add していないものも含む)にしました。
この反例を違反として捕まえる回帰試験も入れています。


証拠の由来を、契約が要求するようにしました

v0.6.16 は testCountsSha256 / testCountsGeneratedFromCommit / testCountsGeneratedAt
**書いてはいましたが、schema が要求していませんでした。**変異対照:

                                  v2(旧)   v3(新)
基準(無変更)                       適合       適合
3 欄をそれぞれ消す                    適合     **不適合**
SHA を 63 桁 / commit を短縮 / 日付を壊す  適合  **不適合**
未知項目を足す                       適合     **不適合**

監査は「狭める変更なので HOLD_RECORD」と想定していましたが、判定器の実測は BUMP でした。
testEvidenceoneOf の中にあり、判定器は枝の言語について単調でないことを理由に
判定不能を返します。判定器を緩めず、validation-results を v3 へ上げました。

2 つの証拠を結び直しました

release:stage は、test_counts.json の鮮度と READY別々に見ていました。
片方だけ作り直した状態は、どちらの検査も単体では通ります。

基準(無変更)              通す
test_counts だけ更新        **止める**
validation だけ更新         **止める**
SHA / commit / 日付 を偽装   **止める**
testEvidence ごと消す       **止める**

関門を切り出してから測っています。release:stage を丸ごと回すと
手前の鮮度検査が先に落ちて全件が同じ exit 1 になり
「この関門が効いた」の証拠になりません(実際に一度そう測って読み違えました)。

最終関門を通ったことが、配布物から読めるようになりました

release-stage-attestation.v1.json を足しました。
validation-results.releaseReadinessStatus: READYrelease:evidence 時点の判定で、
最終関門を通った証拠ではありません。

**索引には入れません。**索引はこれより前に作られ、これは索引を読んでから作るので、
入れると 1 回の実行で収束しません。SHA256SUMS がこの記録の sha256 を持ちます。

**この記録も自己申告です。**作った側が作った側を検査した結果でしかなく、
受け手の独立検証を置き換えません。

終了コード 3 に、機械で読める目印を付けました

stderr の 1 行目: TRS_JACK_VERIFIER_INTERNAL_CONTRACT_FAILURE

**説明文は版ごとに書き換わります。**文言でなくこの 1 語で分岐してください。
通常の CLI 結果 schema へ INTERNAL_ERROR は足していません
——壊れた道具が schema 適合の検証結果を捏造するほうが危ないためです。

到達性の語彙を作りました

SOURCE_ARCHIVE_MISSINGdefensive-invariant(論理的に起こりえない)から
race-defensive(確認と使用の間で消されれば到達する)へ移しました。外部監査の指摘どおりです。

あわせて、両方向の照合が reachability !== 'defensive-invariant' という
文字列の否定で群を分けていたのを、語彙表から引く形にしました。
否定で分けていると、新しい種類を足した瞬間に黙ってどちらかへ入ります。


まだやっていないこと

  • **実測が 1 件も来ていません。**ジャック内部の接点位置は全部仮定のままで、
    導通測定も音響測定もしていません(verifiedPhysicalfalse)。
  • **core と CLI の分離をしていません。**v0.6.16 の notes で「次の版で着手します」と
    書きましたが、**この版でも着手していません。**外部監査も
    「先に契約の versioning を閉じ、その次の独立版で着手するのが妥当」としています。
    **この版は P0 の是正です。**次の版の最初に着手します。
  • BusyBox を oracle に足すこと。開発機にも CI にもありません。
  • annotated tag への署名。
  • 検算ツールの強化は試験した範囲までです。すべての細工に耐えることは示していません。
  • SOURCE_ARCHIVE_MISSING を実経路として踏んでいません。
    race-defensive は「確認と使用の間で消されれば到達する」という分類であって、
    実際に踏んだ記録ではありません。踏むには filesystem を差し替える必要があり、
    それは core / CLI 分離のあとになります。
  • 由来の検査(release:evidence)の範囲は、artifacts/docs/ を含みません。
    含めると、証拠を書く工程が自分の書き込みで「古くなった」と判定して収束しません。
    最終判定は release:stage が実際に測り直して行います。

再生成の途中で、新しい門が自分を止めました

**第3段の順序を間違えました。**第1段・第2段で artifact を作り直したあと、
manifest と索引がまだ追随していない状態で件数を数えたので、
突合の試験が 26 件落ちました。

記録された値   failed 26 / exitCode 1 / allPassed false
門の反応       releaseReadiness NOT_READY・release:stage が拒否

生成は止めず、配布を止める設計どおりです——件数の照合が落ちている間は
件数を更新できない、という循環を避けるためにこの形にしてあります。
正しい順序(release:evidencetest:countrelease:evidence)で回し直しました。

数えたもの

v0.6.16 v0.6.17
検算ツール toolVersion 19 20
出力契約 …cli-result.v1 / schemaVersion 1 …cli-result.v2 / 2
止め方の名前(catalog) 82 種類 変わらず 82
うち到達しないと宣言 4(defensive-invariant 3 + race-defensive 1
schema 23 25(cli-result.v2 と validation-results.v3 を新設)
検証結果の契約 validation-results.v2 validation-results.v3
version 検査の母集団 v0.5.1 の 21 本 現行の 25 本

変わっていないもの

  • **モデルの数値。**接点位置も走査結果も同じです。
    葉ノードで比較して実測しています(感度 artifact: 由来まわりの差 10 件・モデル数値の差 0 件
    対照として 1 つの値を 0.001 動かすと「モデル数値の差 1 件」が出ます)。
  • verifiedPhysicalfalse。実測記録は依然 0 件です。
  • 止め方の名前の一覧・archivePolicy・status・終了コードの意味。
  • 過去 release の asset。

v0.6.16

Choose a tag to compare

@Driedsandwich Driedsandwich released this 14 Aug 14:53

v0.6.16 — v0.6.15 は NOT_READY です。toolVersion を 19 へ上げます

⚠ 先に読むところ

1. v0.6.15 が配ったテスト証拠は、v0.6.14 のものでした

v0.6.15 は releaseReadinessStatus: READY を名乗って公開されましたが、
その根拠は前の版の実行です。

v0.6.15 が配った test_counts.json    total 1236 / 2026-08-12
                                     generatedFromCommit 1c79e059…(v0.6.14 第2段)
v0.6.15 が配った validation-results  testEvidence.total 1236 / READY
v0.6.15 の tag 時点の CI             1304 件 / 32 ファイル / 失敗 0
v0.6.15 の release notes             1304 件

公開済みの asset は上書きしません。
docs/ERRATA.md §13 で
NOT_READY へ再分類しました。

受け手への影響は限定されます。 v0.6.15 の profile の数値・区間・event・profileId
変わりません。使えないのは test_counts.jsonvalidation-results.testEvidence だけです。
tag v0.6.15 の CI が 1304 件で通っていることが、実際の状態です。

なぜ 3 つの門が全部通したかも実測しました。

check:vacuity      byFile を「これ以上減ってはいけない下限」として使う
                   → 1304 ≥ 1236 なので通る(空振り検査としては正しい)
check:doc-numbers  docs/TEST_RESULTS.md と test_counts.json を突き合わせる
                   → **どちらも古いので一致する**
release:evidence   allPassed / failed / exitCode しか見ない
                   → 古いかどうかは一度も見ていない

一致は現在性の証拠になりません。古いもの同士は仲良く一致します。

v0.6.16 で門を 2 つ置きました。役割が違います。

release:stage     実測して total / byFile / skipped / failed / failedSuites /
                  exitCode / allPassed を**完全一致**で突き合わせる(byFile も下限にしない)
release:evidence  由来を見る——その証拠を取ってから、テストに効くファイルが
                  commit 間で動いていないか/未コミットで触っていないか

変異対照(v0.6.15 が実際に配った 1236 件の証拠を戻して実測):

                             v0.6.15 の門    v0.6.16 の門
check:vacuity                   exit 0          exit 0(下限のまま。これは正しい)
check:test-evidence-current     (無い)        **exit 1**
release:evidence                READY           **NOT_READY**
release:stage                   exit 0          **exit 1**

2. toolVersion が 18 → 19 になります。受け手の分岐が変わります

v0.6.15 の道具は、自分で配った schema に反する出力を出していました。

実測(公開した道具そのもの)
  --source も --tag も渡さない   SOURCE_UNAVAILABLE / SOURCE_UNAVAILABLE_OTHER
  GitHub 取得中に fetch が失敗    同じ

どちらも同梱 schema の 80 種類の enum に無い値です。
v0.6.15 で enum へ狭めたときに、名前を付け忘れた経路が受け皿へ落ち続けていました。

                              v0.6.15                     v0.6.16
引数不足                  SOURCE_UNAVAILABLE_OTHER  →  CLI_ARGUMENTS_MISSING
GitHub へ繋がらない        同じ                      →  SOURCE_FETCH_FAILED
応答が時間内に来ない       同じ                      →  SOURCE_FETCH_TIMEOUT

止め方の名前は 80 → 82 種類。一覧は同梱の
source-verifier-cli-result.v1.schema.jsonstableReasonCode.enum にあります。

*_OTHER で分岐している自動処理があれば、そこは直す必要があります。

3. 終了コード 3 が増えます。これは「道具が壊れている」だけです

出口に関門を置き、契約を破る出力はそもそも出しません

JSON を出さず、終了コード 3 で止まる
  status が CLI_STATUS_META に無い/exitCode が表と食い違う/
  OK なのに理由が付いている/非 OK なのに catalog の名前が無い/
  catalog の status と実際の status が違う

3 はどの status の終了コードとも重なりません。
例外のまま落とすと Node は 1 で終わり、MISMATCH と見分けが付かないためです
——stdout が空なのに終了コードだけ「不一致」に見えるのが、いちばん悪い読まれ方です。


「到達しない」と宣言した 8 件のうち、4 件は実経路でした

SOURCE_GIT_ARCHIVE_FAILED   PATH の先頭に偽 git を置き、rev-parse は通して archive だけ失敗させる
SOURCE_FETCH_FAILED         fetch の reject(v0.6.15 では未配線でもあった)
SOURCE_HTTP_ERROR           HTTP 503
SOURCE_BODY_UNREADABLE      応答本文の reader が失敗

**v0.6.15 の両方向照合がこれを通した理由。**route の母集団を試験が手で持っていたので、
**書かなかった経路は当然「出なかった」**のです。
「この run で出なかった」は「出ない」の証拠になりません。

route の表を 1 つにし、契約の検査と到達性の照合が同じ表を使うようにしました。
注入は globalThis.fetch の差し替えで、道具は 1 バイトも変えません。

catalog 82 種類 = corpus 45 / cli-route 33 / defensive-invariant 4
この run で実際に出た 78 種類
到達しないと宣言した 4 種類(出ていないことも実測)

文言の検査そのものが、同じ形の欠陥でした

v0.6.15 で新設した検査は「全面へ当てる」と説明しながら、手書きの一覧を使っていました。

実測: 新しい文書を作り、古い言い方と実在しないパスを両方書いて全試験を回すと
      **14 件すべて緑**

追跡ファイルの探索へ変えたところ、旧版が見ていなかった実在しない参照を 1 件見つけました。


互換性には向きが 2 つあります

producer-forward     新しい出力が古い schema を通るか       → 通る
historical-instance  保存した古い結果が新しい schema を通るか → **通らない**

保存した検算結果は、その版と一緒に配った schema で検証してください。
$id から最新の v1 を引くと、後の版で狭めた分だけ過去の結果が落ちます。
索引の notes が、突合先の schema と信頼の起点(道具の sha256)を名指ししています。

policySha256 を信頼の起点にしないでください。
同じ道具が policy と digest の両方を書くので、両方書き換えれば一致します。


profileId が変わりました。lock を作り直してください

v0.6.15  trs-jack-3d:TRS|JACK-TRS:1ee9fcdb2c88
v0.6.16  trs-jack-3d:TRS|JACK-TRS:8347dcf33002

v0.6.15  trs-jack-3d:TRS|JACK-TRRS:2eb28f2d173e
v0.6.16  trs-jack-3d:TRS|JACK-TRRS:e460e8cc0c6b

この文書から転記せず、同梱の trs-jack-3d-release-index.v1.json から引いてください。

動いた原因は版数上げ(package-lock.json)です。モデルの数値は変わっていません。


まだやっていないこと

  • **実測が 1 件も来ていません。**ジャック内部の接点位置は全部仮定のままで、
    導通測定も音響測定もしていません(verifiedPhysicalfalse)。
  • **core と CLI の分離をしていません。**外部監査 P2 の指摘です。
    P0 の訂正版と混ぜないという判断は監査からも支持されました。次の版で着手します。
  • BusyBox を oracle に足すこと。開発機にも CI にもありません。
    監査が BusyBox 1.37.0 で外部再現しており、その結果は採っていますが、
    こちらでは走らせていません。
  • annotated tag への署名。
  • 検算ツールの強化は試験した範囲までです。すべての細工に耐えることは示していません。

数えたもの

v0.6.15 v0.6.16
単体テスト 1304(配った証拠は 1236 1320(skip 0 / 失敗 0)
壊れた tar の材料 190 個 20 種類 変わらず
うち実際に通る材料 72 72
止め方の名前(catalog) 80 種類 82 種類
うちこの run で実際に出た 78 78
到達しないと宣言 8 種類 4 種類
検証対象(validate:profiles 14 14
schema 23 23(BUMP 0)
記録された入力 32 32
検算ツール toolVersion 18 19
区間 / event TRS 23/32・TRRS 30/36 変わらず

変わっていないもの

  • **モデルの数値。**接点位置も走査結果も同じです。
  • verifiedPhysicalfalse。実測記録は依然 0 件です。
  • 過去 release の asset。v0.1.0 以降の 634 件すべて byte 一致であることを、
    再生成の段ごとに対照つきで実測しています。

v0.6.15

Choose a tag to compare

@Driedsandwich Driedsandwich released this 14 Aug 10:27

v0.6.15 — toolVersion を 18 へ。止めた理由の名前が、ほぼすべての止まり方に付きます

⚠ 先に読むところ

1. toolVersion が 17 → 18 になります。受け手の分岐が変わります

v0.6.14 までの道具は、stableReasonCode(止めた理由の変わらない名前)を
archive 系の 2 つの status にしか付けていませんでした。
残る 4 つの止まり方は ${status}_OTHER へ落ちるだけで、機械では分岐できません。

                              v0.6.14                     v0.6.15
source を取れない        SOURCE_UNAVAILABLE_OTHER  →  SOURCE_DIRECTORY_MISSING など 8 種類
manifest を読めない      (名前なし)               →  MANIFEST_MISSING / _UNREADABLE / _NOT_JSON
入力 0 件                (名前なし)               →  MANIFEST_INPUTS_EMPTY
工程が終わっていない      入れ子の中にだけ            →  SCOPE_* を top-level へ 7 種類
記録と合わない            null                      →  MISMATCH_* 6 種類

*_OTHERnull で分岐している自動処理があれば、そこは直す必要があります。
OK のときは今までどおり null です。

止め方の名前は 55 → 80 種類になりました。一覧は同梱の
source-verifier-cli-result.v1.schema.jsonstableReasonCode.enum にあります。

v0.6.13 と v0.6.14 の道具は、それぞれ 16 と 17 を名乗ります。
v0.6.12 と v0.6.13 だけはどちらも 16 なので、tool.sha256 で見分けてください。

2. v0.6.14 で「一覧そのものを消しました」と書いたのは誤りでした

v0.6.14 の notes と作業指示:「A は検査を足すのではなく一覧そのものを消しました」
実測: 消したのは冒頭 28〜32 行の 5 種類版だけ。
      **正本 CLI_STATUS_META の 12 行上に、8 種類の手書き一覧が残っていた**
      ——しかも「同じ境界は 1 か所で持つ」と書いた同じコメント塊の中に。

同じ欠陥を直したと宣言しながら、同じファイルの中に同じ形を残していました。

あわせて 2 件。受け手向けのエラー文が v0.6.14 で消したファイル
scripts/reasonCodes.mjs)を指したままで、
SECURITY.md と配布ソースの版数境界は「v0.3.0 より前」のままでした
——ERRATA で notes を訂正したときに、この 3 か所を見落としています。

変異対照(2026-08-14)
  コメントから 1 status を消す      → 新たに落ちた試験 **0 件**
  SECURITY.md を v0.9.9 に書き換え  → **1236 件すべて緑**

文言は 1 か所も検査されていませんでした。

3. catalog に載せた止め方のうち 2 つは、一度も出ていませんでした

実測 2026-08-14(v0.6.14 の道具)
  FIFO を置いた directory   → ENTRY_TYPE_UNSUPPORTED
                              (SOURCE_SPECIAL_NODE は出ない)
  読めない directory        → SOURCE_UNAVAILABLE / SOURCE_UNAVAILABLE_OTHER
                              (catalog は ARCHIVE_UNSUPPORTED と宣言していた)

名前を足すのは無料ですが、受け手はその名前で分岐を書きます。
出ない名前を配るのは、来ない分岐を実装させることです。

訂正は
docs/ERRATA.md
気づいた方法と再現手順つきで積んでいます(今回で 12 件目まで)。


直し方 — 到達性を宣言させ、両方向で照合する

catalog の各項目に reachability を足しました。これは宣言です。
そのまま信じず、毎 run 実測と突き合わせます。

到達すると宣言した code   → この run で**実際に出た**ことを確かめる
到達しないと宣言した code → この run で**出ていない**ことを確かめる

片方向だけだと逃げ道が残ります——宣言を defensive-invariant へ書き換えるだけで、
どんな未配線 code も「合格」にできてしまう。

catalog 80 種類 = corpus 45 / cli-route 27 / defensive-invariant 8
この run で実際に出た 72 種類(残り 8 は「出ていない」ことも実測)

corpus は 182 個 19 種類 → 190 個 20 種類。足した 8 件は、
catalog に前から在ったのに材料が無く、一度も踏まれていなかった止め方です。

こちらで 1 件見つけました

SOURCE_ARCHIVE_MISSING(今回の新設)は到達しません。
存在しない path は、先に directory の判定が SOURCE_DIRECTORY_MISSING で止めるので、
loadFromArchive の存在検査は死んでいます。defensive-invariant として宣言しました。


archivePolicy の中身を、配布 schema が拘束するようにしました

v0.6.14 の schema は、欄の有無も値も検査していませんでした。
外部監査の反例 7 件を、自分の道具の実出力と自分の ajv で再現しています。

                                        v0.6.14    v0.6.15
覆っている範囲の一覧を消す                  適合    → 落ちる
中身を ['TOTALLY_FAKE'] に差し替える        適合    → 落ちる
acceptedTypeflags を偽値に                 適合    → 落ちる
endOfArchiveConvention を偽値に            適合    → 落ちる
limits を {} にする                        適合    → 落ちる
acceptedHeaderFormats を [] にする         適合    → 落ちる
stableReasonCode を TOTALLY_FAKE に        適合    → 落ちる
対照: status を enum の外へ                落ちる  → 落ちる
対照: archivePolicy ごと削除               落ちる  → 落ちる

policyId / policyVersion / policySha256 / coverage を足しました。

⚠️ policySha256 は改竄されていないことの証明にはなりません。
同じ道具が policy と digest の両方を書いているので、両方書き換えれば一致します。
捕まえられるのは、版を跨いだ取り違えと、途中で欠けた欄だけです。

schema を狭めましたが、版は据え置きです——版数判定器で
HOLD_RECORD(狭まった=据え置き可・要記録)と実測しました。


profileId が変わりました。lock を作り直してください

v0.6.14  trs-jack-3d:TRS|JACK-TRS:864677448069
v0.6.15  trs-jack-3d:TRS|JACK-TRS:1ee9fcdb2c88

v0.6.14  trs-jack-3d:TRS|JACK-TRRS:a5cf46509971
v0.6.15  trs-jack-3d:TRS|JACK-TRRS:2eb28f2d173e

この文書から転記せず、同梱の trs-jack-3d-release-index.v1.json から引いてください。

動いた原因は版数上げ(package-lock.json)です。モデルの数値は変わっていません。


まだやっていないこと

  • **実測が 1 件も来ていません。**ジャック内部の接点位置は全部仮定のままで、
    導通測定も音響測定もしていません(verifiedPhysicalfalse)。
  • core と CLI の分離をしていません。外部監査 P2 の指摘です。
    2,890 行の単一ファイルで、単体配布の制約は試験で強制されています。
    bundle 工程を足すと配布物と source の byte 一致を確かめる鎖をもう 1 本作る
    ことになり、
    これだけの変更を入れた直後に同じ版でやるのは危険だと判断しました。
    次の版の最初に、他の変更を混ぜずに着手します。
  • 「非 OK なら理由必須」を schema の条件節で書いていません。
    版数判定器が if/then/else を扱えず、入れると
    この schema の今後の差分がすべて判定不能(=無条件で版上げ)になります。
    規則は道具側と試験で担保しています。
  • BusyBox を oracle に足すこと。開発機にも CI にもありません。
  • annotated tag への署名。
  • 検算ツールの強化は試験した範囲までです。すべての細工に耐えることは示していません
    ——v5 から v16 までに計 33 件見落とし、v9〜v15 では正当な archive を拒みもしていました。
    v0.6.15 では、catalog に載せた止め方のうち 2 つが一度も出ていなかったことが分かっています。

数えたもの

v0.6.14 v0.6.15
単体テスト 1236 1304(skip 0 / 失敗 0)
壊れた tar の材料 182 個 19 種類 190 個 20 種類
うち実際に通る材料 72 72
止め方の名前(catalog) 55 種類 80 種類
うち corpus が踏む 37 種類 45 種類
うちこの run で実際に出た 未計測 72 種類
検証対象(validate:profiles 14 14
schema 23 23(BUMP 0)
記録された入力 32 32
検算ツール toolVersion 17 18
区間 / event TRS 23/32・TRRS 30/36 変わらず

変わっていないもの

  • **モデルの数値。**接点位置も走査結果も同じです。
  • verifiedPhysicalfalse。実測記録は依然 0 件です。
  • 過去 release の asset。v0.1.0 以降の 605 件すべて byte 一致であることを、
    再生成の段ごとに対照つきで実測しています。