@lmzhen/dsh-evolution-core 0.26.26 → 0.27.1

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,135 @@
1
+ /**
2
+ * Fuzzy string matching and replacement for patch/restructure edits of skill files.
3
+ *
4
+ * Split out of skill-store.ts (S2-1): pure text functions with no store state.
5
+ * The store imports the scan, the budgets and the replace entry points directly;
6
+ * none of them is re-exported, so the package export surface is unchanged.
7
+ */
8
+ function fuzzyIndexOf(content, pattern, from = 0) {
9
+ const isSpace = (char) => char !== undefined && /[ \t]/.test(char);
10
+ const escaped = (char) => {
11
+ if (char === 'n')
12
+ return '\n';
13
+ if (char === 't')
14
+ return '\t';
15
+ if (char === 'r')
16
+ return '\r';
17
+ return null;
18
+ };
19
+ for (let start = from; start < content.length; start += 1) {
20
+ let contentIndex = start;
21
+ let patternIndex = 0;
22
+ while (patternIndex < pattern.length && contentIndex < content.length) {
23
+ const patternChar = pattern[patternIndex];
24
+ const contentChar = content[contentIndex];
25
+ if (isSpace(patternChar)) {
26
+ // A pattern whitespace run consumes its full run plus any content run
27
+ // (even a shorter or empty one) before matching the next non-space.
28
+ while (patternIndex < pattern.length && isSpace(pattern[patternIndex]))
29
+ patternIndex += 1;
30
+ while (contentIndex < content.length && isSpace(content[contentIndex]))
31
+ contentIndex += 1;
32
+ continue;
33
+ }
34
+ const escapedChar = patternChar === '\\' ? escaped(pattern[patternIndex + 1]) : null;
35
+ if (escapedChar !== null && contentChar === escapedChar) {
36
+ patternIndex += 2;
37
+ contentIndex += 1;
38
+ continue;
39
+ }
40
+ if (patternChar === contentChar) {
41
+ contentIndex += 1;
42
+ patternIndex += 1;
43
+ continue;
44
+ }
45
+ break;
46
+ }
47
+ if (patternIndex === pattern.length)
48
+ return [start, contentIndex];
49
+ }
50
+ return null;
51
+ }
52
+ /** V6-17 (0.3.37): the fuzzy-patch scan is O(n·m) with no input bound; a
53
+ * non-exact anchor past these budgets would block the event loop (measured
54
+ * ~6s at 20k×20k). Exact matches go through the fast `includes` path and stay
55
+ * allowed regardless of size. */
56
+ export const FUZZY_MAX_PATTERN_CHARS = 4096;
57
+ export const FUZZY_MAX_WORK = 8_000_000;
58
+ /** Trim leading whitespace of the first line and trailing whitespace of the last line. */
59
+ export function trimPatternBoundaries(pattern) {
60
+ const from = pattern.search(/\S/);
61
+ const trimmed = from < 0 ? pattern : pattern.slice(from);
62
+ const trailing = trimmed.search(/\s+$/);
63
+ return trailing < 0 ? trimmed : trimmed.slice(0, trailing);
64
+ }
65
+ /** Replace only the fuzzy-matched span, preserving all surrounding bytes.
66
+ * V7-11 (0.3.44): the replaceAll loop accumulates the per-scan cost — the
67
+ * single-scan budget at the caller only bounded ONE fuzzyIndexOf, while an
68
+ * unbounded number of matches × O(n·m) each could still stall the loop.
69
+ * When the accumulated cost exceeds the budget the whole replace fails
70
+ * (null) instead of partially applying an arbitrary prefix. */
71
+ function fuzzyReplace(content, oldString, newString, replaceAll) {
72
+ let current = content;
73
+ let scanFrom = 0;
74
+ let totalWork = 0;
75
+ for (;;) {
76
+ totalWork += current.length * oldString.length;
77
+ if (totalWork > FUZZY_MAX_WORK)
78
+ return null;
79
+ const match = fuzzyIndexOf(current, oldString, scanFrom);
80
+ if (match === null)
81
+ return current;
82
+ const [start, end] = match;
83
+ const next = current.slice(0, start) + newString + current.slice(end);
84
+ if (!replaceAll)
85
+ return next;
86
+ current = next;
87
+ // Resume scanning after the inserted span: a self-containing newString must
88
+ // not be re-matched (mirrors String.replaceAll and guarantees termination).
89
+ scanFrom = start + newString.length;
90
+ }
91
+ }
92
+ export function fuzzyPatch(content, oldString, newString, replaceAll = false) {
93
+ // An empty oldString would `includes('')` trivially and splice newString
94
+ // between every character; refuse it at the boundary (P0-5).
95
+ if (oldString === '')
96
+ return null;
97
+ if (content.includes(oldString)) {
98
+ // 0.3.16 (E-2): a STRING replacement expands $&/$`/$'/$$ special
99
+ // sequences even in the exact-match fast path — a model writing a shell
100
+ // replacement (`100$'`) silently corrupted the file, and the fuzzy path
101
+ // (string concat, literal) disagreed with the fast path. The replacement
102
+ // function returns the newString verbatim; the replaceAll branch's
103
+ // split/join is already literal.
104
+ return replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, () => newString);
105
+ }
106
+ const boundary = trimPatternBoundaries(oldString);
107
+ // A whitespace-only pattern has no non-whitespace footprint; matching it
108
+ // would hit an empty span and splice content without consuming anything.
109
+ if (boundary === '')
110
+ return null;
111
+ // Stage 1: boundary trim — the model often cites a line with its leading
112
+ // indent (or a trailing space); trimming only the pattern's first/last line
113
+ // keeps the replacement span on the real text without touching other bytes.
114
+ if (boundary !== oldString) {
115
+ if (fuzzyIndexOf(content, boundary) !== null) {
116
+ const patched = fuzzyReplace(content, boundary, newString, replaceAll);
117
+ // F-317: patched === content is an IDENTITY replacement (the effective
118
+ // old/new text are equal), NOT "no match". Return the original so the
119
+ // caller's noop judgment (patch()) reports it as unchanged — returning
120
+ // null here made patch() say "Could not find old_string" for a patch
121
+ // that was actually a no-op. `null` remains exclusively "no match".
122
+ return patched;
123
+ }
124
+ }
125
+ // Stage 2: whitespace-plus-escape tolerance (pattern `\n`/`\t`/`\r` literals
126
+ // match real characters, runs match runs); replacement lands on the span
127
+ // only, so file indentation/formatting survives.
128
+ if (fuzzyIndexOf(content, oldString) !== null) {
129
+ const patched = fuzzyReplace(content, oldString, newString, replaceAll);
130
+ return patched;
131
+ }
132
+ return null;
133
+ }
134
+ /** Deterministic section-extraction plan facts; the caller owns the IO and the append semantics. */
135
+ //# sourceMappingURL=fuzzy-match.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The control-plane protection sets, held once and queried everywhere
3
+ * (decision B, rc.44 plan M2): the lifecycle engine, the scope view, the LLM
4
+ * nomination gate and the control-plane consolidate all answer "is this name
5
+ * off limits — and why" from the same instance, so the gate sets can never
6
+ * drift apart the way the three pre-rc.46 implementations did.
7
+ *
8
+ * Scope boundary: a GateSet covers NAME-SET protections only. Marker-based
9
+ * protections (pinned / bundled / hub-installed) are file markers resolved by
10
+ * `SkillLibrary.writeProtection` / `deleteProtection` — they depend on the
11
+ * filesystem and the write origin, not on a name list.
12
+ * @module @lmzhen/dsh-evolution-core
13
+ */
14
+ import { PROTECTED_BUILTIN_SKILLS } from "./constants.js";
15
+ export class EvolutionGateSet {
16
+ exclude;
17
+ referenced;
18
+ suppressed;
19
+ constructor(inputs = {}) {
20
+ this.exclude = inputs.exclude ?? new Set();
21
+ this.referenced = inputs.referenced ?? new Set();
22
+ this.suppressed = inputs.suppressed ?? new Set();
23
+ }
24
+ /**
25
+ * The first protection blocking this name, or null. Any hit blocks — the
26
+ * order is diagnostic only, so a name in two sets reports the first.
27
+ */
28
+ blockReason(name) {
29
+ if (this.exclude.has(name))
30
+ return 'excluded';
31
+ if (this.referenced.has(name))
32
+ return 'referenced';
33
+ if (this.suppressed.has(name))
34
+ return 'suppressed';
35
+ if (PROTECTED_BUILTIN_SKILLS.has(name))
36
+ return 'protected-builtin';
37
+ return null;
38
+ }
39
+ isBlocked(name) {
40
+ return this.blockReason(name) !== null;
41
+ }
42
+ }
43
+ /** Build a GateSet from the curator-style config field names. */
44
+ export function createGateSet(config) {
45
+ return new EvolutionGateSet({
46
+ exclude: config.excludeSkillNames,
47
+ referenced: config.referencedSkillNames,
48
+ suppressed: config.suppressedNames,
49
+ });
50
+ }
51
+ //# sourceMappingURL=gates.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The family's plain-object predicate, in ONE place (v46 G7.1 / audit A8).
3
+ *
4
+ * The audit found the same guard — "a non-null object that is not an array" — rewritten fifteen
5
+ * times across nine packages, four times inside core itself, and with two byte-identical `asRecord`
6
+ * copies in evolution-policy and evolution-threat. This module is the single definition; callers
7
+ * import it instead of spelling the clause again.
8
+ *
9
+ * @module @lmzhen/dsh-evolution-core
10
+ */
11
+ /**
12
+ * Is this a non-null, non-array object (what JSON calls an object)?
13
+ * @param value - the value to test.
14
+ * @returns whether it is a plain object.
15
+ */
16
+ export function isRecord(value) {
17
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
18
+ }
19
+ /**
20
+ * Read a value as a record, or undefined when it is not one.
21
+ * @param value - the value to read.
22
+ * @returns the value typed as a record, or undefined.
23
+ */
24
+ export function asRecord(value) {
25
+ return isRecord(value) ? value : undefined;
26
+ }
27
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1,63 @@
1
+ /*
2
+ * health-baseline — calibration and rule ADMISSION, as pure functions (G1, contract §4).
3
+ *
4
+ * A rule may only enter the engine when it is worth its noise, and that is a fact about two corpora,
5
+ * not an opinion: the platform's own skills must not trip it at all (a rule the shipped library
6
+ * already violates is a rule nobody can satisfy), and on the user's library it must bite often
7
+ * enough to matter but not so often that it is furniture. This module holds the counting and the
8
+ * verdict; walking the corpora and writing the artifact stay in the caller.
9
+ *
10
+ * Pure: no IO. `values` is keyed by rule id so a diff between two baselines is a map diff.
11
+ */
12
+ import { lintSkill, RULES } from "./skill-lint.js";
13
+ export const DEFAULT_ADMISSION = { minUserHitRate: 0.05, maxUserHitRate: 0.25 };
14
+ export function buildHealthBaseline(skills) {
15
+ const values = {};
16
+ for (const rule of RULES)
17
+ values[rule.id] = { platformHits: 0, userHits: 0, userHitRate: 0 };
18
+ let user = 0;
19
+ let platform = 0;
20
+ for (const skill of skills) {
21
+ if (skill.corpus === 'user')
22
+ user += 1;
23
+ else
24
+ platform += 1;
25
+ for (const finding of lintSkill(skill.facts)) {
26
+ const row = values[finding.rule];
27
+ if (row === undefined)
28
+ continue;
29
+ if (skill.corpus === 'user')
30
+ row.userHits += 1;
31
+ else
32
+ row.platformHits += 1;
33
+ }
34
+ }
35
+ for (const row of Object.values(values))
36
+ row.userHitRate = user === 0 ? 0 : row.userHits / user;
37
+ return {
38
+ unit: 'chars',
39
+ scope: 'references markdown file count',
40
+ corpora: { user, platform },
41
+ values,
42
+ };
43
+ }
44
+ /** The admission verdict for ONE rule id (contract §4): platform corpus must not trip it, and the
45
+ * user-library hit rate must land inside the band. Every failure names the number it failed on. */
46
+ export function admissionVerdict(baseline, ruleId, thresholds = DEFAULT_ADMISSION) {
47
+ const row = baseline.values[ruleId];
48
+ const failures = [];
49
+ if (row === undefined)
50
+ return { admitted: false, failures: ['rule ' + ruleId + ' has no baseline row'] };
51
+ if (baseline.corpora.platform === 0)
52
+ failures.push('no platform-corpus skills measured — a vacuous 0-hit count admits any rule');
53
+ else if (row.platformHits !== 0)
54
+ failures.push('platform corpus trips ' + ruleId + ' ' + String(row.platformHits) + ' time(s) — a rule the shipped library violates is unsatisfiable');
55
+ if (baseline.corpora.user === 0)
56
+ failures.push('no user-library skills measured — a rate over an empty corpus proves nothing');
57
+ else if (row.userHitRate < thresholds.minUserHitRate)
58
+ failures.push(ruleId + ' hits ' + row.userHitRate.toFixed(3) + ' of the user library, below ' + String(thresholds.minUserHitRate) + ' — too rare to be worth a rule');
59
+ else if (row.userHitRate > thresholds.maxUserHitRate)
60
+ failures.push(ruleId + ' hits ' + row.userHitRate.toFixed(3) + ' of the user library, above ' + String(thresholds.maxUserHitRate) + ' — furniture, no signal');
61
+ return { admitted: failures.length === 0, failures };
62
+ }
63
+ //# sourceMappingURL=health-baseline.js.map
@@ -1,49 +1,81 @@
1
1
  /**
2
- * The loopback HTTP plumbing every family route surface shares (E-302-era conventions).
2
+ * The Host data-plane contract + Fetch-shaped helpers every family route surface shares (W1/W6).
3
3
  *
4
- * WHY THIS IS IN CORE: the fence, the bounded body reader and the JSON writer are the same for every
5
- * family route surface, and `evolution-core` is the one package all of them already depend on. The
6
- * canonical platform implementation (packages/client/connection/src/api-request-trust.ts) is not part
7
- * of that package's published surface, so the family keeps its own copy of the RULES here — once.
4
+ * WHY THIS IS IN CORE: HOST_ROUTES is the ONE path table (clients are generated from it), and the
5
+ * response/body helpers are thin platform-Request utilities. The loopback fence that used to live here
6
+ * is GONE (G4): trust, browser auth and owner admission are applied by the platform's connection
7
+ * carrier BEFORE a route's fetch(request) runs, so a family copy of those rules was a second admission
8
+ * path (and did diverge).
8
9
  *
9
10
  * Layering: pure transport. Nothing here knows about skills, approvals, curators or route tables.
10
11
  * @module @lmzhen/dsh-evolution-core/http-routes
11
12
  */
