@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 +78 -50
- package/lib/types/index.d.ts +16 -9
- package/package.json +7 -7
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,
|
|
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: "
|
|
35
|
+
writeJson(res, 403, { error: "禁止访问:仅限回环地址" });
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
38
|
if (req.method !== method) {
|
|
39
|
-
writeJson(res, 405, { error: "
|
|
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
|
|
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
|
|
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: "
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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: "
|
|
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: "
|
|
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:
|
|
388
|
+
message: `写入已暂存待批准。用 /evolution pending 查看,并批准 ${record.id}。`
|
|
360
389
|
};
|
|
361
390
|
}
|
|
362
391
|
return {
|
|
363
392
|
action: "allow",
|
|
364
|
-
message: "
|
|
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
|
|
431
|
-
*
|
|
432
|
-
*
|
|
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:
|
|
477
|
+
message: `待批准写入「${id}」已经在待批准窗口里:直接批准或拒绝它。`
|
|
449
478
|
};
|
|
450
479
|
return {
|
|
451
480
|
ok: false,
|
|
452
|
-
message:
|
|
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:
|
|
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:
|
|
491
|
+
message: `待批准写入「${id}」没有可释放的占用声明(记录形状异常):请改为拒绝它。`
|
|
463
492
|
};
|
|
464
|
-
const holderPid =
|
|
493
|
+
const holderPid = claimHolderPid(claimId);
|
|
465
494
|
if (holderPid !== null && holderPid > 0 && holderPid !== process.pid && isProcessAlive(holderPid)) return {
|
|
466
495
|
ok: false,
|
|
467
|
-
message:
|
|
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:
|
|
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:
|
|
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:
|
|
522
|
+
message: `已拒绝执行中的写入「${id}」(未持有占用声明:这可能与在途的批准运行器抢跑;如果写入已落地,其效果按现状保留。请手动核验写入状态)。`
|
|
494
523
|
} : {
|
|
495
524
|
ok: false,
|
|
496
|
-
message:
|
|
525
|
+
message: `待批准写入「${id}」无法拒绝:它并发地了结了。`
|
|
497
526
|
};
|
|
498
527
|
return {
|
|
499
528
|
ok: false,
|
|
500
|
-
message:
|
|
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:
|
|
537
|
+
message: `待批准写入「${id}」已不在待批准状态(已了结或不存在)。`
|
|
509
538
|
};
|
|
510
539
|
}
|
|
511
540
|
return {
|
|
512
541
|
ok: true,
|
|
513
|
-
message:
|
|
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 — `
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
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:
|
|
586
|
+
message: `待批准写入「${id}」正在执行:可能有另一个写入方正在了结它,或此前一次批准跑过之后崩了。如果不是你发起的这次批准,就不要拒绝它(在途的运行会自行完成);改为核验效果。确实卡住的记录(运行器没有任何动静)仍然可以拒绝 —— /evolution pending 会把它显示为 EXECUTING。`
|
|
559
587
|
};
|
|
560
588
|
return {
|
|
561
589
|
ok: false,
|
|
562
|
-
message:
|
|
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:
|
|
599
|
+
message: `能力「${id}」已获批准,但记录被并发地了结为「rejected」:没有代码运行,也无需重放;重新提交前请先核验。`
|
|
572
600
|
};
|
|
573
601
|
return {
|
|
574
602
|
ok: false,
|
|
575
|
-
message:
|
|
603
|
+
message: `待批准写入「${id}」已被另一个写入方了结。`
|
|
576
604
|
};
|
|
577
605
|
}
|
|
578
606
|
return {
|
|
579
607
|
ok: true,
|
|
580
|
-
message: "
|
|
608
|
+
message: "能力已批准,可在 Creator 模式下手动激活(未执行任何代码)。"
|
|
581
609
|
};
|
|
582
610
|
}
|
|
583
611
|
await this.state().releasePendingClaim(id, claimId);
|
|
584
612
|
return {
|
|
585
613
|
ok: false,
|
|
586
|
-
message:
|
|
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:
|
|
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: "
|
|
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:
|
|
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:
|
|
645
|
+
message: `已批准写入「${id}」已重放,但记录被并发地了结为「rejected」:效果已落地,而审计显示为 rejected。请核验该写入,且不要重放它。`
|
|
618
646
|
};
|
|
619
647
|
return {
|
|
620
648
|
ok: false,
|
|
621
|
-
message:
|
|
649
|
+
message: `待批准写入「${id}」已被另一个写入方了结。`
|
|
622
650
|
};
|
|
623
651
|
}
|
|
624
652
|
return {
|
|
625
653
|
ok: true,
|
|
626
|
-
message:
|
|
654
|
+
message: `已批准 ${record.kind} 写入「${id}」(${record.summary})。`
|
|
627
655
|
};
|
|
628
656
|
}
|
|
629
657
|
};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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
|
|
267
|
-
*
|
|
268
|
-
*
|
|
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 — `
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
36
|
-
"@lmzhen/dsh-evolution-state": "^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.
|
|
41
|
-
"@lmzhen/dsh-evolution-state-storage": "^0.
|
|
42
|
-
"@lmzhen/dsh-evolution-state": "^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
|
}
|