Agent-almanac monitor-binary-version-baselines

install
source · Clone the upstream repo
git clone https://github.com/pjt222/agent-almanac
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/pjt222/agent-almanac "$T" && mkdir -p ~/.claude/skills && cp -r "$T/i18n/ja/skills/monitor-binary-version-baselines" ~/.claude/skills/pjt222-agent-almanac-monitor-binary-version-baselines-54becb && rm -rf "$T"
manifest: i18n/ja/skills/monitor-binary-version-baselines/SKILL.md
source content

バイナリバージョンベースラインの監視

CLIハーネスバイナリに出現する機能システムマーカーについて、比較可能でバージョンをキーとした記録を構築・維持することにより、リリースをまたいで追加、削除、ダークローンチされた機能を機械的に検出できるようにする。

使用する場面

  • クローズドソースのCLIハーネスの複数リリースをまたいで機能のライフサイクルを追跡する
  • ダークローンチされた機能(出荷されているがゲートオフ)または静かに削除された機能を探索する
  • マーカースキャナが古いバイナリ上で既知の良好なマーカーを検出し続けることを検証する(スキャナ自体の回帰テスト)
  • 後続フェーズ(フラグ発見、ダークローンチ検出、ワイヤーキャプチャ)が消費するPhase 1の基盤を構築する
  • アドホックな
    grep
    が「Xは今日存在するか」に答えるが、実際には「X、Y、Zで構成されるシステムがバージョン間でどう動いたか」が必要な任意のコンテキスト

入力

  • 必須: 同じCLIハーネスのインストール済みバイナリバージョン1つ以上(または抽出済みバンドル)
  • 必須: マーカー定義のための作業用カタログファイル(初回実行で作成、バージョンをまたいで拡張)
  • オプション: 以前の実行で記録済みのベースラインファイル(インプレースで拡張、書き直しはしない)
  • オプション: 未公開と判明しているバージョンのリスト(スキップされたリリース、撤回されたビルド)
  • オプション: 既に追跡中の機能システムのリスト(再発見ではなく拡張)

手順

Step 1: マーカーをカテゴリ別に選定する

リビルドで生き残る文字列を選ぶ。安定していて意味的に有意な識別子を選ぶ — 次のリリースでバンドラにリネームされるミニファイ済みの名前ではない。

推奨されるカテゴリは6つ:

  • API — ハーネスのネットワーク面で公開されるエンドポイントパス、メソッド名
  • Identity — 内部製品名、コードネーム、バージョン識別子
  • Config — ユーザー向け設定ファイルで認識されるキー
  • Telemetry — 分析パイプラインに送信されるイベント名
  • Flag — ゲート述語が消費する機能ゲートキー
  • Function — 特定のハンドラ内で使われる既知の文字列定数(エラーメッセージ、ログラベル)

避けるべきもの: ミニファイされたように見える短い識別子(例:

_a1
bX
、数字付き2文字の名前)、テキスト改訂で変わってしまうインラインリテラル、バンドラ自身の内部命名規則に一致するもの。

Expected: 各候補マーカーにカテゴリタグと短い正当化の注釈(「ユーザー向けドキュメントに出現」「過去Nリリース安定」など)がある。典型的な初回パスでは1システムあたり20-50マーカーが得られる。

On failure: 連続するマイナーバージョン間でマーカーが消失する場合、カタログは安定した識別子ではなくリビルドで変動する文字列を捕まえている。該当エントリを削除し、より長く意味的にアンカーされた部分文字列に広げる。

Step 2: マーカーを機能システム別にグループ化する

独立に進化する能力ごとにシステムテーブル1つにマーカーをまとめる。「システム」とは、機能ライフサイクルを共有するために存在/不在が一緒に動くマーカーの一貫した集合である(例: 仮想の

acme_widget_v3
機能に属するすべてのマーカー)。

グループ化が重要な理由: システム単位のスコアリングが相互汚染を防ぐ。あるシステムのマーカー不在が他のシステムの検出を抑制してはならず、無関係なシステムを跨ぐ合計スコアは情報を持たない。

作業用カタログの形(疑似コード):

catalog:
  acme_widget_v3:
    markers:
      - { id: "acme_widget_v3_init",         category: function, weight: 10 }
      - { id: "acme.widget.v3.dialog.open",  category: telemetry, weight: 5 }
      - { id: "ACME_WIDGET_V3_DISABLE",      category: flag,     weight: 10 }
  acme_other_system:
    markers:
      - ...

Expected: 各システムが自身のマーカーリストを持つ。2つのシステムに同じマーカーが出現することはない。新しいシステムを追加するということは、新しいトップレベルエントリを追加することであり、マーカーをシステム間で後から移動させることではない。

On failure: マーカーを1つのシステムに割り当てるのが難しい(重複、曖昧さ)場合、システムの定義が粗すぎる。システムを分割するか、一部のマーカーは「共有基盤」であると認めてシステム単位のスコアリングから除外する。

Step 3: マーカーをシグナル強度で重み付けする