12
- import type { IncomingMessage, ServerResponse } from 'node:http';
13
+ import type { Context } from '@deepseek-ai/cordis';
13
14
  /** Largest request body a family route accepts; a bigger one reads as "no body" (400 from the handler). */
14
15
  export declare const MAX_REQUEST_BODY_BYTES: number;
15
- /** The request facts the trust fence reads: a Node request, or the same fields in a spec fixture. */
16
- export interface FenceRequest {
17
- readonly headers: Record<string, string | string[] | undefined>;
18
- readonly socket?: {
19
- readonly remoteAddress?: string | undefined;
20
- } | undefined;
21
- }
22
16
  /**
23
- * Whether one request may enter a family route.
24
- *
25
- * Rules, in order: the SOCKET must be loopback (authoritative — X-Forwarded-For is never trusted), the
26
- * Host header must name a loopback authority (DNS-rebinding defense), an explicit cross-site marker is
27
- * refused, and an attached Origin must be exactly this authority.
28
- * @param request - the request to judge.
29
- * @returns true when the request may proceed.
30
- */
31
- export declare function isLoopbackRequest(request: FenceRequest): boolean;
32
- /** Write one JSON response. The routes own their response lifecycle, so every path ends here. */
33
- export declare function writeJson(res: ServerResponse, status: number, value: unknown): void;
34
- /**
35
- * Read one JSON request body, bounded.
36
- * @param req - the request.
37
- * @returns the parsed value, or null when the body is oversized, empty, or not JSON.
17
+ * Read one JSON request body that must be an OBJECT, bounded.
18
+ * @param request - the platform Fetch Request handed to a route.
19
+ * @param maxBytes - the body bound (defaults to MAX_REQUEST_BODY_BYTES).
20
+ * @returns the body as a plain object, or null when it is absent, oversized, not JSON, or not an object.
38
21
  */
