agentleFS
Sign inSign up

pdf-trust

shuji-bonji/pdf-trust-skill/skills/pdf-trust/SKILL.md

PDF の信頼性監査オーケストレーション。受け取った PDF(契約書・請求書・診療文書・申告書・行政文書など)が「本物か・信用してよいか・改ざんされていないか」を、pdf-verify-mcp を軸に pdf-reader-mcp / pdf-spec-mcp / houki 系 MCP を編成して監査し、推奨判定付きの Trust Report を返す。4 値判定は pdf-verify-mcp の evaluate_policy(決定論的ルールエンジン)が下し、Skill は発火ルールの解説・推奨アクション・法令根拠を担う。ユーザーが「この PDF は信用できる?」「署名を確認して」「受入監査して」「改ざんされてない?」「電子署名の有効性」「長期保存(電帳法・PDF/A・LTV)できるか」「取引先から届いた PDF のチェック」などに言及したら、単発のツール呼び出しで済ませず必ずこの Skill を使う。複数 PDF の一括監査にも使う。

Skill1 starsChanged 3 months ago
---
name: pdf-trust
description: PDF の信頼性監査オーケストレーション。受け取った PDF(契約書・請求書・診療文書・申告書・行政文書など)が「本物か・信用してよいか・改ざんされていないか」を、pdf-verify-mcp を軸に pdf-reader-mcp / pdf-spec-mcp / houki 系 MCP を編成して監査し、推奨判定付きの Trust Report を返す。4 値判定は pdf-verify-mcp の evaluate_policy(決定論的ルールエンジン)が下し、Skill は発火ルールの解説・推奨アクション・法令根拠を担う。ユーザーが「この PDF は信用できる?」「署名を確認して」「受入監査して」「改ざんされてない?」「電子署名の有効性」「長期保存(電帳法・PDF/A・LTV)できるか」「取引先から届いた PDF のチェック」などに言及したら、単発のツール呼び出しで済ませず必ずこの Skill を使う。複数 PDF の一括監査にも使う。
---

# pdf-trust — ドメイン別 PDF 信頼性監査

PDF family の trust 層を担う Skill。自前の検証ロジックは持たず、
検証は必ず MCP ツールの結果を根拠にする。**構造解析からの推測で真正性を代替しない**こと —
「本物か」に答えられるのは暗号学的検証だけであり、それは pdf-verify-mcp の仕事。

中核原則(family 共通):

1. 内容の真偽は判定しない — 判定するのは真正性(原本性・完全性)のみ
2. 検証結果は技術的事実として返し、解釈・最終判断は利用者に委ねる
3. 判定の根拠(どのツールの何の結果か)を必ず明示する
4. **ジャッジはコード、ナラティブは LLM** — 4 値判定は `evaluate_policy`
   (pdf-verify-mcp v0.7.0+ の決定論的ルールエンジン)が下す。この Skill(LLM)の
   仕事は firedRules の解説・推奨アクションの文章化・法令根拠の引用であり、
   判定の上書きではない

## 前提 MCP

