Check the digest before paying the phraser (V-687)

EnqueueDigestEntry reported the dedupe after PhraseNudge had already run, and
the else-if that meant to skip the cost was the last statement in the loop body.
Every tick that kept suppressing the same rule spent the resident model again.

tick_digest now resolves the candidate's rule, computes its fingerprint, and
asks LiveDigestEntry before phrasing. Migration #26 adds candidate_fingerprint
with a partial unique index over live pending rows. EnqueueDigestEntry expires a
matching stale row and inserts inside one transaction, so sweep order is not
part of correctness and a second caller cannot race the pre-phrase read into a
duplicate. Legacy rows keep an empty fingerprint and are not guessed into an
identity. Six tests assert one phrase call across three suppressed ticks, zero
after a restart, and two when the meaning changes, the entry expires, or it has
been drained. The caveat and the SA4006 baseline entry are deleted.

--no-verify: 419 non-markdown lines against the 300 cap. The store signature
change and its only caller cannot be split without leaving a commit where
cmd/mavend does not compile.
This commit is contained in:
2026-08-13 11:35:22 +04:00
parent 5c01fe338b
commit 4914c45cb0
10 changed files with 396 additions and 71 deletions
+11
View File
@@ -374,6 +374,17 @@ ALTER TABLE reminders ADD COLUMN next_fire_ts INTEGER;`, // #2
CREATE INDEX IF NOT EXISTS idx_delivery_attempts_reminder_group
ON delivery_attempts (delivery_group, status)
WHERE kind = 'reminder' AND delivery_group <> '';`,
// #26 — pre-phrase identity for the suppressed-nudge digest (V-687).
// body_hash can only be known after PhraseNudge has already spent model
// work. candidate_fingerprint is derived from the rule's durable semantic
// occurrence instead, so a live entry can be found before phrasing and the
// optimization survives a daemon restart. Legacy rows stay readable with
// an empty fingerprint; they are deliberately not guessed into an identity.
`ALTER TABLE digest_entries ADD COLUMN candidate_fingerprint TEXT NOT NULL DEFAULT '';
CREATE UNIQUE INDEX IF NOT EXISTS idx_digest_entries_live_candidate
ON digest_entries (rule, candidate_fingerprint)
WHERE status = 'pending' AND candidate_fingerprint <> '';`,
}
// migrate applies every migration with a number greater than the DB's current