fix(review): give shadow overrides the same expired-clear_at handling as live overrides - #10325
Merged
JSONbored merged 1 commit intoAug 4, 2026
Conversation
… as live overrides auto-apply.ts's LIVE override read/write pair treats an already-lapsed clear_at as cleared: loadOverride threads nowIso into rowToOverride, and writeLiveOverride drops an expired clear_at rather than resurrecting it. The SHADOW pair only ported the "preserve the column" half of the stale-clear-at fix, not the "drop it once expired" half: writeShadowOverride had no nowIso param and re-persisted the existing row's clear_at unconditionally, and loadShadowOverride called rowToOverride with no nowIso. So a shadow override whose clear_at has lapsed was read back as active, and a stale shadow tightening could still be promoted to live after its operator-set expiry passed. Thread an optional nowIso through both, mirroring the live pair exactly: loadShadowOverride passes nowIso to rowToOverride, and writeShadowOverride computes clearAt via the same "existing row AND clear_at not expired, else null" rule writeLiveOverride uses, plus rowToOverride with nowIso on the merge read. Both params are optional, so every existing caller compiles and behaves exactly as before. The live pair, the promotion/soak-gate decision logic, and every other behaviour are unchanged. Closes JSONbored#10291
Contributor
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
JSONbored
approved these changes
Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
src/review/auto-apply.tskeeps two parallel tunable-override tables — LIVE (tunables_overrides) and SHADOW (tunables_overrides_shadow, a soak-gated staging area promoted to live once validated). Both carry an operator-settableclear_at(a temporary override's own expiration).The LIVE pair correctly treats an already-lapsed
clear_atas cleared:The SHADOW pair only ported the "preserve the column" half of the
#stale-clear-at-fix, not the "drop it once expired" half:writeShadowOverridehad nonowIsoparameter and computedclearAtasexistingRow?.clear_at ?? null— re-persisting a lapsedclear_atunconditionally, and callingrowToOverride(existingRow)with nonowIsosoclearAtIsExpiredalways short-circuited to false during the merge.loadShadowOverridehad nonowIsoparameter and calledrowToOverride(row)with no second argument, so a shadow row with a lapsedclear_atwas read back as still active.Impact:
loadShadowOverridefeedsrunAutoApplyRecommendations's promotion step and thegate-config/effective/live-gate-thresholdsAPI + MCP routes. A shadow override whoseclear_athas already lapsed was treated as active indefinitely, and a stale shadow tightening could be promoted to LIVE after its own operator-set expiration had passed — silently defeating the temporary-override-expiration semantics the live side already honours.The fix
Thread an optional
nowIsothrough both, mirroring the live pair exactly:loadShadowOverride(env, project, nowIso?)passesnowIsointorowToOverride, so a lapsedclear_atreads as cleared.writeShadowOverride(env, project, o, validatedUntilIso, nowIso?)computesclearAtviaexistingRow && !clearAtIsExpired(existingRow.clear_at, nowIso) ? existingRow.clear_at : nulland passesnowIsointo its ownrowToOverride(existingRow, nowIso)merge read.Both parameters are optional and additive, so every existing caller compiles and behaves exactly as before when
nowIsois omitted (matching the live side's convention).Unchanged: the live
loadOverride/writeLiveOverridepair, the promotion/soak-gate decision logic, and every other behaviour. No migration (additive optional params only).Tests (
test/unit/auto-apply.test.ts)writeShadowOverridedrops an already-expiredclear_at(and does not resurrect the expired floor) whennowIsois after it — mirroring the existing live-side "does NOT resurrect an ALREADY-EXPIRED override" test.loadShadowOverridereads an expired-clear_atrow as cleared (null) whennowIsois after it, and still reads it active whennowIsois omitted (the non-breaking, additive contract).clear_at) still passes unchanged.main.Validation
src/review/auto-apply.tsis 100% line and branch (all three arms of the newexistingRow && !clearAtIsExpired(...)guard).npm run typecheckclean for these files;npm run engine-parity:drift-checkpasses (not a twin);npm run dead-exports:checkclean; the suite (100 tests) green.git diff --checkclean; no schema/migration/generated-artifact change.Closes #10291