| MCP | 必須/任意 | 役割 |
|---|---|---|
| pdf-verify-mcp(**v0.21.0+ 推奨**) | **必須** | `evaluate_policy` による 4 値判定・署名検証・改ざん検知・PAdES レベル・PDF/A 検証・PDF/UA 検証(`validate_conformance` の `flavour: "pdfua-1"`)。**v0.10.0 で `verify_integrity` にリビジョン間のオブジェクト単位差分**が入り、**v0.11.0 で PDF/A-4(`pdfa-4` / `pdfa-4e` / `pdfa-4f`)が受けられるようになった**(下記「署名後の変更の特定」)。**v0.15.0 で xref チェーンの歩き方が是正され、追えない `/Prev` を完全なチェーンとして飲み込まなくなった** — 全履歴を約束する legal / medical では**この版以降でないと報告書が書けない**(下記 Phase 2.5 の 2)。**v0.16.0 で `verify_integrity` が `revisionChain` を返すようになった** — それまでは打ち切りが `notes` の英文にしか出ておらず、「全履歴を約束してよいか」を**散文の照合で決めていた**。**v0.17.0 で `revisionCountAgreement` が返るようになった** — `revisionCount`(startxref の個数)と `revisions.length` の食い違いに説明が付いているかをフィールドで読める(下記 Phase 2.5 の 2)。v0.7.0〜0.9 は差分が無いだけで **verdict は同一**(ルール表は不変。advisory は 0.7.1 / 0.8.0 で追加)。v0.7.0 未満は evaluate_policy が無くフォールバック手動判定に縮退。**v0.20.0 で全ツールの報告の先頭に `scope`(判定の射程)が入った** —— 相互参照表を組み直した文書かどうかがここでしか分からない(Phase 1.5)。**v0.20.0 で、条文を名指しする拒否の `code` が `INTERNAL_ERROR` から `PARSE_FAILED` に変わった** —— それ以前は所見と故障が同じ code で返っていた。**v0.21.0 で `verify_signatures` と `detect_pades_level` の JSON の最上位が配列から辞書になった**(`{ scope, signatures: [...] }` / `{ scope, levels: [...] }`)。署名の一覧だけを読んでいると、一覧が不完全であることに気づけないため。**v0.29.0 で JSON の応答を文字数で切らなくなった** — それまでは 25,000 文字で構造の途中で切れ、`isError: false` のまま読めない本文が届いていた。代わりに配列ごとに件数の上限(署名・リビジョン 32、違反・結果 200)が掛かり、切ったときは配列の隣に `xxxTruncated: { returned, total }` が出る(下記 Phase 2.5 の表) |
| pdf-reader-mcp(**v0.10.0+ 推奨**) | 任意(**位置特定が要るなら実質必須**) | 署名フィールド構造・メタデータ。**v0.10.0 の `locate_objects`** で「変わったオブジェクト」を「ページ + 矩形」に落とせる。※ PDF/UA 検証は verify の `validate_conformance` へ移管済み(reader の `validate_tagged` / `validate_metadata` は非推奨) |
| pdf-spec-mcp | 任意 | 逸脱時の ISO 32000 根拠引用 |
| houki-egov / houki-nta / tax-law / labor-law | 任意 | 法令根拠(プロファイルが指定) |

pdf-verify-mcp が未接続なら監査は成立しない。その旨を伝え、`npx @shuji-bonji/pdf-verify-mcp`
の接続を案内して停止する。任意 MCP が無い場合は縮退動作し、レポートの該当項目に
「未実施(ツール未接続)」と明記する — 黙って項目を落とすと「チェック済みで問題なし」と誤読される。

## 手順

### Phase 0 — 目的とプロファイルの特定

ファイルパス(絶対パス)と利用目的を確認し、プロファイルを選ぶ。目的が不明瞭でも
文書種別から推定できることが多い。推定した場合はレポートに推定根拠を書く。

| プロファイル | 想定文書 | 参照 |
|---|---|---|
| contract | 契約書・NDA・発注書 | [references/contract.md](references/contract.md) |
| financial | 請求書・決算書・申告書・領収書 | [references/financial.md](references/financial.md) |
| legal | 訴訟資料・法務文書全般 | [references/legal.md](references/legal.md) |
| medical | 診療情報提供書・検査報告書 | [references/medical.md](references/medical.md) |
| government | 行政文書・公共告知 | [references/government.md](references/government.md) |
| general | 上記以外 | プロファイル追加チェックなし |

**該当プロファイルの references ファイルをこの時点で読む**(必須チェック・閾値・法令照合先が
書いてある)。あわせて次も確認する:

- trust_anchors(信頼する CA 証明書)を持っているか → あれば Phase 1 で渡す
- 長期保存が目的に含まれるか(「保存」「アーカイブ」「電帳法」「10年」等)→ Phase 3 の長期保存チェックを追加
- 暗号化 PDF でパスワードを知っているか → `password` パラメータで渡す

### Phase 1 — 基礎検証と判定(全ファイル一括)

全対象ファイルに対して `pdf-verify-mcp: evaluate_policy` を実行する(v0.7.0+)。
`profile` に Phase 0 で選んだプロファイルを渡し、trust_anchors・password があれば渡す。
`check_revocation` はプロファイル指定に従う(既定 `embedded`。`online` は外部への
HTTP アクセスを伴うため、プロファイルが要求する場合もユーザーに一言断ってから)。
pdf-verify-mcp 0.28.0 以降の失効確認の引数は、次のように扱う。

- `revocation_freshness`(秒、既定 86400):ユーザーやプロファイルの指定が無ければ
  **渡さない**(既定の 24 時間を使う)。判定を通すために勝手に広げない
- `trusted_ocsp_responders`:ユーザーが信頼する OCSP 応答者の証明書を渡したときだけ渡す

