@aikdna/kdna-core 0.15.10 → 0.15.12
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 +49 -0
- package/README.md +24 -21
- package/package.json +2 -7
- package/schema/manifest.schema.json +3 -3
- package/src/asset-reader.js +9 -13
- package/src/container-dispatcher.js +24 -102
- package/src/crypto-profile.js +6 -4
- package/src/index.mjs +22 -0
- package/src/lint-pure.js +8 -13
- package/src/public-api.js +1 -1
- package/src/types.d.ts +15 -19
- package/src/v1/index.js +178 -84
- package/src/v1/index.mjs +7 -8
- package/src/validate-pure.js +4 -1
- package/src/workpack-pure.js +2 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.15.12 (2026-07-12)
|
|
4
|
+
|
|
5
|
+
**Single-format refactor + CBOR wire contract + Runtime Capsule.**
|
|
6
|
+
|
|
7
|
+
### Breaking changes
|
|
8
|
+
|
|
9
|
+
- `MIMETYPE_V1`, `MIMETYPE_V2`, `MIMETYPE_LEGACY`, `isV1SourceDir`, `isV2SourceDir`
|
|
10
|
+
removed from public API. Use `MIMETYPE`, `isKdnaSourceDir`, `detectContainerFormat`.
|
|
11
|
+
- `detectContainerFormat` returns only `"kdna"` or `null`.
|
|
12
|
+
- `kdna_version: "2.0"` is no longer accepted. Only `"1.0"`.
|
|
13
|
+
- `payload.encoding` accepts only `"cbor"`. JSON payload bytes are rejected;
|
|
14
|
+
there is no production JSON fallback.
|
|
15
|
+
- Version-qualified public APIs (`V1_REQUIRED_DIR_ENTRIES`, `readV1Layout`,
|
|
16
|
+
`buildChecksumsV1`, `loadV1`, and the `./v1` export) are removed. Use the
|
|
17
|
+
unversioned APIs below.
|
|
18
|
+
- `kdna load --as=json` now returns Runtime Capsule format
|
|
19
|
+
(`type: "kdna.context.capsule"`) instead of raw `{status, content}`.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `REQUIRED_DIR_ENTRIES`, `readLayout`, `buildChecksums`, `load`, and
|
|
24
|
+
`loadAsset` are the sole public container/loading APIs.
|
|
25
|
+
- `buildCapsule(loadResult, v1, profile, opts)` — wraps load output as
|
|
26
|
+
`kdna.context.capsule` per `specs/runtime-capsule.md`.
|
|
27
|
+
- `parsePayloadEntry` — strict CBOR decoding for `payload.kdnab`.
|
|
28
|
+
|
|
29
|
+
### Fixes
|
|
30
|
+
|
|
31
|
+
- Producer/consumer encoding alignment: `dev-pack.js`, `studio.js` write CBOR;
|
|
32
|
+
validation and loading decode CBOR, including encrypted envelopes.
|
|
33
|
+
- `loadV1Unsafe` extends/deps processing occurs before Capsule wrapping (Story 12 fix).
|
|
34
|
+
- All test fixtures converted to CBOR; golden files regenerated.
|
|
35
|
+
|
|
36
|
+
## v0.15.11 (2026-07-03)
|
|
37
|
+
|
|
38
|
+
**Fix**: repair Core v1 container dispatcher module resolution in the
|
|
39
|
+
published package layout.
|
|
40
|
+
|
|
41
|
+
- Corrects internal `src/container-dispatcher.js` imports so the dispatcher can
|
|
42
|
+
load sibling `asset-reader` and `v1` modules after npm packaging.
|
|
43
|
+
- Loads v1 JSON schemas through package-relative static imports before falling
|
|
44
|
+
back to disk discovery, so bundled server environments do not look for
|
|
45
|
+
KDNA schemas in the host application directory.
|
|
46
|
+
- Adds regression coverage for loading a v1 source asset through the dispatcher
|
|
47
|
+
from the package `src/` layout and for validating from a non-repository cwd.
|
|
48
|
+
- Fix webpack bundler warning: use `eval('require')` for ajv meta-schema import
|
|
49
|
+
so webpack does not try to statically resolve the JSON file at build time.
|
|
50
|
+
|
|
51
|
+
No public API changes.
|
|
3
52
|
|
|
4
53
|
## v0.15.10 (2026-06-30)
|
|
5
54
|
|
package/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# @aikdna/kdna-core
|
|
2
2
|
|
|
3
|
-
Core library for
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Studio export, skills, MCP integrations, and downstream agent runtimes.
|
|
3
|
+
Core library for `.kdna` judgment assets. It implements the single KDNA Asset
|
|
4
|
+
Container, strict-CBOR payload decoding, validation, LoadPlan authorization,
|
|
5
|
+
and Runtime Capsule projection used by the CLI, Studio, skills, MCP
|
|
6
|
+
integrations, and downstream Agent runtimes.
|
|
8
7
|
|
|
9
8
|
## Installation
|
|
10
9
|
|
|
@@ -18,9 +17,10 @@ If you need full JSON Schema validation through Ajv, also install:
|
|
|
18
17
|
npm install ajv ajv-formats
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
##
|
|
20
|
+
## Public API
|
|
22
21
|
|
|
23
|
-
Use the
|
|
22
|
+
Use the package root. Version-qualified entrypoints and API names are not part
|
|
23
|
+
of the public contract.
|
|
24
24
|
|
|
25
25
|
```js
|
|
26
26
|
const {
|
|
@@ -30,8 +30,8 @@ const {
|
|
|
30
30
|
loadAuthorized,
|
|
31
31
|
pack,
|
|
32
32
|
unpack,
|
|
33
|
-
|
|
34
|
-
} = require('@aikdna/kdna-core
|
|
33
|
+
buildChecksums
|
|
34
|
+
} = require('@aikdna/kdna-core');
|
|
35
35
|
|
|
36
36
|
const validation = validate('./asset.kdna');
|
|
37
37
|
if (!validation.overall_valid) {
|
|
@@ -43,10 +43,14 @@ if (plan.can_load_now !== true) {
|
|
|
43
43
|
throw new Error(`Asset is not loadable yet: ${plan.required_action}`);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const
|
|
46
|
+
const capsule = loadAuthorized('./asset.kdna', {
|
|
47
47
|
profile: 'compact',
|
|
48
|
-
as: '
|
|
48
|
+
as: 'json'
|
|
49
49
|
});
|
|
50
|
+
|
|
51
|
+
if (capsule.type !== 'kdna.context.capsule') {
|
|
52
|
+
throw new Error('Expected a Runtime Capsule');
|
|
53
|
+
}
|
|
50
54
|
```
|
|
51
55
|
|
|
52
56
|
## Supported Runtime Flow
|
|
@@ -56,7 +60,8 @@ const compact = loadAuthorized('./asset.kdna', {
|
|
|
56
60
|
→ validate
|
|
57
61
|
→ planLoad
|
|
58
62
|
→ loadAuthorized
|
|
59
|
-
→
|
|
63
|
+
→ Runtime Capsule
|
|
64
|
+
→ Agent/runtime context
|
|
60
65
|
```
|
|
61
66
|
|
|
62
67
|
For authoring and test fixtures, `pack()` can turn a local working folder into
|
|
@@ -71,9 +76,9 @@ local working folder
|
|
|
71
76
|
→ loadAuthorized
|
|
72
77
|
```
|
|
73
78
|
|
|
74
|
-
##
|
|
79
|
+
## KDNA Asset Container
|
|
75
80
|
|
|
76
|
-
A
|
|
81
|
+
A `.kdna` asset contains:
|
|
77
82
|
|
|
78
83
|
- `mimetype`
|
|
79
84
|
- `kdna.json`
|
|
@@ -88,6 +93,10 @@ A v1 `.kdna` container contains:
|
|
|
88
93
|
- checksum consistency
|
|
89
94
|
- load-profile contract
|
|
90
95
|
|
|
96
|
+
`payload.kdnab` is CBOR. A password-protected or licensed asset stores a CBOR
|
|
97
|
+
encryption envelope in the same entry; authorized plaintext is decrypted in
|
|
98
|
+
memory and projected into the Runtime Capsule.
|
|
99
|
+
|
|
91
100
|
## Load Profiles
|
|
92
101
|
|
|
93
102
|
`loadAuthorized()` supports:
|
|
@@ -104,15 +113,9 @@ Output formats:
|
|
|
104
113
|
|
|
105
114
|
## Boundary
|
|
106
115
|
|
|
107
|
-
KDNA Core
|
|
116
|
+
KDNA Core is content-neutral. It validates file structure and loadability;
|
|
108
117
|
it does not rank judgment quality or endorse specific assets.
|
|
109
118
|
|
|
110
|
-
## Legacy API
|
|
111
|
-
|
|
112
|
-
The package root still exports compatibility APIs for older KDNA paths. New
|
|
113
|
-
tooling should prefer `@aikdna/kdna-core/v1` and the `planLoad` /
|
|
114
|
-
`loadAuthorized` path.
|
|
115
|
-
|
|
116
119
|
## License
|
|
117
120
|
|
|
118
121
|
Apache-2.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-core",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.12",
|
|
4
4
|
"description": "KDNA Core library for validating, planning, and loading local .kdna judgment assets.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -15,12 +15,7 @@
|
|
|
15
15
|
"types": "./src/types.d.ts"
|
|
16
16
|
},
|
|
17
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
|
-
}
|
|
18
|
+
"./schema/*": "./schema/*"
|
|
24
19
|
},
|
|
25
20
|
"types": "src/types.d.ts",
|
|
26
21
|
"scripts": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"properties": {
|
|
23
23
|
"kdna_version": {
|
|
24
24
|
"type": "string",
|
|
25
|
-
"description": "KDNA
|
|
26
|
-
"enum": ["1.0"
|
|
25
|
+
"description": "KDNA Asset Container wire discriminator.",
|
|
26
|
+
"enum": ["1.0"]
|
|
27
27
|
},
|
|
28
28
|
"asset_id": {
|
|
29
29
|
"type": "string",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"additionalProperties": true,
|
|
103
103
|
"properties": {
|
|
104
104
|
"path": { "type": "string", "const": "payload.kdnab" },
|
|
105
|
-
"encoding": { "type": "string", "enum": ["
|
|
105
|
+
"encoding": { "type": "string", "enum": ["cbor"] },
|
|
106
106
|
"encrypted": { "type": "boolean" }
|
|
107
107
|
}
|
|
108
108
|
},
|
package/src/asset-reader.js
CHANGED
|
@@ -8,7 +8,7 @@ const zlib = require('zlib');
|
|
|
8
8
|
const cbor = require('cbor-x');
|
|
9
9
|
const { loadDomainFromFiles, formatContext } = require('./loader');
|
|
10
10
|
|
|
11
|
-
const KDNA_MEDIA_TYPE = 'application/vnd.
|
|
11
|
+
const KDNA_MEDIA_TYPE = 'application/vnd.kdna.asset';
|
|
12
12
|
|
|
13
13
|
// Standard KDNA domain data entries — kept in lockstep with loader.FILE_MAP.
|
|
14
14
|
// Asset reader pre-loads these so callers don't have to enumerate entry names.
|
|
@@ -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
|
|
|
@@ -325,7 +325,7 @@ function verifyHumanLockSignatures(coreData, manifest, errors, warnings) {
|
|
|
325
325
|
];
|
|
326
326
|
|
|
327
327
|
let verified = 0, missing = 0, invalid = 0;
|
|
328
|
-
for (const [arrayName
|
|
328
|
+
for (const [arrayName] of CORE_CARD_ARRAYS) {
|
|
329
329
|
const cards = coreData?.[arrayName];
|
|
330
330
|
if (!Array.isArray(cards) || cards.length === 0) continue;
|
|
331
331
|
|
|
@@ -363,19 +363,18 @@ function verifyHumanLockSignatures(coreData, manifest, errors, warnings) {
|
|
|
363
363
|
|
|
364
364
|
function validateManifestIdentity(manifest, errors, _warnings) {
|
|
365
365
|
if (manifest.kdna_spec) {
|
|
366
|
-
errors.push('kdna.json: kdna_spec is not allowed. Use
|
|
366
|
+
errors.push('kdna.json: kdna_spec is not allowed. Use kdna_version.');
|
|
367
367
|
}
|
|
368
368
|
if (manifest.format && manifest.format !== 'kdna') {
|
|
369
369
|
errors.push(`kdna.json.format: invalid value "${manifest.format}". Expected "kdna".`);
|
|
370
370
|
}
|
|
371
|
-
if (manifest.
|
|
371
|
+
if (!manifest.kdna_version) {
|
|
372
|
+
errors.push('kdna.json: missing required field "kdna_version"');
|
|
373
|
+
} else if (manifest.kdna_version !== '1.0') {
|
|
372
374
|
errors.push(
|
|
373
|
-
`kdna.json.
|
|
375
|
+
`kdna.json.kdna_version: invalid value "${manifest.kdna_version}". Expected "1.0".`,
|
|
374
376
|
);
|
|
375
377
|
}
|
|
376
|
-
if (!manifest.spec_version) errors.push('kdna.json: missing required field "spec_version"');
|
|
377
|
-
if (!manifest.format) errors.push('kdna.json: missing required field "format"');
|
|
378
|
-
if (!manifest.format_version) errors.push('kdna.json: missing required field "format_version"');
|
|
379
378
|
if (manifest.language) {
|
|
380
379
|
errors.push('kdna.json: language is not allowed. Use default_language and languages.');
|
|
381
380
|
}
|
|
@@ -624,10 +623,7 @@ function createKdnaAssetReader() {
|
|
|
624
623
|
|
|
625
624
|
if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
|
|
626
625
|
verifyMediaType(asset, errors);
|
|
627
|
-
if (!asset.entries.has('
|
|
628
|
-
if (!asset.entries.has('KDNA_Patterns.json')) {
|
|
629
|
-
errors.push('required entry missing: KDNA_Patterns.json');
|
|
630
|
-
}
|
|
626
|
+
if (!asset.entries.has('payload.kdnab')) errors.push('required entry missing: payload.kdnab');
|
|
631
627
|
|
|
632
628
|
const content_digest = buildContentDigest(asset);
|
|
633
629
|
const asset_digest = asset.asset_digest;
|
|
@@ -4,45 +4,25 @@ const fs = require('node:fs');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const crypto = require('node:crypto');
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const MIMETYPE_V1 = 'application/vnd.kdna.asset';
|
|
10
|
-
const MIMETYPE_V2 = 'application/vnd.aikdna.kdna+zip';
|
|
7
|
+
const MIMETYPE = 'application/vnd.kdna.asset';
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
|
-
* CanonicalAssetModel — the single internal representation of
|
|
14
|
-
* regardless of
|
|
15
|
-
* or Canonical Distribution Container (Container v2 / SPEC 2.0).
|
|
16
|
-
*
|
|
17
|
-
* All downstream code (planLoad, loadAuthorized, decrypt, loadV1Unsafe)
|
|
18
|
-
* operates on this model. No consumer should check container format directly.
|
|
10
|
+
* CanonicalAssetModel — the single internal representation of a KDNA asset,
|
|
11
|
+
* regardless of source (directory or .kdna container).
|
|
19
12
|
*
|
|
20
13
|
* @typedef {object} CanonicalAssetModel
|
|
21
|
-
* @property {'dir'|'file'} sourceKind
|
|
22
|
-
* @property {string} sourcePath
|
|
23
|
-
* @property {string} format — '
|
|
24
|
-
* @property {string} mimetype
|
|
14
|
+
* @property {'dir'|'file'} sourceKind
|
|
15
|
+
* @property {string} sourcePath
|
|
16
|
+
* @property {string} format — 'dir' | 'kdna'
|
|
17
|
+
* @property {string} mimetype
|
|
25
18
|
* @property {object} manifest — parsed kdna.json
|
|
26
|
-
* @property {Buffer} payloadRaw — raw payload.kdnab bytes
|
|
27
|
-
* @property {object|null} checksums
|
|
28
|
-
* @property {Buffer|null} assetDigest
|
|
29
|
-
* @property {Buffer|null} containerBuffer
|
|
30
|
-
* @property {Map<string, Buffer>|null} entries
|
|
19
|
+
* @property {Buffer} payloadRaw — raw payload.kdnab bytes
|
|
20
|
+
* @property {object|null} checksums
|
|
21
|
+
* @property {Buffer|null} assetDigest
|
|
22
|
+
* @property {Buffer|null} containerBuffer
|
|
23
|
+
* @property {Map<string, Buffer>|null} entries
|
|
31
24
|
*/
|
|
32
25
|
|
|
33
|
-
/**
|
|
34
|
-
* Read any KDNA asset into a CanonicalAssetModel.
|
|
35
|
-
*
|
|
36
|
-
* Handles:
|
|
37
|
-
* - Source directories (mimetype + kdna.json + payload.kdnab on disk)
|
|
38
|
-
* - Container v1 (.kdna ZIP with application/vnd.kdna.asset mimetype)
|
|
39
|
-
* - Canonical containers (.kdna ZIP with application/vnd.aikdna.kdna+zip mimetype)
|
|
40
|
-
*
|
|
41
|
-
* @param {string} inputPath - absolute path to asset file or directory
|
|
42
|
-
* @param {object} [opts]
|
|
43
|
-
* @param {object} [opts.limits] - container size/entry limits
|
|
44
|
-
* @returns {CanonicalAssetModel}
|
|
45
|
-
*/
|
|
46
26
|
function readAsset(inputPath, opts = {}) {
|
|
47
27
|
const absPath = path.resolve(inputPath);
|
|
48
28
|
const stat = fs.statSync(absPath);
|
|
@@ -59,11 +39,8 @@ function readAsset(inputPath, opts = {}) {
|
|
|
59
39
|
}
|
|
60
40
|
|
|
61
41
|
const format = detectFormat(absPath);
|
|
62
|
-
if (format === '
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
if (format === 'v2-container') {
|
|
66
|
-
return readV2Container(absPath, opts);
|
|
42
|
+
if (format === 'kdna') {
|
|
43
|
+
return readKdnaContainer(absPath, opts);
|
|
67
44
|
}
|
|
68
45
|
|
|
69
46
|
throw Object.assign(
|
|
@@ -72,10 +49,6 @@ function readAsset(inputPath, opts = {}) {
|
|
|
72
49
|
);
|
|
73
50
|
}
|
|
74
51
|
|
|
75
|
-
/**
|
|
76
|
-
* Detect the container format of a .kdna file.
|
|
77
|
-
* Returns 'v1-container', 'v2-container', or throws.
|
|
78
|
-
*/
|
|
79
52
|
function detectFormat(absPath) {
|
|
80
53
|
const buf = fs.readFileSync(absPath);
|
|
81
54
|
if (buf.length < 4) {
|
|
@@ -85,7 +58,6 @@ function detectFormat(absPath) {
|
|
|
85
58
|
);
|
|
86
59
|
}
|
|
87
60
|
|
|
88
|
-
// Must be a ZIP (PK signature)
|
|
89
61
|
const sig = buf.readUInt32LE(0);
|
|
90
62
|
if (sig !== 0x04034b50) {
|
|
91
63
|
throw Object.assign(
|
|
@@ -94,7 +66,6 @@ function detectFormat(absPath) {
|
|
|
94
66
|
);
|
|
95
67
|
}
|
|
96
68
|
|
|
97
|
-
// Read the mimetype from the first entry (central directory first entry)
|
|
98
69
|
const eocdOffset = findEocd(buf);
|
|
99
70
|
let cdOffset = buf.readUInt32LE(eocdOffset + 16);
|
|
100
71
|
const cdEntryCount = buf.readUInt16LE(eocdOffset + 10);
|
|
@@ -126,8 +97,7 @@ function detectFormat(absPath) {
|
|
|
126
97
|
const compressedSize = buf.readUInt32LE(entryOffset + 20);
|
|
127
98
|
const mime = buf.toString('utf8', dataOffset, dataOffset + compressedSize).trim();
|
|
128
99
|
|
|
129
|
-
if (mime ===
|
|
130
|
-
if (mime === MIMETYPE_V2) return 'v2-container';
|
|
100
|
+
if (mime === MIMETYPE) return 'kdna';
|
|
131
101
|
|
|
132
102
|
throw Object.assign(
|
|
133
103
|
new Error(`Unknown KDNA mimetype: ${mime}`),
|
|
@@ -154,9 +124,6 @@ function findEocd(buf) {
|
|
|
154
124
|
);
|
|
155
125
|
}
|
|
156
126
|
|
|
157
|
-
/**
|
|
158
|
-
* Read a source directory into CanonicalAssetModel.
|
|
159
|
-
*/
|
|
160
127
|
function readSourceDirectory(absPath, _opts) {
|
|
161
128
|
const mimetypePath = path.join(absPath, 'mimetype');
|
|
162
129
|
const manifestPath = path.join(absPath, 'kdna.json');
|
|
@@ -171,7 +138,7 @@ function readSourceDirectory(absPath, _opts) {
|
|
|
171
138
|
}
|
|
172
139
|
|
|
173
140
|
const mimetype = fs.readFileSync(mimetypePath, 'utf8').trim();
|
|
174
|
-
if (
|
|
141
|
+
if (mimetype !== MIMETYPE) {
|
|
175
142
|
throw Object.assign(
|
|
176
143
|
new Error(`Unknown mimetype in source directory: ${mimetype}`),
|
|
177
144
|
{ code: 'KDNA_FORMAT_UNKNOWN' },
|
|
@@ -204,7 +171,7 @@ function readSourceDirectory(absPath, _opts) {
|
|
|
204
171
|
return {
|
|
205
172
|
sourceKind: 'dir',
|
|
206
173
|
sourcePath: absPath,
|
|
207
|
-
format: '
|
|
174
|
+
format: 'dir',
|
|
208
175
|
mimetype,
|
|
209
176
|
manifest,
|
|
210
177
|
payloadRaw,
|
|
@@ -215,21 +182,16 @@ function readSourceDirectory(absPath, _opts) {
|
|
|
215
182
|
};
|
|
216
183
|
}
|
|
217
184
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
*/
|
|
221
|
-
function readV1Container(absPath, _opts) {
|
|
222
|
-
// Delegate to the existing v1 layout reader for validation
|
|
223
|
-
const v1Layout = require('../v1/index.js').readV1Layout(absPath);
|
|
224
|
-
|
|
185
|
+
function readKdnaContainer(absPath, _opts) {
|
|
186
|
+
const v1Layout = require('./v1/index.js').readLayout(absPath);
|
|
225
187
|
const containerBuf = fs.readFileSync(absPath);
|
|
226
188
|
const assetDigest = crypto.createHash('sha256').update(containerBuf).digest();
|
|
227
189
|
|
|
228
190
|
return {
|
|
229
191
|
sourceKind: 'file',
|
|
230
192
|
sourcePath: absPath,
|
|
231
|
-
format: '
|
|
232
|
-
mimetype:
|
|
193
|
+
format: 'kdna',
|
|
194
|
+
mimetype: MIMETYPE,
|
|
233
195
|
manifest: v1Layout.manifest,
|
|
234
196
|
payloadRaw: v1Layout.map['payload.kdnab'] || null,
|
|
235
197
|
checksums: v1Layout.map['checksums.json']
|
|
@@ -241,58 +203,18 @@ function readV1Container(absPath, _opts) {
|
|
|
241
203
|
};
|
|
242
204
|
}
|
|
243
205
|
|
|
244
|
-
/**
|
|
245
|
-
* Read a Canonical Container (v2 mimetype) into CanonicalAssetModel.
|
|
246
|
-
* Uses the existing KdnaAssetReader for ZIP parsing.
|
|
247
|
-
*/
|
|
248
|
-
function readV2Container(absPath, _opts) {
|
|
249
|
-
const reader = createKdnaAssetReader();
|
|
250
|
-
const asset = reader.openSync(absPath);
|
|
251
|
-
|
|
252
|
-
const manifest = reader.readManifestSync(asset);
|
|
253
|
-
const payloadRaw = reader.readEntrySync(asset, 'payload.kdnab');
|
|
254
|
-
const checksums = (() => {
|
|
255
|
-
try {
|
|
256
|
-
const raw = reader.readEntrySync(asset, 'checksums.json');
|
|
257
|
-
return raw ? JSON.parse(raw.toString('utf8')) : null;
|
|
258
|
-
} catch {
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
})();
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
sourceKind: 'file',
|
|
265
|
-
sourcePath: absPath,
|
|
266
|
-
format: 'v2-container',
|
|
267
|
-
mimetype: MIMETYPE_V2,
|
|
268
|
-
manifest,
|
|
269
|
-
payloadRaw,
|
|
270
|
-
checksums,
|
|
271
|
-
assetDigest: Buffer.from(asset.asset_digest.replace(/^sha256:/, ''), 'hex'),
|
|
272
|
-
containerBuffer: fs.readFileSync(absPath),
|
|
273
|
-
entries: null, // v2 uses lazy reads, not pre-loaded map
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Extract the mimetype from a CanonicalAssetModel.
|
|
279
|
-
*/
|
|
280
206
|
function getMimetype(asset) {
|
|
281
207
|
return asset.mimetype;
|
|
282
208
|
}
|
|
283
209
|
|
|
284
|
-
|
|
285
|
-
* Determine if a CanonicalAssetModel represents a source directory.
|
|
286
|
-
*/
|
|
287
|
-
function isSourceDirectory(asset) {
|
|
210
|
+
function isKdnaSourceDirectory(asset) {
|
|
288
211
|
return asset.sourceKind === 'dir';
|
|
289
212
|
}
|
|
290
213
|
|
|
291
214
|
module.exports = {
|
|
292
|
-
|
|
293
|
-
MIMETYPE_V2,
|
|
215
|
+
MIMETYPE,
|
|
294
216
|
readAsset,
|
|
295
217
|
detectFormat,
|
|
296
218
|
getMimetype,
|
|
297
|
-
|
|
219
|
+
isKdnaSourceDirectory,
|
|
298
220
|
};
|
package/src/crypto-profile.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
|
+
const cbor = require('cbor-x');
|
|
2
3
|
|
|
3
4
|
let argon2id;
|
|
4
5
|
try {
|
|
@@ -56,11 +57,13 @@ function decodeBase64(value, label) {
|
|
|
56
57
|
|
|
57
58
|
function normalizeEnvelope(value) {
|
|
58
59
|
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
59
|
-
return
|
|
60
|
+
try { return cbor.decode(Buffer.from(value)); } catch { /* fall through */ }
|
|
61
|
+
}
|
|
62
|
+
if (typeof value === 'string') {
|
|
63
|
+
try { return cbor.decode(Buffer.from(value, 'utf8')); } catch { /* fall through */ }
|
|
60
64
|
}
|
|
61
|
-
if (typeof value === 'string') return JSON.parse(value);
|
|
62
65
|
if (value && typeof value === 'object') return value;
|
|
63
|
-
throw new Error('encrypted entry envelope must be
|
|
66
|
+
throw new Error('encrypted entry envelope must be CBOR (kdna_version 1.0)');
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
function encryptedEntryAad(entryName, manifest = {}, profile = LICENSED_ENTRY_PROFILE) {
|
|
@@ -102,7 +105,6 @@ function hkdfSha256(ikm, salt = null, info = '', length = 32) {
|
|
|
102
105
|
|
|
103
106
|
// ── AES-256-KW (RFC 3394) ─────────────────────────────────────────
|
|
104
107
|
|
|
105
|
-
const AES_BLOCK = 16;
|
|
106
108
|
const KW_IV = Buffer.from('a6a6a6a6a6a6a6a6', 'hex'); // RFC 3394 default IV
|
|
107
109
|
|
|
108
110
|
function aesWrap(key, plaintext) {
|
package/src/index.mjs
CHANGED
|
@@ -12,6 +12,28 @@ export {
|
|
|
12
12
|
|
|
13
13
|
export { lintDomain } from './lint-pure.js';
|
|
14
14
|
|
|
15
|
+
export {
|
|
16
|
+
MIMETYPE,
|
|
17
|
+
REQUIRED_DIR_ENTRIES,
|
|
18
|
+
isKdnaSourceDir,
|
|
19
|
+
detectContainerFormat,
|
|
20
|
+
readLayout,
|
|
21
|
+
inspect,
|
|
22
|
+
validate,
|
|
23
|
+
planLoad,
|
|
24
|
+
loadAuthorized,
|
|
25
|
+
buildChecksums,
|
|
26
|
+
pack,
|
|
27
|
+
unpack,
|
|
28
|
+
load,
|
|
29
|
+
loadAsset,
|
|
30
|
+
buildCapsule,
|
|
31
|
+
FORBIDDEN_OUTPUT_TERMS,
|
|
32
|
+
parseSemver,
|
|
33
|
+
compareSemver,
|
|
34
|
+
satisfies,
|
|
35
|
+
} from './v1/index.js';
|
|
36
|
+
|
|
15
37
|
export { validateDomainSchema, validateCrossFile } from './validate-pure.js';
|
|
16
38
|
|
|
17
39
|
export { renderPreviewHTML, escHtml, renderCard } from './render.js';
|
package/src/lint-pure.js
CHANGED
|
@@ -273,13 +273,13 @@ const VALID_ASSET_TYPE = new Set([
|
|
|
273
273
|
]);
|
|
274
274
|
|
|
275
275
|
const MANIFEST_REQUIRED = [
|
|
276
|
-
'
|
|
276
|
+
'kdna_version', 'name', 'version',
|
|
277
277
|
'judgment_version', 'description', 'author', 'license', 'status',
|
|
278
278
|
'quality_badge', 'access', 'languages', 'default_language',
|
|
279
279
|
];
|
|
280
280
|
|
|
281
281
|
/**
|
|
282
|
-
* Validate a kdna.json manifest against the canonical
|
|
282
|
+
* Validate a kdna.json manifest against the canonical KDNA Asset format.
|
|
283
283
|
*
|
|
284
284
|
* @param {Object} manifest — parsed kdna.json
|
|
285
285
|
* @returns {{ errors: string[], warnings: string[] }}
|
|
@@ -296,7 +296,7 @@ function validateManifest(manifest) {
|
|
|
296
296
|
// 1. Check disallowed pre-v1.0 manifest aliases
|
|
297
297
|
if ('kdna_spec' in manifest) {
|
|
298
298
|
errors.push(
|
|
299
|
-
'kdna.json: kdna_spec is not allowed. Use
|
|
299
|
+
'kdna.json: kdna_spec is not allowed. Use kdna_version.',
|
|
300
300
|
);
|
|
301
301
|
}
|
|
302
302
|
if ('language' in manifest) {
|
|
@@ -319,9 +319,11 @@ function validateManifest(manifest) {
|
|
|
319
319
|
if (manifest.format && manifest.format !== 'kdna') {
|
|
320
320
|
errors.push(`kdna.json.format: invalid value "${manifest.format}". Expected "kdna".`);
|
|
321
321
|
}
|
|
322
|
-
if (manifest.
|
|
322
|
+
if (!manifest.kdna_version) {
|
|
323
|
+
errors.push('kdna.json: missing required field "kdna_version"');
|
|
324
|
+
} else if (manifest.kdna_version !== '1.0') {
|
|
323
325
|
errors.push(
|
|
324
|
-
`kdna.json.
|
|
326
|
+
`kdna.json.kdna_version: invalid value "${manifest.kdna_version}". Expected "1.0".`,
|
|
325
327
|
);
|
|
326
328
|
}
|
|
327
329
|
if (manifest.status && !VALID_STATUS.has(manifest.status)) {
|
|
@@ -394,14 +396,7 @@ function validateManifest(manifest) {
|
|
|
394
396
|
errors.push('kdna.json.license: missing "type"');
|
|
395
397
|
}
|
|
396
398
|
|
|
397
|
-
// 9. Validate
|
|
398
|
-
if (manifest.spec_version && manifest.spec_version !== '1.0-rc') {
|
|
399
|
-
warnings.push(
|
|
400
|
-
`kdna.json.spec_version: non-standard value "${manifest.spec_version}". Expected "1.0-rc".`,
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// 10. Validate version format
|
|
399
|
+
// 9. Validate version format
|
|
405
400
|
if (manifest.version && !/^\d+\.\d+\.\d+/.test(manifest.version)) {
|
|
406
401
|
warnings.push(
|
|
407
402
|
`kdna.json.version: non-semver format "${manifest.version}". Expected MAJOR.MINOR.PATCH.`,
|
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/types.d.ts
CHANGED
|
@@ -203,9 +203,7 @@ export interface KDNAFileDataMap {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
export interface KDNAManifest {
|
|
206
|
-
|
|
207
|
-
format_version: '2.0';
|
|
208
|
-
spec_version: string;
|
|
206
|
+
kdna_version: '1.0';
|
|
209
207
|
name: string;
|
|
210
208
|
version: string;
|
|
211
209
|
judgment_version: string;
|
|
@@ -484,36 +482,33 @@ export function matchDomain(input: string, candidates: Array<KDNAAssetInput | KD
|
|
|
484
482
|
export function matchDomainSync(input: string, candidates: Array<KDNAAssetInput | KDNAInspectResult>, options?: KdnaDecryptOptions): KDNAMatchResult[];
|
|
485
483
|
export function composeKDNA(inputs: KDNAAssetInput[], options?: { input?: string; profile?: 'compact' | 'scenario' | 'full' | string; separator?: string } & KdnaDecryptOptions): Promise<KDNAComposeResult>;
|
|
486
484
|
|
|
487
|
-
// KDNA Core
|
|
485
|
+
// KDNA Core — source directory / container API
|
|
488
486
|
export const MIMETYPE: string;
|
|
489
|
-
export const
|
|
490
|
-
export const MIMETYPE_V2: string;
|
|
491
|
-
export const V1_REQUIRED_DIR_ENTRIES: string[];
|
|
487
|
+
export const REQUIRED_DIR_ENTRIES: string[];
|
|
492
488
|
|
|
493
|
-
export interface
|
|
489
|
+
export interface KDNAChecksumEntry {
|
|
494
490
|
algorithm: 'sha256';
|
|
495
491
|
value: string;
|
|
496
492
|
}
|
|
497
493
|
|
|
498
|
-
export interface
|
|
494
|
+
export interface KDNAChecksums {
|
|
499
495
|
algorithm: 'sha256';
|
|
500
496
|
manifest_digest: string;
|
|
501
497
|
payload_digest: string;
|
|
502
498
|
asset_digest: string;
|
|
503
|
-
entries: Record<string,
|
|
499
|
+
entries: Record<string, KDNAChecksumEntry>;
|
|
504
500
|
}
|
|
505
501
|
|
|
506
|
-
export function
|
|
507
|
-
export function
|
|
508
|
-
export function detectContainerFormat(inputPath: string): 'v1' | 'v2' | null;
|
|
502
|
+
export function isKdnaSourceDir(inputPath: string): boolean;
|
|
503
|
+
export function detectContainerFormat(inputPath: string): 'kdna' | null;
|
|
509
504
|
export function inspect(inputPath: string, options?: Record<string, any>): Record<string, any>;
|
|
510
505
|
export function validate(inputPath: string, options?: Record<string, any>): Record<string, any>;
|
|
511
|
-
export interface
|
|
506
|
+
export interface KDNALoadPlanIssue {
|
|
512
507
|
code: string;
|
|
513
508
|
severity: 'info' | 'warning' | 'blocking' | string;
|
|
514
509
|
message: string;
|
|
515
510
|
}
|
|
516
|
-
export interface
|
|
511
|
+
export interface KDNALoadPlan {
|
|
517
512
|
kdna_version: string | null;
|
|
518
513
|
asset: {
|
|
519
514
|
asset_id: string | null;
|
|
@@ -552,18 +547,19 @@ export interface KDNAV1LoadPlan {
|
|
|
552
547
|
projection_policy: 'minimal' | 'remote' | 'none' | string;
|
|
553
548
|
input_fingerprint: string | null;
|
|
554
549
|
checks: Record<string, boolean>;
|
|
555
|
-
issues:
|
|
550
|
+
issues: KDNALoadPlanIssue[];
|
|
556
551
|
source: {
|
|
557
552
|
kind: 'dir' | 'file' | string | null;
|
|
558
553
|
path: string;
|
|
559
554
|
};
|
|
560
555
|
}
|
|
561
|
-
export function planLoad(inputPath: string, options?: { password?: string; hasPassword?: boolean; entitlement?: Record<string, any> }):
|
|
562
|
-
export function
|
|
556
|
+
export function planLoad(inputPath: string, options?: { password?: string; hasPassword?: boolean; entitlement?: Record<string, any> }): KDNALoadPlan;
|
|
557
|
+
export function buildChecksums(sourceDir: string): KDNAChecksums;
|
|
563
558
|
export function pack(sourceDir: string, outputPath: string): void;
|
|
564
559
|
export function unpack(inputPath: string, outputDir: string): void;
|
|
565
560
|
export function loadAuthorized(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
|
|
566
|
-
export function
|
|
561
|
+
export function load(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
|
|
562
|
+
export function loadAsset(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
|
|
567
563
|
export const FORBIDDEN_OUTPUT_TERMS: readonly string[];
|
|
568
564
|
|
|
569
565
|
// Lint
|
package/src/v1/index.js
CHANGED
|
@@ -38,24 +38,24 @@ const fs = require('node:fs');
|
|
|
38
38
|
const path = require('node:path');
|
|
39
39
|
const zlib = require('node:zlib');
|
|
40
40
|
const crypto = require('node:crypto');
|
|
41
|
+
const cbor = require('cbor-x');
|
|
41
42
|
|
|
42
43
|
const { readAsset } = (() => {
|
|
43
44
|
try {
|
|
44
45
|
return require('../container-dispatcher.js');
|
|
45
46
|
} catch {
|
|
46
|
-
return { readAsset: null
|
|
47
|
+
return { readAsset: null };
|
|
47
48
|
}
|
|
48
49
|
})();
|
|
49
50
|
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
...
|
|
56
|
-
...V1_OPTIONAL_DIR_ENTRIES,
|
|
51
|
+
const MIMETYPE = 'application/vnd.kdna.asset';
|
|
52
|
+
const REQUIRED_DIR_ENTRIES = ['mimetype', 'kdna.json', 'payload.kdnab'];
|
|
53
|
+
const OPTIONAL_DIR_ENTRIES = ['checksums.json', 'signatures', 'attachments'];
|
|
54
|
+
const ALLOWED_TOP_LEVEL_ENTRIES = new Set([
|
|
55
|
+
...REQUIRED_DIR_ENTRIES,
|
|
56
|
+
...OPTIONAL_DIR_ENTRIES,
|
|
57
57
|
]);
|
|
58
|
-
const
|
|
58
|
+
const FORBIDDEN_LEGACY_TOP_LEVEL = new Set([
|
|
59
59
|
'KDNA_Core.json',
|
|
60
60
|
'KDNA_Patterns.json',
|
|
61
61
|
'KDNA_Scenarios.json',
|
|
@@ -64,6 +64,12 @@ const V1_FORBIDDEN_LEGACY_TOP_LEVEL = new Set([
|
|
|
64
64
|
'KDNA_Evolution.json',
|
|
65
65
|
]);
|
|
66
66
|
|
|
67
|
+
// Legacy aliases — prefer the unversioned names above
|
|
68
|
+
const V1_REQUIRED_DIR_ENTRIES = REQUIRED_DIR_ENTRIES;
|
|
69
|
+
const V1_OPTIONAL_DIR_ENTRIES = OPTIONAL_DIR_ENTRIES;
|
|
70
|
+
const V1_ALLOWED_TOP_LEVEL_ENTRIES = ALLOWED_TOP_LEVEL_ENTRIES;
|
|
71
|
+
const V1_FORBIDDEN_LEGACY_TOP_LEVEL = FORBIDDEN_LEGACY_TOP_LEVEL;
|
|
72
|
+
|
|
67
73
|
const DEFAULT_CONTAINER_LIMITS = Object.freeze({
|
|
68
74
|
maxContainerBytes: 25 * 1024 * 1024,
|
|
69
75
|
maxEntries: 128,
|
|
@@ -91,6 +97,16 @@ const FORBIDDEN_OUTPUT_TERMS = Object.freeze([
|
|
|
91
97
|
let _ajv = null;
|
|
92
98
|
let _validators = null;
|
|
93
99
|
|
|
100
|
+
function loadPackagedSchemas() {
|
|
101
|
+
return {
|
|
102
|
+
manifestSchema: require('../../schema/manifest.schema.json'),
|
|
103
|
+
payloadSchema: require('../../schema/payload-profile-v1.schema.json'),
|
|
104
|
+
bundlePayloadSchema: require('../../schema/bundle-profile-v1.schema.json'),
|
|
105
|
+
checksumsSchema: require('../../schema/checksums.schema.json'),
|
|
106
|
+
loadContractSchema: require('../../schema/load-contract.schema.json'),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
function getRepoRoot() {
|
|
95
111
|
// Walk up from this file to find the repo root (where schema/ lives).
|
|
96
112
|
// Works whether this module is loaded from packages/kdna/src/ or
|
|
@@ -106,6 +122,26 @@ function getRepoRoot() {
|
|
|
106
122
|
return process.cwd();
|
|
107
123
|
}
|
|
108
124
|
|
|
125
|
+
function loadSchemasFromDisk() {
|
|
126
|
+
const repoRoot = getRepoRoot();
|
|
127
|
+
const schemaDir = path.join(repoRoot, 'schema');
|
|
128
|
+
return {
|
|
129
|
+
manifestSchema: JSON.parse(fs.readFileSync(path.join(schemaDir, 'manifest.schema.json'), 'utf8')),
|
|
130
|
+
payloadSchema: JSON.parse(
|
|
131
|
+
fs.readFileSync(path.join(schemaDir, 'payload-profile-v1.schema.json'), 'utf8'),
|
|
132
|
+
),
|
|
133
|
+
bundlePayloadSchema: JSON.parse(
|
|
134
|
+
fs.readFileSync(path.join(schemaDir, 'bundle-profile-v1.schema.json'), 'utf8'),
|
|
135
|
+
),
|
|
136
|
+
checksumsSchema: JSON.parse(
|
|
137
|
+
fs.readFileSync(path.join(schemaDir, 'checksums.schema.json'), 'utf8'),
|
|
138
|
+
),
|
|
139
|
+
loadContractSchema: JSON.parse(
|
|
140
|
+
fs.readFileSync(path.join(schemaDir, 'load-contract.schema.json'), 'utf8'),
|
|
141
|
+
),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
109
145
|
function loadSchemas() {
|
|
110
146
|
if (_validators) return _validators;
|
|
111
147
|
let Ajv;
|
|
@@ -119,21 +155,13 @@ function loadSchemas() {
|
|
|
119
155
|
// to structural checks (no JSON-schema enforcement).
|
|
120
156
|
return null;
|
|
121
157
|
}
|
|
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
|
-
);
|
|
158
|
+
let schemas;
|
|
159
|
+
try {
|
|
160
|
+
schemas = loadPackagedSchemas();
|
|
161
|
+
} catch {
|
|
162
|
+
schemas = loadSchemasFromDisk();
|
|
163
|
+
}
|
|
164
|
+
const { manifestSchema, payloadSchema, bundlePayloadSchema, checksumsSchema, loadContractSchema } = schemas;
|
|
137
165
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
138
166
|
addFormats(ajv);
|
|
139
167
|
ajv.addSchema(loadContractSchema, 'load-contract.schema.json');
|
|
@@ -154,45 +182,28 @@ function loadSchemas() {
|
|
|
154
182
|
* Required entries: mimetype, kdna.json, payload.kdnab.
|
|
155
183
|
* mimetype content must equal "application/vnd.kdna.asset".
|
|
156
184
|
*/
|
|
157
|
-
function
|
|
185
|
+
function isKdnaSourceDir(absPath) {
|
|
158
186
|
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isDirectory()) return false;
|
|
159
187
|
for (const f of V1_REQUIRED_DIR_ENTRIES) {
|
|
160
188
|
if (!fs.existsSync(path.join(absPath, f))) return false;
|
|
161
189
|
}
|
|
162
190
|
const mime = fs.readFileSync(path.join(absPath, 'mimetype'), 'utf8');
|
|
163
|
-
return mime ===
|
|
191
|
+
return mime === MIMETYPE;
|
|
164
192
|
}
|
|
165
193
|
|
|
166
194
|
/**
|
|
167
|
-
* Detect whether a directory is a v2 source layout.
|
|
168
|
-
* Required entries: mimetype, kdna.json, payload.kdnab.
|
|
169
|
-
* mimetype content must equal "application/vnd.aikdna.kdna+zip".
|
|
170
|
-
*/
|
|
171
|
-
function isV2SourceDir(absPath) {
|
|
172
|
-
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isDirectory()) return false;
|
|
173
|
-
for (const f of V1_REQUIRED_DIR_ENTRIES) {
|
|
174
|
-
if (!fs.existsSync(path.join(absPath, f))) return false;
|
|
175
|
-
}
|
|
176
|
-
const mime = fs.readFileSync(path.join(absPath, 'mimetype'), 'utf8');
|
|
177
|
-
return mime === MIMETYPE_V2;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
195
|
/**
|
|
181
|
-
* Detect whether a file is a
|
|
182
|
-
* Returns '
|
|
196
|
+
* Detect whether a file is a .kdna container.
|
|
197
|
+
* Returns 'kdna' | null. null = not a .kdna file or unreadable.
|
|
183
198
|
*/
|
|
184
199
|
function detectContainerFormat(absPath) {
|
|
185
200
|
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isFile()) return null;
|
|
186
|
-
// Quick header check: must look like a ZIP.
|
|
187
201
|
const fd = fs.openSync(absPath, 'r');
|
|
188
202
|
const head = Buffer.alloc(4);
|
|
189
203
|
fs.readSync(fd, head, 0, 4, 0);
|
|
190
204
|
fs.closeSync(fd);
|
|
191
205
|
if (head[0] !== 0x50 || head[1] !== 0x4b) return null;
|
|
192
206
|
|
|
193
|
-
// Read only the first central-directory entry. Dangerous later entries
|
|
194
|
-
// must not make detection fall through to a less strict legacy route;
|
|
195
|
-
// readV1Layout/validate will reject them with the secure container reader.
|
|
196
207
|
let first;
|
|
197
208
|
try {
|
|
198
209
|
first = readFirstZipEntry(absPath);
|
|
@@ -200,12 +211,9 @@ function detectContainerFormat(absPath) {
|
|
|
200
211
|
return null;
|
|
201
212
|
}
|
|
202
213
|
if (first.name !== 'mimetype') return null;
|
|
203
|
-
// The mimetype entry must be STORED (method 0).
|
|
204
214
|
if (first.method !== 0) return null;
|
|
205
215
|
const mime = first.method === 0 ? first.data.toString('utf8') : '';
|
|
206
|
-
|
|
207
|
-
if (mime === MIMETYPE_V2) return 'v2';
|
|
208
|
-
return null;
|
|
216
|
+
return (mime === MIMETYPE) ? 'kdna' : null;
|
|
209
217
|
}
|
|
210
218
|
|
|
211
219
|
// ─── ZIP I/O ────────────────────────────────────────────────────────────
|
|
@@ -521,7 +529,7 @@ function readV1Layout(absPath) {
|
|
|
521
529
|
|
|
522
530
|
const map = {};
|
|
523
531
|
let entries = null; // ZIP entries if container
|
|
524
|
-
let kind
|
|
532
|
+
let kind; // 'dir' | 'file'
|
|
525
533
|
|
|
526
534
|
if (stat.isDirectory()) {
|
|
527
535
|
kind = 'dir';
|
|
@@ -577,9 +585,9 @@ function readV1Layout(absPath) {
|
|
|
577
585
|
|
|
578
586
|
// mimetype content must equal the literal v1 media type.
|
|
579
587
|
const mime = map.mimetype.toString('utf8');
|
|
580
|
-
if (mime !==
|
|
588
|
+
if (mime !== MIMETYPE) {
|
|
581
589
|
throw new Error(
|
|
582
|
-
`not a KDNA layout: mimetype is "${mime}", expected "${
|
|
590
|
+
`not a KDNA layout: mimetype is "${mime}", expected "${MIMETYPE}"`,
|
|
583
591
|
);
|
|
584
592
|
}
|
|
585
593
|
|
|
@@ -589,7 +597,7 @@ function readV1Layout(absPath) {
|
|
|
589
597
|
try {
|
|
590
598
|
manifest = parseJsonEntry('kdna.json', map['kdna.json']);
|
|
591
599
|
} catch (e) {
|
|
592
|
-
throw new Error(`kdna.json is not valid JSON: ${e.message}
|
|
600
|
+
throw new Error(`kdna.json is not valid JSON: ${e.message}`, { cause: e });
|
|
593
601
|
}
|
|
594
602
|
if (manifest.lineage !== undefined && Array.isArray(manifest.lineage)) {
|
|
595
603
|
throw new Error('kdna.json.lineage must be an object, not an array');
|
|
@@ -635,6 +643,24 @@ function assertJsonWithinLimits(value, name, limits) {
|
|
|
635
643
|
walk(value, 0);
|
|
636
644
|
}
|
|
637
645
|
|
|
646
|
+
function parsePayloadEntry(name, bytes, opts = {}) {
|
|
647
|
+
if (!Buffer.isBuffer(bytes)) {
|
|
648
|
+
throw new Error(`${name} is not a file entry`);
|
|
649
|
+
}
|
|
650
|
+
const limits = { ...DEFAULT_CONTAINER_LIMITS, ...(opts.limits || {}) };
|
|
651
|
+
if (bytes.length > limits.maxEntryBytes) {
|
|
652
|
+
throw new Error(`${name} exceeds maximum size (${limits.maxEntryBytes} bytes)`);
|
|
653
|
+
}
|
|
654
|
+
let parsed;
|
|
655
|
+
try {
|
|
656
|
+
parsed = cbor.decode(bytes);
|
|
657
|
+
} catch {
|
|
658
|
+
throw new Error(`${name}: not valid CBOR. payload.kdnab must be CBOR-encoded (kdna_version 1.0).`);
|
|
659
|
+
}
|
|
660
|
+
assertJsonWithinLimits(parsed, name, limits);
|
|
661
|
+
return parsed;
|
|
662
|
+
}
|
|
663
|
+
|
|
638
664
|
// ─── inspect ───────────────────────────────────────────────────────────
|
|
639
665
|
|
|
640
666
|
/**
|
|
@@ -688,9 +714,9 @@ function runValidate(v1) {
|
|
|
688
714
|
}
|
|
689
715
|
if (v1.map.mimetype) {
|
|
690
716
|
const mimeStr = v1.map.mimetype.toString('utf8');
|
|
691
|
-
if (mimeStr !==
|
|
717
|
+
if (mimeStr !== MIMETYPE) {
|
|
692
718
|
result.format_valid = false;
|
|
693
|
-
problems.push(`format: mimetype is not ${
|
|
719
|
+
problems.push(`format: mimetype is not ${MIMETYPE}`);
|
|
694
720
|
}
|
|
695
721
|
}
|
|
696
722
|
|
|
@@ -714,10 +740,10 @@ function runValidate(v1) {
|
|
|
714
740
|
// For actually encrypted payloads (envelopes), skip schema validation.
|
|
715
741
|
let payload;
|
|
716
742
|
try {
|
|
717
|
-
payload =
|
|
743
|
+
payload = parsePayloadEntry("payload.kdnab", v1.map['payload.kdnab']);
|
|
718
744
|
} catch (e) {
|
|
719
745
|
result.payload_valid = false;
|
|
720
|
-
problems.push(`payload: not valid
|
|
746
|
+
problems.push(`payload: not valid KDNA payload (${e.message})`);
|
|
721
747
|
return finalizeValidate(result, problems);
|
|
722
748
|
}
|
|
723
749
|
|
|
@@ -909,12 +935,22 @@ function pack(sourceDir, outputPath) {
|
|
|
909
935
|
}
|
|
910
936
|
}
|
|
911
937
|
const mime = fs.readFileSync(path.join(absSrc, 'mimetype'), 'utf8');
|
|
912
|
-
if (mime !==
|
|
913
|
-
throw new Error(`cannot pack: mimetype is "${mime}", expected "${
|
|
938
|
+
if (mime !== MIMETYPE) {
|
|
939
|
+
throw new Error(`cannot pack: mimetype is "${mime}", expected "${MIMETYPE}"`);
|
|
914
940
|
}
|
|
915
941
|
|
|
916
942
|
// Collect deterministically; mimetype is forced first.
|
|
917
943
|
const collected = listSourceDir(absSrc);
|
|
944
|
+
for (const entry of collected) {
|
|
945
|
+
const normalizedName = normalizeContainerEntryName(entry.rel);
|
|
946
|
+
const topLevel = normalizedName.split('/')[0];
|
|
947
|
+
if (!ALLOWED_TOP_LEVEL_ENTRIES.has(topLevel)) {
|
|
948
|
+
if (FORBIDDEN_LEGACY_TOP_LEVEL.has(topLevel)) {
|
|
949
|
+
throw new Error(`cannot pack forbidden top-level source entry: ${topLevel}`);
|
|
950
|
+
}
|
|
951
|
+
throw new Error(`cannot pack unsupported top-level entry: ${topLevel}`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
918
954
|
const order = ['mimetype', ...collected.map((e) => e.rel).filter((n) => n !== 'mimetype')];
|
|
919
955
|
|
|
920
956
|
// Build the ZIP body.
|
|
@@ -924,7 +960,7 @@ function pack(sourceDir, outputPath) {
|
|
|
924
960
|
for (const rel of order) {
|
|
925
961
|
let data;
|
|
926
962
|
if (rel === 'mimetype') {
|
|
927
|
-
data = Buffer.from(
|
|
963
|
+
data = Buffer.from(MIMETYPE, 'utf8');
|
|
928
964
|
} else {
|
|
929
965
|
const found = collected.find((e) => e.rel === rel);
|
|
930
966
|
if (!found) continue;
|
|
@@ -989,9 +1025,9 @@ function unpack(inputPath, outputDir) {
|
|
|
989
1025
|
throw new Error('not a KDNA container: mimetype must be uncompressed');
|
|
990
1026
|
}
|
|
991
1027
|
const mime = entries[0].data.toString('utf8');
|
|
992
|
-
if (mime !==
|
|
1028
|
+
if (mime !== MIMETYPE) {
|
|
993
1029
|
throw new Error(
|
|
994
|
-
`not a KDNA container: mimetype is "${mime}", expected "${
|
|
1030
|
+
`not a KDNA container: mimetype is "${mime}", expected "${MIMETYPE}"`,
|
|
995
1031
|
);
|
|
996
1032
|
}
|
|
997
1033
|
const absOut = path.resolve(outputDir);
|
|
@@ -1599,26 +1635,22 @@ function assertNoForbiddenTerms(obj) {
|
|
|
1599
1635
|
}
|
|
1600
1636
|
|
|
1601
1637
|
module.exports = {
|
|
1602
|
-
MIMETYPE
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
V1_REQUIRED_DIR_ENTRIES,
|
|
1606
|
-
isV1SourceDir,
|
|
1607
|
-
isV2SourceDir,
|
|
1638
|
+
MIMETYPE,
|
|
1639
|
+
REQUIRED_DIR_ENTRIES,
|
|
1640
|
+
isKdnaSourceDir,
|
|
1608
1641
|
detectContainerFormat,
|
|
1609
|
-
readV1Layout,
|
|
1642
|
+
readLayout: readV1Layout,
|
|
1610
1643
|
inspect,
|
|
1611
1644
|
validate,
|
|
1612
1645
|
planLoad,
|
|
1613
1646
|
loadAuthorized,
|
|
1614
|
-
buildChecksumsV1,
|
|
1647
|
+
buildChecksums: buildChecksumsV1,
|
|
1615
1648
|
pack,
|
|
1616
1649
|
unpack,
|
|
1617
|
-
loadV1,
|
|
1650
|
+
load: loadV1,
|
|
1651
|
+
loadAsset: loadV1,
|
|
1652
|
+
buildCapsule,
|
|
1618
1653
|
FORBIDDEN_OUTPUT_TERMS,
|
|
1619
|
-
// Semver utilities (Story 6 / Story 13) — exported so consumers
|
|
1620
|
-
// (e.g. kdna-cli) can use the canonical implementation instead of
|
|
1621
|
-
// duplicating it.
|
|
1622
1654
|
parseSemver,
|
|
1623
1655
|
compareSemver,
|
|
1624
1656
|
satisfies,
|
|
@@ -1720,8 +1752,11 @@ function loadAuthorized(inputPath, opts = {}) {
|
|
|
1720
1752
|
const mergedOpts = {
|
|
1721
1753
|
...opts,
|
|
1722
1754
|
resolvedDependencies: plan.resolved_dependencies,
|
|
1723
|
-
// Pass extends_chain from plan to loadV1Unsafe so inheritance is applied (Story 12)
|
|
1724
1755
|
extendsChain: plan.extends_chain || [],
|
|
1756
|
+
_validation: {
|
|
1757
|
+
schema_valid: plan.checks && plan.checks.overall_valid === true,
|
|
1758
|
+
signature_valid: plan.signature_valid === true,
|
|
1759
|
+
},
|
|
1725
1760
|
};
|
|
1726
1761
|
return loadV1Unsafe(inputPath, mergedOpts);
|
|
1727
1762
|
}
|
|
@@ -1771,9 +1806,9 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1771
1806
|
let payload;
|
|
1772
1807
|
try {
|
|
1773
1808
|
const rawPayload = v1.map['payload.kdnab'];
|
|
1774
|
-
payload =
|
|
1809
|
+
payload = parsePayloadEntry("payload.kdnab", rawPayload);
|
|
1775
1810
|
} catch (e) {
|
|
1776
|
-
throw new Error(`payload.kdnab is not valid
|
|
1811
|
+
throw new Error(`payload.kdnab is not valid KDNA payload: ${e.message}`, { cause: e });
|
|
1777
1812
|
}
|
|
1778
1813
|
|
|
1779
1814
|
if (payload.profile && payload.ciphertext && (m.payload?.encrypted || m.encryption?.encrypted_entries?.includes('payload.kdnab'))) {
|
|
@@ -1790,7 +1825,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1790
1825
|
const decryptedBuf = typeof decrypted === 'string'
|
|
1791
1826
|
? Buffer.from(decrypted, 'utf8')
|
|
1792
1827
|
: Buffer.from(decrypted);
|
|
1793
|
-
payload =
|
|
1828
|
+
payload = parsePayloadEntry('payload.kdnab', decryptedBuf);
|
|
1794
1829
|
} catch (e) {
|
|
1795
1830
|
const err = new Error(`Decryption failed: ${e.message}`);
|
|
1796
1831
|
err.code = 'KDNA_DECRYPT_FAILED';
|
|
@@ -1805,8 +1840,10 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1805
1840
|
decryptProtectedEntryScrypt,
|
|
1806
1841
|
PASSWORD_PROTECTED_SCRYPT_PROFILE,
|
|
1807
1842
|
} = require('../crypto-profile.js');
|
|
1808
|
-
const encryptedEnvelope = v1.map['payload.kdnab']
|
|
1809
|
-
|
|
1843
|
+
const encryptedEnvelope = v1.map['payload.kdnab'];
|
|
1844
|
+
let envelope;
|
|
1845
|
+
try { envelope = cbor.decode(Buffer.isBuffer(encryptedEnvelope) ? encryptedEnvelope : Buffer.from(encryptedEnvelope)); }
|
|
1846
|
+
catch { throw new Error('Cannot decode encrypted envelope: payload.kdnab must be CBOR-encoded (kdna_version 1.0)'); }
|
|
1810
1847
|
const decryptFn = (envelope.profile === PASSWORD_PROTECTED_SCRYPT_PROFILE)
|
|
1811
1848
|
? decryptProtectedEntryScrypt
|
|
1812
1849
|
: decryptProtectedEntry;
|
|
@@ -1815,7 +1852,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1815
1852
|
manifest: m,
|
|
1816
1853
|
password: opts.password,
|
|
1817
1854
|
});
|
|
1818
|
-
payload =
|
|
1855
|
+
payload = parsePayloadEntry('payload.kdnab', decryptedBuf);
|
|
1819
1856
|
} catch (e) {
|
|
1820
1857
|
const err = new Error(`Decryption failed with provided password: ${e.message}`);
|
|
1821
1858
|
err.code = 'KDNA_DECRYPT_FAILED';
|
|
@@ -1916,8 +1953,9 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1916
1953
|
resolvedDependencies: [],
|
|
1917
1954
|
extendsChain: [],
|
|
1918
1955
|
});
|
|
1919
|
-
|
|
1920
|
-
|
|
1956
|
+
const baseContent = baseLoaded && (baseLoaded.content || baseLoaded.context);
|
|
1957
|
+
if (baseContent) {
|
|
1958
|
+
const bc = baseContent;
|
|
1921
1959
|
const cc = result.content;
|
|
1922
1960
|
// Merge axioms: child overrides by id; parent axioms not in child are added
|
|
1923
1961
|
if (bc.axioms || cc.axioms) {
|
|
@@ -1946,7 +1984,7 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1946
1984
|
}));
|
|
1947
1985
|
result.inheritance_applied = true;
|
|
1948
1986
|
}
|
|
1949
|
-
} catch
|
|
1987
|
+
} catch {
|
|
1950
1988
|
// extends merge is best-effort — never block load
|
|
1951
1989
|
result.inheritance_applied = false;
|
|
1952
1990
|
}
|
|
@@ -2026,9 +2064,65 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
2026
2064
|
return { status: result.status, profile: result.profile, text: textOut };
|
|
2027
2065
|
}
|
|
2028
2066
|
|
|
2067
|
+
if (as === 'prompt') {
|
|
2068
|
+
return result;
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
return wrapAsCapsule(result, v1, profile, opts);
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
function wrapAsCapsule(result, v1, profile, opts) {
|
|
2075
|
+
if (opts.as === 'json') {
|
|
2076
|
+
return buildCapsule(result, v1, profile, opts);
|
|
2077
|
+
}
|
|
2029
2078
|
return result;
|
|
2030
2079
|
}
|
|
2031
2080
|
|
|
2081
|
+
function buildCapsule(loadResult, v1, profile, opts = {}) {
|
|
2082
|
+
const m = v1.manifest;
|
|
2083
|
+
const val = opts._validation || {};
|
|
2084
|
+
const assetDigest = v1.assetDigest || (v1.map && v1.map['checksums.json']
|
|
2085
|
+
? (() => {
|
|
2086
|
+
try {
|
|
2087
|
+
const checks = JSON.parse(v1.map['checksums.json'].toString('utf8'));
|
|
2088
|
+
return checks.asset_digest || null;
|
|
2089
|
+
} catch { return null; }
|
|
2090
|
+
})()
|
|
2091
|
+
: null);
|
|
2092
|
+
|
|
2093
|
+
const pubkey = (m.creator && m.creator.pubkey) || (m.author && m.author.pubkey) || null;
|
|
2094
|
+
const sigVerified = val.signature_valid === true;
|
|
2095
|
+
const capsule = {
|
|
2096
|
+
type: 'kdna.context.capsule',
|
|
2097
|
+
version: '1.0',
|
|
2098
|
+
domain: m.name || m.asset_id || null,
|
|
2099
|
+
judgment_version: m.judgment_version || null,
|
|
2100
|
+
asset_digest: assetDigest,
|
|
2101
|
+
signature: pubkey
|
|
2102
|
+
? { state: sigVerified ? 'verified' : 'not_checked', issuer: pubkey }
|
|
2103
|
+
: { state: 'absent' },
|
|
2104
|
+
access: m.access || 'public',
|
|
2105
|
+
risk_level: m.risk_level || null,
|
|
2106
|
+
profile: loadResult.profile,
|
|
2107
|
+
context: loadResult.content || {},
|
|
2108
|
+
trace: {
|
|
2109
|
+
payload_encoding: (m.payload && m.payload.encoding) || 'cbor',
|
|
2110
|
+
loaded_by: 'kdna-core',
|
|
2111
|
+
loaded_at: new Date().toISOString(),
|
|
2112
|
+
schema_valid: val.schema_valid === true,
|
|
2113
|
+
signature_state: sigVerified ? 'verified' : (pubkey ? 'not_checked' : 'absent'),
|
|
2114
|
+
profile: profile,
|
|
2115
|
+
},
|
|
2116
|
+
};
|
|
2117
|
+
|
|
2118
|
+
if (loadResult.extends_chain) capsule.extends_chain = loadResult.extends_chain;
|
|
2119
|
+
if (loadResult.inheritance_applied !== undefined) capsule.inheritance_applied = loadResult.inheritance_applied;
|
|
2120
|
+
if (loadResult.resolved_dependencies) capsule.resolved_dependencies = loadResult.resolved_dependencies;
|
|
2121
|
+
if (loadResult.rag_isolation_policy) capsule.rag_isolation_policy = loadResult.rag_isolation_policy;
|
|
2122
|
+
|
|
2123
|
+
return capsule;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2032
2126
|
// ─── Semver & Dependency Resolution (Story 6) ─────────────────────────
|
|
2033
2127
|
|
|
2034
2128
|
function parseSemver(v) {
|
package/src/v1/index.mjs
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import v1 from './index.js';
|
|
2
2
|
|
|
3
3
|
export const MIMETYPE = v1.MIMETYPE;
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
6
|
-
export const V1_REQUIRED_DIR_ENTRIES = v1.V1_REQUIRED_DIR_ENTRIES;
|
|
7
|
-
export const isV1SourceDir = v1.isV1SourceDir;
|
|
8
|
-
export const isV2SourceDir = v1.isV2SourceDir;
|
|
4
|
+
export const REQUIRED_DIR_ENTRIES = v1.REQUIRED_DIR_ENTRIES;
|
|
5
|
+
export const isKdnaSourceDir = v1.isKdnaSourceDir;
|
|
9
6
|
export const detectContainerFormat = v1.detectContainerFormat;
|
|
10
|
-
export const
|
|
7
|
+
export const readLayout = v1.readLayout;
|
|
11
8
|
export const inspect = v1.inspect;
|
|
12
9
|
export const validate = v1.validate;
|
|
13
10
|
export const planLoad = v1.planLoad;
|
|
14
11
|
export const loadAuthorized = v1.loadAuthorized;
|
|
15
|
-
export const
|
|
12
|
+
export const buildChecksums = v1.buildChecksums;
|
|
16
13
|
export const pack = v1.pack;
|
|
17
14
|
export const unpack = v1.unpack;
|
|
18
|
-
export const
|
|
15
|
+
export const load = v1.load;
|
|
16
|
+
export const loadAsset = v1.loadAsset;
|
|
17
|
+
export const buildCapsule = v1.buildCapsule;
|
|
19
18
|
export const FORBIDDEN_OUTPUT_TERMS = v1.FORBIDDEN_OUTPUT_TERMS;
|
|
20
19
|
|
|
21
20
|
export default v1;
|
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
|
}
|