@lmzhen/dsh-evolution-approval 0.22.0 → 0.24.0

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
@@ -1,6 +1,6 @@
1
1
  import { Service } from "@deepseek-ai/cordis";
2
2
  import { randomUUID } from "node:crypto";
3
- import { clampOnce, elapsedSince, isLoopbackRequest, isProcessAlive, parseLockBody, readJsonObject, textDiffFacts, writeJson } from "@lmzhen/dsh-evolution-core";
3
+ import { clampOnce, elapsedSince, isLoopbackRequest, readJsonObject, textDiffFacts, writeJson } from "@lmzhen/dsh-evolution-core";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
  import { PENDING_STATUSES } from "@lmzhen/dsh-evolution-state-storage";
6
6
  //#region lib/types/routes.js
@@ -32,11 +32,11 @@ function makeApprovalRoutes(approval) {
32
32
  /** Guard: trust fence, then method. Every refusal is a JSON body, never an empty response. */
33
33
  const guard = (req, res, method) => {
34
34
  if (!isLoopbackRequest(req)) {
35
- writeJson(res, 403, { error: "forbidden: loopback-only" });
35
+ writeJson(res, 403, { error: "禁止访问:仅限回环地址" });
36
36
  return false;
37
37
  }
38
38
  if (req.method !== method) {
39
- writeJson(res, 405, { error: "method not allowed: " + method });
39
+ writeJson(res, 405, { error: "方法不允许:" + method });
40
40
  return false;
41
41
  }
42
42
  return true;
@@ -82,7 +82,7 @@ function makeApprovalRoutes(approval) {
82
82
  writeJson(res, 400, {
83
83
  ok: false,
84
84
  code: "bad-request",
85
- message: "status must be one of " + PENDING_STATUSES.join(", ")
85
+ message: "status 必须是以下之一:" + PENDING_STATUSES.join(", ")
86
86
  });
87
87
  return;
88
88
  }
@@ -118,7 +118,7 @@ function makeApprovalRoutes(approval) {
118
118
  writeJson(res, 400, {
119
119
  ok: false,
120
120
  code: "bad-request",
121
- message: "id is required"
121
+ message: "缺少 id"
122
122
  });
123
123
  return;
124
124
  }
@@ -128,7 +128,7 @@ function makeApprovalRoutes(approval) {
128
128
  writeJson(res, 200, {
129
129
  ok: false,
130
130
  code: "not-found",
131
- message: "no staged write with id \"" + id + "\""
131
+ message: "没有 id 为「" + id + "」的待批准写入"
132
132
  });
133
133
  return;
134
134
  }
@@ -138,7 +138,7 @@ function makeApprovalRoutes(approval) {
138
138
  ok: true,
139
139
  data: {
140
140
  available: false,
141
- reason: "a \"" + record.kind + "\" write has no preview"
141
+ reason: "类型 \"" + record.kind + "\" 的写入没有可预览内容"
142
142
  }
143
143
  });
144
144
  return;
@@ -182,7 +182,7 @@ function makeApprovalRoutes(approval) {
182
182
  writeJson(res, 400, {
183
183
  ok: false,
184
184
  code: "bad-request",
185
- message: "id is required"
185
+ message: "缺少 id"
186
186
  });
187
187
  return;
188
188
  }
@@ -199,7 +199,7 @@ function makeApprovalRoutes(approval) {
199
199
  writeJson(res, 400, {
200
200
  ok: false,
201
201
  code: "bad-request",
202
- message: "id is required"
202
+ message: "缺少 id"
203
203
  });
204
204
  return;
205
205
  }
@@ -228,6 +228,30 @@ function makeApprovalRoutes(approval) {
228
228
  *
229
229
  * @module @lmzhen/dsh-evolution-approval
230
230
  */
231
+ /** A claim id carries its holder pid as `<pid>:<hex token>` (minted by `newClaimId()`). */
232
+ const CLAIM_ID_RE = /^(\d+):[0-9a-f]*$/;
233
+ /**
234
+ * @param claimId - an id from a record's `claimedBy` field.
235
+ * @returns the holder pid, or null when the id does not have the claim shape (pre-v43 bare UUID).
236
+ */
237
+ function claimHolderPid(claimId) {
238
+ const match = CLAIM_ID_RE.exec(claimId.trim());
239
+ return match === null ? null : Number(match[1]);
240
+ }
241
+ /**
242
+ * Whether a pid can still be running. `kill(pid, 0)` is the POSIX/Windows probe: it sends no
243
+ * signal and answers through the error — EPERM means the process EXISTS but is not ours.
244
+ * @param pid - the holder pid from a claim id (callers guard `> 0`; there is no pid 0).
245
+ * @returns true when the process exists.
246
+ */
247
+ function isProcessAlive(pid) {
248
+ try {
249
+ process.kill(pid, 0);
250
+ return true;
251
+ } catch (error) {
252
+ return error.code === "EPERM";
253
+ }
254
+ }
231
255
  /** The requesting session's effective policy (override ?? configured default);
232
256
  * undefined when the approval service is not mounted or no session is
233
257
  * available — callers keep their previous behavior. Single source (G4.8,
@@ -242,7 +266,12 @@ var EvolutionApproval = class extends Service {
242
266
  static Config = z.object({
243
267
  enabled: z.boolean().default(false),
244
268
  stageForeground: z.boolean().default(true),
245
- pendingTtlMs: z.number().min(0).default(0).volatile()
269
+ pendingTtlMs: z.number().min(0).default(0).volatile(),
270
+ approvalConfirm: z.union([
271
+ z.const("auto"),
272
+ z.const("ask"),
273
+ z.const("timeout")
274
+ ]).default("auto").volatile()
246
275
  });
247
276
  enabled;
248
277
  stageForegroundConfig;
@@ -320,12 +349,12 @@ var EvolutionApproval = class extends Service {
320
349
  async run(kind, args, intent) {
321
350
  if (intent?.interface !== "background_review") return {
322
351
  ok: false,
323
- message: "approval.run is the background-review replay channel; direct execution must use the tool path (staging is the only gate bypass for foreground)."
352
+ message: "approval.run 是后台复核的回放通道;直接执行必须走工具路径(对前台而言,暂存是唯一的门禁旁路)。"
324
353
  };
325
354
  const runner = this.runners.get(kind);
326
355
  if (!runner) return {
327
356
  ok: false,
328
- message: `No replay runner registered for kind "${kind}".`
357
+ message: `没有为类型「${kind}」注册回放运行器。`
329
358
  };
330
359
  return await runner(args);
331
360
  }
@@ -334,11 +363,11 @@ var EvolutionApproval = class extends Service {
334
363
  const summary = normalizeSummary(input);
335
364
  if (!this.enabled) return {
336
365
  action: "allow",
337
- message: "Approval disabled."
366
+ message: "批准已禁用。"
338
367
  };
339
368
  if ((this.deriveSessionPolicy(input.sessionId, input.session) ?? (this.ctx.get("approval") ? void 0 : input.sessionPolicy)) === "never") return {
340
369
  action: "allow",
341
- message: "Session approval policy is \"never\"; write allowed without staging."
370
+ message: "会话批准策略是「never」:写入无需暂存,直接放行。"
342
371
  };
343
372
  if (input.origin === "background_review" || this.stageForeground) {
344
373
  if (input.kind !== "capability" && !this.runners.has(input.kind)) this.ctx.logger.warn(`evolution-approval: staging "${input.kind}" write with no replay runner registered - it will not be approvable`);
@@ -356,12 +385,12 @@ var EvolutionApproval = class extends Service {
356
385
  return {
357
386
  action: "staged",
358
387
  pendingId: record.id,
359
- message: `Write staged for approval. Review with /evolution pending and approve ${record.id}.`
388
+ message: `写入已暂存待批准。用 /evolution pending 查看,并批准 ${record.id}。`
360
389
  };
361
390
  }
362
391
  return {
363
392
  action: "allow",
364
- message: "Foreground write allowed."
393
+ message: "前台写入已放行。"
365
394
  };
366
395
  }
367
396
  /** T4-09/A51: the effective expiry of the staged window, 0 = never. Read by the command surface
@@ -427,9 +456,9 @@ var EvolutionApproval = class extends Service {
427
456
  * core/instance-scope.ts, i.e. PER PROCESS, so a second process over one home
428
457
  * is not excluded at all. Two checks stand in its place:
429
458
  * 1. `inFlight` — an approve/reject running in THIS service instance;
430
- * 2. the claim's holder pid. Claim ids are minted as `<pid>:<hex token>`, the
431
- * same body shape the io write lock uses, so `parseLockBody` + the core
432
- * `isProcessAlive` probe answer whether that holder can still be running: a
459
+ * 2. the claim's holder pid. Claim ids are minted as `<pid>:<hex token>` (this
460
+ * service's own grammar, see `claimHolderPid`), so that id plus a liveness
461
+ * probe answer whether the holder can still be running: a
433
462
  * LIVE FOREIGN pid REFUSES the release; our own pid is this process's
434
463
  * leftover (a live in-process approve is what `inFlight` covers); a dead pid
435
464
  * is a crash. The last two are releasable, which is the S2-P2-22 case.
@@ -445,26 +474,26 @@ var EvolutionApproval = class extends Service {
445
474
  if (!executing) {
446
475
  if ((await this.state().listPending("pending")).find((item) => item.id === id)) return {
447
476
  ok: false,
448
- message: `Pending write "${id}" is already in the pending window — approve or reject it.`
477
+ message: `待批准写入「${id}」已经在待批准窗口里:直接批准或拒绝它。`
449
478
  };
450
479
  return {
451
480
  ok: false,
452
- message: `Pending write "${id}" is not in the executing window — nothing to release.`
481
+ message: `待批准写入「${id}」不在执行窗口中:没有可释放的。`
453
482
  };
454
483
  }
455
484
  if (this.inFlight.has(`approve:${id}`) || this.inFlight.has(`reject:${id}`)) return {
456
485
  ok: false,
457
- message: `Pending write "${id}" has an approve/reject RUNNING in this process — do not release it. Verify the effect instead; a completed approve resolves its own record.`
486
+ message: `待批准写入「${id}」在本进程里有正在运行的批准或拒绝:不要释放它。改为核验效果;已完成的批准会自行了结该记录。`
458
487
  };
459
488
  const claimId = typeof executing.claimedBy === "string" ? executing.claimedBy : "";
460
489
  if (claimId === "") return {
461
490
  ok: false,
462
- message: `Pending write "${id}" carries no claim to release (unexpected record shape) — reject it instead.`
491
+ message: `待批准写入「${id}」没有可释放的占用声明(记录形状异常):请改为拒绝它。`
463
492
  };
464
- const holderPid = parseLockBody(claimId);
493
+ const holderPid = claimHolderPid(claimId);
465
494
  if (holderPid !== null && holderPid > 0 && holderPid !== process.pid && isProcessAlive(holderPid)) return {
466
495
  ok: false,
467
- message: `Pending write "${id}" is claimed by pid ${holderPid}, which is ALIVE — another process may be running this approve. Do not release it: verify the write effect instead and let that run finish (a completed approve resolves its own record).`
496
+ message: `待批准写入「${id}」被 pid ${holderPid} 占用且该进程仍存活:可能有另一个进程正在执行这次批准。不要释放它:改为核验写入效果,并让那次运行跑完(已完成的批准会自行了结该记录)。`
468
497
  };
469
498
  let released = false;
470
499
  let failure = "";
@@ -477,11 +506,11 @@ var EvolutionApproval = class extends Service {
477
506
  }
478
507
  if (!released) return {
479
508
  ok: false,
480
- message: `Release of "${id}" did not take effect (${failure === "" ? "the claim no longer matches the record — a concurrent writer moved it" : failure}) — it stays EXECUTING with its claim unless it resolved, and the write effect is still unverified. Retry, or reject it to close the record without replaying.`
509
+ message: `释放「${id}」没有生效(${failure === "" ? "the claim no longer matches the record — a concurrent writer moved it" : failure}):除非它已了结,否则仍处于 EXECUTING 且保留占用声明,写入效果也仍未核验。请重试,或拒绝它以在不重放的情况下关闭记录。`
481
510
  };
482
511
  return {
483
512
  ok: true,
484
- message: `Released "${id}" back to the pending window.${holderPid === null ? " Its claim carries no pid, so liveness could NOT be verified: another process may still be running this approve." : ""} VERIFY the effect before approving: approve re-runs the write, and a non-idempotent replay duplicates an effect that already landed; reject closes the record instead.`
513
+ message: `已把「${id}」放回待批准窗口。${holderPid === null ? " Its claim carries no pid, so liveness could NOT be verified: another process may still be running this approve." : ""} 批准前请先核验效果:批准会重跑该写入,非幂等的重放会造成效果重复;改为拒绝则可关闭该记录。`
485
514
  };
486
515
  }
487
516
  async reject(id) {
@@ -490,14 +519,14 @@ var EvolutionApproval = class extends Service {
490
519
  if (!await this.state().claimPending(id, claimId)) {
491
520
  if ((await this.state().listPending("executing")).find((item) => item.id === id)) return (await this.state().tryResolvePending(id, "rejected")).applied ? {
492
521
  ok: true,
493
- message: `Rejected executing write "${id}" (no claim held — this may race an in-flight approve runner; if the write already landed its effect stands as-is. Verify the write state manually).`
522
+ message: `已拒绝执行中的写入「${id}」(未持有占用声明:这可能与在途的批准运行器抢跑;如果写入已落地,其效果按现状保留。请手动核验写入状态)。`
494
523
  } : {
495
524
  ok: false,
496
- message: `Pending write "${id}" could not be rejected: it resolved concurrently.`
525
+ message: `待批准写入「${id}」无法拒绝:它并发地了结了。`
497
526
  };
498
527
  return {
499
528
  ok: false,
500
- message: `Pending write "${id}" is not in the pending window (rotated or resolved).`
529
+ message: `待批准写入「${id}」不在待批准窗口里(已轮转或已了结)。`
501
530
  };
502
531
  }
503
532
  const resolution = await this.state().tryResolvePending(id, "rejected", claimId);
@@ -505,12 +534,12 @@ var EvolutionApproval = class extends Service {
505
534
  await this.state().releasePendingClaim(id, claimId);
506
535
  return {
507
536
  ok: false,
508
- message: `Pending write "${id}" is not pending (already resolved or missing).`
537
+ message: `待批准写入「${id}」已不在待批准状态(已了结或不存在)。`
509
538
  };
510
539
  }
511
540
  return {
512
541
  ok: true,
513
- message: `Rejected ${resolution.record.kind} write "${id}".`
542
+ message: `已拒绝 ${resolution.record.kind} 写入「${id}」。`
514
543
  };
515
544
  });
516
545
  }
@@ -531,12 +560,11 @@ var EvolutionApproval = class extends Service {
531
560
  if (override === "never" || override === "ask") return override;
532
561
  return platformApproval.config?.policy ?? "ask";
533
562
  }
534
- /** v43 FLOW2-1: a claim id that CARRIES its holder pid — `parseLockBody`
535
- * reads it back and `isProcessAlive` turns it into `release()`'s verifiable
536
- * orphan criterion. The `<pid>:<hex token>` body is the io write lock's own
537
- * shape, so one set of core helpers reads both; the token stays unique per
538
- * call, so two approves of one record can never share a claim (the token is a
539
- * dashed UUID stripped to hex because `LOCK_BODY_RE` accepts only hex). */
563
+ /** v43 FLOW2-1: a claim id that CARRIES its holder pid — `claimHolderPid` reads
564
+ * it back and the liveness probe turns it into `release()`'s verifiable orphan
565
+ * criterion. The token stays unique per call, so two approves of one record can
566
+ * never share a claim (a dashed UUID stripped to hex, which is what the
567
+ * claim-id grammar accepts). */
540
568
  newClaimId() {
541
569
  return `${process.pid}:${randomUUID().split("-").join("")}`;
542
570
  }
@@ -555,11 +583,11 @@ var EvolutionApproval = class extends Service {
555
583
  if (!record) {
556
584
  if ((await this.state().listPending("executing")).find((item) => item.id === id)) return {
557
585
  ok: false,
558
- message: `Pending write "${id}" is executing — either another writer is resolving it right now, or a previous approve ran then crashed. If you did not start this approve, do not reject it (the in-flight run completes on its own); verify the effect instead. A genuinely stuck record (no runner activity) can still be rejected — /evolution pending shows it as EXECUTING.`
586
+ message: `待批准写入「${id}」正在执行:可能有另一个写入方正在了结它,或此前一次批准跑过之后崩了。如果不是你发起的这次批准,就不要拒绝它(在途的运行会自行完成);改为核验效果。确实卡住的记录(运行器没有任何动静)仍然可以拒绝 —— /evolution pending 会把它显示为 EXECUTING。`
559
587
  };
560
588
  return {
561
589
  ok: false,
562
- message: `Pending write "${id}" is not in the pending window (rotated or resolved).`
590
+ message: `待批准写入「${id}」不在待批准窗口里(已轮转或已了结)。`
563
591
  };
564
592
  }
565
593
  const runner = this.runners.get(record.kind);
@@ -568,22 +596,22 @@ var EvolutionApproval = class extends Service {
568
596
  if (!(await this.state().tryResolvePending(id, "approved", claimId)).applied) {
569
597
  if ((await this.state().listPending("rejected")).find((item) => item.id === id)) return {
570
598
  ok: false,
571
- message: `Capability "${id}" was approved, but the record was resolved to "rejected" concurrently — no code ran and nothing needs replaying; verify before re-submitting.`
599
+ message: `能力「${id}」已获批准,但记录被并发地了结为「rejected」:没有代码运行,也无需重放;重新提交前请先核验。`
572
600
  };
573
601
  return {
574
602
  ok: false,
575
- message: `Pending write "${id}" was already resolved by another writer.`
603
+ message: `待批准写入「${id}」已被另一个写入方了结。`
576
604
  };
577
605
  }
578
606
  return {
579
607
  ok: true,
580
- message: "Capability approved for manual activation in Creator mode (no code was executed)."
608
+ message: "能力已批准,可在 Creator 模式下手动激活(未执行任何代码)。"
581
609
  };
582
610
  }
583
611
  await this.state().releasePendingClaim(id, claimId);
584
612
  return {
585
613
  ok: false,
586
- message: `No replay runner registered for kind "${record.kind}".`
614
+ message: `没有为类型「${record.kind}」注册回放运行器。`
587
615
  };
588
616
  }
589
617
  try {
@@ -592,7 +620,7 @@ var EvolutionApproval = class extends Service {
592
620
  await this.state().releasePendingClaim(id, claimId);
593
621
  return {
594
622
  ok: false,
595
- message: /changed since this plan|no entry matching|not found|no longer exists|path traversal/i.test(result.message) ? `${result.message} — a sibling write from the same plan may have already changed this target, so repeated approves will keep failing. Reject this record unless the target changed again.` : result.message
623
+ message: (/* @__PURE__ */ new RegExp("changed since this plan|no entry matching|not found|no longer exists|path traversal|在图渲染之后被改动过|匹配到多条不同条目|已不存在|不存在|路径穿越|不在技能|找不到|被改动过|已改动|不匹配|已过期|changed since it was read", "i")).test(result.message) ? `${result.message} 同一计划里的兄弟写入可能已改动该目标,反复批准会一直失败;除非目标再次变更,请驳回该记录。` : result.message
596
624
  };
597
625
  }
598
626
  } catch (error) {
@@ -600,7 +628,7 @@ var EvolutionApproval = class extends Service {
600
628
  this.ctx.logger.warn(error);
601
629
  return {
602
630
  ok: false,
603
- message: "Replay runner failed; the pending write remains pending."
631
+ message: "回放运行器失败:该待批准写入仍处于待批准状态。"
604
632
  };
605
633
  }
606
634
  const resolution = await this.state().tryResolvePending(id, "approved", claimId).catch((error) => {
@@ -609,21 +637,21 @@ var EvolutionApproval = class extends Service {
609
637
  });
610
638
  if (typeof resolution === "string") return {
611
639
  ok: false,
612
- message: `Approved write "${id}" was replayed, but resolving the record failed (${resolution.slice(8)}) — the effect has LANDED while the audit row stays "executing". Verify the write manually; approve will not re-run it.`
640
+ message: `已批准写入「${id}」已重放,但了结记录失败(${resolution.slice(8)}):效果已经落地,而审计行仍停在「executing」。请手动核验该写入;批准不会再次重跑它。`
613
641
  };
614
642
  if (!resolution.applied) {
615
643
  if ((await this.state().listPending("rejected")).find((item) => item.id === id)) return {
616
644
  ok: false,
617
- message: `Approved write "${id}" was replayed, but the record was resolved to "rejected" concurrently — the effect has landed while the audit reads rejected. Verify the write and do NOT replay it.`
645
+ message: `已批准写入「${id}」已重放,但记录被并发地了结为「rejected」:效果已落地,而审计显示为 rejected。请核验该写入,且不要重放它。`
618
646
  };
619
647
  return {
620
648
  ok: false,
621
- message: `Pending write "${id}" was already resolved by another writer.`
649
+ message: `待批准写入「${id}」已被另一个写入方了结。`
622
650
  };
623
651
  }
624
652
  return {
625
653
  ok: true,
626
- message: `Approved ${record.kind} write "${id}" (${record.summary}).`
654
+ message: `已批准 ${record.kind} 写入「${id}」(${record.summary})。`
627
655
  };
628
656
  }
629
657
  };
@@ -164,6 +164,12 @@ export interface Config {
164
164
  * reference and is read at USE time — a settings write changes the window without a reload.
165
165
  */
166
166
  pendingTtlMs?: Volatile<number>;
167
+ /**
168
+ * U-6: what the confirmation before an APPROVE does. The same three words `skillWriteConfirm` uses
169
+ * for a skill write (`tool-skill-manage/src/write-gates.ts:50`) — mirrored, not imported: the two
170
+ * packages do not depend on each other and this is a vocabulary, not behaviour.
171
+ */
172
+ approvalConfirm?: Volatile<'auto' | 'ask' | 'timeout'>;
167
173
  }
168
174
  export declare class EvolutionApproval extends Service {
169
175
  static inject: string[];
@@ -171,10 +177,12 @@ export declare class EvolutionApproval extends Service {
171
177
  enabled: z<boolean, boolean, "defined">;
172
178
  stageForeground: z<boolean, boolean, "defined">;
173
179
  pendingTtlMs: z<number, number, "volatile-defined">;
180
+ approvalConfirm: z<"ask" | "auto" | "timeout", "ask" | "auto" | "timeout", "volatile-defined">;
174
181
  }>>, Schemastery.ObjectT<NoInfer<{
175
182
  enabled: z<boolean, boolean, "defined">;
176
183
  stageForeground: z<boolean, boolean, "defined">;
177
184
  pendingTtlMs: z<number, number, "volatile-defined">;
185
+ approvalConfirm: z<"ask" | "auto" | "timeout", "ask" | "auto" | "timeout", "volatile-defined">;
178
186
  }>>, "plain">;
179
187
  private readonly enabled;
180
188
  private readonly stageForegroundConfig;
@@ -263,9 +271,9 @@ export declare class EvolutionApproval extends Service {
263
271
  * core/instance-scope.ts, i.e. PER PROCESS, so a second process over one home
264
272
  * is not excluded at all. Two checks stand in its place:
265
273
  * 1. `inFlight` — an approve/reject running in THIS service instance;
266
- * 2. the claim's holder pid. Claim ids are minted as `<pid>:<hex token>`, the
267
- * same body shape the io write lock uses, so `parseLockBody` + the core
268
- * `isProcessAlive` probe answer whether that holder can still be running: a
274
+ * 2. the claim's holder pid. Claim ids are minted as `<pid>:<hex token>` (this
275
+ * service's own grammar, see `claimHolderPid`), so that id plus a liveness
276
+ * probe answer whether the holder can still be running: a
269
277
  * LIVE FOREIGN pid REFUSES the release; our own pid is this process's
270
278
  * leftover (a live in-process approve is what `inFlight` covers); a dead pid
271
279
  * is a crash. The last two are releasable, which is the S2-P2-22 case.
@@ -294,12 +302,11 @@ export declare class EvolutionApproval extends Service {
294
302
  * check is lazy: the platform service can start before or after this plugin.
295
303
  */
296
304
  private deriveSessionPolicy;
297
- /** v43 FLOW2-1: a claim id that CARRIES its holder pid — `parseLockBody`
298
- * reads it back and `isProcessAlive` turns it into `release()`'s verifiable
299
- * orphan criterion. The `<pid>:<hex token>` body is the io write lock's own
300
- * shape, so one set of core helpers reads both; the token stays unique per
301
- * call, so two approves of one record can never share a claim (the token is a
302
- * dashed UUID stripped to hex because `LOCK_BODY_RE` accepts only hex). */
305
+ /** v43 FLOW2-1: a claim id that CARRIES its holder pid — `claimHolderPid` reads
306
+ * it back and the liveness probe turns it into `release()`'s verifiable orphan
307
+ * criterion. The token stays unique per call, so two approves of one record can
308
+ * never share a claim (a dashed UUID stripped to hex, which is what the
309
+ * claim-id grammar accepts). */
303
310
  private newClaimId;
304
311
  private dedupe;
305
312
  private doApprove;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-approval",
3
3
  "description": "Stage/pending approval service for Hermes-style self-evolution writes (community build)",
4
- "version": "0.22.0",
4
+ "version": "0.24.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,18 +27,18 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.4",
30
- "@lmzhen/dsh-evolution-core": "^0.22.0"
30
+ "@lmzhen/dsh-evolution-core": "^0.24.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@deepseek-ai/cordis": "^4.0.1",
34
34
  "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
35
- "@lmzhen/dsh-evolution-state-storage": "^0.22.0",
36
- "@lmzhen/dsh-evolution-state": "^0.22.0"
35
+ "@lmzhen/dsh-evolution-state-storage": "^0.24.0",
36
+ "@lmzhen/dsh-evolution-state": "^0.24.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
40
- "@lmzhen/dsh-evolution-core": "^0.22.0",
41
- "@lmzhen/dsh-evolution-state-storage": "^0.22.0",
42
- "@lmzhen/dsh-evolution-state": "^0.22.0"
40
+ "@lmzhen/dsh-evolution-core": "^0.24.0",
41
+ "@lmzhen/dsh-evolution-state-storage": "^0.24.0",
42
+ "@lmzhen/dsh-evolution-state": "^0.24.0"
43
43
  }
44
44
  }