**最終判定(recommendation)は evaluate_policy の `verdict` をそのまま使う。**
このツールは verify_signatures / verify_integrity / detect_pades_level(長期保存
プロファイルでは validate_conformance も)を内部で実行し、固定ルール表で決定論的に
4 値判定する。同じファイル・同じプロファイルなら常に同じ判定になる。
**LLM(この Skill を実行しているあなた)が verdict を上書きすることは禁止** —
firedRules / advisories を「なぜこの判定になったか」の解説材料として使うこと。
文書の本文内容(契約金額・重要度など)を判定材料にしてはならない。

接続先の pdf-verify-mcp が古く evaluate_policy が無い場合のみ、旧手順
(verify_signatures + verify_integrity を個別実行し、後述のフォールバック判定表で
判定)に縮退し、レポートに「判定: 手動判定(evaluate_policy 未使用)」と明記する。

複数ファイルの一括監査では、まず evaluate_policy だけを全件に回して問題のある
ファイル(reject / human_review_required)を特定し、Phase 2 以降の深掘りは
問題のあるファイルに絞る(全件深掘りは時間と文脈の無駄遣い)。

### Phase 1.5 — 読んだ範囲を確かめる(verify v0.20.0+)

**判定を読む前に `scope` を読む。** v0.20.0 から 5 本、v0.21.0 から 7 本すべての
ツールが報告の先頭に `scope` を返す。**判定ではなく、判定の射程**である。

見るのは 3 つ。

| `scope` | 意味 | 報告に書くこと |
|---|---|---|
| **`reconstructed: true`** | 🔴 **相互参照表は verify が組み直したもの**で、ファイルが持っている表ではない | **必ず書く。**「違反なし」も「署名 N 本」も、**推測した表の上での話**である。全履歴・全署名を約束しない |
| `chainStop.kind !== 'complete'` | チェーンを最後まで歩けていない(`prev-zero` / `unreadable` / `cyclic` / `malformed`) | 古いリビジョンが読めていない可能性を書く。`verify_integrity` の `revisionChain` と合わせて読む |
| `encrypted: true` かつ `authenticated: false` | 鍵が導けず、**オブジェクトを 1 つも読んでいない** | 「調べた結果として問題なし」ではない。パスワードの入手を促す |

`recovered: true` は「ライブラリがそのまま読めず、verify が組み立てた」の意味で、
`refusal` にその理由(条文を名指しする文)が入る。**`refusal` の文面で分岐しない**
—— normativepdf の版が上がれば言い回しが変わる。分岐は `recovered` と
`chainStop.kind` で行う。

v0.19.0 以前には `scope` が無い。そのときは「射程は確認していない」と書く
(「射程に問題は無かった」ではない)。

### Phase 2 — 結果の解釈

判定は Phase 1 の evaluate_policy が済ませている。この Phase の仕事は
**firedRules の各ルールを人間に説明できるようにする**こと。深掘りが必要なときは
`verify_signatures` / `verify_integrity` を個別に呼んで詳細(cms.error・notes・
certificatePath 等)を取得する。解釈の背景知識として次の表を使う:

| 観測(firedRules / facts) | 意味 | 解説・推奨アクションに書くこと |
|---|---|---|
| POL-CAUTION-TRUST-NOT-EVALUATED | 暗号学的完全性のみ確認。**署名者の身元は未評価** | 必ずその旨を明記。trust_anchors の提供を促す |
| (何も発火せず trust_and_use) | 完全性 + 署名者身元 + 失効とも確認 | 最良の状態 |
| POL-CAUTION-TRUST-UNTRUSTED | 署名は有効だがチェーンがアンカーに到達しない | 中間 CA 不足か、アンカー相違。trust.detail を確認 |
| POL-REJECT-INVALID / POL-REJECT-REVOKED | ダイジェスト不一致・署名検証失敗・失効 | 原因を verify_signatures の notes で特定して解説。pdf-verify-mcp 0.27.0 以降、失効(`revoked`)の署名は `verdict: indeterminate` になり、POL-REVIEW-INDETERMINATE も同時に出る。「改ざん」とは書かず、「失効より前の署名だと示す材料が無い」と説明する |
| POL-REVIEW-INDETERMINATE | 未対応形式 or 検証未完了 | 下記の切り分けへ |
| POL-REVIEW-UNSIGNED-REQUIRED / POL-CAUTION-UNSIGNED | 真正性の技術的裏付けなし | 入手経路など他の補強手段を提案 |
| POL-CAUTION-REVOCATION-UNKNOWN | 失効情報が確認できなかった | 「失効していない」とは言えない。verify_signatures の `revocation.detail` で理由を確かめて説明する。埋め込みデータが無い → online 再試行を検討。「too old」(`thisUpdate` が `revocation_freshness` より古い)→ 失効情報の発行時刻(`revocation.thisUpdate`)と検証時刻(`validationTime`)を示す。許容幅を広げるかはユーザーに判断を委ねる。「NOT verified」→ 失効情報の署名を検証できなかった。発行 CA 以外が発行した OCSP 応答者なら、`trusted_ocsp_responders` の指定で解決するか尋ねる。中間 CA の結果は `trust.chainRevocation` にある |
| POL-CAUTION-REVOKED-AFTER-SIGNING | 署名者証明書は失効しているが、タイムスタンプが失効より前の署名であることを示している(pdf-verify-mcp 0.27.0+) | 署名は有効と説明してよい。ただし証明書は現在失効しているので、この証明書による新しい署名は信用しない旨を添える。`validationTime` と `revocation.revocationTime` を示す |

