@aikdna/kdna-core 0.7.2 → 0.9.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.
@@ -0,0 +1,254 @@
1
+ /**
2
+ * @aikdna/kdna-core — Work Pack validation (pure logic)
3
+ *
4
+ * Zero-dependency pure functions for validating KDNA Work Pack
5
+ * manifests against the Work Pack schema. Does not depend on ajv
6
+ * at source level — the validator is injected.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ // ── Embedded Work Pack Schema v0.1 ──────────────────────────────────
13
+ // Self-contained schema with resolved $refs for zero-dependency validation.
14
+
15
+ const WORK_PACK_SCHEMA = {
16
+ $schema: 'http://json-schema.org/draft-07/schema#',
17
+ $id: 'https://aikdna.com/schemas/work-pack.schema.json',
18
+ title: 'KDNA Work Pack Manifest',
19
+ type: 'object',
20
+ required: ['format', 'format_version', 'name', 'version', 'description', 'status', 'kdna'],
21
+ properties: {
22
+ format: { type: 'string', const: 'kdna-workpack' },
23
+ format_version: { type: 'string', pattern: '^\\d+\\.\\d+$' },
24
+ name: { type: 'string', pattern: '^[a-z0-9]+(-[a-z0-9]+)*$', maxLength: 64 },
25
+ version: { type: 'string', pattern: '^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.]+)?(\\+[a-zA-Z0-9.]+)?$' },
26
+ description: { type: 'string', maxLength: 280 },
27
+ status: { type: 'string', enum: ['draft', 'experimental', 'stable', 'deprecated'] },
28
+ access: { type: 'string', enum: ['open', 'licensed', 'runtime', 'enterprise', 'partner'], default: 'open' },
29
+ license: { type: 'string', default: 'Apache-2.0' },
30
+ kdna: {
31
+ type: 'object',
32
+ oneOf: [
33
+ {
34
+ required: ['mode', 'asset'],
35
+ properties: {
36
+ mode: { const: 'single' },
37
+ asset: {
38
+ type: 'object',
39
+ required: ['name', 'version', 'role'],
40
+ properties: {
41
+ name: { type: 'string', pattern: '^[a-z0-9_]+$' },
42
+ version: { type: 'string' },
43
+ digest: { type: 'string', pattern: '^sha256:[a-f0-9]{64}$' },
44
+ role: { type: 'string', enum: ['primary', 'constraint', 'fallback'] },
45
+ },
46
+ additionalProperties: false,
47
+ },
48
+ },
49
+ },
50
+ {
51
+ required: ['mode', 'assets'],
52
+ properties: {
53
+ mode: { const: 'cluster' },
54
+ assets: {
55
+ type: 'array',
56
+ minItems: 2,
57
+ items: {
58
+ type: 'object',
59
+ required: ['name', 'version', 'role'],
60
+ properties: {
61
+ name: { type: 'string', pattern: '^[a-z0-9_]+$' },
62
+ version: { type: 'string' },
63
+ digest: { type: 'string', pattern: '^sha256:[a-f0-9]{64}$' },
64
+ role: { type: 'string', enum: ['primary', 'constraint', 'fallback'] },
65
+ },
66
+ additionalProperties: false,
67
+ },
68
+ },
69
+ },
70
+ },
71
+ ],
72
+ },
73
+ skills: {
74
+ type: 'array',
75
+ default: [],
76
+ items: {
77
+ type: 'object',
78
+ required: ['name'],
79
+ properties: {
80
+ name: { type: 'string', pattern: '^[a-z0-9]+([_-][a-z0-9]+)*$', maxLength: 64 },
81
+ type: { type: 'string' },
82
+ required: { type: 'boolean', default: true },
83
+ mcp_server: { type: ['string', 'null'], default: null },
84
+ fallback: { type: ['string', 'null'], default: null },
85
+ },
86
+ additionalProperties: false,
87
+ },
88
+ },
89
+ templates: {
90
+ type: 'object',
91
+ default: {},
92
+ properties: {
93
+ task: { type: 'string' },
94
+ output: { type: 'string' },
95
+ },
96
+ },
97
+ review_gates: {
98
+ type: 'array',
99
+ default: [],
100
+ items: { type: 'string' },
101
+ },
102
+ risk_policy: { type: 'string' },
103
+ trace_policy: { type: 'string' },
104
+ evals: { type: 'string' },
105
+ },
106
+ additionalProperties: false,
107
+ };
108
+
109
+ // ── Public API ─────────────────────────────────────────────────────
110
+
111
+ /**
112
+ * Validate a Work Pack manifest against the schema.
113
+ *
114
+ * @param {object} manifest — parsed workpack.json content
115
+ * @param {object} [opts]
116
+ * @param {object} [opts.ajv] — optional pre-configured Ajv instance
117
+ * @returns {{ valid: boolean, errors: string[] }}
118
+ */
119
+ function validateWorkPackManifest(manifest, opts = {}) {
120
+ const Ajv = opts.ajv || _lazyAjv();
121
+ let validate;
122
+ try {
123
+ validate = Ajv.compile(WORK_PACK_SCHEMA);
124
+ } catch (e) {
125
+ return { valid: false, errors: [`Schema compilation error: ${e.message}`] };
126
+ }
127
+ const ok = validate(manifest);
128
+ if (ok) return { valid: true, errors: [] };
129
+ const errors = (validate.errors || []).map(
130
+ (e) => `${e.instancePath || '/'}: ${e.message}`
131
+ );
132
+ return { valid: false, errors };
133
+ }
134
+
135
+ /**
136
+ * Check structural completeness — verify all referenced files exist.
137
+ *
138
+ * @param {object} manifest — parsed workpack.json
139
+ * @param {string} rootDir — directory containing workpack.json
140
+ * @returns {{ complete: boolean, missing: string[] }}
141
+ */
142
+ function checkWorkPackStructure(manifest, rootDir) {
143
+ const missing = [];
144
+ const refs = [];
145
+
146
+ if (manifest.templates) {
147
+ if (manifest.templates.task) refs.push(manifest.templates.task);
148
+ if (manifest.templates.output) refs.push(manifest.templates.output);
149
+ }
150
+ if (manifest.review_gates) refs.push(...manifest.review_gates);
151
+ if (manifest.risk_policy) refs.push(manifest.risk_policy);
152
+ if (manifest.trace_policy) refs.push(manifest.trace_policy);
153
+ if (manifest.evals) refs.push(manifest.evals);
154
+
155
+ for (const ref of refs) {
156
+ const fullPath = path.resolve(rootDir, ref);
157
+ if (!fs.existsSync(fullPath)) missing.push(ref);
158
+ }
159
+
160
+ return { complete: missing.length === 0, missing };
161
+ }
162
+
163
+ /**
164
+ * Inspect a Work Pack — return a structured summary.
165
+ *
166
+ * @param {object} manifest — parsed workpack.json
167
+ * @param {string} rootDir — directory containing workpack.json
168
+ * @returns {object}
169
+ */
170
+ function inspectWorkPack(manifest, rootDir) {
171
+ const kdnaMode = manifest.kdna?.mode || 'unknown';
172
+ const kdnaCount =
173
+ kdnaMode === 'single' ? 1 : (manifest.kdna?.assets || []).length;
174
+
175
+ const structure = checkWorkPackStructure(manifest, rootDir);
176
+
177
+ return {
178
+ name: manifest.name,
179
+ version: manifest.version,
180
+ description: manifest.description,
181
+ status: manifest.status,
182
+ access: manifest.access || 'open',
183
+ license: manifest.license || 'Apache-2.0',
184
+ format_version: manifest.format_version,
185
+ kdna: {
186
+ mode: kdnaMode,
187
+ assets: kdnaMode === 'single'
188
+ ? [{ name: manifest.kdna.asset.name, version: manifest.kdna.asset.version, role: manifest.kdna.asset.role }]
189
+ : (manifest.kdna?.assets || []).map(a => ({ name: a.name, version: a.version, role: a.role })),
190
+ },
191
+ skills: (manifest.skills || []).map(s => ({
192
+ name: s.name,
193
+ type: s.type || 'unspecified',
194
+ required: s.required !== false,
195
+ fallback: s.fallback || null,
196
+ })),
197
+ templates: manifest.templates
198
+ ? { task: manifest.templates.task || null, output: manifest.templates.output || null }
199
+ : null,
200
+ review_gates: (manifest.review_gates || []).length,
201
+ has_risk_policy: !!manifest.risk_policy,
202
+ has_trace_policy: !!manifest.trace_policy,
203
+ has_evals: !!manifest.evals,
204
+ structural_complete: structure.complete,
205
+ missing_files: structure.missing,
206
+ };
207
+ }
208
+
209
+ /**
210
+ * Load a Work Pack from a directory.
211
+ *
212
+ * @param {string} dirPath — path to Work Pack directory
213
+ * @returns {{ manifest: object|null, error: string|null }}
214
+ */
215
+ function loadWorkPack(dirPath) {
216
+ const wpPath = path.join(dirPath, 'workpack.json');
217
+ if (!fs.existsSync(wpPath)) {
218
+ return { manifest: null, error: `workpack.json not found in ${dirPath}` };
219
+ }
220
+ let manifest;
221
+ try {
222
+ manifest = JSON.parse(fs.readFileSync(wpPath, 'utf8'));
223
+ } catch (e) {
224
+ return { manifest: null, error: `Invalid JSON in workpack.json: ${e.message}` };
225
+ }
226
+ return { manifest, error: null };
227
+ }
228
+
229
+ // ── Internal ────────────────────────────────────────────────────────
230
+
231
+ let _ajvInstance = null;
232
+
233
+ function _lazyAjv() {
234
+ if (_ajvInstance) return _ajvInstance;
235
+ try {
236
+ const Ajv = require('ajv');
237
+ const addFormats = require('ajv-formats');
238
+ _ajvInstance = new Ajv({ allErrors: true, strict: false });
239
+ addFormats(_ajvInstance);
240
+ return _ajvInstance;
241
+ } catch (e) {
242
+ throw new Error(
243
+ 'ajv is required for Work Pack validation. Install: npm install ajv ajv-formats'
244
+ );
245
+ }
246
+ }
247
+
248
+ module.exports = {
249
+ WORK_PACK_SCHEMA,
250
+ validateWorkPackManifest,
251
+ checkWorkPackStructure,
252
+ inspectWorkPack,
253
+ loadWorkPack,
254
+ };
@@ -1,130 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "title": "KDNA Composition Policy",
4
- "description": "Schema for composition.policy.json — defines runtime composition behavior for domain clusters.",
5
- "type": "object",
6
- "required": ["policy_id", "version"],
7
- "properties": {
8
- "policy_id": {
9
- "type": "string",
10
- "description": "Unique policy identifier"
11
- },
12
- "version": {
13
- "type": "string",
14
- "description": "Semantic version of this policy"
15
- },
16
- "selection_policy": {
17
- "type": "object",
18
- "properties": {
19
- "mode": {
20
- "type": "string",
21
- "enum": ["signal_based", "fixed", "staged", "overlay", "user_confirmed"]
22
- },
23
- "max_domains": { "type": "integer", "minimum": 1 },
24
- "min_signal_score": { "type": "number", "minimum": 0, "maximum": 1 },
25
- "ask_user_when_ambiguous": { "type": "boolean" }
26
- }
27
- },
28
- "priority_policy": {
29
- "type": "array",
30
- "items": {
31
- "type": "object",
32
- "required": ["when", "domain", "priority", "effect"],
33
- "properties": {
34
- "when": { "type": "string", "description": "Condition expression triggering this rule" },
35
- "domain": { "type": "string", "description": "Domain ID" },
36
- "priority": { "type": "integer", "minimum": 0, "maximum": 100 },
37
- "effect": {
38
- "type": "string",
39
- "enum": ["must_include", "block_or_require_confirmation", "deprioritize", "override"]
40
- }
41
- }
42
- }
43
- },
44
- "conflict_policy": {
45
- "type": "object",
46
- "properties": {
47
- "default": {
48
- "type": "string",
49
- "enum": ["surface", "priority_wins", "risk_wins", "block", "ask_user", "allow_with_warning"]
50
- },
51
- "rules": {
52
- "type": "array",
53
- "items": {
54
- "type": "object",
55
- "required": ["conflict_type", "action"],
56
- "properties": {
57
- "conflict_type": {
58
- "type": "string",
59
- "enum": ["value_conflict", "term_conflict", "risk_conflict", "stance_conflict", "framework_conflict", "output_policy_conflict", "priority_conflict"]
60
- },
61
- "action": {
62
- "type": "string",
63
- "enum": ["surface", "surface_and_ask", "risk_wins", "priority_wins", "block", "allow_with_warning"]
64
- }
65
- }
66
- }
67
- }
68
- }
69
- },
70
- "merge_policy": {
71
- "type": "object",
72
- "properties": {
73
- "axioms": { "type": "string", "enum": ["include_all_with_source", "dedupe_by_id", "select_by_priority"] },
74
- "banned_terms": { "type": "string", "enum": ["union_with_source", "strictest_only", "priority_domain_only"] },
75
- "self_checks": { "type": "string", "enum": ["dedupe_by_semantic_similarity", "include_all_with_source", "priority_domain_only"] },
76
- "frameworks": { "type": "string", "enum": ["select_by_scenario", "include_all_with_source", "priority_domain_only"] },
77
- "stances": { "type": "string", "enum": ["include_conflicts", "priority_domain_only", "merge_with_source"] }
78
- }
79
- },
80
- "output_policy": {
81
- "type": "object",
82
- "properties": {
83
- "must_show_loaded_domains": { "type": "boolean" },
84
- "must_record_judgment_trace": { "type": "boolean" },
85
- "must_surface_conflicts": { "type": "boolean" },
86
- "must_include_uncertainty": { "type": "boolean" }
87
- }
88
- },
89
- "governance": {
90
- "type": "object",
91
- "properties": {
92
- "ownership": {
93
- "type": "object",
94
- "properties": {
95
- "owner_team": { "type": "string" },
96
- "maintainers": { "type": "array", "items": { "type": "string" } },
97
- "approvers": { "type": "array", "items": { "type": "string" } },
98
- "review_cycle": { "type": "string" }
99
- }
100
- },
101
- "lifecycle": {
102
- "type": "object",
103
- "properties": {
104
- "status": { "type": "string", "enum": ["draft", "review", "approved", "active", "deprecated", "archived"] },
105
- "effective_from": { "type": "string" },
106
- "expires_at": { "type": "string" },
107
- "review_required_before": { "type": "string" }
108
- }
109
- },
110
- "change_control": {
111
- "type": "object",
112
- "properties": {
113
- "requires_approval_for": { "type": "array", "items": { "type": "string" } },
114
- "minor_changes_allowed_for": { "type": "array", "items": { "type": "string" } },
115
- "audit_log_required": { "type": "boolean" }
116
- }
117
- },
118
- "deployment": {
119
- "type": "object",
120
- "properties": {
121
- "environments": { "type": "array", "items": { "type": "string" } },
122
- "active_version": { "type": "string" },
123
- "rollout_strategy": { "type": "string", "enum": ["manual", "canary", "all_at_once"] },
124
- "rollback_version": { "type": "string" }
125
- }
126
- }
127
- }
128
- }
129
- }
130
- }
@@ -1,77 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "title": "KDNA_Cases",
4
- "type": "object",
5
- "required": [
6
- "meta",
7
- "cases"
8
- ],
9
- "properties": {
10
- "meta": {
11
- "type": "object",
12
- "required": [
13
- "version",
14
- "domain",
15
- "created",
16
- "purpose",
17
- "load_condition"
18
- ],
19
- "properties": {
20
- "version": { "type": "string" },
21
- "domain": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" },
22
- "created": { "type": "string" },
23
- "purpose": { "type": "string" },
24
- "load_condition": { "type": "string" }
25
- },
26
- "additionalProperties": true
27
- },
28
- "cases": {
29
- "type": "array",
30
- "items": {
31
- "type": "object",
32
- "required": ["id", "title"],
33
- "properties": {
34
- "id": { "type": "string" },
35
- "scene_id": { "type": "string" },
36
- "title": { "type": "string" },
37
- "context": { "type": "string" },
38
- "what_happened": { "type": "string" },
39
- "what_was_learned": { "type": "string" },
40
- "structural_pattern": { "type": "string" },
41
- "narrative": { "type": "string" },
42
- "outcome": { "type": "string" },
43
- "kdna_analysis": { "type": "string" },
44
- "modern_parallel": { "type": "string" },
45
- "axiom": { "type": "string" },
46
- "judgment_path": {
47
- "type": "string",
48
- "description": "The judgment process applied in this case."
49
- },
50
- "good_response": {
51
- "type": "string",
52
- "description": "An example of a good judgment response."
53
- },
54
- "bad_response": {
55
- "type": "string",
56
- "description": "An example of a poor judgment response."
57
- },
58
- "why_good": {
59
- "type": "string",
60
- "description": "Why the good response aligns with domain judgment."
61
- },
62
- "why_bad": {
63
- "type": "string",
64
- "description": "Why the bad response violates domain judgment."
65
- },
66
- "triggered_axioms": {
67
- "type": "array",
68
- "items": { "type": "string" },
69
- "description": "Axioms triggered by this case."
70
- }
71
- },
72
- "additionalProperties": true
73
- }
74
- }
75
- },
76
- "additionalProperties": true
77
- }
@@ -1,132 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "title": "KDNA Cluster Manifest",
4
- "description": "Schema for kdna.cluster.json — defines a composable judgment system of KDNA domains.",
5
- "type": "object",
6
- "required": ["cluster_id", "name", "version", "domains", "composition"],
7
- "properties": {
8
- "cluster_id": {
9
- "type": "string",
10
- "description": "Fully qualified cluster identifier, e.g., @company/product-launch-system"
11
- },
12
- "name": {
13
- "type": "string",
14
- "description": "Human-readable cluster name"
15
- },
16
- "version": {
17
- "type": "string",
18
- "description": "Semantic version of this cluster manifest"
19
- },
20
- "description": {
21
- "type": "string",
22
- "description": "What judgment system this cluster provides"
23
- },
24
- "type": {
25
- "type": "string",
26
- "enum": ["horizontal", "vertical", "governance", "enterprise_system"],
27
- "description": "Cluster type determining default loading strategy"
28
- },
29
- "status": {
30
- "type": "string",
31
- "enum": ["draft", "experimental", "stable", "deprecated"]
32
- },
33
- "access": {
34
- "type": "string",
35
- "enum": ["open", "licensed", "runtime"]
36
- },
37
- "domains": {
38
- "type": "array",
39
- "items": {
40
- "type": "object",
41
- "required": ["id", "version", "role", "required", "load_condition"],
42
- "properties": {
43
- "id": {
44
- "type": "string",
45
- "description": "Fully qualified domain name (@scope/name)"
46
- },
47
- "version": {
48
- "type": "string",
49
- "description": "Semver range, e.g., ^1.2.0"
50
- },
51
- "role": {
52
- "type": "string",
53
- "enum": ["primary", "advisor", "risk_guard", "style_and_trust", "evaluator"]
54
- },
55
- "required": {
56
- "type": "boolean",
57
- "description": "Whether this domain must load for cluster to function"
58
- },
59
- "load_condition": {
60
- "type": "string",
61
- "description": "When this domain should activate"
62
- }
63
- }
64
- }
65
- },
66
- "relationships": {
67
- "type": "array",
68
- "items": {
69
- "type": "object",
70
- "required": ["from", "to", "type"],
71
- "properties": {
72
- "from": { "type": "string" },
73
- "to": { "type": "string" },
74
- "type": {
75
- "type": "string",
76
- "enum": ["depends_on", "constrains", "overrides", "blocks", "informs", "extends", "refines", "conflicts_with", "evaluates", "expressed_through"]
77
- },
78
- "description": { "type": "string" }
79
- }
80
- }
81
- },
82
- "composition": {
83
- "type": "object",
84
- "required": ["strategy", "conflict_policy", "priority_order"],
85
- "properties": {
86
- "strategy": {
87
- "type": "string",
88
- "enum": ["fixed", "signal_based", "staged", "overlay", "user_confirmed"]
89
- },
90
- "max_active_domains": {
91
- "type": "integer",
92
- "minimum": 1
93
- },
94
- "default_domains": {
95
- "type": "array",
96
- "items": { "type": "string" }
97
- },
98
- "conflict_policy": {
99
- "type": "string",
100
- "enum": ["surface", "priority", "block", "ask_user"]
101
- },
102
- "priority_order": {
103
- "type": "array",
104
- "items": { "type": "string" },
105
- "description": "Ordered list of domain names, highest priority first"
106
- }
107
- }
108
- },
109
- "token_budget": {
110
- "type": "object",
111
- "properties": {
112
- "max_context_tokens": { "type": "integer" },
113
- "domain_summary_mode": {
114
- "type": "string",
115
- "enum": ["full", "compact", "axioms_only", "scenario_only"]
116
- },
117
- "compression_strategy": { "type": "string" }
118
- }
119
- },
120
- "evaluation": {
121
- "type": "object",
122
- "properties": {
123
- "eval_set": { "type": "string" },
124
- "minimum_pass_rate": { "type": "number", "minimum": 0, "maximum": 1 },
125
- "required_dimensions": {
126
- "type": "array",
127
- "items": { "type": "string" }
128
- }
129
- }
130
- }
131
- }
132
- }