maxpool 1.8.5 → 1.8.7
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.
- package/package.json +1 -1
- package/src/capacity-ledger.js +26 -3
package/package.json
CHANGED
package/src/capacity-ledger.js
CHANGED
|
@@ -34,6 +34,17 @@ const SAME_BOUNDARY_MS = 5_000;
|
|
|
34
34
|
const MAX_CYCLES_PER_WINDOW = 50;
|
|
35
35
|
const MAX_DAY_BUCKETS = 10;
|
|
36
36
|
|
|
37
|
+
// Demote a pre-v3 closed cycle: unverifiable (every one predates the joined-mid-window
|
|
38
|
+
// fix) AND untrustworthy in its timestamps (the v1.8.4 advance-close could date a row
|
|
39
|
+
// hours into the future — live: a row ending 19:54 while the clock read 15:02). Also
|
|
40
|
+
// CLAMPS a future endedAt to startedAt so the stored row itself stops violating
|
|
41
|
+
// invariants after migration, not just the averages.
|
|
42
|
+
function demote(c) {
|
|
43
|
+
// Timestamp repair is NOT done here — the load-time pass below handles a future
|
|
44
|
+
// endedAt at every schema version, so doing it twice would be two places to fix.
|
|
45
|
+
return { ...c, complete: false, partialReason: 'pre-v3-unverified' };
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
export class CapacityLedger {
|
|
38
49
|
constructor({ now = () => Date.now() } = {}) {
|
|
39
50
|
this._now = now;
|
|
@@ -55,9 +66,9 @@ export class CapacityLedger {
|
|
|
55
66
|
for (const [name, rec] of Object.entries(payload.accounts || {})) {
|
|
56
67
|
migrated.accounts[name] = {
|
|
57
68
|
ses: { open: rec.ses?.open || null,
|
|
58
|
-
closed: (rec.ses?.closed || []).map(c => (
|
|
69
|
+
closed: (rec.ses?.closed || []).map(c => demote(c)) },
|
|
59
70
|
wk: { open: rec.wk?.open || null,
|
|
60
|
-
closed: (rec.wk?.closed || []).map(c => (
|
|
71
|
+
closed: (rec.wk?.closed || []).map(c => demote(c)) },
|
|
61
72
|
days: rec.days || {},
|
|
62
73
|
};
|
|
63
74
|
}
|
|
@@ -77,7 +88,19 @@ export class CapacityLedger {
|
|
|
77
88
|
const a = { ses: { open: null, closed: [] }, wk: { open: null, closed: [] }, days: {} };
|
|
78
89
|
for (const w of ['ses', 'wk']) {
|
|
79
90
|
if (rec[w]?.open) a[w].open = { ...rec[w].open };
|
|
80
|
-
if (Array.isArray(rec[w]?.closed))
|
|
91
|
+
if (Array.isArray(rec[w]?.closed)) {
|
|
92
|
+
// LOAD-TIME REPAIR, applied at EVERY version — not inside the v2→v3 branch,
|
|
93
|
+
// which is where it was first (wrongly) placed: a future-dated row can be
|
|
94
|
+
// written by any build whose writer had the bug, and the payload it lands in
|
|
95
|
+
// is then already current, so a version-gated repair never runs. Measured
|
|
96
|
+
// 2026-08-23: a row ending 19:54 survived the v3 migration untouched because
|
|
97
|
+
// the state was already v3. A cycle cannot end after now; such a row is
|
|
98
|
+
// corrupt, so demote it and clamp it rather than let it re-alert until evicted.
|
|
99
|
+
a[w].closed = rec[w].closed.slice(-MAX_CYCLES_PER_WINDOW).map(c =>
|
|
100
|
+
(c.endedAt > Date.now() + 60_000)
|
|
101
|
+
? { ...c, endedAt: c.startedAt, complete: false, partialReason: 'repaired-future-endedAt' }
|
|
102
|
+
: c);
|
|
103
|
+
}
|
|
81
104
|
}
|
|
82
105
|
if (rec.days && typeof rec.days === 'object') {
|
|
83
106
|
for (const [d, v] of Object.entries(rec.days)) a.days[d] = { ...v };
|