🔴 **ツールが `isError` を返したとき、それが「調べられなかった」とは限らない。**
`code: "PARSE_FAILED"` で、`message` が条文を名指ししている(`§7.5.4` など)なら、
それは **文書についての所見**である —— その文書は ISO 32000 の構造条文に反していて、
書かれたとおりには読めない。`suggestion` にもそう書いてある
("This is a finding about the file, not a failure of this server")。

**これを「未実施項目(ツール未接続・取得失敗)」に入れてはいけない。**
入れると、条文違反が「調べられませんでした」として報告され、
**壊れた文書ほど無罪になる。** 根拠の表に「構造が条文に反していて読めない
(§X.Y.Z)」と書き、recommendation に反映する。

`code: "INTERNAL_ERROR"` は本当にサーバ側で落ちたときだけ。こちらは未実施項目でよい。
(v0.19.0 以前は条文の拒否も `INTERNAL_ERROR` で返っていた。`message` に `§` が
含まれているかで見分ける。)

indeterminate の切り分け: cms の error / notes を読む → SubFilter 未対応
(adbe.pkcs7.sha1 等)か、CMS パース失敗か、暗号化で復号できないか。pdf-reader-mcp が
あれば `inspect_signatures` で構造側から確認する。

知っておくべき実挙動(実測に基づく):

- 自己署名のリーフ証明書そのものを trust_anchors に渡すと、チェーンエンジンは
  「not a CA certificate」で untrusted にする(直接信頼リーフは非対応)。CA 証明書を渡すこと
- untrusted のメッセージは原因を区別できる: 「not a CA certificate」= 渡したアンカーが
  CA でない、「No valid certificate paths found」= アンカーが署名者チェーンと無関係。
  後者は正しい CA 証明書の入手を促す
- indeterminate + 「CMS payload is not valid BER/DER」+ Embedded certificates: 0 は、
  改ざんではなく署名生成ツール側の不備(証明書の埋め込み漏れ等)のサイン。
  改ざんと断定せず「検証不能な署名」として human_review_required に送る
- 署名済み PDF を後から暗号化・再保存すると署名は壊れる(INVALID は改ざんとは限らない —
  再保存の痕跡かもしれない。verify_integrity の増分更新情報と突き合わせる)
- 増分更新は合法(DSS 追加・連署など)。「署名後に変更あり」= 改ざんではなく、
  DocMDP 違反や digest 不一致と組み合わせて判断する(連署と本文書き換えの読み分けは Phase 2.5)
- **linearized PDF は `Revisions:` の数字が +1 に見える** — 線形化(ISO 32000-2 Annex F)は
  1 回の保存で xref を 2 つ持つため。**verify が併合するのは `revisions[]` の側だけで、
  `revisionCount` / `incrementalUpdateCount` は今も `startxref` の個数を数えている**。
  v0.17.0+ は食い違いに説明が付いているかを **`revisionCountAgreement`** で返すので、
  **数字を引用する前にそれを読む**(読み方と旧版の退避は Phase 2.5 の 2)

### Phase 2.5 — 署名後の変更の特定(verify v0.10.0+)

「署名後に N バイト足された」を「何が足されたか」に上げる。**Phase 1 の verdict は動かさない** —
増分更新は PDF で合法(ISO 32000-2 §7.5.6)なので、これは見るべき場所の特定であって
改ざんの証明ではない。