39
- export declare function readJsonBody(req: IncomingMessage): Promise<unknown>;
22
+ export declare function readJsonObject(request: Request, maxBytes?: number): Promise<Record<string, unknown> | null>;
23
+ export type HttpMethod = 'GET' | 'HEAD' | 'POST';
24
+ export type HostRoutePath = `/api/dsh-evolution/${string}`;
25
+ export interface HostRouteSpec {
26
+ readonly path: HostRoutePath;
27
+ readonly methods: readonly HttpMethod[];
28
+ readonly requestBody: 'buffered' | 'streaming';
29
+ }
30
+ export interface ConnectionFetchRouteLike extends HostRouteSpec {
31
+ fetch(request: Request): Promise<Response>;
32
+ }
33
+ export interface DecisionAudit {
34
+ readonly channel: 'connection.fetch' | 'command';
35
+ readonly principal: 'owner';
36
+ readonly deploymentAllowsRemote: boolean;
37
+ }
38
+ export declare const HOST_ROUTES: {
39
+ readonly approval: {
40
+ readonly pending: "/api/dsh-evolution/approval/pending";
41
+ readonly preview: "/api/dsh-evolution/approval/preview";
42
+ readonly approve: "/api/dsh-evolution/approval/approve";
43
+ readonly reject: "/api/dsh-evolution/approval/reject";
44
+ };
45
+ readonly skillHistory: {
46
+ readonly skills: "/api/dsh-evolution/skill-history/skills";
47
+ readonly versions: "/api/dsh-evolution/skill-history/versions";
48
+ readonly diff: "/api/dsh-evolution/skill-history/versions/diff";
49
+ readonly body: "/api/dsh-evolution/skill-history/versions/body";
50
+ readonly undo: "/api/dsh-evolution/skill-history/undo";
51
+ readonly health: "/api/dsh-evolution/skill-history/health";
52
+ };
53
+ readonly skillHealth: {
54
+ readonly rows: "/api/dsh-evolution/skill-health/rows";
55
+ };
56
+ readonly diagnostics: {
57
+ readonly view: "/api/dsh-evolution/diagnostics";
58
+ };
59
+ };
60
+ export type HostSurface = {
61
+ readonly available: true;
62
+ readonly allowsRemoteAuthorities: boolean;
63
+ } | {
64
+ readonly available: false;
65
+ readonly code: 'E-325';
66
+ readonly reason: 'connection-missing' | 'service-absent';
67
+ };
68
+ export declare function hostSurface(ctx: Context): HostSurface;
40
69
  /**
41
- * Read one JSON request body that must be an OBJECT.
70
+ * Register the family host surface on the platform's exact-route carrier.
42
71
  *
43
- * The three route surfaces all take named fields, so "array or scalar" is a bad request rather than a
44
- * shape each handler has to re-check.
45
- * @param req - the request.
46
- * @returns the body as a plain object, or null when it is absent, oversized, not JSON, or not an object.
72
+ * The platform owns admission (Host/Origin hygiene + browserAuth + exact-route owner peer) and the
73
+ * disposer is bound to the CALLING fiber, not the connection service: the connection service can
74
+ * outlive a reloaded family fiber, so the family must release its own registrations on dispose.
75
+ * Routes carry the audit snapshot taken at registration time (deployment-level facts only — the
76
+ * exact-route handler receives a Request, not the peer identity).
47
77
  */
