@aikdna/kdna-studio-core 1.5.2 → 1.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 CHANGED
@@ -13,6 +13,22 @@ Studio-compatible authoring pipeline that performs human confirmation,
13
13
  validation, canonicalization, identity generation, digest computation, signing,
14
14
  optional encryption, and provenance recording.
15
15
 
16
+ Studio Core distinguishes authoring compile output from runtime distribution
17
+ output. Authoring compile output may include source entries such as
18
+ `KDNA_Core.json` and `KDNA_Patterns.json` for audit and review. Runtime export
19
+ must produce the canonical KDNA Core v1 distribution shape:
20
+
21
+ ```text
22
+ mimetype
23
+ kdna.json
24
+ payload.kdnab
25
+ checksums.json
26
+ ```
27
+
28
+ Runtime export must validate with `@aikdna/kdna-core` and must plan through the
29
+ LoadPlan contract in `aikdna/kdna`. Studio products must not create app-private
30
+ `.kdna` shapes that Chat or CLI cannot inspect, validate, or plan-load.
31
+
16
32
  **Hard boundary:** Optional encryption, when supported, MUST be represented as
17
33
  protected entries inside the `.kdna` container (RFC-0008). App-private encrypted
18
34
  envelopes or transfer wrappers that cannot be opened by KDNA Core are NOT
@@ -41,6 +57,8 @@ conforming KDNA runtime assets.
41
57
  - **Feynman Restatement** — verify understanding, not just agreement
42
58
  - **Quality Gates** — readiness check: draft → structurally_ready → judgment_ready → publish_ready
43
59
  - **Compiler** — locked cards → `KDNA_Core.json` + `KDNA_Patterns.json`
60
+ - **Runtime Export** — compiled judgment → canonical `mimetype` +
61
+ `kdna.json` + `payload.kdnab` + `checksums.json`
44
62
  - **Test Lab** — A/B comparison (No KDNA vs Best Prompt vs KDNA)
45
63
  - **Provenance** — content fingerprinting, build tracking, audit trail
46
64
 
@@ -110,6 +128,8 @@ kdna publish dist/my_domain.kdna
110
128
  const {
111
129
  project: projectApi,
112
130
  cards: cardApi,
131
+ compile,
132
+ exportRuntime,
113
133
  distillation
114
134
  } = require('@aikdna/kdna-studio-core');
115
135
 
