@aikdna/kdna-studio-cli 0.9.2 → 0.10.2
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 +15 -0
- package/bin/kdna-studio.js +420 -142
- package/package.json +13 -7
package/README.md
CHANGED
|
@@ -125,6 +125,11 @@ A KDNA runtime export contains only these top-level entries:
|
|
|
125
125
|
- `payload.kdnab`
|
|
126
126
|
- `checksums.json`
|
|
127
127
|
|
|
128
|
+
The current producer writes `format_version: 0.1.0`, payload profile
|
|
129
|
+
`kdna.payload.judgment` with `profile_version: 0.1.0`, and digest profile
|
|
130
|
+
`kdna.digest-basis.runtime-entry-set` with `digest_profile_version: 0.1.0`.
|
|
131
|
+
These compatibility coordinates are independent of package versions.
|
|
132
|
+
|
|
128
133
|
Authoring/source entries such as `KDNA_Core.json`, `KDNA_Patterns.json`, and
|
|
129
134
|
`source_cards` are not runtime distribution entries. They may exist in Studio
|
|
130
135
|
compile output or legacy imports, but they must not be emitted by the runtime
|
|
@@ -161,6 +166,16 @@ kdna-studio create forked --from-kdna ./parent.kdna --name @scope/forked
|
|
|
161
166
|
kdna-studio create migrated --from-folder ./old-domain-json --name @scope/migrated
|
|
162
167
|
```
|
|
163
168
|
|
|
169
|
+
Current `.kdna` imports also preserve explicitly declared `highest_question`,
|
|
170
|
+
`worldview`, ordered `value_order`, and `judgment_role` values in the editable
|
|
171
|
+
project. Re-export writes those values back exactly; Studio does not infer a
|
|
172
|
+
replacement judgment core from an axiom.
|
|
173
|
+
|
|
174
|
+
Imports preserve authored source references, `core_structure` relations,
|
|
175
|
+
pattern subtypes, scenario and case metadata, extended reasoning fields, and
|
|
176
|
+
source-authored evolution. Studio-only card state and lock audit records remain
|
|
177
|
+
authoring provenance and are not rewritten as Runtime judgment evolution.
|
|
178
|
+
|
|
164
179
|
## Related
|
|
165
180
|
|
|
166
181
|
- [KDNA Core](https://github.com/aikdna/kdna) — Official format specification
|
package/bin/kdna-studio.js
CHANGED
|
@@ -9,7 +9,7 @@ const { execFileSync } = require('child_process');
|
|
|
9
9
|
|
|
10
10
|
function loadStudioCore() {
|
|
11
11
|
const publishedCore = require('@aikdna/kdna-studio-core');
|
|
12
|
-
if (publishedCore.exportRuntime) return publishedCore;
|
|
12
|
+
if (publishedCore.exportRuntime && publishedCore.protocolContract) return publishedCore;
|
|
13
13
|
try {
|
|
14
14
|
return require('../../kdna-studio-core/src');
|
|
15
15
|
} catch (_) {
|
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
creator: creatorApi,
|
|
27
27
|
distillation: distillationApi,
|
|
28
28
|
exportRuntime,
|
|
29
|
+
protocolContract,
|
|
29
30
|
} = loadStudioCore();
|
|
30
31
|
|
|
31
32
|
const llm = require('../src/llm');
|
|
@@ -365,12 +366,23 @@ function cleanFields(fields = {}) {
|
|
|
365
366
|
return cleaned;
|
|
366
367
|
}
|
|
367
368
|
|
|
369
|
+
function importedFields(entry = {}, options = {}) {
|
|
370
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return {};
|
|
371
|
+
const fields = JSON.parse(JSON.stringify(entry));
|
|
372
|
+
for (const key of ['id', 'status', 'locked', 'human_lock', 'audit_log', 'source_authored']) {
|
|
373
|
+
delete fields[key];
|
|
374
|
+
}
|
|
375
|
+
if (options.keepType !== true) delete fields.type;
|
|
376
|
+
return cleanFields(fields);
|
|
377
|
+
}
|
|
378
|
+
|
|
368
379
|
function importedCard(type, fields = {}, id = null) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
380
|
+
const card = cardApi.createCard(type, cleanFields(fields), id || null);
|
|
381
|
+
if (card === null) {
|
|
382
|
+
throw new Error(`failed to create card type="${type}" id="${id || '(auto)'}" — ` +
|
|
383
|
+
`the card type may require additional required fields or the fields may be malformed`);
|
|
373
384
|
}
|
|
385
|
+
return card;
|
|
374
386
|
}
|
|
375
387
|
|
|
376
388
|
// Bug (#3 + #4 UX): payload.kdnab intentionally carries the same
|
|
@@ -400,8 +412,10 @@ function pushImportedCard(cards, type, fields = {}, id = null) {
|
|
|
400
412
|
}
|
|
401
413
|
return;
|
|
402
414
|
}
|
|
403
|
-
if (id) _importedIds.add(id);
|
|
404
415
|
const card = importedCard(type, fields, id);
|
|
416
|
+
// Register the id only after card creation succeeds; a failed creation
|
|
417
|
+
// must not consume the id and prevent retry.
|
|
418
|
+
if (card && id) _importedIds.add(id);
|
|
405
419
|
if (card) cards.push(card);
|
|
406
420
|
}
|
|
407
421
|
|
|
@@ -437,31 +451,22 @@ function cardsFromRuntimePayload(payload) {
|
|
|
437
451
|
if (cards.length > 0) return cards;
|
|
438
452
|
|
|
439
453
|
for (const ax of (Array.isArray(payload.core?.axioms) ? payload.core?.axioms : [])) {
|
|
440
|
-
pushImportedCard(cards, 'axiom',
|
|
441
|
-
one_sentence: ax.one_sentence || '',
|
|
442
|
-
full_statement: ax.full_statement || '',
|
|
443
|
-
why: ax.why || '',
|
|
444
|
-
applies_when: ax.applies_when || [],
|
|
445
|
-
does_not_apply_when: ax.does_not_apply_when || [],
|
|
446
|
-
failure_risk: ax.failure_risk || '',
|
|
447
|
-
confidence: ax.confidence || '',
|
|
448
|
-
evidence_type: ax.evidence_type || '',
|
|
449
|
-
}, ax.id || null);
|
|
454
|
+
pushImportedCard(cards, 'axiom', importedFields(ax), ax.id || null);
|
|
450
455
|
}
|
|
451
456
|
for (const ont of (Array.isArray(payload.core?.ontology) ? payload.core?.ontology : [])) {
|
|
452
|
-
pushImportedCard(cards, 'ontology', ont, ont.id || null);
|
|
457
|
+
pushImportedCard(cards, 'ontology', importedFields(ont), ont.id || null);
|
|
453
458
|
}
|
|
454
459
|
for (const fw of (Array.isArray(payload.core?.frameworks) ? payload.core?.frameworks : [])) {
|
|
455
|
-
pushImportedCard(cards, 'framework', fw, fw.id || null);
|
|
460
|
+
pushImportedCard(cards, 'framework', importedFields(fw), fw.id || null);
|
|
456
461
|
}
|
|
457
462
|
for (const stance of (Array.isArray(payload.core?.stances) ? payload.core?.stances : [])) {
|
|
458
|
-
pushImportedCard(cards, 'stance', stance, stance.id || null);
|
|
463
|
+
pushImportedCard(cards, 'stance', importedFields(stance), stance.id || null);
|
|
459
464
|
}
|
|
460
465
|
for (const aesthetic of (Array.isArray(payload.core?.aesthetics) ? payload.core?.aesthetics : [])) {
|
|
461
|
-
pushImportedCard(cards, 'aesthetic', aesthetic, aesthetic.id || null);
|
|
466
|
+
pushImportedCard(cards, 'aesthetic', importedFields(aesthetic), aesthetic.id || null);
|
|
462
467
|
}
|
|
463
468
|
for (const boundary of (Array.isArray(payload.core?.boundaries) ? payload.core?.boundaries : [])) {
|
|
464
|
-
pushImportedCard(cards, 'boundary', boundary, boundary.id || null);
|
|
469
|
+
pushImportedCard(cards, 'boundary', importedFields(boundary), boundary.id || null);
|
|
465
470
|
}
|
|
466
471
|
// risk_model may be either an array of risk items (legacy) or
|
|
467
472
|
// `{risks: [...]}` (current). Treat both shapes; ignore everything else
|
|
@@ -469,7 +474,7 @@ function cardsFromRuntimePayload(payload) {
|
|
|
469
474
|
const riskModel = payload.core?.risk_model;
|
|
470
475
|
const riskItems = Array.isArray(riskModel) ? riskModel : (riskModel?.risks || []);
|
|
471
476
|
for (const risk of riskItems) {
|
|
472
|
-
pushImportedCard(cards, 'risk', risk, risk.id || null);
|
|
477
|
+
pushImportedCard(cards, 'risk', importedFields(risk), risk.id || null);
|
|
473
478
|
}
|
|
474
479
|
for (const pattern of (Array.isArray(payload.patterns) ? payload.patterns : [])) {
|
|
475
480
|
// Bug (#146): prior version dropped any payload.patterns entry
|
|
@@ -499,32 +504,34 @@ function cardsFromRuntimePayload(payload) {
|
|
|
499
504
|
);
|
|
500
505
|
continue;
|
|
501
506
|
}
|
|
502
|
-
fields =
|
|
503
|
-
delete fields.type;
|
|
507
|
+
fields = importedFields(pattern);
|
|
504
508
|
} else {
|
|
505
|
-
|
|
506
|
-
|
|
509
|
+
fields = importedFields(pattern, { keepType: type === 'pattern' || LEGACY_PATTERN_SUBTYPES.has(type) });
|
|
510
|
+
}
|
|
511
|
+
if (LEGACY_PATTERN_SUBTYPES.has(type)) {
|
|
512
|
+
fields = {
|
|
513
|
+
...fields,
|
|
514
|
+
type,
|
|
515
|
+
legacy_subtype: fields.legacy_subtype || type,
|
|
516
|
+
};
|
|
517
|
+
type = 'pattern';
|
|
507
518
|
}
|
|
508
519
|
pushImportedCard(cards, type, fields, pattern.id || null);
|
|
509
520
|
}
|
|
510
521
|
for (const scenario of (Array.isArray(payload.scenarios) ? payload.scenarios : [])) {
|
|
511
|
-
pushImportedCard(cards, 'scenario', scenario, scenario.id || null);
|
|
522
|
+
pushImportedCard(cards, 'scenario', importedFields(scenario), scenario.id || null);
|
|
512
523
|
}
|
|
513
524
|
for (const item of (Array.isArray(payload.cases) ? payload.cases : [])) {
|
|
514
|
-
pushImportedCard(cards, 'case', item, item.id || null);
|
|
515
|
-
}
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
|
|
520
|
-
// the field was a truthy non-array (e.g. a single `{question: ...}`
|
|
521
|
-
// object instead of an array). The fix guards with Array.isArray.
|
|
522
|
-
const rawSelfCheck = payload.reasoning?.self_check
|
|
523
|
-
|| payload.reasoning?.self_checks;
|
|
525
|
+
pushImportedCard(cards, 'case', importedFields(item), item.id || null);
|
|
526
|
+
}
|
|
527
|
+
// The current payload contract has one self-check field. Refuse to infer a
|
|
528
|
+
// parallel field name: Core validation owns format migration, while Studio
|
|
529
|
+
// imports only the current validated payload shape.
|
|
530
|
+
const rawSelfCheck = payload.reasoning?.self_check;
|
|
524
531
|
const selfCheckArr = Array.isArray(rawSelfCheck) ? rawSelfCheck : [];
|
|
525
532
|
for (const selfCheck of selfCheckArr) {
|
|
526
|
-
const fields = typeof selfCheck === 'string' ? { question: selfCheck } : selfCheck;
|
|
527
|
-
pushImportedCard(cards, 'self_check', fields,
|
|
533
|
+
const fields = typeof selfCheck === 'string' ? { question: selfCheck } : importedFields(selfCheck);
|
|
534
|
+
pushImportedCard(cards, 'self_check', fields, typeof selfCheck === 'object' ? selfCheck.id || null : null);
|
|
528
535
|
}
|
|
529
536
|
for (const failureMode of (Array.isArray(payload.reasoning?.failure_modes) ? payload.reasoning?.failure_modes : [])) {
|
|
530
537
|
// Bug (#145 follow-up): the prior version dropped three
|
|
@@ -533,7 +540,8 @@ function cardsFromRuntimePayload(payload) {
|
|
|
533
540
|
// project that round-tripped through `migrate` lost
|
|
534
541
|
// these fields every time. The fix reads them through, matching
|
|
535
542
|
// the export-side map in kdna-studio-core's buildPayload.
|
|
536
|
-
|
|
543
|
+
const fields = {
|
|
544
|
+
...importedFields(failureMode),
|
|
537
545
|
wrong: failureMode.mode || failureMode.wrong || '',
|
|
538
546
|
correct: failureMode.correct || '',
|
|
539
547
|
key_distinction: failureMode.key_distinction || '',
|
|
@@ -541,7 +549,9 @@ function cardsFromRuntimePayload(payload) {
|
|
|
541
549
|
failure_risk: failureMode.failure_risk || '',
|
|
542
550
|
applies_when: Array.isArray(failureMode.applies_when) ? failureMode.applies_when : [],
|
|
543
551
|
does_not_apply_when: Array.isArray(failureMode.does_not_apply_when) ? failureMode.does_not_apply_when : [],
|
|
544
|
-
}
|
|
552
|
+
};
|
|
553
|
+
delete fields.mode;
|
|
554
|
+
pushImportedCard(cards, 'misunderstanding', fields, failureMode.id || null);
|
|
545
555
|
}
|
|
546
556
|
for (const chain of (Array.isArray(payload.reasoning?.reasoning_chains) ? payload.reasoning?.reasoning_chains : [])) {
|
|
547
557
|
// Bug (#4 UX): compile/index.js synthesises one reasoning chain
|
|
@@ -550,7 +560,7 @@ function cardsFromRuntimePayload(payload) {
|
|
|
550
560
|
// doubled the card count on round-trip. The fix skips chains
|
|
551
561
|
// that the producer explicitly marked as synthesised.
|
|
552
562
|
if (chain.source_authored === false) continue;
|
|
553
|
-
pushImportedCard(cards, 'reasoning', chain, chain.id || null);
|
|
563
|
+
pushImportedCard(cards, 'reasoning', importedFields(chain), chain.id || null);
|
|
554
564
|
}
|
|
555
565
|
for (const stage of (Array.isArray(payload.evolution?.stages) ? payload.evolution?.stages : [])) {
|
|
556
566
|
// Distinguish source-authored stages (preserved identity) from
|
|
@@ -559,7 +569,7 @@ function cardsFromRuntimePayload(payload) {
|
|
|
559
569
|
// so the importer doesn't double-count what was already in the
|
|
560
570
|
// locked judgment cards.
|
|
561
571
|
if (stage.source_authored === true) {
|
|
562
|
-
pushImportedCard(cards, 'evolution_stage', stage, stage.id || null);
|
|
572
|
+
pushImportedCard(cards, 'evolution_stage', importedFields(stage), stage.id || null);
|
|
563
573
|
}
|
|
564
574
|
}
|
|
565
575
|
return cards;
|
|
@@ -567,12 +577,27 @@ function cardsFromRuntimePayload(payload) {
|
|
|
567
577
|
|
|
568
578
|
function cardsFromPayload(payload) {
|
|
569
579
|
if (!payload || typeof payload !== 'object') return [];
|
|
570
|
-
if (
|
|
580
|
+
if (
|
|
581
|
+
payload.profile === protocolContract.PAYLOAD_PROFILE &&
|
|
582
|
+
payload.profile_version === protocolContract.PAYLOAD_PROFILE_VERSION
|
|
583
|
+
) {
|
|
571
584
|
return cardsFromRuntimePayload(payload);
|
|
572
585
|
}
|
|
573
586
|
return [];
|
|
574
587
|
}
|
|
575
588
|
|
|
589
|
+
function judgmentCoreFromRuntimePayload(payload) {
|
|
590
|
+
const core = payload?.core;
|
|
591
|
+
if (!core || typeof core !== 'object' || Array.isArray(core)) return null;
|
|
592
|
+
const declared = {};
|
|
593
|
+
for (const field of ['highest_question', 'worldview', 'value_order', 'judgment_role']) {
|
|
594
|
+
if (Object.hasOwn(core, field)) {
|
|
595
|
+
declared[field] = JSON.parse(JSON.stringify(core[field]));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return Object.keys(declared).length > 0 ? declared : null;
|
|
599
|
+
}
|
|
600
|
+
|
|
576
601
|
function buildManifest(project, name) {
|
|
577
602
|
// Bug (#58): prior version was a hand-rolled manifest builder that
|
|
578
603
|
// shared zero logic with the canonical buildManifest in
|
|
@@ -591,12 +616,12 @@ function buildManifest(project, name) {
|
|
|
591
616
|
const assetId = assetIdFromName(name || source.name || project.name);
|
|
592
617
|
const author = source.author || project.author || {};
|
|
593
618
|
return {
|
|
594
|
-
|
|
619
|
+
format_version: protocolContract.FORMAT_VERSION,
|
|
595
620
|
asset_id: assetId,
|
|
596
621
|
asset_uid: project.asset_uid || source.asset_uid || 'urn:uuid:' + crypto.randomUUID(),
|
|
597
622
|
asset_type: project.type === 'cluster' ? 'cluster' : 'domain',
|
|
598
623
|
source_asset_type: source.asset_type || null,
|
|
599
|
-
title: titleFromName(source.name || name || project.name, assetId),
|
|
624
|
+
title: source.title || project.title || titleFromName(source.name || name || project.name, assetId),
|
|
600
625
|
version: semverValue(project.release?.version || source.version, '1.0.0'),
|
|
601
626
|
judgment_version: semverValue(project.release?.judgment_version || source.judgment_version || source.version, '1.0.0'),
|
|
602
627
|
created_at: isoDateTime(source.created_at || source.created || project.created),
|
|
@@ -605,7 +630,11 @@ function buildManifest(project, name) {
|
|
|
605
630
|
name: author.name || project.author?.name || 'Studio Export',
|
|
606
631
|
id: author.id || project.author?.id || undefined,
|
|
607
632
|
},
|
|
608
|
-
compatibility: {
|
|
633
|
+
compatibility: {
|
|
634
|
+
min_loader_version: '0.19.0',
|
|
635
|
+
profile: protocolContract.PAYLOAD_PROFILE,
|
|
636
|
+
profile_version: protocolContract.PAYLOAD_PROFILE_VERSION,
|
|
637
|
+
},
|
|
609
638
|
payload: { path: 'payload.kdnab', encoding: 'cbor', encrypted: false },
|
|
610
639
|
summary: source.core_insight || project.release?.description || source.description || '',
|
|
611
640
|
description: source.description || project.release?.description || '',
|
|
@@ -664,6 +693,7 @@ function cmdCreate(args) {
|
|
|
664
693
|
sourceMode,
|
|
665
694
|
creatorIdentity,
|
|
666
695
|
lineage,
|
|
696
|
+
judgmentCore: kdnaData.judgment_core,
|
|
667
697
|
});
|
|
668
698
|
project.cards = kdnaData.cards;
|
|
669
699
|
// Bug #15 / #28 follow-up: store the source KDNA_* files on the
|
|
@@ -674,7 +704,13 @@ function cmdCreate(args) {
|
|
|
674
704
|
if (kdnaData.source_patterns) project.source_patterns = kdnaData.source_patterns;
|
|
675
705
|
if (kdnaData.source_reasoning) project.source_reasoning = kdnaData.source_reasoning;
|
|
676
706
|
if (kdnaData.source_evolution) project.source_evolution = kdnaData.source_evolution;
|
|
707
|
+
if (kdnaData.source_core_structure) project.source_core_structure = kdnaData.source_core_structure;
|
|
677
708
|
if (kdnaData.source_manifest) project.source_manifest = publicManifestMetadata(kdnaData.source_manifest);
|
|
709
|
+
if (kdnaData.source_manifest?.asset_uid) project.asset_uid = kdnaData.source_manifest.asset_uid;
|
|
710
|
+
if (kdnaData.source_manifest?.title) project.title = kdnaData.source_manifest.title;
|
|
711
|
+
if (kdnaData.source_manifest?.license) project.license = kdnaData.source_manifest.license;
|
|
712
|
+
if (kdnaData.source_manifest?.languages) project.languages = kdnaData.source_manifest.languages;
|
|
713
|
+
if (kdnaData.source_manifest?.language) project.default_language = kdnaData.source_manifest.language;
|
|
678
714
|
if (kdnaData.source_manifest?.version) {
|
|
679
715
|
if (!project.release) project.release = {};
|
|
680
716
|
project.release.version = kdnaData.source_manifest.version;
|
|
@@ -683,9 +719,9 @@ function cmdCreate(args) {
|
|
|
683
719
|
if (!project.release) project.release = {};
|
|
684
720
|
project.release.judgment_version = kdnaData.source_manifest.judgment_version;
|
|
685
721
|
}
|
|
686
|
-
if (kdnaData.source_manifest?.description) {
|
|
722
|
+
if (kdnaData.source_manifest?.description || kdnaData.source_manifest?.summary) {
|
|
687
723
|
if (!project.release) project.release = {};
|
|
688
|
-
project.release.description = kdnaData.source_manifest.description;
|
|
724
|
+
project.release.description = kdnaData.source_manifest.description || kdnaData.source_manifest.summary;
|
|
689
725
|
}
|
|
690
726
|
if (project.stages?.judgment_cards) {
|
|
691
727
|
project.stages.judgment_cards.total = kdnaData.cards.length;
|
|
@@ -739,19 +775,36 @@ function importFromKdna(kdnaPath) {
|
|
|
739
775
|
if (stats.size > 50 * 1024 * 1024) {
|
|
740
776
|
fail(`KDNA file too large (${(stats.size / (1024 * 1024)).toFixed(1)} MiB, max 50 MiB): ${absKdna}`);
|
|
741
777
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
fail('@aikdna/kdna-core is required to validate --from-kdna imports.');
|
|
745
|
-
}
|
|
746
|
-
const validation = runtimeCore.validate(absKdna);
|
|
747
|
-
if (!validation.overall_valid) {
|
|
748
|
-
fail(`Not a current .kdna asset: ${(validation.problems || []).join('; ')}`);
|
|
749
|
-
}
|
|
778
|
+
|
|
779
|
+
// ── read ZIP before Core validation so the profile can branch ──
|
|
750
780
|
const zipBuf = fs.readFileSync(absKdna);
|
|
751
781
|
const entries = readZipEntries(zipBuf);
|
|
782
|
+
validateZipContainer(entries);
|
|
752
783
|
if (!entries.has('kdna.json')) fail('Not a valid .kdna asset: missing kdna.json');
|
|
784
|
+
if (!entries.has('payload.kdnab')) fail('Not a valid .kdna asset: missing payload.kdnab. Use --from-folder for legacy JSON source migration.');
|
|
753
785
|
|
|
754
786
|
const manifest = JSON.parse(entries.get('kdna.json').toString());
|
|
787
|
+
const payload = decodePayload(entries.get('payload.kdnab'), manifest);
|
|
788
|
+
const profile = payload?.profile;
|
|
789
|
+
|
|
790
|
+
// ── branch: current profile requires Core validate; legacy uses a frozen contract ──
|
|
791
|
+
let runtimeCore, isLegacy;
|
|
792
|
+
try { runtimeCore = require('@aikdna/kdna-core'); } catch {
|
|
793
|
+
fail('@aikdna/kdna-core is required to validate --from-kdna imports.');
|
|
794
|
+
}
|
|
795
|
+
if (profile === 'kdna.payload.judgment') {
|
|
796
|
+
const validation = runtimeCore.validate(absKdna);
|
|
797
|
+
if (!validation.overall_valid) {
|
|
798
|
+
fail(`Not a current .kdna asset: ${(validation.problems || []).join('; ')}`);
|
|
799
|
+
}
|
|
800
|
+
isLegacy = false;
|
|
801
|
+
} else if (profile === 'judgment-profile-v1') {
|
|
802
|
+
validateLegacyImportContract(manifest, payload);
|
|
803
|
+
isLegacy = true;
|
|
804
|
+
} else {
|
|
805
|
+
fail(`Unsupported payload profile "${profile || '(none)'}". Only kdna.payload.judgment and judgment-profile-v1 are accepted for --from-kdna.`);
|
|
806
|
+
}
|
|
807
|
+
|
|
755
808
|
const lineage = {
|
|
756
809
|
type: 'fork',
|
|
757
810
|
parent_name: packageNameFromManifest(manifest),
|
|
@@ -760,15 +813,16 @@ function importFromKdna(kdnaPath) {
|
|
|
760
813
|
parent_asset_digest: manifest.content_digest || manifest.asset_digest || null,
|
|
761
814
|
};
|
|
762
815
|
|
|
763
|
-
// Current assets carry strict-CBOR `payload.kdnab`. Legacy source folders
|
|
764
|
-
// are migrated explicitly with --from-folder; they are not runtime assets.
|
|
765
816
|
const importedCards = [];
|
|
766
|
-
|
|
767
|
-
fail('Not a current .kdna asset: missing payload.kdnab. Use --from-folder for legacy JSON source migration.');
|
|
768
|
-
}
|
|
817
|
+
let judgmentCore = null;
|
|
769
818
|
try {
|
|
770
|
-
|
|
771
|
-
|
|
819
|
+
if (isLegacy) {
|
|
820
|
+
importedCards.push(...cardsFromLegacyPayload(payload));
|
|
821
|
+
judgmentCore = judgmentCoreFromLegacyPayload(payload);
|
|
822
|
+
} else {
|
|
823
|
+
importedCards.push(...cardsFromPayload(payload));
|
|
824
|
+
judgmentCore = judgmentCoreFromRuntimePayload(payload);
|
|
825
|
+
}
|
|
772
826
|
} catch (e) {
|
|
773
827
|
fail(`Could not import cards from payload.kdnab: ${e.message}`);
|
|
774
828
|
}
|
|
@@ -777,22 +831,239 @@ function importFromKdna(kdnaPath) {
|
|
|
777
831
|
fail(`No current judgment cards could be extracted from ${absKdna}.`);
|
|
778
832
|
}
|
|
779
833
|
|
|
780
|
-
// Bug #15 / #28 follow-up: surface the source's KDNA_Patterns /
|
|
781
|
-
// KDNA_Reasoning / KDNA_Evolution entries so cmdCreate can store
|
|
782
|
-
// them on the project. The next export then forwards them to
|
|
783
|
-
// compileDomain via exportRuntimeAsset, which preserves
|
|
784
|
-
// changelog / version_notes / reasoning_chains / banned_terms
|
|
785
|
-
// through the round-trip.
|
|
786
834
|
return {
|
|
787
835
|
lineage,
|
|
788
836
|
cards: importedCards,
|
|
837
|
+
judgment_core: judgmentCore,
|
|
789
838
|
source_manifest: manifest,
|
|
790
839
|
source_patterns: null,
|
|
791
|
-
source_reasoning:
|
|
792
|
-
|
|
840
|
+
source_reasoning: payload.reasoning && typeof payload.reasoning === 'object'
|
|
841
|
+
? JSON.parse(JSON.stringify(payload.reasoning))
|
|
842
|
+
: null,
|
|
843
|
+
source_evolution: payload.evolution && typeof payload.evolution === 'object'
|
|
844
|
+
? JSON.parse(JSON.stringify(payload.evolution))
|
|
845
|
+
: null,
|
|
846
|
+
source_core_structure: Array.isArray(payload.core?.core_structure)
|
|
847
|
+
? JSON.parse(JSON.stringify(payload.core.core_structure))
|
|
848
|
+
: null,
|
|
793
849
|
};
|
|
794
850
|
}
|
|
795
851
|
|
|
852
|
+
// ── legacy judgment-profile-v1 import support ───────────────────────────
|
|
853
|
+
|
|
854
|
+
const LEGACY_PROFILE = 'judgment-profile-v1';
|
|
855
|
+
|
|
856
|
+
const LEGACY_PATTERN_TYPE_MAP = {
|
|
857
|
+
term: 'term',
|
|
858
|
+
banned_term: 'banned_term',
|
|
859
|
+
misunderstanding: 'misunderstanding',
|
|
860
|
+
self_check: 'self_check',
|
|
861
|
+
scenario: 'scenario',
|
|
862
|
+
case: 'case',
|
|
863
|
+
reasoning: 'reasoning',
|
|
864
|
+
risk: 'risk',
|
|
865
|
+
axiom: 'axiom',
|
|
866
|
+
ontology: 'ontology',
|
|
867
|
+
framework: 'framework',
|
|
868
|
+
stance: 'stance',
|
|
869
|
+
aesthetic: 'aesthetic',
|
|
870
|
+
boundary: 'boundary',
|
|
871
|
+
evolution_stage: 'evolution_stage',
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
// Pattern subtypes that map to the generic 'pattern' card; the original
|
|
875
|
+
// subtype is preserved in fields.legacy_subtype for audit and re-export.
|
|
876
|
+
const LEGACY_PATTERN_SUBTYPES = new Set([
|
|
877
|
+
'failure_pattern', 'design_pattern', 'response_pattern',
|
|
878
|
+
'stopping_pattern', 'completion_pattern', 'decision_pattern',
|
|
879
|
+
'reasoning_pattern', 'action_pattern', 'recovery_pattern',
|
|
880
|
+
'responsibility_pattern',
|
|
881
|
+
]);
|
|
882
|
+
|
|
883
|
+
const MAX_ZIP_ENTRIES = 100;
|
|
884
|
+
const MAX_SINGLE_ENTRY_UNCOMPRESSED = 10 * 1024 * 1024;
|
|
885
|
+
const MAX_TOTAL_UNCOMPRESSED = 50 * 1024 * 1024;
|
|
886
|
+
const REQUIRED_ZIP_ENTRIES = ['kdna.json', 'payload.kdnab', 'checksums.json', 'mimetype'];
|
|
887
|
+
|
|
888
|
+
function validateZipContainer(entries) {
|
|
889
|
+
if (entries.size > MAX_ZIP_ENTRIES) {
|
|
890
|
+
fail(`ZIP container has too many entries (${entries.size}, max ${MAX_ZIP_ENTRIES})`);
|
|
891
|
+
}
|
|
892
|
+
for (const name of REQUIRED_ZIP_ENTRIES) {
|
|
893
|
+
if (!entries.has(name)) fail(`Not a valid .kdna container: missing required entry "${name}"`);
|
|
894
|
+
}
|
|
895
|
+
let totalSize = 0;
|
|
896
|
+
for (const [name, buf] of entries) {
|
|
897
|
+
if (path.isAbsolute(name) || name.includes('..') || name.includes('\\')) {
|
|
898
|
+
fail(`ZIP entry "${name}" has an unsafe path`);
|
|
899
|
+
}
|
|
900
|
+
if (Buffer.isBuffer(buf) && buf.length > MAX_SINGLE_ENTRY_UNCOMPRESSED) {
|
|
901
|
+
fail(`ZIP entry "${name}" is too large (${buf.length} bytes)`);
|
|
902
|
+
}
|
|
903
|
+
totalSize += Buffer.isBuffer(buf) ? buf.length : 0;
|
|
904
|
+
}
|
|
905
|
+
if (totalSize > MAX_TOTAL_UNCOMPRESSED) {
|
|
906
|
+
fail(`ZIP container total uncompressed size exceeds limit (${totalSize} bytes)`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function validateLegacyImportContract(manifest, payload) {
|
|
911
|
+
// Frozen validation for judgment-profile-v1 assets.
|
|
912
|
+
if (!manifest || typeof manifest !== 'object') fail('Legacy asset manifest is missing or invalid');
|
|
913
|
+
if (manifest.kdna_version !== '1.0') fail(`Legacy asset must declare kdna_version "1.0", got "${manifest.kdna_version}"`);
|
|
914
|
+
if (!manifest.asset_id || typeof manifest.asset_id !== 'string') fail('Legacy asset_id is missing');
|
|
915
|
+
if (!manifest.compatibility || manifest.compatibility.profile !== LEGACY_PROFILE) {
|
|
916
|
+
fail(`Legacy asset compatibility.profile must be "${LEGACY_PROFILE}"`);
|
|
917
|
+
}
|
|
918
|
+
if (!manifest.payload || manifest.payload.path !== 'payload.kdnab') {
|
|
919
|
+
fail('Legacy asset payload.path must be "payload.kdnab"');
|
|
920
|
+
}
|
|
921
|
+
if (manifest.payload.encoding !== 'cbor') {
|
|
922
|
+
fail('Legacy asset payload.encoding must be "cbor"');
|
|
923
|
+
}
|
|
924
|
+
if (manifest.payload.encrypted) fail('Legacy asset must not be encrypted');
|
|
925
|
+
if (!payload || typeof payload !== 'object') fail('Legacy asset payload is missing or invalid');
|
|
926
|
+
if (payload.profile !== LEGACY_PROFILE) fail('Legacy payload must declare profile "judgment-profile-v1"');
|
|
927
|
+
if (!payload.core || typeof payload.core !== 'object') fail('Legacy payload must contain a "core" object');
|
|
928
|
+
if (!Array.isArray(payload.core.axioms)) fail('Legacy payload must contain axioms');
|
|
929
|
+
if (!payload.patterns || !Array.isArray(payload.patterns)) fail('Legacy payload must contain patterns');
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function judgmentCoreFromLegacyPayload(payload) {
|
|
933
|
+
const core = payload.core || {};
|
|
934
|
+
const result = {};
|
|
935
|
+
if (typeof core.highest_question === 'string' && core.highest_question) result.highest_question = core.highest_question;
|
|
936
|
+
if (Array.isArray(core.worldview) && core.worldview.length) result.worldview = core.worldview;
|
|
937
|
+
if (Array.isArray(core.value_order) && core.value_order.length) result.value_order = core.value_order;
|
|
938
|
+
if (core.judgment_role && typeof core.judgment_role === 'object' && Object.keys(core.judgment_role).length) result.judgment_role = core.judgment_role;
|
|
939
|
+
return Object.keys(result).length ? result : null;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function legacyStableId(type, fields, prefix) {
|
|
943
|
+
// Deterministic ID for legacy items that lack a declared id.
|
|
944
|
+
// Prefix is the parent scope (e.g. the asset name or domain id).
|
|
945
|
+
const sorted = JSON.stringify(Object.entries(fields).sort());
|
|
946
|
+
const h = require('crypto').createHash('sha256').update(`${type}:${sorted}`).digest('hex');
|
|
947
|
+
return `${prefix}_${h.slice(0, 12)}`;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const _legacyDedupIds = new Set();
|
|
951
|
+
|
|
952
|
+
function _legacyDedupPush(cards, type, fields, id) {
|
|
953
|
+
// Deduplicate by id IF an id is declared. Items without ids are unique
|
|
954
|
+
// (deterministic by content). A second push with the same id merges.
|
|
955
|
+
if (!id) { pushImportedCard(cards, type, fields, null); return; }
|
|
956
|
+
if (_legacyDedupIds.has(id)) { pushImportedCard(cards, type, fields, id); return; }
|
|
957
|
+
_legacyDedupIds.add(id);
|
|
958
|
+
pushImportedCard(cards, type, fields, id);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function cardsFromLegacyPayload(payload) {
|
|
962
|
+
_legacyDedupIds.clear();
|
|
963
|
+
const cards = [];
|
|
964
|
+
const core = payload.core || {};
|
|
965
|
+
|
|
966
|
+
// axioms
|
|
967
|
+
for (const ax of (core.axioms || [])) {
|
|
968
|
+
_legacyDedupPush(cards, 'axiom', importedFields(ax), ax.id || null);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// ontology
|
|
972
|
+
for (const ont of (core.ontology || [])) {
|
|
973
|
+
pushImportedCard(cards, 'ontology', importedFields(ont), ont.id || null);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// frameworks
|
|
977
|
+
for (const fw of (core.frameworks || [])) {
|
|
978
|
+
pushImportedCard(cards, 'framework', importedFields(fw), fw.id || null);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// stances
|
|
982
|
+
for (const st of (core.stances || [])) {
|
|
983
|
+
const fields = typeof st === 'string' ? { statement: st } : importedFields(st);
|
|
984
|
+
pushImportedCard(cards, 'stance', fields, typeof st === 'object' ? st.id || null : null);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// boundaries
|
|
988
|
+
for (const bd of (core.boundaries || [])) {
|
|
989
|
+
pushImportedCard(cards, 'boundary', importedFields(bd), bd.id || null);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// risks
|
|
993
|
+
const legacyRisks = Array.isArray(core.risk_model)
|
|
994
|
+
? core.risk_model
|
|
995
|
+
: (Array.isArray(core.risk_model?.risks) ? core.risk_model.risks : []);
|
|
996
|
+
for (const risk of legacyRisks) {
|
|
997
|
+
pushImportedCard(cards, 'risk', importedFields(risk.fields || risk), risk.id || null);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// patterns with explicit types (including legacy subtypes)
|
|
1001
|
+
const patterns = payload.patterns || [];
|
|
1002
|
+
for (const p of patterns) {
|
|
1003
|
+
if (!p || typeof p !== 'object') continue;
|
|
1004
|
+
const rawType = p.type;
|
|
1005
|
+
if (!rawType) continue; // type-less items handled separately (misunderstandings below)
|
|
1006
|
+
|
|
1007
|
+
if (LEGACY_PATTERN_SUBTYPES.has(rawType)) {
|
|
1008
|
+
pushImportedCard(cards, 'pattern', {
|
|
1009
|
+
...importedFields(p, { keepType: true }),
|
|
1010
|
+
legacy_subtype: rawType,
|
|
1011
|
+
}, p.id || null);
|
|
1012
|
+
} else if (rawType === 'term') {
|
|
1013
|
+
pushImportedCard(cards, 'term', importedFields(p), p.id || null);
|
|
1014
|
+
} else if (rawType === 'banned_term') {
|
|
1015
|
+
pushImportedCard(cards, 'banned_term', importedFields(p), p.id || null);
|
|
1016
|
+
} else if (LEGACY_PATTERN_TYPE_MAP.hasOwnProperty(rawType)) {
|
|
1017
|
+
pushImportedCard(cards, LEGACY_PATTERN_TYPE_MAP[rawType], importedFields(p), p.id || null);
|
|
1018
|
+
} else {
|
|
1019
|
+
fail(`Unknown legacy pattern type "${rawType}" for card "${p.id || '(no id)'}"`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// misunderstandings: appear in patterns[] with no type and in
|
|
1024
|
+
// reasoning.failure_modes[] with slightly different field names.
|
|
1025
|
+
// Import from patterns[] (richer fields). The pushImportedCard
|
|
1026
|
+
// dedup handles the failure_modes view via id matching.
|
|
1027
|
+
for (const p of patterns) {
|
|
1028
|
+
if (!p || typeof p !== 'object') continue;
|
|
1029
|
+
if (p.type) continue; // already handled above
|
|
1030
|
+
if (!p.wrong && !p.correct) continue;
|
|
1031
|
+
pushImportedCard(cards, 'misunderstanding', importedFields(p), p.id || null);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// reasoning chains
|
|
1035
|
+
for (const chain of (payload.reasoning?.reasoning_chains || [])) {
|
|
1036
|
+
const fields = importedFields(chain);
|
|
1037
|
+
pushImportedCard(cards, 'reasoning', fields, chain.id || null);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// self checks: array of plain strings in legacy payload
|
|
1041
|
+
for (const check of (payload.reasoning?.self_check || [])) {
|
|
1042
|
+
if (typeof check === 'string' && check.trim()) {
|
|
1043
|
+
const fields = { question: check.trim() };
|
|
1044
|
+
const id = legacyStableId('self_check', fields, 'legacy_sc');
|
|
1045
|
+
pushImportedCard(cards, 'self_check', fields, id);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// scenarios
|
|
1050
|
+
for (const sc of (payload.scenarios || [])) {
|
|
1051
|
+
pushImportedCard(cards, 'scenario', importedFields(sc), sc.id || null);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// cases
|
|
1055
|
+
for (const cs of (payload.cases || [])) {
|
|
1056
|
+
pushImportedCard(cards, 'case', importedFields(cs), cs.id || null);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// evolution stages
|
|
1060
|
+
for (const stage of (payload.evolution?.stages || [])) {
|
|
1061
|
+
pushImportedCard(cards, 'evolution_stage', importedFields(stage), stage.id || null);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
return cards;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
796
1067
|
/**
|
|
797
1068
|
* Import cards from a legacy source folder into a new Studio project.
|
|
798
1069
|
* Reads KDNA_*.json files and converts entries to draft judgment cards.
|
|
@@ -827,6 +1098,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
827
1098
|
if (manifest.default_language) manifestData.default_language = manifest.default_language;
|
|
828
1099
|
if (manifest.description) manifestData.description = manifest.description;
|
|
829
1100
|
if (manifest.name) manifestData.name = manifest.name;
|
|
1101
|
+
if (manifest.title) manifestData.title = manifest.title;
|
|
830
1102
|
if (manifest.keywords) manifestData.keywords = manifest.keywords;
|
|
831
1103
|
if (manifest.license) manifestData.license = manifest.license;
|
|
832
1104
|
if (manifest.author) manifestData.author = manifest.author;
|
|
@@ -834,6 +1106,8 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
834
1106
|
if (manifest.core_insight) manifestData.core_insight = manifest.core_insight;
|
|
835
1107
|
if (manifest.created_at || manifest.created) manifestData.created = manifest.created_at || manifest.created;
|
|
836
1108
|
if (manifest.updated_at || manifest.updated) manifestData.updated = manifest.updated_at || manifest.updated;
|
|
1109
|
+
// legacy-migration bookkeeping only: preserved in the Studio project for
|
|
1110
|
+
// issuer-scoped governance; the Core 0.20 export manifest never projects it
|
|
837
1111
|
if (manifest.risk_level) manifestData.risk_level = manifest.risk_level;
|
|
838
1112
|
if (manifest.fitness_for_purpose) manifestData.fitness_for_purpose = manifest.fitness_for_purpose;
|
|
839
1113
|
}
|
|
@@ -865,25 +1139,17 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
865
1139
|
|
|
866
1140
|
if (core) {
|
|
867
1141
|
for (const ax of (Array.isArray(core.axioms) ? core.axioms : [])) {
|
|
868
|
-
const fields =
|
|
1142
|
+
const fields = importedFields(ax);
|
|
869
1143
|
for (const key of ['one_sentence', 'full_statement', 'why', 'applies_when', 'does_not_apply_when', 'failure_risk', 'confidence', 'evidence_type']) {
|
|
870
|
-
if (key in ax)
|
|
871
|
-
else audit.missingFields.push(`axiom.${ax.id || '?'}.${key}`);
|
|
1144
|
+
if (!(key in ax)) audit.missingFields.push(`axiom.${ax.id || '?'}.${key}`);
|
|
872
1145
|
}
|
|
873
1146
|
importedCards.push(cardApi.createCard('axiom', fields, ax.id || null));
|
|
874
1147
|
}
|
|
875
1148
|
for (const ont of (Array.isArray(core.ontology) ? core.ontology : [])) {
|
|
876
|
-
importedCards.push(cardApi.createCard('ontology',
|
|
877
|
-
one_sentence: ont.one_sentence || ont.essence || '',
|
|
878
|
-
essence: ont.essence || '', boundary: ont.boundary || '',
|
|
879
|
-
trigger_signal: ont.trigger_signal || '',
|
|
880
|
-
}, ont.id || null));
|
|
1149
|
+
importedCards.push(cardApi.createCard('ontology', importedFields(ont), ont.id || null));
|
|
881
1150
|
}
|
|
882
1151
|
for (const b of (Array.isArray(core.boundaries) ? core.boundaries : [])) {
|
|
883
|
-
importedCards.push(cardApi.createCard('boundary',
|
|
884
|
-
scope: b.scope || '', out_of_scope: b.out_of_scope || '',
|
|
885
|
-
acceptable_exceptions: b.acceptable_exceptions || [],
|
|
886
|
-
}, b.id || null));
|
|
1152
|
+
importedCards.push(cardApi.createCard('boundary', importedFields(b), b.id || null));
|
|
887
1153
|
}
|
|
888
1154
|
// Accept risks as a top-level array (`risks`) or wrapped in `risk_model`
|
|
889
1155
|
// (either as `{risks: [...]}` or as an array itself).
|
|
@@ -894,23 +1160,16 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
894
1160
|
else risksSource = [];
|
|
895
1161
|
}
|
|
896
1162
|
for (const r of risksSource) {
|
|
897
|
-
importedCards.push(cardApi.createCard('risk', r.fields || r, r.id || null));
|
|
1163
|
+
importedCards.push(cardApi.createCard('risk', importedFields(r.fields || r), r.id || null));
|
|
898
1164
|
}
|
|
899
1165
|
// NEW: import stances
|
|
900
1166
|
for (const s of (Array.isArray(core.stances) ? core.stances : [])) {
|
|
901
|
-
const
|
|
902
|
-
importedCards.push(cardApi.createCard('stance',
|
|
903
|
-
statement: text,
|
|
904
|
-
applies_when: s.applies_when || [],
|
|
905
|
-
does_not_apply_when: s.does_not_apply_when || [],
|
|
906
|
-
}, typeof s === 'object' ? s.id || null : null));
|
|
1167
|
+
const fields = typeof s === 'string' ? { statement: s } : importedFields(s);
|
|
1168
|
+
importedCards.push(cardApi.createCard('stance', fields, typeof s === 'object' ? s.id || null : null));
|
|
907
1169
|
}
|
|
908
1170
|
// NEW: import frameworks
|
|
909
1171
|
for (const fw of (Array.isArray(core.frameworks) ? core.frameworks : [])) {
|
|
910
|
-
importedCards.push(cardApi.createCard('framework',
|
|
911
|
-
name: fw.name || '', when_to_use: fw.when_to_use || '',
|
|
912
|
-
steps: fw.steps || [],
|
|
913
|
-
}, fw.id || null));
|
|
1172
|
+
importedCards.push(cardApi.createCard('framework', importedFields(fw), fw.id || null));
|
|
914
1173
|
}
|
|
915
1174
|
}
|
|
916
1175
|
|
|
@@ -918,27 +1177,25 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
918
1177
|
// NEW: import terminology
|
|
919
1178
|
if (patterns.terminology) {
|
|
920
1179
|
for (const t of (Array.isArray(patterns.terminology.standard_terms) ? patterns.terminology.standard_terms : [])) {
|
|
921
|
-
importedCards.push(cardApi.createCard('term',
|
|
922
|
-
term: t.term || '', definition: t.definition || '',
|
|
923
|
-
}, t.id || null));
|
|
1180
|
+
importedCards.push(cardApi.createCard('term', importedFields(t), t.id || null));
|
|
924
1181
|
}
|
|
925
1182
|
for (const bt of (Array.isArray(patterns.terminology.banned_terms) ? patterns.terminology.banned_terms : [])) {
|
|
926
|
-
importedCards.push(cardApi.createCard('banned_term',
|
|
927
|
-
term: bt.term || '', why: bt.why || '', replace_with: bt.replace_with || '',
|
|
928
|
-
}, bt.id || null));
|
|
1183
|
+
importedCards.push(cardApi.createCard('banned_term', importedFields(bt), bt.id || null));
|
|
929
1184
|
}
|
|
930
1185
|
}
|
|
1186
|
+
for (const pattern of (Array.isArray(patterns.patterns) ? patterns.patterns : [])) {
|
|
1187
|
+
const fields = importedFields(pattern, { keepType: true });
|
|
1188
|
+
if (!fields.type) fields.type = 'pattern';
|
|
1189
|
+
importedCards.push(cardApi.createCard('pattern', fields, pattern.id || null));
|
|
1190
|
+
}
|
|
931
1191
|
for (const ms of (Array.isArray(patterns.misunderstandings) ? patterns.misunderstandings : [])) {
|
|
932
|
-
importedCards.push(cardApi.createCard('misunderstanding',
|
|
933
|
-
wrong: ms.wrong || '', correct: ms.correct || '',
|
|
934
|
-
key_distinction: ms.key_distinction || '', why: ms.why || '',
|
|
935
|
-
}, ms.id || null));
|
|
1192
|
+
importedCards.push(cardApi.createCard('misunderstanding', importedFields(ms), ms.id || null));
|
|
936
1193
|
}
|
|
937
1194
|
for (const sc of (Array.isArray(patterns.self_check) ? patterns.self_check : [])) {
|
|
938
|
-
const
|
|
1195
|
+
const fields = typeof sc === 'string' ? { question: sc } : importedFields(sc);
|
|
939
1196
|
importedCards.push(cardApi.createCard(
|
|
940
1197
|
'self_check',
|
|
941
|
-
|
|
1198
|
+
fields,
|
|
942
1199
|
typeof sc === 'object' ? sc.id || null : null,
|
|
943
1200
|
));
|
|
944
1201
|
}
|
|
@@ -951,7 +1208,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
951
1208
|
for (const aesthetic of (Array.isArray(patterns.aesthetics) ? patterns.aesthetics : [])) {
|
|
952
1209
|
importedCards.push(cardApi.createCard(
|
|
953
1210
|
'aesthetic',
|
|
954
|
-
aesthetic.fields || aesthetic,
|
|
1211
|
+
importedFields(aesthetic.fields || aesthetic),
|
|
955
1212
|
aesthetic.id || null,
|
|
956
1213
|
));
|
|
957
1214
|
}
|
|
@@ -960,20 +1217,14 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
960
1217
|
// NEW: import scenarios
|
|
961
1218
|
if (scenarios && Array.isArray(scenarios.scenes)) {
|
|
962
1219
|
for (const scene of scenarios.scenes) {
|
|
963
|
-
importedCards.push(cardApi.createCard('scenario',
|
|
964
|
-
name: scene.name || scene.id || '',
|
|
965
|
-
trigger: scene.trigger || '', action: scene.action || '',
|
|
966
|
-
}, scene.id || null));
|
|
1220
|
+
importedCards.push(cardApi.createCard('scenario', importedFields(scene), scene.id || null));
|
|
967
1221
|
}
|
|
968
1222
|
}
|
|
969
1223
|
|
|
970
1224
|
// NEW: import cases
|
|
971
1225
|
if (cases && Array.isArray(cases.cases)) {
|
|
972
1226
|
for (const c of cases.cases) {
|
|
973
|
-
importedCards.push(cardApi.createCard('case',
|
|
974
|
-
title: c.title || c.id || '',
|
|
975
|
-
scenario: c.scenario || '', expected: c.expected || '',
|
|
976
|
-
}, c.id || null));
|
|
1227
|
+
importedCards.push(cardApi.createCard('case', importedFields(c), c.id || null));
|
|
977
1228
|
}
|
|
978
1229
|
}
|
|
979
1230
|
|
|
@@ -986,26 +1237,14 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
986
1237
|
// identity.
|
|
987
1238
|
if (reasoning && Array.isArray(reasoning.reasoning_chains)) {
|
|
988
1239
|
for (const rc of reasoning.reasoning_chains) {
|
|
989
|
-
importedCards.push(cardApi.createCard('reasoning',
|
|
990
|
-
axiom: rc.axiom || '',
|
|
991
|
-
one_sentence: rc.one_sentence || rc.conclusion || '',
|
|
992
|
-
chain: Array.isArray(rc.chain) ? rc.chain : (rc.logic || []),
|
|
993
|
-
principle: rc.principle || rc.name || '',
|
|
994
|
-
concrete_action: rc.concrete_action || rc.so_what || '',
|
|
995
|
-
so_what: rc.so_what || rc.concrete_action || '',
|
|
996
|
-
conclusion: rc.conclusion || '',
|
|
997
|
-
}, rc.id || null));
|
|
1240
|
+
importedCards.push(cardApi.createCard('reasoning', importedFields(rc), rc.id || null));
|
|
998
1241
|
}
|
|
999
1242
|
}
|
|
1000
1243
|
|
|
1001
1244
|
// NEW: import evolution stages
|
|
1002
1245
|
if (evolution && Array.isArray(evolution.stages)) {
|
|
1003
1246
|
for (const stage of evolution.stages) {
|
|
1004
|
-
pushImportedCard(importedCards, 'evolution_stage',
|
|
1005
|
-
name: stage.name || stage.id || '',
|
|
1006
|
-
level: stage.level != null ? stage.level : '',
|
|
1007
|
-
description: stage.description || '',
|
|
1008
|
-
}, stage.id || null);
|
|
1247
|
+
pushImportedCard(importedCards, 'evolution_stage', importedFields(stage), stage.id || null);
|
|
1009
1248
|
}
|
|
1010
1249
|
}
|
|
1011
1250
|
|
|
@@ -1016,6 +1255,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
1016
1255
|
sourcePath: absSource,
|
|
1017
1256
|
creatorIdentity: creatorIdentity || null,
|
|
1018
1257
|
lineage: { type: 'migrated', parent_name: manifestData.name || null, parent_asset_uid: null, parent_version: manifestData.version || null, parent_asset_digest: null },
|
|
1258
|
+
judgmentCore: judgmentCoreFromLegacyPayload({ core: core || {} }),
|
|
1019
1259
|
});
|
|
1020
1260
|
// Preserve manifest metadata
|
|
1021
1261
|
if (manifestData.asset_uid) project.asset_uid = manifestData.asset_uid;
|
|
@@ -1031,11 +1271,18 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
1031
1271
|
if (!project.release) project.release = {};
|
|
1032
1272
|
project.release.description = manifestData.description;
|
|
1033
1273
|
}
|
|
1274
|
+
if (manifestData.title) project.title = manifestData.title;
|
|
1034
1275
|
if (manifestData.license) project.license = manifestData.license;
|
|
1035
1276
|
if (manifestData.author) project.author = manifestData.author;
|
|
1036
1277
|
if (manifestData.languages) project.languages = manifestData.languages;
|
|
1037
1278
|
if (manifestData.default_language) project.default_language = manifestData.default_language;
|
|
1038
1279
|
if (manifestData.raw) project.source_manifest = publicManifestMetadata(manifestData.raw);
|
|
1280
|
+
if (patterns) project.source_patterns = JSON.parse(JSON.stringify(patterns));
|
|
1281
|
+
if (reasoning) project.source_reasoning = JSON.parse(JSON.stringify(reasoning));
|
|
1282
|
+
if (evolution) project.source_evolution = JSON.parse(JSON.stringify(evolution));
|
|
1283
|
+
if (Array.isArray(core?.core_structure)) {
|
|
1284
|
+
project.source_core_structure = JSON.parse(JSON.stringify(core.core_structure));
|
|
1285
|
+
}
|
|
1039
1286
|
|
|
1040
1287
|
project.cards = importedCards;
|
|
1041
1288
|
if (project.stages?.judgment_cards) {
|
|
@@ -1052,10 +1299,12 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
1052
1299
|
}
|
|
1053
1300
|
|
|
1054
1301
|
/**
|
|
1055
|
-
* Minimal ZIP central directory parser —
|
|
1302
|
+
* Minimal ZIP central directory parser — validates every entry before decompressing.
|
|
1056
1303
|
*/
|
|
1057
1304
|
function readZipEntries(buf) {
|
|
1058
1305
|
const entries = new Map();
|
|
1306
|
+
const metadata = [];
|
|
1307
|
+
|
|
1059
1308
|
// Find end-of-central-directory record
|
|
1060
1309
|
let eocdOffset = buf.length - 22;
|
|
1061
1310
|
while (eocdOffset >= 0) {
|
|
@@ -1067,6 +1316,8 @@ function readZipEntries(buf) {
|
|
|
1067
1316
|
const centralDirOffset = buf.readUInt32LE(eocdOffset + 16);
|
|
1068
1317
|
let offset = centralDirOffset;
|
|
1069
1318
|
|
|
1319
|
+
// Pass 1: collect metadata, validate safety before any decompression
|
|
1320
|
+
let totalUncomp = 0;
|
|
1070
1321
|
while (offset < eocdOffset) {
|
|
1071
1322
|
const sig = buf.readUInt32LE(offset);
|
|
1072
1323
|
if (sig !== 0x02014b50) break;
|
|
@@ -1079,20 +1330,47 @@ function readZipEntries(buf) {
|
|
|
1079
1330
|
const localOffset = buf.readUInt32LE(offset + 42);
|
|
1080
1331
|
const name = buf.toString('utf8', offset + 46, offset + 46 + nameLen);
|
|
1081
1332
|
|
|
1082
|
-
//
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
const dataStart = localOffset + 30 + localNameLen + localExtraLen;
|
|
1086
|
-
|
|
1087
|
-
if (compMethod === 0) {
|
|
1088
|
-
entries.set(name, buf.subarray(dataStart, dataStart + uncompSize));
|
|
1089
|
-
} else if (compMethod === 8) {
|
|
1090
|
-
entries.set(name, zlib.inflateRawSync(buf.subarray(dataStart, dataStart + compSize)));
|
|
1333
|
+
// Security: path must not be absolute, must not escape
|
|
1334
|
+
if (!name || name.startsWith('/') || name.includes('..') || name.includes('\\')) {
|
|
1335
|
+
throw new Error(`ZIP entry "${name}" has an unsafe path`);
|
|
1091
1336
|
}
|
|
1092
|
-
|
|
1337
|
+
// Duplicate detection
|
|
1338
|
+
if (entries.has(name) || metadata.some(m => m.name === name)) {
|
|
1339
|
+
throw new Error(`Duplicate ZIP entry: "${name}"`);
|
|
1340
|
+
}
|
|
1341
|
+
if (compMethod !== 0 && compMethod !== 8) {
|
|
1342
|
+
throw new Error(`ZIP entry "${name}" uses unsupported compression method ${compMethod}`);
|
|
1343
|
+
}
|
|
1344
|
+
totalUncomp += uncompSize;
|
|
1345
|
+
if (totalUncomp > MAX_TOTAL_UNCOMPRESSED) {
|
|
1346
|
+
throw new Error(`ZIP total uncompressed size ${totalUncomp} exceeds limit`);
|
|
1347
|
+
}
|
|
1348
|
+
if (uncompSize > MAX_SINGLE_ENTRY_UNCOMPRESSED) {
|
|
1349
|
+
throw new Error(`ZIP entry "${name}" declared size ${uncompSize} exceeds limit`);
|
|
1350
|
+
}
|
|
1351
|
+
const dataStart = localOffset + 30 + buf.readUInt16LE(localOffset + 26) + buf.readUInt16LE(localOffset + 28);
|
|
1352
|
+
metadata.push({ name, compMethod, compSize, uncompSize, dataStart });
|
|
1093
1353
|
offset += 46 + nameLen + extraLen + commentLen;
|
|
1094
1354
|
}
|
|
1095
1355
|
|
|
1356
|
+
if (metadata.length > MAX_ZIP_ENTRIES) {
|
|
1357
|
+
throw new Error(`ZIP container has too many entries (${metadata.length}, max ${MAX_ZIP_ENTRIES})`);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// Pass 2: decompress into entries
|
|
1361
|
+
for (const m of metadata) {
|
|
1362
|
+
if (m.compMethod === 0) {
|
|
1363
|
+
entries.set(m.name, buf.subarray(m.dataStart, m.dataStart + m.uncompSize));
|
|
1364
|
+
} else {
|
|
1365
|
+
const raw = buf.subarray(m.dataStart, m.dataStart + m.compSize);
|
|
1366
|
+
const inflated = zlib.inflateRawSync(raw, { maxOutputLength: m.uncompSize });
|
|
1367
|
+
if (inflated.length !== m.uncompSize) {
|
|
1368
|
+
throw new Error(`ZIP entry "${m.name}" decompressed length ${inflated.length} != declared ${m.uncompSize}`);
|
|
1369
|
+
}
|
|
1370
|
+
entries.set(m.name, inflated);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1096
1374
|
return entries;
|
|
1097
1375
|
}
|
|
1098
1376
|
|
|
@@ -1936,7 +2214,7 @@ function cmdStudioInstall(args) {
|
|
|
1936
2214
|
function cmdStudioUpdate(args) {
|
|
1937
2215
|
const target = args[0];
|
|
1938
2216
|
if (!target) fail('Usage: kdna-studio update <@scope/name>');
|
|
1939
|
-
// kdna update does not exist in kdna-cli
|
|
2217
|
+
// kdna update does not exist in kdna-cli 0.28.x. The correct
|
|
1940
2218
|
// path is to re-install via npm or use kdna install to pull
|
|
1941
2219
|
// the latest published version of the asset.
|
|
1942
2220
|
console.log(`To update ${target}, run:`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-studio-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Official KDNA Studio command-line authoring entry for creating, reviewing, compiling, and exporting .kdna judgment assets through the KDNA toolchain.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -14,11 +14,18 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"lint": "node
|
|
17
|
+
"lint": "node scripts/run-lint.js",
|
|
18
|
+
"pretest": "node scripts/acquire-trusted-npm-release.js",
|
|
18
19
|
"test": "node --test tests/*.test.js",
|
|
19
|
-
"test:all": "
|
|
20
|
+
"test:all": "node scripts/run-test-all.js",
|
|
21
|
+
"test:runtime": "node scripts/run-product-tests.js",
|
|
22
|
+
"test:candidate-chain": "node scripts/verify-runtime-candidate-chain.js",
|
|
23
|
+
"verify:candidate-sources": "node scripts/verify-runtime-candidate-sources.js",
|
|
24
|
+
"check:protocol-names": "node scripts/check-current-protocol-names.js",
|
|
20
25
|
"release:check": "node scripts/release-check.js",
|
|
21
|
-
"
|
|
26
|
+
"release:generate-evidence": "node scripts/generate-release-evidence.js",
|
|
27
|
+
"release:publish-verified": "node scripts/publish-verified-artifact.js",
|
|
28
|
+
"prepublishOnly": "node scripts/run-test-all.js && node scripts/verify-runtime-candidate-chain.js && node scripts/release-check.js"
|
|
22
29
|
},
|
|
23
30
|
"keywords": [
|
|
24
31
|
"kdna",
|
|
@@ -41,13 +48,12 @@
|
|
|
41
48
|
"node": ">=18"
|
|
42
49
|
},
|
|
43
50
|
"dependencies": {
|
|
44
|
-
"@aikdna/kdna-core": "0.
|
|
45
|
-
"@aikdna/kdna-studio-core": "
|
|
51
|
+
"@aikdna/kdna-core": "0.20.0",
|
|
52
|
+
"@aikdna/kdna-studio-core": "2.0.2",
|
|
46
53
|
"ajv": "^8.20.0",
|
|
47
54
|
"ajv-formats": "^3.0.1",
|
|
48
55
|
"cbor-x": "^1.6.4"
|
|
49
56
|
},
|
|
50
|
-
"main": "index.js",
|
|
51
57
|
"directories": {
|
|
52
58
|
"test": "tests"
|
|
53
59
|
},
|