`update_rule` now snapshots the rule's text before it writes. Rescoping rule
79 meant hand-copying the superseded statement into a task log to keep it
(#3237); the history is that, done by the write path instead of by somebody
remembering.
The snapshot is taken BEFORE the field loop, which is the one ordering that
matters. `update_rule` drops `verified_at` when `verify_with` changes, and
rewriting a check is exactly the edit whose history is worth most — a
snapshot taken afterwards would file the NEW check against the OLD wording.
Taking it up front also covers `clear`, which is a separate argument from the
field loop and is how a rule that stops being a constraint loses its check
entirely.
Session-bound rather than opening its own like note_versions.create_version:
the version and the edit that caused it commit together, so a failed update
cannot leave a history entry for an edit that never happened.
Two guards from the sibling are deliberately absent, and both are now pinned
by tests rather than only by comments — "make it consistent with
note_versions" is a plausible-sounding change that would silently start
dropping history:
- No MIN_VERSION_INTERVAL_SECONDS. That 300-second gate exists because note
autosave fires every 60. Every version here comes from a deliberate
update_rule, so three edits in one second are three edits.
- No MAX_VERSIONS and no pruning. A rule is edited a handful of times in its
life; a cap could only ever discard the one edit somebody went looking for.
Kept from the sibling: the identical-content skip. Both doors resend every
field, so without it a form saved twice would file an identical snapshot.
`order_index` is excluded from the snapshot fields for the same reason —
reordering a rulebook is not an edit to what any rule says.
No delete-time snapshot, against the task's original scope and on the
operator's call. A delete goes through trash_svc and is SOFT: the rule row
keeps its full text and restores untouched, so there is nothing for a
snapshot to preserve. Anything that survived a purge would be data the
operator explicitly asked to be gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>