@aikdna/kdna-studio-cli 0.5.3 → 0.5.4
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 +28 -5
- package/bin/kdna-studio.js +27 -128
- package/package.json +1 -1
- package/tests/cli.test.js +69 -13
package/README.md
CHANGED
|
@@ -45,12 +45,14 @@ kdna-studio candidate accept my_domain <candidate-id>
|
|
|
45
45
|
kdna-studio candidate promote my_domain
|
|
46
46
|
kdna-studio card add my_domain axiom \
|
|
47
47
|
--field one_sentence="Prefer specific evidence over broad claims" \
|
|
48
|
+
--field full_statement="When reviewing content, prefer specific evidence over broad claims because unsupported generalizations make the judgment impossible to verify or improve." \
|
|
49
|
+
--field why="Broad claims hide the actual reason for a judgment, so reviewers cannot tell whether the conclusion is evidence based, reusable, or merely plausible sounding." \
|
|
48
50
|
--field applies_when='["reviewing content"]' \
|
|
49
51
|
--field does_not_apply_when='["pure formatting"]' \
|
|
50
52
|
--field failure_risk="generic advice"
|
|
51
|
-
kdna-studio card approve my_domain
|
|
53
|
+
kdna-studio card approve my_domain --all --by expert --statement "I confirm this judgment."
|
|
52
54
|
kdna-studio lock my_domain
|
|
53
|
-
kdna-studio export my_domain --out dist/my_domain.kdna
|
|
55
|
+
kdna-studio export my_domain --format v1 --out dist/my_domain.kdna
|
|
54
56
|
```
|
|
55
57
|
|
|
56
58
|
Candidate promotion is scope-gated: only candidates with `status == accepted` and `scope_fit == true` are promoted to cards by default. Use `kdna-studio candidate override <project> <candidate-id>` only when a human intentionally overrides the scope gate.
|
|
@@ -58,14 +60,35 @@ Candidate promotion is scope-gated: only candidates with `status == accepted` an
|
|
|
58
60
|
After export, use the runtime CLI:
|
|
59
61
|
|
|
60
62
|
```bash
|
|
61
|
-
kdna validate dist/my_domain.kdna
|
|
63
|
+
kdna validate dist/my_domain.kdna --runtime
|
|
64
|
+
kdna plan-load dist/my_domain.kdna --json
|
|
62
65
|
kdna load dist/my_domain.kdna --profile=compact --as=prompt
|
|
63
66
|
```
|
|
64
67
|
|
|
65
68
|
`kdna-studio` is the CLI entry for confirmed KDNA authoring. `kdna` is the
|
|
66
69
|
runtime control plane for inspecting, validating, packing, unpacking, and
|
|
67
|
-
loading existing `.kdna` assets.
|
|
68
|
-
|
|
70
|
+
loading existing `.kdna` assets.
|
|
71
|
+
|
|
72
|
+
## Runtime Export Contract
|
|
73
|
+
|
|
74
|
+
`kdna-studio export --format v1` is the canonical runtime export path. It uses
|
|
75
|
+
`@aikdna/kdna-studio-core` to compile the Studio project into a KDNA Core v1
|
|
76
|
+
runtime asset and then packs it with `@aikdna/kdna-core`.
|
|
77
|
+
|
|
78
|
+
A v1 runtime export contains only these top-level entries:
|
|
79
|
+
|
|
80
|
+
- `mimetype`
|
|
81
|
+
- `kdna.json`
|
|
82
|
+
- `payload.kdnab`
|
|
83
|
+
- `checksums.json`
|
|
84
|
+
|
|
85
|
+
Authoring/source entries such as `KDNA_Core.json`, `KDNA_Patterns.json`, and
|
|
86
|
+
`source_cards` are not runtime distribution entries. They may exist in Studio
|
|
87
|
+
compile output or legacy imports, but they must not be emitted by the runtime
|
|
88
|
+
export path.
|
|
89
|
+
|
|
90
|
+
Signature, encryption, registry publishing, paid distribution, and private
|
|
91
|
+
assets are future/gated phases, not the current Core v1 baseline.
|
|
69
92
|
|
|
70
93
|
## Identity
|
|
71
94
|
|
package/bin/kdna-studio.js
CHANGED
|
@@ -6,6 +6,16 @@ const crypto = require('crypto');
|
|
|
6
6
|
const zlib = require('zlib');
|
|
7
7
|
const cbor = require('cbor-x');
|
|
8
8
|
|
|
9
|
+
function loadStudioCore() {
|
|
10
|
+
const publishedCore = require('@aikdna/kdna-studio-core');
|
|
11
|
+
if (publishedCore.exportRuntime) return publishedCore;
|
|
12
|
+
try {
|
|
13
|
+
return require('../../kdna-studio-core/src');
|
|
14
|
+
} catch (_) {
|
|
15
|
+
return publishedCore;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
const {
|
|
10
20
|
project: projectApi,
|
|
11
21
|
cards: cardApi,
|
|
@@ -14,8 +24,8 @@ const {
|
|
|
14
24
|
quality,
|
|
15
25
|
creator: creatorApi,
|
|
16
26
|
distillation: distillationApi,
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
exportRuntime,
|
|
28
|
+
} = loadStudioCore();
|
|
19
29
|
|
|
20
30
|
const llm = require('../src/llm');
|
|
21
31
|
const ai = require('../src/ai');
|
|
@@ -152,12 +162,6 @@ function parseFields(args) {
|
|
|
152
162
|
return fields;
|
|
153
163
|
}
|
|
154
164
|
|
|
155
|
-
function arrayValue(value) {
|
|
156
|
-
if (Array.isArray(value)) return value;
|
|
157
|
-
if (value === undefined || value === null || value === '') return [];
|
|
158
|
-
return [value];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
165
|
function semverValue(value, fallback = '1.0.0') {
|
|
162
166
|
const raw = String(value || '').trim();
|
|
163
167
|
if (/^[0-9]+\.[0-9]+\.[0-9]+([+-].+)?$/.test(raw)) return raw;
|
|
@@ -208,10 +212,6 @@ function publicManifestMetadata(raw = {}) {
|
|
|
208
212
|
return copy;
|
|
209
213
|
}
|
|
210
214
|
|
|
211
|
-
function cardPayload(card) {
|
|
212
|
-
return { id: card.id, ...(card.fields || {}) };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
215
|
function cleanFields(fields = {}) {
|
|
216
216
|
const cleaned = {};
|
|
217
217
|
for (const [key, value] of Object.entries(fields || {})) {
|
|
@@ -381,116 +381,6 @@ function cardsFromPayload(payload) {
|
|
|
381
381
|
return [];
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
-
function buildV1Payload(project) {
|
|
385
|
-
const locked = (project.cards || []).filter((c) => c.locked);
|
|
386
|
-
const byType = (type) => locked.filter((c) => c.type === type);
|
|
387
|
-
const source = project.source_manifest || {};
|
|
388
|
-
|
|
389
|
-
const axioms = byType('axiom').map((c) => ({
|
|
390
|
-
id: c.id,
|
|
391
|
-
one_sentence: c.fields?.one_sentence || '',
|
|
392
|
-
full_statement: c.fields?.full_statement || '',
|
|
393
|
-
why: c.fields?.why || '',
|
|
394
|
-
applies_when: arrayValue(c.fields?.applies_when),
|
|
395
|
-
does_not_apply_when: arrayValue(c.fields?.does_not_apply_when),
|
|
396
|
-
failure_risk: c.fields?.failure_risk || '',
|
|
397
|
-
status: c.status || null,
|
|
398
|
-
human_lock: c.human_lock || null,
|
|
399
|
-
}));
|
|
400
|
-
|
|
401
|
-
const boundaries = [
|
|
402
|
-
...byType('boundary').map((c) => ({ type: 'explicit_boundary', ...cardPayload(c) })),
|
|
403
|
-
...byType('axiom').map((c) => ({
|
|
404
|
-
id: `${c.id}_applicability`,
|
|
405
|
-
type: 'axiom_applicability',
|
|
406
|
-
axiom_id: c.id,
|
|
407
|
-
one_sentence: c.fields?.one_sentence || '',
|
|
408
|
-
applies_when: arrayValue(c.fields?.applies_when),
|
|
409
|
-
does_not_apply_when: arrayValue(c.fields?.does_not_apply_when),
|
|
410
|
-
failure_risk: c.fields?.failure_risk || '',
|
|
411
|
-
})),
|
|
412
|
-
...byType('ontology')
|
|
413
|
-
.filter((c) => c.fields?.boundary || c.fields?.applies_when || c.fields?.does_not_apply_when)
|
|
414
|
-
.map((c) => ({
|
|
415
|
-
id: `${c.id}_boundary`,
|
|
416
|
-
type: 'ontology_boundary',
|
|
417
|
-
concept_id: c.id,
|
|
418
|
-
one_sentence: c.fields?.one_sentence || '',
|
|
419
|
-
boundary: c.fields?.boundary || '',
|
|
420
|
-
applies_when: arrayValue(c.fields?.applies_when),
|
|
421
|
-
does_not_apply_when: arrayValue(c.fields?.does_not_apply_when),
|
|
422
|
-
trigger_signal: c.fields?.trigger_signal || '',
|
|
423
|
-
})),
|
|
424
|
-
...byType('stance')
|
|
425
|
-
.filter((c) => c.fields?.applies_when || c.fields?.does_not_apply_when)
|
|
426
|
-
.map((c) => ({
|
|
427
|
-
id: `${c.id}_boundary`,
|
|
428
|
-
type: 'stance_boundary',
|
|
429
|
-
stance_id: c.id,
|
|
430
|
-
stance: c.fields?.stance || c.fields?.statement || c.fields?.one_sentence || '',
|
|
431
|
-
applies_when: arrayValue(c.fields?.applies_when),
|
|
432
|
-
does_not_apply_when: arrayValue(c.fields?.does_not_apply_when),
|
|
433
|
-
})),
|
|
434
|
-
];
|
|
435
|
-
|
|
436
|
-
const patterns = [
|
|
437
|
-
...byType('ontology').map((c) => ({ type: 'ontology', ...cardPayload(c) })),
|
|
438
|
-
...byType('misunderstanding').map((c) => ({ type: 'misunderstanding', ...cardPayload(c) })),
|
|
439
|
-
...byType('term').map((c) => ({ type: 'term', ...cardPayload(c) })),
|
|
440
|
-
...byType('banned_term').map((c) => ({ type: 'banned_term', ...cardPayload(c) })),
|
|
441
|
-
...byType('framework').map((c) => ({ type: 'framework', ...cardPayload(c) })),
|
|
442
|
-
...byType('stance').map((c) => ({ type: 'stance', ...cardPayload(c) })),
|
|
443
|
-
];
|
|
444
|
-
|
|
445
|
-
const failureModes = byType('misunderstanding').map((c) => ({
|
|
446
|
-
id: c.id,
|
|
447
|
-
mode: c.fields?.wrong || c.fields?.key_distinction || c.id,
|
|
448
|
-
correct: c.fields?.correct || '',
|
|
449
|
-
key_distinction: c.fields?.key_distinction || '',
|
|
450
|
-
why: c.fields?.why || '',
|
|
451
|
-
}));
|
|
452
|
-
|
|
453
|
-
return {
|
|
454
|
-
profile: 'judgment-profile-v1',
|
|
455
|
-
core: {
|
|
456
|
-
highest_question: source.core_insight || project.release?.description || '',
|
|
457
|
-
axioms,
|
|
458
|
-
boundaries,
|
|
459
|
-
risk_model: {
|
|
460
|
-
risks: byType('risk').map((c) => cardPayload(c)),
|
|
461
|
-
source_risk_level: source.risk_level || null,
|
|
462
|
-
fitness_for_purpose: source.fitness_for_purpose || null,
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
patterns,
|
|
466
|
-
scenarios: byType('scenario').map((c) => cardPayload(c)),
|
|
467
|
-
cases: byType('case').map((c) => cardPayload(c)),
|
|
468
|
-
reasoning: {
|
|
469
|
-
self_checks: byType('self_check').map((c) => c.fields?.question || c.fields?.one_sentence || c.id),
|
|
470
|
-
failure_modes: failureModes,
|
|
471
|
-
reasoning_chains: byType('reasoning').map((c) => cardPayload(c)),
|
|
472
|
-
},
|
|
473
|
-
evolution: {
|
|
474
|
-
stages: byType('evolution_stage').map((c) => cardPayload(c)),
|
|
475
|
-
changelog: locked.map((c) => ({
|
|
476
|
-
id: `locked_${c.id}`,
|
|
477
|
-
card_id: c.id,
|
|
478
|
-
type: c.type,
|
|
479
|
-
event: 'locked',
|
|
480
|
-
at: c.human_lock?.at || null,
|
|
481
|
-
})),
|
|
482
|
-
version_notes: source.judgment_version ? [`Migrated from source judgment_version ${source.judgment_version}`] : [],
|
|
483
|
-
},
|
|
484
|
-
source_cards: locked.map((c) => ({
|
|
485
|
-
id: c.id,
|
|
486
|
-
type: c.type,
|
|
487
|
-
status: c.status,
|
|
488
|
-
fields: c.fields || {},
|
|
489
|
-
human_lock: c.human_lock || null,
|
|
490
|
-
})),
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
384
|
function buildV1Manifest(project, name) {
|
|
495
385
|
const source = project.source_manifest || {};
|
|
496
386
|
const assetId = assetIdFromName(name || source.name || project.name);
|
|
@@ -1044,10 +934,10 @@ function cmdCard(args) {
|
|
|
1044
934
|
}
|
|
1045
935
|
if (sub === 'approve') {
|
|
1046
936
|
const projectInput = args[1];
|
|
1047
|
-
const cardId = args[2];
|
|
1048
937
|
const approveAll = args.includes('--all');
|
|
938
|
+
const cardId = approveAll ? null : args[2];
|
|
1049
939
|
if (!projectInput || (!cardId && !approveAll)) {
|
|
1050
|
-
fail('Usage: kdna-studio card approve <project> <card-id> --by <id> --statement <text> [--sign]');
|
|
940
|
+
fail('Usage: kdna-studio card approve <project> <card-id|--all> --by <id> --statement <text> [--sign]');
|
|
1051
941
|
}
|
|
1052
942
|
const by = option(args, '--by');
|
|
1053
943
|
const statement = option(args, '--statement');
|
|
@@ -1255,6 +1145,9 @@ function exportProjectV1(project, name, outPath) {
|
|
|
1255
1145
|
try { core = require('@aikdna/kdna-core'); } catch (e) {
|
|
1256
1146
|
fail('@aikdna/kdna-core@0.11.0+ is required for v1 export.', 2);
|
|
1257
1147
|
}
|
|
1148
|
+
if (!exportRuntime || typeof exportRuntime.exportRuntimeAsset !== 'function') {
|
|
1149
|
+
fail('@aikdna/kdna-studio-core with exportRuntime.exportRuntimeAsset is required for v1 export.', 2);
|
|
1150
|
+
}
|
|
1258
1151
|
const gate = projectApi.checkHumanLockGate(project);
|
|
1259
1152
|
if (gate.blocked) {
|
|
1260
1153
|
const reasons = gate.issues.map((i) => `${i.cardId}: ${i.reason}`).join('\n - ');
|
|
@@ -1263,10 +1156,16 @@ function exportProjectV1(project, name, outPath) {
|
|
|
1263
1156
|
const lockedCards = (project.cards || []).filter(c => c.locked);
|
|
1264
1157
|
const v1Dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-v1-'));
|
|
1265
1158
|
try {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1159
|
+
const manifestDefaults = buildV1Manifest(project, name);
|
|
1160
|
+
const runtimeAsset = exportRuntime.exportRuntimeAsset(project, {
|
|
1161
|
+
asset_id: manifestDefaults.asset_id,
|
|
1162
|
+
asset_uid: manifestDefaults.asset_uid,
|
|
1163
|
+
title: manifestDefaults.title,
|
|
1164
|
+
created_at: manifestDefaults.created_at,
|
|
1165
|
+
updated_at: manifestDefaults.updated_at,
|
|
1166
|
+
access: project.release?.access || project.source_manifest?.access || 'public',
|
|
1167
|
+
});
|
|
1168
|
+
writeFiles(v1Dir, runtimeAsset.files);
|
|
1270
1169
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
1271
1170
|
core.pack(v1Dir, outPath);
|
|
1272
1171
|
const vr = core.validate(outPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-studio-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Official KDNA Studio command-line authoring entry for creating, locking, compiling, and exporting .kdna judgment assets through the KDNA toolchain.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
package/tests/cli.test.js
CHANGED
|
@@ -27,6 +27,15 @@ function tmpDir() {
|
|
|
27
27
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-studio-cli-'));
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function assertCanonicalRuntimeContainer(outFile) {
|
|
31
|
+
const layout = kdnaCore.readV1Layout(outFile);
|
|
32
|
+
const names = (layout.entries || []).map((entry) => entry.name).sort();
|
|
33
|
+
assert.deepEqual(names, ['checksums.json', 'kdna.json', 'mimetype', 'payload.kdnab']);
|
|
34
|
+
assert.equal(names.includes('KDNA_Core.json'), false);
|
|
35
|
+
assert.equal(names.includes('KDNA_Patterns.json'), false);
|
|
36
|
+
return layout;
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
function createLockedProject(t) {
|
|
31
40
|
const tmp = tmpDir();
|
|
32
41
|
t.after(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
|
@@ -89,6 +98,53 @@ test('rejects unknown commands with input error', () => {
|
|
|
89
98
|
assert.match(result.stderr, /Unknown command/);
|
|
90
99
|
});
|
|
91
100
|
|
|
101
|
+
test('card approve --all locks every unlocked card', (t) => {
|
|
102
|
+
const tmp = tmpDir();
|
|
103
|
+
t.after(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
|
104
|
+
const projectDir = path.join(tmp, 'project');
|
|
105
|
+
|
|
106
|
+
let result = run(['create', projectDir, '--name', '@test/approve-all'], { tmp });
|
|
107
|
+
assert.equal(result.status, 0, result.stderr);
|
|
108
|
+
|
|
109
|
+
result = run([
|
|
110
|
+
'card',
|
|
111
|
+
'add',
|
|
112
|
+
projectDir,
|
|
113
|
+
'axiom',
|
|
114
|
+
'--field',
|
|
115
|
+
'one_sentence=Prefer specific evidence over broad claims',
|
|
116
|
+
'--field',
|
|
117
|
+
'full_statement=When evaluating a claim the agent must weigh the specificity of the supporting evidence. A narrow claim backed by concrete data should be preferred over a broad claim supported only by general principles.',
|
|
118
|
+
'--field',
|
|
119
|
+
'why=Without this axiom the agent may accept broad plausible-sounding claims as equally credible as narrow evidence-backed ones leading to false balance.',
|
|
120
|
+
'--field',
|
|
121
|
+
'applies_when=["reviewing content"]',
|
|
122
|
+
'--field',
|
|
123
|
+
'does_not_apply_when=["pure formatting"]',
|
|
124
|
+
'--field',
|
|
125
|
+
'failure_risk=generic advice',
|
|
126
|
+
], { tmp });
|
|
127
|
+
assert.equal(result.status, 0, result.stderr);
|
|
128
|
+
|
|
129
|
+
result = run([
|
|
130
|
+
'card',
|
|
131
|
+
'approve',
|
|
132
|
+
projectDir,
|
|
133
|
+
'--all',
|
|
134
|
+
'--by',
|
|
135
|
+
'expert',
|
|
136
|
+
'--statement',
|
|
137
|
+
'I confirm every unlocked card in this project.',
|
|
138
|
+
], { tmp });
|
|
139
|
+
assert.equal(result.status, 0, result.stderr);
|
|
140
|
+
assert.match(result.stdout, /Approved and Human Locked: 1 cards/);
|
|
141
|
+
|
|
142
|
+
const project = JSON.parse(fs.readFileSync(path.join(projectDir, 'studio.project.json'), 'utf8'));
|
|
143
|
+
assert.equal(project.cards.length, 1);
|
|
144
|
+
assert.equal(project.cards[0].locked, true);
|
|
145
|
+
assert.ok(project.cards[0].human_lock);
|
|
146
|
+
});
|
|
147
|
+
|
|
92
148
|
test('creates a Studio project and rejects accidental overwrite', (t) => {
|
|
93
149
|
const tmp = tmpDir();
|
|
94
150
|
t.after(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
|
@@ -309,7 +365,7 @@ test('create --from-folder imports legacy JSON source, outputs audit', (t) => {
|
|
|
309
365
|
assert.equal(project.lineage.type, 'migrated');
|
|
310
366
|
});
|
|
311
367
|
|
|
312
|
-
test('migrate --format v1
|
|
368
|
+
test('migrate --format v1 exports canonical runtime payload without source entries', (t) => {
|
|
313
369
|
assert.ok(kdnaCore, '@aikdna/kdna-core is required for v1 export verification');
|
|
314
370
|
const tmp = tmpDir();
|
|
315
371
|
t.after(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
|
@@ -374,25 +430,22 @@ test('migrate --format v1 maps scoped name, writes checksums, and preserves sour
|
|
|
374
430
|
const inspected = kdnaCore.inspect(outFile);
|
|
375
431
|
assert.equal(inspected.asset_id, 'kdna:test:legacy-import');
|
|
376
432
|
assert.equal(inspected.checksums_present, true);
|
|
433
|
+
const layout = assertCanonicalRuntimeContainer(outFile);
|
|
434
|
+
assert.equal(layout.manifest.lineage.type, 'adaptation');
|
|
435
|
+
assert.equal(layout.manifest.lineage.source_lineage_type, 'migrated');
|
|
377
436
|
|
|
378
437
|
const full = kdnaCore.loadV1(outFile, { profile: 'full', as: 'json' }).content;
|
|
379
438
|
assert.equal(full.manifest.creator.name, 'Test Author');
|
|
380
439
|
assert.equal(full.manifest.version, '0.7.3');
|
|
381
440
|
assert.equal(full.manifest.judgment_version, '2026.05.0');
|
|
441
|
+
assert.equal(full.payload.profile, 'judgment-profile-v1');
|
|
382
442
|
assert.equal(full.payload.core.axioms.length, 1);
|
|
383
|
-
assert.equal(full.payload.core.boundaries.length, 3);
|
|
384
|
-
assert.ok(full.payload.core.boundaries.some((b) => b.type === 'axiom_applicability'));
|
|
385
|
-
assert.ok(full.payload.core.boundaries.some((b) => b.type === 'ontology_boundary'));
|
|
386
|
-
assert.ok(full.payload.core.boundaries.some((b) => b.type === 'stance_boundary'));
|
|
387
|
-
assert.equal(full.payload.patterns.filter((p) => p.type === 'term').length, 1);
|
|
388
|
-
assert.equal(full.payload.patterns.filter((p) => p.type === 'banned_term').length, 1);
|
|
389
443
|
assert.equal(full.payload.scenarios.length, 1);
|
|
390
444
|
assert.equal(full.payload.cases.length, 1);
|
|
391
445
|
assert.equal(full.payload.reasoning.self_checks.length, 1);
|
|
392
446
|
assert.equal(full.payload.reasoning.failure_modes.length, 1);
|
|
393
|
-
assert.
|
|
394
|
-
assert.equal(full.payload
|
|
395
|
-
assert.equal(full.payload.source_cards.length, 12);
|
|
447
|
+
assert.ok(full.payload.evolution.stages.length >= 1);
|
|
448
|
+
assert.equal(Object.hasOwn(full.payload, 'source_cards'), false);
|
|
396
449
|
assert.equal(JSON.stringify(full).includes('quality_badge'), false);
|
|
397
450
|
assert.equal(JSON.stringify(full).includes('registry'), false);
|
|
398
451
|
});
|
|
@@ -408,10 +461,12 @@ test('export --format v1 exports a Studio project as a valid non-empty v1 asset'
|
|
|
408
461
|
|
|
409
462
|
const validation = kdnaCore.validate(outFile);
|
|
410
463
|
assert.equal(validation.overall_valid, true, JSON.stringify(validation.problems));
|
|
464
|
+
assertCanonicalRuntimeContainer(outFile);
|
|
411
465
|
const loaded = kdnaCore.loadV1(outFile, { profile: 'full', as: 'json' }).content;
|
|
412
466
|
assert.equal(loaded.payload.core.axioms.length, 1);
|
|
413
|
-
assert.equal(loaded.payload
|
|
414
|
-
assert.
|
|
467
|
+
assert.equal(Object.hasOwn(loaded.payload, 'source_cards'), false);
|
|
468
|
+
assert.equal(loaded.manifest.payload.path, 'payload.kdnab');
|
|
469
|
+
assert.equal(loaded.manifest.payload.encrypted, false);
|
|
415
470
|
});
|
|
416
471
|
|
|
417
472
|
test('migrate --format v1 accepts an existing Studio project without dropping cards', (t) => {
|
|
@@ -431,9 +486,10 @@ test('migrate --format v1 accepts an existing Studio project without dropping ca
|
|
|
431
486
|
|
|
432
487
|
const validation = kdnaCore.validate(outFile);
|
|
433
488
|
assert.equal(validation.overall_valid, true, JSON.stringify(validation.problems));
|
|
489
|
+
assertCanonicalRuntimeContainer(outFile);
|
|
434
490
|
const loaded = kdnaCore.loadV1(outFile, { profile: 'full', as: 'json' }).content;
|
|
435
491
|
assert.equal(loaded.payload.core.axioms.length, 1);
|
|
436
|
-
assert.equal(loaded.payload
|
|
492
|
+
assert.equal(Object.hasOwn(loaded.payload, 'source_cards'), false);
|
|
437
493
|
});
|
|
438
494
|
|
|
439
495
|
// ── Create from KDNA ────────────────────────────────────────────────
|