@lmzhen/dsh-evolution-core 0.26.26 → 0.27.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.
Files changed (107) hide show
  1. package/README.md +16 -1
  2. package/lib/composition/index.js +3 -0
  3. package/lib/composition-D8Y3-ROd.js +604 -0
  4. package/lib/ensure-MEYMj9jn.js +222 -0
  5. package/lib/errors-Br2-uUSK.js +57 -0
  6. package/lib/http-routes-BP-S3KV-.js +149 -0
  7. package/lib/http-routes.js +2 -0
  8. package/lib/index.js +66 -850
  9. package/lib/restrictor.js +37 -0
  10. package/lib/subagent/index.js +186 -0
  11. package/lib/types/citations.js +192 -0
  12. package/lib/types/{preset-composition.d.ts → composition/composer.d.ts} +1 -1
  13. package/lib/types/composition/composer.js +681 -0
  14. package/lib/types/composition/ensure.d.ts +10 -0
  15. package/lib/types/composition/ensure.js +95 -0
  16. package/lib/types/composition/index.d.ts +5 -0
  17. package/lib/types/composition/index.js +7 -0
  18. package/lib/types/composition/preset-ids.d.ts +17 -0
  19. package/lib/types/composition/preset-ids.js +21 -0
  20. package/lib/types/composition/restricted-preset.d.ts +36 -0
  21. package/lib/types/composition/restricted-preset.js +42 -0
  22. package/lib/types/constants.js +223 -0
  23. package/lib/types/cost.js +67 -0
  24. package/lib/types/curator.js +346 -0
  25. package/lib/types/diagnostics.d.ts +26 -0
  26. package/lib/types/diagnostics.js +23 -0
  27. package/lib/types/document-body.js +91 -0
  28. package/lib/types/drift-signals.js +319 -0
  29. package/lib/types/env.js +25 -0
  30. package/lib/types/errors.d.ts +4 -1
  31. package/lib/types/errors.js +59 -0
  32. package/lib/types/events.js +16 -0
  33. package/lib/types/evidence.js +65 -0
  34. package/lib/types/evolution-events.js +479 -0
  35. package/lib/types/fold-untrusted.js +44 -0
  36. package/lib/types/frontmatter.js +623 -0
  37. package/lib/types/fuzzy-match.js +135 -0
  38. package/lib/types/gates.js +51 -0
  39. package/lib/types/guards.js +27 -0
  40. package/lib/types/health-baseline.js +63 -0
  41. package/lib/types/http-routes.d.ts +67 -35
  42. package/lib/types/http-routes.js +135 -0
  43. package/lib/types/index.d.ts +7 -2
  44. package/lib/types/index.js +101 -0
  45. package/lib/types/instance-scope.js +69 -0
  46. package/lib/types/io.js +675 -0
  47. package/lib/types/learn-prompt.js +42 -0
  48. package/lib/types/limits.js +63 -0
  49. package/lib/types/live.js +107 -0
  50. package/lib/types/long-runs.js +96 -0
  51. package/lib/types/memory-store.d.ts +0 -16
  52. package/lib/types/memory-store.js +606 -0
  53. package/lib/types/mutations.js +95 -0
  54. package/lib/types/namespace-migration.js +156 -0
  55. package/lib/types/numeric.js +47 -0
  56. package/lib/types/opt-in.js +101 -0
  57. package/lib/types/package-identity.js +47 -0
  58. package/lib/types/params.js +328 -0
  59. package/lib/types/payload.js +36 -0
  60. package/lib/types/platform-line.generated.d.ts +10 -0
  61. package/lib/types/platform-line.generated.js +12 -0
  62. package/lib/types/platform-services.d.ts +2 -2
  63. package/lib/types/platform-services.js +52 -0
  64. package/lib/types/probe.js +122 -0
  65. package/lib/types/prompts.js +409 -0
  66. package/lib/types/quality.js +308 -0
  67. package/lib/types/reachability.js +122 -0
  68. package/lib/types/redact.js +257 -0
  69. package/lib/types/reference-rewrite.js +144 -0
  70. package/lib/types/reports.js +110 -0
  71. package/lib/types/restrictor.d.ts +11 -0
  72. package/lib/types/restrictor.js +48 -0
  73. package/lib/types/review-channel.js +59 -0
  74. package/lib/types/run-registry.js +345 -0
  75. package/lib/types/scope.js +7 -0
  76. package/lib/types/serial.js +17 -0
  77. package/lib/types/session-fold.d.ts +15 -0
  78. package/lib/types/session-fold.js +28 -0
  79. package/lib/types/session-projection.js +295 -0
  80. package/lib/types/signals.js +209 -0
  81. package/lib/types/skill-discovery.js +66 -0
  82. package/lib/types/skill-health.js +134 -0
  83. package/lib/types/skill-history.js +745 -0
  84. package/lib/types/skill-lint.js +112 -0
  85. package/lib/types/skill-reads.js +67 -0
  86. package/lib/types/skill-row-cells.js +123 -0
  87. package/lib/types/skill-store.js +3528 -0
  88. package/lib/types/state-store.js +60 -0
  89. package/lib/types/store-ledger.js +117 -0
  90. package/lib/types/subagent/channel.d.ts +4 -0
  91. package/lib/types/subagent/channel.js +131 -0
  92. package/lib/types/subagent/index.d.ts +4 -0
  93. package/lib/types/subagent/index.js +5 -0
  94. package/lib/types/subagent/readiness.d.ts +6 -0
  95. package/lib/types/subagent/readiness.js +35 -0
  96. package/lib/types/subagent/types.d.ts +58 -0
  97. package/lib/types/subagent/types.js +2 -0
  98. package/lib/types/threats.js +338 -0
  99. package/lib/types/tool-dispatch.d.ts +5 -0
  100. package/lib/types/tool-dispatch.js +535 -0
  101. package/lib/types/tree-write.js +40 -0
  102. package/lib/types/unit-members.js +121 -0
  103. package/lib/types/usage.js +474 -0
  104. package/lib/types/write-authority.js +23 -0
  105. package/lib/types/write-gates.js +344 -0
  106. package/lib/types/write-inventory.js +158 -0
  107. package/package.json +39 -11
