@aikdna/kdna-core 0.15.11 → 0.16.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 CHANGED
@@ -1,5 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.16.0 (2026-07-13)
4
+
5
+ ### Added
6
+
7
+ - RFC-0019 external account/device key grants, schemas, deterministic golden
8
+ and negative fixtures, and JS/Swift parity vectors.
9
+ - JS Core helpers for external envelope encryption, per-device grant issuance,
10
+ signature/binding verification, in-memory decryption, and explicit zeroization.
11
+ - LoadPlan accepts only a runtime-verified external entitlement for account/org
12
+ assets; structurally similar status objects no longer authorize loading.
13
+ - Reject authoring source directories at the public runtime API boundary;
14
+ `planLoad`, `load`, `loadAsset`, and `loadAuthorized` now require a packaged
15
+ `.kdna` file, including resolver-provided dependencies.
16
+ - Regenerate authorization conformance fixtures from packaged assets and keep
17
+ source directories available only to authoring operations such as inspect,
18
+ validate, pack, and unpack.
19
+ - Remove stale public format-generation wording and local coordination-path
20
+ assumptions from protocol documentation and public audit scripts.
21
+ - Make release readiness fail for dirty inputs or when the selected version
22
+ tag does not point to the current commit.
23
+
24
+ ### Security
25
+
26
+ - Account/device envelopes never fall back to the password profile.
27
+ - Revoked, expired, tampered, digest/version/account/device mismatches fail
28
+ closed before Runtime Capsule projection.
29
+
30
+ ## v0.15.12 (2026-07-12)
31
+
32
+ **Single-format refactor + CBOR wire contract + Runtime Capsule.**
33
+
34
+ ### Breaking changes
35
+
36
+ - `MIMETYPE_V1`, `MIMETYPE_V2`, `MIMETYPE_LEGACY`, `isV1SourceDir`, `isV2SourceDir`
37
+ removed from public API. Use `MIMETYPE`, `isKdnaSourceDir`, `detectContainerFormat`.
38
+ - `detectContainerFormat` returns only `"kdna"` or `null`.
39
+ - `kdna_version: "2.0"` is no longer accepted. Only `"1.0"`.
40
+ - `payload.encoding` accepts only `"cbor"`. JSON payload bytes are rejected;
41
+ there is no production JSON fallback.
42
+ - Version-qualified public APIs (`V1_REQUIRED_DIR_ENTRIES`, `readV1Layout`,
43
+ `buildChecksumsV1`, `loadV1`, and the `./v1` export) are removed. Use the
44
+ unversioned APIs below.
45
+ - `kdna load --as=json` now returns Runtime Capsule format
46
+ (`type: "kdna.context.capsule"`) instead of raw `{status, content}`.
47
+
48
+ ### Added
49
+
50
+ - `REQUIRED_DIR_ENTRIES`, `readLayout`, `buildChecksums`, `load`, and
51
+ `loadAsset` are the sole public container/loading APIs.
52
+ - `buildCapsule(loadResult, v1, profile, opts)` — wraps load output as
53
+ `kdna.context.capsule` per `specs/runtime-capsule.md`.
54
+ - `parsePayloadEntry` — strict CBOR decoding for `payload.kdnab`.
55
+
56
+ ### Fixes
57
+
58
+ - Producer/consumer encoding alignment: `dev-pack.js`, `studio.js` write CBOR;
59
+ validation and loading decode CBOR, including encrypted envelopes.
60
+ - `loadV1Unsafe` extends/deps processing occurs before Capsule wrapping (Story 12 fix).
61
+ - All test fixtures converted to CBOR; golden files regenerated.
62
+
3
63
  ## v0.15.11 (2026-07-03)
4
64
 
5
65
  **Fix**: repair Core v1 container dispatcher module resolution in the
package/README.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # @aikdna/kdna-core
2
2
 
3
- Core library for local `.kdna` judgment assets.
4
-
5
- KDNA Core v1 defines the `.kdna` file format, schemas, secure container
6
- reader, LoadPlan contract, and runtime projection helpers used by the CLI,
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
- ## KDNA Core v1 API
20
+ ## Public API
22
21
 
