@lmzhen/dsh-evolution-curator 0.3.82 → 0.3.83

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @deepseek-ai/dsh-evolution-curator
1
+ # @lmzhen/dsh-evolution-curator
2
2
 
3
3
  Deterministic skill lifecycle and recovery
4
4
 
@@ -8,7 +8,7 @@ Deterministic skill lifecycle and recovery
8
8
 
9
9
  #### What the model sees
10
10
 
11
- `@deepseek-ai/dsh-evolution-curator` registers no direct prompt or tool schema itself. Model-visible effects are owned by the packages that consume this service.
11
+ `@lmzhen/dsh-evolution-curator` registers no direct prompt or tool schema itself. Model-visible effects are owned by the packages that consume this service.
12
12
 
13
13
  #### Token effect
14
14
 
package/lib/index.js CHANGED
@@ -355,6 +355,7 @@ var EvolutionCurator = class extends Service {
355
355
  async restoreSnapshot() {
356
356
  const release = await this.acquireMutex();
357
357
  try {
358
+ if (!this.holdsInstance) return this.instanceHeldRefusal("restoreSnapshot");
358
359
  return await this.restoreSnapshotCore();
359
360
  } finally {
360
361
  release();
@@ -468,6 +469,27 @@ var EvolutionCurator = class extends Service {
468
469
  };
469
470
  return prev.then(() => release);
470
471
  }
472
+ /**
473
+ * PLAN S4.5 (2026-09-16) (audit P2-16): the manual control-plane entries
474
+ * (restore/consolidate/restoreSnapshot) honor the same per-home instance
475
+ * claim `run()` checks before every pass. The claim is taken at MOUNT and
476
+ * released at DISPOSE (core/instance-scope.ts), so `holdsInstance` is
477
+ * stable for this instance's lifetime: a single-instance deployment always
478
+ * holds it and every manual entry behaves exactly as before. The refusal
479
+ * fires only on a YIELDING second row of one process, whose manual mutation
480
+ * would otherwise race the holder row's in-flight run over the same tree —
481
+ * this instance's control-plane mutex cannot see the holder's chain. The
482
+ * shape is the plain `SkillActionResult` failure the command face already
483
+ * renders through `err(result.message)` — no new error type — and the
484
+ * leading `instance-held` token names the same outcome run() reports via
485
+ * its `skipped` field.
486
+ */
487
+ instanceHeldRefusal(operation) {
488
+ return {
489
+ ok: false,
490
+ message: `instance-held: this curator row yielded the per-home claim to another curator row of this process — ${operation} is refused alongside run() so the holder row stays the only control-plane writer (run it on the holder row)`
491
+ };
492
+ }
471
493
  async runCore(options = {}) {
472
494
  const { ignoreGates = false, dryRun = false } = options;
473
495
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -498,13 +520,13 @@ var EvolutionCurator = class extends Service {
498
520
  };
499
521
  if (!ignoreGates && persisted === null && (stateService !== void 0 || !this.statelessFirstRunDeferred)) {
500
522
  if (stateService) try {
501
- await stateService.saveCuratorState({
523
+ await stateService.transactCuratorState((current) => current === null ? {
502
524
  schemaVersion: 1,
503
525
  lastRunAt: Date.now(),
504
526
  runCount: 0,
505
527
  lastSummary: "first-run-deferred",
506
528
  paused: false
507
- });
529
+ } : null);
508
530
  } catch (error) {
509
531
  this.ctx.logger.warn(`evolution-curator: failed to persist the first-run baseline: ${error instanceof Error ? error.message : String(error)}`);
510
532
  }
@@ -547,7 +569,7 @@ var EvolutionCurator = class extends Service {
547
569
  if (text) contents.set(name, text);
548
570
  }
549
571
  const protectedNames = await this.protectedNameMap();
550
- const dedupMembers = [...new Set(computeDedupGroups({ contents }).filter((group) => group.length >= 2).flat())].filter((name) => !protectedNames.has(name));
572
+ const dedupMembers = [...new Set(computeDedupGroups({ contents }).groups.filter((group) => group.length >= 2).flat())].filter((name) => !protectedNames.has(name));
551
573
  await this.scoreTree(usage, treeNames, contents);
552
574
  const result = computeLifecycleTransitions(usage, {
553
575
  staleAfterDays: lifecycle.staleAfterDays,
@@ -1111,6 +1133,7 @@ var EvolutionCurator = class extends Service {
1111
1133
  async consolidate(target, sources) {
1112
1134
  const release = await this.acquireMutex();
1113
1135
  try {
1136
+ if (!this.holdsInstance) return this.instanceHeldRefusal("consolidate");
1114
1137
  return await this.consolidateMutate(target, sources);
1115
1138
  } finally {
1116
1139
  release();
@@ -1155,6 +1178,7 @@ var EvolutionCurator = class extends Service {
1155
1178
  async restore(name) {
1156
1179
  const release = await this.acquireMutex();
1157
1180
  try {
1181
+ if (!this.holdsInstance) return this.instanceHeldRefusal("restore");
1158
1182
  return await this.restoreMutate(name);
1159
1183
  } finally {
1160
1184
  release();
@@ -243,6 +243,22 @@ export declare class EvolutionCurator extends Service {
243
243
  private mutexDepth;
244
244
  private mutexTail;
245
245
  private acquireMutex;
246
+ /**
247
+ * PLAN S4.5 (2026-09-16) (audit P2-16): the manual control-plane entries
248
+ * (restore/consolidate/restoreSnapshot) honor the same per-home instance
249
+ * claim `run()` checks before every pass. The claim is taken at MOUNT and
250
+ * released at DISPOSE (core/instance-scope.ts), so `holdsInstance` is
251
+ * stable for this instance's lifetime: a single-instance deployment always
252
+ * holds it and every manual entry behaves exactly as before. The refusal
253
+ * fires only on a YIELDING second row of one process, whose manual mutation
254
+ * would otherwise race the holder row's in-flight run over the same tree —
255
+ * this instance's control-plane mutex cannot see the holder's chain. The
256
+ * shape is the plain `SkillActionResult` failure the command face already
257
+ * renders through `err(result.message)` — no new error type — and the
258
+ * leading `instance-held` token names the same outcome run() reports via
259
+ * its `skipped` field.
260
+ */
261
+ private instanceHeldRefusal;
246
262
  private runCore;
247
263
  /**
248
264
  * Seed baseline records for tree skills the sidecar has not seen yet, so
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-curator",
3
3
  "description": "Deterministic skill lifecycle and recovery (community build)",
4
- "version": "0.3.82",
4
+ "version": "0.3.83",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,20 +27,20 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.1",
30
- "@lmzhen/dsh-evolution-core": "^0.3.82"
30
+ "@lmzhen/dsh-evolution-core": "^0.3.83"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@deepseek-ai/cordis": "^4.0.1",
34
34
  "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
35
35
  "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
36
- "@lmzhen/dsh-evolution-io": "^0.3.82",
37
- "@lmzhen/dsh-evolution-state": "^0.3.82"
36
+ "@lmzhen/dsh-evolution-io": "^0.3.83",
37
+ "@lmzhen/dsh-evolution-state": "^0.3.83"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
41
41
  "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
42
- "@lmzhen/dsh-evolution-core": "^0.3.82",
43
- "@lmzhen/dsh-evolution-io": "^0.3.82",
44
- "@lmzhen/dsh-evolution-state": "^0.3.82"
42
+ "@lmzhen/dsh-evolution-core": "^0.3.83",
43
+ "@lmzhen/dsh-evolution-io": "^0.3.83",
44
+ "@lmzhen/dsh-evolution-state": "^0.3.83"
45
45
  }
46
46
  }