Originally created by: fu351
doberman approvals ttl raising the TTL (a weakening: approvals stay trusted for longer) runs _run_weaken_gate directly (cli/main.py, the approvals_ttl command), so it is gated but never ledgered: no policy_changes row, no drift-observer fan-out, and since [#513] its policy-catalogue observation carries no ledger_ts. It bypassed apply_change for a real reason — classify_change compares token ranks, reads two integers as neutral, and would auto-approve the raise ungated.
Add a numeric chokepoint in policy/drift.py mirroring apply_preferences_change / apply_egress_velocity_change: classify an increase of approval_memory_seconds as weaken (a decrease is strengthen, unchanged is neutral), run the same _run_weaken_gate, record every attempt including denials via _record_change, notify observers, and return a ChangeOutcome. The CLI then routes through it and passes outcome.ts into save_policy(..., ledger_ts=...) so the catalogue links the version to its ledger row.
Tests: a raise crosses the gate and lands in the ledger (approved and denied variants); a lower applies automatically and is still ledgered; the catalogue observation's ledger_ts equals the new ledger row's ts; and the single-write-path guarantee (test_policy_ledger_is_only_written_by_apply_change) still holds — the new chokepoint lives in drift.py, so the existing source scan stays true.