diff --git a/docs/known-risks.md b/docs/known-risks.md index 1cb1dc0..d9ae6cb 100644 --- a/docs/known-risks.md +++ b/docs/known-risks.md @@ -25,7 +25,7 @@ | B1 | 再実行で Notion ページを重複作成しない | Preventive(URL 突合で create/update 分岐) | 2(`test_sync_action_creates_updates_and_deletes_pages` + `test_does_not_create_duplicate_when_page_already_exists_in_notion`) | 🟢 OK | — | 2026-06-22 | | B2 | 1 件の Notion API エラーでバッチ全体が止まらない | Preventive(delete/add 両方とも項目単位 try/catch + 継続 + 失敗件数を error ログ。`addNewNotionPages` は `keyNotionPagesByUrl()` で URL 解析失敗も個別隔離。URL が無い Notion ページは削除対象から除外) | 3(`test_continues_syncing_when_one_item_fails` + `test_continues_deleting_when_one_delete_fails` + `test_does_not_delete_notion_page_without_url`) | 🟢 OK | — | 2026-06-22 | | B3 | 未変更項目を毎回 re-PUSH せず API クォータを浪費しない | None(`synced_at`/状態フラグ無し) | 0 | 🔴 Missing | **記録のみ**。API 呼び出し回数が問題化したら `synced_at` を導入 | 2026-06-22 | -| B4 | Notion シークレットを例外/ログ/Discord に流出させない | Preventive(`SecretScrubber` で送出前に伏字化。C3 と一体) | 7(C3 のテストと共有) | 🟢 OK | — | 2026-06-22 | +| B4 | Notion シークレットを例外/ログ/Discord に流出させない | Preventive(`SecretScrubber` で送出前に伏字化。C3 と一体) | 8(C3 のテストと共有) | 🟢 OK | — | 2026-06-22 | ## C. シークレット / 資格情報の取扱い @@ -33,7 +33,7 @@ |----|----------------------------------|------|--------|--------|----------|--------| | C1 | デバッグページが config/スタックトレースを露出しない | Preventive(`.env.example`/`.ci`/`.deploy` すべて `APP_DEBUG=false`、コード既定も false) | 2(`Http/ApiErrorResponseTest` + `Http/WebErrorResponseTest`) | 🟢 OK | — | 2026-06-22 | | C2 | API 例外が生のトレースを返さない | Preventive(`APP_DEBUG=false` で汎用 JSON エラー) | 2(`Http/ApiErrorResponseTest`×2) | 🟢 OK | — | 2026-06-22 | -| C3 | 例外レポート(log/Discord)に機密値を混入させない | Preventive(`SecretScrubber`(伏字化対象は5文字以上の値のみ。"root"等の短い開発用パスワードでの誤爆を回避)+ `RedactSecretsProcessor`/tap + `ConvertDiscord`(親クラスの `addMessageStacktrace` を no-op 化し、未伏字化の生トレースでの上書きも防止) で送出前に伏字化) | 7(`SecretScrubberTest`×4 + `RedactSecretsProcessorTest` + `ConvertDiscordTest`×2) | 🟢 OK | — | 2026-06-22 | +| C3 | 例外レポート(log/Discord)に機密値を混入させない | Preventive(`SecretScrubber`(伏字化対象は5文字以上の値のみ。"root"等の短い開発用パスワードでの誤爆を回避)+ `RedactSecretsProcessor`/tap + `ConvertDiscord`(親クラスの `addMessageStacktrace` を no-op 化し、未伏字化の生トレースでの上書きも防止) で送出前に伏字化) | 8(`SecretScrubberTest`×5 + `RedactSecretsProcessorTest` + `ConvertDiscordTest`×2) | 🟢 OK | — | 2026-08-26(assurance-audit是正: `test_scrub_array_recurses_and_masks_throwable` が `json_encode` 経由でアサートしており例外オブジェクトの伏字化を検証できていなかった欠陥を修正。`scrubArray()` の戻り値を直接アサートする形に変更し、5文字境界値テストを追加) | | C4 | 実シークレットを git に混入させない | Preventive(`.gitignore` で `.env` 除外、committed な `.env.*` は空値) | N/A | 🟢 OK | — | 2026-06-22 | ## D. 公開検索 / Feed / API @@ -43,7 +43,7 @@ | D1 | raw_pages(生 HTML)を公開経路に露出しない | Preventive(検索/Feed で rawPage を eager-load しない、Page に html 列無し) | 2(`PageResourceTest::test_search_does_not_eager_load_raw_page_relation` + `SearchPage/FeedActionTest`) | 🟢 OK | — | 2026-06-22 | | D2 | 非公開コンテンツを検索/Feed に出さない | N/A(Page に公開状態の概念が無い) | — | — | 対象外(将来 status 列を足す場合は再評価) | 2026-06-22 | | D3 | API/Feed 応答に内部フィールドを含めない | Preventive(PageResource 許可リスト + `Page::toFeedItem()`) | 2(`PageResourceTest::test_resource_exposes_only_whitelisted_fields` + `test_feed_item_does_not_expose_internal_fields_or_raw_html`) | 🟢 OK | — | 2026-06-22 | -| D4 | 半分書かれた Page 行を検索に見せない(原子性) | Preventive(`UpdateOrCreatePageWithPaks` が Page 更新 + Pak 同期を `DB::transaction()` で原子化。3 Handler すべてこれを使用) | 2(`Extract/UpdateOrCreatePageWithPaksTest`) | 🟢 OK | — | 2026-06-22 | +| D4 | 半分書かれた Page 行を検索に見せない(原子性) | Preventive(`UpdateOrCreatePageWithPaks` が Page 更新 + Pak 同期を `DB::transaction()` で原子化。3 Handler すべてこれを使用) | 3(`Extract/UpdateOrCreatePageWithPaksTest`) | 🟢 OK | — | 2026-08-26(assurance-audit是正: 「DB::transaction が呼ばれたか」のモック確認と正常系コミットのみで、実際に途中失敗させてロールバックを確認するテストが無かった欠落を発見。`test_page_update_is_rolled_back_when_pak_sync_fails` を追加し、SyncPak/UpdateOrCreatePage が final のため実コードの契約違反経路で本物の失敗を起こしロールバックを検証) |