1. `pdf-verify-mcp: verify_integrity` の **`revisions[]`** を読む。各リビジョンの
   `changes[]` に `change`(added / modified / freed)・`type` / `subtype`・`role`(人間向けの役割)・
   `bookkeeping`・`inObjectStream` が付く。
   **`objectChangesAfterLastSignature` は「最後の署名以降」だけのショートリスト**なので、
   連署文書(署名 1 と署名 2 の間に足されたもの)や legal / medical の「全履歴」要求には足りない。
   **件数を数値で引用するなら `response_format: "json"`** — markdown には `changeCount` /
   `changesTruncated` が出ない
2. 🔴 **`revisionChain.status` を読む。`revisions[]` が返ったことは「歩き切れた」ことを意味しない。**
   チェーンが途中で切れた場合も、最新セクションが読めず古い入口から入った場合も、
   リストは**短いまま返る**。しかもそのとき残った 1 件は「元版」扱いになるので、
   `changeCount: 0` / `changes: null` / `objectChangesAfterLastSignature: []` になり、
   **機械が読めるフィールドは全部「何も足されていない」と言う**。

   | `revisionChain.status` | 書いてよいこと |
   |---|---|
   | `complete` | 「全履歴」。**ここだけ**「一覧に出てこない = 行われていない」と書ける |
   | `partial` | 「辿れた範囲の履歴」。`missing` が `'oldest'`(元版側)/ `'newest'`(最後の追記)を名指す |
   | `unwalkable` | 「未確認」。`revisions` は null。**「変更なし」ではない** |

   🔴 **`revisionCount` を「保存回数」として引用する前に `revisionCountAgreement` を読む**(v0.17.0+)。
   `revisionCount` は `startxref` の個数、`revisions.length` はチェーンが到達した xref セクション数で、
   **2 つは合法に食い違う**。このフィールドは食い違いに説明が付いているかを言う。

   | `revisionCountAgreement` | 意味 | 書いてよいこと |
   |---|---|---|
   | `status: "agree"` | 2 つの数が一致 | 数をそのまま引用できる |
   | `status: "accounted"` + `causes: ["linearised"]` | 線形化(1 回の保存が xref を 2 つ持つ)。1 リビジョンに併合済み | 「startxref は N 個だが、線形化のため保存は N−1 回」 |
   | `status: "accounted"` + `causes: ["chain-incomplete"]` | チェーンが全セクションに到達していない。**どちらの端が欠けたかは `revisionChain.missing`**(ここでは繰り返されない) | 数の差を打ち切りの帰結として報告する |
   | `status: "unaccounted"` | **歩き切っていて線形化でもないのに数が合わない** = 歩いたチェーンが到達しない `startxref` がファイルにある | 「差分未説明」として human_review 対象に含める |

   **版による退避はここだけに置く**(各プロファイルの文書では繰り返さない):

   - **v0.17.0 未満** — `revisionCountAgreement` は無い。食い違いの説明は notes の英文 1 文だけで、
     その文は**原因を 2 つ並べてどちらなのかを言っていない**。数字ではなく notes と `revisions[]` を読む
   - **v0.16.0 未満** — `revisionChain` は無い。`notes` に
     「chain ended before reaching the original revision」(= `missing: 'oldest'`)または
     「last "startxref" does not point at a parseable cross-reference section」
     (= `missing: 'newest'`)が出ていないかを見る。**「歩き切れた」と言う note は無い**ので、
     完全性はその 2 文が**無いこと**でしか表せない
   - **v0.15.0 未満** — 追えない `/Prev 0` を「前のセクションは無い」として飲み込み、
     打ち切りを立てずに完全なチェーンとして返していた(8 リビジョン 5 署名の実検体が
     「1 リビジョン」として報告された)。**全履歴を要求するプロファイル(legal / medical)は
     この版では成立しない。版数を上げてから測り直す**
3. reader があれば **`pdf-reader-mcp: locate_objects`** にそのオブジェクト番号を渡し、
   `locations[]` を得る。矩形は PDF 座標系・左下原点・pt・正規化済み(= writer `add_annotation` が
   そのまま取る形)。**1 オブジェクトが複数ページに載ることがあり、`page` も `rect` も null になりうる**
4. レポートには **`basis` を必ず転記する**。4 値の意味:
   `annotation-rect`(注釈自身の `/Rect` = 正確)/ `page-box`(ページの箱 = ページ全体)/
   `page-content-stream`(**ページ全体**。変更箇所ではない)/ `page-resource`(矩形は存在しない)

