@aikdna/kdna-core 0.3.0 → 0.5.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/index.js CHANGED
@@ -6,6 +6,8 @@ const lint = require('./lint-pure');
6
6
  const validate = require('./validate-pure');
7
7
  const render = require('./render');
8
8
  const compose = require('./compose');
9
+ const assetReader = require('./asset-reader');
10
+ const cryptoProfile = require('./crypto-profile');
9
11
 
10
12
  module.exports = {
11
13
  ...loader,
@@ -13,4 +15,6 @@ module.exports = {
13
15
  ...validate,
14
16
  ...render,
15
17
  ...compose,
18
+ ...assetReader,
19
+ ...cryptoProfile,
16
20
  };
package/src/index.mjs CHANGED
@@ -17,3 +17,14 @@ export { validateDomainSchema, validateCrossFile } from './validate-pure.js';
17
17
  export { renderPreviewHTML, escHtml, renderCard } from './render.js';
18
18
 
19
19
  export { composeContext, composeContextWithAttribution, classifySignals, classifySignalsAcrossDomains, composeChecks, loadAndCompose, loadCluster, detectDomainConflicts, generateClusterTrace } from './compose.js';
20
+
21
+ import assetReader from './asset-reader.js';
22
+ import cryptoProfile from './crypto-profile.js';
23
+
24
+ export const STANDARD_ENTRIES = assetReader.STANDARD_ENTRIES;
25
+ export const createKdnaAssetReader = assetReader.createKdnaAssetReader;
26
+ export const LICENSED_ENTRY_PROFILE = cryptoProfile.LICENSED_ENTRY_PROFILE;
27
+ export const deriveLicensedEntryKey = cryptoProfile.deriveLicensedEntryKey;
28
+ export const encryptLicensedEntry = cryptoProfile.encryptLicensedEntry;
29
+ export const decryptLicensedEntry = cryptoProfile.decryptLicensedEntry;
30
+ export const createLicensedDecryptEntry = cryptoProfile.createLicensedDecryptEntry;
package/src/lint-pure.js CHANGED
@@ -29,6 +29,15 @@ const OLD_FIELD_HINTS = {
29
29
  judgment: 'via',
30
30
  };
31
31
 
32
+ const KDNA_DOMAIN_FILES = new Set([
33
+ 'KDNA_Core.json',
34
+ 'KDNA_Patterns.json',
35
+ 'KDNA_Scenarios.json',
36
+ 'KDNA_Cases.json',
37
+ 'KDNA_Reasoning.json',
38
+ 'KDNA_Evolution.json',
39
+ ]);
40
+
32
41
  /**
33
42
  * Lint a KDNA domain from a map of parsed JSON objects.
34
43
  *
@@ -104,9 +113,7 @@ function lintDomain(dataMap) {
104
113
  }
105
114
 
106
115
  // Check file count
107
- const kdnaFiles = Object.keys(dataMap).filter(
108
- (f) => f.endsWith('.json') && f !== 'kdna.json',
109
- );
116
+ const kdnaFiles = Object.keys(dataMap).filter((f) => KDNA_DOMAIN_FILES.has(f));
110
117
  if (kdnaFiles.length > 6) errors.push(`Domain has ${kdnaFiles.length} JSON files; KDNA allows at most 6.`);
111
118
 
112
119
  // Validate meta on all files
@@ -245,4 +252,152 @@ function lintDomain(dataMap) {
245
252
  return { errors, warnings };
246
253
  }
247
254
 
248
- module.exports = { lintDomain };
255
+ /**
256
+ * Canonical enum tables for manifest validation.
257
+ * Single source of truth — keep in sync with schema/kdna-manifest-v1rc.json and specs/enum-tables.md.
258
+ */
259
+ const VALID_STATUS = new Set(['draft', 'experimental', 'stable', 'deprecated', 'staging']);
260
+ const VALID_BADGE = new Set(['untested', 'tested', 'validated', 'expert_reviewed', 'production_ready']);
261
+ const VALID_ACCESS = new Set(['open', 'licensed', 'runtime']);
262
+ const VALID_RISK = new Set(['R0', 'R1', 'R2', 'R3']);
263
+ const VALID_I18N = new Set(['L0', 'L1', 'L2', 'L3']);
264
+
265
+ const MANIFEST_REQUIRED = [
266
+ 'kdna_spec', 'name', 'version', 'judgment_version',
267
+ 'description', 'author', 'license', 'status',
268
+ 'quality_badge', 'access', 'language',
269
+ ];
270
+
271
+ /**
272
+ * Validate a kdna.json manifest against the canonical v1.0-rc schema.
273
+ *
274
+ * @param {Object} manifest — parsed kdna.json
275
+ * @returns {{ errors: string[], warnings: string[] }}
276
+ */
277
+ function validateManifest(manifest) {
278
+ const errors = [];
279
+ const warnings = [];
280
+
281
+ if (!manifest || typeof manifest !== 'object') {
282
+ errors.push('kdna.json: missing or empty manifest');
283
+ return { errors, warnings };
284
+ }
285
+
286
+ // 1. Check spec_version is NOT in domain manifest (use kdna_spec only)
287
+ if ('spec_version' in manifest) {
288
+ errors.push(
289
+ 'kdna.json: spec_version is deprecated in domain manifests. Use kdna_spec. ' +
290
+ '(spec_version is reserved for .kdna container manifests only.)',
291
+ );
292
+ }
293
+
294
+ // 2. Check required fields
295
+ for (const field of MANIFEST_REQUIRED) {
296
+ if (!(field in manifest) || manifest[field] === undefined || manifest[field] === '') {
297
+ errors.push(`kdna.json: missing required field "${field}"`);
298
+ }
299
+ }
300
+
301
+ // 3. Validate name format
302
+ if (manifest.name && !/^@[a-z][a-z0-9-]*\/[a-z][a-z0-9_]*$/.test(manifest.name)) {
303
+ errors.push(`kdna.json.name: invalid format "${manifest.name}". Expected @scope/name.`);
304
+ }
305
+
306
+ // 4. Validate enum fields
307
+ if (manifest.status && !VALID_STATUS.has(manifest.status)) {
308
+ errors.push(
309
+ `kdna.json.status: invalid value "${manifest.status}". ` +
310
+ `Valid: ${[...VALID_STATUS].join(', ')}`,
311
+ );
312
+ }
313
+ if (manifest.quality_badge && !VALID_BADGE.has(manifest.quality_badge)) {
314
+ errors.push(
315
+ `kdna.json.quality_badge: invalid value "${manifest.quality_badge}". ` +
316
+ `Valid: ${[...VALID_BADGE].join(', ')}`,
317
+ );
318
+ }
319
+ if (manifest.access && !VALID_ACCESS.has(manifest.access)) {
320
+ errors.push(
321
+ `kdna.json.access: invalid value "${manifest.access}". ` +
322
+ `Valid: ${[...VALID_ACCESS].join(', ')}`,
323
+ );
324
+ }
325
+ if (manifest.risk_level && !VALID_RISK.has(manifest.risk_level)) {
326
+ errors.push(
327
+ `kdna.json.risk_level: invalid value "${manifest.risk_level}". ` +
328
+ `Valid: ${[...VALID_RISK].join(', ')}`,
329
+ );
330
+ }
331
+ if (manifest.i18n_level && !VALID_I18N.has(manifest.i18n_level)) {
332
+ warnings.push(
333
+ `kdna.json.i18n_level: non-standard value "${manifest.i18n_level}". ` +
334
+ `Valid: ${[...VALID_I18N].join(', ')}`,
335
+ );
336
+ }
337
+
338
+ // 5. Deprecated status must have replaced_by
339
+ if (manifest.status === 'deprecated' && !manifest.replaced_by) {
340
+ errors.push('kdna.json: status is "deprecated" but replaced_by is missing');
341
+ }
342
+
343
+ // 6. Tested+ badge must have signature
344
+ const needsSig = ['tested', 'validated', 'expert_reviewed', 'production_ready'];
345
+ if (needsSig.includes(manifest.quality_badge) && !manifest.signature) {
346
+ warnings.push(
347
+ `kdna.json: quality_badge "${manifest.quality_badge}" should have a signature`,
348
+ );
349
+ }
350
+
351
+ // 7. Validate author
352
+ if (manifest.author) {
353
+ if (!manifest.author.name) errors.push('kdna.json.author: missing "name"');
354
+ if (!manifest.author.id) errors.push('kdna.json.author: missing "id"');
355
+ if (manifest.author.pubkey && !/^ed25519:[0-9a-f]{64}$/.test(manifest.author.pubkey)) {
356
+ warnings.push('kdna.json.author.pubkey: non-standard format. Expected ed25519:<64 hex chars>.');
357
+ }
358
+ }
359
+
360
+ // 8. Validate license
361
+ if (manifest.license && !manifest.license.type) {
362
+ errors.push('kdna.json.license: missing "type"');
363
+ }
364
+
365
+ // 9. Validate kdna_spec value
366
+ if (manifest.kdna_spec && manifest.kdna_spec !== '1.0-rc') {
367
+ warnings.push(
368
+ `kdna.json.kdna_spec: non-standard value "${manifest.kdna_spec}". Expected "1.0-rc".`,
369
+ );
370
+ }
371
+
372
+ // 10. Validate version format
373
+ if (manifest.version && !/^\d+\.\d+\.\d+/.test(manifest.version)) {
374
+ warnings.push(
375
+ `kdna.json.version: non-semver format "${manifest.version}". Expected MAJOR.MINOR.PATCH.`,
376
+ );
377
+ }
378
+
379
+ // 11. Check for removed fields
380
+ const removedFields = ['release_status', 'domain_field', 'judgment_patterns', 'files', 'registry'];
381
+ for (const field of removedFields) {
382
+ if (field in manifest) {
383
+ warnings.push(
384
+ `kdna.json: field "${field}" is not in the canonical domain manifest and should be removed`,
385
+ );
386
+ }
387
+ }
388
+
389
+ // 12. Check removed license sub-fields
390
+ if (manifest.license && typeof manifest.license === 'object') {
391
+ for (const field of ['commercial', 'allow_agent_use', 'allow_redistribution', 'allow_training']) {
392
+ if (field in manifest.license) {
393
+ warnings.push(
394
+ `kdna.json.license.${field}: license-type-specific field, not universal. Consider removing.`,
395
+ );
396
+ }
397
+ }
398
+ }
399
+
400
+ return { errors, warnings };
401
+ }
402
+
403
+ module.exports = { lintDomain, validateManifest };
package/src/loader.js CHANGED
@@ -174,6 +174,16 @@ function detectOldFieldNames(obj, path = '', warnings = []) {
174
174
  * @param {object} domain — result from loadDomainFromData() or loadDomainFromFiles()
175
175
  * @returns {string}
176
176
  */
177
+ function sanitize(str) {
178
+ if (typeof str !== 'string') return str;
179
+ return str
180
+ .replace(/^#{1,6}\s/gm, '\\# ') // Escape leading # to prevent fake headers
181
+ .replace(/```/g, '\\`\\`\\`') // Escape code blocks
182
+ .replace(/<\|/g, '&lt;|') // Escape special tokens
183
+ .replace(/\b(ignore|forget|disregard)\s+(all\s+)?(previous|prior|above)\s+(instructions?|directives?|rules?|constraints?)\b/gi,
184
+ '[filtered: $&]'); // Filter prompt injection patterns
185
+ }
186
+
177
187
  function formatContext(domain) {
178
188
  if (!domain || !domain.core || !domain.patterns) return '';
179
189
 
@@ -193,13 +203,13 @@ function formatContext(domain) {
193
203
  }
194
204
 
195
205
  parts.push('## Domain Cognition (KDNA)');
196
- parts.push(`Domain: ${core.meta.domain}`);
206
+ parts.push(`Domain: ${sanitize(core.meta.domain)}`);
197
207
  parts.push('');
198
208
 
199
209
  if (core.stances && core.stances.length) {
200
210
  parts.push('### Stances');
201
211
  for (const s of core.stances) {
202
- parts.push(`- ${s}`);
212
+ parts.push(`- ${sanitize(s)}`);
203
213
  }
204
214
  parts.push('');
205
215
  }
@@ -207,8 +217,8 @@ function formatContext(domain) {
207
217
  if (core.axioms && core.axioms.length) {
208
218
  parts.push('### Axioms');
209
219
  for (const a of core.axioms) {
210
- parts.push(`- **${a.one_sentence}** ${a.full_statement}`);
211
- parts.push(` *Why:* ${a.why}`);
220
+ parts.push(`- **${sanitize(a.one_sentence)}** ${sanitize(a.full_statement)}`);
221
+ parts.push(` *Why:* ${sanitize(a.why)}`);
212
222
  }
213
223
  parts.push('');
214
224
  }
@@ -216,8 +226,8 @@ function formatContext(domain) {
216
226
  if (core.ontology && core.ontology.length) {
217
227
  parts.push('### Key Concepts');
218
228
  for (const c of core.ontology) {
219
- parts.push(`- **${c.id.replace(/_/g, ' ')}** — ${c.one_sentence}`);
220
- parts.push(` Boundary: ${c.boundary}`);
229
+ parts.push(`- **${sanitize(c.id.replace(/_/g, ' '))}** — ${sanitize(c.one_sentence)}`);
230
+ parts.push(` Boundary: ${sanitize(c.boundary)}`);
221
231
  }
222
232
  parts.push('');
223
233
  }
@@ -225,7 +235,7 @@ function formatContext(domain) {
225
235
  if (core.frameworks && core.frameworks.length) {
226
236
  parts.push('### Frameworks');
227
237
  for (const fw of core.frameworks) {
228
- parts.push(`- **${fw.name}**: ${fw.when_to_use}`);
238
+ parts.push(`- **${sanitize(fw.name)}**: ${sanitize(fw.when_to_use)}`);
229
239
  }
230
240
  parts.push('');
231
241
  }
@@ -233,7 +243,7 @@ function formatContext(domain) {
233
243
  if (pat.terminology && pat.terminology.banned_terms && pat.terminology.banned_terms.length) {
234
244
  parts.push('### Avoid These Terms');
235
245
  for (const b of pat.terminology.banned_terms) {
236
- parts.push(`- Avoid "${b.term}". ${b.why} Use "${b.replace_with}" instead.`);
246
+ parts.push(`- Avoid "${sanitize(b.term)}". ${sanitize(b.why)} Use "${sanitize(b.replace_with)}" instead.`);
237
247
  }
238
248
  parts.push('');
239
249
  }
@@ -241,8 +251,8 @@ function formatContext(domain) {
241
251
  if (pat.misunderstandings && pat.misunderstandings.length) {
242
252
  parts.push('### Watch For These Misunderstandings');
243
253
  for (const m of pat.misunderstandings) {
244
- parts.push(`- **Wrong:** ${m.wrong}`);
245
- parts.push(` **Correct:** ${m.correct}`);
254
+ parts.push(`- **Wrong:** ${sanitize(m.wrong)}`);
255
+ parts.push(` **Correct:** ${sanitize(m.correct)}`);
246
256
  }
247
257
  parts.push('');
248
258
  }
@@ -250,7 +260,7 @@ function formatContext(domain) {
250
260
  if (pat.self_check && pat.self_check.length) {
251
261
  parts.push('### Before Responding, Check');
252
262
  for (const s of pat.self_check) {
253
- parts.push(`- [ ] ${s}`);
263
+ parts.push(`- [ ] ${sanitize(s)}`);
254
264
  }
255
265
  parts.push('');
256
266
  }
@@ -258,7 +268,7 @@ function formatContext(domain) {
258
268
  if (domain.scenarios && domain.scenarios.scenes) {
259
269
  parts.push('### Relevant Scenarios');
260
270
  for (const scene of domain.scenarios.scenes) {
261
- parts.push(`- **${scene.name}**: ${scene.trigger_signal}`);
271
+ parts.push(`- **${sanitize(scene.name)}**: ${sanitize(scene.trigger_signal)}`);
262
272
  }
263
273
  parts.push('');
264
274
  }
@@ -266,7 +276,7 @@ function formatContext(domain) {
266
276
  if (domain.reasoning && domain.reasoning.reasoning_chains) {
267
277
  parts.push('### Reasoning Chains');
268
278
  for (const r of domain.reasoning.reasoning_chains) {
269
- parts.push(`- **${r.one_sentence}** → ${r.so_what}`);
279
+ parts.push(`- **${sanitize(r.one_sentence)}** → ${sanitize(r.so_what)}`);
270
280
  }
271
281
  parts.push('');
272
282
  }
@@ -274,11 +284,11 @@ function formatContext(domain) {
274
284
  if (domain.cases && domain.cases.cases && domain.cases.cases.length) {
275
285
  parts.push('### Cases');
276
286
  for (const c of domain.cases.cases) {
277
- parts.push(`- **${c.title}**`);
278
- parts.push(` Context: ${c.context}`);
279
- parts.push(` What happened: ${c.what_happened}`);
280
- parts.push(` Learned: ${c.what_was_learned}`);
281
- parts.push(` Pattern: ${c.structural_pattern}`);
287
+ parts.push(`- **${sanitize(c.title)}**`);
288
+ parts.push(` Context: ${sanitize(c.context)}`);
289
+ parts.push(` What happened: ${sanitize(c.what_happened)}`);
290
+ parts.push(` Learned: ${sanitize(c.what_was_learned)}`);
291
+ parts.push(` Pattern: ${sanitize(c.structural_pattern)}`);
282
292
  }
283
293
  parts.push('');
284
294
  }
@@ -288,7 +298,7 @@ function formatContext(domain) {
288
298
  if (evo.stages && evo.stages.length) {
289
299
  parts.push('### Growth Stages');
290
300
  for (const stage of evo.stages) {
291
- parts.push(`- **${stage.name}**: ${stage.description}`);
301
+ parts.push(`- **${sanitize(stage.name)}**: ${sanitize(stage.description)}`);
292
302
  }
293
303
  parts.push('');
294
304
  }
@@ -296,7 +306,7 @@ function formatContext(domain) {
296
306
  parts.push('### Capability Layers');
297
307
  for (const layer of evo.evolution_layers) {
298
308
  parts.push(
299
- `- **${layer.name}**: ${layer.capability} (${layer.from_stage} → ${layer.to_stage})`,
309
+ `- **${sanitize(layer.name)}**: ${sanitize(layer.capability)} (${sanitize(layer.from_stage)} → ${sanitize(layer.to_stage)})`,
300
310
  );
301
311
  }
302
312
  parts.push('');
package/src/types.d.ts CHANGED
@@ -206,12 +206,23 @@ export interface KDNAManifest {
206
206
  kdna_spec: string;
207
207
  name: string;
208
208
  version: string;
209
- status: 'experimental' | 'basic' | 'stable' | 'pro';
209
+ judgment_version?: string;
210
+ status: 'draft' | 'experimental' | 'stable' | 'deprecated' | 'basic' | 'pro';
210
211
  access: 'open' | 'licensed' | 'runtime';
211
- language: string;
212
- author: { name: string; id?: string };
212
+ language?: string;
213
+ default_language?: string;
214
+ languages?: string[];
215
+ author: { name: string; id?: string; pubkey?: string; public_key_pem?: string };
213
216
  license: { type: string; url?: string };
214
217
  description: string;
218
+ keywords?: string[];
219
+ encryption?: {
220
+ profile?: string;
221
+ encrypted_entries?: string[];
222
+ [key: string]: any;
223
+ };
224
+ content_digest?: string;
225
+ signature?: string;
215
226
  }
216
227
 
217
228
  export interface LintResult {
@@ -241,6 +252,167 @@ export function formatContext(domain: LoadedDomain): string;
241
252
 
242
253
  export const FILE_MAP: Record<string, string>;
243
254
 
255
+ // Asset reader — direct .kdna API
256
+ export const STANDARD_ENTRIES: string[];
257
+
258
+ export interface KdnaAsset {
259
+ path: string | null;
260
+ size: number;
261
+ asset_digest: string;
262
+ entries: Map<string, unknown>;
263
+ readEntry(name: string): Uint8Array;
264
+ }
265
+
266
+ export interface KdnaAssetVerifyResult {
267
+ ok: boolean;
268
+ errors: string[];
269
+ warnings: string[];
270
+ entries: string[];
271
+ manifest: KDNAManifest | null;
272
+ asset_digest: string;
273
+ content_digest: string;
274
+ signature_valid: boolean | null;
275
+ }
276
+
277
+ export interface KdnaAssetIndexProfile {
278
+ profile: 'index';
279
+ manifest: KDNAManifest;
280
+ asset_digest: string;
281
+ content_digest: string;
282
+ entries: string[];
283
+ name: string | null;
284
+ version: string | null;
285
+ judgment_version: string | null;
286
+ keywords: string[];
287
+ }
288
+
289
+ export interface KdnaAssetLoadProfile {
290
+ profile: string;
291
+ manifest: KDNAManifest;
292
+ domain: LoadedDomain | null;
293
+ context: string | null;
294
+ }
295
+
296
+ export interface KdnaAssetReader {
297
+ openSync(input: string | Uint8Array): KdnaAsset;
298
+ open(input: string | Uint8Array): Promise<KdnaAsset>;
299
+ listEntriesSync(asset: KdnaAsset): string[];
300
+ listEntries(asset: KdnaAsset): Promise<string[]>;
301
+ readEntrySync(asset: KdnaAsset, entryName: string): Uint8Array;
302
+ readEntrySync(asset: KdnaAsset, entryName: string, encoding: string): string;
303
+ readEntry(asset: KdnaAsset, entryName: string): Promise<Uint8Array>;
304
+ readEntry(asset: KdnaAsset, entryName: string, encoding: string): Promise<string>;
305
+ readJsonSync(asset: KdnaAsset, entryName: string, options?: KdnaDecryptOptions): any;
306
+ readJson(asset: KdnaAsset, entryName: string, options?: KdnaDecryptOptions): Promise<any>;
307
+ readManifestSync(asset: KdnaAsset): KDNAManifest;
308
+ readManifest(asset: KdnaAsset): Promise<KDNAManifest>;
309
+ readDataMapSync(
310
+ asset: KdnaAsset,
311
+ entries?: string[],
312
+ options?: KdnaDecryptOptions,
313
+ ): KDNAFileDataMap;
314
+ readDataMap(
315
+ asset: KdnaAsset,
316
+ entries?: string[],
317
+ options?: KdnaDecryptOptions,
318
+ ): Promise<KDNAFileDataMap>;
319
+ contentDigestSync(asset: KdnaAsset): string;
320
+ contentDigest(asset: KdnaAsset): Promise<string>;
321
+ verifySync(
322
+ asset: KdnaAsset,
323
+ options?: {
324
+ asset_digest?: string;
325
+ content_digest?: string;
326
+ requireSignature?: boolean;
327
+ requireDecryption?: boolean;
328
+ } & KdnaDecryptOptions,
329
+ ): KdnaAssetVerifyResult;
330
+ verify(
331
+ asset: KdnaAsset,
332
+ options?: {
333
+ asset_digest?: string;
334
+ content_digest?: string;
335
+ requireSignature?: boolean;
336
+ requireDecryption?: boolean;
337
+ } & KdnaDecryptOptions,
338
+ ): Promise<KdnaAssetVerifyResult>;
339
+ loadProfileSync(
340
+ asset: KdnaAsset,
341
+ profile: 'index',
342
+ options?: { input?: string; context?: boolean } & KdnaDecryptOptions,
343
+ ): KdnaAssetIndexProfile;
344
+ loadProfileSync(
345
+ asset: KdnaAsset,
346
+ profile?: 'compact' | 'scenario' | 'full' | string,
347
+ options?: { input?: string; context?: boolean } & KdnaDecryptOptions,
348
+ ): KdnaAssetLoadProfile;
349
+ loadProfile(
350
+ asset: KdnaAsset,
351
+ profile: 'index',
352
+ options?: { input?: string; context?: boolean } & KdnaDecryptOptions,
353
+ ): Promise<KdnaAssetIndexProfile>;
354
+ loadProfile(
355
+ asset: KdnaAsset,
356
+ profile?: 'compact' | 'scenario' | 'full' | string,
357
+ options?: { input?: string; context?: boolean } & KdnaDecryptOptions,
358
+ ): Promise<KdnaAssetLoadProfile>;
359
+ }
360
+
361
+ export interface KdnaDecryptOptions {
362
+ decryptEntry?: (args: {
363
+ asset: KdnaAsset;
364
+ manifest: KDNAManifest;
365
+ entryName: string;
366
+ ciphertext: Uint8Array;
367
+ }) => string | Uint8Array | Promise<string | Uint8Array>;
368
+ }
369
+
370
+ export function createKdnaAssetReader(): KdnaAssetReader;
371
+
372
+ export const LICENSED_ENTRY_PROFILE: string;
373
+
374
+ export interface LicensedEntryEnvelope {
375
+ profile: string;
376
+ alg: 'AES-256-GCM';
377
+ kdf: 'scrypt-sha256';
378
+ salt: string;
379
+ iv: string;
380
+ tag: string;
381
+ ciphertext: string;
382
+ }
383
+
384
+ export function deriveLicensedEntryKey(options: {
385
+ licenseKey: string;
386
+ machineFingerprint: string;
387
+ salt: string | Uint8Array;
388
+ keyLength?: number;
389
+ }): Uint8Array;
390
+
391
+ export function encryptLicensedEntry(
392
+ plaintext: string | Uint8Array,
393
+ options: {
394
+ entryName: string;
395
+ manifest?: KDNAManifest;
396
+ licenseKey: string;
397
+ machineFingerprint: string;
398
+ },
399
+ ): LicensedEntryEnvelope;
400
+
401
+ export function decryptLicensedEntry(
402
+ envelope: string | Uint8Array | LicensedEntryEnvelope,
403
+ options: {
404
+ entryName: string;
405
+ manifest?: KDNAManifest;
406
+ licenseKey: string;
407
+ machineFingerprint: string;
408
+ },
409
+ ): Uint8Array;
410
+
411
+ export function createLicensedDecryptEntry(options: {
412
+ licenseKey: string;
413
+ machineFingerprint: string;
414
+ }): NonNullable<KdnaDecryptOptions['decryptEntry']>;
415
+
244
416
  // Lint
245
417
  export function lintDomain(dataMap: KDNAFileDataMap): LintResult;
246
418
 
@@ -45,9 +45,6 @@ function validateDomainSchema(dataMap, schemaMap) {
45
45
  return { valid: true, errors: [], warnings };
46
46
  }
47
47
 
48
- let validCount = 0;
49
- let failCount = 0;
50
-
51
48
  for (const [file, schemaFile] of Object.entries(FILE_TO_SCHEMA)) {
52
49
  if (!dataMap[file]) continue;
53
50
  if (!schemaMap[schemaFile]) {
@@ -69,10 +66,7 @@ function validateDomainSchema(dataMap, schemaMap) {
69
66
  const validate = ajvInstance.compile(schema);
70
67
  const valid = validate(dataMap[file]);
71
68
 
72
- if (valid) {
73
- validCount++;
74
- } else {
75
- failCount++;
69
+ if (!valid) {
76
70
  for (const err of validate.errors || []) {
77
71
  const instancePath = err.instancePath || '/';
78
72
  errors.push(`${file}${instancePath}: ${err.message} (${err.keyword})`);