@aikdna/kdna-cli 0.19.2 → 0.20.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/LICENSE +1 -1
- package/README.md +42 -28
- package/package.json +4 -4
- package/skills/kdna-loader/SKILL.md +3 -1
- package/src/capsule-verify.js +70 -0
- package/src/cli.js +72 -47
- package/src/cmds/_common.js +66 -8
- package/src/cmds/domain.js +20 -1
- package/src/cmds/governance.js +1 -1
- package/src/cmds/protect.js +313 -0
- package/src/cmds/protect.js.bak +245 -0
- package/src/cmds/protocol.js +181 -0
- package/src/cmds/studio.js +7 -10
- package/src/cmds/workpack.js +875 -0
- package/src/dev-pack-v2.js +117 -0
- package/src/init.js +14 -6
- package/src/install.js +116 -2
- package/src/kdf-spec.js +42 -0
- package/src/package-store.js +29 -7
- package/src/paths.js +3 -2
- package/src/publish.js +92 -184
- package/src/registry.js +73 -3
- package/src/signature.js +39 -0
- package/src/verify.js +78 -0
- package/templates/cluster/README.md +1 -1
- package/templates/standard-domain/USAGE.md +2 -1
- package/templates/standard-domain/kdna.json +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// KDNA dev pack — v2 container support
|
|
2
|
+
// Produces KDNA Container v2 (.kdna files with CBOR payload.kdnab) from dev source directories.
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
|
|
7
|
+
let _cbor = null;
|
|
8
|
+
function getCbor() {
|
|
9
|
+
if (!_cbor) {
|
|
10
|
+
try {
|
|
11
|
+
_cbor = require('cbor-x');
|
|
12
|
+
} catch {
|
|
13
|
+
try {
|
|
14
|
+
_cbor = require('@aikdna/kdna-core/../cbor-x');
|
|
15
|
+
} catch {
|
|
16
|
+
throw new Error('cbor-x is required for dev pack v2. Install: npm install cbor-x');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return _cbor;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const KDNA_FILES = [
|
|
24
|
+
'KDNA_Core.json',
|
|
25
|
+
'KDNA_Patterns.json',
|
|
26
|
+
'KDNA_Scenarios.json',
|
|
27
|
+
'KDNA_Cases.json',
|
|
28
|
+
'KDNA_Reasoning.json',
|
|
29
|
+
'KDNA_Evolution.json',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
function packV2(sourceDir, manifest, options = {}) {
|
|
33
|
+
const abs = path.resolve(sourceDir);
|
|
34
|
+
|
|
35
|
+
// 1. Read source files
|
|
36
|
+
const judgment = {};
|
|
37
|
+
for (const f of KDNA_FILES) {
|
|
38
|
+
const filePath = path.join(abs, f);
|
|
39
|
+
if (fs.existsSync(filePath)) {
|
|
40
|
+
try {
|
|
41
|
+
judgment[
|
|
42
|
+
f
|
|
43
|
+
.replace(/^KDNA_/, '')
|
|
44
|
+
.replace(/\.json$/, '')
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
] = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
47
|
+
} catch {
|
|
48
|
+
/* skip unreadable */
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. Build CBOR payload
|
|
54
|
+
const payload = {
|
|
55
|
+
kind: 'kdna.payload',
|
|
56
|
+
payload_version: '2.0',
|
|
57
|
+
domain: { name: manifest.name || '', version: manifest.version || '0.1.0' },
|
|
58
|
+
judgment,
|
|
59
|
+
profiles: {},
|
|
60
|
+
integrity: {
|
|
61
|
+
source_tree_digest: `sha256:${computeDirHash(abs)}`,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const payloadBuf = getCbor().encode(payload);
|
|
65
|
+
|
|
66
|
+
// 3. Build manifest (v2)
|
|
67
|
+
const v2Manifest = {
|
|
68
|
+
...manifest,
|
|
69
|
+
format_version: '2.0',
|
|
70
|
+
spec_version: '2.0',
|
|
71
|
+
container: {
|
|
72
|
+
type: 'kdna-container-v2',
|
|
73
|
+
payload: 'payload.kdnab',
|
|
74
|
+
payload_encoding: 'cbor',
|
|
75
|
+
payload_schema: 'kdna-payload-v2',
|
|
76
|
+
payload_digest: `sha256:${crypto.createHash('sha256').update(payloadBuf).digest('hex')}`,
|
|
77
|
+
},
|
|
78
|
+
runtime: {
|
|
79
|
+
min_runtime_version: '0.3.0',
|
|
80
|
+
load_contract: 'context-capsule-v1',
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
entries: {
|
|
86
|
+
mimetype: 'application/vnd.aikdna.kdna+zip',
|
|
87
|
+
'kdna.json': JSON.stringify(v2Manifest, null, 2),
|
|
88
|
+
'payload.kdnab': payloadBuf,
|
|
89
|
+
},
|
|
90
|
+
manifest: v2Manifest,
|
|
91
|
+
payload,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function computeDirHash(dir) {
|
|
96
|
+
const files = fs
|
|
97
|
+
.readdirSync(dir)
|
|
98
|
+
.filter((f) => fs.statSync(path.join(dir, f)).isFile())
|
|
99
|
+
.sort();
|
|
100
|
+
const hash = crypto.createHash('sha256');
|
|
101
|
+
for (const f of files) {
|
|
102
|
+
hash.update(f);
|
|
103
|
+
hash.update(fs.readFileSync(path.join(dir, f)));
|
|
104
|
+
}
|
|
105
|
+
return hash.digest('hex');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function verifySourceIntegrity(sourceDir, expectedDigest) {
|
|
109
|
+
if (!expectedDigest) return { valid: false, error: 'No expected digest provided' };
|
|
110
|
+
const actual = `sha256:${computeDirHash(sourceDir)}`;
|
|
111
|
+
if (actual !== expectedDigest) {
|
|
112
|
+
return { valid: false, error: `Digest mismatch: expected ${expectedDigest}, got ${actual}` };
|
|
113
|
+
}
|
|
114
|
+
return { valid: true, digest: actual };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { packV2, verifySourceIntegrity, computeDirHash };
|
package/src/init.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* kdna init <name> —
|
|
2
|
+
* kdna init <name> — Deprecated alias for kdna dev scaffold <name>.
|
|
3
|
+
* kdna dev scaffold <name> — Scaffold a non-canonical dev source workspace.
|
|
3
4
|
* kdna cluster init <name> — Scaffold a new KDNA cluster from template.
|
|
4
5
|
*/
|
|
5
6
|
|
|
@@ -27,9 +28,9 @@ function copyRecursive(src, dest, replacements) {
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function cmdInit(name) {
|
|
31
|
+
function cmdInit(name, options = {}) {
|
|
31
32
|
if (!name) {
|
|
32
|
-
console.error('Error: Domain name required. Usage: kdna
|
|
33
|
+
console.error('Error: Domain name required. Usage: kdna dev scaffold <name>');
|
|
33
34
|
process.exit(1);
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -56,8 +57,15 @@ function cmdInit(name) {
|
|
|
56
57
|
'YYYY-MM-DD': today,
|
|
57
58
|
});
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
if (options.deprecatedAlias) {
|
|
61
|
+
console.warn(
|
|
62
|
+
'Warning: kdna init is deprecated. Use kdna dev scaffold for dev source workspaces.',
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
console.log(`✓ Created non-canonical KDNA dev source workspace: ${targetDir}/`);
|
|
60
66
|
console.log(` Files: KDNA_Core.json, KDNA_Patterns.json, kdna.json, tests/before-after.json`);
|
|
67
|
+
console.log(' This workspace is not a trusted KDNA asset.');
|
|
68
|
+
console.log(' To create a trusted .kdna asset, use KDNA Studio compile/export.');
|
|
61
69
|
|
|
62
70
|
// Run structural validation (lint + schema only). Content quality checks
|
|
63
71
|
// are for publish time, not scaffold time — the template contains placeholders
|
|
@@ -92,8 +100,8 @@ function cmdInit(name) {
|
|
|
92
100
|
);
|
|
93
101
|
console.log(` 3. Edit ${targetDir}/kdna.json — set author, description, repo`);
|
|
94
102
|
console.log(` 4. Run: kdna dev validate ${name} (structural check)`);
|
|
95
|
-
console.log(` 5. Run: kdna publish --check ${name} (
|
|
96
|
-
console.log(` 6.
|
|
103
|
+
console.log(` 5. Run: kdna publish --check ${name} (dev readiness check only)`);
|
|
104
|
+
console.log(` 6. Use KDNA Studio to Human Lock, compile, and export a trusted .kdna asset`);
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
/**
|
package/src/install.js
CHANGED
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
sha256File,
|
|
28
28
|
readContainer,
|
|
29
29
|
readContainerJson,
|
|
30
|
+
readContainerEntry,
|
|
30
31
|
verifyAsset,
|
|
31
32
|
} = require('./package-store');
|
|
32
33
|
|
|
@@ -153,6 +154,24 @@ function ensureDir(dir) {
|
|
|
153
154
|
fs.mkdirSync(dir, { recursive: true });
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
// ─── Audit Log ───────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
function auditLog(action, details) {
|
|
160
|
+
try {
|
|
161
|
+
const logDir = path.join(USER_KDNA_DIR, 'logs');
|
|
162
|
+
ensureDir(logDir);
|
|
163
|
+
const logFile = path.join(logDir, 'audit.log');
|
|
164
|
+
const entry = JSON.stringify({
|
|
165
|
+
timestamp: new Date().toISOString(),
|
|
166
|
+
action,
|
|
167
|
+
...details,
|
|
168
|
+
});
|
|
169
|
+
fs.appendFileSync(logFile, entry + '\n');
|
|
170
|
+
} catch {
|
|
171
|
+
/* audit is best-effort, never block on it */
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
156
175
|
// ─── Source parsing ─────────────────────────────────────────────────────
|
|
157
176
|
|
|
158
177
|
function parseSource(input) {
|
|
@@ -306,13 +325,14 @@ function cmdInstallExtended(input, args = []) {
|
|
|
306
325
|
|
|
307
326
|
const yes = args.includes('--yes');
|
|
308
327
|
const jsonMode = args.includes('--json');
|
|
328
|
+
const trusted = args.includes('--trusted');
|
|
309
329
|
const source = parseSource(input);
|
|
310
330
|
|
|
311
331
|
switch (source.type) {
|
|
312
332
|
case 'registry':
|
|
313
333
|
return installFromRegistry(source.parsed, yes, jsonMode);
|
|
314
334
|
case 'local-file':
|
|
315
|
-
return installFromLocalFile(source.path, yes, jsonMode);
|
|
335
|
+
return installFromLocalFile(source.path, yes, jsonMode, trusted);
|
|
316
336
|
}
|
|
317
337
|
}
|
|
318
338
|
|
|
@@ -411,6 +431,7 @@ function installSingleFromUrl({ entry, scope }, jsonMode = false) {
|
|
|
411
431
|
|
|
412
432
|
verifySignature({ assetPath: tmpFile, scope, entry, lenient: true });
|
|
413
433
|
|
|
434
|
+
auditLog('install', { name: entry.name, version: entry.version, source: 'registry' });
|
|
414
435
|
const installed = installAsset({
|
|
415
436
|
sourcePath: tmpFile,
|
|
416
437
|
name: entry.name,
|
|
@@ -483,7 +504,7 @@ function installCluster(clusterEntry, resolver, _yes, jsonMode = false) {
|
|
|
483
504
|
}
|
|
484
505
|
}
|
|
485
506
|
|
|
486
|
-
function installFromLocalFile(filePath,
|
|
507
|
+
function installFromLocalFile(filePath, yes, jsonMode = false, trusted = false) {
|
|
487
508
|
const abs = path.resolve(filePath);
|
|
488
509
|
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) error(`Not a file: ${abs}`);
|
|
489
510
|
if (!abs.endsWith('.kdna')) error(`Not a .kdna asset: ${abs}`, EXIT.INPUT_ERROR);
|
|
@@ -493,6 +514,98 @@ function installFromLocalFile(filePath, _yes, jsonMode = false) {
|
|
|
493
514
|
if (!declared || !/^@[a-z][a-z0-9-]*\/[a-z][a-z0-9_]*$/.test(declared)) {
|
|
494
515
|
error(scopedNameError('package kdna.json.name', declared), EXIT.INPUT_ERROR);
|
|
495
516
|
}
|
|
517
|
+
|
|
518
|
+
// ── Trust checks for local install ──────────────────────────
|
|
519
|
+
const trustLevel = { label: 'local_unverified', issues: [] };
|
|
520
|
+
|
|
521
|
+
// Check mimetype (plain text, not JSON)
|
|
522
|
+
try {
|
|
523
|
+
const mimeEntry = readContainerEntry(abs, 'mimetype');
|
|
524
|
+
const hasMimetype =
|
|
525
|
+
mimeEntry && mimeEntry.toString().trim() === 'application/vnd.aikdna.kdna+zip';
|
|
526
|
+
if (!hasMimetype) trustLevel.issues.push('missing or incorrect mimetype');
|
|
527
|
+
} catch {
|
|
528
|
+
trustLevel.issues.push('no mimetype entry');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Check for KDNA_Core.json (may be encrypted — skip if unreadable)
|
|
532
|
+
try {
|
|
533
|
+
const hasCore = readContainerJson(abs, 'KDNA_Core.json');
|
|
534
|
+
if (!hasCore) trustLevel.issues.push('missing KDNA_Core.json');
|
|
535
|
+
} catch {
|
|
536
|
+
trustLevel.issues.push('KDNA_Core.json unreadable (may be encrypted)');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Check content_digest
|
|
540
|
+
if (!manifest.content_digest) trustLevel.issues.push('no content_digest');
|
|
541
|
+
|
|
542
|
+
// Check authoring provenance
|
|
543
|
+
if (!manifest.authoring?.created_by) {
|
|
544
|
+
trustLevel.issues.push('no authoring provenance (not Studio-compiled)');
|
|
545
|
+
} else if (manifest.authoring.created_by === 'manual-dev-source') {
|
|
546
|
+
trustLevel.issues.push('created by manual dev source (not Studio-compiled)');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Try signature verification if present
|
|
550
|
+
if (manifest.signature) {
|
|
551
|
+
try {
|
|
552
|
+
const sigResult = verifyAsset(abs, { requireSignature: true });
|
|
553
|
+
if (sigResult.signature_valid) {
|
|
554
|
+
trustLevel.label = 'local_signature_verified';
|
|
555
|
+
trustLevel.issues = trustLevel.issues.filter((i) => !i.includes('not Studio-compiled'));
|
|
556
|
+
} else {
|
|
557
|
+
trustLevel.issues.push('signature present but failed verification');
|
|
558
|
+
}
|
|
559
|
+
} catch {
|
|
560
|
+
trustLevel.issues.push('signature verification failed');
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// --trusted mode: signature must be present and verified
|
|
565
|
+
if (trusted && trustLevel.issues.length > 0) {
|
|
566
|
+
const reasons = trustLevel.issues.map((i) => ` - ${i}`).join('\n');
|
|
567
|
+
error(
|
|
568
|
+
`Trust verification failed for local .kdna asset:\n${reasons}\n\n` +
|
|
569
|
+
`Use 'kdna install <file.kdna>' without --trusted to install anyway (unverified local asset).`,
|
|
570
|
+
EXIT.TRUST_FAILED,
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
// Signature is required for --trusted mode
|
|
574
|
+
if (trusted && !manifest.signature) {
|
|
575
|
+
error(
|
|
576
|
+
'--trusted requires a signed .kdna asset. This asset has no signature.\n' +
|
|
577
|
+
'Use Studio compile/export with --sign, or install without --trusted.',
|
|
578
|
+
EXIT.TRUST_FAILED,
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
// For tested+ quality_badge, require Studio-compatible authoring provenance
|
|
582
|
+
const highTrustBadges = new Set(['tested', 'validated', 'expert_reviewed', 'production_ready']);
|
|
583
|
+
if (
|
|
584
|
+
trusted &&
|
|
585
|
+
highTrustBadges.has(manifest.quality_badge) &&
|
|
586
|
+
(!manifest.authoring?.compiler || !manifest.authoring?.compiler_version)
|
|
587
|
+
) {
|
|
588
|
+
error(
|
|
589
|
+
`--trusted requires Studio-compatible authoring provenance for quality_badge "${manifest.quality_badge}".\n` +
|
|
590
|
+
'This asset lacks compiler provenance. Re-publish through Studio pipeline.',
|
|
591
|
+
EXIT.TRUST_FAILED,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (!jsonMode) {
|
|
596
|
+
if (trustLevel.label === 'local_signature_verified') {
|
|
597
|
+
console.log(` Trust: ${trustLevel.label}`);
|
|
598
|
+
} else {
|
|
599
|
+
console.warn(` Trust: ${trustLevel.label} — ${trustLevel.issues.join('; ')}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
auditLog('install', {
|
|
604
|
+
name: declared,
|
|
605
|
+
version: manifest.version,
|
|
606
|
+
source: 'local-file',
|
|
607
|
+
path: abs,
|
|
608
|
+
});
|
|
496
609
|
const installed = installAsset({
|
|
497
610
|
sourcePath: abs,
|
|
498
611
|
name: declared,
|
|
@@ -524,6 +637,7 @@ function installFromLocalFile(filePath, _yes, jsonMode = false) {
|
|
|
524
637
|
function cmdRemove(input) {
|
|
525
638
|
const parsed = parseName(input);
|
|
526
639
|
if (!parsed) error(`Invalid name "${input}". Use @scope/name or bare name.`);
|
|
640
|
+
auditLog('remove', { name: parsed.full });
|
|
527
641
|
if (!removeInstalled(parsed.full)) {
|
|
528
642
|
console.log(`${parsed.full} is not installed.`);
|
|
529
643
|
return;
|
package/src/kdf-spec.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// KDNA Key Derivation Parameters — canonical reference
|
|
2
|
+
// Per RFC-0008 and RFC-0009, these parameters MUST be used for all KDF operations.
|
|
3
|
+
|
|
4
|
+
const KDF_PARAMS = {
|
|
5
|
+
'kdna-password-protected-v1': {
|
|
6
|
+
algorithm: 'Argon2id',
|
|
7
|
+
version: 0x13,
|
|
8
|
+
memoryCostKiB: 65536, // 64 MiB
|
|
9
|
+
timeCost: 3, // 3 iterations
|
|
10
|
+
parallelism: 4, // 4 lanes
|
|
11
|
+
saltLength: 32, // 256-bit random salt
|
|
12
|
+
hashLength: 32, // 256-bit derived key
|
|
13
|
+
tagLength: 16, // AES-256-GCM authentication tag
|
|
14
|
+
},
|
|
15
|
+
'kdna-licensed-entry-v1': {
|
|
16
|
+
algorithm: 'HKDF-SHA256',
|
|
17
|
+
info: 'kdna-licensed-entry-v1',
|
|
18
|
+
salt: null, // No salt — deterministic from master key
|
|
19
|
+
keyLength: 32, // 256-bit AES key
|
|
20
|
+
wrapAlgorithm: 'AES-256-KW',
|
|
21
|
+
wireTagSize: 16, // Tag prepended to ciphertext in wire format
|
|
22
|
+
contentEncryption: 'AES-256-GCM',
|
|
23
|
+
},
|
|
24
|
+
'kdna-identity-backup-v1': {
|
|
25
|
+
algorithm: 'PBKDF2-SHA256',
|
|
26
|
+
iterations: 100000,
|
|
27
|
+
keyLength: 32, // 256-bit AES key
|
|
28
|
+
ivLength: 16, // 128-bit random IV
|
|
29
|
+
encryption: 'AES-256-CBC',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function validateParameters(profile) {
|
|
34
|
+
const params = KDF_PARAMS[profile];
|
|
35
|
+
if (!params)
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Unknown KDF profile: ${profile}. Valid: ${Object.keys(KDF_PARAMS).join(', ')}`,
|
|
38
|
+
);
|
|
39
|
+
return params;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { KDF_PARAMS, validateParameters };
|
package/src/package-store.js
CHANGED
|
@@ -11,6 +11,25 @@ if (typeof core.createKdnaAssetReader !== 'function') {
|
|
|
11
11
|
|
|
12
12
|
const assetReader = core.createKdnaAssetReader();
|
|
13
13
|
|
|
14
|
+
const V1_ENTRIES = [
|
|
15
|
+
'KDNA_Core.json',
|
|
16
|
+
'KDNA_Patterns.json',
|
|
17
|
+
'KDNA_Scenarios.json',
|
|
18
|
+
'KDNA_Cases.json',
|
|
19
|
+
'KDNA_Reasoning.json',
|
|
20
|
+
'KDNA_Evolution.json',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function validateContainerV2(asset, assetPath) {
|
|
24
|
+
const hasPayload = asset.entries.has('payload.kdnab');
|
|
25
|
+
if (hasPayload) return; // v2, OK
|
|
26
|
+
const hasV1 = V1_ENTRIES.some((e) => asset.entries.has(e));
|
|
27
|
+
if (!hasV1) return; // neither v1 nor v2, probably empty — let caller decide
|
|
28
|
+
const found = V1_ENTRIES.filter((e) => asset.entries.has(e));
|
|
29
|
+
const msg = `ERR_LEGACY_PLAINTEXT_CONTAINER: This .kdna uses the removed v1 plaintext ZIP format (found: ${found.join(', ')}). Rebuild from source with KDNA Container v2.`;
|
|
30
|
+
throw Object.assign(new Error(msg), { code: 'ERR_LEGACY_PLAINTEXT_CONTAINER' });
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
const INDEX_VERSION = 2;
|
|
15
34
|
|
|
16
35
|
function ensureDir(dir) {
|
|
@@ -78,14 +97,16 @@ function listContainerEntries(kdnaPath) {
|
|
|
78
97
|
|
|
79
98
|
function readContainer(kdnaPath, options = {}) {
|
|
80
99
|
const asset = assetReader.openSync(kdnaPath);
|
|
100
|
+
validateContainerV2(asset, kdnaPath);
|
|
101
|
+
const dataMap = assetReader.readDataMapSync(asset, undefined, options);
|
|
81
102
|
return {
|
|
82
|
-
manifest:
|
|
83
|
-
core:
|
|
84
|
-
patterns:
|
|
85
|
-
scenarios:
|
|
86
|
-
cases:
|
|
87
|
-
reasoning:
|
|
88
|
-
evolution:
|
|
103
|
+
manifest: dataMap['kdna.json'] || {},
|
|
104
|
+
core: dataMap['KDNA_Core.json'] || {},
|
|
105
|
+
patterns: dataMap['KDNA_Patterns.json'] || {},
|
|
106
|
+
scenarios: dataMap['KDNA_Scenarios.json'] || null,
|
|
107
|
+
cases: dataMap['KDNA_Cases.json'] || null,
|
|
108
|
+
reasoning: dataMap['KDNA_Reasoning.json'] || null,
|
|
109
|
+
evolution: dataMap['KDNA_Evolution.json'] || null,
|
|
89
110
|
files: assetReader.listEntriesSync(asset),
|
|
90
111
|
};
|
|
91
112
|
}
|
|
@@ -222,6 +243,7 @@ module.exports = {
|
|
|
222
243
|
readContainer,
|
|
223
244
|
readContainerEntry,
|
|
224
245
|
readContainerJson,
|
|
246
|
+
readAssetManifest,
|
|
225
247
|
listContainerEntries,
|
|
226
248
|
verifyAsset,
|
|
227
249
|
getInstalled,
|
package/src/paths.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// KDNA shared path configuration — canonical source for ~/.kdna structure
|
|
2
2
|
// Spec: docs/local-kdna-home-spec.md
|
|
3
|
+
// NOTE: domains/ is NOT part of the runtime model (see local-kdna-home-spec.md §Invariants).
|
|
4
|
+
// The domains field below is retained ONLY for legacy migration. New code MUST use packages/.
|
|
3
5
|
|
|
4
6
|
const path = require('path');
|
|
5
7
|
|
|
@@ -10,14 +12,13 @@ const PATHS = {
|
|
|
10
12
|
root: KDNA_HOME,
|
|
11
13
|
config: path.join(KDNA_HOME, 'config.json'),
|
|
12
14
|
identity: path.join(KDNA_HOME, 'identity'),
|
|
15
|
+
// LEGACY — domains/ is not part of the runtime model. Retained for migration only.
|
|
13
16
|
domains: {
|
|
14
17
|
root: path.join(KDNA_HOME, 'domains'),
|
|
15
18
|
official: path.join(KDNA_HOME, 'domains', 'official'),
|
|
16
19
|
local: path.join(KDNA_HOME, 'domains', 'local'),
|
|
17
20
|
private: path.join(KDNA_HOME, 'domains', 'private'),
|
|
18
|
-
// Legacy flat path — used for migration only
|
|
19
21
|
legacy: path.join(KDNA_HOME, 'domains'),
|
|
20
|
-
// All three directories for scanning
|
|
21
22
|
all: [
|
|
22
23
|
path.join(KDNA_HOME, 'domains', 'official'),
|
|
23
24
|
path.join(KDNA_HOME, 'domains', 'local'),
|