@lmzhen/dsh-evolution-plan-validator 0.1.0-rc.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.
- package/README.md +24 -0
- package/lib/index.js +123 -0
- package/lib/invariant.js +8 -0
- package/lib/types/index.d.ts +49 -0
- package/lib/types/invariant.d.ts +5 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-evolution-plan-validator
|
|
2
|
+
|
|
3
|
+
Deterministic validator for model-produced evolution plans
|
|
4
|
+
|
|
5
|
+
## Model Experience
|
|
6
|
+
|
|
7
|
+
### Indirect model surface
|
|
8
|
+
|
|
9
|
+
#### What the model sees
|
|
10
|
+
|
|
11
|
+
`@deepseek-ai/dsh-evolution-plan-validator` registers no direct prompt or tool schema itself. Model-visible effects are owned by the packages that consume this service.
|
|
12
|
+
|
|
13
|
+
#### Token effect
|
|
14
|
+
|
|
15
|
+
Zero direct token effect from this package; consumers add any model-visible tokens.
|
|
16
|
+
|
|
17
|
+
#### KV Cache effect
|
|
18
|
+
|
|
19
|
+
Independent of request-prefix construction. This package does not alter the assembled prompt or tool list.
|
|
20
|
+
|
|
21
|
+
## Known Limitations and Deferred Work
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
- No known durable consumer gaps at this time. Runtime contracts are covered by package and boundary tests.
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//#region lib/types/index.js
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic validator for model-produced evolution plans.
|
|
4
|
+
* The validator never calls the model and never mutates state.
|
|
5
|
+
* @module @lmzhen/dsh-evolution-plan-validator
|
|
6
|
+
*/
|
|
7
|
+
const MEMORY_ACTIONS = new Set([
|
|
8
|
+
"add",
|
|
9
|
+
"replace",
|
|
10
|
+
"remove"
|
|
11
|
+
]);
|
|
12
|
+
const SKILL_ACTIONS = new Set([
|
|
13
|
+
"create",
|
|
14
|
+
"edit",
|
|
15
|
+
"update",
|
|
16
|
+
"patch",
|
|
17
|
+
"delete",
|
|
18
|
+
"write_file",
|
|
19
|
+
"remove_file"
|
|
20
|
+
]);
|
|
21
|
+
const FORBIDDEN_KEYS = [
|
|
22
|
+
"policy",
|
|
23
|
+
"threshold",
|
|
24
|
+
"prompt_hash",
|
|
25
|
+
"model_route",
|
|
26
|
+
"evolution_config"
|
|
27
|
+
];
|
|
28
|
+
function hasValidEvidence(evidence, sessionSeq) {
|
|
29
|
+
if (!Array.isArray(evidence) || evidence.length === 0) return false;
|
|
30
|
+
return evidence.every((item) => {
|
|
31
|
+
if (!item || typeof item !== "object") return false;
|
|
32
|
+
const record = item;
|
|
33
|
+
const seq = typeof record.event_seq === "number" ? record.event_seq : typeof record.seq === "number" ? record.seq : Number(record.event_seq);
|
|
34
|
+
return Number.isInteger(seq) && seq >= 0 && seq <= sessionSeq;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function validateEvolutionPlan(plan, context) {
|
|
38
|
+
const maxOps = context.maxOpsPerPlan ?? 32;
|
|
39
|
+
const rejected = [];
|
|
40
|
+
const memoryOps = [];
|
|
41
|
+
const skillOps = [];
|
|
42
|
+
const accepted = {
|
|
43
|
+
memoryOps,
|
|
44
|
+
skillOps,
|
|
45
|
+
...plan.summary === void 0 ? {} : { summary: plan.summary }
|
|
46
|
+
};
|
|
47
|
+
const allOps = (plan.memoryOps?.length ?? 0) + (plan.skillOps?.length ?? 0);
|
|
48
|
+
if (allOps === 0) {
|
|
49
|
+
rejected.push({
|
|
50
|
+
index: 0,
|
|
51
|
+
kind: "memory",
|
|
52
|
+
reason: "plan contains no operations"
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
accepted,
|
|
56
|
+
rejected,
|
|
57
|
+
ok: false
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (allOps > maxOps) {
|
|
61
|
+
rejected.push({
|
|
62
|
+
index: 0,
|
|
63
|
+
kind: "memory",
|
|
64
|
+
reason: `plan exceeds maxOpsPerPlan ${maxOps}`
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
accepted,
|
|
68
|
+
rejected,
|
|
69
|
+
ok: false
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
for (const [index, op] of (plan.memoryOps ?? []).entries()) {
|
|
73
|
+
const reason = validateMemoryOp(op, context, index);
|
|
74
|
+
if (reason) rejected.push({
|
|
75
|
+
index,
|
|
76
|
+
kind: "memory",
|
|
77
|
+
reason
|
|
78
|
+
});
|
|
79
|
+
else memoryOps.push(op);
|
|
80
|
+
}
|
|
81
|
+
for (const [index, op] of (plan.skillOps ?? []).entries()) {
|
|
82
|
+
const reason = validateSkillOp(op, context, index);
|
|
83
|
+
if (reason) rejected.push({
|
|
84
|
+
index,
|
|
85
|
+
kind: "skill",
|
|
86
|
+
reason
|
|
87
|
+
});
|
|
88
|
+
else skillOps.push(op);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
accepted,
|
|
92
|
+
rejected,
|
|
93
|
+
ok: rejected.length === 0 && memoryOps.length + skillOps.length > 0
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function validateMemoryOp(op, context, index) {
|
|
97
|
+
if (!hasValidEvidence(op.evidence, context.sessionSeq)) return `memory op ${index}: evidence is required and must reference a valid session seq`;
|
|
98
|
+
for (const key of FORBIDDEN_KEYS) if (key in op) return `memory op ${index}: forbidden field ${key}`;
|
|
99
|
+
if (op.target !== "memory" && op.target !== "user") return `memory op ${index}: target must be memory or user`;
|
|
100
|
+
const action = op.action ?? "add";
|
|
101
|
+
if (!MEMORY_ACTIONS.has(action)) return `memory op ${index}: unknown action ${action}`;
|
|
102
|
+
const text = (op.facts ?? op.content ?? "").trim();
|
|
103
|
+
if (action !== "remove" && text.length === 0) return `memory op ${index}: ${action} requires facts/content`;
|
|
104
|
+
if (action !== "add" && !(op.old_text ?? "").trim()) return `memory op ${index}: ${action} requires old_text`;
|
|
105
|
+
const budget = op.target === "user" ? context.maxUserChars ?? 1375 : context.maxMemoryChars ?? 2200;
|
|
106
|
+
if (text.length > budget) return `memory op ${index}: content exceeds ${op.target === "user" ? "user" : "memory"} budget`;
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
function validateSkillOp(op, context, index) {
|
|
110
|
+
for (const key of FORBIDDEN_KEYS) if (key in op) return `skill op ${index}: forbidden field ${key}`;
|
|
111
|
+
const name = (op.name ?? "").trim();
|
|
112
|
+
if (!name) return `skill op ${index}: name is required`;
|
|
113
|
+
if (context.protectedSkillNames?.has(name)) return `skill op ${index}: skill "${name}" is protected`;
|
|
114
|
+
if (!hasValidEvidence(op.evidence, context.sessionSeq)) return `skill op ${index}: evidence is required and must reference a valid session seq`;
|
|
115
|
+
const action = op.action ?? "patch";
|
|
116
|
+
if (!SKILL_ACTIONS.has(action)) return `skill op ${index}: unknown action ${action}`;
|
|
117
|
+
if ((action === "create" || action === "edit" || action === "update") && !(op.content ?? "").trim()) return `skill op ${index}: ${action} requires content`;
|
|
118
|
+
if (action === "patch" && !(op.old_string ?? "")) return `skill op ${index}: patch requires old_string`;
|
|
119
|
+
if ((op.content ?? "").length > (context.maxSkillContentChars ?? 1e5)) return `skill op ${index}: content exceeds skill budget`;
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
export { validateEvolutionPlan };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-evolution-plan-validator";
|
|
3
|
+
const name = "evolution-plan-validator-invariant";
|
|
4
|
+
const inject = ["invariants"];
|
|
5
|
+
const install = () => {};
|
|
6
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
7
|
+
//#endregion
|
|
8
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic validator for model-produced evolution plans.
|
|
3
|
+
* The validator never calls the model and never mutates state.
|
|
4
|
+
* @module @deepseek-ai/dsh-evolution-plan-validator
|
|
5
|
+
*/
|
|
6
|
+
export interface MemoryOp {
|
|
7
|
+
target?: string;
|
|
8
|
+
action?: string;
|
|
9
|
+
facts?: string;
|
|
10
|
+
content?: string;
|
|
11
|
+
old_text?: string;
|
|
12
|
+
evidence?: unknown[];
|
|
13
|
+
}
|
|
14
|
+
export interface SkillOp {
|
|
15
|
+
action?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
content?: string;
|
|
18
|
+
old_string?: string;
|
|
19
|
+
new_string?: string;
|
|
20
|
+
file_path?: string;
|
|
21
|
+
absorbed_into?: string;
|
|
22
|
+
evidence?: unknown[];
|
|
23
|
+
}
|
|
24
|
+
export interface EvolutionPlan {
|
|
25
|
+
memoryOps?: MemoryOp[];
|
|
26
|
+
skillOps?: SkillOp[];
|
|
27
|
+
summary?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ValidationContext {
|
|
30
|
+
/** Upper bound for the latest valid session seq. */
|
|
31
|
+
sessionSeq: number;
|
|
32
|
+
maxOpsPerPlan?: number;
|
|
33
|
+
protectedSkillNames?: ReadonlySet<string>;
|
|
34
|
+
maxMemoryChars?: number;
|
|
35
|
+
maxUserChars?: number;
|
|
36
|
+
maxSkillContentChars?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface RejectedOp {
|
|
39
|
+
index: number;
|
|
40
|
+
kind: 'memory' | 'skill';
|
|
41
|
+
reason: string;
|
|
42
|
+
}
|
|
43
|
+
export interface ValidationResult {
|
|
44
|
+
accepted: EvolutionPlan;
|
|
45
|
+
rejected: RejectedOp[];
|
|
46
|
+
ok: boolean;
|
|
47
|
+
}
|
|
48
|
+
export declare function validateEvolutionPlan(plan: EvolutionPlan, context: ValidationContext): ValidationResult;
|
|
49
|
+
//# sourceMappingURL=index.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lmzhen/dsh-evolution-plan-validator",
|
|
3
|
+
"description": "Deterministic validator for model-produced evolution plans (community build)",
|
|
4
|
+
"version": "0.1.0-rc.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/lmzhen/dsh-evolution.git",
|
|
11
|
+
"directory": "packages/dsh-evolution-plan-validator"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"lib/index.js",
|
|
29
|
+
"lib/invariant.js",
|
|
30
|
+
"lib/types/**/*.d.ts",
|
|
31
|
+
"lib/types/invariant.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
|
|
39
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.6"
|
|
43
|
+
}
|
|
44
|
+
}
|