@aikdna/kdna-core 0.15.9 → 0.15.11
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/CHANGELOG.md +32 -0
- package/package.json +65 -65
- package/src/asset-reader.js +34 -20
- package/src/container-dispatcher.js +2 -2
- package/src/crypto-profile.js +0 -1
- package/src/public-api.js +1 -1
- package/src/v1/index.js +47 -19
- package/src/validate-pure.js +4 -1
- package/src/workpack-pure.js +2 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.15.11 (2026-07-03)
|
|
4
|
+
|
|
5
|
+
**Fix**: repair Core v1 container dispatcher module resolution in the
|
|
6
|
+
published package layout.
|
|
7
|
+
|
|
8
|
+
- Corrects internal `src/container-dispatcher.js` imports so the dispatcher can
|
|
9
|
+
load sibling `asset-reader` and `v1` modules after npm packaging.
|
|
10
|
+
- Loads v1 JSON schemas through package-relative static imports before falling
|
|
11
|
+
back to disk discovery, so bundled server environments do not look for
|
|
12
|
+
KDNA schemas in the host application directory.
|
|
13
|
+
- Adds regression coverage for loading a v1 source asset through the dispatcher
|
|
14
|
+
from the package `src/` layout and for validating from a non-repository cwd.
|
|
15
|
+
- Fix webpack bundler warning: use `eval('require')` for ajv meta-schema import
|
|
16
|
+
so webpack does not try to statically resolve the JSON file at build time.
|
|
17
|
+
|
|
18
|
+
No public API changes.
|
|
19
|
+
|
|
20
|
+
## v0.15.10 (2026-06-30)
|
|
21
|
+
|
|
22
|
+
**API**: export `parseSemver`, `compareSemver`, `satisfies` from public module.
|
|
23
|
+
|
|
24
|
+
These semver utilities were already used internally by kdna-core (Story 6 dependency
|
|
25
|
+
resolution) but were not part of the public API. Exporting them allows consumers
|
|
26
|
+
(e.g. `@aikdna/kdna-cli`) to use the canonical implementation rather than maintaining
|
|
27
|
+
a parallel copy.
|
|
28
|
+
|
|
29
|
+
- **`parseSemver(v)`** — parse a semver string into `{major, minor, patch}` or `null`
|
|
30
|
+
- **`compareSemver(a, b)`** — compare two version strings, returns `< 0 | 0 | > 0`
|
|
31
|
+
- **`satisfies(version, range)`** — test a version against a range (`^`, `~`, `>=`, etc.)
|
|
32
|
+
|
|
33
|
+
No breaking changes. Purely additive.
|
|
34
|
+
|
|
3
35
|
## v0.15.9 (2026-06-28)
|
|
4
36
|
|
|
5
37
|
Story 12 — Asset inheritance (`extends` field, RFC #148 v2.x Phase 3).
|
package/package.json
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
},
|
|
8
|
-
"type": "commonjs",
|
|
9
|
-
"main": "src/index.js",
|
|
10
|
-
"module": "src/index.mjs",
|
|
11
|
-
"exports": {
|
|
12
|
-
".": {
|
|
13
|
-
"import": "./src/index.mjs",
|
|
14
|
-
"require": "./src/index.js",
|
|
15
|
-
"types": "./src/types.d.ts"
|
|
2
|
+
"name": "@aikdna/kdna-core",
|
|
3
|
+
"version": "0.15.11",
|
|
4
|
+
"description": "KDNA Core library for validating, planning, and loading local .kdna judgment assets.",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=18.0.0"
|
|
16
7
|
},
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
8
|
+
"type": "commonjs",
|
|
9
|
+
"main": "src/index.js",
|
|
10
|
+
"module": "src/index.mjs",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./src/index.mjs",
|
|
14
|
+
"require": "./src/index.js",
|
|
15
|
+
"types": "./src/types.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json",
|
|
18
|
+
"./schema/*": "./schema/*",
|
|
19
|
+
"./v1": {
|
|
20
|
+
"import": "./src/v1/index.mjs",
|
|
21
|
+
"require": "./src/v1/index.js",
|
|
22
|
+
"types": "./src/types.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"types": "src/types.d.ts",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "node ../../scripts/run-node-tests.js test/*.test.js && node -e \"const m = require('./src/index.js'); console.log('kdna-core exports:', Object.keys(m).join(', '));\"",
|
|
28
|
+
"prepublishOnly": "npm run release:check",
|
|
29
|
+
"release:check": "node scripts/check-release-readiness.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"src/",
|
|
33
|
+
"schema/",
|
|
34
|
+
"README.md",
|
|
35
|
+
"CHANGELOG.md",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"NOTICE"
|
|
38
|
+
],
|
|
39
|
+
"keywords": [
|
|
40
|
+
"kdna",
|
|
41
|
+
"kdna-core",
|
|
42
|
+
"ai-agent",
|
|
43
|
+
"judgment-assets"
|
|
44
|
+
],
|
|
45
|
+
"license": "Apache-2.0",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/aikdna/kdna.git",
|
|
49
|
+
"directory": "packages/kdna-core"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://aikdna.com",
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"ajv": "^8.17.1",
|
|
54
|
+
"ajv-formats": "^3.0.1"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"ajv": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"ajv-formats": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
59
63
|
},
|
|
60
|
-
"
|
|
61
|
-
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@noble/hashes": "^1.8.0",
|
|
66
|
+
"cbor-x": "^1.6.4",
|
|
67
|
+
"ajv": "^8.20.0",
|
|
68
|
+
"ajv-formats": "^3.0.1"
|
|
62
69
|
}
|
|
63
|
-
},
|
|
64
|
-
"dependencies": {
|
|
65
|
-
"@noble/hashes": "^1.8.0",
|
|
66
|
-
"cbor-x": "^1.6.4",
|
|
67
|
-
"ajv": "^8.20.0",
|
|
68
|
-
"ajv-formats": "^3.0.1"
|
|
69
|
-
}
|
|
70
70
|
}
|
package/src/asset-reader.js
CHANGED
|
@@ -47,7 +47,7 @@ function parseJson(buf, entryName) {
|
|
|
47
47
|
try {
|
|
48
48
|
return JSON.parse(Buffer.isBuffer(buf) ? buf.toString('utf8') : String(buf));
|
|
49
49
|
} catch (e) {
|
|
50
|
-
throw new Error(`${entryName}: invalid JSON: ${e.message}
|
|
50
|
+
throw new Error(`${entryName}: invalid JSON: ${e.message}`, { cause: e });
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -311,28 +311,42 @@ function verifyHumanLockSignatures(coreData, manifest, errors, warnings) {
|
|
|
311
311
|
// Skip if no public key available (unsigned assets are valid)
|
|
312
312
|
return;
|
|
313
313
|
}
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
|
|
315
|
+
// All card-type arrays in KDNA_Core.json that may carry human_lock.
|
|
316
|
+
// Ordered by how they appear in the compiled payload (compileCore in
|
|
317
|
+
// kdna-studio-core/src/compile/index.js).
|
|
318
|
+
const CORE_CARD_ARRAYS = [
|
|
319
|
+
['axioms', 'axiom'],
|
|
320
|
+
['ontology', 'ontology'],
|
|
321
|
+
['frameworks', 'framework'],
|
|
322
|
+
['boundaries', 'boundary'],
|
|
323
|
+
['risks', 'risk'],
|
|
324
|
+
['stances', 'stance'],
|
|
325
|
+
];
|
|
316
326
|
|
|
317
327
|
let verified = 0, missing = 0, invalid = 0;
|
|
318
|
-
for (const
|
|
319
|
-
const
|
|
320
|
-
if (!
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
328
|
+
for (const [arrayName] of CORE_CARD_ARRAYS) {
|
|
329
|
+
const cards = coreData?.[arrayName];
|
|
330
|
+
if (!Array.isArray(cards) || cards.length === 0) continue;
|
|
331
|
+
|
|
332
|
+
for (const card of cards) {
|
|
333
|
+
const hl = card.human_lock;
|
|
334
|
+
if (!hl || !hl.signature) {
|
|
335
|
+
if (card.status === 'locked' || card.status === 'tested' || card.status === 'published') {
|
|
336
|
+
missing++;
|
|
337
|
+
}
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
const payload = [card.id, hl.statement || '', hl.judgment_fingerprint || ''].join('\n');
|
|
342
|
+
const sig = Buffer.from(String(hl.signature).replace(/^ed25519:/, ''), 'hex');
|
|
343
|
+
const key = crypto.createPublicKey(publicKeyPEM);
|
|
344
|
+
const ok = crypto.verify(null, Buffer.from(payload), key, sig);
|
|
345
|
+
if (ok) { verified++; }
|
|
346
|
+
else { invalid++; }
|
|
347
|
+
} catch {
|
|
348
|
+
invalid++;
|
|
324
349
|
}
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
try {
|
|
328
|
-
const payload = [ax.id, hl.statement || '', hl.judgment_fingerprint || ''].join('\n');
|
|
329
|
-
const sig = Buffer.from(String(hl.signature).replace(/^ed25519:/, ''), 'hex');
|
|
330
|
-
const key = crypto.createPublicKey(publicKeyPEM);
|
|
331
|
-
const ok = crypto.verify(null, Buffer.from(payload), key, sig);
|
|
332
|
-
if (ok) { verified++; }
|
|
333
|
-
else { invalid++; }
|
|
334
|
-
} catch {
|
|
335
|
-
invalid++;
|
|
336
350
|
}
|
|
337
351
|
}
|
|
338
352
|
|
|
@@ -4,7 +4,7 @@ const fs = require('node:fs');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const crypto = require('node:crypto');
|
|
6
6
|
|
|
7
|
-
const { createKdnaAssetReader } = require('
|
|
7
|
+
const { createKdnaAssetReader } = require('./asset-reader.js');
|
|
8
8
|
|
|
9
9
|
const MIMETYPE_V1 = 'application/vnd.kdna.asset';
|
|
10
10
|
const MIMETYPE_V2 = 'application/vnd.aikdna.kdna+zip';
|
|
@@ -220,7 +220,7 @@ function readSourceDirectory(absPath, _opts) {
|
|
|
220
220
|
*/
|
|
221
221
|
function readV1Container(absPath, _opts) {
|
|
222
222
|
// Delegate to the existing v1 layout reader for validation
|
|
223
|
-
const v1Layout = require('
|
|
223
|
+
const v1Layout = require('./v1/index.js').readV1Layout(absPath);
|
|
224
224
|
|
|
225
225
|
const containerBuf = fs.readFileSync(absPath);
|
|
226
226
|
const assetDigest = crypto.createHash('sha256').update(containerBuf).digest();
|
package/src/crypto-profile.js
CHANGED
|
@@ -102,7 +102,6 @@ function hkdfSha256(ikm, salt = null, info = '', length = 32) {
|
|
|
102
102
|
|
|
103
103
|
// ── AES-256-KW (RFC 3394) ─────────────────────────────────────────
|
|
104
104
|
|
|
105
|
-
const AES_BLOCK = 16;
|
|
106
105
|
const KW_IV = Buffer.from('a6a6a6a6a6a6a6a6', 'hex'); // RFC 3394 default IV
|
|
107
106
|
|
|
108
107
|
function aesWrap(key, plaintext) {
|
package/src/public-api.js
CHANGED
|
@@ -110,7 +110,7 @@ async function validateKDNA(input, options = {}) {
|
|
|
110
110
|
const reader = readerFrom(options);
|
|
111
111
|
const asset = await asAsset(input, { ...options, reader });
|
|
112
112
|
const assetResult = await reader.verify(asset, options);
|
|
113
|
-
let dataMap
|
|
113
|
+
let dataMap;
|
|
114
114
|
let lintResult = { errors: [], warnings: [] };
|
|
115
115
|
let schemaResult = { errors: [], warnings: [] };
|
|
116
116
|
let crossFileResult = { errors: [], warnings: [] };
|
package/src/v1/index.js
CHANGED
|
@@ -91,6 +91,16 @@ const FORBIDDEN_OUTPUT_TERMS = Object.freeze([
|
|
|
91
91
|
let _ajv = null;
|
|
92
92
|
let _validators = null;
|
|
93
93
|
|
|
94
|
+
function loadPackagedSchemas() {
|
|
95
|
+
return {
|
|
96
|
+
manifestSchema: require('../../schema/manifest.schema.json'),
|
|
97
|
+
payloadSchema: require('../../schema/payload-profile-v1.schema.json'),
|
|
98
|
+
bundlePayloadSchema: require('../../schema/bundle-profile-v1.schema.json'),
|
|
99
|
+
checksumsSchema: require('../../schema/checksums.schema.json'),
|
|
100
|
+
loadContractSchema: require('../../schema/load-contract.schema.json'),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
94
104
|
function getRepoRoot() {
|
|
95
105
|
// Walk up from this file to find the repo root (where schema/ lives).
|
|
96
106
|
// Works whether this module is loaded from packages/kdna/src/ or
|
|
@@ -106,6 +116,26 @@ function getRepoRoot() {
|
|
|
106
116
|
return process.cwd();
|
|
107
117
|
}
|
|
108
118
|
|
|
119
|
+
function loadSchemasFromDisk() {
|
|
120
|
+
const repoRoot = getRepoRoot();
|
|
121
|
+
const schemaDir = path.join(repoRoot, 'schema');
|
|
122
|
+
return {
|
|
123
|
+
manifestSchema: JSON.parse(fs.readFileSync(path.join(schemaDir, 'manifest.schema.json'), 'utf8')),
|
|
124
|
+
payloadSchema: JSON.parse(
|
|
125
|
+
fs.readFileSync(path.join(schemaDir, 'payload-profile-v1.schema.json'), 'utf8'),
|
|
126
|
+
),
|
|
127
|
+
bundlePayloadSchema: JSON.parse(
|
|
128
|
+
fs.readFileSync(path.join(schemaDir, 'bundle-profile-v1.schema.json'), 'utf8'),
|
|
129
|
+
),
|
|
130
|
+
checksumsSchema: JSON.parse(
|
|
131
|
+
fs.readFileSync(path.join(schemaDir, 'checksums.schema.json'), 'utf8'),
|
|
132
|
+
),
|
|
133
|
+
loadContractSchema: JSON.parse(
|
|
134
|
+
fs.readFileSync(path.join(schemaDir, 'load-contract.schema.json'), 'utf8'),
|
|
135
|
+
),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
109
139
|
function loadSchemas() {
|
|
110
140
|
if (_validators) return _validators;
|
|
111
141
|
let Ajv;
|
|
@@ -119,21 +149,13 @@ function loadSchemas() {
|
|
|
119
149
|
// to structural checks (no JSON-schema enforcement).
|
|
120
150
|
return null;
|
|
121
151
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const bundlePayloadSchema =
|
|
129
|
-
fs.readFileSync(path.join(schemaDir, 'bundle-profile-v1.schema.json'), 'utf8'),
|
|
130
|
-
);
|
|
131
|
-
const checksumsSchema = JSON.parse(
|
|
132
|
-
fs.readFileSync(path.join(schemaDir, 'checksums.schema.json'), 'utf8'),
|
|
133
|
-
);
|
|
134
|
-
const loadContractSchema = JSON.parse(
|
|
135
|
-
fs.readFileSync(path.join(schemaDir, 'load-contract.schema.json'), 'utf8'),
|
|
136
|
-
);
|
|
152
|
+
let schemas;
|
|
153
|
+
try {
|
|
154
|
+
schemas = loadPackagedSchemas();
|
|
155
|
+
} catch {
|
|
156
|
+
schemas = loadSchemasFromDisk();
|
|
157
|
+
}
|
|
158
|
+
const { manifestSchema, payloadSchema, bundlePayloadSchema, checksumsSchema, loadContractSchema } = schemas;
|
|
137
159
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
138
160
|
addFormats(ajv);
|
|
139
161
|
ajv.addSchema(loadContractSchema, 'load-contract.schema.json');
|
|
@@ -521,7 +543,7 @@ function readV1Layout(absPath) {
|
|
|
521
543
|
|
|
522
544
|
const map = {};
|
|
523
545
|
let entries = null; // ZIP entries if container
|
|
524
|
-
let kind
|
|
546
|
+
let kind; // 'dir' | 'file'
|
|
525
547
|
|
|
526
548
|
if (stat.isDirectory()) {
|
|
527
549
|
kind = 'dir';
|
|
@@ -589,7 +611,7 @@ function readV1Layout(absPath) {
|
|
|
589
611
|
try {
|
|
590
612
|
manifest = parseJsonEntry('kdna.json', map['kdna.json']);
|
|
591
613
|
} catch (e) {
|
|
592
|
-
throw new Error(`kdna.json is not valid JSON: ${e.message}
|
|
614
|
+
throw new Error(`kdna.json is not valid JSON: ${e.message}`, { cause: e });
|
|
593
615
|
}
|
|
594
616
|
if (manifest.lineage !== undefined && Array.isArray(manifest.lineage)) {
|
|
595
617
|
throw new Error('kdna.json.lineage must be an object, not an array');
|
|
@@ -1616,6 +1638,12 @@ module.exports = {
|
|
|
1616
1638
|
unpack,
|
|
1617
1639
|
loadV1,
|
|
1618
1640
|
FORBIDDEN_OUTPUT_TERMS,
|
|
1641
|
+
// Semver utilities (Story 6 / Story 13) — exported so consumers
|
|
1642
|
+
// (e.g. kdna-cli) can use the canonical implementation instead of
|
|
1643
|
+
// duplicating it.
|
|
1644
|
+
parseSemver,
|
|
1645
|
+
compareSemver,
|
|
1646
|
+
satisfies,
|
|
1619
1647
|
};
|
|
1620
1648
|
|
|
1621
1649
|
// ─── loadV1 — v1 runtime loading / load contract ──────────────────────
|
|
@@ -1767,7 +1795,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1767
1795
|
const rawPayload = v1.map['payload.kdnab'];
|
|
1768
1796
|
payload = parseJsonEntry('payload.kdnab', rawPayload);
|
|
1769
1797
|
} catch (e) {
|
|
1770
|
-
throw new Error(`payload.kdnab is not valid JSON: ${e.message}
|
|
1798
|
+
throw new Error(`payload.kdnab is not valid JSON: ${e.message}`, { cause: e });
|
|
1771
1799
|
}
|
|
1772
1800
|
|
|
1773
1801
|
if (payload.profile && payload.ciphertext && (m.payload?.encrypted || m.encryption?.encrypted_entries?.includes('payload.kdnab'))) {
|
|
@@ -1940,7 +1968,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1940
1968
|
}));
|
|
1941
1969
|
result.inheritance_applied = true;
|
|
1942
1970
|
}
|
|
1943
|
-
} catch
|
|
1971
|
+
} catch {
|
|
1944
1972
|
// extends merge is best-effort — never block load
|
|
1945
1973
|
result.inheritance_applied = false;
|
|
1946
1974
|
}
|
package/src/validate-pure.js
CHANGED
|
@@ -59,7 +59,10 @@ function validateDomainSchema(dataMap, schemaMap) {
|
|
|
59
59
|
const ajvInstance = new ajv({ allErrors: true, strict: false });
|
|
60
60
|
if (addFormats) addFormats(ajvInstance);
|
|
61
61
|
try {
|
|
62
|
-
|
|
62
|
+
// Use eval('require') to avoid webpack static analysis trying to
|
|
63
|
+
// resolve this JSON file at build time (it is only needed at runtime).
|
|
64
|
+
const _require = eval('require');
|
|
65
|
+
ajvInstance.addMetaSchema(_require('ajv/dist/refs/json-schema-2020-12.json'));
|
|
63
66
|
} catch {
|
|
64
67
|
/* meta-schema already available or not needed */
|
|
65
68
|
}
|
package/src/workpack-pure.js
CHANGED
|
@@ -169,9 +169,6 @@ function checkWorkPackStructure(manifest, rootDir) {
|
|
|
169
169
|
*/
|
|
170
170
|
function inspectWorkPack(manifest, rootDir) {
|
|
171
171
|
const kdnaMode = manifest.kdna?.mode || 'unknown';
|
|
172
|
-
const kdnaCount =
|
|
173
|
-
kdnaMode === 'single' ? 1 : (manifest.kdna?.assets || []).length;
|
|
174
|
-
|
|
175
172
|
const structure = checkWorkPackStructure(manifest, rootDir);
|
|
176
173
|
|
|
177
174
|
return {
|
|
@@ -240,7 +237,8 @@ function _lazyAjv() {
|
|
|
240
237
|
return _ajvInstance;
|
|
241
238
|
} catch (e) {
|
|
242
239
|
throw new Error(
|
|
243
|
-
'ajv is required for Work Pack validation. Install: npm install ajv ajv-formats'
|
|
240
|
+
'ajv is required for Work Pack validation. Install: npm install ajv ajv-formats',
|
|
241
|
+
{ cause: e },
|
|
244
242
|
);
|
|
245
243
|
}
|
|
246
244
|
}
|