@lmzhen/dsh-evolution-core 0.26.15 → 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.
- package/README.md +16 -1
- package/lib/composition/index.js +3 -0
- package/lib/composition-D8Y3-ROd.js +604 -0
- package/lib/ensure-MEYMj9jn.js +222 -0
- package/lib/errors-Br2-uUSK.js +57 -0
- package/lib/http-routes-BP-S3KV-.js +149 -0
- package/lib/http-routes.js +2 -0
- package/lib/index.js +777 -840
- package/lib/restrictor.js +37 -0
- package/lib/subagent/index.js +186 -0
- package/lib/types/citations.js +192 -0
- package/lib/types/{preset-composition.d.ts → composition/composer.d.ts} +2 -2
- package/lib/types/composition/composer.js +681 -0
- package/lib/types/composition/ensure.d.ts +10 -0
- package/lib/types/composition/ensure.js +95 -0
- package/lib/types/composition/index.d.ts +5 -0
- package/lib/types/composition/index.js +7 -0
- package/lib/types/composition/preset-ids.d.ts +17 -0
- package/lib/types/composition/preset-ids.js +21 -0
- package/lib/types/composition/restricted-preset.d.ts +36 -0
- package/lib/types/composition/restricted-preset.js +42 -0
- package/lib/types/constants.js +223 -0
- package/lib/types/cost.js +67 -0
- package/lib/types/curator.d.ts +2 -0
- package/lib/types/curator.js +346 -0
- package/lib/types/diagnostics.d.ts +26 -0
- package/lib/types/diagnostics.js +23 -0
- package/lib/types/document-body.js +91 -0
- package/lib/types/drift-signals.js +319 -0
- package/lib/types/env.js +25 -0
- package/lib/types/errors.d.ts +4 -1
- package/lib/types/errors.js +59 -0
- package/lib/types/events.js +16 -0
- package/lib/types/evidence.js +65 -0
- package/lib/types/evolution-events.js +479 -0
- package/lib/types/fold-untrusted.js +44 -0
- package/lib/types/frontmatter.js +623 -0
- package/lib/types/fuzzy-match.js +135 -0
- package/lib/types/gates.js +51 -0
- package/lib/types/guards.js +27 -0
- package/lib/types/health-baseline.js +63 -0
- package/lib/types/http-routes.d.ts +67 -35
- package/lib/types/http-routes.js +135 -0
- package/lib/types/index.d.ts +13 -2
- package/lib/types/index.js +101 -0
- package/lib/types/instance-scope.js +69 -0
- package/lib/types/io.js +675 -0
- package/lib/types/learn-prompt.js +42 -0
- package/lib/types/limits.js +63 -0
- package/lib/types/live.js +107 -0
- package/lib/types/long-runs.js +96 -0
- package/lib/types/memory-store.d.ts +0 -16
- package/lib/types/memory-store.js +606 -0
- package/lib/types/mutations.js +95 -0
- package/lib/types/namespace-migration.js +156 -0
- package/lib/types/numeric.js +47 -0
- package/lib/types/opt-in.js +101 -0
- package/lib/types/package-identity.d.ts +14 -0
- package/lib/types/package-identity.js +47 -0
- package/lib/types/params.js +328 -0
- package/lib/types/payload.js +36 -0
- package/lib/types/platform-line.generated.d.ts +10 -0
- package/lib/types/platform-line.generated.js +12 -0
- package/lib/types/platform-services.d.ts +2 -2
- package/lib/types/platform-services.js +52 -0
- package/lib/types/probe.d.ts +9 -3
- package/lib/types/probe.js +122 -0
- package/lib/types/prompts.js +409 -0
- package/lib/types/quality.js +308 -0
- package/lib/types/reachability.d.ts +46 -0
- package/lib/types/reachability.js +122 -0
- package/lib/types/redact.js +257 -0
- package/lib/types/reference-rewrite.js +144 -0
- package/lib/types/reports.js +110 -0
- package/lib/types/restrictor.d.ts +11 -0
- package/lib/types/restrictor.js +48 -0
- package/lib/types/review-channel.js +59 -0
- package/lib/types/run-registry.js +345 -0
- package/lib/types/scope.js +7 -0
- package/lib/types/serial.js +17 -0
- package/lib/types/session-fold.d.ts +15 -0
- package/lib/types/session-fold.js +28 -0
- package/lib/types/session-projection.js +295 -0
- package/lib/types/signals.js +209 -0
- package/lib/types/skill-discovery.js +66 -0
- package/lib/types/skill-health.js +134 -0
- package/lib/types/skill-history.js +745 -0
- package/lib/types/skill-lint.js +112 -0
- package/lib/types/skill-reads.js +67 -0
- package/lib/types/skill-row-cells.js +123 -0
- package/lib/types/skill-store.d.ts +34 -3
- package/lib/types/skill-store.js +3528 -0
- package/lib/types/state-store.js +60 -0
- package/lib/types/store-ledger.d.ts +44 -0
- package/lib/types/store-ledger.js +117 -0
- package/lib/types/subagent/channel.d.ts +4 -0
- package/lib/types/subagent/channel.js +131 -0
- package/lib/types/subagent/index.d.ts +4 -0
- package/lib/types/subagent/index.js +5 -0
- package/lib/types/subagent/readiness.d.ts +6 -0
- package/lib/types/subagent/readiness.js +35 -0
- package/lib/types/subagent/types.d.ts +58 -0
- package/lib/types/subagent/types.js +2 -0
- package/lib/types/threats.js +338 -0
- package/lib/types/tool-dispatch.d.ts +5 -0
- package/lib/types/tool-dispatch.js +535 -0
- package/lib/types/tree-write.d.ts +53 -0
- package/lib/types/tree-write.js +40 -0
- package/lib/types/unit-members.js +121 -0
- package/lib/types/usage.js +474 -0
- package/lib/types/write-authority.d.ts +87 -0
- package/lib/types/write-authority.js +23 -0
- package/lib/types/write-gates.d.ts +111 -0
- package/lib/types/write-gates.js +344 -0
- package/lib/types/write-inventory.js +158 -0
- 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
|
|
2
|
+
* The Host data-plane contract + Fetch-shaped helpers every family route surface shares (W1/W6).
|
|
3
3
|
*
|
|
4
|
-
* WHY THIS IS IN CORE:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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 {
|
|
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
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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
|
|
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
|
-
*
|
|
70
|
+
* Register the family host surface on the platform's exact-route carrier.
|
|
42
71
|
*
|
|
43
|
-
* The
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
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
|
|
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
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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
|
-
* `
|
|
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
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
* @module @lmzhen/dsh-evolution-core
|
|
33
33
|
*/
|
|
34
34
|
export * from './citations.ts';
|
|
35
|
+
export * from './tree-write.ts';
|
|
36
|
+
export * from './package-identity.ts';
|
|
37
|
+
export * from './write-authority.ts';
|
|
38
|
+
export * from './write-gates.ts';
|
|
35
39
|
export * from './reference-rewrite.ts';
|
|
36
40
|
export * from './cost.ts';
|
|
37
41
|
export * from './skill-lint.ts';
|
|
@@ -49,7 +53,6 @@ export * from './io.ts';
|
|
|
49
53
|
export * from './learn-prompt.ts';
|
|
50
54
|
export * from './memory-store.ts';
|
|
51
55
|
export * from './mutations.ts';
|
|
52
|
-
export * from './preset-composition.ts';
|
|
53
56
|
export * from './prompts.ts';
|
|
54
57
|
export * from './quality.ts';
|
|
55
58
|
export * from './redact.ts';
|
|
@@ -74,6 +77,8 @@ export * from './drift-signals.ts';
|
|
|
74
77
|
export * from './skill-history.ts';
|
|
75
78
|
export * from './unit-members.ts';
|
|
76
79
|
export * from './skill-store.ts';
|
|
80
|
+
export * from './store-ledger.ts';
|
|
81
|
+
export * from './reachability.ts';
|
|
77
82
|
export * from './state-store.ts';
|
|
78
83
|
export * from './threats.ts';
|
|
79
84
|
export * from './tool-dispatch.ts';
|
|
@@ -86,4 +91,10 @@ export * from './numeric.ts';
|
|
|
86
91
|
export * from './opt-in.ts';
|
|
87
92
|
export * from './payload.ts';
|
|
88
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';
|
|
89
100
|
//# sourceMappingURL=index.d.ts.map
|