**「無い」と「分からない」を混ぜないための区別。どれも空・不在を根拠にしてはいけない**:

| 出てくるもの | 意味 | 書いてはいけないこと |
|---|---|---|
| `revisionChain.status: "unwalkable"`(`revisions: null` と対で立つ) | xref チェーンを**辿れなかった** | 「変更なし」「差分なし」 |
| `revisionChain: { status: "partial", missing: ["oldest"] }` | **チェーンが途中で切れた**(壊れた / 巡回する `/Prev`・リビジョン上限)。`revisions[]` は返るが**そのファイルの全リビジョンではない** | リストの件数を「このファイルのリビジョン数」として報告する |
| `revisionChain: { status: "partial", missing: ["newest"] }` | 最新セクションが読めず、**古い入口から入った**。**最後に足されたものはリストに載っていない** | 一覧を「署名後の変更の全部」として報告する |
| `revisionChain.missing: []` | **`status` が `complete` のときだけ**空になる。`unwalkable` では `["oldest","newest"]` が入る | 空を見て「何も欠けていない」と読む(`status` を先に見る) |
| `revisionCountAgreement.status: "unaccounted"` | 数の食い違いに**ファイルから読めた説明が付かない**(線形化でも打ち切りでもない) | 「合法な食い違い」「誤差」として流す |
| `objectChangesAfterLastSignature: []` | **`revisions: null` のときも、打ち切りで 1 件だけ残ったときも空になる**。空 ≠ 署名後に何も書かれていない | 空を「署名後の変更なし」と読む |
| `changesTruncated: true` | 一覧は 25 件/リビジョンで打ち切り。真の総数は同じリビジョンの `changeCount`(`revisions[]` 側のフィールド) | 列挙した件数を全件として報告する |
| `signaturesTruncated: { returned, total }`(`verify_signatures`、v0.29.0+) | 署名フィールドが 32 件を超えた。一覧に無い署名は**検証していない**(判定が無い) | 一覧の件数を署名数として報告する。一覧に無い署名を「無い」と書く。**`evaluate_policy` は全署名を検証している**ので、判定はそちらの `verdict` で言う |
| `facts.signaturesTruncated`(`evaluate_policy`、v0.29.0+) | `verdict` は全署名で下されている。facts の一覧だけが 32 件で切れている | facts の件数を署名数として報告する(署名数は `facts.signatureCount`) |
| `revisionsTruncated`(`verify_integrity`、v0.29.0+) | `revisions[]` は新しい順に 32 件まで。歩いた全体は `revisionCount` / `revisionChain` | 一覧の件数をリビジョン数として報告する |
| `violationsTruncated` / `resultsTruncated`(`validate_conformance` / `validate_clauses`、v0.29.0+) | 一覧は 200 件まで。`compliant`・`failedRules`・`violations`(数)・`notDecided` は全件で数えてある | 一覧の件数を違反数として報告する |
| `inObjectStream: true` | オブジェクトストリームの中にあるので**型が読めていない**(`type` / `role` は null) | 「型なし」を性質として報告する |
| `basis: "page-content-stream"` / `"page-box"` | 矩形は**ページ全体** | 矩形を「変更された領域」として示す |
| `locate_objects` の `found: false` | そのオブジェクトは今の文書に存在しない(後のリビジョンで free された = 想定内) | 「異常」「エラー」 |

`bookkeeping: true`(xref ストリーム・オブジェクトストリーム)は**どの保存でも書き換わる**ので、
本文の変更と並べて数えない。

### Phase 3 — プロファイル別チェック

references/<profile>.md の必須チェックのうち、evaluate_policy が扱わないもの
(署名時刻の突き合わせ、PDF/UA 検証など)を実行する。**増分更新履歴の詳述は Phase 2.5 で済んでいる**
(legal / medical プロファイルが要求する「全履歴」「署名後変更の全注記」はそこが担う)。
PAdES レベルと PDF/A 適合は evaluate_policy が facts / advisories に出している
(financial / government プロファイルでは PDF/A 検証込み)。深掘りが必要なら:

1. `pdf-verify-mcp: detect_pades_level` — 構造が B-LT / B-LTA に一致しなければ、証明書失効後に
   検証不能になるリスクを警告(LTV データの実在検証込みなので「宣言だけの B-LT」も検出される)。
   **返り値の `normativeBasis` は常に `"T3"`** — レベルは観測であって適合判定ではないので、
   レポートには「構造が一致する」と書く(上記「報告するときの言い方」)
