fix: repair decayBeliefs CTE so belief decay actually runs
The CTE was `WITH halflives AS (SELECT profile, CASE profile ...)` with no FROM clause. Postgres rejects it with 42703 (column "profile" does not exist) on every hourly invocation, so the temporal dimension of the recommendation engine had never executed once — obsession (14d half-life) and contextual (7d) never faded. Rewritten as `WITH halflives(profile, halflife_sec) AS (VALUES ...)`, half-lives preserved exactly. One deliberate semantic change: the broken CASE had an `ELSE 30 * 86400` fallback, so an unrecognised profile would have decayed on a 30-day half-life. The VALUES join leaves unknown profiles undecayed instead. Today that is a no-op (only `forgotten`, already excluded by the WHERE), but a future profile added without a half-life will now conspicuously not decay rather than quietly decaying at an arbitrary rate. Verified against a scratch PG16 with one belief per profile aged exactly one half-life: UPDATE 2, obsession and contextual halved, forgotten and a fresh longterm untouched. Against the live DB (in a rolled-back transaction) the fix reports UPDATE 843. NOTE ON ROLLOUT: the first successful run applies ~23 days of accrued decay at once, cutting obsession beliefs to ~0.32x. That is correct behaviour, but recommendations will shift visibly. Expected, not a regression. REVIEW-2026-07-30.md finding 3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1790,16 +1790,16 @@ export class DbService {
|
|||||||
*/
|
*/
|
||||||
async decayBeliefs(): Promise<number> {
|
async decayBeliefs(): Promise<number> {
|
||||||
const res = await this.pgClient.query(`
|
const res = await this.pgClient.query(`
|
||||||
WITH halflives AS (
|
-- NOTE: this MUST be a VALUES list, not "SELECT profile, CASE profile ...",
|
||||||
SELECT profile,
|
-- which has no FROM clause and is rejected by Postgres with 42703
|
||||||
CASE profile
|
-- (column "profile" does not exist) on every single run.
|
||||||
WHEN 'longterm' THEN 365 * 86400
|
WITH halflives(profile, halflife_sec) AS (
|
||||||
WHEN 'obsession' THEN 14 * 86400
|
VALUES
|
||||||
WHEN 'discovery' THEN 30 * 86400
|
('longterm', 365 * 86400),
|
||||||
WHEN 'negative' THEN 180 * 86400
|
('obsession', 14 * 86400),
|
||||||
WHEN 'contextual' THEN 7 * 86400
|
('discovery', 30 * 86400),
|
||||||
ELSE 30 * 86400
|
('negative', 180 * 86400),
|
||||||
END AS halflife_sec
|
('contextual', 7 * 86400)
|
||||||
)
|
)
|
||||||
UPDATE listener_beliefs lb
|
UPDATE listener_beliefs lb
|
||||||
SET value = GREATEST(-1.0, LEAST(1.0, lb.value * POWER(0.5,
|
SET value = GREATEST(-1.0, LEAST(1.0, lb.value * POWER(0.5,
|
||||||
|
|||||||
Reference in New Issue
Block a user