Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ updates:
# Microsoft.EntityFrameworkCore.SqlServer へ同じ変更セットで上げる
# (この ignore は残したままでよい)。
#
# 解決版と EF Core の宣言版のメジャー一致・床値の後退・この保留の消失や効きすぎは、
# いずれも EfCorePackageAlignmentTests が機械的に検出する。
# この保留と対になる検査は EfCorePackageAlignmentTests にある。
# 【何を検出し、何が漏れるか】は CLAUDE.md §3 の同名の節を唯一の一覧とする
# (ここに書き写すと検査を足したとき片方だけ古くなる。実際 SqlClientPin_StaysADirectReference
# を足したときに、この行が 1 件少ないまま取り残された)。
- dependency-name: "Microsoft.Data.SqlClient"
update-types: ["version-update:semver-major"]

Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,15 @@ catch (DbUpdateConcurrencyException) { TempData["Warning"] = "..."; return ...;
### 注意点(固有の不変条件)

- **マイグレーションは起動時 `Database.Migrate()` で自動適用** — モデル変更は同一変更セットでマイグレーションを追加する。
- **ロックファイルは全プロジェクト分を同時に再生成する** — `packages.lock.json` は `src/IncidentInsight.Web` と `tests/IncidentInsight.Tests` の 2 つある。テストプロジェクトは web を `ProjectReference` で参照しているため、**web の依存が動くとテスト側の解決結果も動く**。片方だけ更新すると CI の `dotnet restore --locked-mode` が `NU1004: The project references incidentinsight.web whose dependencies has changed` で落ちる。**とくに Dependabot の nuget PR は `src/` 側しか更新しないので、そのままではマージできない**(実例: PR #180 → #181 で手当て)。取り込むときは `dotnet restore` を流して 2 ファイルとも再生成し、同一コミットに含める。ロックファイルを持つプロジェクトを増やしたときも同じ(`RestorePackagesWithLockFile` を宣言し忘れたプロジェクトは locked-mode restore も `EfCorePackageAlignmentTests` の検査もすり抜けるため、同テストが宣言漏れ自体を検出する)。
- **`HasAtLeastOneValidMeasure` をバイパスしない**(インシデントは予防策が最低 1 件ないと登録不可)。
- **重症度/部署/インシデント種別の enum は `Incident` クラス上**(DB ではない)。値追加は static 辞書/配列+それを回す View を更新(マイグレーション不要)。enum の日本語ラベルと Bootstrap カラーは `Models/Enums/EnumLabels.cs` に集約し、`EnumLabels.Japanese()` と `EnumLabels.Color()` の両方を更新。`IncidentTypeMapping.cs` が日本語↔DB 文字列の双方向変換を担う。
- **再発ロジックは `IRecurrenceService`**(`Services/RecurrenceService.cs`)に集約。`HomeController.Index`(90 日窓 `FindRecurrenceAlertsAsync`)と `IncidentsController.Details`(時間無制限 `FindRecurrencesForIncidentAsync`)が委譲。マッチャ `RecurrenceDetector.FindSimilar` は純粋関数。ルール変更時はサービスとそのテストを更新。
- **監査ログの正しさは `SaveChanges` 経由が前提** — `Incident` / `CauseAnalysis` / `PreventiveMeasure` に `ExecuteUpdate` / `ExecuteDelete` を使わない(変更追跡を迂回し監査漏れになる)。
- **PHI を `AuditLog.ChangesJson` に平文で残さない** — 自由記述・個人名カラムには `[Sensitive(Mask.Redact)]` か `[Sensitive(Mask.Hash)]`(`Models/Auditing/SensitiveAttribute.cs`)を付与。インターセプタが `[REDACTED]` か HMAC-SHA256 擬似匿名化(鍵 = `Audit:HashSalt`)に置換。**新しい自由記述/個人名カラムは必ず annotate**。本番で `Audit:HashSalt` 空は起動失敗。salt ローテーションは過去ハッシュとの相関を失うので runbook に記録。
- **時刻は常に `IClock`**(`Services/IClock.cs`、JST)。`DateTime.Now/Today/UtcNow` を直接呼ばない。永続業務タイムスタンプと `AuditLog.ChangedAt` は運用ローカル時刻(JST)で保存。新規 View では `@inject IClock Clock` + `Clock.Today` を使う。
- **EF Core 本体とプロバイダ実装はメジャー版を全プロジェクトで揃える** — プロバイダ実装(Sqlite / SqlServer / Npgsql)は EF Core の内部 API に結び付くため、本体と同じメジャー版でしか動作保証がない。一方 `Microsoft.AspNetCore.Identity.EntityFrameworkCore` と `Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore` は公開 API しか使わず、メジャー版は .NET のリリース(`net8.0` → `net9.0`)に追随する(9.0.0 は `net9.0` 専用)ため、この規則の対象外にしている(束ねると EF Core 9 への更新が復元不能になる)。一部だけ上げてもビルドもテストも通る(テストは InMemory / SQLite しか触らない)ため、**PostgreSQL 配備でだけ実行時に壊れる**。Dependabot は `.github/dependabot.yml` の `nuget-ef-core`(通常の版更新)と `nuget-ef-core-security`(セキュリティ更新。`applies-to` の既定は version-updates なので別グループが要る)が major も含めて 1 本の PR に束ね、版ズレ自体は `EfCorePackageAlignmentTests` が検出する。検査は `packages.lock.json` の解決済み版を読む(壊れる主役は推移依存の `Relational` で、どの csproj にも直接書かれていないため)。「EF Core 系とは何か」の定義は dependabot.yml の `patterns` が唯一の真実の源で、名前に `EntityFrameworkCore` **または** `EFCore`(`EFCore.NamingConventions` 等)を含むパッケージがパターンから漏れていないことも同テストが固定する(上記 .NET リリース列の 2 つだけは理由付きで明示除外)。**プロバイダを足すときは 3 箇所(上記 2 グループの `patterns` と `nuget-minor-and-patch.exclude-patterns`)すべてに反映する。** 2 グループの `patterns` は集合として一致している必要があるが、`exclude-patterns` 側は書き方を問わず「EF Core 系を 1 つも拾わないこと」だけを求める。判定は文字列の一致ではなく、解決済みのパッケージ ID と `patterns` が表す範囲の代表値の両方が拾われないことで行う(`patterns` 側が前方一致 `"Foo*"` のときは、除外もその前置詞で始まるものをまるごと覆う必要があり、`"Foo"` と `"Foo.*"` のようにドット区切りの形を列挙する書き方では `FooBar` 等の同族が漏れるため足りない。完全一致のパターンは 1 つの ID しか指さないので、その ID を覆えていれば足りる)。そのため`"Npgsql*"` のように広く書くのは可、逆に `"Microsoft.EntityFrameworkCore*"` から末尾 `*` を落とすと(`.Sqlite` / `.SqlServer` / `.Relational` など既存のプロバイダ実装がその時点で漏れるため)通らない。広げすぎて上記 .NET リリース列の 2 つまで minor / patch の束ねから外すのも同テストが検出する。
- **SQL Server の ADO.NET ドライバ(`Microsoft.Data.SqlClient`)のメジャー版は EF Core の SqlServer プロバイダに合わせる** — このドライバは `Microsoft.EntityFrameworkCore.SqlServer` の実体で、動作保証があるのはプロバイダが宣言したメジャー版に対してだけ。ところが名前に `EntityFrameworkCore` / `EFCore` を含まないため上記の `nuget-ef-core` グループには入らず、メジャー更新が単独 PR として現れる(実際 PR #178 が 5.1.7 → 7.0.2 で出ていた)。**壊れ方は EF Core 本体とプロバイダの版ズレとまったく同じ**で、テストは InMemory / SQLite しか触らないため**ビルドも全テストも緑のまま通り、SQL Server 配備でだけ実行時に壊れる**。`.github/dependabot.yml` の nuget エコシステムの `ignore` が major 更新だけを保留し(minor / patch は csproj の「床値」を上げる更新なので止めない)、`EfCorePackageAlignmentTests` が 4 つを検出する: 解決版と EF Core 宣言版のメジャー不一致 / 床値の後退 / 保留の消失 / 保留の効きすぎ(`update-types` の欠落・`versions` の追加・重複エントリ)。**期待値はテストに書かず EF Core の宣言側(`packages.lock.json` の `dependencies`)から読む**ので、EF Core を上げれば検査が自動で追随する。ドライバのメジャーを上げたいときは、それを宣言する版の `Microsoft.EntityFrameworkCore.SqlServer` へ同じ変更セットで上げる。
- **SQL Server の ADO.NET ドライバ(`Microsoft.Data.SqlClient`)のメジャー版は EF Core の SqlServer プロバイダに合わせる** — このドライバは `Microsoft.EntityFrameworkCore.SqlServer` の実体で、動作保証があるのはプロバイダが宣言したメジャー版に対してだけ。ところが名前に `EntityFrameworkCore` / `EFCore` を含まないため上記の `nuget-ef-core` グループには入らず、メジャー更新が単独 PR として現れる(実際 PR #178 が 5.1.7 → 7.0.2 で出ていた)。**壊れ方は EF Core 本体とプロバイダの版ズレとまったく同じ**で、テストは InMemory / SQLite しか触らないため**ビルドも全テストも緑のまま通り、SQL Server 配備でだけ実行時に壊れる**。`.github/dependabot.yml` の nuget エコシステムの `ignore` が major 更新だけを保留し(minor / patch は csproj の「床値」を上げる更新なので止めない)、`EfCorePackageAlignmentTests` が 5 つを検出する: 解決版と EF Core 宣言版のメジャー不一致 / 床値の後退 / **ピンが直接参照でなくなること**(`packages.lock.json` の `type` が `Direct` から外れる = csproj のピン削除や推移依存への差し戻し)/ 保留の消失 / 保留の効きすぎ(`update-types` の欠落・`versions` の追加・重複エントリ)。**期待値はテストに書かず EF Core の宣言側(`packages.lock.json` の `dependencies`)から読む**ので、EF Core を上げれば検査が自動で追随する。**ただし「床値の後退」の比較相手はあくまで EF Core の宣言(現在 5.1.6)であって、csproj に書かれたピンの値ではない**——ピンを削除した場合は上記の `Direct` 検査が捕まえるが、**ピンの値を「EF Core の宣言版以上・現在のピン未満」の範囲(今なら 5.1.6 以上 5.2.3 未満、たとえば Dependabot の更新を revert して 5.1.7 に戻す変更)へ下げると 5 つすべてを素通りする**。床値検査が比べるのは EF Core の宣言であって直前のピンではないため、`5.1.7 >= 5.1.6` が成立してしまう。期待値を書かない方針と引き換えに残している穴なので、**ピンの値が下がる差分は(下げ幅の大小によらず)レビューで理由を確認する**。ドライバのメジャーを上げたいときは、それを宣言する版の `Microsoft.EntityFrameworkCore.SqlServer` へ同じ変更セットで上げる。
- **`SameDepartmentHandler`(`Authorization/SameDepartmentHandler.cs`)は `Incident` の eager-load が前提**。`PreventiveMeasure` / `CauseAnalysis` で `AuthorizeAsync` する前に `.Include(x => x.Incident)`。**fail-closed**(`Incident` が null なら拒否)。

### 新規 POST アクション追加時のチェックリスト
Expand Down
27 changes: 21 additions & 6 deletions src/IncidentInsight.Web/IncidentInsight.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,31 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.19" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.19" />
<!-- EF Core SqlServer 9.0.19 が推移的にピンする Microsoft.Data.SqlClient 5.1.6 は、
直前まで 8.0.29 経由で解決されていた 5.1.7(および付随する SNI.runtime 5.1.2)より
古いサービシングパッチ。オンプレ SQL Server 配備が本番ターゲットのため、
セキュリティ更新のバージョンを後退させないよう直接依存として床値をピン留めする。
EF Core 側の 5.1.6 要求とは互換で、Relational は上位版を採用する。
直前まで 8.0.29 経由で解決されていた 5.1.7 より古いサービシングパッチだった。
オンプレ SQL Server 配備が本番ターゲットのため、セキュリティ更新のバージョンを
後退させないよう直接依存として床値をピン留めしている。床値そのもの(現在 5.2.3、
付随する SNI.runtime は 5.2.0)は Dependabot の minor / patch 更新で引き上げていく
——止めているのは下の理由で major だけ。その更新 PR をそのままマージすると CI が
NU1004 で落ちる(手順は CLAUDE.md §3「ロックファイルは全プロジェクト分を同時に再生成する」)。
実際に 5.2.3 が採用されるのは、EF Core SqlServer の 5.1.6 要求とこの直接参照の
[5.2.3, ) 要求を NuGet が突き合わせて上位を採るため。
【5.2.3 で 1 件だけ版が下がる】5.1.7 が要求していた System.Security.Cryptography.Cng
5.0.0 は 5.2.3 では要求されなくなり、残る要求元(Microsoft.IdentityModel.Tokens 6.35.0)
の 4.5.0 に落ちる。net8.0 では共有フレームワーク側の実装が優先されるためこのパッケージ版に
実行時の意味は無く、確認のうえ許容している(この床値ピンが守る対象はドライバ本体)。
【メジャー版は EF Core の SqlServer プロバイダに合わせる】このドライバはプロバイダの
実体で、動作保証があるのはプロバイダが宣言したメジャー版に対してだけ。ここだけを
別メジャーへ上げても、テストは InMemory / SQLite しか触らないため全て緑のまま通り、
SQL Server 配備でだけ実行時に壊れる。dependabot.yml の ignore が major 更新を保留し、
EfCorePackageAlignmentTests がメジャー一致と床値の後退を検出する。 -->
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.7" />
EfCorePackageAlignmentTests がメジャー一致と床値の後退を検出する。
【この行を消さない】床値検査が比べる相手は EF Core の宣言(現在 5.1.6)であって
ここに書かれた値ではないため、この行を「冗長だから」と消しても床値検査は素通りする
(解決版が 5.1.6 まで落ちても 5.1.6 >= 5.1.6 は成立する)。削除だけは
SqlClientPin_StaysADirectReference がロックファイルの type で捕まえる。
残る穴は「この値を 5.1.6 以上・現在のピン未満へ下げる」変更(例: この更新を revert して
5.1.7 に戻す)で、5.1.7 >= 5.1.6 が成立するため検査は全て緑のまま通る。
下げ幅の大小によらずレビューでしか止まらない。 -->
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.3" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.30" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
</ItemGroup>
Expand Down
Loading