@aikdna/kdna-core 0.5.0 → 0.7.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.
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Stable public API for third-party KDNA integrations.
3
+ *
4
+ * Lower-level modules remain exported for advanced runtimes, but external
5
+ * adapters should prefer these names. They encode the asset-first contract:
6
+ * callers pass .kdna files or bytes, and no persistent directory extraction is
7
+ * required.
8
+ */
9
+
10
+ const { createKdnaAssetReader } = require('./asset-reader');
11
+ const { lintDomain } = require('./lint-pure');
12
+ const { validateCrossFile, validateDomainSchema } = require('./validate-pure');
13
+ const { formatContext } = require('./loader');
14
+ const {
15
+ composeContextWithAttribution,
16
+ detectDomainConflicts,
17
+ classifySignalsAcrossDomains,
18
+ generateClusterTrace,
19
+ } = require('./compose');
20
+
21
+ function readerFrom(options = {}) {
22
+ return options.reader || createKdnaAssetReader();
23
+ }
24
+
25
+ function isAsset(value) {
26
+ return value && typeof value === 'object' && value.entries instanceof Map && typeof value.readEntry === 'function';
27
+ }
28
+
29
+ async function asAsset(input, options = {}) {
30
+ if (isAsset(input)) return input;
31
+ return readerFrom(options).open(input);
32
+ }
33
+
34
+ function asAssetSync(input, options = {}) {
35
+ if (isAsset(input)) return input;
36
+ return readerFrom(options).openSync(input);
37
+ }
38
+
39
+ async function openKDNA(input, options = {}) {
40
+ return asAsset(input, options);
41
+ }
42
+
43
+ function openKDNASync(input, options = {}) {
44
+ return asAssetSync(input, options);
45
+ }
46
+
47
+ async function inspectKDNA(input, options = {}) {
48
+ const reader = readerFrom(options);
49
+ const asset = await asAsset(input, { ...options, reader });
50
+ const profile = await reader.loadProfile(asset, 'index', options);
51
+ const verification = options.verify === false ? null : await reader.verify(asset, options);
52
+ return {
53
+ name: profile.name,
54
+ version: profile.version,
55
+ judgment_version: profile.judgment_version,
56
+ access: profile.manifest.access || 'open',
57
+ status: profile.manifest.status || null,
58
+ quality_badge: profile.manifest.quality_badge || null,
59
+ risk_level: profile.manifest.risk_level || null,
60
+ keywords: profile.keywords,
61
+ entries: profile.entries,
62
+ asset_digest: profile.asset_digest,
63
+ content_digest: profile.content_digest,
64
+ signature_valid: verification ? verification.signature_valid : null,
65
+ ok: verification ? verification.ok : null,
66
+ errors: verification ? verification.errors : [],
67
+ warnings: verification ? verification.warnings : [],
68
+ manifest: profile.manifest,
69
+ };
70
+ }
71
+
72
+ function inspectKDNASync(input, options = {}) {
73
+ const reader = readerFrom(options);
74
+ const asset = asAssetSync(input, { ...options, reader });
75
+ const profile = reader.loadProfileSync(asset, 'index', options);
76
+ const verification = options.verify === false ? null : reader.verifySync(asset, options);
77
+ return {
78
+ name: profile.name,
79
+ version: profile.version,
80
+ judgment_version: profile.judgment_version,
81
+ access: profile.manifest.access || 'open',
82
+ status: profile.manifest.status || null,
83
+ quality_badge: profile.manifest.quality_badge || null,
84
+ risk_level: profile.manifest.risk_level || null,
85
+ keywords: profile.keywords,
86
+ entries: profile.entries,
87
+ asset_digest: profile.asset_digest,
88
+ content_digest: profile.content_digest,
89
+ signature_valid: verification ? verification.signature_valid : null,
90
+ ok: verification ? verification.ok : null,
91
+ errors: verification ? verification.errors : [],
92
+ warnings: verification ? verification.warnings : [],
93
+ manifest: profile.manifest,
94
+ };
95
+ }
96
+
97
+ async function loadKDNA(input, options = {}) {
98
+ const reader = readerFrom(options);
99
+ const asset = await asAsset(input, { ...options, reader });
100
+ return reader.loadProfile(asset, options.profile || 'compact', options);
101
+ }
102
+
103
+ function loadKDNASync(input, options = {}) {
104
+ const reader = readerFrom(options);
105
+ const asset = asAssetSync(input, { ...options, reader });
106
+ return reader.loadProfileSync(asset, options.profile || 'compact', options);
107
+ }
108
+
109
+ async function validateKDNA(input, options = {}) {
110
+ const reader = readerFrom(options);
111
+ const asset = await asAsset(input, { ...options, reader });
112
+ const assetResult = await reader.verify(asset, options);
113
+ let dataMap = null;
114
+ let lintResult = { errors: [], warnings: [] };
115
+ let schemaResult = { errors: [], warnings: [] };
116
+ let crossFileResult = { errors: [], warnings: [] };
117
+
118
+ try {
119
+ dataMap = await reader.readDataMap(asset, undefined, options);
120
+ lintResult = lintDomain(dataMap);
121
+ schemaResult = validateDomainSchema(dataMap, options.schemas || {});
122
+ crossFileResult = validateCrossFile(dataMap);
123
+ } catch (e) {
124
+ lintResult.errors.push(e.message);
125
+ }
126
+
127
+ const errors = [
128
+ ...assetResult.errors,
129
+ ...lintResult.errors,
130
+ ...schemaResult.errors,
131
+ ...crossFileResult.errors,
132
+ ];
133
+ const warnings = [
134
+ ...assetResult.warnings,
135
+ ...lintResult.warnings,
136
+ ...schemaResult.warnings,
137
+ ...crossFileResult.warnings,
138
+ ];
139
+ return {
140
+ ok: errors.length === 0,
141
+ errors,
142
+ warnings,
143
+ asset: assetResult,
144
+ lint: lintResult,
145
+ schema: schemaResult,
146
+ cross_file: crossFileResult,
147
+ };
148
+ }
149
+
150
+ function validateKDNASync(input, options = {}) {
151
+ const reader = readerFrom(options);
152
+ const asset = asAssetSync(input, { ...options, reader });
153
+ const assetResult = reader.verifySync(asset, options);
154
+ let lintResult = { errors: [], warnings: [] };
155
+ let schemaResult = { errors: [], warnings: [] };
156
+ let crossFileResult = { errors: [], warnings: [] };
157
+
158
+ try {
159
+ const dataMap = reader.readDataMapSync(asset, undefined, options);
160
+ lintResult = lintDomain(dataMap);
161
+ schemaResult = validateDomainSchema(dataMap, options.schemas || {});
162
+ crossFileResult = validateCrossFile(dataMap);
163
+ } catch (e) {
164
+ lintResult.errors.push(e.message);
165
+ }
166
+
167
+ const errors = [
168
+ ...assetResult.errors,
169
+ ...lintResult.errors,
170
+ ...schemaResult.errors,
171
+ ...crossFileResult.errors,
172
+ ];
173
+ const warnings = [
174
+ ...assetResult.warnings,
175
+ ...lintResult.warnings,
176
+ ...schemaResult.warnings,
177
+ ...crossFileResult.warnings,
178
+ ];
179
+ return {
180
+ ok: errors.length === 0,
181
+ errors,
182
+ warnings,
183
+ asset: assetResult,
184
+ lint: lintResult,
185
+ schema: schemaResult,
186
+ cross_file: crossFileResult,
187
+ };
188
+ }
189
+
190
+ async function renderForAgent(input, options = {}) {
191
+ const loaded = await loadKDNA(input, options);
192
+ if (loaded.context != null) return loaded.context;
193
+ return loaded.domain ? formatContext(loaded.domain) : '';
194
+ }
195
+
196
+ function renderForAgentSync(input, options = {}) {
197
+ const loaded = loadKDNASync(input, options);
198
+ if (loaded.context != null) return loaded.context;
199
+ return loaded.domain ? formatContext(loaded.domain) : '';
200
+ }
201
+
202
+ async function verifyAsset(input, options = {}) {
203
+ const reader = readerFrom(options);
204
+ const asset = await asAsset(input, { ...options, reader });
205
+ return reader.verify(asset, options);
206
+ }
207
+
208
+ function verifyAssetSync(input, options = {}) {
209
+ const reader = readerFrom(options);
210
+ const asset = asAssetSync(input, { ...options, reader });
211
+ return reader.verifySync(asset, options);
212
+ }
213
+
214
+ async function verifyDigest(input, expectedDigest, options = {}) {
215
+ return verifyAsset(input, { ...options, asset_digest: expectedDigest });
216
+ }
217
+
218
+ function verifyDigestSync(input, expectedDigest, options = {}) {
219
+ return verifyAssetSync(input, { ...options, asset_digest: expectedDigest });
220
+ }
221
+
222
+ async function verifySignature(input, options = {}) {
223
+ return verifyAsset(input, { ...options, requireSignature: true });
224
+ }
225
+
226
+ function verifySignatureSync(input, options = {}) {
227
+ return verifyAssetSync(input, { ...options, requireSignature: true });
228
+ }
229
+
230
+ function scoreMatch(input, info) {
231
+ const haystack = String(input || '').toLowerCase();
232
+ const terms = [
233
+ info.name,
234
+ ...(info.keywords || []),
235
+ info.manifest?.description,
236
+ info.manifest?.core_insight,
237
+ ]
238
+ .filter(Boolean)
239
+ .map((v) => String(v).toLowerCase());
240
+ const matched = terms.filter((term) => term && haystack.includes(term.replace(/^@[^/]+\//, '')));
241
+ return { score: matched.length, matched };
242
+ }
243
+
244
+ async function matchDomain(input, candidates, options = {}) {
245
+ const results = [];
246
+ for (const candidate of candidates || []) {
247
+ const info = typeof candidate === 'string' || candidate instanceof Uint8Array || isAsset(candidate)
248
+ ? await inspectKDNA(candidate, { ...options, verify: false })
249
+ : candidate;
250
+ const match = scoreMatch(input, info);
251
+ if (match.score > 0) results.push({ ...info, score: match.score, matched: match.matched });
252
+ }
253
+ return results.sort((a, b) => b.score - a.score || String(a.name).localeCompare(String(b.name)));
254
+ }
255
+
256
+ function matchDomainSync(input, candidates, options = {}) {
257
+ const results = [];
258
+ for (const candidate of candidates || []) {
259
+ const info = typeof candidate === 'string' || candidate instanceof Uint8Array || isAsset(candidate)
260
+ ? inspectKDNASync(candidate, { ...options, verify: false })
261
+ : candidate;
262
+ const match = scoreMatch(input, info);
263
+ if (match.score > 0) results.push({ ...info, score: match.score, matched: match.matched });
264
+ }
265
+ return results.sort((a, b) => b.score - a.score || String(a.name).localeCompare(String(b.name)));
266
+ }
267
+
268
+ async function composeKDNA(inputs, options = {}) {
269
+ const loaded = [];
270
+ for (const input of inputs || []) {
271
+ const profile = await loadKDNA(input, { ...options, profile: options.profile || 'compact', context: false });
272
+ if (profile.domain) {
273
+ loaded.push({
274
+ id: profile.manifest.name || profile.domain.core?.meta?.domain,
275
+ name: profile.manifest.name || profile.domain.core?.meta?.domain,
276
+ manifest: profile.manifest,
277
+ ...profile.domain,
278
+ });
279
+ }
280
+ }
281
+ const { selected, excluded } = classifySignalsAcrossDomains(options.input || '', loaded);
282
+ const selectedIds = new Set(selected.map((d) => d.id));
283
+ const activeDomains = options.input ? loaded.filter((d) => selectedIds.has(d.id)) : loaded;
284
+ const conflicts = detectDomainConflicts(activeDomains);
285
+ const { context, attributionMap } = composeContextWithAttribution(activeDomains, options);
286
+ return {
287
+ domains: loaded,
288
+ activeDomains,
289
+ selected,
290
+ excluded,
291
+ conflicts,
292
+ context,
293
+ attributionMap,
294
+ trace: generateClusterTrace({
295
+ input: options.input || '',
296
+ loadedDomains: loaded,
297
+ activeDomains,
298
+ conflicts,
299
+ }),
300
+ };
301
+ }
302
+
303
+ module.exports = {
304
+ openKDNA,
305
+ openKDNASync,
306
+ inspectKDNA,
307
+ inspectKDNASync,
308
+ loadKDNA,
309
+ loadKDNASync,
310
+ validateKDNA,
311
+ validateKDNASync,
312
+ renderForAgent,
313
+ renderForAgentSync,
314
+ verifyAsset,
315
+ verifyAssetSync,
316
+ verifyDigest,
317
+ verifyDigestSync,
318
+ verifySignature,
319
+ verifySignatureSync,
320
+ matchDomain,
321
+ matchDomainSync,
322
+ composeKDNA,
323
+ };
package/src/types.d.ts CHANGED
@@ -203,15 +203,18 @@ export interface KDNAFileDataMap {
203
203
  }
204
204
 
205
205
  export interface KDNAManifest {
206
- kdna_spec: string;
206
+ format: 'kdna';
207
+ format_version: '1.0';
208
+ spec_version: string;
207
209
  name: string;
208
210
  version: string;
209
- judgment_version?: string;
210
- status: 'draft' | 'experimental' | 'stable' | 'deprecated' | 'basic' | 'pro';
211
+ judgment_version: string;
212
+ status: 'draft' | 'experimental' | 'stable' | 'deprecated' | 'staging';
213
+ quality_badge: 'untested' | 'tested' | 'validated' | 'expert_reviewed' | 'production_ready';
211
214
  access: 'open' | 'licensed' | 'runtime';
212
215
  language?: string;
213
- default_language?: string;
214
- languages?: string[];
216
+ default_language: string;
217
+ languages: string[];
215
218
  author: { name: string; id?: string; pubkey?: string; public_key_pem?: string };
216
219
  license: { type: string; url?: string };
217
220
  description: string;
@@ -413,6 +416,74 @@ export function createLicensedDecryptEntry(options: {
413
416
  machineFingerprint: string;
414
417
  }): NonNullable<KdnaDecryptOptions['decryptEntry']>;
415
418
 
419
+ // Stable public API — preferred entry points for third-party integrations.
420
+ export type KDNAAssetInput = string | Uint8Array | KdnaAsset;
421
+
422
+ export interface KDNAInspectResult {
423
+ name: string | null;
424
+ version: string | null;
425
+ judgment_version: string | null;
426
+ access: string;
427
+ status: string | null;
428
+ quality_badge: string | null;
429
+ risk_level: string | null;
430
+ keywords: string[];
431
+ entries: string[];
432
+ asset_digest: string;
433
+ content_digest: string;
434
+ signature_valid: boolean | null;
435
+ ok: boolean | null;
436
+ errors: string[];
437
+ warnings: string[];
438
+ manifest: KDNAManifest;
439
+ }
440
+
441
+ export interface KDNAValidationReport {
442
+ ok: boolean;
443
+ errors: string[];
444
+ warnings: string[];
445
+ asset: KdnaAssetVerifyResult;
446
+ lint: LintResult;
447
+ schema: ValidationResult;
448
+ cross_file: ValidationResult;
449
+ }
450
+
451
+ export interface KDNAMatchResult extends KDNAInspectResult {
452
+ score: number;
453
+ matched: string[];
454
+ }
455
+
456
+ export interface KDNAComposeResult {
457
+ domains: Array<LoadedDomain & { id?: string; name?: string; manifest?: KDNAManifest }>;
458
+ activeDomains: Array<LoadedDomain & { id?: string; name?: string; manifest?: KDNAManifest }>;
459
+ selected: Array<{ id: string; name?: string; role?: string; reason: string }>;
460
+ excluded: Array<{ id: string; name?: string; role?: string; reason: string }>;
461
+ conflicts: Array<{ type: string; domains: string[]; description: string }>;
462
+ context: string;
463
+ attributionMap: Record<string, any>;
464
+ trace: Record<string, any>;
465
+ }
466
+
467
+ export function openKDNA(input: KDNAAssetInput): Promise<KdnaAsset>;
468
+ export function openKDNASync(input: KDNAAssetInput): KdnaAsset;
469
+ export function inspectKDNA(input: KDNAAssetInput, options?: { verify?: boolean } & KdnaDecryptOptions): Promise<KDNAInspectResult>;
470
+ export function inspectKDNASync(input: KDNAAssetInput, options?: { verify?: boolean } & KdnaDecryptOptions): KDNAInspectResult;
471
+ export function loadKDNA(input: KDNAAssetInput, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; input?: string; context?: boolean } & KdnaDecryptOptions): Promise<KdnaAssetIndexProfile | KdnaAssetLoadProfile>;
472
+ export function loadKDNASync(input: KDNAAssetInput, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; input?: string; context?: boolean } & KdnaDecryptOptions): KdnaAssetIndexProfile | KdnaAssetLoadProfile;
473
+ export function validateKDNA(input: KDNAAssetInput, options?: { schemas?: Record<string, any>; requireSignature?: boolean; requireDecryption?: boolean } & KdnaDecryptOptions): Promise<KDNAValidationReport>;
474
+ export function validateKDNASync(input: KDNAAssetInput, options?: { schemas?: Record<string, any>; requireSignature?: boolean; requireDecryption?: boolean } & KdnaDecryptOptions): KDNAValidationReport;
475
+ export function renderForAgent(input: KDNAAssetInput, options?: { profile?: 'compact' | 'scenario' | 'full' | string; input?: string } & KdnaDecryptOptions): Promise<string>;
476
+ export function renderForAgentSync(input: KDNAAssetInput, options?: { profile?: 'compact' | 'scenario' | 'full' | string; input?: string } & KdnaDecryptOptions): string;
477
+ export function verifyAsset(input: KDNAAssetInput, options?: { asset_digest?: string; content_digest?: string; requireSignature?: boolean; requireDecryption?: boolean } & KdnaDecryptOptions): Promise<KdnaAssetVerifyResult>;
478
+ export function verifyAssetSync(input: KDNAAssetInput, options?: { asset_digest?: string; content_digest?: string; requireSignature?: boolean; requireDecryption?: boolean } & KdnaDecryptOptions): KdnaAssetVerifyResult;
479
+ export function verifyDigest(input: KDNAAssetInput, expectedDigest: string, options?: KdnaDecryptOptions): Promise<KdnaAssetVerifyResult>;
480
+ export function verifyDigestSync(input: KDNAAssetInput, expectedDigest: string, options?: KdnaDecryptOptions): KdnaAssetVerifyResult;
481
+ export function verifySignature(input: KDNAAssetInput, options?: KdnaDecryptOptions): Promise<KdnaAssetVerifyResult>;
482
+ export function verifySignatureSync(input: KDNAAssetInput, options?: KdnaDecryptOptions): KdnaAssetVerifyResult;
483
+ export function matchDomain(input: string, candidates: Array<KDNAAssetInput | KDNAInspectResult>, options?: KdnaDecryptOptions): Promise<KDNAMatchResult[]>;
484
+ export function matchDomainSync(input: string, candidates: Array<KDNAAssetInput | KDNAInspectResult>, options?: KdnaDecryptOptions): KDNAMatchResult[];
485
+ export function composeKDNA(inputs: KDNAAssetInput[], options?: { input?: string; profile?: 'compact' | 'scenario' | 'full' | string; separator?: string } & KdnaDecryptOptions): Promise<KDNAComposeResult>;
486
+
416
487
  // Lint
417
488
  export function lintDomain(dataMap: KDNAFileDataMap): LintResult;
418
489