diff --git a/validators/SignalDecay.ts b/validators/SignalDecay.ts index a8dc7df..f7a02e8 100644 --- a/validators/SignalDecay.ts +++ b/validators/SignalDecay.ts @@ -17,30 +17,34 @@ import { decay } from "@afi-protocol/afi-math"; /** - * Default half-life for signal decay (in hours). - * - * Signals lose 50% of their value after this many hours. - * Default: 24 hours (1 day) - * - * TODO: Source from governance config (afi-config) + * DLC-GOV D-DLC-3(2): the silent 24h default (`DEFAULT_SIGNAL_HALF_LIFE_HOURS`) + * is RETIRED — a half-life a caller never chose is never quietly substituted. + * Every entry point below requires the half-life explicitly. The canonical, + * minutes-based production derivation is `applyTimeDecay` in `src/decay` + * (KAT-bound to afi-config's governed 32-vector apply-time-decay KAT); + * this hour-unit wrapper survives only for the dormant validator surface + * (ValidatorDecision), which always passes explicit parameters. */ -export const DEFAULT_SIGNAL_HALF_LIFE_HOURS = 24; /** * Apply time-based decay to a UWR score. - * + * * Formula: decayed_score = uwr_score * e^(-λ * age) * where λ = ln(2) / half_life - * + * + * SUPERSEDED for production use by the minutes-based `applyTimeDecay` + * (`src/decay`, DLC-GOV D-DLC-1) — hour-unit, explicit-parameter wrapper + * kept for the dormant validator surface only (D-DLC-3(2)). + * * @param uwrScore - Base UWR score in [0, 1] * @param ageHours - Signal age in hours - * @param halfLifeHours - Half-life in hours (default: 24) + * @param halfLifeHours - Half-life in hours (required — no default) * @returns Time-decayed score in [0, 1] */ export function applyTimeDecayToUwrScore( uwrScore: number, ageHours: number, - halfLifeHours: number = DEFAULT_SIGNAL_HALF_LIFE_HOURS + halfLifeHours: number ): number { return decay.timeWeightedScore({ baseScore: uwrScore, @@ -81,7 +85,10 @@ export function calculateAdjustedHalfLife( * @param ageHours - Signal age in hours * @param volatility - Volatility factor (1.0 = normal) * @param conviction - Conviction factor (1.0 = normal) - * @param baseHalfLifeHours - Base half-life in hours + * @param baseHalfLifeHours - Base half-life in hours (required — no default, + * D-DLC-3(2); note this adjusted-half-life family is expressly NOT wired + * by DLC-GOV D-DLC-3(4) — an adjusted half-life is a different declared + * parameter, introducible only by a future filing) * @returns Volatility-adjusted decayed score in [0, 1] */ export function applyVolatilityAdjustedDecay( @@ -89,7 +96,7 @@ export function applyVolatilityAdjustedDecay( ageHours: number, volatility: number, conviction: number, - baseHalfLifeHours: number = DEFAULT_SIGNAL_HALF_LIFE_HOURS + baseHalfLifeHours: number ): number { const adjustedHalfLife = calculateAdjustedHalfLife( baseHalfLifeHours, diff --git a/validators/ValidatorDecision.ts b/validators/ValidatorDecision.ts index dc68978..e2fda10 100644 --- a/validators/ValidatorDecision.ts +++ b/validators/ValidatorDecision.ts @@ -60,7 +60,12 @@ export function computeValidatorScore( // Calculate adjusted half-life based on conviction // Higher conviction → longer half-life (signal stays relevant longer) - const baseHalfLifeHours = 24; // Default 24 hours + // DLC-GOV D-DLC-3(2) conformance: every half-life here is an EXPLICIT + // parameter of this dormant surface — no silent library default is + // reachable. The canonical live derivation is src/decay applyTimeDecay, + // driven by the determination's stamped decayParams (D-DLC-1); the + // adjusted-half-life family stays unwired per D-DLC-3(4). + const baseHalfLifeHours = 24; // this module's own declared base (hours) const adjustedHalfLife = calculateAdjustedHalfLife( baseHalfLifeHours, volatility, diff --git a/validators/__tests__/SignalDecay.test.ts b/validators/__tests__/SignalDecay.test.ts index 493692a..6d1c644 100644 --- a/validators/__tests__/SignalDecay.test.ts +++ b/validators/__tests__/SignalDecay.test.ts @@ -12,15 +12,14 @@ import { applyTimeDecayToUwrScore, calculateAdjustedHalfLife, applyVolatilityAdjustedDecay, - remainingAfterHalfLives, - DEFAULT_SIGNAL_HALF_LIFE_HOURS + remainingAfterHalfLives } from "../SignalDecay.js"; describe("SignalDecay - Time-based UWR Decay", () => { describe("applyTimeDecayToUwrScore", () => { it("should not decay a fresh signal (age = 0)", () => { const uwrScore = 0.8; - const decayed = applyTimeDecayToUwrScore(uwrScore, 0); + const decayed = applyTimeDecayToUwrScore(uwrScore, 0, 24); expect(decayed).toBe(0.8); }); @@ -54,13 +53,13 @@ describe("SignalDecay - Time-based UWR Decay", () => { expect(decayed).toBeGreaterThan(uwrScore * 0.5); }); - it("should use default half-life of 24 hours", () => { + it("requires an explicit half-life — the silent 24h default is retired (DLC-GOV D-DLC-3(2))", () => { const uwrScore = 0.8; const ageHours = 24; - - const decayed = applyTimeDecayToUwrScore(uwrScore, ageHours); - - // After 24 hours with default half-life, should be 0.8 * 0.5 = 0.4 + + const decayed = applyTimeDecayToUwrScore(uwrScore, ageHours, 24); + + // After 24 hours with an EXPLICIT 24h half-life: 0.8 * 0.5 = 0.4 expect(decayed).toBeCloseTo(0.4, 6); }); }); @@ -98,8 +97,8 @@ describe("SignalDecay - Time-based UWR Decay", () => { const uwrScore = 0.8; const ageHours = 24; - const normalDecay = applyVolatilityAdjustedDecay(uwrScore, ageHours, 1.0, 1.0); - const highVolDecay = applyVolatilityAdjustedDecay(uwrScore, ageHours, 2.0, 1.0); + const normalDecay = applyVolatilityAdjustedDecay(uwrScore, ageHours, 1.0, 1.0, 24); + const highVolDecay = applyVolatilityAdjustedDecay(uwrScore, ageHours, 2.0, 1.0, 24); // High volatility should cause more decay expect(highVolDecay).toBeLessThan(normalDecay); @@ -109,8 +108,8 @@ describe("SignalDecay - Time-based UWR Decay", () => { const uwrScore = 0.8; const ageHours = 24; - const normalDecay = applyVolatilityAdjustedDecay(uwrScore, ageHours, 1.0, 1.0); - const highConvictionDecay = applyVolatilityAdjustedDecay(uwrScore, ageHours, 1.0, 2.0); + const normalDecay = applyVolatilityAdjustedDecay(uwrScore, ageHours, 1.0, 1.0, 24); + const highConvictionDecay = applyVolatilityAdjustedDecay(uwrScore, ageHours, 1.0, 2.0, 24); // High conviction should cause less decay expect(highConvictionDecay).toBeGreaterThan(normalDecay); @@ -126,7 +125,8 @@ describe("SignalDecay - Time-based UWR Decay", () => { uwrScore, ageHours, volatility, - conviction + conviction, + 24 ); // Should be between 0 and original score