Context
PR #115 (closing #109) and PR #119 (closing #117) shipped the same bug class twice in 14 days: a Pydantic v2 `ConfigDict(strict=True)` request model with a JSON-non-native field type (`date`/`datetime`/`time`/`UUID`/`Decimal`) and no `Field(strict=False, ...)` override → every HTTP caller returns 422 with a `*_type` error.
The fix is mechanical (one-line `Field` override), but until now the policy was only enforced by a human reviewer reading `docs/_base/SECURITY.md` → "Pydantic v2 strict mode on FastAPI request bodies".
Goal
Codify the policy as a load-bearing pytest invariant (mirrors the `app/features/featuresets/tests/test_leakage.py` precedent) so the third instance of this regression class fails CI before it leaves a contributor's machine.
Scope
Per `PRPs/PRP-14-strict-mode-policy-linter.md`:
- NEW `app/core/tests/test_strict_mode_policy.py` — single ~80-LOC AST-walker module (zero new deps; stdlib `ast` only)
- M `docs/_base/SECURITY.md` — one-line "Enforced by:" cross-reference
Out of scope: `app/shared/seeder/config.py` (not request bodies — no `ConfigDict(strict=True)` anywhere), `app/features/agents/tools/*` (PydanticAI `@agent.tool` decorators, not `BaseModel`), response models, auto-fix mode, pre-commit hook variant.
Acceptance
Precedent
Effort
4–6 focused hours per PRP §12 — one file, ~80 LOC, two test functions + one baseline guard.
Context
PR #115 (closing #109) and PR #119 (closing #117) shipped the same bug class twice in 14 days: a Pydantic v2 `ConfigDict(strict=True)` request model with a JSON-non-native field type (`date`/`datetime`/`time`/`UUID`/`Decimal`) and no `Field(strict=False, ...)` override → every HTTP caller returns 422 with a `*_type` error.
The fix is mechanical (one-line `Field` override), but until now the policy was only enforced by a human reviewer reading `docs/_base/SECURITY.md` → "Pydantic v2 strict mode on FastAPI request bodies".
Goal
Codify the policy as a load-bearing pytest invariant (mirrors the `app/features/featuresets/tests/test_leakage.py` precedent) so the third instance of this regression class fails CI before it leaves a contributor's machine.
Scope
Per `PRPs/PRP-14-strict-mode-policy-linter.md`:
Out of scope: `app/shared/seeder/config.py` (not request bodies — no `ConfigDict(strict=True)` anywhere), `app/features/agents/tools/*` (PydanticAI `@agent.tool` decorators, not `BaseModel`), response models, auto-fix mode, pre-commit hook variant.
Acceptance
Precedent
Effort
4–6 focused hours per PRP §12 — one file, ~80 LOC, two test functions + one baseline guard.