@aikdna/kdna-cli 0.17.0 → 0.18.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/src/setup.js CHANGED
@@ -15,9 +15,11 @@
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
 
18
- const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
19
- const DOMAINS_DIR = path.join(USER_KDNA_DIR, 'domains');
20
- const CLUSTERS_DIR = path.join(USER_KDNA_DIR, 'clusters');
18
+ const PATHS = require('./paths');
19
+
20
+ const USER_KDNA_DIR = PATHS.root;
21
+ const DOMAINS_DIR = PATHS.domains.root;
22
+ const CLUSTERS_DIR = PATHS.clusters;
21
23
  const SKILLS_REPO = 'https://raw.githubusercontent.com/aikdna/kdna-skills/main';
22
24
 
23
25
  const AGENTS = [
@@ -119,15 +121,23 @@ async function cmdSetup() {
119
121
  const pkg = require(path.join(__dirname, '..', 'package.json'));
120
122
  log(`KDNA CLI v${pkg.version}`);
121
123
 
122
- // 2. KDNA data root
123
- ensureDir(DOMAINS_DIR);
124
+ // 2. KDNA data root — .kdna asset store
125
+ ensureDir(PATHS.root);
126
+ ensureDir(PATHS.packages);
124
127
  ensureDir(CLUSTERS_DIR);
128
+ ensureDir(PATHS.registry);
129
+ ensureDir(PATHS.traces);
130
+ ensureDir(PATHS.feedback);
131
+ ensureDir(PATHS.evals);
132
+ ensureDir(PATHS.cache);
133
+ ensureDir(PATHS.identity);
134
+ ensureDir(PATHS.licenses);
125
135
  log(`Data root: ${USER_KDNA_DIR}/`);
126
136
 
127
- // 2b. Clean legacy (un-scoped) domain directories from pre-v0.7
137
+ // 2b. Directory installs are not part of the runtime model.
128
138
  if (fs.existsSync(DOMAINS_DIR)) {
129
139
  const legacy = fs.readdirSync(DOMAINS_DIR).filter((e) => {
130
- if (e.startsWith('@') || e.startsWith('.')) return false;
140
+ if (e.startsWith('.')) return false;
131
141
  try {
132
142
  return fs.statSync(path.join(DOMAINS_DIR, e)).isDirectory();
133
143
  } catch {
@@ -136,19 +146,8 @@ async function cmdSetup() {
136
146
  });
137
147
  if (legacy.length) {
138
148
  console.log('');
139
- warn(
140
- `Removing ${legacy.length} legacy (un-scoped) domain director${legacy.length > 1 ? 'ies' : 'y'}:`,
141
- );
142
- for (const d of legacy) {
143
- const dPath = path.join(DOMAINS_DIR, d);
144
- try {
145
- fs.rmSync(dPath, { recursive: true, force: true });
146
- log(` removed ~/.kdna/domains/${d}/`);
147
- } catch (e) {
148
- warn(` could not remove ~/.kdna/domains/${d}/ — ${e.message}`);
149
- console.log(` To remove manually: rm -rf ~/.kdna/domains/${d}/`);
150
- }
151
- }
149
+ warn(`Ignoring ${legacy.length} legacy domain director${legacy.length > 1 ? 'ies' : 'y'}.`);
150
+ console.log(' Runtime assets now live under ~/.kdna/packages/ as .kdna files.');
152
151
  console.log(' Re-install with: kdna install @aikdna/<name>');
153
152
  console.log('');
154
153
  }
package/src/verify.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * kdna verify <name> — Quality signal across three layers.
2
+ * kdna verify <name|file.kdna> — Quality signal across three layers.
3
3
  *
4
4
  * --structure files exist, schema OK
5
- * --trust sha256 + Ed25519 signature against scope trust key
5
+ * --trust asset digest + Ed25519 signature against scope trust key
6
6
  * --judgment v2.1 governance fields (boundary, applies_when, eval cases)
7
7
  *
8
8
  * No flag = run all three.
@@ -17,9 +17,9 @@
17
17
 
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
- const crypto = require('crypto');
21
- const { RegistryResolver, parseName } = require('./registry');
22
- const { EXIT } = require('./cmds/_common');
20
+ const { RegistryResolver, parseName, registryTrustIssues, isEntryRevoked } = require('./registry');
21
+ const { EXIT, isYesNoSelfCheck } = require('./cmds/_common');
22
+ const { licenseDecryptOptionsForManifest } = require('./cmds/license');
23
23
 
24
24
  let validateManifestFn;
25
25
  try {
@@ -28,8 +28,14 @@ try {
28
28
  // kdna-core not available — manifest validation skipped
29
29
  }
30
30
 
31
- const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
32
- const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
31
+ const {
32
+ getInstalled,
33
+ listContainerEntries,
34
+ readContainerEntry,
35
+ readContainerJson,
36
+ resolveAsset,
37
+ verifyAsset,
38
+ } = require('./package-store');
33
39
 
34
40
  function readJson(p) {
35
41
  try {
@@ -39,11 +45,84 @@ function readJson(p) {
39
45
  }
40
46
  }
41
47
 
48
+ function directoryView(root) {
49
+ return {
50
+ kind: 'directory',
51
+ exists(name) {
52
+ return fs.existsSync(path.join(root, name));
53
+ },
54
+ readJson(name) {
55
+ return readJson(path.join(root, name));
56
+ },
57
+ readText(name) {
58
+ try {
59
+ return fs.readFileSync(path.join(root, name), 'utf8');
60
+ } catch {
61
+ return '';
62
+ }
63
+ },
64
+ listDirFiles(dirName) {
65
+ const dir = path.join(root, dirName);
66
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return [];
67
+ return fs.readdirSync(dir);
68
+ },
69
+ };
70
+ }
71
+
72
+ function assetView(kdnaPath, options = {}) {
73
+ const entries = new Set(listContainerEntries(kdnaPath));
74
+ return {
75
+ kind: 'asset',
76
+ path: kdnaPath,
77
+ exists(name) {
78
+ return entries.has(name);
79
+ },
80
+ readJson(name) {
81
+ return readContainerJson(kdnaPath, name, options);
82
+ },
83
+ readText(name) {
84
+ if (!entries.has(name)) return '';
85
+ return readContainerEntry(kdnaPath, name).toString('utf8');
86
+ },
87
+ listDirFiles(dirName) {
88
+ const prefix = `${dirName.replace(/\/+$/, '')}/`;
89
+ const files = [];
90
+ for (const entryName of entries) {
91
+ if (!entryName.startsWith(prefix)) continue;
92
+ const rest = entryName.slice(prefix.length);
93
+ if (rest && !rest.includes('/')) files.push(rest);
94
+ }
95
+ return files;
96
+ },
97
+ };
98
+ }
99
+
100
+ function asView(input, options = {}) {
101
+ if (input && typeof input.exists === 'function') return input;
102
+ return directoryView(input, options);
103
+ }
104
+
105
+ function readJsonFromView(view, entryName, issues = null) {
106
+ try {
107
+ return view.readJson(entryName);
108
+ } catch (e) {
109
+ if (issues) issues.push({ severity: 'error', msg: `${entryName}: ${e.message}` });
110
+ return null;
111
+ }
112
+ }
113
+
42
114
  // ─── Structure layer ───────────────────────────────────────────────────
43
115
 
44
- function checkStructure(destDir) {
116
+ function checkStructure(input, options = {}) {
117
+ const view = asView(input);
45
118
  const issues = [];
46
119
  const passed = [];
120
+ if (options.licenseError) {
121
+ issues.push({
122
+ severity: 'error',
123
+ msg: `license required to verify encrypted entries: ${options.licenseError}`,
124
+ });
125
+ }
47
126
 
48
127
  const required = ['KDNA_Core.json', 'KDNA_Patterns.json', 'kdna.json'];
49
128
  const optional = [
@@ -54,7 +133,7 @@ function checkStructure(destDir) {
54
133
  ];
55
134
 
56
135
  for (const f of required) {
57
- if (!fs.existsSync(path.join(destDir, f))) {
136
+ if (!view.exists(f)) {
58
137
  issues.push({ severity: 'error', msg: `required file missing: ${f}` });
59
138
  } else {
60
139
  passed.push(`has ${f}`);
@@ -63,7 +142,7 @@ function checkStructure(destDir) {
63
142
 
64
143
  // Validate kdna.json against canonical manifest schema
65
144
  if (validateManifestFn) {
66
- const manifest = readJson(path.join(destDir, 'kdna.json'));
145
+ const manifest = readJsonFromView(view, 'kdna.json', issues);
67
146
  if (manifest) {
68
147
  const mResult = validateManifestFn(manifest);
69
148
  for (const e of mResult.errors) issues.push({ severity: 'error', msg: e });
@@ -73,12 +152,12 @@ function checkStructure(destDir) {
73
152
  }
74
153
 
75
154
  for (const f of optional) {
76
- if (fs.existsSync(path.join(destDir, f))) passed.push(`has ${f}`);
155
+ if (view.exists(f)) passed.push(`has ${f}`);
77
156
  }
78
157
 
79
158
  // Schema check using kdna-core if available
80
159
  try {
81
- const core = readJson(path.join(destDir, 'KDNA_Core.json'));
160
+ const core = options.licenseError ? null : readJsonFromView(view, 'KDNA_Core.json', issues);
82
161
  if (core) {
83
162
  if (!core.axioms || !Array.isArray(core.axioms) || core.axioms.length === 0) {
84
163
  issues.push({ severity: 'error', msg: 'KDNA_Core.axioms missing or empty' });
@@ -90,7 +169,7 @@ function checkStructure(destDir) {
90
169
  issues.push({ severity: 'warn', msg: 'KDNA_Core.stances missing or empty' });
91
170
  }
92
171
  }
93
- const pat = readJson(path.join(destDir, 'KDNA_Patterns.json'));
172
+ const pat = options.licenseError ? null : readJsonFromView(view, 'KDNA_Patterns.json', issues);
94
173
  if (pat) {
95
174
  if (!pat.misunderstandings || pat.misunderstandings.length === 0) {
96
175
  issues.push({ severity: 'warn', msg: 'KDNA_Patterns.misunderstandings missing or empty' });
@@ -111,11 +190,12 @@ function checkStructure(destDir) {
111
190
 
112
191
  // ─── Trust layer ───────────────────────────────────────────────────────
113
192
 
114
- function checkTrust(destDir, scope, entry) {
193
+ function checkTrust(input, scope, entry, options = {}) {
194
+ const view = asView(input);
115
195
  const issues = [];
116
196
  const passed = [];
117
197
 
118
- const manifest = readJson(path.join(destDir, 'kdna.json'));
198
+ const manifest = readJsonFromView(view, 'kdna.json', issues);
119
199
  if (!manifest) {
120
200
  issues.push({ severity: 'error', msg: 'kdna.json missing — cannot verify trust' });
121
201
  return { layer: 'trust', issues, passed };
@@ -151,58 +231,56 @@ function checkTrust(destDir, scope, entry) {
151
231
  });
152
232
  } else {
153
233
  passed.push('embedded public_key_pem present');
234
+ }
154
235
 
155
- // Recompute fingerprint
156
- const fp =
157
- 'ed25519:' + crypto.createHash('sha256').update(manifest.author.public_key_pem).digest('hex');
158
- if (fp !== manifest.author.pubkey) {
236
+ if (options.assetPath) {
237
+ const verification = verifyAsset(options.assetPath, { requireSignature: true });
238
+ for (const warning of verification.warnings || []) {
239
+ if (!/signature missing/.test(warning)) issues.push({ severity: 'warn', msg: warning });
240
+ }
241
+ for (const err of verification.errors || []) {
242
+ if (/signature|author\.|public_key|pubkey|fingerprint|Ed25519/i.test(err)) {
243
+ issues.push({ severity: 'error', msg: err });
244
+ }
245
+ }
246
+ if (verification.signature_valid === true) {
247
+ passed.push('Ed25519 signature VALID over canonical payload');
248
+ } else if (manifest.signature) {
159
249
  issues.push({
160
250
  severity: 'error',
161
- msg: 'embedded PEM does not match author.pubkey fingerprint',
251
+ msg: 'Ed25519 signature INVALID or unavailable',
162
252
  });
163
- } else {
164
- passed.push('PEM fingerprint matches author.pubkey');
165
-
166
- // Full Ed25519 verify
167
- try {
168
- const files = fs
169
- .readdirSync(destDir)
170
- .filter((f) => f.endsWith('.json'))
171
- .sort();
172
- const parts = [];
173
- for (const f of files) {
174
- const full = path.join(destDir, f);
175
- let buf;
176
- if (f === 'kdna.json') {
177
- const obj = JSON.parse(fs.readFileSync(full, 'utf8'));
178
- delete obj.signature;
179
- delete obj._source;
180
- buf = Buffer.from(JSON.stringify(obj));
181
- } else {
182
- buf = fs.readFileSync(full);
183
- }
184
- const hash = crypto.createHash('sha256').update(buf).digest('hex');
185
- parts.push(`${f}:${hash}`);
186
- }
187
- const payload = parts.join('\n');
188
- const sigHex = manifest.signature.replace(/^ed25519:/, '');
189
- const publicKey = crypto.createPublicKey(manifest.author.public_key_pem);
190
- const ok = crypto.verify(null, Buffer.from(payload), publicKey, Buffer.from(sigHex, 'hex'));
191
- if (ok) passed.push('Ed25519 signature VALID over canonical payload');
192
- else
193
- issues.push({
194
- severity: 'error',
195
- msg: 'Ed25519 signature INVALID — package may be tampered',
196
- });
197
- } catch (e) {
198
- issues.push({ severity: 'error', msg: `signature verify failed: ${e.message}` });
199
- }
200
253
  }
201
254
  }
202
255
 
203
- // 4. sha256 vs registry (if entry provided)
204
- if (entry?.sha256) {
205
- passed.push(`registry sha256 declared: ${entry.sha256.slice(0, 16)}…`);
256
+ // 4. asset digest vs registry (if entry provided)
257
+ const registry = options.registry || null;
258
+ const registryIssues = registry ? registryTrustIssues(registry) : [];
259
+ for (const issue of registryIssues) {
260
+ issues.push({ severity: 'error', msg: issue });
261
+ }
262
+
263
+ const revocation = registry && entry ? isEntryRevoked(registry, entry) : null;
264
+ if (revocation) {
265
+ issues.push({
266
+ severity: 'error',
267
+ msg: `registry revokes this asset${revocation.reason ? `: ${revocation.reason}` : ''}`,
268
+ });
269
+ }
270
+
271
+ const registryDigest = entry?.asset_digest || null;
272
+ if (registryDigest) {
273
+ passed.push(`registry asset_digest declared: ${registryDigest.slice(0, 23)}…`);
274
+ if (options.assetDigest && options.assetDigest !== registryDigest) {
275
+ issues.push({
276
+ severity: 'error',
277
+ msg: `asset digest mismatch: registry ${registryDigest}, local ${options.assetDigest}`,
278
+ });
279
+ } else if (options.assetDigest) {
280
+ passed.push('local asset_digest matches registry');
281
+ }
282
+ } else if (entry) {
283
+ issues.push({ severity: 'error', msg: 'registry asset_digest missing' });
206
284
  }
207
285
 
208
286
  // 5. scope governance
@@ -218,7 +296,8 @@ function checkTrust(destDir, scope, entry) {
218
296
 
219
297
  // ─── Judgment layer ────────────────────────────────────────────────────
220
298
 
221
- function checkJudgment(destDir) {
299
+ function checkJudgment(input, options = {}) {
300
+ const view = asView(input);
222
301
  const issues = [];
223
302
  const passed = [];
224
303
  const score = { total: 0, max: 0 };
@@ -231,17 +310,23 @@ function checkJudgment(destDir) {
231
310
  else issues.push({ severity: 'warn', msg: `missing: ${label}` });
232
311
  }
233
312
 
234
- const core = readJson(path.join(destDir, 'KDNA_Core.json'));
235
- const pat = readJson(path.join(destDir, 'KDNA_Patterns.json'));
236
- const manifest = readJson(path.join(destDir, 'kdna.json'));
313
+ if (options.licenseError) {
314
+ issues.push({
315
+ severity: 'error',
316
+ msg: `license required to verify encrypted judgment: ${options.licenseError}`,
317
+ });
318
+ }
319
+
320
+ const core = options.licenseError ? null : readJsonFromView(view, 'KDNA_Core.json', issues);
321
+ const pat = options.licenseError ? null : readJsonFromView(view, 'KDNA_Patterns.json', issues);
322
+ const manifest = readJsonFromView(view, 'kdna.json', issues);
237
323
 
238
324
  // 1. Boundary declaration in README (REQUIRED)
239
325
  // Either classic "## Scope" + "## Out of Scope" pair,
240
326
  // OR v2.1 "Four Questions" section (#2 = applies, #4 = does not).
241
- const readmePath = path.join(destDir, 'README.md');
242
327
  let readme = '';
243
328
  try {
244
- readme = fs.readFileSync(readmePath, 'utf8');
329
+ readme = view.readText('README.md');
245
330
  } catch {
246
331
  /* ok */
247
332
  }
@@ -330,16 +415,16 @@ function checkJudgment(destDir) {
330
415
  // 4. self_check format: yes/no questions
331
416
  if (pat?.self_check) {
332
417
  const total = pat.self_check.length;
333
- const yn = pat.self_check.filter((q) => typeof q === 'string' && q.trim().endsWith('?')).length;
334
- bump(total, yn, `self_check questions ending in "?" (${yn}/${total})`);
418
+ const yn = pat.self_check.filter((q) => isYesNoSelfCheck(q)).length;
419
+ bump(total, yn, `self_check yes/no questions (${yn}/${total})`);
335
420
  if (total < 3)
336
421
  issues.push({ severity: 'warn', msg: `only ${total} self_check entries (recommend ≥3)` });
337
422
  }
338
423
 
339
424
  // 5. eval cases present (REQUIRED: ≥4 cases)
340
- const evalDir = path.join(destDir, 'evals');
341
- if (fs.existsSync(evalDir)) {
342
- const files = fs.readdirSync(evalDir).filter((f) => f.endsWith('.json'));
425
+ const evalFiles = view.listDirFiles('evals').filter((f) => f.endsWith('.json'));
426
+ if (evalFiles.length) {
427
+ const files = evalFiles;
343
428
  if (files.length >= 4) {
344
429
  bump(2, 2, `evals/ directory has ${files.length} case files`);
345
430
  } else if (files.length > 0) {
@@ -399,10 +484,11 @@ function renderLayer(result) {
399
484
 
400
485
  // ─── I18N layer ──────────────────────────────────────────────────────
401
486
 
402
- function checkI18n(destDir) {
487
+ function checkI18n(input) {
488
+ const view = asView(input);
403
489
  const issues = [];
404
490
  const passed = [];
405
- const manifest = readJson(path.join(destDir, 'kdna.json')) || {};
491
+ const manifest = readJsonFromView(view, 'kdna.json', issues) || {};
406
492
  const languages = manifest.languages || [];
407
493
  const i18nLevel = manifest.i18n_level || 'L0';
408
494
 
@@ -417,14 +503,13 @@ function checkI18n(destDir) {
417
503
  const canonical = manifest.default_language || languages[0] || 'en';
418
504
  for (const lang of languages) {
419
505
  if (lang === canonical) continue;
420
- const localeDir = path.join(destDir, 'locales', lang);
421
506
 
422
507
  // L1: card + readme
423
508
  if (['L1', 'L2', 'L3', 'L4'].includes(i18nLevel)) {
424
- if (!fs.existsSync(path.join(localeDir, 'KDNA_CARD.json'))) {
509
+ if (!view.exists(`locales/${lang}/KDNA_CARD.json`)) {
425
510
  issues.push({ severity: 'error', msg: `i18n: ${lang} KDNA_CARD.json missing` });
426
511
  } else {
427
- const card = readJson(path.join(localeDir, 'KDNA_CARD.json'));
512
+ const card = readJsonFromView(view, `locales/${lang}/KDNA_CARD.json`, issues);
428
513
  if (card) {
429
514
  passed.push(`locales/${lang}/KDNA_CARD.json OK`);
430
515
  if (!card.display_name)
@@ -433,7 +518,7 @@ function checkI18n(destDir) {
433
518
  issues.push({ severity: 'warn', msg: `i18n: ${lang} card missing intended_use` });
434
519
  }
435
520
  }
436
- if (!fs.existsSync(path.join(localeDir, 'README.md'))) {
521
+ if (!view.exists(`locales/${lang}/README.md`)) {
437
522
  issues.push({ severity: 'warn', msg: `i18n: ${lang} README.md missing` });
438
523
  } else {
439
524
  passed.push(`locales/${lang}/README.md OK`);
@@ -442,13 +527,13 @@ function checkI18n(destDir) {
442
527
 
443
528
  // L2: overlay files
444
529
  if (['L2', 'L3', 'L4'].includes(i18nLevel)) {
445
- const coreOverlay = path.join(localeDir, 'KDNA_Core.overlay.json');
446
- if (!fs.existsSync(coreOverlay)) {
530
+ const coreOverlay = `locales/${lang}/KDNA_Core.overlay.json`;
531
+ if (!view.exists(coreOverlay)) {
447
532
  issues.push({ severity: 'error', msg: `i18n: ${lang} KDNA_Core.overlay.json missing` });
448
533
  } else {
449
- const overlay = readJson(coreOverlay);
534
+ const overlay = readJsonFromView(view, coreOverlay, issues);
450
535
  if (overlay?.translations) {
451
- const core = readJson(path.join(destDir, 'KDNA_Core.json'));
536
+ const core = readJsonFromView(view, 'KDNA_Core.json', issues);
452
537
  if (core?.axioms) {
453
538
  const validIds = new Set(core.axioms.map((a) => a.id));
454
539
  for (const key of Object.keys(overlay.translations)) {
@@ -466,7 +551,7 @@ function checkI18n(destDir) {
466
551
  );
467
552
  }
468
553
  }
469
- if (!fs.existsSync(path.join(localeDir, 'KDNA_Patterns.overlay.json'))) {
554
+ if (!view.exists(`locales/${lang}/KDNA_Patterns.overlay.json`)) {
470
555
  issues.push({ severity: 'warn', msg: `i18n: ${lang} KDNA_Patterns.overlay.json missing` });
471
556
  }
472
557
  }
@@ -487,12 +572,13 @@ function checkI18n(destDir) {
487
572
 
488
573
  // ─── Governance layer ───────────────────────────────────────────────
489
574
 
490
- function checkGovernance(destDir) {
575
+ function checkGovernance(input) {
576
+ const view = asView(input);
491
577
  const issues = [];
492
578
  const passed = [];
493
- const card = readJson(path.join(destDir, 'KDNA_CARD.json')) || {};
579
+ const card = readJsonFromView(view, 'KDNA_CARD.json', issues) || {};
494
580
 
495
- if (!readJson(path.join(destDir, 'KDNA_CARD.json'))) {
581
+ if (!card || !view.exists('KDNA_CARD.json')) {
496
582
  issues.push({ severity: 'error', msg: 'governance: KDNA_CARD.json missing — required' });
497
583
  return { layer: 'governance', passed: false, issues, results: issues.map((i) => i.msg) };
498
584
  }
@@ -579,8 +665,8 @@ function cmdVerify(input, args = []) {
579
665
  console.log(JSON.stringify({ ok: false, error: `Invalid name: ${input}` }));
580
666
  process.exit(EXIT.INPUT_ERROR);
581
667
  }
582
- const destDir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
583
- if (!destDir || !require('fs').existsSync(destDir)) {
668
+ const installed = getInstalled(parsed.full);
669
+ if (!installed) {
584
670
  console.log(JSON.stringify({ ok: false, error: `Domain not installed: ${input}` }));
585
671
  process.exit(EXIT.INPUT_ERROR);
586
672
  }
@@ -608,58 +694,80 @@ function cmdVerify(input, args = []) {
608
694
  const all = !want.structure && !want.trust && !want.judgment && !want.i18n && !want.governance;
609
695
  if (all) want.structure = want.trust = want.judgment = true;
610
696
 
611
- // Resolve name installed path + scope/entry
612
- const parsed = parseName(input);
613
- if (!parsed) {
697
+ // Resolve installed name or direct local .kdna asset path.
698
+ const asset = resolveAsset(input);
699
+ const parsed = asset?.parsed || parseName(asset?.name || '');
700
+ const displayName = parsed?.full || asset?.name || input;
701
+ if (!asset) {
614
702
  if (jsonMode) {
615
703
  console.log(
616
704
  JSON.stringify({
617
705
  name: input,
618
706
  ok: false,
619
- error: `Invalid name "${input}". Use @scope/name or bare name.`,
707
+ error: `KDNA asset not found: ${input}. Use an installed name or a .kdna file.`,
620
708
  }),
621
709
  );
622
710
  } else {
623
- console.error(`Invalid name "${input}". Use @scope/name or bare name.`);
624
- }
625
- process.exit(EXIT.INPUT_ERROR);
626
- }
627
-
628
- const destDir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
629
- if (!fs.existsSync(destDir)) {
630
- if (jsonMode) {
631
- console.log(
632
- JSON.stringify({
633
- name: parsed.full,
634
- ok: false,
635
- error: `${parsed.full} is not installed. Run: kdna install ${input}`,
636
- }),
637
- );
638
- } else {
639
- console.error(`${parsed.full} is not installed. Run: kdna install ${input}`);
711
+ console.error(`KDNA asset not found: ${input}. Use an installed name or a .kdna file.`);
640
712
  }
641
713
  process.exit(EXIT.INPUT_ERROR);
642
714
  }
643
715
 
644
716
  let scope = null,
645
- entry = null;
717
+ entry = null,
718
+ registry = null;
646
719
  if (want.trust) {
647
720
  try {
648
721
  const resolver = new RegistryResolver({ allowNetwork: false });
649
- const r = resolver.resolve(parsed.full);
650
- scope = r.scope;
651
- entry = r.entry;
722
+ if (parsed) {
723
+ const r = resolver.resolve(parsed.full);
724
+ scope = r.scope;
725
+ entry = r.entry;
726
+ registry = r.registry;
727
+ }
652
728
  } catch (e) {
653
729
  if (!jsonMode) console.warn(` ⚠ registry lookup failed: ${e.message.split('\n')[0]}`);
654
730
  }
655
731
  }
656
732
 
733
+ let decryptOptions = {};
734
+ let licenseError = null;
735
+ let manifest = null;
736
+ try {
737
+ manifest = readContainerJson(asset.asset_path, 'kdna.json') || {};
738
+ } catch (e) {
739
+ licenseError = e.message;
740
+ }
741
+
742
+ const encryptedEntries = Array.isArray(manifest?.encryption?.encrypted_entries)
743
+ ? manifest.encryption.encrypted_entries
744
+ : [];
745
+ const requiresProtectedRead =
746
+ encryptedEntries.length > 0 && (want.structure || want.judgment || want.i18n || want.governance);
747
+ if (requiresProtectedRead) {
748
+ const licensed = licenseDecryptOptionsForManifest(manifest);
749
+ if (licensed.ok) {
750
+ decryptOptions = { decryptEntry: licensed.decryptEntry };
751
+ } else {
752
+ licenseError = licensed.error;
753
+ }
754
+ }
755
+
756
+ const view = assetView(asset.asset_path, decryptOptions);
657
757
  const results = [];
658
- if (want.structure) results.push(checkStructure(destDir));
659
- if (want.trust) results.push(checkTrust(destDir, scope, entry));
660
- if (want.judgment) results.push(checkJudgment(destDir));
661
- if (want.i18n) results.push(checkI18n(destDir));
662
- if (want.governance) results.push(checkGovernance(destDir));
758
+ if (want.structure) results.push(checkStructure(view, { licenseError }));
759
+ if (want.trust) {
760
+ results.push(
761
+ checkTrust(view, scope, entry, {
762
+ registry,
763
+ assetDigest: asset.asset_digest || null,
764
+ assetPath: asset.asset_path,
765
+ }),
766
+ );
767
+ }
768
+ if (want.judgment) results.push(checkJudgment(view, { licenseError }));
769
+ if (want.i18n) results.push(checkI18n(view, { licenseError }));
770
+ if (want.governance) results.push(checkGovernance(view, { licenseError }));
663
771
 
664
772
  // ── JSON output ──────────────────────────────────────────────────────
665
773
  if (jsonMode) {
@@ -676,9 +784,6 @@ function cmdVerify(input, args = []) {
676
784
  const structureResult = results.find((r) => r.layer === 'structure');
677
785
  const trustResult = results.find((r) => r.layer === 'trust');
678
786
  const judgmentResult = results.find((r) => r.layer === 'judgment');
679
- const i18nResult = results.find((r) => r.layer === 'i18n');
680
- const governanceResult = results.find((r) => r.layer === 'governance');
681
-
682
787
  let exitCode = EXIT.OK;
683
788
  if (structureResult && structureResult.issues.some((i) => i.severity === 'error')) {
684
789
  exitCode = EXIT.VALIDATION_FAILED;
@@ -691,8 +796,10 @@ function cmdVerify(input, args = []) {
691
796
  console.log(
692
797
  JSON.stringify(
693
798
  {
694
- name: parsed.full,
695
- path: destDir,
799
+ name: displayName,
800
+ path: asset.asset_path,
801
+ asset_digest: asset.asset_digest || null,
802
+ content_digest: asset.content_digest || null,
696
803
  layers,
697
804
  ok: exitCode === EXIT.OK,
698
805
  },
@@ -705,8 +812,10 @@ function cmdVerify(input, args = []) {
705
812
 
706
813
  // ── Text output (default) ────────────────────────────────────────────
707
814
  console.log('═'.repeat(64));
708
- console.log(` Verify ${parsed.full}`);
709
- console.log(` Path: ${destDir}`);
815
+ console.log(` Verify ${displayName}`);
816
+ console.log(` Asset: ${asset.asset_path}`);
817
+ if (asset.asset_digest) console.log(` Asset digest: ${asset.asset_digest}`);
818
+ if (asset.content_digest) console.log(` Content digest: ${asset.content_digest}`);
710
819
  console.log('═'.repeat(64));
711
820
 
712
821
  for (const r of results) renderLayer(r);
@@ -1,5 +1,5 @@
1
1
  {
2
- "kdna_spec": "1.0",
2
+ "kdna_spec": "1.0-rc",
3
3
  "name": "@yourscope/your_domain_id",
4
4
  "version": "0.1.0",
5
5
  "judgment_version": "YYYY.MM",
@@ -11,6 +11,7 @@
11
11
  "description": "[one-sentence description; appears on registry and in install output]",
12
12
  "core_insight": "[one-sentence core insight that distinguishes this domain]",
13
13
  "keywords": ["[add 3-6 keywords for kdna search]"],
14
+ "quality_badge": "untested",
14
15
  "author": {
15
16
  "name": "Your Name",
16
17
  "id": "your-id",