48
- export declare function readJsonObject(req: IncomingMessage): Promise<Record<string, unknown> | null>;
78
+ export declare function registerHostRoutes(owner: Context, build: (audit: DecisionAudit) => readonly ConnectionFetchRouteLike[]): void;
79
+ export declare function json(status: number, body: unknown): Response;
80
+ export declare function methodNotAllowed(allowed: readonly HttpMethod[]): Response;
49
81
  //# sourceMappingURL=http-routes.d.ts.map
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The Host data-plane contract + Fetch-shaped helpers every family route surface shares (W1/W6).
3
+ *
4
+ * WHY THIS IS IN CORE: HOST_ROUTES is the ONE path table (clients are generated from it), and the
5
+ * response/body helpers are thin platform-Request utilities. The loopback fence that used to live here
6
+ * is GONE (G4): trust, browser auth and owner admission are applied by the platform's connection
7
+ * carrier BEFORE a route's fetch(request) runs, so a family copy of those rules was a second admission
8
+ * path (and did diverge).
9
+ *
10
+ * Layering: pure transport. Nothing here knows about skills, approvals, curators or route tables.
11
+ * @module @lmzhen/dsh-evolution-core/http-routes
12
+ */
13
+ import { isRecord } from "./guards.js";
14
+ /** Largest request body a family route accepts; a bigger one reads as "no body" (400 from the handler). */
15
+ export const MAX_REQUEST_BODY_BYTES = 64 * 1024;
16
+ /**
17
+ * Read one JSON request body that must be an OBJECT, bounded.
18
+ * @param request - the platform Fetch Request handed to a route.
19
+ * @param maxBytes - the body bound (defaults to MAX_REQUEST_BODY_BYTES).
20
+ * @returns the body as a plain object, or null when it is absent, oversized, not JSON, or not an object.
21
+ */
22
+ export async function readJsonObject(request, maxBytes = MAX_REQUEST_BODY_BYTES) {
23
+ const buffer = await request.arrayBuffer();
24
+ if (buffer.byteLength > maxBytes || buffer.byteLength === 0)
25
+ return null;
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(Buffer.from(buffer).toString('utf8'));
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ return isRecord(parsed) ? parsed : null;
34
+ }
35
+ export const HOST_ROUTES = {
36
+ approval: {
37
+ pending: '/api/dsh-evolution/approval/pending',
38
+ preview: '/api/dsh-evolution/approval/preview',
39
+ approve: '/api/dsh-evolution/approval/approve',
40
+ reject: '/api/dsh-evolution/approval/reject',
41
+ },
42
+ skillHistory: {
43
+ skills: '/api/dsh-evolution/skill-history/skills',
44
+ versions: '/api/dsh-evolution/skill-history/versions',
45
+ diff: '/api/dsh-evolution/skill-history/versions/diff',
46
+ body: '/api/dsh-evolution/skill-history/versions/body',
47
+ undo: '/api/dsh-evolution/skill-history/undo',
48
+ health: '/api/dsh-evolution/skill-history/health',
49
+ },
50
+ skillHealth: {
51
+ rows: '/api/dsh-evolution/skill-health/rows',
52
+ },
53
+ diagnostics: {
54
+ view: '/api/dsh-evolution/diagnostics',
55
+ },
56
+ };
57
+ export function hostSurface(ctx) {
58
+ const connection = ctx.get('connection');
59
+ if (connection === undefined)
60
+ return { available: false, code: 'E-325', reason: 'connection-missing' };
61
+ const allows = connection.allowsRemoteAuthorities === true;
62
+ return { available: true, allowsRemoteAuthorities: allows };
63
+ }
64
+ /**
65
+ * Register the family host surface on the platform's exact-route carrier.
66
+ *
67
+ * The platform owns admission (Host/Origin hygiene + browserAuth + exact-route owner peer) and the
68
+ * disposer is bound to the CALLING fiber, not the connection service: the connection service can
69
+ * outlive a reloaded family fiber, so the family must release its own registrations on dispose.
70
+ * Routes carry the audit snapshot taken at registration time (deployment-level facts only — the
71
+ * exact-route handler receives a Request, not the peer identity).
72
+ */
73
+ export function registerHostRoutes(owner, build) {
74
+ const injecting = owner;
75
+ if (typeof injecting.inject !== 'function')
76
+ throw new Error('host routes require a context that owns inject()');
77
+ injecting.inject(['connection'], (scope) => {
78
+ const connection = scope.connection;
79
+ if (connection === undefined || connection.fetch === undefined || typeof connection.fetch.register !== 'function') {
80
+ throw new Error('connection service is mounted without fetch.register()');
81
+ }
82
+ const register = connection.fetch.register.bind(connection.fetch);
83
+ const audit = {
84
+ channel: 'connection.fetch',
85
+ principal: 'owner',
86
+ deploymentAllowsRemote: connection.allowsRemoteAuthorities === true,
87
+ };
88
+ const routes = build(audit);
89
+ for (const route of routes) {
90
+ if (!route.path.startsWith('/api/dsh-evolution/')) {
91
+ throw new Error('host route path must carry the /api/dsh-evolution/ prefix: ' + route.path);
92
+ }
93
+ }
94
+ const effect = scope.effect;
95
+ if (typeof effect !== 'function')
96
+ throw new Error('connection scope has no effect() to own the registration');
97
+ effect.call(scope, () => {
98
+ const disposers = [];
99
+ try {
100
+ for (const route of routes)
101
+ disposers.push(register(route));
102
+ }
103
+ catch (error) {
104
+ for (const dispose of disposers) {
105
+ try {
106
+ void dispose();
107
+ }
108
+ catch { /* best-effort rollback */ }
109
+ }
110
+ throw error;
111
+ }
112
+ return () => {
113
+ for (const dispose of disposers) {
114
+ try {
115
+ void dispose();
116
+ }
117
+ catch { /* a failed disposer must not mask the others */ }
118
+ }
119
+ };
120
+ }, 'evolution-host-routes');
121
+ });
122
+ }
123
+ export function json(status, body) {
124
+ return new Response(JSON.stringify(body), {
125
+ status,
126
+ headers: { 'content-type': 'application/json; charset=utf-8' },
127
+ });
128
+ }
129
+ export function methodNotAllowed(allowed) {
130
+ return new Response(JSON.stringify({ error: 'method-not-allowed' }), {
131
+ status: 405,
132
+ headers: { 'content-type': 'application/json; charset=utf-8', allow: allowed.join(', ') },
133
+ });
134
+ }
135
+ //# sourceMappingURL=http-routes.js.map
@@ -24,7 +24,7 @@
24
24
  * IO-seam consumer), `memory-store.ts`, `usage.ts`, `curator.ts`,