各マーカーに、その存在単独でシステムを確認できる度合いを反映する重みを割り当てる:

  • 10 = 単独で診断可能 — そのマーカーを見つけるだけでシステムの存在を確認できるほど固有である(例: 他のコードパスが発しない、長いシステム固有の文字列)
  • 3-5 = 補強のみ — 単独で確認するには汎用的すぎるが、集計スコアに寄与する(例: ハーネスが機能をまたいで再利用する短いテレメトリ接尾辞)

具体的な数字ではなく慣習を教える。「診断可能」と「補強」の間の差の方が、選ぶ整数の正確さよりも重要である — 肝心なのは、Step 5の閾値が「1つの強いシグナル」と「多くの弱いシグナル」を区別できることである。

Expected: 各マーカーに重みがある。カタログの重み分布は補強マーカー(3-5)に偏り、システムあたり少数の単独診断可能マーカー(10)がある。

On failure: 全マーカーの重みが10の場合、スコアリングは解像度を失う — 部分存在の発見が不可能になる。複数のシステムに再出現するマーカーや無関係なハンドラに出現するマーカーを降格させる。

Step 4: バージョン単位のベースラインを記録する

スキャンしたバージョンごとに、存在するマーカーと不在のマーカーの両方を、バージョンをキーとして記録する。両方とも証拠である: バージョンNで不在のマーカーは、バージョンN+1で再導入されたときには、存在するマーカーと同じくらい情報を持つ。

ベースラインの形:

baselines:
  "1.4.0":
    acme_widget_v3:
      present: ["acme_widget_v3_init", "ACME_WIDGET_V3_DISABLE"]
      absent:  ["acme.widget.v3.dialog.open"]
      score:   20
  "1.5.0":
    acme_widget_v3:
      present: ["acme_widget_v3_init", "ACME_WIDGET_V3_DISABLE", "acme.widget.v3.dialog.open"]
      absent:  []
      score:   25
  "1.4.1":
    _annotation: "never-published; skipped from upstream release timeline"

未公開のバージョンは、黙示的に省略するのではなく、明示的な注釈を付ける。黙って省略されたバージョンは、次の読み手にはデータ欠損のように見える。

Expected: 各バージョンが追跡中のシステムごとに1つのレコードを生成し、

present
absent
score
が設定されているか、未公開の場合は明示的な
_annotation
が付いている。

On failure: 以前は存在したシステムに対してベースラインスキャンがマーカー0件を返した場合、バイナリパスが正しかったこと、stringsコマンドが出力を生成したこと、マーカーIDがカタログと完全一致することを確認するまで削除と仮定してはならない。偽のゼロは縦断的記録を汚染する。

Step 5: 完全検出と部分検出の閾値を設定する

システムごとに、集計スコアに適用する2つのゲートを定義する:

  • full
    — このバージョンでシステムが存在しアクティブとみなされるスコア以上
  • partial
    — システムが出荷されているが不完全(一部マーカー存在、ただし
    full
    閾値未満)とみなされるスコア以上

partial
未満 = 不在(または進行方向次第では未到達)。

thresholds:
  acme_widget_v3:
    full:    25
    partial: 10

閾値の選択: 健全なインストールが発すると期待する重みの合計を

full
に設定する。1つの診断マーカーと1つの補強シグナルを
partial
に設定する。複数バージョンの証拠が揃ってから再チューニングする。

Expected: 各スキャンがシステムごとにラベル付きの発見を生成する:

full | partial | absent
partial
の発見は調査に値する — それらがダークローンチと削除の候補である。

On failure: 全バージョンで全システムが

partial
を報告する場合、閾値が過度に敏感である(おそらくマーカーの合計では到達できないほど高く設定されている)。システムが検証可能にライブである既知の良好なバージョンに対して再較正する。

Step 6:
strings -n 8
でスキャンする

最小長フィルタ付きの

strings
を抽出プリミティブとして使う。
-n 8
の下限がほとんどのノイズ(短い断片、パディング、アドレステーブルのゴミ)をフィルタし、ほぼ常に8文字超である有意味な識別子は失わない。

strings -n 8 path/to/binary > /tmp/binary-strings.txt

次に、カタログマッチを

/tmp/binary-strings.txt
に対して実行する(任意の行指向マッチャ:
grep -F -f markers.txt
ripgrep
、または小さなスクリプト)。

注意点:

  • より低い最小値(
    -n 4
    -n 6
    )は出力をバイナリゴミとミニファイシンボルノイズで溢れさせる。診断対補強の区別が崩壊する
  • より高い最小値(
    -n 12+
    )は短いフラグ識別子と設定キーを取り逃す
  • 一部のバンドラは文字列を圧縮またはエンコードする。
    strings
    がほぼ空の出力を返す場合、バイナリはバンドル抽出が先に必要かもしれない(このスキルの範囲外)

Expected: バイナリサイズに応じて1k-100k行の1行1文字列の出力。手動検査で最初の100行に認識可能な識別子が見えるはず。

On failure: 出力が空または認識不能な場合、バイナリはおそらくパックされているか、暗号化されているか、