@@ -0,0 +1,222 @@
1
+ import { n as errorText } from "./errors-Br2-uUSK.js";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import * as yaml from "js-yaml";
6
+ //#region lib/types/package-identity.js
7
+ /**
8
+ * G1/1.8 (G-04): the SCOPE this build was published under, DERIVED from a manifest instead of written as a
9
+ * literal in running code.
10
+ *
11
+ * The family used to hard-code `@deepseek-ai/dsh-...` names and let `prepare-release.mjs --scope @lmzhen`
12
+ * patch the text afterwards. A published install therefore depended on a release-time string rewrite
13
+ * reaching every copy, and the copies had already drifted (doctor.ts, index.ts, preset-composition.ts and
14
+ * its .mjs mirror each spelled the identity). The scope is knowable at runtime: it is the scope segment of
15
+ * the nearest `package.json` above the calling module. Nothing to rewrite, one owner of the answer.
16
+ *
17
+ * @module @lmzhen/dsh-evolution-core/package-identity
18
+ */
19
+ /**
20
+ * The scope segment of the nearest `package.json` at or above `from`.
21
+ * @param from - the calling module URL (its own manifest is the answer).
22
+ * @returns the scope, e.g. `@deepseek-ai` or `@lmzhen`; an unscoped manifest yields an empty string.
23
+ */
24
+ function scopeOfNearestManifest(from) {
25
+ let dir = dirname(from instanceof URL ? fileURLToPath(from) : fileURLToPath(new URL(from)));
26
+ for (let hops = 0; hops < 32; hops += 1) {
27
+ const manifest = join(dir, "package.json");
28
+ if (existsSync(manifest)) {
29
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
30
+ const name = typeof parsed.name === "string" ? parsed.name : "";
31
+ return name.startsWith("@") ? name.split("/")[0] ?? "" : "";
32
+ }
33
+ const parent = dirname(dir);
34
+ if (parent === dir) break;
35
+ dir = parent;
36
+ }
37
+ return "";
38
+ }
39
+ /**
40
+ * One family/platform package name under the RUNNING scope.
41
+ * @param bareName - the name without any scope, e.g. `dsh-agent-preset`.
42
+ * @param from - the calling module URL.
43
+ * @returns `scope/bareName`, or `bareName` when the manifest carries no scope.
44
+ */
45
+ function scopedPackageName(bareName, from) {
46
+ const scope = scopeOfNearestManifest(from);
47
+ return scope === "" ? bareName : scope + "/" + bareName;
48
+ }
49
+ //#endregion
50
+ //#region lib/types/composition/preset-ids.js
51
+ const EVOLUTION_PRESET_IDS = [
52
+ "evolution",
53
+ "evolution-ptc",
54
+ "evolution-cordis",
55
+ "evolution-minimal"
56
+ ];
57
+ const REVIEWER_PRESET_ID = "evolution-reviewer-readonly";
58
+ const MAINTAINER_PRESET_ID = "evolution-maintainer-readonly";
59
+ const RESTRICTED_TOOLS_ROW_ID = "evolution-restricted-tools";
60
+ const RESTRICTOR_MODULE = scopedPackageName("dsh-evolution-core/restrictor", import.meta.url);
61
+ const RESTRICTED_PRESET_DELIVERY = "runtime";
62
+ const RESTRICTED_PRESET_DISPLAY = {
63
+ reviewer: {
64
+ name: "evolution reviewer (read-only)",
65
+ description: "Internal restricted preset used by the family review channel.",
66
+ order: 900
67
+ },
68
+ maintainer: {
69
+ name: "evolution maintainer (read-only)",
70
+ description: "Internal restricted preset used by the family maintenance channel.",
71
+ order: 901
72
+ }
73
+ };
74
+ function restrictedPresetId(kind) {
75
+ return kind === "reviewer" ? REVIEWER_PRESET_ID : MAINTAINER_PRESET_ID;
76
+ }
77
+ /** Trim, drop blanks, de-duplicate and sort: ONE spelling for an allow/deny set. */
78
+ function normalizeAllow(names) {
79
+ return [...new Set(names.map((name) => name.trim()).filter((name) => name !== ""))].sort();
80
+ }
81
+ //#endregion
82
+ //#region lib/types/composition/restricted-preset.js
83
+ const JsExpr = new yaml.Type("tag:yaml.org,2002:js", {
84
+ kind: "scalar",
85
+ resolve: (data) => typeof data === "string",
86
+ construct: (data) => ({ __jsExpr: data }),
87
+ represent: (data) => data.__jsExpr
88
+ });
89
+ const entryListSchema = yaml.JSON_SCHEMA.extend(JsExpr);
90
+ function parsePresetRows(content) {
91
+ const parsed = yaml.load(content, { schema: entryListSchema });
92
+ if (!Array.isArray(parsed)) throw new Error(errorText("e-324-restricted-preset-unavailable", { a1: "parent preset composition is not a top-level list" }));
93
+ return parsed;
94
+ }
95
+ function composeRestrictedPresetDefinition(source, req) {
96
+ const allow = normalizeAllow(req.allow);
97
+ if (allow.length === 0) throw new Error(errorText("e-324-restricted-preset-unavailable", { a1: "restricted preset needs a non-empty allow set" }));
98
+ const rows = parsePresetRows(source.content);
99
+ const display = RESTRICTED_PRESET_DISPLAY[req.kind];
100
+ const restrictorRow = {
101
+ id: RESTRICTED_TOOLS_ROW_ID,
102
+ name: RESTRICTOR_MODULE,
103
+ config: {
104
+ allow,
105
+ presetId: restrictedPresetId(req.kind)
106
+ }
107
+ };
108
+ return {
109
+ id: restrictedPresetId(req.kind),
110
+ name: display.name,
111
+ description: display.description,
112
+ order: display.order,
113
+ plugins: [...rows, restrictorRow]
114
+ };
115
+ }
116
+ //#endregion
117
+ //#region lib/types/composition/ensure.js
118
+ const SLOTS = /* @__PURE__ */ new WeakMap();
119
+ function slotFor(ctx, kind) {
120
+ let byKind = SLOTS.get(ctx);
121
+ if (byKind === void 0) {
122
+ byKind = /* @__PURE__ */ new Map();
123
+ SLOTS.set(ctx, byKind);
124
+ }
125
+ let slot = byKind.get(kind);
126
+ if (slot === void 0) {
127
+ slot = {};
128
+ byKind.set(kind, slot);
129
+ }
130
+ return slot;
131
+ }
132
+ function registryOf(ctx) {
133
+ const candidate = ctx.get("agentPresets");
134
+ if (candidate === void 0) return void 0;
135
+ if (typeof candidate.readDocument !== "function" || typeof candidate.register !== "function") return void 0;
136
+ return candidate;
137
+ }
138
+ function installReleaseEffect(ctx, slot) {
139
+ if (slot.effectInstalled === true) return;
140
+ const effect = ctx.effect;
141
+ if (effect === void 0) return;
142
+ slot.effectInstalled = true;
143
+ effect.call(ctx, () => () => {
144
+ releaseRestrictedPreset(ctx, void 0, slot);
145
+ }, "evolution-restricted-preset");
146
+ }
147
+ async function releaseRestrictedPreset(ctx, kind, explicitSlot) {
148
+ const slot = explicitSlot ?? (kind === void 0 ? void 0 : slotFor(ctx, kind));
149
+ if (slot === void 0) return;
150
+ const release = slot.release;
151
+ slot.release = void 0;
152
+ slot.readyKey = void 0;
153
+ slot.state = void 0;
154
+ if (release !== void 0) await release();
155
+ }
156
+ async function ensureRestrictedPreset(ctx, req) {
157
+ const allow = normalizeAllow(req.allow);
158
+ const key = req.parentPresetId + "\n" + allow.join("\n");
159
+ const slot = slotFor(ctx, req.kind);
160
+ if (slot.readyKey === key && slot.state !== void 0 && slot.state.kind === "ready") return slot.state;
161
+ if (slot.inflight !== void 0) return slot.inflight;
162
+ slot.inflight = (async () => {
163
+ await releaseRestrictedPreset(ctx, req.kind, slot);
164
+ if (allow.length === 0) {
165
+ slot.state = {
166
+ kind: "failed",
167
+ code: "E-324",
168
+ reason: "allow set is empty"
169
+ };
170
+ return slot.state;
171
+ }
172
+ const registry = registryOf(ctx);
173
+ if (registry === void 0) {
174
+ slot.state = {
175
+ kind: "failed",
176
+ code: "E-324",
177
+ reason: "agentPresets service unavailable or incomplete"
178
+ };
179
+ return slot.state;
180
+ }
181
+ try {
182
+ const definition = composeRestrictedPresetDefinition(await registry.readDocument(req.parentPresetId), {
183
+ kind: req.kind,
184
+ parentPresetId: req.parentPresetId,
185
+ allow
186
+ });
187
+ slot.release = await registry.register(definition);
188
+ slot.readyKey = key;
189
+ slot.state = {
190
+ kind: "ready",
191
+ presetId: definition.id,
192
+ parentPresetId: req.parentPresetId,
193
+ allow
194
+ };
195
+ installReleaseEffect(ctx, slot);
196
+ return slot.state;
197
+ } catch (error) {
198
+ slot.readyKey = void 0;
199
+ slot.state = {
200
+ kind: "failed",
201
+ code: "E-324",
202
+ reason: error instanceof Error ? error.message : String(error)
203
+ };
204
+ return slot.state;
205
+ }
206
+ })();
207
+ try {
208
+ return await slot.inflight;
209
+ } finally {
210
+ slot.inflight = void 0;
211
+ }
212
+ }
213
+ /** Read-only view of the current registration (doctor runtime row). */
214
+ function restrictedPresetState(ctx, kind) {
215
+ return SLOTS.get(ctx)?.get(kind)?.state;
216
+ }
217
+ /** Release the registration for one kind (HMR/tests); the fiber effect also calls this. */
218
+ async function disposeRestrictedPreset(ctx, kind) {
219
+ await releaseRestrictedPreset(ctx, kind);
220
+ }
221
+ //#endregion
222
+ export { scopedPackageName as _, entryListSchema as a, MAINTAINER_PRESET_ID as c, RESTRICTED_TOOLS_ROW_ID as d, RESTRICTOR_MODULE as f, scopeOfNearestManifest as g, restrictedPresetId as h, composeRestrictedPresetDefinition as i, RESTRICTED_PRESET_DELIVERY as l, normalizeAllow as m, ensureRestrictedPreset as n, parsePresetRows as o, REVIEWER_PRESET_ID as p, restrictedPresetState as r, EVOLUTION_PRESET_IDS as s, disposeRestrictedPreset as t, RESTRICTED_PRESET_DISPLAY as u };
@@ -0,0 +1,57 @@
1
+ //#region lib/types/errors.js
2
+ /**
3
+ * THE error-code table: E-301…E-325 with the exact text each one emits (E-317…E-319 are the
4
+ * write-admission gates' own refusals).
5
+ *
6
+ * The codes used to be spelled out at 33 call sites across two packages, so one
7
+ * code's wording could drift between the branches that answer it and nothing could
8
+ * see the drift. Keys name the SCENARIO rather than the code, because a code
9
+ * legitimately carries more than one text (E-301 answers pending / approve / reject
10
+ * with different guidance); the code still leads every string, so model- and
11
+ * user-facing output is unchanged. Interpolations are written `%aN%` and filled by
12
+ * `errorText` from the values the call site used to interpolate inline.
13
+ * @module @lmzhen/dsh-evolution-core
14
+ */
15
+ const EVOLUTION_ERRORS = Object.freeze({
16
+ "e-305-this-invocation-carries-no": "E-305: 本次调用没有携带 agent —— `%a1%` 需要由会话支撑的调用(请在 GUI 或 CLI 的会话里运行)。",
17
+ "e-306-this-deployment-stages-foreground": "E-306: 本部署会暂存前台写入,但 `/evolution %a1%` 无法通过技能运行器回放 —— 没有可暂存的内容。请在批准策略为 'never' 的会话里运行,或在 evolution-approval 行上设置 `stageForeground: false`,然后重试该命令。",
18
+ "e-301-approval-service-not-mounted": "E-301: 批准服务未挂载:待批准写入无法列出或回放。挂载 evolution-approval 行(随 evolution-host/evolution-all 提供)后,运行 /evolution doctor 可查看已挂载的服务。",
19
+ "e-301-approval-service-not-mounted-2": "E-301: 批准服务未挂载。下一步:evolution-approval 行随 evolution-host/evolution-all 一起提供 —— 运行 /evolution doctor 查看已挂载的服务。",
20
+ "e-304-the-mounted-approval-service": "E-304: 已挂载的批准服务早于本能力发布 —— 请改为驳回该记录。",
21
+ "e-302-curator-service-not-mounted": "E-302: 策展服务未挂载。下一步:挂载 evolution-curator 行(evolution-host/evolution-all),并运行 /evolution doctor。",
22
+ "e-305-the-invocation-agent-exposes": "E-305: 调用 agent 既没有暴露 `followup` 也没有 `inject` —— %a1% 没有投递通道。",
23
+ "e-306-this-deployment-stages-foreground-2": "E-306: 本部署会暂存前台写入,但本次调用没有 agent 会话 —— `/evolution restructure` 会暂存一条没有会话归属的记录。请在 GUI 或 CLI 的会话里运行,或在 `stageForeground: false` 行上设置,然后重试该命令。",
24
+ "e-303-replay-service-not-mounted": "E-303: 回放服务未挂载。下一步:挂载 evolution-replay 行(evolution-host/evolution-all),并运行 /evolution doctor。",
25
+ "e-307-the-settings-service-could": "E-307: 设置服务无法报告它的分区(%a1%)。/evolution params 需要用户层来区分覆盖值与部署值;运行 /evolution doctor 查看已挂载的服务。",
26
+ "e-308-unknown-parameter-group-group": "E-308: 未知的参数分组「%a1%」—— 已知分组:%a2%。",
27
+ "e-311-no-settings-service-is": "E-311: 没有挂载设置服务,因此没有可写入的用户层。请挂载 settings 行(packages/settings/settings-file)后重试;本命令从不修改 cordis.yml —— 部署值属于部署。/evolution doctor 会列出已挂载的服务。",
28
+ "e-312-error-instanceof-Error-error": "E-312: %a1% —— /evolution params 会列出规范 id。",
29
+ "e-313-unknown-parameter-id-evolution": "E-313: 未知参数「%a1%」—— /evolution params 会列出每个已注册 id 及其层级与用户层。",
30
+ "e-314-id-is-a-deployment": "E-314: 「%a1%」是部署参数(层级 %a2%,归属 %a3%)—— 它写在 cordis.yml 的插件行或策略行上,而不是从会话写入。/evolution params 会显示每一行由谁可写。",
31
+ "e-315-id-has-no-user": "E-315: 「%a1%」没有用户层 —— 归属 %a2% 没有发布设置命名空间,因此该值留在部署层。",
32
+ "e-307-the-settings-service-could-2": "E-307: 设置服务无法报告它的分区(%a1%)。/evolution policy set 需要当前修订号才能安全写入;运行 /evolution doctor 查看已挂载的服务。",
33
+ "e-316-namespace-namespace-is-not": "E-316: namespace \"%a1%\" 未注册 —— 归属 %a2% 在本组合中未挂载,因此无法从这里写入它的参数。 /evolution params 会把那些行标为 'unregistered'.",
34
+ "e-309-revision-conflict-you-sent": "E-309: 修订冲突 —— 你提交的是 %a1%,文档当前为 %a2%。请用 /evolution params --json 重新读取后重试。",
35
+ "e-309-revision-conflict-message-Re": "E-309: 修订冲突 —— %a1%。请用 /evolution params --json 重新读取后重试。",
36
+ "e-310-the-settings-service-refused": "E-310: 设置服务拒绝了这次写入:%a1% —— 归属插件的规则优先(跨字段成对约束,或只能收紧的上限)。/evolution params 会显示当前值。",
37
+ "e-317-skill-write-not-confirmed": "E-317: 技能 \"%a1%\" 未被%a2% —— 确认提示被拒绝或关闭,因此没有写入任何内容。每次创建与裸删除都会出现该提示;只有操作者要求时才重复调用。",
38
+ "e-318-skill-write-without-a-read": "E-318: 本会话没有读取技能 \"%a1%\",因此拒绝这次写入。请先用 `skill` 工具读取它(失败的读取不算),然后重复该写入。",
39
+ "e-320-the-profile-home-is-not": "E-320: 本宿主没有报告 profile home,因此无法定位旧设置文档。/evolution migrate 读取 `<profile home>/settings.yaml.imported`;请在由 profile 启动的会话里运行。",
40
+ "e-322-a-parameter-would-break-a": "E-322: %a1% 至少应为 %a2%,但这次写入把它设成 %a3%。未写入任何内容。",
41
+ "e-321-no-io-provider-is": "E-321: 没有挂载 evolution-io 提供方,因此无法读取旧设置文档。请挂载 evolution-io 行(evolution-host/evolution-all)后重试。",
42
+ "e-319-skill-write-confirm-timed-out": "E-319: 技能 \"%a1%\" 未被%a2% —— 确认提示在 %a3% 秒内无人应答,因此该写入被取消。没有人在场决定:把 `skillWriteConfirm` 设为 `auto` 可免提示直接写入,保留 `ask` 则一直等到有人应答,或提高 `skillWriteConfirmTimeoutSeconds`。",
43
+ "e-323-subagent-channel-unavailable": "E-323: 子代理通道不可用:缺少 subagents/agentPresets 服务,或 spawn provider 不支持 agentPreset 能力。审阅/维护会明确标记为不可用,不会静默退回 inject。运行 /evolution doctor 查看原因与修复指引。",
44
+ "e-324-restricted-preset-unavailable": "E-324: 受限 preset 创建失败(读取父 preset、解析、注册或限制器挂载任一步失败):%a1%。审阅/维护通道不可用;运行 /evolution doctor 查看 preset id、allow 集合与失败原因。",
45
+ "e-325-host-data-plane-unavailable": "E-325: Host 数据面不可用:没有挂载 connection 服务,面板与审批路由不会注册。改用 /evolution pending、approve、reject 命令面完成同等操作;运行 /evolution doctor 查看详情。"
46
+ });
47
+ /**
48
+ * Render one table entry, substituting its interpolation slots.
49
+ * @param id - the scenario key.
50
+ * @param args - values for the entry's `%aN%` slots.
51
+ * @returns the message text the call site used to spell out inline.
52
+ */
53
+ function errorText(id, args = {}) {
54
+ return EVOLUTION_ERRORS[id].replace(/%a(\d+)%/g, (_match, n) => String(args["a" + n] ?? ""));
55
+ }
56
+ //#endregion
57
+ export { errorText as n, EVOLUTION_ERRORS as t };
@@ -0,0 +1,149 @@
1
+ //#region lib/types/guards.js
2
+ /**
3
+ * The family's plain-object predicate, in ONE place (v46 G7.1 / audit A8).
4
+ *
5
+ * The audit found the same guard — "a non-null object that is not an array" — rewritten fifteen
6
+ * times across nine packages, four times inside core itself, and with two byte-identical `asRecord`
7
+ * copies in evolution-policy and evolution-threat. This module is the single definition; callers
8
+ * import it instead of spelling the clause again.
9
+ *
10
+ * @module @lmzhen/dsh-evolution-core
11
+ */
12
+ /**
13
+ * Is this a non-null, non-array object (what JSON calls an object)?
14
+ * @param value - the value to test.
15
+ * @returns whether it is a plain object.
16
+ */
17
+ function isRecord(value) {
18
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19
+ }
20
+ /**
21
+ * Read a value as a record, or undefined when it is not one.
22
+ * @param value - the value to read.
23
+ * @returns the value typed as a record, or undefined.
24
+ */
25
+ function asRecord(value) {
26
+ return isRecord(value) ? value : void 0;
27
+ }
28
+ //#endregion
29
+ //#region lib/types/http-routes.js
30
+ /**
31
+ * The Host data-plane contract + Fetch-shaped helpers every family route surface shares (W1/W6).
32
+ *
33
+ * WHY THIS IS IN CORE: HOST_ROUTES is the ONE path table (clients are generated from it), and the
34
+ * response/body helpers are thin platform-Request utilities. The loopback fence that used to live here
35
+ * is GONE (G4): trust, browser auth and owner admission are applied by the platform's connection
36
+ * carrier BEFORE a route's fetch(request) runs, so a family copy of those rules was a second admission
37
+ * path (and did diverge).
38
+ *
39
+ * Layering: pure transport. Nothing here knows about skills, approvals, curators or route tables.
40
+ * @module @lmzhen/dsh-evolution-core/http-routes
41
+ */
42
+ /** Largest request body a family route accepts; a bigger one reads as "no body" (400 from the handler). */
43
+ const MAX_REQUEST_BODY_BYTES = 64 * 1024;
44
+ /**
45
+ * Read one JSON request body that must be an OBJECT, bounded.
46
+ * @param request - the platform Fetch Request handed to a route.
47
+ * @param maxBytes - the body bound (defaults to MAX_REQUEST_BODY_BYTES).
48
+ * @returns the body as a plain object, or null when it is absent, oversized, not JSON, or not an object.
49
+ */
50
+ async function readJsonObject(request, maxBytes = MAX_REQUEST_BODY_BYTES) {
51
+ const buffer = await request.arrayBuffer();
52
+ if (buffer.byteLength > maxBytes || buffer.byteLength === 0) return null;
53
+ let parsed;
54
+ try {
55
+ parsed = JSON.parse(Buffer.from(buffer).toString("utf8"));
56
+ } catch {
57
+ return null;
58
+ }
59
+ return isRecord(parsed) ? parsed : null;
60
+ }
61
+ const HOST_ROUTES = {
62
+ approval: {
63
+ pending: "/api/dsh-evolution/approval/pending",
64
+ preview: "/api/dsh-evolution/approval/preview",
65
+ approve: "/api/dsh-evolution/approval/approve",
66
+ reject: "/api/dsh-evolution/approval/reject"
67
+ },
68
+ skillHistory: {
69
+ skills: "/api/dsh-evolution/skill-history/skills",
70
+ versions: "/api/dsh-evolution/skill-history/versions",
71
+ diff: "/api/dsh-evolution/skill-history/versions/diff",
72
+ body: "/api/dsh-evolution/skill-history/versions/body",
73
+ undo: "/api/dsh-evolution/skill-history/undo",
74
+ health: "/api/dsh-evolution/skill-history/health"
75
+ },
76
+ skillHealth: { rows: "/api/dsh-evolution/skill-health/rows" },
77
+ diagnostics: { view: "/api/dsh-evolution/diagnostics" }
78
+ };
79
+ function hostSurface(ctx) {
80
+ const connection = ctx.get("connection");
81
+ if (connection === void 0) return {
82
+ available: false,
83
+ code: "E-325",
84
+ reason: "connection-missing"
85
+ };
86
+ return {
87
+ available: true,
88
+ allowsRemoteAuthorities: connection.allowsRemoteAuthorities === true
89
+ };
90
+ }
91
+ /**
92
+ * Register the family host surface on the platform's exact-route carrier.
93
+ *
94
+ * The platform owns admission (Host/Origin hygiene + browserAuth + exact-route owner peer) and the
95
+ * disposer is bound to the CALLING fiber, not the connection service: the connection service can
96
+ * outlive a reloaded family fiber, so the family must release its own registrations on dispose.
97
+ * Routes carry the audit snapshot taken at registration time (deployment-level facts only — the
98
+ * exact-route handler receives a Request, not the peer identity).
99
+ */
100
+ function registerHostRoutes(owner, build) {
101
+ const injecting = owner;
102
+ if (typeof injecting.inject !== "function") throw new Error("host routes require a context that owns inject()");
103
+ injecting.inject(["connection"], (scope) => {
104
+ const connection = scope.connection;
105
+ if (connection === void 0 || connection.fetch === void 0 || typeof connection.fetch.register !== "function") throw new Error("connection service is mounted without fetch.register()");
106
+ const register = connection.fetch.register.bind(connection.fetch);
107
+ const routes = build({
108
+ channel: "connection.fetch",
109
+ principal: "owner",
110
+ deploymentAllowsRemote: connection.allowsRemoteAuthorities === true
111
+ });
112
+ for (const route of routes) if (!route.path.startsWith("/api/dsh-evolution/")) throw new Error("host route path must carry the /api/dsh-evolution/ prefix: " + route.path);
113
+ const effect = scope.effect;
114
+ if (typeof effect !== "function") throw new Error("connection scope has no effect() to own the registration");
115
+ effect.call(scope, () => {
116
+ const disposers = [];
117
+ try {
118
+ for (const route of routes) disposers.push(register(route));
119
+ } catch (error) {
120
+ for (const dispose of disposers) try {
121
+ dispose();
122
+ } catch {}
123
+ throw error;
124
+ }
125
+ return () => {
126
+ for (const dispose of disposers) try {
127
+ dispose();
128
+ } catch {}
129
+ };
130
+ }, "evolution-host-routes");
131
+ });
132
+ }
133
+ function json(status, body) {
134
+ return new Response(JSON.stringify(body), {
135
+ status,
136
+ headers: { "content-type": "application/json; charset=utf-8" }
137
+ });
138
+ }
139
+ function methodNotAllowed(allowed) {
140
+ return new Response(JSON.stringify({ error: "method-not-allowed" }), {
141
+ status: 405,
142
+ headers: {
143
+ "content-type": "application/json; charset=utf-8",
144
+ allow: allowed.join(", ")
145
+ }
146
+ });
147
+ }
148
+ //#endregion
149
+ export { methodNotAllowed as a, asRecord as c, json as i, isRecord as l, MAX_REQUEST_BODY_BYTES as n, readJsonObject as o, hostSurface as r, registerHostRoutes as s, HOST_ROUTES as t };
@@ -0,0 +1,2 @@
1
+ import { a as methodNotAllowed, i as json, n as MAX_REQUEST_BODY_BYTES, o as readJsonObject, r as hostSurface, s as registerHostRoutes, t as HOST_ROUTES } from "./http-routes-BP-S3KV-.js";
2
+ export { HOST_ROUTES, MAX_REQUEST_BODY_BYTES, hostSurface, json, methodNotAllowed, readJsonObject, registerHostRoutes };