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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ catch (DbUpdateConcurrencyException) { TempData["Warning"] = "..."; return ...;
- **値変換で文字列として保存する列は、実際に保存しうる値がすべて収まる長さにする** — 上の検査が裸の数値を禁じることで `FieldLengths.EnumCode` の使用を積極的に誘導するため、定数が実際の値より短いと誘導に従った結果として切り詰めが起きる。壊れ方は**プロバイダ依存**(SQL Server / PostgreSQL は例外、SQLite は黙って保存、テストの InMemory は列長の概念すら無い)で、**ビルドも全テストも緑のまま特定の配備先でだけ壊れる**。`ConvertedEnumColumnLengthTests` が、変換器を実際に通した文字列(`HasConversion<string>()` なら enum 名、ラムダ変換なら変換結果)の長さを上限と突き合わせて固定する。**検査範囲は誘導する検査と同じ**にする(誘導する範囲より検証する範囲が狭いと、その差分がそのまま死角になる)。あわせて `EveryLengthLimitedEnumColumn_IsActuallyExamined` が「見るべき列を全部見たか」を**判定とは独立な手がかり**(enum 型でかつ長さ上限を持つ永続化列)で照合する — 同じ判定でガードを書くと、判定が狭まったときにガードも一緒に狭まって「違反ゼロ=緑」で無力化されるため。文字列として保存する enum 列の上限は `FieldLengths.EnumCode`(20)と `FieldLengths.EnumCodeJapanese`(50。`IncidentType` は日本語の DB 文字列を保存するため)。
- **時刻は常に `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` が 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` へ同じ変更セットで上げる。
- **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.9)であって、csproj に書かれたピンの値ではない**——ピンを削除した場合は上記の `Direct` 検査が捕まえるが、**ピンの値を「EF Core の宣言版以上・現在のピン未満」の範囲(今なら 5.1.9 以上 5.2.3 未満、たとえば 5.2.0 へ下げる変更)へ下げると 5 つすべてを素通りする**。床値検査が比べるのは EF Core の宣言であって直前のピンではないため、`5.2.0 >= 5.1.9` が成立してしまう。期待値を書かない方針と引き換えに残している穴なので、**ピンの値が下がる差分は(下げ幅の大小によらず)レビューで理由を確認する**。ドライバのメジャーを上げたいときは、それを宣言する版の `Microsoft.EntityFrameworkCore.SqlServer` へ同じ変更セットで上げる。
- **`SameDepartmentHandler`(`Authorization/SameDepartmentHandler.cs`)は `Incident` の eager-load が前提**。`PreventiveMeasure` / `CauseAnalysis` で `AuthorizeAsync` する前に `.Include(x => x.Incident)`。**fail-closed**(`Incident` が null なら拒否)。

### 新規 POST アクション追加時のチェックリスト
Expand Down
29 changes: 18 additions & 11 deletions src/IncidentInsight.Web/IncidentInsight.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,27 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.31" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.19">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.20">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<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 は、
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.20" />
<!-- 【このピンの発端】EF Core SqlServer 9.0.19 が推移的にピンしていた Microsoft.Data.SqlClient 5.1.6 は、
直前まで 8.0.29 経由で解決されていた 5.1.7 より古いサービシングパッチだった。
オンプレ SQL Server 配備が本番ターゲットのため、セキュリティ更新のバージョンを
後退させないよう直接依存として床値をピン留めしている。床値そのもの(現在 5.2.3、
付随する SNI.runtime は 5.2.0)は Dependabot の minor / patch 更新で引き上げていく
——止めているのは下の理由で major だけ。その更新 PR をそのままマージすると CI が
付随する 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 は STS で 5.2.3(2025-04-29)が最後の公開版、
一方 5.1 は LTS として 5.1.9(2026-01-13)まで保守が続いている。つまり 5.2.x の
minor / patch はもう出ず、残る 5.x → 6.x は上の ignore が止めるため、この床値は
どの経路でも動かない。しかも EF Core が宣言する版(現在 5.1.9)の方が新しいので、
このピンは「後退を防ぐ」どころか新しいサービシングを押しとどめている。
数値としては 5.2.3 >= 5.1.9 が成立するため、どの検査も緑のまま気付けない。
ピンを保守の続く 5.1 系へ移す変更は別の変更セットで扱う。
実際に 5.2.3 が採用されるのは、EF Core SqlServer の 5.1.9 要求とこの直接参照の
[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)
Expand All @@ -36,12 +43,12 @@
別メジャーへ上げても、テストは InMemory / SQLite しか触らないため全て緑のまま通り、
SQL Server 配備でだけ実行時に壊れる。dependabot.yml の ignore が major 更新を保留し、
EfCorePackageAlignmentTests がメジャー一致と床値の後退を検出する。
【この行を消さない】床値検査が比べる相手は EF Core の宣言(現在 5.1.6)であって
【この行を消さない】床値検査が比べる相手は EF Core の宣言(現在 5.1.9)であって
ここに書かれた値ではないため、この行を「冗長だから」と消しても床値検査は素通りする
(解決版が 5.1.6 まで落ちても 5.1.6 >= 5.1.6 は成立する)。削除だけは
(解決版が 5.1.9 まで落ちても 5.1.9 >= 5.1.9 は成立する)。削除だけは
SqlClientPin_StaysADirectReference がロックファイルの type で捕まえる。
残る穴は「この値を 5.1.6 以上・現在のピン未満へ下げる」変更(例: この更新を revert して
5.1.7 に戻す)で、5.1.7 >= 5.1.6 が成立するため検査は全て緑のまま通る。
残る穴は「この値を 5.1.9 以上・現在のピン未満へ下げる」変更(例: 5.2.0 へ下げる)で、
5.2.0 >= 5.1.9 が成立するため検査は全て緑のまま通る。
下げ幅の大小によらずレビューでしか止まらない。 -->
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.3" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.31" />
Expand Down
Loading