@aikdna/kdna-core 0.15.11 → 0.16.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,91 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const v1 = require('./v1');
6
+
7
+ function invalidDirectoryPlan(inputPath) {
8
+ const resolved = path.resolve(inputPath);
9
+ return {
10
+ kdna_version: null,
11
+ asset: { asset_id: null, asset_uid: null, title: null, version: null, judgment_version: null },
12
+ access: null,
13
+ access_alias: null,
14
+ entitlement_profile: null,
15
+ state: 'invalid',
16
+ required_action: 'block',
17
+ can_load_now: false,
18
+ projection_policy: 'none',
19
+ input_fingerprint: null,
20
+ checks: {
21
+ format_valid: false,
22
+ schema_valid: false,
23
+ payload_valid: false,
24
+ checksums_valid: false,
25
+ load_contract_valid: false,
26
+ overall_valid: false,
27
+ },
28
+ issues: [{
29
+ code: 'KDNA_ASSET_FILE_REQUIRED',
30
+ severity: 'blocking',
31
+ message: 'Runtime loading requires a packaged .kdna asset file. Source directories are authoring inputs only.',
32
+ }],
33
+ source: { kind: 'dir', path: resolved },
34
+ };
35
+ }
36
+
37
+ function isDirectory(inputPath) {
38
+ if (typeof inputPath !== 'string') return false;
39
+ try {
40
+ return fs.statSync(path.resolve(inputPath)).isDirectory();
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ function guardResolver(options = {}) {
47
+ if (typeof options.resolveAsset !== 'function') return options;
48
+ const resolveAsset = options.resolveAsset;
49
+ return {
50
+ ...options,
51
+ resolveAsset(name) {
52
+ const resolved = resolveAsset(name);
53
+ if (resolved?.path && isDirectory(resolved.path)) {
54
+ const error = new Error(
55
+ `Resolved asset "${name}" is a source directory. Runtime dependencies must be packaged .kdna files.`,
56
+ );
57
+ error.code = 'KDNA_ASSET_FILE_REQUIRED';
58
+ throw error;
59
+ }
60
+ return resolved;
61
+ },
62
+ };
63
+ }
64
+
65
+ function planLoad(inputPath, options = {}) {
66
+ if (isDirectory(inputPath)) return invalidDirectoryPlan(inputPath);
67
+ return v1.planLoad(inputPath, guardResolver(options));
68
+ }
69
+
70
+ function loadAuthorized(inputPath, options = {}) {
71
+ const plan = planLoad(inputPath, options);
72
+ if (plan.can_load_now !== true) {
73
+ const issueCodes = Array.isArray(plan.issues)
74
+ ? plan.issues.map((issue) => issue.code).filter(Boolean)
75
+ : [];
76
+ const error = new Error(
77
+ `LoadPlan denied loading: state=${plan.state || 'invalid'} required_action=${plan.required_action || 'block'}`,
78
+ );
79
+ error.code = issueCodes[0] || 'KDNA_LOAD_NOT_AUTHORIZED';
80
+ error.plan = plan;
81
+ throw error;
82
+ }
83
+ return v1.loadAuthorized(inputPath, guardResolver(options));
84
+ }
85
+
86
+ module.exports = {
87
+ planLoad,
88
+ loadAuthorized,
89
+ load: loadAuthorized,
90
+ loadAsset: loadAuthorized,
91
+ };
package/src/types.d.ts CHANGED
@@ -203,9 +203,7 @@ export interface KDNAFileDataMap {
203
203
  }
204
204
 
205
205
  export interface KDNAManifest {
206
- format: 'kdna';
207
- format_version: '2.0';
208
- spec_version: string;
206
+ kdna_version: '1.0';
209
207
  name: string;
210
208
  version: string;
211
209
  judgment_version: string;
@@ -416,6 +414,165 @@ export function createLicensedDecryptEntry(options: {
416
414
  machineFingerprint: string;
417
415
  }): NonNullable<KdnaDecryptOptions['decryptEntry']>;
418
416
 
417
+ export const EXTERNAL_ENVELOPE_PROFILE: 'kdna-envelope-external-grant-v1';
418
+ export const EXTERNAL_GRANT_PROFILE: 'kdna-key-grant-v1';
419
+
420
+ export class KDNAExternalGrantError extends Error {
421
+ code: string;
422
+ }
423
+
424
+ export interface KDNAExternalGrantEnvelope {
425
+ profile: 'kdna-envelope-external-grant-v1';
426
+ alg: 'A256GCM';
427
+ cek_derivation: 'HKDF-SHA256';
428
+ key_ref: string;
429
+ issuer_key_id: string;
430
+ entry_path: string;
431
+ plaintext_digest: string;
432
+ iv: string;
433
+ tag: string;
434
+ ciphertext: string;
435
+ }
436
+
437
+ export interface KDNAExternalKeyGrant {
438
+ profile: 'kdna-key-grant-v1';
439
+ grant_id: string;
440
+ issuer: string;
441
+ signing_key_id: string;
442
+ entitlement_id: string;
443
+ account_id: string;
444
+ device_id: string;
445
+ device_public_key: string;
446
+ device_signing_public_key: string;
447
+ asset: {
448
+ asset_id: string;
449
+ asset_uid: string;
450
+ version: string;
451
+ digest: string;
452
+ entry_path: string;
453
+ ciphertext_digest: string;
454
+ key_ref: string;
455
+ issuer_key_id: string;
456
+ };
457
+ issued_at: string;
458
+ refresh_after: string;
459
+ offline_grace_until: string;
460
+ expires_at: string;
461
+ status: 'active' | 'revoked' | 'expired';
462
+ status_version: number;
463
+ wrap: {
464
+ alg: 'X25519-HKDF-SHA256+A256KW';
465
+ ephemeral_public_key: string;
466
+ salt: string;
467
+ wrapped_cek: string;
468
+ };
469
+ signature: string;
470
+ }
471
+
472
+ export interface KDNADeviceKeyPairs {
473
+ agreement: { public_key: string; private_key: string };
474
+ signing: { public_key: string; private_key: string };
475
+ }
476
+
477
+ /** Runtime-branded by authorizeExternalKeyGrant; a structurally similar object is not accepted. */
478
+ export interface KDNAVerifiedExternalEntitlement {
479
+ readonly status: 'active' | 'offline_grace';
480
+ readonly profile: 'kdna-key-grant-v1';
481
+ readonly grant_id: string;
482
+ readonly entitlement_id: string;
483
+ readonly account_id: string;
484
+ readonly device_id: string;
485
+ readonly refresh_after: string;
486
+ readonly offline_grace_until: string;
487
+ readonly expires_at: string;
488
+ readonly asset: {
489
+ readonly asset_id: string;
490
+ readonly asset_uid: string;
491
+ readonly version: string;
492
+ readonly digest: string;
493
+ readonly entry_path: string;
494
+ readonly ciphertext_digest: string;
495
+ readonly key_ref: string;
496
+ readonly issuer_key_id: string;
497
+ };
498
+ }
499
+
500
+ export interface KDNAExternalAuthorization {
501
+ entitlement: KDNAVerifiedExternalEntitlement;
502
+ decryptEntry: NonNullable<KdnaDecryptOptions['decryptEntry']>;
503
+ dispose(): void;
504
+ }
505
+
506
+ export function canonicalJson(value: unknown): string;
507
+ export function grantSigningPayload(grant: Omit<KDNAExternalKeyGrant, 'signature'> | KDNAExternalKeyGrant): Uint8Array;
508
+ export function validateExternalEnvelope(value: unknown): KDNAExternalGrantEnvelope;
509
+ export function validateExternalKeyGrant(value: unknown): KDNAExternalKeyGrant;
510
+ export function externalEnvelopeAad(options: {
511
+ manifest: KDNAManifest;
512
+ entryName: string;
513
+ plaintextDigest: string;
514
+ keyRef: string;
515
+ issuerKeyId: string;
516
+ }): Uint8Array;
517
+ export function deriveExternalAssetCek(options: {
518
+ issuerRootKey: string | Uint8Array;
519
+ manifest: KDNAManifest;
520
+ entryName: string;
521
+ plaintextDigest: string;
522
+ keyRef: string;
523
+ issuerKeyId: string;
524
+ }): Uint8Array;
525
+ export function encodeExternalEnvelope(value: KDNAExternalGrantEnvelope): Uint8Array;
526
+ export function decodeExternalEnvelope(value: KDNAExternalGrantEnvelope | Uint8Array): KDNAExternalGrantEnvelope;
527
+ export function encryptExternalGrantEntry(plaintext: string | Uint8Array, options: {
528
+ manifest: KDNAManifest;
529
+ entryName?: string;
530
+ issuerRootKey: string | Uint8Array;
531
+ keyRef: string;
532
+ issuerKeyId: string;
533
+ iv?: Uint8Array;
534
+ }): KDNAExternalGrantEnvelope;
535
+ export function generateDeviceKeyPairs(): KDNADeviceKeyPairs;
536
+ export function createExternalKeyGrant(options: {
537
+ issuerRootKey: string | Uint8Array;
538
+ issuerSigningPrivateKey: unknown;
539
+ signingKeyId: string;
540
+ issuer: string;
541
+ entitlementId: string;
542
+ accountId: string;
543
+ deviceId: string;
544
+ devicePublicKey: string;
545
+ deviceSigningPublicKey: string;
546
+ manifest: KDNAManifest;
547
+ envelope: KDNAExternalGrantEnvelope | Uint8Array;
548
+ assetDigest: string;
549
+ status?: 'active' | 'revoked' | 'expired';
550
+ statusVersion?: number;
551
+ issuedAt?: Date | string;
552
+ refreshAfter?: Date | string;
553
+ offlineGraceUntil?: Date | string;
554
+ expiresAt?: Date | string;
555
+ grantId?: string;
556
+ ephemeralKeyPair?: unknown;
557
+ wrapSalt?: Uint8Array;
558
+ }): KDNAExternalKeyGrant;
559
+ export function authorizeExternalKeyGrant(options: {
560
+ grant: KDNAExternalKeyGrant;
561
+ issuerPublicKeys: Map<string, unknown> | Record<string, unknown>;
562
+ manifest: KDNAManifest;
563
+ checksums: KDNAChecksums;
564
+ envelope: KDNAExternalGrantEnvelope | Uint8Array;
565
+ deviceAgreementKey: KDNADeviceKeyPairs['agreement'] | unknown;
566
+ expectedAccountId: string;
567
+ expectedDeviceId: string;
568
+ expectedDeviceSigningPublicKey: string;
569
+ minimumStatusVersion?: number;
570
+ now?: Date | string;
571
+ networkAvailable?: boolean;
572
+ allowOffline?: boolean;
573
+ }): KDNAExternalAuthorization;
574
+ export function isVerifiedExternalEntitlement(value: unknown): value is KDNAVerifiedExternalEntitlement;
575
+
419
576
  // Stable public API — preferred entry points for third-party integrations.
420
577
  export type KDNAAssetInput = string | Uint8Array | KdnaAsset;
421
578
 
@@ -484,36 +641,33 @@ export function matchDomain(input: string, candidates: Array<KDNAAssetInput | KD
484
641
  export function matchDomainSync(input: string, candidates: Array<KDNAAssetInput | KDNAInspectResult>, options?: KdnaDecryptOptions): KDNAMatchResult[];
485
642
  export function composeKDNA(inputs: KDNAAssetInput[], options?: { input?: string; profile?: 'compact' | 'scenario' | 'full' | string; separator?: string } & KdnaDecryptOptions): Promise<KDNAComposeResult>;
486
643
 
487
- // KDNA Core v1 — source directory / container API
644
+ // KDNA Core — authoring source and packaged runtime API
488
645
  export const MIMETYPE: string;
489
- export const MIMETYPE_V1: string;
490
- export const MIMETYPE_V2: string;
491
- export const V1_REQUIRED_DIR_ENTRIES: string[];
646
+ export const REQUIRED_DIR_ENTRIES: string[];
492
647
 
493
- export interface KDNAV1ChecksumEntry {
648
+ export interface KDNAChecksumEntry {
494
649
  algorithm: 'sha256';
495
650
  value: string;
496
651
  }
497
652
 
498
- export interface KDNAV1Checksums {
653
+ export interface KDNAChecksums {
499
654
  algorithm: 'sha256';
500
655
  manifest_digest: string;
501
656
  payload_digest: string;
502
657
  asset_digest: string;
503
- entries: Record<string, KDNAV1ChecksumEntry>;
658
+ entries: Record<string, KDNAChecksumEntry>;
504
659
  }
505
660
 
506
- export function isV1SourceDir(inputPath: string): boolean;
507
- export function isV2SourceDir(inputPath: string): boolean;
508
- export function detectContainerFormat(inputPath: string): 'v1' | 'v2' | null;
661
+ export function isKdnaSourceDir(inputPath: string): boolean;
662
+ export function detectContainerFormat(inputPath: string): 'kdna' | null;
509
663
  export function inspect(inputPath: string, options?: Record<string, any>): Record<string, any>;
510
664
  export function validate(inputPath: string, options?: Record<string, any>): Record<string, any>;
511
- export interface KDNAV1LoadPlanIssue {
665
+ export interface KDNALoadPlanIssue {
512
666
  code: string;
513
667
  severity: 'info' | 'warning' | 'blocking' | string;
514
668
  message: string;
515
669
  }
516
- export interface KDNAV1LoadPlan {
670
+ export interface KDNALoadPlan {
517
671
  kdna_version: string | null;
518
672
  asset: {
519
673
  asset_id: string | null;
@@ -552,18 +706,19 @@ export interface KDNAV1LoadPlan {
552
706
  projection_policy: 'minimal' | 'remote' | 'none' | string;
553
707
  input_fingerprint: string | null;
554
708
  checks: Record<string, boolean>;
555
- issues: KDNAV1LoadPlanIssue[];
709
+ issues: KDNALoadPlanIssue[];
556
710
  source: {
557
711
  kind: 'dir' | 'file' | string | null;
558
712
  path: string;
559
713
  };
560
714
  }
561
- export function planLoad(inputPath: string, options?: { password?: string; hasPassword?: boolean; entitlement?: Record<string, any> }): KDNAV1LoadPlan;
562
- export function buildChecksumsV1(sourceDir: string): KDNAV1Checksums;
715
+ export function planLoad(inputPath: string, options?: { password?: string; hasPassword?: boolean; entitlement?: KDNAVerifiedExternalEntitlement | Record<string, any> }): KDNALoadPlan;
716
+ export function buildChecksums(sourceDir: string): KDNAChecksums;
563
717
  export function pack(sourceDir: string, outputPath: string): void;
564
718
  export function unpack(inputPath: string, outputDir: string): void;
565
719
  export function loadAuthorized(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
566
- export function loadV1(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
720
+ export function load(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
721
+ export function loadAsset(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
567
722
  export const FORBIDDEN_OUTPUT_TERMS: readonly string[];
568
723
 
569
724
  // Lint