@lmzhen/dsh-evolution-learning-graph 0.26.15 → 0.26.26

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
+ import { SKILL_NAME_RE, authorityFrom, contentHash, errorText, evolutionIoAdapter, isPresent, isUnknown, newSkillLibrary, relatedSkillNames, resolveExecOrigins, runWriteGates, skillWriteGateContext } from "@lmzhen/dsh-evolution-core";
1
2
  import { effectiveSessionPolicy } from "@lmzhen/dsh-evolution-approval";
2
3
  import z from "@deepseek-ai/schemastery";
3
- import { SKILL_NAME_RE, contentHash, errorText, evolutionIoAdapter, newSkillLibrary, relatedSkillNames, resolveExecOrigins } from "@lmzhen/dsh-evolution-core";
4
4
  //#region lib/types/index.js
5
5
  /**
6
6
  * Learning graph over skills and memory, plus node-level commands
@@ -219,6 +219,41 @@ function capRenderedLines(lines) {
219
219
  if (lines.length <= 200) return lines.join("\n");
220
220
  return [...lines.slice(0, 200), `…${lines.length - 200} more`].join("\n");
221
221
  }
222
+ /**
223
+ * G1/1.2: the graph commands' write-admission call, as a NAMED function.
224
+ *
225
+ * The gates run at the execution point: these commands write at the bytes, exactly like the tool's
226
+ * execution path, so the read-before-write gate does not apply and a missing session read set is not a
227
+ * refusal. `delete` archives (reversible) and is allowed through by the protection gate by design;
228
+ * `update` is what a protected skill is refused for.
229
+ *
230
+ * Named rather than inlined because the wiring it represents - "this command passes the SAME sequence" -
231
+ * has to be provable by a case that fails when the call disappears.
232
+ * @param services - the policy source and the warn sink, both from the command's own context.
233
+ * @param request - the action, the target name, the new body for an update, and the library write origin.
234
+ * @returns the refusal message, or null when the write may proceed.
235
+ */
236
+ async function graphWriteGate(services, request) {
237
+ const isUpdate = request.action === "update";
238
+ const args = isUpdate ? {
239
+ action: "update",
240
+ name: request.name,
241
+ content: request.content ?? ""
242
+ } : {
243
+ action: "delete",
244
+ name: request.name
245
+ };
246
+ return runWriteGates(skillWriteGateContext({
247
+ policy: services.policy,
248
+ confirm: void 0,
249
+ readNames: isUpdate ? new Set([request.name]) : void 0,
250
+ warn: services.warn
251
+ }, {
252
+ point: isUpdate ? "admission" : "execution",
253
+ args,
254
+ origin: request.origin
255
+ }));
256
+ }
222
257
  function apply(ctx, rawConfig = {}) {
223
258
  ctx.inject(["commands"], (commandCtx) => {
224
259
  const commands = commandCtx.commands;
@@ -235,6 +270,12 @@ function apply(ctx, rawConfig = {}) {
235
270
  kind: "error",
236
271
  text
237
272
  });
273
+ let writeGateWarned = false;
274
+ const warnWriteGateOnce = (message) => {
275
+ if (writeGateWarned) return;
276
+ writeGateWarned = true;
277
+ ctx.logger.warn(message);
278
+ };
238
279
  const usageService = ctx.get("skillUsage");
239
280
  const memoryService = ctx.get("memory");
240
281
  const ioService = ctx.get("evolutionIo");
@@ -318,6 +359,7 @@ function apply(ctx, rawConfig = {}) {
318
359
  }
319
360
  function withSkills() {
320
361
  return newSkillLibrary({
362
+ authority: authorityFrom({ origin: "foreground" }),
321
363
  config: rawConfig,
322
364
  io: evolutionIoAdapter(() => io.provider()),
323
365
  ctx,
@@ -344,7 +386,8 @@ function apply(ctx, rawConfig = {}) {
344
386
  const stagesForeground = approval.stageForeground !== false;
345
387
  if (approval.isEnabled !== false && sessionPolicyEdit !== "never" && (origins.approval === "background_review" || stagesForeground) && !approval.hasRunner("skill")) return err("无法暂存 graph 技能写入:没有注册技能回放运行器 —— 请挂载 tool-skill-manage 行(evolution-agent 预设),或禁用 evolution-approval。");
346
388
  const sessionPolicy = sessionPolicyEdit;
347
- const stageCurrent = await withSkills().read(parsed.name).catch(() => void 0);
389
+ const stageProbe = await withSkills().readSkillProbe(parsed.name);
390
+ const stageCurrent = isUnknown(stageProbe) ? void 0 : isPresent(stageProbe) ? stageProbe.value : null;
348
391
  const decision = await approval.request({
349
392
  kind: "skill",
350
393
  summary: `graph edit ${parsed.name}`,
@@ -365,6 +408,16 @@ function apply(ctx, rawConfig = {}) {
365
408
  });
366
409
  if (decision.action === "staged") return ok(decision.message);
367
410
  }
411
+ const graphRefusal = await graphWriteGate({
412
+ policy: ctx.get("evolutionPolicy"),
413
+ warn: warnWriteGateOnce
414
+ }, {
415
+ action: "update",
416
+ name: parsed.name,
417
+ content,
418
+ origin: resolveExecOrigins(invocationAgent && { agent: invocationAgent }).library
419
+ });
420
+ if (graphRefusal !== null) return err(graphRefusal);
368
421
  const result = await withSkills().update(parsed.name, content, origins.library);
369
422
  if (result.ok && result.noop !== true) await usageService.record?.(parsed.name, "patch");
370
423
  return result.ok ? ok(result.message) : err(result.message);
@@ -428,6 +481,15 @@ function apply(ctx, rawConfig = {}) {
428
481
  });
429
482
  if (decision.action === "staged") return ok(decision.message);
430
483
  }
484
+ const graphRefusal = await graphWriteGate({
485
+ policy: ctx.get("evolutionPolicy"),
486
+ warn: warnWriteGateOnce
487
+ }, {
488
+ action: "delete",
489
+ name: parsed.name,
490
+ origin: resolveExecOrigins(invocationAgent && { agent: invocationAgent }).library
491
+ });
492
+ if (graphRefusal !== null) return err(graphRefusal);
431
493
  const result = await withSkills().archive(parsed.name);
432
494
  if (result.ok) await usageService?.markArchived?.(parsed.name);
433
495
  return result.ok ? ok(result.message) : err(result.message);
@@ -472,4 +534,4 @@ function apply(ctx, rawConfig = {}) {
472
534
  });
473
535
  }
474
536
  //#endregion
475
- export { Config, GRAPH_RENDER_LINE_CAP, apply, buildLearningGraph, capRenderedLines, graphDensity, memorySnapshotOf, name, parseGraphNodeId, readMemoryIndex, renderNodeLine, resolveGraphNode };
537
+ export { Config, GRAPH_RENDER_LINE_CAP, apply, buildLearningGraph, capRenderedLines, graphDensity, graphWriteGate, memorySnapshotOf, name, parseGraphNodeId, readMemoryIndex, renderNodeLine, resolveGraphNode };
@@ -10,6 +10,7 @@
10
10
  * @module @lmzhen/dsh-evolution-learning-graph
11
11
  */
12
12
  import type { Context } from '@deepseek-ai/cordis';
13
+ import { type WriteOrigin } from '@lmzhen/dsh-evolution-core';
13
14
  import z from '@deepseek-ai/schemastery';
14
15
  export interface GraphNode {
15
16
  id: string;
@@ -150,5 +151,28 @@ export declare const Config: z<Config>;
150
151
  export declare const GRAPH_RENDER_LINE_CAP = 200;
151
152
  /** Join lines under the render cap; overflow is summarized, not dropped silently. */
152
153
  export declare function capRenderedLines(lines: string[]): string;
154
+ /**
155
+ * G1/1.2: the graph commands' write-admission call, as a NAMED function.
156
+ *
157
+ * The gates run at the execution point: these commands write at the bytes, exactly like the tool's
158
+ * execution path, so the read-before-write gate does not apply and a missing session read set is not a
159
+ * refusal. `delete` archives (reversible) and is allowed through by the protection gate by design;
160
+ * `update` is what a protected skill is refused for.
161
+ *
162
+ * Named rather than inlined because the wiring it represents - "this command passes the SAME sequence" -
163
+ * has to be provable by a case that fails when the call disappears.
164
+ * @param services - the policy source and the warn sink, both from the command's own context.
165
+ * @param request - the action, the target name, the new body for an update, and the library write origin.
166
+ * @returns the refusal message, or null when the write may proceed.
167
+ */
168
+ export declare function graphWriteGate(services: {
169
+ readonly policy: unknown;
170
+ readonly warn: (message: string) => void;
171
+ }, request: {
172
+ readonly action: 'update' | 'delete';
173
+ readonly name: string;
174
+ readonly content?: string | undefined;
175
+ readonly origin: WriteOrigin;
176
+ }): Promise<string | null>;
153
177
  export declare function apply(ctx: Context, rawConfig?: Config): void;
154
178
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-learning-graph",
3
3
  "description": "Learning graph over skills and memory (community build)",
4
- "version": "0.26.15",
4
+ "version": "0.26.26",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,8 +27,8 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.4",
30
- "@lmzhen/dsh-evolution-approval": "^0.26.15",
31
- "@lmzhen/dsh-evolution-core": "^0.26.15"
30
+ "@lmzhen/dsh-evolution-approval": "^0.26.26",
31
+ "@lmzhen/dsh-evolution-core": "^0.26.26"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@deepseek-ai/dsh-commands": "^0.2.0-rc.1",