23
- Use the `./v1` entrypoint for current KDNA Core v1 tooling:
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
- buildChecksumsV1
34
- } = require('@aikdna/kdna-core/v1');
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 compact = loadAuthorized('./asset.kdna', {
46
+ const capsule = loadAuthorized('./asset.kdna', {
47
47
  profile: 'compact',
48
- as: 'prompt'
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
- → agent/runtime context
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
- ## v1 Container
79
+ ## KDNA Asset Container
75
80
 
76
- A v1 `.kdna` container contains:
81
+ A `.kdna` asset contains:
77
82
 
78
83
  - `mimetype`
79
84
  - `kdna.json`
@@ -88,6 +93,22 @@ 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
+
100
+ ### Account/device external grants
101
+
102
+ RFC-0019 licensed assets can keep the encrypted container publicly available
103
+ while an issuer grants independent account/device access. `authorizeExternalKeyGrant()`
104
+ verifies the issuer signature, lease, revocation state, account, device, asset
105
+ UID/version/digest, and encrypted entry before returning a branded entitlement
106
+ and in-memory decrypt hook. A plain `{status: "active"}` object is not accepted.
107
+
108
+ Device private keys belong in the platform SecretStore. The returned session
109
+ must be disposed after loading; Agent-facing callers should receive only the
110
+ Runtime Capsule. Account grants never fall back to the password profile.
111
+
91
112
  ## Load Profiles
92
113
 
93
114
  `loadAuthorized()` supports:
@@ -104,15 +125,9 @@ Output formats:
104
125
 
105
126
  ## Boundary
106
127
 
107
- KDNA Core v1 is content-neutral. It validates file structure and loadability;
128
+ KDNA Core is content-neutral. It validates file structure and loadability;
108
129
  it does not rank judgment quality or endorse specific assets.
109
130
 
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
131
  ## License
117
132
 
118
133
  Apache-2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-core",
3
- "version": "0.15.11",
3
+ "version": "0.16.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"
@@ -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": {
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://kdna.ai/specs/kdna-envelope-external-grant-v1.schema.json",
4
+ "title": "KDNA external grant envelope v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["profile", "alg", "cek_derivation", "key_ref", "issuer_key_id", "entry_path", "plaintext_digest", "iv", "tag", "ciphertext"],
8
+ "properties": {
9
+ "profile": { "const": "kdna-envelope-external-grant-v1" },
10
+ "alg": { "const": "A256GCM" },
11
+ "cek_derivation": { "const": "HKDF-SHA256" },
12
+ "key_ref": { "type": "string", "minLength": 1, "maxLength": 256 },
13
+ "issuer_key_id": { "type": "string", "minLength": 1, "maxLength": 128 },
14
+ "entry_path": { "type": "string", "pattern": "^[^/].*[^/]$|^[^/]$", "maxLength": 256 },
15
+ "plaintext_digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
16
+ "iv": { "type": "string", "pattern": "^[A-Za-z0-9_-]{16}$" },
17
+ "tag": { "type": "string", "pattern": "^[A-Za-z0-9_-]{22}$" },
18
+ "ciphertext": { "type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$" }
19
+ }
20
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://kdna.ai/specs/kdna-key-grant-v1.schema.json",
4
+ "title": "KDNA account/device external key grant v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["profile", "grant_id", "issuer", "signing_key_id", "entitlement_id", "account_id", "device_id", "device_public_key", "device_signing_public_key", "asset", "issued_at", "refresh_after", "offline_grace_until", "expires_at", "status", "status_version", "wrap", "signature"],
8
+ "properties": {
9
+ "profile": { "const": "kdna-key-grant-v1" },
10
+ "grant_id": { "$ref": "#/$defs/id" },
11
+ "issuer": { "type": "string", "format": "uri", "maxLength": 256 },
12
+ "signing_key_id": { "$ref": "#/$defs/id" },
13
+ "entitlement_id": { "$ref": "#/$defs/id" },
14
+ "account_id": { "$ref": "#/$defs/id" },
15
+ "device_id": { "$ref": "#/$defs/id" },
16
+ "device_public_key": { "type": "string", "pattern": "^x25519:[A-Za-z0-9_-]{43}$" },
17
+ "device_signing_public_key": { "type": "string", "pattern": "^ed25519:[A-Za-z0-9_-]{43}$" },
18
+ "asset": {
19
+ "type": "object", "additionalProperties": false,
20
+ "required": ["asset_id", "asset_uid", "version", "digest", "entry_path", "ciphertext_digest", "key_ref", "issuer_key_id"],
21
+ "properties": {
22
+ "asset_id": { "type": "string", "minLength": 1, "maxLength": 256 },
23
+ "asset_uid": { "type": "string", "minLength": 1, "maxLength": 512 },
24
+ "version": { "type": "string", "minLength": 1, "maxLength": 128 },
25
+ "digest": { "$ref": "#/$defs/digest" },
26
+ "entry_path": { "type": "string", "minLength": 1, "maxLength": 256 },
27
+ "ciphertext_digest": { "$ref": "#/$defs/digest" },
28
+ "key_ref": { "type": "string", "minLength": 1, "maxLength": 256 },
29
+ "issuer_key_id": { "$ref": "#/$defs/id" }
30
+ }
31
+ },
32
+ "issued_at": { "type": "string", "format": "date-time" },
33
+ "refresh_after": { "type": "string", "format": "date-time" },
34
+ "offline_grace_until": { "type": "string", "format": "date-time" },
35
+ "expires_at": { "type": "string", "format": "date-time" },
36
+ "status": { "enum": ["active", "revoked", "expired"] },
37
+ "status_version": { "type": "integer", "minimum": 1 },
38
+ "wrap": {
39
+ "type": "object", "additionalProperties": false,
40
+ "required": ["alg", "ephemeral_public_key", "salt", "wrapped_cek"],
41
+ "properties": {
42
+ "alg": { "const": "X25519-HKDF-SHA256+A256KW" },
43
+ "ephemeral_public_key": { "type": "string", "pattern": "^x25519:[A-Za-z0-9_-]{43}$" },
44
+ "salt": { "type": "string", "pattern": "^[A-Za-z0-9_-]{22}$" },
45
+ "wrapped_cek": { "type": "string", "pattern": "^[A-Za-z0-9_-]{54}$" }
46
+ }
47
+ },
48
+ "signature": { "type": "string", "pattern": "^ed25519:[A-Za-z0-9_-]{86}$" }
49
+ },
50
+ "$defs": {
51
+ "id": { "type": "string", "pattern": "^[A-Za-z0-9._:@/-]{1,256}$" },
52
+ "digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
53
+ }
54
+ }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/aikdna/kdna/schema/load-plan.schema.json",
4
4
  "title": "KDNA LoadPlan v1",
5
- "description": "Machine-readable pre-load authorization and runtime planning result for KDNA Core v1 assets.",
5
+ "description": "Machine-readable pre-load authorization and runtime planning result for packaged KDNA assets.",
6
6
  "type": "object",
7
7
  "required": [
8
8
  "kdna_version",
@@ -22,8 +22,8 @@
22
22
  "properties": {
23
23
  "kdna_version": {
24
24
  "type": "string",
25
- "description": "KDNA Core version this manifest conforms to.",
26
- "enum": ["1.0", "2.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": ["json", "cbor"] },
105
+ "encoding": { "type": "string", "enum": ["cbor"] },
106
106
  "encrypted": { "type": "boolean" }
107
107
  }
108
108
  },
@@ -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.aikdna.kdna+zip';
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.
@@ -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 spec_version.');
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.format_version && manifest.format_version !== '2.0') {
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.format_version: invalid value "${manifest.format_version}". Expected "2.0".`,
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('KDNA_Core.json')) errors.push('required entry missing: KDNA_Core.json');
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 { createKdnaAssetReader } = require('./asset-reader.js');
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 any KDNA asset,
14
- * regardless of whether it came from a source directory, Container v1,
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 — original input type
22
- * @property {string} sourcePath — resolved absolute path
23
- * @property {string} format — 'v1-dir' | 'v1-container' | 'v2-container'
24
- * @property {string} mimetype — normalized 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 (CBOR/JSON)
27
- * @property {object|null} checksums — parsed checksums.json, or null
28
- * @property {Buffer|null} assetDigest — SHA-256 of entire container (file only)
29
- * @property {Buffer|null} containerBuffer — full container bytes (for ZIP inputs, may be null for dirs)
30
- * @property {Map<string, Buffer>|null} entries — raw entry map (for v1 dir layout; null for v2/container)
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 === 'v1-container') {
63
- return readV1Container(absPath, opts);
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 === MIMETYPE_V1) return 'v1-container';
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 (![MIMETYPE_V1, MIMETYPE_V2].includes(mimetype)) {
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: 'v1-dir',
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
- * Read a Container v1 (.kdna with v1 mimetype) into CanonicalAssetModel.
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: 'v1-container',
232
- mimetype: MIMETYPE_V1,
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
- MIMETYPE_V1,
293
- MIMETYPE_V2,
215
+ MIMETYPE,
294
216
  readAsset,
295
217
  detectFormat,
296
218
  getMimetype,
297
- isSourceDirectory,
219
+ isKdnaSourceDirectory,
298
220
  };
@@ -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 JSON.parse(Buffer.from(value).toString('utf8'));
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 JSON');
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) {