@aikdna/kdna-core 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +7 -5
- package/package.json +2 -2
- package/schema/load-contract.schema.json +3 -2
- package/schema/load-plan.schema.json +7 -0
- package/schema/runtime-capsule.schema.json +25 -9
- package/src/asset-reader.js +29 -13
- package/src/container/index.js +279 -12
- package/src/index.js +2 -0
- package/src/index.mjs +22 -0
- package/src/public-api.js +55 -1
- package/src/runtime-capsule.js +81 -16
- package/src/signature.js +320 -0
- package/src/types.d.ts +104 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.22.0 (2026-08-17)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- RFC-0021 M1 asset signatures (`kdsig.ed25519`): sign and verify packaged
|
|
10
|
+
`.kdna` assets with Ed25519 signature bundles — `signKDNA`,
|
|
11
|
+
`verifyKDNASignature`, `composeKDNA`, signing-payload builders, key-pair
|
|
12
|
+
generation, key fingerprints, and signature-bundle parse/serialize —
|
|
13
|
+
mirrored byte-for-byte in the Python Core, with cross-language signature
|
|
14
|
+
conformance vectors. M1 is bound as a pre-release candidate; M2–M6 are not
|
|
15
|
+
implemented and remain non-binding.
|
|
16
|
+
- RFC-0020 minimal projection profile: a fifth load profile for small local
|
|
17
|
+
models, a strict subset of compact carrying the highest-question and
|
|
18
|
+
boundary-friendly axioms plus full boundaries (~0.8–1.1k tokens vs compact
|
|
19
|
+
~5.4k on the reference assets). Availability follows the manifest
|
|
20
|
+
declaration; assets without a minimal declaration fail closed.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
5
24
|
- Validate decrypted external-grant and password payload plaintext against the
|
|
6
25
|
same declared Payload schema used for ordinary assets. Correct credentials
|
|
7
26
|
can no longer load schema-invalid judgment bytes.
|
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# @aikdna/kdna-core
|
|
2
2
|
|
|
3
|
-
> **Status:** Pre-release. This source tree contains
|
|
4
|
-
>
|
|
5
|
-
>
|
|
3
|
+
> **Status:** Pre-release. This source tree contains unreleased work beyond
|
|
4
|
+
> the published incumbent `0.22.0` (RFC-0021 M1 asset signatures and the
|
|
5
|
+
> RFC-0020 minimal projection profile); the next release coordinate is not
|
|
6
|
+
> finalized. The older `0.21.0` line predates the signature track. Candidate
|
|
7
|
+
> source, APIs, and evidence must not be described as already published.
|
|
6
8
|
|
|
7
9
|
Core library for packaged `.kdna` judgment assets. It implements one current
|
|
8
10
|
container contract, authorization planning, integrity evidence, Runtime Capsule
|
|
@@ -11,11 +13,11 @@ projection, and the correlated Agent Host execution boundary.
|
|
|
11
13
|
## Install
|
|
12
14
|
|
|
13
15
|
```bash
|
|
14
|
-
npm install @aikdna/kdna-core@0.
|
|
16
|
+
npm install @aikdna/kdna-core@0.22.0
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
That command installs the published incumbent. Use its release notes for the
|
|
18
|
-
exact `0.
|
|
20
|
+
exact `0.22.0` contract. The examples below describe the current source
|
|
19
21
|
candidate and require its final release coordinate before registry use.
|
|
20
22
|
|
|
21
23
|
## Pack, validate, plan, and load
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "KDNA Core library for validating, planning, and loading local .kdna judgment assets.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@noble/hashes": "^1.8.0",
|
|
67
|
-
"cbor-x": "^1.6.
|
|
67
|
+
"cbor-x": "^1.6.5",
|
|
68
68
|
"ajv": "^8.20.0",
|
|
69
69
|
"ajv-formats": "^3.0.1"
|
|
70
70
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"properties": {
|
|
10
10
|
"default_profile": {
|
|
11
11
|
"type": "string",
|
|
12
|
-
"enum": ["index", "compact", "scenario", "full"]
|
|
12
|
+
"enum": ["index", "compact", "scenario", "full", "minimal"]
|
|
13
13
|
},
|
|
14
14
|
"profiles": {
|
|
15
15
|
"type": "object",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"index": { "$ref": "#/$defs/profile" },
|
|
20
20
|
"compact": { "$ref": "#/$defs/profile" },
|
|
21
21
|
"scenario": { "$ref": "#/$defs/profile" },
|
|
22
|
-
"full": { "$ref": "#/$defs/profile" }
|
|
22
|
+
"full": { "$ref": "#/$defs/profile" },
|
|
23
|
+
"minimal": { "$ref": "#/$defs/profile" }
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
},
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"can_load_now",
|
|
16
16
|
"projection_policy",
|
|
17
17
|
"checks",
|
|
18
|
+
"signature_state",
|
|
18
19
|
"issues",
|
|
19
20
|
"source",
|
|
20
21
|
"input_fingerprint"
|
|
@@ -87,6 +88,7 @@
|
|
|
87
88
|
"schema_valid",
|
|
88
89
|
"payload_valid",
|
|
89
90
|
"checksums_valid",
|
|
91
|
+
"signature_valid",
|
|
90
92
|
"load_contract_valid",
|
|
91
93
|
"overall_valid"
|
|
92
94
|
],
|
|
@@ -96,10 +98,15 @@
|
|
|
96
98
|
"schema_valid": { "type": "boolean" },
|
|
97
99
|
"payload_valid": { "type": "boolean" },
|
|
98
100
|
"checksums_valid": { "type": "boolean" },
|
|
101
|
+
"signature_valid": { "type": "boolean" },
|
|
99
102
|
"load_contract_valid": { "type": "boolean" },
|
|
100
103
|
"overall_valid": { "type": "boolean" }
|
|
101
104
|
}
|
|
102
105
|
},
|
|
106
|
+
"signature_state": {
|
|
107
|
+
"description": "RFC-0021 M1 signature evidence state: absent (unsigned), verified (signature.kdsig verified), or invalid (present but rejected).",
|
|
108
|
+
"enum": ["absent", "verified", "invalid"]
|
|
109
|
+
},
|
|
103
110
|
"issues": {
|
|
104
111
|
"type": "array",
|
|
105
112
|
"items": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"signature": { "$ref": "#/$defs/signature" },
|
|
38
38
|
"access": { "enum": ["public", "licensed", "remote"] },
|
|
39
|
-
"profile": { "enum": ["index", "compact", "scenario", "full"] },
|
|
39
|
+
"profile": { "enum": ["index", "compact", "scenario", "full", "minimal"] },
|
|
40
40
|
"context": { "type": "object" },
|
|
41
41
|
"trace": { "$ref": "#/$defs/trace" }
|
|
42
42
|
},
|
|
@@ -78,12 +78,28 @@
|
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
"signature": {
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
"oneOf": [
|
|
82
|
+
{
|
|
83
|
+
"type": "object",
|
|
84
|
+
"required": ["state"],
|
|
85
|
+
"additionalProperties": false,
|
|
86
|
+
"properties": {
|
|
87
|
+
"state": { "const": "absent" }
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"type": "object",
|
|
92
|
+
"required": ["state", "profile", "profile_version", "key_fingerprint", "content_digest"],
|
|
93
|
+
"additionalProperties": false,
|
|
94
|
+
"properties": {
|
|
95
|
+
"state": { "const": "verified" },
|
|
96
|
+
"profile": { "const": "kdsig.ed25519" },
|
|
97
|
+
"profile_version": { "const": "0.1.0" },
|
|
98
|
+
"key_fingerprint": { "$ref": "#/$defs/sha256" },
|
|
99
|
+
"content_digest": { "$ref": "#/$defs/sha256" }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
]
|
|
87
103
|
},
|
|
88
104
|
"trace": {
|
|
89
105
|
"type": "object",
|
|
@@ -105,8 +121,8 @@
|
|
|
105
121
|
"input_kind": { "enum": ["packaged_file", "packaged_bytes"] },
|
|
106
122
|
"runtime_eligible": { "const": true },
|
|
107
123
|
"schema_valid": { "const": true },
|
|
108
|
-
"signature_state": { "
|
|
109
|
-
"profile": { "enum": ["index", "compact", "scenario", "full"] },
|
|
124
|
+
"signature_state": { "enum": ["absent", "verified"] },
|
|
125
|
+
"profile": { "enum": ["index", "compact", "scenario", "full", "minimal"] },
|
|
110
126
|
"projection_report": { "$ref": "#/$defs/projectionReport" }
|
|
111
127
|
}
|
|
112
128
|
},
|
package/src/asset-reader.js
CHANGED
|
@@ -7,6 +7,7 @@ const crypto = require('crypto');
|
|
|
7
7
|
const zlib = require('zlib');
|
|
8
8
|
const cbor = require('cbor-x');
|
|
9
9
|
const { loaderVersionUnsupportedMessage } = require('./loader-compatibility');
|
|
10
|
+
const { SIGNATURE_ENTRY_NAME } = require('./signature');
|
|
10
11
|
|
|
11
12
|
const KDNA_MEDIA_TYPE = 'application/vnd.kdna.asset';
|
|
12
13
|
const ASSET_SOURCE_BYTES = Symbol.for('@aikdna/kdna-core.asset-source-bytes');
|
|
@@ -46,10 +47,6 @@ function compareEntryNamesByUtf8(left, right) {
|
|
|
46
47
|
return Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8'));
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
function sortedEntryNames(entries) {
|
|
50
|
-
return [...entries.keys()].sort(compareEntryNamesByUtf8);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
50
|
function parseJson(buf, entryName) {
|
|
54
51
|
try {
|
|
55
52
|
return JSON.parse(Buffer.isBuffer(buf) ? buf.toString('utf8') : String(buf));
|
|
@@ -202,13 +199,23 @@ function manifestForDigest(manifest) {
|
|
|
202
199
|
return copy;
|
|
203
200
|
}
|
|
204
201
|
|
|
205
|
-
|
|
202
|
+
// Entries excluded from the canonical content tree (docs/CANONICALIZATION.md).
|
|
203
|
+
// signature.kdsig can never be part of the content it signs.
|
|
204
|
+
const CONTENT_DIGEST_EXCLUDED_ENTRIES = new Set(['.DS_Store', 'build-receipt.json', 'signature.kdsig']);
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Compute the canonical content digest over a map of entry name → Buffer.
|
|
208
|
+
* Shared by the asset reader and the container signature gate so both always
|
|
209
|
+
* hash the same canonical bytes.
|
|
210
|
+
*/
|
|
211
|
+
function contentDigestFromEntryBuffers(buffers) {
|
|
212
|
+
const names = buffers instanceof Map ? [...buffers.keys()] : Object.keys(buffers);
|
|
213
|
+
const readBuffer = (name) => (buffers instanceof Map ? buffers.get(name) : buffers[name]);
|
|
206
214
|
const parts = [];
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
if (excluded.has(entryName)) continue;
|
|
215
|
+
for (const entryName of names.sort(compareEntryNamesByUtf8)) {
|
|
216
|
+
if (CONTENT_DIGEST_EXCLUDED_ENTRIES.has(entryName)) continue;
|
|
210
217
|
if (entryName.startsWith('reports/')) continue;
|
|
211
|
-
const entryBuf =
|
|
218
|
+
const entryBuf = readBuffer(entryName);
|
|
212
219
|
let digestBuf = entryBuf;
|
|
213
220
|
if (JSON_ENTRY_RE.test(entryName)) {
|
|
214
221
|
const parsed = parseJson(entryBuf, entryName);
|
|
@@ -220,6 +227,14 @@ function buildContentDigest(asset) {
|
|
|
220
227
|
return `sha256:${sha256Hex(Buffer.from(parts.join('\n')))}`;
|
|
221
228
|
}
|
|
222
229
|
|
|
230
|
+
function buildContentDigest(asset) {
|
|
231
|
+
const buffers = new Map();
|
|
232
|
+
for (const entryName of asset.entries.keys()) {
|
|
233
|
+
buffers.set(entryName, asset.readEntry(entryName));
|
|
234
|
+
}
|
|
235
|
+
return contentDigestFromEntryBuffers(buffers);
|
|
236
|
+
}
|
|
237
|
+
|
|
223
238
|
function verifyMediaType(asset, errors) {
|
|
224
239
|
if (!asset.entries.has('mimetype')) {
|
|
225
240
|
errors.push('required entry missing: mimetype');
|
|
@@ -380,8 +395,8 @@ function verifySync(asset, options = {}) {
|
|
|
380
395
|
// adds transport identity; it must not maintain a second manifest or payload
|
|
381
396
|
// dialect.
|
|
382
397
|
appendCurrentValidationErrors(asset, errors);
|
|
383
|
-
if (options.requireSignature === true) {
|
|
384
|
-
errors.push(
|
|
398
|
+
if (options.requireSignature === true && !asset.entries.has(SIGNATURE_ENTRY_NAME)) {
|
|
399
|
+
errors.push(`required asset signature is absent: missing entry ${SIGNATURE_ENTRY_NAME}`);
|
|
385
400
|
}
|
|
386
401
|
|
|
387
402
|
if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
|
|
@@ -541,8 +556,8 @@ function createKdnaAssetReader() {
|
|
|
541
556
|
const entries = [...asset.entries.keys()].sort();
|
|
542
557
|
|
|
543
558
|
appendCurrentValidationErrors(asset, errors);
|
|
544
|
-
if (options.requireSignature === true) {
|
|
545
|
-
errors.push(
|
|
559
|
+
if (options.requireSignature === true && !asset.entries.has(SIGNATURE_ENTRY_NAME)) {
|
|
560
|
+
errors.push(`required asset signature is absent: missing entry ${SIGNATURE_ENTRY_NAME}`);
|
|
546
561
|
}
|
|
547
562
|
|
|
548
563
|
if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
|
|
@@ -617,5 +632,6 @@ function createKdnaAssetReader() {
|
|
|
617
632
|
|
|
618
633
|
module.exports = {
|
|
619
634
|
STANDARD_ENTRIES,
|
|
635
|
+
contentDigestFromEntryBuffers,
|
|
620
636
|
createKdnaAssetReader,
|
|
621
637
|
};
|