strings
が読めないバイトコード形式で出荷されている。停止して抽出層で解決する。読めないスキャンからベースラインを記録してはならない。

Step 7: 過去レコードを書き換えずにベースラインを前方に拡張する

カタログに新しいシステムやマーカーが追加された場合、前方バージョンのみがそれに対してスキャンされる。過去バージョンのレコードは元のまま残る。

理由: 過去バージョンのベースラインは、当時スキャンされたものの経験的証拠であって、過去バージョンに何が含まれていたかの現時点のモデルではない。新しく発見されたマーカーで遡及的に書き換えると、「今わかっていること」と「当時観測したこと」が混同される。両方とも有用だが、ベースラインファイルに生きるべきは1つだけである。

遡及的スキャンが本当に必要な場合(例: 新しいマーカーがバージョンN-3に存在したかをテストしたい)、別の補遺として記録する:

addenda:
  "1.4.0":
    scan_date: "2026-04-15"
    catalog_revision: "v7"
    findings:
      acme_new_system:
        present: ["..."]

元の

baselines["1.4.0"]
エントリは手付かずのままである。読み手は元のレコードと、それぞれのカタログリビジョンを持つ後の遡及的スキャンの両方を見ることができる。

Expected: ベースラインファイルは単調に前方に成長し、過去レコードは追加のみで、オプションの補遺ブロックを持つ。各スキャンを使用したカタログ状態に結び付けられるよう、カタログリビジョンがバージョン管理されている。

On failure: 過去バージョンの

present
リストを直接編集したい衝動に駆られたら、停止する。代わりに補遺を追加する。過去レコードを変更すると、スキャナの回帰を検出する能力が失われる(後のスキャナ検証パスのStep 8は履歴レコードが不変であることに依存している)。

検証

  • カタログのすべてのマーカーに明示的なカテゴリタグがある(API / identity / config / telemetry / flag / function のいずれか)
  • すべてのマーカーが正確に1つのシステムに割り当てられている。2つのシステムに出現するマーカーはない
  • 重みが実際にある程度の範囲に広がっている(10が一部、3-5が一部)。重みがすべて同一ではない
  • スキャンされた各バージョンが、追跡中のシステムごとに
    present
    absent
    score
    を持つレコードを持つ
  • 未公開バージョンが明示的に注釈されており、黙示的に省略されていない
  • 各システムが
    full
    partial
    の両閾値を持ち、それに応じて発見がラベル付けされる
  • strings -n 8
    が抽出プリミティブである(または非テキストバイナリに対するドキュメント化された同等物)
  • 最新のスキャンによって過去バージョンのレコードが変更されていない。新規発見は遡及的であれば補遺ブロックに存在する

よくある落とし穴

  • 具体的な発見をカタログとして記録する。カタログはマーカーのカテゴリと形状を記述すべきで、バージョンに紐づいたリテラルを列挙すべきではない。発見の形をしたエントリで溢れるカタログは急速に腐敗し、誤って公開された場合に最高の漏洩リスクとなる。
  • ミニファイされた識別子の捕捉
    _p3a
    q9X
    のような名前はリビルドのたびにリネームされる。今日一致しても、明日はノイズである。意味的に有意な識別子に留まる。
  • テレメトリイベントを機能フラグと混同する。多くのハーネスで命名規則を共有するが、異なる役割を果たす。カテゴリでタグ付けする(Step 1)ことで、カテゴリ別の分析が清潔に保たれる。
  • 未公開バージョンを黙って飛ばす。注釈のないバージョン列のギャップはスキャン漏れのように見える。明示的に注釈する:
    _annotation: "never-published"
  • ベースラインデータが存在する前に閾値を設定する。最初のスキャンが経験的な重み合計を確立する。事前ではなく、それに対して閾値をチューニングする。
  • カタログが成長したときに過去バージョンレコードを書き換える。過去レコードは証拠であり、補遺が遡及的スキャンのサポートされたパターンである。
  • 空のスキャン出力を信じる。マーカー0件は常に「不在」を意味しない。削除を宣言する前に、バイナリが読めることとカタログIDが完全一致することを確認する。
  • strings -n 4
    -n 8
    より徹底的だと扱う
    。より低い最小値はシグナルより速くノイズを加える。診断マーカーは本質的に常に8文字以上である。

関連スキル

  • security-audit-codebase
    — 共有された規律。両方のパイプラインがマーカー存在を発見として扱い、異なる下流消費者を持つ
  • audit-dependency-versions
    — 同じバージョン追跡の厳密さを外部依存マニフェストに拡張する。このスキルはそれをバイナリアーティファクトに適用する
  • probe-feature-flag-state
    — Phase 2-3のフォローアップ。ベースラインを消費してフラグ展開状態(live / opt-in / dark / removed)を分類する
  • conduct-empirical-wire-capture
    — Phase 4のフォローアップ。推論された挙動を実際のハーネストラフィックに対して検証する
  • redact-for-public-disclosure
    — Phase 5のフォローアップ。どの発見がプライベートワークスペースを離れられるかを統制する