@gamaze/hicortex 0.22.0 → 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/assets/dashboard.html +209 -76
  2. package/dist/calibration.d.ts +92 -12
  3. package/dist/calibration.js +102 -15
  4. package/dist/classify-domains.js +6 -1
  5. package/dist/consolidate.js +87 -19
  6. package/dist/dashboard.d.ts +11 -0
  7. package/dist/dashboard.js +9 -0
  8. package/dist/db.js +74 -0
  9. package/dist/eval/decay-eval.d.ts +4 -2
  10. package/dist/eval/decay-eval.js +4 -4
  11. package/dist/eval/eval-clock.d.ts +32 -0
  12. package/dist/eval/eval-clock.js +47 -0
  13. package/dist/eval/graph-eval.d.ts +15 -2
  14. package/dist/eval/graph-eval.js +51 -5
  15. package/dist/eval/planted-eval.d.ts +4 -0
  16. package/dist/eval/planted-eval.js +27 -2
  17. package/dist/eval/planted-harness.d.ts +7 -0
  18. package/dist/eval/planted-harness.js +2 -0
  19. package/dist/eval/ranking-battery.d.ts +49 -2
  20. package/dist/eval/ranking-battery.js +110 -2
  21. package/dist/eval/ranking-eval.d.ts +26 -6
  22. package/dist/eval/ranking-eval.js +197 -34
  23. package/dist/eval/ranking-fixtures.d.ts +41 -1
  24. package/dist/eval/ranking-fixtures.js +261 -2
  25. package/dist/eval/recall-sweep.d.ts +7 -2
  26. package/dist/eval/recall-sweep.js +42 -13
  27. package/dist/eval/relevance-eval.d.ts +115 -1
  28. package/dist/eval/relevance-eval.js +318 -32
  29. package/dist/eval/run-eval.d.ts +7 -4
  30. package/dist/eval/run-eval.js +36 -9
  31. package/dist/mcp-server.js +37 -9
  32. package/dist/nightly.js +14 -0
  33. package/dist/recall-index.d.ts +46 -3
  34. package/dist/recall-index.js +83 -26
  35. package/dist/recall-precision.d.ts +212 -0
  36. package/dist/recall-precision.js +381 -0
  37. package/dist/retrieval.d.ts +34 -15
  38. package/dist/retrieval.js +132 -59
  39. package/dist/types.d.ts +7 -0
  40. package/package.json +1 -1
  41. package/server.json +3 -3
@@ -427,6 +427,24 @@
427
427
  .rec-band{ position:absolute; inset:0; border-radius:4px; }
428
428
  .rec-dot{ position:absolute; top:50%; transform:translate(-50%,-50%); width:11px; height:11px; border-radius:50%; background:var(--text); border:2px solid var(--panel); box-shadow:0 0 0 1px color-mix(in srgb, var(--text) 40%, transparent), 0 0 8px color-mix(in srgb, var(--text) 30%, transparent); }
429
429
  .rec-foot{ display:flex; align-items:center; gap:7px; }
