fix(api): reclassify on root and policy changes

Moving a title to a root with a different policy, and pointing a root
at a different policy via PUT /api/roots/{id}, both changed the
effective policy without re-deriving stored verdicts — which §9.3's
deck and the daemon's manual-grab gate read. Both now run the same
reclassify the overrides path uses, inline in the request; §5.1 states
the contract, and relocate.rs no longer claims the move alone makes
the policy apply.

PUT /api/policies/{id} has the same gap one level up; noted on #241
for its own issue.

Closes #241

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Miguel Palhas
2026-08-25 11:17:33 +01:00
parent 2a58a103d3
commit 43e65514ed
8 changed files with 429 additions and 6 deletions
+11
View File
@@ -199,6 +199,17 @@ fake profile per root and ignoring whatever it sends.
Per-title `overrides` relax the root policy for one title. Same mechanism in
both directions — `only_4k` tightens, `allow_english_audio` loosens.
**Stored verdicts follow the effective policy.** A release's verdict is
stamped by the search that found it, and both §9.3's deck and the daemon's
manual-grab gate read that stored column. So the three operator actions that
change a title's effective policy — editing its overrides, moving it to a
root with a different policy, and pointing a root at a different policy —
re-derive the stored verdicts of everything they touch, inline in the same
request. The operator is never left reading a verdict computed under a
policy that no longer applies. Inline is affordable even root-wide:
re-evaluation is pure and in-memory, and a row whose verdict does not move
is not rewritten, so a library-sized repoint costs reads, not writes.
### 5.2 Language
Requires a concept the release name does not carry: the **original language of