@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/README.md +42 -23
- package/package.json +5 -4
- package/skills/kdna-loader/SKILL.md +5 -6
- package/src/agent.js +145 -109
- package/src/cli.js +104 -60
- package/src/cmds/_common.js +32 -16
- package/src/cmds/badge.js +7 -7
- package/src/cmds/changelog.js +1 -1
- package/src/cmds/cluster.js +16 -48
- package/src/cmds/doctor.js +10 -27
- package/src/cmds/domain.js +77 -400
- package/src/cmds/explain.js +122 -0
- package/src/cmds/legacy.js +8 -8
- package/src/cmds/license.js +483 -26
- package/src/cmds/quality.js +2 -2
- package/src/cmds/registry.js +15 -67
- package/src/cmds/studio.js +4 -5
- package/src/cmds/test.js +4 -4
- package/src/cmds/trace.js +11 -7
- package/src/compare.js +28 -22
- package/src/diff.js +11 -13
- package/src/init.js +2 -2
- package/src/install.js +138 -460
- package/src/loader.js +10 -10
- package/src/package-store.js +229 -0
- package/src/paths.js +44 -0
- package/src/publish.js +73 -22
- package/src/registry.js +76 -9
- package/src/setup.js +19 -20
- package/src/verify.js +233 -124
- package/templates/standard-domain/kdna.json +2 -1
- package/src/cmds/encrypt.js +0 -199
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
|
|
19
|
-
|
|
20
|
-
const
|
|
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(
|
|
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.
|
|
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('
|
|
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
|
-
|
|
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
|
|
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
|
|
21
|
-
const {
|
|
22
|
-
const {
|
|
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
|
|
32
|
-
|
|
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(
|
|
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 (!
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
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: '
|
|
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.
|
|
204
|
-
|
|
205
|
-
|
|
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(
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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 =
|
|
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) =>
|
|
334
|
-
bump(total, yn, `self_check questions
|
|
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
|
|
341
|
-
if (
|
|
342
|
-
const files =
|
|
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(
|
|
487
|
+
function checkI18n(input) {
|
|
488
|
+
const view = asView(input);
|
|
403
489
|
const issues = [];
|
|
404
490
|
const passed = [];
|
|
405
|
-
const manifest =
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
446
|
-
if (!
|
|
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 =
|
|
534
|
+
const overlay = readJsonFromView(view, coreOverlay, issues);
|
|
450
535
|
if (overlay?.translations) {
|
|
451
|
-
const core =
|
|
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 (!
|
|
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(
|
|
575
|
+
function checkGovernance(input) {
|
|
576
|
+
const view = asView(input);
|
|
491
577
|
const issues = [];
|
|
492
578
|
const passed = [];
|
|
493
|
-
const card =
|
|
579
|
+
const card = readJsonFromView(view, 'KDNA_CARD.json', issues) || {};
|
|
494
580
|
|
|
495
|
-
if (!
|
|
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
|
|
583
|
-
if (!
|
|
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
|
|
612
|
-
const
|
|
613
|
-
|
|
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: `
|
|
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(`
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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(
|
|
659
|
-
if (want.trust)
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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:
|
|
695
|
-
path:
|
|
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 ${
|
|
709
|
-
console.log(`
|
|
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",
|