@lmzhen/dsh-evolution-approval 0.19.3 → 0.21.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 +34 -11
- package/lib/types/index.d.ts +31 -6
- 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 { elapsedSince, isLoopbackRequest, isProcessAlive, parseLockBody, readJsonObject, textDiffFacts, writeJson } from "@lmzhen/dsh-evolution-core";
|
|
3
|
+
import { clampOnce, elapsedSince, isLoopbackRequest, isProcessAlive, parseLockBody, 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
|
|
@@ -242,13 +242,15 @@ var EvolutionApproval = class extends Service {
|
|
|
242
242
|
static Config = z.object({
|
|
243
243
|
enabled: z.boolean().default(false),
|
|
244
244
|
stageForeground: z.boolean().default(true),
|
|
245
|
-
pendingTtlMs: z.number().min(0).default(0)
|
|
245
|
+
pendingTtlMs: z.number().min(0).default(0).volatile()
|
|
246
246
|
});
|
|
247
247
|
enabled;
|
|
248
248
|
stageForegroundConfig;
|
|
249
|
-
/**
|
|
250
|
-
*
|
|
251
|
-
|
|
249
|
+
/** The TTL carrier as configured: a plain number, or the live reference `.volatile()` hands a
|
|
250
|
+
* user-layer field. Read through {@link ttl} at every use, never cached (registry row: live). */
|
|
251
|
+
pendingTtlMsConfig;
|
|
252
|
+
/** Keys this instance already warned about (clampOnce's once-per-key sink). */
|
|
253
|
+
clampedKeys = /* @__PURE__ */ new Set();
|
|
252
254
|
/** P3 (v16): public read for staging pre-checks (learning-graph refuses a
|
|
253
255
|
* stage that no runner could replay only when foreground writes stage at
|
|
254
256
|
* all). Mirrors `this.stageForeground`. */
|
|
@@ -262,8 +264,13 @@ var EvolutionApproval = class extends Service {
|
|
|
262
264
|
super(ctx, "evolutionApproval");
|
|
263
265
|
this.enabled = config.enabled ?? false;
|
|
264
266
|
this.stageForegroundConfig = config.stageForeground ?? true;
|
|
265
|
-
|
|
266
|
-
this.
|
|
267
|
+
this.pendingTtlMsConfig = config.pendingTtlMs;
|
|
268
|
+
this.ctx.inject(["settings"], (injected) => {
|
|
269
|
+
const settings = injected.settings;
|
|
270
|
+
if (typeof settings?.configure !== "function") return;
|
|
271
|
+
const disposer = settings.configure({ auto: false }, this.ctx.fiber);
|
|
272
|
+
if (typeof disposer === "function") this.ctx.effect(() => disposer, "evolution-approval: settings presentation");
|
|
273
|
+
});
|
|
267
274
|
this.ctx.inject(["webServer"], (scope) => {
|
|
268
275
|
scope.effect(() => {
|
|
269
276
|
const disposers = makeApprovalRoutes(this).map((route) => scope.webServer.register(route));
|
|
@@ -360,7 +367,22 @@ var EvolutionApproval = class extends Service {
|
|
|
360
367
|
/** T4-09/A51: the effective expiry of the staged window, 0 = never. Read by the command surface
|
|
361
368
|
* and the doctor so both can say which of the three human-confirmation states is in force. */
|
|
362
369
|
get ttlMs() {
|
|
363
|
-
return this.
|
|
370
|
+
return this.ttl();
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* The effective TTL in ms (0 = never expire), read at USE time.
|
|
374
|
+
*
|
|
375
|
+
* `clampOnce` is the family's ONE clamp-and-warn helper for a numeric knob that may be a live
|
|
376
|
+
* settings reference (S2.9/T3-10): a plain number, a `{ get() }` reference, or nothing all land
|
|
377
|
+
* on the same floor, and an invalid value warns once per instance instead of silently disabling
|
|
378
|
+
* the window. The old inline `Number.isFinite` read stopped working the moment the field became
|
|
379
|
+
* `.volatile()` — a reference is not a finite number, so the TTL silently read as 0.
|
|
380
|
+
* @returns the TTL the sweep may use.
|
|
381
|
+
*/
|
|
382
|
+
ttl() {
|
|
383
|
+
return Math.floor(clampOnce(this.clampedKeys, (message) => {
|
|
384
|
+
this.ctx.logger.warn(message);
|
|
385
|
+
}, "pendingTtlMs", this.pendingTtlMsConfig, 0, 0));
|
|
364
386
|
}
|
|
365
387
|
/**
|
|
366
388
|
* Close every staged write that has been waiting longer than the TTL (T4-09/A51).
|
|
@@ -379,15 +401,16 @@ var EvolutionApproval = class extends Service {
|
|
|
379
401
|
* @returns the ids that were closed by this sweep (empty when the TTL is off or nothing was stale).
|
|
380
402
|
*/
|
|
381
403
|
async expireStale() {
|
|
382
|
-
|
|
383
|
-
|
|
404
|
+
const ttl = this.ttl();
|
|
405
|
+
if (ttl <= 0) return [];
|
|
406
|
+
const cutoff = Date.now() - ttl;
|
|
384
407
|
const expired = [];
|
|
385
408
|
for (const record of await this.state().listPending("pending")) {
|
|
386
409
|
const stagedAt = Date.parse(record.createdAt);
|
|
387
410
|
if (!Number.isFinite(stagedAt) || stagedAt > cutoff) continue;
|
|
388
411
|
if ((await this.state().tryResolvePending(record.id, "rejected")).applied) expired.push(record.id);
|
|
389
412
|
}
|
|
390
|
-
if (expired.length > 0) this.ctx.logger.warn(`evolution-approval: ${expired.length} staged write(s) passed pendingTtlMs=${
|
|
413
|
+
if (expired.length > 0) this.ctx.logger.warn(`evolution-approval: ${expired.length} staged write(s) passed pendingTtlMs=${ttl} and were closed as rejected WITHOUT executing: ${expired.join(", ")} — stage them again if they are still wanted`);
|
|
391
414
|
return expired;
|
|
392
415
|
}
|
|
393
416
|
async list(status = "pending") {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* @module @lmzhen/dsh-evolution-approval
|
|
18
18
|
*/
|
|
19
19
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
20
|
-
import type
|
|
20
|
+
import type { Volatile } from '@deepseek-ai/cordis';
|
|
21
|
+
import z from '@deepseek-ai/schemastery';
|
|
21
22
|
import type { PendingKind, PendingRecord, PendingStatus } from '@lmzhen/dsh-evolution-state-storage';
|
|
22
23
|
export type { PendingKind, PendingRecord, PendingStatus };
|
|
23
24
|
export type WriteRunner = (args: unknown) => Promise<{
|
|
@@ -158,17 +159,30 @@ export interface Config {
|
|
|
158
159
|
* the window then behaves exactly as before. Expiry closes the record the same way a reject does
|
|
159
160
|
* (status `rejected`, `resolvedAt` stamped, runner NEVER invoked): an expiry is a cleanup of the
|
|
160
161
|
* window, never an approval, so nothing a human did not approve can execute.
|
|
162
|
+
*
|
|
163
|
+
* `.volatile()` (0.21.0, G1): the field carries a USER LAYER, so the value arrives as a live
|
|
164
|
+
* reference and is read at USE time — a settings write changes the window without a reload.
|
|
161
165
|
*/
|
|
162
|
-
pendingTtlMs?: number
|
|
166
|
+
pendingTtlMs?: Volatile<number>;
|
|
163
167
|
}
|
|
164
168
|
export declare class EvolutionApproval extends Service {
|
|
165
169
|
static inject: string[];
|
|
166
|
-
static Config:
|
|
170
|
+
static Config: z<Schemastery.ObjectS<NoInfer<{
|
|
171
|
+
enabled: z<boolean, boolean, "defined">;
|
|
172
|
+
stageForeground: z<boolean, boolean, "defined">;
|
|
173
|
+
pendingTtlMs: z<number, number, "volatile-defined">;
|
|
174
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
175
|
+
enabled: z<boolean, boolean, "defined">;
|
|
176
|
+
stageForeground: z<boolean, boolean, "defined">;
|
|
177
|
+
pendingTtlMs: z<number, number, "volatile-defined">;
|
|
178
|
+
}>>, "plain">;
|
|
167
179
|
private readonly enabled;
|
|
168
180
|
private readonly stageForegroundConfig;
|
|
169
|
-
/**
|
|
170
|
-
*
|
|
171
|
-
private readonly
|
|
181
|
+
/** The TTL carrier as configured: a plain number, or the live reference `.volatile()` hands a
|
|
182
|
+
* user-layer field. Read through {@link ttl} at every use, never cached (registry row: live). */
|
|
183
|
+
private readonly pendingTtlMsConfig?;
|
|
184
|
+
/** Keys this instance already warned about (clampOnce's once-per-key sink). */
|
|
185
|
+
private readonly clampedKeys;
|
|
172
186
|
/** P3 (v16): public read for staging pre-checks (learning-graph refuses a
|
|
173
187
|
* stage that no runner could replay only when foreground writes stage at
|
|
174
188
|
* all). Mirrors `this.stageForeground`. */
|
|
@@ -209,6 +223,17 @@ export declare class EvolutionApproval extends Service {
|
|
|
209
223
|
/** T4-09/A51: the effective expiry of the staged window, 0 = never. Read by the command surface
|
|
210
224
|
* and the doctor so both can say which of the three human-confirmation states is in force. */
|
|
211
225
|
get ttlMs(): number;
|
|
226
|
+
/**
|
|
227
|
+
* The effective TTL in ms (0 = never expire), read at USE time.
|
|
228
|
+
*
|
|
229
|
+
* `clampOnce` is the family's ONE clamp-and-warn helper for a numeric knob that may be a live
|
|
230
|
+
* settings reference (S2.9/T3-10): a plain number, a `{ get() }` reference, or nothing all land
|
|
231
|
+
* on the same floor, and an invalid value warns once per instance instead of silently disabling
|
|
232
|
+
* the window. The old inline `Number.isFinite` read stopped working the moment the field became
|
|
233
|
+
* `.volatile()` — a reference is not a finite number, so the TTL silently read as 0.
|
|
234
|
+
* @returns the TTL the sweep may use.
|
|
235
|
+
*/
|
|
236
|
+
private ttl;
|
|
212
237
|
/**
|
|
213
238
|
* Close every staged write that has been waiting longer than the TTL (T4-09/A51).
|
|
214
239
|
*
|
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.21.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.21.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.21.0",
|
|
36
|
+
"@lmzhen/dsh-evolution-state": "^0.21.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.21.0",
|
|
41
|
+
"@lmzhen/dsh-evolution-state-storage": "^0.21.0",
|
|
42
|
+
"@lmzhen/dsh-evolution-state": "^0.21.0"
|
|
43
43
|
}
|
|
44
44
|
}
|