2. `pdf-verify-mcp: validate_conformance` — PDF/A 適合。engine は auto のまま
   (veraPDF があれば権威的結果、なければ内蔵サブセット。どちらだったかをレポートに書く —
   native の「違反なし」は認証ではない)。
   **flavour は文書の自称に合わせる**(`identify_conformance` が返す part をそのまま使う)。
   part 4 の文書は `pdfa-4`、**`pdfaid:conformance` が `F` なら `pdfa-4f`**。
   -4 は **`pdfa-4b` という flavour が存在しない**(conformance level を持たない規格)ので、
   -3b の癖で `4b` と書かない。**v0.11.0 未満の verify は -4 を `INVALID_FLAVOUR` で弾く** —
   その場合は「この環境では PDF/A-4 を採点できない」とレポートに書く(黙って -3b で測らない)。
   **添付を持つ -4 文書を素の `pdfa-4` で測ると `6.9-3`(添付自身が PDF/A でない)で落ちる**が、
   それは文書の欠陥ではなく**こちらの flavour 選択の誤り**でありうる。自称が `-4f` なら `-4f` で測る

### Phase 4 — 法令根拠(プロファイル指定時)

プロファイルが法令照合を指定する場合、houki 系 MCP で根拠条文を取得する。
houki 系 MCP のサーバ指示(原文引用・出典 URL 明記)に従うこと。取得できなければ
「法令根拠: 未取得」と明記(勝手に条文番号を記憶から書かない)。

### 報告するときの言い方 — 規範の 3 層(T1 / T2 / T3)

**同じレポートの中で、項目ごとに言える強さが違う。** 正典は `specs/09-family-scope.md §2`。
verify の出力は `normativeBasis` フィールドと注記でこれを示してくるので、**それに従って語彙を選ぶ**。

| 層 | 対象 | 書いてよい | 書いてはいけない |
|---|---|---|---|
| **T1** | ISO 32000(署名構造・増分更新)・ISO 14289(PDF/UA) | 条文を引用して断定できる | — |
| **T2** | **PDF/A(ISO 19005)** | 「**veraPDF が COMPLIANT と判定**」 | 「ISO 19005-3 準拠」 |
| **T3** | **PAdES(ETSI EN 319 142)** | 「**構造が B-LT に一致する**」「LTV データが署名者証明書を覆っている」 | 「**PAdES B-LT に適合**」 |

**T3 が特殊なのは、判定者すら居ないこと。** T2 には veraPDF という第三者検証器があるので
「誰が判定したか」を名指しできるが、**PAdES には規範も検証器も無く、構造を観測しているのは
family 自身**である。だから「veraPDF はこう言った」のような逃げ方ができない —
**「これは観測であって適合判定ではない」と述べるしかない**。

`detect_pades_level` は各レポートに `normativeBasis: "T3"` を返し、markdown では
レベルより**前**に注記を置く。**その注記をレポートへ運ぶこと**(数字だけ抜き出さない)。

> **なぜここまで言うか**: 受入監査のレポートは、相手に「この文書は PAdES B-LT 準拠です」と
> 伝えるために使われる。**適合の刻印は、押した者が責任を負う。** ETSI の原文を読んでいないのに
> 適合と書けば、それは実測していないことを実測したかのように述べたことになる。
> `pdf-writer-mcp` が「ラベルは書けるが、規格どおりにはできない」と言うのと、向きが逆の同じ話。

### Phase 5 — Trust Report 生成

以下のテンプレートで報告する。複数ファイルの場合は冒頭にサマリ表を置き、
問題のあるファイルのみ個票を付ける。

