@lmzhen/dsh-evolution-replay 0.3.67 → 0.3.69

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/lib/index.js CHANGED
@@ -64,7 +64,7 @@ function scorePlan(plan, weights = DEFAULT_WEIGHTS) {
64
64
  function comparePlans(plans, weights = DEFAULT_WEIGHTS) {
65
65
  if (plans.length === 0) return {
66
66
  winner: null,
67
- margin: 0,
67
+ margin: null,
68
68
  plans,
69
69
  report: "No plans to compare."
70
70
  };
@@ -75,20 +75,21 @@ function comparePlans(plans, weights = DEFAULT_WEIGHTS) {
75
75
  const winner = scored[0];
76
76
  if (!winner) return {
77
77
  winner: null,
78
- margin: 0,
78
+ margin: null,
79
79
  plans,
80
80
  report: "No plans to compare."
81
81
  };
82
82
  const runnerUp = scored[1];
83
- const margin = runnerUp ? winner.score - runnerUp.score : winner.score;
83
+ const margin = runnerUp ? winner.score - runnerUp.score : null;
84
+ const singleNote = runnerUp ? "" : "\n(single plan recorded — margin is not a comparison)";
84
85
  return {
85
86
  winner: winner.plan.policyId,
86
87
  margin,
87
88
  plans,
88
- report: scored.map(({ plan, score }) => `${plan.policyId}: ${score.toFixed(1)} (${plan.acceptedOps} accepted, ${plan.rejectedOps} rejected${(plan.executionFailures ?? 0) > 0 ? `, ${plan.executionFailures} failed${plan.executionError !== void 0 ? `: ${plan.executionError}` : ""}` : ""})`).join("\n")
89
+ report: scored.map(({ plan, score }) => `${plan.policyId}: ${score.toFixed(1)} (${plan.acceptedOps} accepted, ${plan.rejectedOps} rejected${(plan.executionFailures ?? 0) > 0 ? `, ${plan.executionFailures} failed${plan.executionError !== void 0 ? `: ${plan.executionError}` : ""}` : ""})`).join("\n") + singleNote
89
90
  };
90
91
  }
91
- var EvolutionReplayDriver = class {
92
+ var EvolutionReplayDriver = class EvolutionReplayDriver {
92
93
  plans = [];
93
94
  maxPlans;
94
95
  /** V25-01 (v25): backfill-once latch — see {@link EvolutionReplayDriver.backfill}. */
@@ -96,8 +97,18 @@ var EvolutionReplayDriver = class {
96
97
  /** V26-05 (v25): plan ids recorded live BEFORE the backfill settled. A
97
98
  * plan-applied landing inside the one-shot `loadActivity` read window is
98
99
  * recorded live AND persisted into the sidecar the backfill is reading —
99
- * `backfill()` drops those ids so the event is not counted twice. */
100
+ * `backfill()` drops those ids so the event is not counted twice.
101
+ *
102
+ * V27 INS-05: the set is BOUNDED. `backfill()` is the only place that cleared
103
+ * it, so a sidecar load that never succeeded (the inject callback's catch
104
+ * warns and does not retry until the io dependency is replaced) let every
105
+ * live plan id accumulate for the process lifetime. Past
106
+ * PRE_BACKFILL_ID_CAP the oldest ids are dropped: the dedupe window is then
107
+ * finite, and a dropped duplicate can at worst repeat an entry inside the
108
+ * `maxPlans` window instead of growing memory without bound. */
100
109
  preBackfillIds = /* @__PURE__ */ new Set();
110
+ /** Upper bound on {@link EvolutionReplayDriver.preBackfillIds} (V27 INS-05). */
111
+ static PRE_BACKFILL_ID_CAP = 4096;
101
112
  weights;
102
113
  constructor(config = {}, warn = () => {}) {
103
114
  const maxPlans = clampedNumber(config.maxPlans ?? 50, 50, { min: 1 });
@@ -111,12 +122,29 @@ var EvolutionReplayDriver = class {
111
122
  }
112
123
  }
113
124
  record(plan) {
114
- if (!this.backfilled) this.preBackfillIds.add(plan.planId);
125
+ if (!this.backfilled) {
126
+ this.preBackfillIds.add(plan.planId);
127
+ while (this.preBackfillIds.size > EvolutionReplayDriver.PRE_BACKFILL_ID_CAP) {
128
+ const oldest = this.preBackfillIds.keys().next().value;
129
+ if (oldest === void 0) break;
130
+ this.preBackfillIds.delete(oldest);
131
+ }
132
+ }
133
+ this.plans.push(this.toPlan(plan));
134
+ if (this.plans.length > this.maxPlans) this.plans.shift();
135
+ }
136
+ /**
137
+ * v29 RPL-02: the activity/event record → scored-plan mapping, extracted
138
+ * from `record` so `backfill` can place sidecar plans at the CHRONOLOGICAL
139
+ * head of the window instead of appending them after this boot's live plans
140
+ * (the old order made the FIFO evict the freshest live plan first).
141
+ */
142
+ toPlan(plan) {
115
143
  const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
116
144
  const countOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
117
145
  const memoryApplied = count(plan.memoryApplied);
118
146
  const skillApplied = count(plan.skillApplied);
119
- this.plans.push({
147
+ return {
120
148
  policyId: typeof plan.policyFingerprint === "string" && plan.policyFingerprint.length > 0 ? plan.policyFingerprint : plan.planId,
121
149
  acceptedOps: memoryApplied + skillApplied,
122
150
  rejectedOps: count(plan.rejectedOps),
@@ -126,8 +154,7 @@ var EvolutionReplayDriver = class {
126
154
  estimatedInputChars: count(plan.estimatedInputChars),
127
155
  executionFailures: count(plan.executionFailures),
128
156
  ...typeof plan.executionError === "string" ? { executionError: plan.executionError } : {}
129
- });
130
- if (this.plans.length > this.maxPlans) this.plans.shift();
157
+ };
131
158
  }
132
159
  /**
133
160
  * V25-01 (v25): backfill the leaderboard from the activity sidecar, ONCE
@@ -143,7 +170,9 @@ var EvolutionReplayDriver = class {
143
170
  this.backfilled = true;
144
171
  const fresh = items.filter((item) => !this.preBackfillIds.has(item.planId));
145
172
  this.preBackfillIds.clear();
146
- for (const item of fresh) this.record(item);
173
+ const freshPlans = fresh.map((item) => this.toPlan(item));
174
+ this.plans.unshift(...freshPlans);
175
+ if (this.plans.length > this.maxPlans) this.plans.splice(0, this.plans.length - this.maxPlans);
147
176
  }
148
177
  /**
149
178
  * F-11: test-support API — no production consumer reads the raw
@@ -35,7 +35,10 @@ export interface ReplayPlan {
35
35
  }
36
36
  export interface ReplayResult {
37
37
  winner: string | null;
38
- margin: number;
38
+ /** v28 G4.4 (RPL-01): `null` when fewer than two plans were recorded — a
39
+ * single plan has no runner-up, and reporting its FULL score as a "margin"
40
+ * read as "won by N points" over nothing. Consumers must null-check. */
41
+ margin: number | null;
39
42
  plans: ReplayPlan[];
40
43
  report: string;
41
44
  }
@@ -73,11 +76,28 @@ export declare class EvolutionReplayDriver {
73
76
  /** V26-05 (v25): plan ids recorded live BEFORE the backfill settled. A
74
77
  * plan-applied landing inside the one-shot `loadActivity` read window is
75
78
  * recorded live AND persisted into the sidecar the backfill is reading —
76
- * `backfill()` drops those ids so the event is not counted twice. */
79
+ * `backfill()` drops those ids so the event is not counted twice.
80
+ *
81
+ * V27 INS-05: the set is BOUNDED. `backfill()` is the only place that cleared
82
+ * it, so a sidecar load that never succeeded (the inject callback's catch
83
+ * warns and does not retry until the io dependency is replaced) let every
84
+ * live plan id accumulate for the process lifetime. Past
85
+ * PRE_BACKFILL_ID_CAP the oldest ids are dropped: the dedupe window is then
86
+ * finite, and a dropped duplicate can at worst repeat an entry inside the
87
+ * `maxPlans` window instead of growing memory without bound. */
77
88
  private readonly preBackfillIds;
89
+ /** Upper bound on {@link EvolutionReplayDriver.preBackfillIds} (V27 INS-05). */
90
+ private static readonly PRE_BACKFILL_ID_CAP;
78
91
  private readonly weights;
79
92
  constructor(config?: Config, warn?: (message: string) => void);
80
93
  record(plan: EvolutionPlanAppliedEvent): void;
94
+ /**
95
+ * v29 RPL-02: the activity/event record → scored-plan mapping, extracted
96
+ * from `record` so `backfill` can place sidecar plans at the CHRONOLOGICAL
97
+ * head of the window instead of appending them after this boot's live plans
98
+ * (the old order made the FIFO evict the freshest live plan first).
99
+ */
100
+ private toPlan;
81
101
  /**
82
102
  * V25-01 (v25): backfill the leaderboard from the activity sidecar, ONCE
83
103
  * per driver instance. The inject callback that loads the sidecar re-runs
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-replay",
3
3
  "description": "Replay/A-B evaluation primitives for evolution plans (community build)",
4
- "version": "0.3.67",
4
+ "version": "0.3.69",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,8 +31,8 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-activity": "^0.3.67",
35
- "@lmzhen/dsh-evolution-core": "^0.3.67"
34
+ "@lmzhen/dsh-evolution-activity": "^0.3.69",
35
+ "@lmzhen/dsh-evolution-core": "^0.3.69"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.1",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
43
- "@lmzhen/dsh-evolution-activity": "^0.3.67",
44
- "@lmzhen/dsh-evolution-core": "^0.3.67"
43
+ "@lmzhen/dsh-evolution-activity": "^0.3.69",
44
+ "@lmzhen/dsh-evolution-core": "^0.3.69"
45
45
  }
46
46
  }