430
+ /* Memory Precision Level 1 (#476) — the pushed-index row: a compact
431
+ second line in the .csub scale (the sibling-rhythm ruling 2026-09-14:
432
+ the card keeps the shared .bseg gap rhythm, header → big → band →
433
+ index row → foot). Direction-only coloring: green at/above the echoed
434
+ recall floor, muted below (the index bar reads conservative); the
435
+ floor tick is amber like the pace marker. Theme tokens only — both
436
+ themes render from their own table, no JS colors here. */
437
+ .rec-l1{ display:block; font-size:10.5px; color:var(--dim); min-width:0; line-height:1.6; }
438
+ .rec-l1 .l1row{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
439
+ .rec-l1 .lbl{ display:none; } /* #476 rename: the three-bucket text is self-labeled */
440
+ .rec-l1 .l1bar{ position:relative; flex:1 1 46px; height:5px; border-radius:3px; background:var(--tbar-bg); min-width:36px; }
441
+ .rec-l1 .l1bar .l1-fill{ position:absolute; left:0; top:0; bottom:0; border-radius:3px; background:color-mix(in srgb, var(--green) 65%, transparent); }
442
+ .rec-l1.below .l1bar .l1-fill{ background:color-mix(in srgb, var(--dim) 55%, transparent); }
443
+ .rec-l1 .l1bar .l1-floor{ position:absolute; top:-2px; bottom:-2px; width:1px; background:var(--amber); }
444
+ .rec-l1 .l1txt{ color:var(--dim); overflow:hidden; text-overflow:ellipsis; font-size:10.5px; letter-spacing:-.01em; }
445
+ .rec-l1 b{ color:var(--green-mid); font-weight:600; }
446
+ .rec-l1.below b{ color:var(--dim); }
447
+ #rec-div{ color:var(--amber); overflow:hidden; text-overflow:ellipsis; }
430
448
  .b-health{ display:flex; align-items:center; gap:7px; font-size:11px; color:var(--dim); white-space:nowrap; }
431
449
  .dot{ width:6px; height:6px; border-radius:50%; background:var(--green); box-shadow:0 0 10px color-mix(in srgb, var(--green) 70%, transparent); animation:breathe 3.2s ease-in-out infinite; flex:none; }
432
450
  .dot.blue{ background:var(--blue); box-shadow:0 0 10px color-mix(in srgb, var(--blue) 70%, transparent); }
@@ -1159,21 +1177,32 @@
1159
1177
  <div class="csub" id="tok-sub">&nbsp;</div>
1160
1178
  <div id="tok-nights" aria-label="LLM tokens per nightly run this billing period"></div>
1161
1179
  </div>
1162
- <!-- Recall level — the designed U-band (#426 final ruling 2026-09-13).
1163
- Title/subtitle hierarchy is the mock of record; the raw ratio is
1164
- tooltip-only (relative value, not user-facing); zone edges arrive
1165
- from the /dashboard/field thresholds echo (never hardcoded here).
1166
- #409 fix round 3 (owner, verbatim intent): the zone word is the
1167
- card's BIG element (the .big .n slot, colored by sampling the band
1168
- gradient at the marker); the band sits in the siblings' chart slot
1169
- (header → big → band → foot — same child order as the limitbar
1170
- cards); "provisional" is hover-only (band tooltip); the marker
1171
- keeps the raw-value tooltip; the trend glyph stays (mock family). -->
1180
+ <!-- Memory Precision (#476, owner retitle) — ONE card, two context
1181
+ levels. Level 2 (recall depth) keeps the #426 U-band + zone word +
1182
+ provisional-hover semantics; the marker/tooltip read the WINDOW
1183
+ ratio off D.memory_precision.level2 (a pre-#476 server has no block
1184
+ → the headline ratio — today's rendering exactly). Level 1 (pushed
1185
+ index) is a compact second row: mean similarity of pushed lines to
1186
+ their prompt, with a floor marker at the echoed
1187
+ recall_min_similarity and muted below it (the index's bar reads
1188
+ conservative — its noise costs compound per prompt) + the
1189
+ redundancy share; direction-only coloring, NO verdict words (the
1190
+ #426 semantics ruling extended: similarity is
1191
+ conversation-dependent). Divergence count on the FACE, the ≤3
1192
+ titles hover-only (no memory lists on the main page — owner ruling
1193
+ 2026-09-12; the header search panel is the list surface). The
1194
+ window rides the #452 selector via the echoed window_days — never
1195
+ hardcoded, never a second range control. -->
1172
1196
  <div class="bseg" id="lim-rec">
1173
- <div class="gh"><span class="t">Recall level</span><span class="sub">uses per showing</span></div>
1197
+ <div class="gh"><span class="t">Memory Precision</span><span class="sub" id="rec-sub">uses per showing</span></div>
1174
1198
  <div class="big"><b class="n rec-word" id="rec-zone">—</b></div>
1175
1199
  <div class="rec-bandrow" id="rec-bandrow"><div class="rec-band" id="rec-band"></div><i class="rec-dot" id="rec-dot" hidden></i></div>
1176
- <div class="csub rec-foot"><span class="trend flat" id="rec-trend" title="last 7 nights vs the prior 7">&#9644;</span></div>
1200
+ <div class="rec-l1" id="rec-l1" hidden style="display:block">
1201
+ <div class="l1row" id="l1-rel">&nbsp;</div>
1202
+ <div class="l1row" id="l1-rep">&nbsp;</div>
1203
+ <div class="l1row" id="l1-off">&nbsp;</div>
1204
+ </div>
1205
+ <div class="csub rec-foot"><span id="rec-div" hidden></span></div>
1177
1206
  </div>
1178
1207
  </div>
1179
1208
  <!-- row 2 · ops & maturity — capture health · 30 nights · maturity split · model -->
@@ -1391,9 +1420,15 @@ function fmtTokens(n){
1391
1420
  return String(n);
1392
1421
  }
1393
1422
  /* deterministic per-id randomness — the same memory always lands in the
1394
- same place across reloads (positions are content of the field, not noise) */
1423
+ same place across reloads (positions are content of the field, not noise).
1424
+ #465: the salt hashes at the FRONT (hash32(salt+':'+s)) — FNV-1a does not
1425
+ diffuse a trailing-byte difference, and salts 1/2/3 differ only in their
1426
+ last character, so the old trailing form coupled angle to radius at
1427
+ Pearson r=0.953 (four spiral arms per domain). Salt-first measures
1428
+ r=0.019 (noise). Positions shift once (hash-derived content), then
1429
+ re-stabilize. */
1395
1430
  function hash32(s){ let h=2166136261; for(let i=0;i<s.length;i++){ h^=s.charCodeAt(i); h=Math.imul(h,16777619);} return h>>>0; }
1396
- function rand01(s,salt){ return hash32(s+':'+salt)/4294967296; }
1431
+ function rand01(s,salt){ return hash32(salt+':'+s)/4294967296; }
1397
1432
 
1398
1433
  /* ---------- token handling (harvested verbatim from the 0.20.4 console:
1399
1434
  ?token= handoff stripped from the URL, localStorage persistence, 401 →
@@ -1482,8 +1517,11 @@ const DEPTH_BANDS = { truth:[.9,1.05], belief:[1.05,1.3], forming:[1.3,1.55], fa
1482
1517
  post-backfill store still shows a visible gradient and the inflated
1483
1518
  pre-#425 store shows no monsters. Robust by construction: the mapping is
1484
1519
  absolute (no min/max over the live distribution), so it cannot explode on
1485
- a skewed store. Pure functions — the ratio is behaviorally tested. */
1486
- function bubbleRadius(s){ return 6 + Math.sqrt(clamp(s??0,0,1))*3; }
1520
+ a skewed store. Pure functions — the ratio is behaviorally tested.
1521
+ #474 graph final (owner 2026-09-19): the balls at 30% — 1.8→2.7, exactly
1522
+ 0.30× of the 6→9 law; the depth divisor and the ruled ratio band are
1523
+ scale-invariant and unchanged. */
1524
+ function bubbleRadius(s){ return 1.8 + Math.sqrt(clamp(s??0,0,1))*0.9; }
1487
1525
  function depthSizeDivisor(dz){ return Math.pow(Math.max(.14,dz),0.35); }
1488
1526
 
1489
1527
  /* ---------- live-data state ---------- */
@@ -1500,7 +1538,7 @@ let TOTAL=0, removedNow=0;
1500
1538
  clock (owner ruling 2026-09-13; only the boot reveal counts up) */
1501
1539
 
1502
1540
  /* =====================================================================
1503
- WORLD — domains, memories (every one), the interactive sample, real
1541
+ WORLD — domains, memories, the dateline window (the visible set), real
1504
1542
  links, the replay ledger. All positions derive from id hashes → stable
1505
1543
  across reloads and across the refreshes after Update/Remove.
1506
1544
  ===================================================================== */
@@ -1509,8 +1547,14 @@ const PORT = [ [.50,.20,.15],[.28,.38,.12],[.74,.40,.12],[.50,.62,.13],[.25,.68,
1509
1547
  let DOMAINS = []; // {name,count,land,port,cx,cy,cr}
1510
1548
  const mems = new Map(); // id -> memory object (live + replay ghosts)
1511
1549
  const links = []; // real link edges, engine-decorated
1512
- const bubbles = []; // the ~120-mem interactive importance sample
1513
1550
  let crowdByStage = {}; // stage -> [mem refs] (EVERY memory paints)
1551
+ const bubbles = []; // the latest-N-by-dateline visible set (#474
1552
+ // return; owned by refreshWindow, rebuilt on T)
1553
+ const FIELD_WINDOW_N = 1000; // #474 graph final (owner 2026-09-19): the field shows the latest N memories by dateline
1554
+ let WIN_ORDER = []; // every mem (live + ghosts), effCreated DESC, id ASC
1555
+ let winSet = new Set(); // ids in the window at the current T
1556
+ let winCount = []; // per-domain window counts (labels + tip)
1557
+ let winT = -1;
1514
1558
  let byAgentCounts = {}; // source_agent -> count
1515
1559
  let CONTEXTS = []; // [{id,name,machine,count}] — #421 machine × harness grouping
1516
1560
  let adj = new Map(); // id -> [neighbor ids] (real links)
@@ -1519,6 +1563,13 @@ let learnedL = [], mergedL = [], enrichedL = [];
1519
1563
  let NIGHTS = []; // the events payload's night list
1520
1564
  let WINDOW = 1, NOW_T = .999, REPLAY_OK = false;
1521
1565
 
1566
+ /* Replay pacing (owner 2026-09-19: 2x faster default). Event-driven cruise:
1567
+ each ledger event lands every REPLAY_TICK_MS; a heralded (birth/merge)
1568
+ event holds the clock REPLAY_HERALD_MS then applies; boundary beats rest
1569
+ REPLAY_REST_MS. Halve all three to run 2x; the constants are the only
1570
+ pacing surface. */
1571
+ const REPLAY_TICK_MS=205, REPLAY_HERALD_MS=108, REPLAY_REST_MS=800;
1572
+
1522
1573
  function domIndex(domain){ const i = DOMAINS.findIndex(d=>d.name===domain); return i<0?0:i; }
1523
1574
  function titleOf(id){ const m=mems.get(id); return m?m.title:'(absorbed)'; }
1524
1575
 
@@ -1583,6 +1634,10 @@ function buildWorld(){
1583
1634
  if(!m.ghost && !keep.has(id)) mems.delete(id);
1584
1635
  }
1585
1636
 
1637
+ // the crowd: EVERY memory, bucketed by stage for one fillStyle per pass
1638
+ crowdByStage = { forming:[], belief:[], truth:[], fading:[] };
1639
+ for(const m of mems.values()) (crowdByStage[m.stage]||crowdByStage.forming).push(m);
1640
+
1586
1641
  // replay metadata from the events ledger: in-window births + merge-outs
1587
1642
  // (absorbed losers get ghost entries so their merge animation has a
1588
1643
  // position and their birth a bloom — they are not part of the live field).
@@ -1651,34 +1706,12 @@ function buildWorld(){
1651
1706
  }
1652
1707
  }
1653
1708
 
1654
- // the crowd: EVERY memory, bucketed by stage for one fillStyle per pass
1655
- crowdByStage = { forming:[], belief:[], truth:[], fading:[] };
1656
- for(const m of mems.values()) (crowdByStage[m.stage]||crowdByStage.forming).push(m);
1657
-
1658
- // the interactive sample (#422 live review, "all dots green"): STRATIFIED
1659
- // by stage — quota proportional to each stage's share of live memories
1660
- // (largest-remainder to the fixed total, minimum 1 each), each quota filled
1661
- // by that stage's OWN strength→access ranking with an id-ASC tie-break —
1662
- // fully deterministic, no Math.random. The old global top-120-by-strength
1663
- // sample was 100% truth BY CONSTRUCTION (truth IS the top strength band),
1664
- // so every touchable dot read green and the 1–2px crowd was visually
1665
- // negligible; on an even store this yields ~30/30/30/30.
1666
- bubbles.length = 0;
1667
- {
1668
- const BUBBLE_N=120;
1669
- const stageArrs=STAGE_KEYS.map(k=>crowdByStage[k].filter(m=>!m.ghost)
1670
- .sort((a,b)=> b.strength-a.strength || b.access-a.access || (a.id<b.id?-1:a.id>b.id?1:0)));
1671
- const liveCount=stageArrs.reduce((n,a)=>n+a.length,0);
1672
- if(liveCount>0){
1673
- const exact=stageArrs.map(a=>a.length/liveCount*BUBBLE_N);
1674
- const quota=exact.map(Math.floor);
1675
- for(let i=0;i<quota.length;i++) if(quota[i]===0&&stageArrs[i].length>0) quota[i]=1;
1676
- let left=BUBBLE_N-quota.reduce((n,q)=>n+q,0);
1677
- const order=exact.map((_,i)=>i).sort((a,b)=>(exact[b]%1)-(exact[a]%1)); // remainder DESC, stage order tie
1678
- for(let oi=0; left>0; oi++, left--) quota[order[oi%order.length]]++;
1679
- stageArrs.forEach((a,i)=>{ bubbles.push(...a.slice(0,Math.min(quota[i],a.length))); });
1680
- }
1681
- }
1709
+ // #474 return: the visible set is the dateline WINDOW, not a sample —
1710
+ // WIN_ORDER walks every mem (live + ghosts) newest-first (effCreated DESC,
1711
+ // id ASC tie-break; ghosts sort via their birth night's date), and
1712
+ // refreshWindow (draw) slices the latest FIELD_WINDOW_N alive-at-T rows
1713
+ // out of it each time T moves.
1714
+ WIN_ORDER = Array.from(mems.values()).sort(winCmp);
1682
1715
 
1683
1716
  // contexts (source_agent values) with their machine stamps (#421 machine ×
1684
1717
  // harness): the rail + capture card group on (machine ?? legacy) × harness.
@@ -1740,6 +1773,31 @@ function aliveAt(b,t){
1740
1773
  return true;
1741
1774
  }
1742
1775
  function dateAt(t){ const i=clamp(Math.floor(t),0,NIGHTS.length-1); return NIGHTS[i]?NIGHTS[i].date:''; }
1776
+ /* #474 return: the visible set is a DATELINE WINDOW — at replay clock T the
1777
+ field draws the latest FIELD_WINDOW_N memories by effective created date
1778
+ (created_at; ghosts via their birth night), not a sample and not the whole
1779
+ store. Pure helpers (behaviorally tested). */
1780
+ function effCreated(m){ return m.created || (m.born>=0 ? dateAt(m.born) : ''); }
1781
+ function windowPick(order, t, n, dl, isNow){
1782
+ const out=[];
1783
+ for(const m of order){
1784
+ if(out.length>=n) break;
1785
+ if(!aliveAt(m,t)) continue;
1786
+ if(!isNow && m.born<0 && effCreated(m)>dl) continue; // untracked + newer than the dateline
1787
+ out.push(m);
1788
+ }
1789
+ return out;
1790
+ }
1791
+ function winCmp(a,b){ return effCreated(b)<effCreated(a)?-1: effCreated(b)>effCreated(a)?1: a.id<b.id?-1:1; }
1792
+ function refreshWindow(t){
1793
+ if(t===winT) return;
1794
+ winT=t;
1795
+ const picked = windowPick(WIN_ORDER, t, FIELD_WINDOW_N, dateAt(t), t>=NOW_T);
1796
+ winSet = new Set(picked.map(m=>m.id));
1797
+ winCount = new Array(DOMAINS.length).fill(0);
1798
+ for(const m of picked) winCount[m.dom]++;
1799
+ bubbles.length=0; bubbles.push(...picked);
1800
+ }
1743
1801
 
1744
1802
  /* =====================================================================
1745
1803
  THEME — one engine, two palettes. Dark (the brand night: glow) is the
@@ -1897,22 +1955,16 @@ function sprite(stage, r){
1897
1955
  const st = PAL.stages[stage] || PAL.stages.forming;
1898
1956
  const rgb = st.rgb;
1899
1957
  if(PAL.name==='dark'){
1958
+ /* #474 final polish (owner 2026-09-20): plain dot — no glow halo, no
1959
+ white core gradient. A filled circle with a slightly brighter edge;
1960
+ the maturity color carries the reading, the size stays small. */
1900
1961
  const S = Math.ceil(rad*PAL.sprK), c2 = document.createElement('canvas');
1901
1962
  c2.width=S; c2.height=S;
1902
1963
  const g = c2.getContext('2d'), cxp=S/2;
1903
- let gr = g.createRadialGradient(cxp,cxp,rad*.2, cxp,cxp,rad*2.3);
1904
- gr.addColorStop(0, rgba(rgb,.5*st.glow));
1905
- gr.addColorStop(.45, rgba(rgb,.16*st.glow));
1906
- gr.addColorStop(1, rgba(rgb,0));
1907
- g.fillStyle=gr; g.fillRect(0,0,S,S);
1908
- gr = g.createRadialGradient(cxp-rad*.28,cxp-rad*.32,rad*.1, cxp,cxp,rad);
1909
- gr.addColorStop(0,'rgba(255,255,255,'+(.5*st.alpha)+')');
1910
- gr.addColorStop(.35, rgba(rgb,.55*st.alpha));
1911
- gr.addColorStop(.82, rgba(rgb,.22*st.alpha));
1912
- gr.addColorStop(1, rgba(rgb,.04));
1913
- g.fillStyle=gr; g.beginPath(); g.arc(cxp,cxp,rad,0,7); g.fill();
1914
- g.strokeStyle=rgba(rgb,.34*st.alpha); g.lineWidth=1;
1915
- g.beginPath(); g.arc(cxp,cxp,rad-.5,0,7); g.stroke();
1964
+ g.fillStyle=rgba(rgb,.88*st.alpha);
1965
+ g.beginPath(); g.arc(cxp,cxp,rad*.82,0,7); g.fill();
1966
+ g.strokeStyle=rgba(rgb,.45*st.alpha); g.lineWidth=1;
1967
+ g.beginPath(); g.arc(cxp,cxp,rad*.82,0,7); g.stroke();
1916
1968
  /* #422 round 2: fading's DISTINCT outer ring — the rim stage's fill runs
1917
1969
  at low alpha by design; without its own edge treatment the dots were
1918
1970
  illegible against the night ground */
@@ -1995,7 +2047,7 @@ function buildPaths(){
1995
2047
  }
1996
2048
  }
1997
2049
  function pathAlive(p,t){
1998
- for(const id of p.nodes){ const b=mems.get(id); if(!b||!aliveAt(b,t)) return false; }
2050
+ for(const id of p.nodes){ const b=mems.get(id); if(!b||!aliveAt(b,t)||!winSet.has(id)) return false; }
1999
2051
  return t>=p.born;
2000
2052
  }
2001
2053
  function spawnSignal(showcase){
@@ -2054,8 +2106,10 @@ function advanceSignals(now, dt){
2054
2106
  }
2055
2107
 
2056
2108
  /* =====================================================================
2057
- THE FRAME — theme-aware; crowd first (micro-stars), then synapses,
2058
- signals, bubbles (painter-sorted far→near), waymarkers, furniture.
2109
+ THE FRAME — theme-aware; synapses, signals, bubbles (painter-sorted
2110
+ far→near), waymarkers, furniture. #474 return: the ONLY dots drawn are
2111
+ the dateline window (latest FIELD_WINDOW_N by effective created date at
2112
+ the replay clock — see refreshWindow); no crowd pass, no sample.
2059
2113
  ===================================================================== */
2060
2114
  let labelRects=[];
2061
2115
  /* emergence queue (#422 live review): a night's births/links become VISIBLE
@@ -2099,6 +2153,7 @@ function processEmergence(t){
2099
2153
  }
2100
2154
  function draw(now, dt){
2101
2155
  const t=T;
2156
+ refreshWindow(t); // #474: the visible set = latest-N by dateline at this T (bubbles, winSet, winCount)
2102
2157
  const dark = PAL.name==='dark';
2103
2158
  const ADD = dark ? 'lighter' : 'source-over';
2104
2159
  ctx.clearRect(0,0,view.w,view.h);
@@ -2164,6 +2219,12 @@ function draw(now, dt){
2164
2219
  }
2165
2220
  ctx.globalAlpha=1;
2166
2221
 
2222
+ /* emergence flags resolve FIRST (once per frame) — the visible set and
2223
+ the link pass below read them; dots still queued (_emerged===false)
2224
+ stay invisible until their frame budget comes up. (#474: the old
2225
+ every-memory crowd pass is gone — the window IS the field.) */
2226
+ processEmergence(t);
2227
+
2167
2228
  /* THE CROWD — every memory in the store as maturity-colored micro-stars
2168
2229
  behind the touchable sample (no sampling: the dot count IS the store).
2169
2230
  One fillStyle per stage pass. The emergence queue is resolved FIRST
@@ -2181,7 +2242,7 @@ function draw(now, dt){
2181
2242
  if(fog<.15) continue;
2182
2243
  const [sx,sy]=proj3(m.nx||.5,m.ny||.5,dz);
2183
2244
  if(sx<-2||sy<-2||sx>view.w+2||sy>view.h+2) continue;
2184
- ctx.globalAlpha=fog*.5;
2245
+ ctx.globalAlpha=fog*.5*(focusDom!=null&&m.dom!==focusDom?.15:1);
2185
2246
  const px=Math.max(1,Math.round((rand01(m.id,9)<.85?1:1.8)/dz*1.4));
2186
2247
  ctx.fillRect(sx,sy,px,px);
2187
2248
  }
@@ -2235,6 +2296,7 @@ function draw(now, dt){
2235
2296
  if(L.rk>=lodK) continue; // zoom LOD (lines only)
2236
2297
  const a=mems.get(L.a), b=mems.get(L.b);
2237
2298
  if(!a||!b||!aliveAt(a,t)||!aliveAt(b,t)) continue;
2299
+ /* #474 final polish: the both-endpoints-in-window gate killed the mesh — links draw between ANY placed memories (bedrock parity: the full LOD-tamed mesh), endpoints carry positions regardless of window membership */
2238
2300
  if(a._emerged===false||b._emerged===false) continue; // thread waits for both endpoints
2239
2301
  const [ax,ay]=bpos(a,now,t), [bx,by]=bpos(b,now,t);
2240
2302
  const ctrl=segCtrlN(ax,ay,bx,by,(L.use>18?1:-1));
@@ -2249,6 +2311,9 @@ function draw(now, dt){
2249
2311
  const prog = L.forming ? clamp((t-(L._emergeT!==undefined?L._emergeT:L.born))/1.4,0,1) : 1;
2250
2312
  const drawP=Math.min(prog,linkBoot);
2251
2313
  if(drawP<=0) continue;
2314
+ /* #474 final polish: domain focus — a thread stays lit only if an
2315
+ endpoint belongs to the focused domain (owner 2026-09-20) */
2316
+ const focusDim = (focusDom!=null && a.dom!==focusDom && b.dom!==focusDom) ? .15 : 1;
2252
2317
  const len=Math.hypot(x2-x1,y2-y1)*1.15;
2253
2318
  const superFam = L.rel==='superseded_by'||L.rel==='corrected_by';
2254
2319
  /* fix-round-3 weights: settled alphas roughly halved (both themes) so the
@@ -2256,7 +2321,7 @@ function draw(now, dt){
2256
2321
  const bright=(a.stage==='truth'||b.stage==='truth') ? (dark?.07:.11) : (dark?.035:.06);
2257
2322
  const baseW=.34+useF*.55+heat*.5;
2258
2323
  /* the SETTLED stroke is always the base */
2259
- ctx.strokeStyle=rgba(PAL.thread,(bright*(superFam?.45:1)+useF*(dark?.05:.07)+heat*.28)*linkBoot*tfog);
2324
+ ctx.strokeStyle=rgba(PAL.thread,(bright*(superFam?.45:1)+useF*(dark?.05:.07)+heat*.28)*linkBoot*tfog*focusDim);
2260
2325
  ctx.lineWidth=baseW;
2261
2326
  ctx.setLineDash(superFam?[4,4]:[len*drawP,1e5]);
2262
2327
  ctx.beginPath(); ctx.moveTo(x1,y1); ctx.quadraticCurveTo(cx1,cy1,x2,y2); ctx.stroke();
@@ -2265,7 +2330,7 @@ function draw(now, dt){
2265
2330
  in the same proportion as the settled stroke) */
2266
2331
  const settle=1-prog;
2267
2332
  const baseA=(dark? .07 : .09)+((dark?.15:.17)-(dark?.07:.09))*drawP;
2268
- ctx.strokeStyle=rgba(PAL.threadHot,(baseA+.14*heat)*tfog*(superFam?.5:1)*settle);
2333
+ ctx.strokeStyle=rgba(PAL.threadHot,(baseA+.14*heat)*tfog*(superFam?.5:1)*settle*focusDim);
2269
2334
  ctx.lineWidth=baseW+(.18+drawP*.3)*settle;
2270
2335
  ctx.setLineDash(superFam?[4,4]:[len*drawP,1e5]);
2271
2336
  ctx.beginPath(); ctx.moveTo(x1,y1); ctx.quadraticCurveTo(cx1,cy1,x2,y2); ctx.stroke();
@@ -2340,8 +2405,9 @@ function draw(now, dt){
2340
2405
  dark-blue-on-near-black at ~4px read invisible to a vision model);
2341
2406
  the projection factor is known here, and far-stage bubbles (rim depth
2342
2407
  / small viewports) were collapsing to ~1-2px. Every stage stays a
2343
- legible glimmer; ordering untouched. */
2344
- r=Math.max(r,5);
2408
+ legible glimmer; ordering untouched. #474 graph final: floor 1.5 —
2409
+ 0.30× of 5, proportional with the 30% balls. */
2410
+ r=Math.max(r,1.5);
2345
2411
  let remA=1;
2346
2412
  if(b.removing){ const p=clamp((now-b.removing)/700,0,1); remA=1-p; if(p>=1){ b.removed=true; b.removing=undefined; } }
2347
2413
  const ex=b._ex||0;
@@ -2441,8 +2507,11 @@ function draw(now, dt){
2441
2507
  ctx.font=(9.5*k).toFixed(1)+'px '+FF;
2442
2508
  try{ ctx.letterSpacing=(1*k).toFixed(1)+'px'; }catch(e){}
2443
2509
  ctx.fillStyle= dark? 'rgba(90,90,90,'+(.85*dimming*lp)+')' : hexA(PAL.labelCount,dimming*lp);
2444
- ctx.fillText(fmt(d.count), sx+w+9*k, sy);
2445
- const bw=w+9*k+ctx.measureText(fmt(d.count)).width;
2510
+ /* #474: the label count is the WINDOW count (what is drawn at this T),
2511
+ not the store total — honest with the dateline view. */
2512
+ const cnt=fmt(winCount[i]||0);
2513
+ ctx.fillText(cnt, sx+w+9*k, sy);
2514
+ const bw=w+9*k+ctx.measureText(cnt).width;
2446
2515
  ctx.strokeStyle= active? rgba(PAL.stages.truth.rgb,.55*lp) : (dark? 'rgba(120,130,140,'+(.4*dimming*lp)+')' : 'rgba(96,92,78,'+(.5*dimming*lp)+')');
2447
2516
  ctx.lineWidth=1;
2448
2517
  const bx=sx-9*k, by=sy-11*k, bxx=bx+bw+9*k, byy=by+15*k, tk=4*k;
@@ -2607,18 +2676,18 @@ function autoTick(now){
2607
2676
  if(T>=NOW_T-.001){
2608
2677
  /* a beat at "now": re-kick to the range start ONLY before the first
2609
2678
  full playback; after it, REST here — plays once, never loops (#452) */
2610
- if(played){ autoPlay=false; autoNextAt=now+3200; return; }
2611
- autoEvIdx=0; autoNextAt=now+3200; seek(.001); return;
2679
+ if(played){ autoPlay=false; autoNextAt=now+REPLAY_REST_MS; return; }
2680
+ autoEvIdx=0; autoNextAt=now+REPLAY_REST_MS; seek(.001); return;
2612
2681
  }
2613
2682
  while(autoEvIdx<EVENTS.length && EVENTS[autoEvIdx].t<=T+.0001) autoEvIdx++;
2614
2683
  if(autoEvIdx>=EVENTS.length){
2615
2684
  /* ledger exhausted: arrive at today and complete the playback (the
2616
2685
  flags go BEFORE the seek so onTChanged's control swap sees them) */
2617
- autoNextAt=now+3200; played=true; autoPlay=false; seek(WINDOW); return;
2686
+ autoNextAt=now+REPLAY_REST_MS; played=true; autoPlay=false; seek(WINDOW); return;
2618
2687
  }
2619
2688
  const ev=EVENTS[autoEvIdx++];
2620
- if(ev.id && signals.length<8){ heraldTo(ev.id); pendingApply={t:ev.t+.001, at:now+430}; }
2621
- else { seek(ev.t+.001); autoNextAt=now+820; }
2689
+ if(ev.id && signals.length<8){ heraldTo(ev.id); pendingApply={t:ev.t+.001, at:now+REPLAY_HERALD_MS}; }
2690
+ else { seek(ev.t+.001); autoNextAt=now+REPLAY_TICK_MS; }
2622
2691
  }
2623
2692
  track.addEventListener('pointerdown',e=>{ if(!REPLAY_OK) return; scrubbing=true; manualTake(); track.setPointerCapture(e.pointerId); seek(trackFrac(e)*WINDOW); });
2624
2693
  track.addEventListener('pointermove',e=>{ if(scrubbing) seek(trackFrac(e)*WINDOW); });
@@ -2835,7 +2904,8 @@ cv.addEventListener('pointermove',e=>{
2835
2904
  tip.style.display='block';
2836
2905
  tip.style.left=Math.min(e.clientX+14,window.innerWidth-300)+'px';
2837
2906
  tip.style.top=(e.clientY+16)+'px';
2838
- tip.innerHTML='<b>'+esc(d.name)+'</b><span style="color:var(--dim)">'+fmt(d.count)+' memories</span>';
2907
+ /* #474: the window count at this T — honest with what is drawn */
2908
+ tip.innerHTML='<b>'+esc(d.name)+'</b><span style="color:var(--dim)">'+fmt(winCount[lb]||0)+' in view</span>';
2839
2909
  }else tip.style.display='none';
2840
2910
  dirty=true;
2841
2911
  });
@@ -3174,7 +3244,14 @@ function recallGradAt(frac,z,theme){
3174
3244
  }
3175
3245
  let lastRecUses=null; /* setTheme's re-render carries the last value */
3176
3246
  function renderRecBand(ups){
3247
+ /* #476: the marker/tooltip read the WINDOW ratio when the memory_precision
3248
+ block is present; a pre-#476 server (no block) degrades to the headline
3249
+ ratio — today's rendering exactly (the capture card's block-absence
3250
+ guard pattern). A null window value honestly renders "—" below. */
3251
+ const mp=D&&D.memory_precision?D.memory_precision:null;
3252
+ if(mp&&mp.level2) ups=mp.level2.uses_per_showing;
3177
3253
  lastRecUses=ups;
3254
+ renderRecL1(mp);
3178
3255
  const z=F&&F.thresholds?F.thresholds.recall_uses:null;
3179
3256
  const axis=z&&typeof z.axis_max==='number'&&z.axis_max>0?z.axis_max:null;
3180
3257
  const row=el('rec-bandrow'), band=el('rec-band'), dot=el('rec-dot'), word=el('rec-zone');
@@ -3198,7 +3275,7 @@ function renderRecBand(ups){
3198
3275
  if(zk){
3199
3276
  dot.hidden=false;
3200
3277
  dot.style.left=pct(ups)+'%';
3201
- dot.title=ups.toFixed(3)+' uses per showing — last 30 nights';
3278
+ dot.title=ups.toFixed(3)+' uses per showing — last '+(mp&&mp.window_days?mp.window_days:30)+' days';
3202
3279
  word.textContent=RECALL_ZONE_WORD[zk];
3203
3280
  word.className='n rec-word';
3204
3281
  /* the big word's color = the gradient sampled AT the marker — the word
@@ -3226,6 +3303,62 @@ function renderRecBand(ups){
3226
3303
  }
3227
3304
  }
3228
3305
 
3306
+ /* ---------- Memory Precision — Level 1, the pushed index (#476). A compact
3307
+ second row beside Level 2's U-band: mean similarity of pushed lines to
3308
+ their prompt (a 0..1 bar with a floor tick at the /dashboard/field echo of
3309
+ recall_min_similarity — the server's own recall floor, never a page
3310
+ literal), muted below the floor (the index row reads conservative: its
3311
+ noise lands on every prompt), plus the redundancy share (lines restating
3312
+ the session's standing context at/above the echoed cosine). Direction-only
3313
+ coloring, NO verdict words — the #426 semantics ruling extended.
3314
+ Divergence: the COUNT on the card face, the ≤3 titles hover-only (no
3315
+ memory lists on the main page, owner 2026-09-12 — the search panel is the
3316
+ list surface). Absent block (pre-#476 server) → the whole row hides and
3317
+ the sub keeps the old label — today's rendering exactly. */
3318
+ function renderRecL1(mp){
3319
+ const row=el('rec-l1'), sub=el('rec-sub'), dv=el('rec-div');
3320
+ if(!mp||!mp.level1||!mp.level2){
3321
+ row.hidden=true; dv.hidden=true; dv.title='';
3322
+ if(sub) sub.textContent='uses per showing';
3323
+ return;
3324
+ }
3325
+ if(sub) sub.textContent='last '+(mp.window_days||90)+' days';
3326
+ const l1=mp.level1;
3327
+ const floor=(F&&F.thresholds&&typeof F.thresholds.recall_min_similarity==='number')?F.thresholds.recall_min_similarity:null;
3328
+ const ra=(l1.thresholds&&typeof l1.thresholds.redundant_above==='number')?l1.thresholds.redundant_above:null;
3329
+ const mins=(l1.thresholds&&typeof l1.thresholds.divergence_min_shown==='number')?l1.thresholds.divergence_min_shown:5;
3330
+ const sim=(typeof l1.mean_similarity==='number'&&isFinite(l1.mean_similarity))?l1.mean_similarity:null;
3331
+ const red=(typeof l1.redundant_share==='number'&&isFinite(l1.redundant_share))?l1.redundant_share:null;
3332
+ if(sim===null){
3333
+ row.hidden=true; /* nothing measured in the window — the row degrades away */
3334
+ }else{
3335
+ row.hidden=false;
3336
+ row.classList.toggle('below',floor!==null&&sim<floor);
3337
+ /* numbers only on the face — direction carries the meaning, no verdict words */
3338
+ /* #476 owner rename (2026-09-20): the three buckets as named — Relevant /
3339
+ Repeated known / Off-target. Relevant = the mean relevance; Repeated =
3340
+ the redundant share; Off-target = the remainder (pushed lines that were
3341
+ neither relevant nor new). Numbers, no verdict words (#426). */
3342
+ const relPct=Math.round(clamp(sim,0,1)*100);
3343
+ const repPct=red!==null?Math.round(clamp(red,0,1)*100):0;
3344
+ const offPct=Math.max(0,100-relPct-repPct);
3345
+ const dv=mp.divergence, nUsed=(dv&&typeof dv.count==='number')?dv.count:0;
3346
+ el('l1-rel').innerHTML='<b>Relevant: '+relPct+'%</b>';
3347
+ el('l1-rep').textContent='Repeated known: '+repPct+'%';
3348
+ el('l1-off').textContent='Off-target: '+offPct+'%'+(nUsed>0?' ('+nUsed+' never used)':'');
3349
+ row.title='pushed index, last '+(mp.window_days||90)+' days — mean similarity of pushed lines to their prompt (tick at the recall floor '+
3350
+ (floor!==null?floor.toFixed(2):'—')+'; muted below it)'+
3351
+ (ra!==null?'; redundant = share of lines at ≥'+ra.toFixed(2)+' cosine vs the session\'s standing context (lessons + identity)':'')+
3352
+ ' · '+fmt(l1.pushes)+' pushes, '+fmt(l1.lines)+' lines';
3353
+ }
3354
+ /* divergence — the face count is amber; the ≤3 lines ride the native
3355
+ tooltip ONLY (text via .title, never face content) */
3356
+ /* #476 owner bundling (2026-09-20): the never-used count rides inside
3357
+ Off-target; the separate face element stays hidden (titles on hover) */
3358
+ const dEl=el('rec-div');
3359
+ if(dEl){ dEl.hidden=true; }
3360
+ }
3361
+
3229
3362
  let MODEL_LABEL='';
3230
3363
  /* #409 fix round 5 — owner ruling 2026-09-14 (verbatim): "I do NOT want to
3231
3364
  know what it is compatible with, I want to know the fucking model." The
@@ -105,6 +105,39 @@ export declare const RECALL_USES_NORMAL_MAX = 0.25;
105
105
  * PROVISIONAL (owner anchor 2026-09-13, #426) — chosen so Overfetching
106
106
  * keeps a visible span, not a measured bound. */
107
107
  export declare const RECALL_USES_AXIS_MAX = 0.3;
108
+ /**
109
+ * Retention horizon for the recall_pushes/recall_events sidecar tables AND the
110
+ * longest window the Memory Precision card shows — ONE constant feeds both
111
+ * (the CAPTURE_HEALTH_WINDOW_DAYS single-constant law: the card can never
112
+ * claim a window the store no longer has rows for). 90 = the longest #452
113
+ * range option that the retention can honestly serve; 180d and all clamp to
114
+ * it server-side and the payload echoes the effective window_days.
115
+ */
116
+ export declare const MEMORY_PRECISION_WINDOW_DAYS = 90;
117
+ /**
118
+ * Cosine at/above which a pushed line reads "redundant" (restating the
119
+ * session's standing context — the top lessons + identity every session-start
120
+ * hook injects). Direction-only display guidance, never a gate. 0.80 mirrors
121
+ * SUPERSESSION_MIN_SIMILARITY: at/above it a pair reads as the same
122
+ * statement, below it as topical overlap — a reasonable first anchor until
123
+ * the judged-calibration follow-up measures the real distribution (the
124
+ * #426 provisional-anchor posture).
125
+ */
126
+ export declare const MEMORY_PRECISION_REDUNDANT_ABOVE = 0.8;
127
+ /**
128
+ * Window showings a live memory needs (with zero window fetches) to count as
129
+ * DIVERGING — the index keeps pushing it, nothing ever reads it. 5 is the
130
+ * spec's anchor (#476): enough showings that the silence is a pattern, not
131
+ * one prompt's turn-suppression.
132
+ */
133
+ export declare const MEMORY_PRECISION_DIVERGENCE_MIN_SHOWN = 5;
134
+ /**
135
+ * Cap on the prompt excerpt persisted per recall_pushes row (Option A,
136
+ * owner-blessed 2026-09-19: the first-ever server-side persistence of raw
137
+ * prompt text, bounded to 256 chars and pruned at the retention horizon —
138
+ * the same trust boundary as /distill's denoised conversation text).
139
+ */
140
+ export declare const MEMORY_PRECISION_PROMPT_EXCERPT_CHARS = 256;
108
141
  /** Semantic-similarity share of the composite score. 0.50 (raised from 0.40
109
142
  * in the 0.15.2 rebalance, #191 Phase B): on the production corpus effective
110
143
  * strength (0.30) outweighed what similarity could recover — hardened old
@@ -115,23 +148,58 @@ export declare const SCORE_SIMILARITY_WEIGHT = 0.5;
115
148
  export declare const SCORE_STRENGTH_WEIGHT = 0.2;
116
149
  /** Graph-centrality share of the composite score (was 0.20; see above). */
117
150
  export declare const SCORE_CONNECTIONS_WEIGHT = 0.15;
118
- /** Slow recency curve share of the composite score (was 0.10; see above). */
151
+ /**
152
+ * Log-saturation degree K for the connections term (#449 PR E, items 1+3):
153
+ * the credit is min(1, log1p(k) / log1p(K)) × SCORE_CONNECTIONS_WEIGHT on
154
+ * the ABSOLUTE undirected-degree scale k — full at k = K, exactly +0 at
155
+ * k = 0 (unlinked rows score bit-identically to the linear term they
156
+ * replace). Provenance: p99 of the REAL undirected degree distribution
157
+ * (read-only audit 2026-09-17 of the canonical wave snapshot, 18,798
158
+ * memories / 14,600 linked: p50 = 4, p90 = 8, p95 = 9, p99 = 15, p99.9 =
159
+ * 28.4, max = 40; 135 memories ≥ 16 links = 0.92% of linked) — so the top
160
+ * ~1% of hubs tie at full credit and everything below differentiates
161
+ * modestly. Shape grounded in the #449 research base: ACT-R fan saturation
162
+ * (Anderson & Reder 1999 — activation falls with the LOG of fan), SAM's
163
+ * saturating returns, cue overload (Watkins & Watkins 1975). Replaces the
164
+ * candidate-set-relative normalization #449 deleted (k / the per-query max
165
+ * degree) — a row's score no longer depends on which other rows happened
166
+ * to match. Alternatives K = 8 (p90 — collapses all p90+ rows to
167
+ * full credit) and K = 32 (≈p99.9 — leaves the p99 hub at 0.12 of the
168
+ * term) were tabled in the #449 spec. RELEASE-MANAGED per this module's
169
+ * evolution contract — overridable only through the configureScoring seam.
170
+ */
171
+ export declare const CONNECTIONS_SATURATION_DEGREE = 16;
172
+ /** Time-curve share of the composite score — the merged curve's blend weight
173
+ * AND slow-region amplitude (was 0.10; see above). The four blend weights
174
+ * still sum to 1.0; the head amplitude below is a documented overshoot, NOT
175
+ * a fifth blend weight (#430). Zero (via the seam) disables the whole term. */
119
176
  export declare const SCORE_RECENCY_WEIGHT = 0.15;
120
- /** Fresh-memory window: the additive bonus fades linearly to 0 over this
121
- * many days. 7 — nightly capture means 1 day is the floor of "fresh"
122
- * (#191 Phase B). */
123
- export declare const FRESHNESS_BOOST_DAYS = 7;
124
- /** Fresh-memory bonus size at age 0 (#191 Phase B; 0 = disabled via seam). */
125
- export declare const FRESHNESS_BOOST_WEIGHT = 0.15;
177
+ /** Hourly decay of the merged curve's slow region (#430) — promoted from the
178
+ * inline literal computeScore carried; half-life ≈ 57.7 days. At and beyond
179
+ * RECENCY_HEAD_DAYS the curve is bit-identical to the pre-#430 slow term
180
+ * (SCORE_RECENCY_WEIGHT × RECENCY_HOURLY_DECAY^hours). */
181
+ export declare const RECENCY_HOURLY_DECAY = 0.9995;
182
+ /** Merged time-curve head amplitude at age 0 (#430) — the pre-#430 slow
183
+ * weight 0.15 + fresh bonus 0.15: the freshness job is now the early steep
184
+ * part of ONE curve. The head joins value-continuously onto the slow region
185
+ * at RECENCY_HEAD_DAYS; its rate is DERIVED from that continuity, never a
186
+ * free constant. */
187
+ export declare const RECENCY_HEAD_WEIGHT = 0.3;
188
+ /** Join age in days of the merged time curve (#430) — the head window edge,
189
+ * carried over from the pre-#430 freshness window. Nightly capture means
190
+ * 1 day is the floor of "fresh" (#191 Phase B). */
191
+ export declare const RECENCY_HEAD_DAYS = 7;
126
192
  /** Score multiplier for a memory a later decision superseded (0.15.2; the
127
193
  * belief walk (#393 D) is the primary mechanism — this is the safety net
128
194
  * for rows the walk does not reach). */
129
195
  export declare const SUPERSEDED_DEMOTION = 0.5;
130
- /** #203 soft boost on exact project match. ADDITIVE, zero-boost neutral,
131
- * never a penalty — a foreign memory ranks equal, not lower. */
132
- export declare const PROJECT_AFFINITY_WEIGHT = 0.15;
133
- /** #203 soft boost multiplier on max overlapping domain-tag weight. */
134
- export declare const DOMAIN_AFFINITY_WEIGHT = 0.15;
196
+ /** #430 merged scope affinity — ONE term replacing #203's two boosts (the
197
+ * project-affinity and domain-affinity constants) at their shared value.
198
+ * Boost = max(project-match indicator (1 on exact match), max overlapping
199
+ * domain-tag weight) × this weight: the strongest single scope signal counts
200
+ * once, never stacked. ADDITIVE, zero-boost neutral, never a penalty — an
201
+ * absent scope adds exactly 0; a foreign memory ranks equal, not lower. */
202
+ export declare const SCOPE_AFFINITY_WEIGHT = 0.15;
135
203
  /** #205 RRF k parameter (1/(k+rank+1)) — matches the pre-#205 hardcoded 60
136
204
  * so the no-config path was byte-identical to 0.15.3. */
137
205
  export declare const RRF_K = 60;
@@ -188,6 +256,18 @@ export declare const BM25_WEIGHT_DOMAIN = 2;
188
256
  * base-1.0 rows decay again.
189
257
  */
190
258
  export declare const IMPORTANCE_CEILING = 0.95;
259
+ /**
260
+ * How many days a memory stays in the nightly importance-settle pool after
261
+ * ingest (#478): a row's importance re-settles nightly while young — keyed
262
+ * on PER-ROW age (ingested_at), never the global lastConsolidated
263
+ * watermark, which sticks whenever a run defers and used to drag a growing
264
+ * cohort back through re-scoring (each re-settle overwrites base_strength,
265
+ * erasing promotion gains). Rows leave the pool after the window; promoted
266
+ * or enriched rows leave earlier via the paid-gain guard. Release-managed
267
+ * (#408 discipline): moving it is a release decision with soak evidence,
268
+ * not a config knob.
269
+ */
270
+ export declare const IMPORTANCE_SETTLE_WINDOW_DAYS = 3;
191
271
  /** Deterministic merge ceiling of the unified resolution pass (#392): pairs
192
272
  * at/above this cosine merge LLM-free; [CORRECTION_MIN_SIMILARITY, this)
193
273
  * get the one verdict call. 0.92 — measured on the #191 mechanical audit