```markdown
# PDF 信頼性監査レポート

- 対象: <ファイル名(複数なら件数)> / プロファイル: <profile>(<選定根拠>)
- 実施日時・使用ツール: <MCP 名とバージョン情報が得られれば>

## 判定: <evaluate_policy の verdict をそのまま>

<1〜3 行の要約。発火ルールがなぜ発火したかの平易な説明>

## 根拠

| 検査 | 結果 | 根拠ツール | 規範根拠 |
|---|---|---|---|
| **読んだ範囲** | <scope.reconstructed が true なら「相互参照表は verify が組み直した推測」。chainStop が complete でなければその旨。両方なければ「ファイルの相互参照表どおりに読めた」> | すべてのツールの `scope` | — |
| 総合判定(発火ルール: <POL-... の列挙>) | ... | evaluate_policy | — |
| 署名の暗号学的有効性 | ... | evaluate_policy (facts) / verify_signatures | — |
| 署名者の身元(信頼チェーン) | ... | verify_signatures (trust) | — |
| 失効確認 | ... | verify_signatures (revocation) | — |
| 署名後の変更 | ... | verify_integrity | T1(ISO 32000 §14.4 / §12.8) |
| PAdES レベル | **構造が <B-T 等> に一致** | detect_pades_level | **T3(観測。ETSI 原文は照合していない)** |
| PDF/A 適合 | **veraPDF が <COMPLIANT 等> と判定** | validate_conformance | **T2(判定者は veraPDF)** |
| <プロファイル別項目> | ... | ... | ... |

<Phase 2.5 を実施したときのみ、次の節を出す。
 実施しなかった / できなかったなら、節ごと出さずに理由を「警告・制限事項」に書く —
 空の節を置くと「調べて何も無かった」と読まれる>

### 署名後に書かれたオブジェクト

| リビジョン | オブジェクト | 変更 | 役割 | ページ | 矩形 | 根拠 |
|---|---|---|---|---|---|---|
| 3 | 27 | added | annotation (Highlight) | 1 | 72, 700, 300, 720 | annotation-rect |
| 3 | 6 | modified | stream (content, image or embedded data) | 1 | ページ全体 | page-content-stream |

増分更新は PDF で合法(ISO 32000-2 §7.5.6)。**この表は「見るべき場所」であって改ざんの証明ではない。**
`basis` 列は必ず残す — `page-content-stream` と `page-box` の矩形はページ全体であって変更箇所ではない。
`changesTruncated` なら「全 <changeCount> 件のうち <n> 件を表示」と明記する(件数は json 出力で取る)。
`signaturesTruncated` なら「全 <total> 件のうち <returned> 件を個別に検証。残りは evaluate_policy の判定にのみ含まれ、個別の結果は無い」と明記する。他の `xxxTruncated` も同じ形で「全 <total> 件のうち <returned> 件を表示」と書く。

## 警告・制限事項

- <trust_anchors 未指定なら必ず: 「valid は暗号学的完全性のみの主張であり署名者の身元は未評価」>
- <PAdES に言及したなら必ず: 「PAdES レベルは構造からの観測であり、ETSI EN 319 142 の原文照合ではない」>
- <PDF/A に言及したなら必ず: 「判定者は veraPDF。ISO 19005 の条文は確認していない」>
- <scope.reconstructed が true なら必ず: 「相互参照表は verify が組み直したもので、ファイルが持っているものではない。以下の判定はその表の上での話」>
- <未実施項目(ツール未接続・取得失敗)。**条文を名指しする PARSE_FAILED はここに入れない** —— それは所見であって未実施ではない>

## 推奨アクション

- <trust_anchors の入手、online 失効確認、LTV 化、人手レビュー対象箇所など>
```

### recommendation の判定

**判定は evaluate_policy の verdict をそのまま使う。LLM による上書き禁止。**
判定表・プロファイル上書き(medical の格上げ等)はすべて verify 側の
ルールエンジン(`services/policy-engine.ts`)にコード化されており、この Skill 側で
再現・変更しない。firedRules に無い理由で判定を変えたくなったら、それは
ルールエンジンの改善要望として pdf-verify-mcp に issue を立てる。

**フォールバック判定表**(evaluate_policy が無い旧バージョンに接続した場合のみ):

| 判定 | 条件 |
|---|---|
| trust_and_use | valid + trusted + 失効なし(good / 失効情報が embedded で確認)+ プロファイル必須チェック全通過 |
| use_with_caution | valid だが trust: not_evaluated / untrusted、または revocation: unknown、または任意チェックに軽微な指摘 |
| human_review_required | indeterminate、DocMDP 違反疑い、プロファイル必須チェックの不合格、署名なし(署名必須プロファイル) |
| reject | invalid(digest 不一致・署名検証失敗・失効確認済み) |

フォールバック時もプロファイルの references の上書き条件(例: medical は
use_with_caution を human_review_required に格上げ)を優先し、レポートに
「判定: 手動判定(evaluate_policy 未使用)」と明記する。

## やらないこと

- 内容の真偽・法的有効性の最終判断(技術的事実と法令根拠の提示まで)
- **evaluate_policy の verdict の上書き**(本文内容・重要度・文脈を判定材料にしない)
- 構造解析(inspect_signatures 等)だけを根拠にした「有効/無効」の判断
- 記憶からの条文引用(必ず houki 系 MCP で原文を取得)

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.