@@ -151,12 +171,48 @@ const locked = cardApi.lockCard(card, {
151
171
  // 4. Check readiness
152
172
  const gate = projectApi.checkHumanLockGate(project);
153
173
  if (!gate.blocked) {
154
- // 5. Export
155
- const json = projectApi.exportProject(project);
156
- console.log('Ready to publish');
174
+ // 5. Compile and runtime-export
175
+ const compiled = compile.compileDomain(project, { strictAuthority: false });
176
+ const runtimeAsset = exportRuntime.exportRuntimeAsset(project, { compiled });
177
+ console.log(Object.keys(runtimeAsset.files));
157
178
  }
158
179
  ```
159
180
 
181
+ ## Runtime Export Contract
182
+
183
+ `compile.compileDomain(project)` is an authoring compile step. It returns source
184
+ and evidence artifacts for review, audit, and reports. It is not itself the
185
+ runtime distribution contract.
186
+
187
+ Use `exportRuntime.exportRuntimeAsset(project)` to produce a KDNA Core v1
188
+ runtime source directory payload:
189
+
190
+ ```js
191
+ const { exportRuntime } = require('@aikdna/kdna-studio-core');
192
+
193
+ const runtimeAsset = exportRuntime.exportRuntimeAsset(project);
194
+ // runtimeAsset.files contains only:
195
+ // - mimetype
196
+ // - kdna.json
197
+ // - payload.kdnab
198
+ // - checksums.json
199
+ ```
200
+
201
+ The exported files are tested against `@aikdna/kdna-core.validate`. In the OPEN
202
+ workspace they are also tested against the current `aikdna/kdna` LoadPlan
203
+ implementation when available.
204
+
205
+ Access values are canonicalized for runtime export:
206
+
207
+ | Studio / legacy value | Runtime value |
208
+ |---|---|
209
+ | `open` | `public` |
210
+ | `protected` | `licensed` |
211
+ | `runtime` | `remote` |
212
+
213
+ Top-level source JSON entries such as `KDNA_Core.json`, `KDNA_Patterns.json`,
214
+ and `KDNA_CARD.json` must not be present in runtime export output.
215
+
160
216
  ## Card Types (v1.0)
161
217
 
162
218
  | Type | Compiles to | Description |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-studio-core",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Official KDNA Studio Core SDK for authoring, locking, compiling, and exporting .kdna judgment assets through the KDNA toolchain.",
5
5
  "type": "commonjs",
6
6
  "main": "src/index.js",
@@ -24,7 +24,7 @@
24
24
  "validate"
25
25
  ],
26
26
  "dependencies": {
27
- "@aikdna/kdna-core": "^0.9.1",
27
+ "@aikdna/kdna-core": "^0.12.0",
28
28
  "cbor-x": "^1.6.4"
29
29
  },
30
30
  "engines": {
@@ -36,6 +36,6 @@
36
36
  "test:all": "npm run lint && npm test"
37
37
  },
38
38
  "devDependencies": {
39
- "@aikdna/kdna-cli": "^0.19.3"
39
+ "@aikdna/kdna-cli": "^0.26.1"
40
40
  }
41
41
  }
@@ -472,7 +472,14 @@ function compileDomain(project, options = {}) {
472
472
  throw err;
473
473
  }
474
474
 
475
- const files = {};
475
+ const files = {
476
+ 'KDNA_Core.json': JSON.stringify(core, null, 2),
477
+ 'KDNA_Patterns.json': JSON.stringify(patterns, null, 2),
478
+ };
479
+ if (scenarios) files['KDNA_Scenarios.json'] = JSON.stringify(scenarios, null, 2);
480
+ if (cases) files['KDNA_Cases.json'] = JSON.stringify(cases, null, 2);
481
+ if (reasoning) files['KDNA_Reasoning.json'] = JSON.stringify(reasoning, null, 2);
482
+ if (evolution) files['KDNA_Evolution.json'] = JSON.stringify(evolution, null, 2);
476
483
 
477
484
  // Encode judgment as CBOR payload
478
485
  const payload = {
@@ -0,0 +1,212 @@
1
+ const crypto = require('crypto');
2
+ const { compileDomain } = require('../compile');
3
+
4
+ const MIMETYPE_V1 = 'application/vnd.kdna.asset';
5
+
6
+ function json(value) {
7
+ return `${JSON.stringify(value, null, 2)}\n`;
8
+ }
9
+
10
+ function domainIdFromName(name = 'domain') {
11
+ const base = String(name).includes('/') ? String(name).split('/').pop() : String(name);
12
+ const normalized = base
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9_]+/g, '_')
15
+ .replace(/^_+|_+$/g, '');
16
+ return /^[a-z]/.test(normalized) ? normalized : `domain_${normalized || 'untitled'}`;
17
+ }
18
+
19
+ function canonicalAccess(value) {
20
+ if (!value || value === 'open') return 'public';
21
+ if (value === 'protected') return 'licensed';
22
+ if (value === 'runtime') return 'remote';
23
+ return value;
24
+ }
25
+
26
+ function semverValue(value, fallback = '0.1.0') {
27
+ const raw = String(value || '').trim();
28
+ if (/^[0-9]+\.[0-9]+\.[0-9]+([+-].+)?$/.test(raw)) return raw;
29
+ const twoPart = raw.match(/^([0-9]+)\.([0-9]+)$/);
30
+ if (twoPart) return `${twoPart[1]}.${twoPart[2]}.0`;
31
+ return fallback;
32
+ }
33
+
34
+ function canonicalLineage(lineage) {
35
+ if (!lineage || typeof lineage !== 'object') return { type: 'original' };
36
+ const allowed = new Set([
37
+ 'original',
38
+ 'fork',
39
+ 'adaptation',
40
+ 'translation',
41
+ 'private_variant',
42
+ 'organization_variant',
43
+ 'course_variant',
44
+ ]);
45
+ if (allowed.has(lineage.type)) return lineage;
46
+ return {
47
+ ...lineage,
48
+ type: 'adaptation',
49
+ source_lineage_type: lineage.type || 'unknown',
50
+ };
51
+ }
52
+
53
+ function sha256Hex(value) {
54
+ return crypto.createHash('sha256').update(Buffer.isBuffer(value) ? value : Buffer.from(value)).digest('hex');
55
+ }
56
+
57
+ function buildChecksums(files) {
58
+ const entries = {
59
+ 'kdna.json': { algorithm: 'sha256', value: sha256Hex(files['kdna.json']) },
60
+ 'payload.kdnab': { algorithm: 'sha256', value: sha256Hex(files['payload.kdnab']) },
61
+ };
62
+ const combined = Object.keys(entries)
63
+ .sort()
64
+ .map((name) => `${name}:${entries[name].value}`)
65
+ .join('\n');
66
+ return {
67
+ algorithm: 'sha256',
68
+ manifest_digest: `sha256:${entries['kdna.json'].value}`,
69
+ payload_digest: `sha256:${entries['payload.kdnab'].value}`,
70
+ asset_digest: `sha256:${sha256Hex(combined)}`,
71
+ entries,
72
+ };
73
+ }
74
+
75
+ function parseJsonFile(files, name, fallback = null) {
76
+ if (!files[name]) return fallback;
77
+ return JSON.parse(files[name]);
78
+ }
79
+
80
+ function buildPayload(compiled) {
81
+ const core = parseJsonFile(compiled.files, 'KDNA_Core.json', {});
82
+ const patterns = parseJsonFile(compiled.files, 'KDNA_Patterns.json', {});
83
+ const scenarios = parseJsonFile(compiled.files, 'KDNA_Scenarios.json', { scenes: [] });
84
+ const cases = parseJsonFile(compiled.files, 'KDNA_Cases.json', { cases: [] });
85
+ const reasoning = parseJsonFile(compiled.files, 'KDNA_Reasoning.json', { reasoning_chains: [] });
86
+ const evolution = parseJsonFile(compiled.files, 'KDNA_Evolution.json', { changelog: [], version_notes: [] });
87
+
88
+ const firstAxiom = Array.isArray(core.axioms) ? core.axioms[0] : null;
89
+ return {
90
+ profile: 'judgment-profile-v1',
91
+ core: {
92
+ highest_question:
93
+ core.meta?.load_condition ||
94
+ firstAxiom?.one_sentence ||
95
+ `What judgment should be loaded for ${core.meta?.domain || 'this domain'}?`,
96
+ axioms: Array.isArray(core.axioms) ? core.axioms : [],
97
+ boundaries: Array.isArray(core.boundaries) ? core.boundaries : [],
98
+ risk_model: {
99
+ risks: Array.isArray(core.risks) ? core.risks : [],
100
+ },
101
+ },
102
+ patterns: Array.isArray(patterns.misunderstandings) ? patterns.misunderstandings : [],
103
+ scenarios: Array.isArray(scenarios.scenes) ? scenarios.scenes : [],
104
+ cases: Array.isArray(cases.cases) ? cases.cases : [],
105
+ reasoning: {
106
+ self_checks: Array.isArray(patterns.self_check) ? patterns.self_check : [],
107
+ failure_modes: Array.isArray(reasoning.reasoning_chains) ? reasoning.reasoning_chains : [],
108
+ },
109
+ evolution: {
110
+ stages: Array.isArray(evolution.stages) ? evolution.stages : [],
111
+ evolution_layers: Array.isArray(evolution.evolution_layers) ? evolution.evolution_layers : [],
112
+ measurement: Array.isArray(evolution.measurement) ? evolution.measurement : [],
113
+ changelog: Array.isArray(evolution.changelog) ? evolution.changelog : [],
114
+ version_notes: Array.isArray(evolution.version_notes) ? evolution.version_notes : [],
115
+ },
116
+ };
117
+ }
118
+
119
+ function buildManifest(project, compiled, payloadBytes, options = {}) {
120
+ const sourceManifest = parseJsonFile(compiled.files, 'kdna.json', {});
121
+ const access = canonicalAccess(options.access || project.release?.access || sourceManifest.access);
122
+ const domainId = sourceManifest.domain_id || domainIdFromName(project.name);
123
+ const now = options.timestamp || sourceManifest.updated_at || sourceManifest.updated || new Date().toISOString();
124
+ const creator = project.author || sourceManifest.creator || sourceManifest.author || { name: 'Unknown' };
125
+
126
+ const manifest = {
127
+ kdna_version: '1.0',
128
+ asset_id: options.asset_id || `kdna:studio:${domainId}`,
129
+ asset_uid: options.asset_uid || `urn:uuid:${sourceManifest.asset_uid || compiled.identity?.asset_uid}`,
130
+ asset_type: 'domain',
131
+ title: options.title || project.title || project.name,
132
+ version: semverValue(sourceManifest.version || project.release?.version, '0.1.0'),
133
+ judgment_version: semverValue(sourceManifest.judgment_version || project.release?.judgment_version || project.release?.version, '0.1.0'),
134
+ created_at: options.created_at || sourceManifest.created_at || new Date(project.created || now).toISOString(),
135
+ updated_at: options.updated_at || now,
136
+ creator: {
137
+ name: creator.name || creator.display_name || 'Unknown',
138
+ id: creator.id || creator.creator_id || undefined,
139
+ },
140
+ compatibility: {
141
+ min_loader_version: '1.0.0',
142
+ profile: 'judgment-profile-v1',
143
+ },
144
+ payload: {
145
+ path: 'payload.kdnab',
146
+ encoding: 'json',
147
+ encrypted: false,
148
+ digest: `sha256:${sha256Hex(payloadBytes)}`,
149
+ },
150
+ access,
151
+ summary: sourceManifest.description || project.release?.description || project.name,
152
+ language: project.default_language || sourceManifest.default_language || 'en',
153
+ languages: project.languages || sourceManifest.languages || ['en'],
154
+ license: project.license || sourceManifest.license || { type: 'CC-BY-4.0' },
155
+ keywords: sourceManifest.keywords || [],
156
+ lineage: canonicalLineage(project.lineage || sourceManifest.lineage),
157
+ load_contract: {
158
+ default_profile: 'compact',
159
+ profiles: {
160
+ index: { requires_decryption: false, max_tokens_hint: 200 },
161
+ compact: { requires_decryption: false, max_tokens_hint: 500 },
162
+ scenario: { requires_decryption: false, selection: 'triggered_sections_only' },
163
+ full: { requires_decryption: false, intended_for: ['audit', 'reference'] },
164
+ },
165
+ },
166
+ authoring: {
167
+ compiler: '@aikdna/kdna-studio-core',
168
+ compiler_version: require('../../package.json').version,
169
+ source_build_id: compiled.identity?.build_id || sourceManifest.build_id || null,
170
+ studio_project_digest: sourceManifest.authoring?.studio_project_digest || null,
171
+ human_lock_required: true,
172
+ human_lock_count: sourceManifest.authoring?.human_lock_count || compiled.stats?.locked_cards || 0,
173
+ },
174
+ };
175
+
176
+ if (access === 'licensed') {
177
+ manifest.entitlement = options.entitlement || { profile: 'local_receipt', offline: true, revocable: true };
178
+ }
179
+ if (access === 'remote') {
180
+ manifest.runtime = options.runtime || { endpoint: null };
181
+ }
182
+ return manifest;
183
+ }
184
+
185
+ function exportRuntimeAsset(project, options = {}) {
186
+ const compiled = options.compiled || compileDomain(project, options.compile || {});
187
+ const payload = buildPayload(compiled);
188
+ const payloadBytes = json(payload);
189
+ const manifest = buildManifest(project, compiled, payloadBytes, options);
190
+ const files = {
191
+ mimetype: MIMETYPE_V1,
192
+ 'kdna.json': json(manifest),
193
+ 'payload.kdnab': payloadBytes,
194
+ };
195
+ files['checksums.json'] = json(buildChecksums(files));
196
+ return {
197
+ files,
198
+ manifest,
199
+ payload,
200
+ source: compiled,
201
+ };
202
+ }
203
+
204
+ module.exports = {
205
+ MIMETYPE_V1,
206
+ exportRuntimeAsset,
207
+ buildPayload,
208
+ buildManifest,
209
+ buildChecksums,
210
+ canonicalAccess,
211
+ canonicalLineage,
212
+ };
package/src/index.js CHANGED
@@ -21,6 +21,7 @@ const cards = require('./cards');
21
21
  const compile = require('./compile');
22
22
  const creatorIdentity = require('./creator-identity');
23
23
  const evidence = require('./evidence');
24
+ const exportRuntime = require('./export-runtime');
24
25
  const governance = require('./governance');
25
26
  const i18n = require('./i18n');
26
27
  const packaging = require('./packaging');
@@ -45,6 +46,7 @@ module.exports = {
45
46
  provenance,
46
47
  pipeline,
47
48
  governance,
49
+ exportRuntime,
48
50
  i18n,
49
51
  creator: creatorIdentity,
50
52
  distillation,
@@ -10,6 +10,7 @@
10
10
 
11
11
  const crypto = require('crypto');
12
12
  const projectSchema = require('../../schemas/studio.project.schema.json');
13
+ const { CARD_TYPES } = require('../cards');
13
14
  const { JUDGMENT_CARD_TYPES, cardJudgmentFingerprint } = require('../judgment-fields');
14
15
 
15
16
  function createProject(name, type = 'domain', options = {}) {
@@ -152,8 +153,7 @@ function validateProject(project) {
152
153
  if (!(req in card)) issues.push('cards[' + i + ']: missing required field "' + req + '"');
153
154
  }
154
155
  if (card.type !== undefined) {
155
- const validTypes = ['axiom', 'ontology', 'misunderstanding', 'boundary', 'self_check', 'risk', 'aesthetic', 'scenario', 'case'];
156
- if (!validTypes.includes(card.type)) issues.push('cards[' + i + ']: invalid type "' + card.type + '"');
156
+ if (!CARD_TYPES.includes(card.type)) issues.push('cards[' + i + ']: invalid type "' + card.type + '"');
157
157
  }
158
158
  if (card.status !== undefined) {
159
159
  const validStates = ['draft', 'revised', 'locked', 'tested', 'published', 'deprecated'];