25
25
  * `quality.ts`, `signals.ts`, `skill-reads.ts` (the read-before-write rule, shared by the
26
26
  * tool path and the review's plan path), `drift-signals.ts`, `skill-health.ts`,
27
- * `preset-composition.ts`, `prompts.ts`, `learn-prompt.ts`,
27
+ * `composition/composer.ts`, `prompts.ts`, `learn-prompt.ts`,
28
28
  * `evolution-events.ts`, `evidence.ts` (which session frames count as content, for the plan
29
29
  * path's evidence-class report), `session-projection.ts` (the live per-session units the family's
30
30
  * log-shaped readers read instead of re-reading the session log), `io.ts` (the ctx.evolutionIo
@@ -53,7 +53,6 @@ export * from './io.ts';
53
53
  export * from './learn-prompt.ts';
54
54
  export * from './memory-store.ts';
55
55
  export * from './mutations.ts';
56
- export * from './preset-composition.ts';
57
56
  export * from './prompts.ts';
58
57
  export * from './quality.ts';
59
58
  export * from './redact.ts';
@@ -92,4 +91,10 @@ export * from './numeric.ts';
92
91
  export * from './opt-in.ts';
93
92
  export * from './payload.ts';
94
93
  export * from './fold-untrusted.ts';
94
+ export * from './subagent/index.ts';
95
+ export { EVOLUTION_PRESET_IDS, REVIEWER_PRESET_ID, MAINTAINER_PRESET_ID, RESTRICTED_TOOLS_ROW_ID, RESTRICTOR_MODULE, RESTRICTED_PRESET_DISPLAY, } from './composition/index.ts';
96
+ export type { RestrictedPresetKind, RestrictedPresetDisplay, RestrictedPresetRequest, RestrictedPresetState, } from './composition/index.ts';
97
+ export * from './diagnostics.ts';
98
+ export * from './platform-line.generated.ts';
99
+ export * from './session-fold.ts';
95
100
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Shared library for the dsh-evolution plugin family.
3
+ *
4
+ * Pure stores, prompts, signals, lifecycle logic, threat scanning, IO seam
5
+ * types, and session-event augmentations. This package ships no runtime
6
+ * plugin and no `./invariant` companion (v37 S2.1); consumers import named
7
+ * exports from the package root so published npm bundles never depend on
8
+ * source subpaths.
9
+ *
10
+ * ## Layer map (OPT-28, 2026-09) — locate code by LAYER, not by directory
11
+ *
12
+ * This one physical package carries THREE architecture layers of the family;
13
+ * when adding or looking for something, go by the export's layer:
14
+ *
15
+ * - **Cross-cutting basics** — `state-store.ts` (env roots — the single
16
+ * source of DSH-home semantics), `serial.ts`, `numeric.ts`, `constants.ts`,
17
+ * `mutations.ts`, `events.ts`, `gates.ts`, `errors.ts` (the error-code table),
18
+ * `payload.ts` (which of `facts`/`content` is the payload — one rule for the whole family).
19
+ * - **Security primitives** — `threats.ts` (content threat scanner),
20
+ * `redact.ts` (credential masking at model boundaries),
21
+ * `fold-untrusted.ts` (untrusted text must not become prompt structure). Consumers:
22
+ * evolution-policy/threat, both stores, review, maintenance.
23
+ * - **Core domain stores/logic** — `skill-store.ts` (skill tree engine +
24
+ * IO-seam consumer), `memory-store.ts`, `usage.ts`, `curator.ts`,
25
+ * `quality.ts`, `signals.ts`, `skill-reads.ts` (the read-before-write rule, shared by the
26
+ * tool path and the review's plan path), `drift-signals.ts`, `skill-health.ts`,
27
+ * `composition/composer.ts`, `prompts.ts`, `learn-prompt.ts`,
28
+ * `evolution-events.ts`, `evidence.ts` (which session frames count as content, for the plan
29
+ * path's evidence-class report), `session-projection.ts` (the live per-session units the family's
30
+ * log-shaped readers read instead of re-reading the session log), `io.ts` (the ctx.evolutionIo
31
+ * seam itself).
32
+ * @module @lmzhen/dsh-evolution-core
33
+ */
34
+ export * from "./citations.js";
35
+ // G1/1.1: the ONE authority behind a write (its type and the authorityFrom builder).
36
+ export * from "./tree-write.js";
37
+ export * from "./package-identity.js";
38
+ export * from "./write-authority.js";
39
+ export * from "./write-gates.js";
40
+ export * from "./reference-rewrite.js";
41
+ export * from "./cost.js";
42
+ export * from "./skill-lint.js";
43
+ export * from "./health-baseline.js";
44
+ export * from "./curator.js";
45
+ export * from "./evolution-events.js";
46
+ export * from "./fuzzy-match.js";
47
+ export * from "./gates.js";
48
+ export * from "./guards.js";
49
+ export * from "./http-routes.js";
50
+ export * from "./events.js";
51
+ export * from "./env.js";
52
+ export * from "./errors.js";
53
+ export * from "./io.js";
54
+ export * from "./learn-prompt.js";
55
+ export * from "./memory-store.js";
56
+ export * from "./mutations.js";
57
+ export * from "./prompts.js";
58
+ export * from "./quality.js";
59
+ export * from "./redact.js";
60
+ export * from "./review-channel.js";
61
+ export * from "./run-registry.js";
62
+ export * from "./reports.js";
63
+ export * from "./long-runs.js";
64
+ export * from "./serial.js";
65
+ export * from "./probe.js";
66
+ export * from "./platform-services.js";
67
+ export * from "./instance-scope.js";
68
+ export * from "./write-inventory.js";
69
+ export * from "./scope.js";
70
+ export * from "./skill-health.js";
71
+ export * from "./signals.js";
72
+ export * from "./skill-reads.js";
73
+ export * from "./skill-row-cells.js";
74
+ export * from "./document-body.js";
75
+ export * from "./evidence.js";
76
+ export * from "./session-projection.js";
77
+ export * from "./drift-signals.js";
78
+ export * from "./skill-history.js";
79
+ export * from "./unit-members.js";
80
+ export * from "./skill-store.js";
81
+ export * from "./store-ledger.js";
82
+ export * from "./reachability.js";
83
+ export * from "./state-store.js";
84
+ export * from "./threats.js";
85
+ export * from "./tool-dispatch.js";
86
+ export * from "./usage.js";
87
+ export * from "./constants.js";
88
+ export * from "./params.js";
89
+ export * from "./namespace-migration.js";
90
+ export * from "./live.js";
91
+ export * from "./numeric.js";
92
+ export * from "./opt-in.js";
93
+ export * from "./payload.js";
94
+ export * from "./fold-untrusted.js";
95
+ // G0 contract surface: /subagent types + state probe, /composition preset constants.
96
+ export * from "./subagent/index.js";
97
+ export { EVOLUTION_PRESET_IDS, REVIEWER_PRESET_ID, MAINTAINER_PRESET_ID, RESTRICTED_TOOLS_ROW_ID, RESTRICTOR_MODULE, RESTRICTED_PRESET_DISPLAY, } from "./composition/index.js";
98
+ export * from "./diagnostics.js";
99
+ export * from "./platform-line.generated.js";
100
+ export * from "./session-fold.js";
101
+ //# sourceMappingURL=index.js.map