@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 +59 -3
- package/package.json +3 -3
- package/src/compile/index.js +8 -1
- package/src/export-runtime/index.js +212 -0
- package/src/index.js +2 -0
- package/src/project/index.js +2 -2
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.
|
|
155
|
-
const
|
|
156
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
39
|
+
"@aikdna/kdna-cli": "^0.26.1"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/compile/index.js
CHANGED
|
@@ -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,
|
package/src/project/index.js
CHANGED
|
@@ -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
|
-
|
|
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'];
|