@aikdna/kdna-core 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -1
- package/package.json +1 -1
- package/schema/KDNA_Scenarios.schema.json +2 -2
- package/schema/kdna-file.schema.json +16 -6
- package/schema/kdna-manifest-v1rc.json +241 -0
- package/src/asset-reader.js +51 -17
- package/src/index.js +2 -0
- package/src/lint-pure.js +41 -10
- package/src/public-api.js +323 -0
- package/src/types.d.ts +76 -5
package/README.md
CHANGED
|
@@ -8,7 +8,59 @@ Core library for loading, validating, linting, rendering, composing, and directl
|
|
|
8
8
|
npm install @aikdna/kdna-core
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Preferred public API
|
|
12
|
+
|
|
13
|
+
Third-party adapters should start with the stable asset-first API. These
|
|
14
|
+
functions accept a `.kdna` file path, bytes, or an already opened asset and do
|
|
15
|
+
not require persistent extraction.
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const {
|
|
19
|
+
inspectKDNA,
|
|
20
|
+
validateKDNA,
|
|
21
|
+
loadKDNA,
|
|
22
|
+
renderForAgent,
|
|
23
|
+
verifyAsset,
|
|
24
|
+
verifyDigest,
|
|
25
|
+
verifySignature,
|
|
26
|
+
matchDomain,
|
|
27
|
+
composeKDNA
|
|
28
|
+
} = require('@aikdna/kdna-core');
|
|
29
|
+
|
|
30
|
+
const info = await inspectKDNA('./writing.kdna');
|
|
31
|
+
const validation = await validateKDNA('./writing.kdna');
|
|
32
|
+
const loaded = await loadKDNA('./writing.kdna', { profile: 'compact' });
|
|
33
|
+
const promptContext = await renderForAgent('./writing.kdna');
|
|
34
|
+
|
|
35
|
+
await verifyAsset('./writing.kdna', {
|
|
36
|
+
asset_digest: info.asset_digest,
|
|
37
|
+
requireSignature: true
|
|
38
|
+
});
|
|
39
|
+
await verifyDigest('./writing.kdna', info.asset_digest);
|
|
40
|
+
await verifySignature('./writing.kdna');
|
|
41
|
+
|
|
42
|
+
const matches = await matchDomain('Review this writing draft', ['./writing.kdna']);
|
|
43
|
+
const composed = await composeKDNA(['./writing.kdna', './agent_safety.kdna'], {
|
|
44
|
+
input: 'Review this public release note for safety and writing quality'
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Stable entry points:
|
|
49
|
+
|
|
50
|
+
| Function | Purpose |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `openKDNA()` | Open a `.kdna` file or bytes as an immutable asset. |
|
|
53
|
+
| `inspectKDNA()` | Return manifest, entries, access, quality, risk, and digests. |
|
|
54
|
+
| `validateKDNA()` | Run asset, lint, schema, and cross-file validation. |
|
|
55
|
+
| `loadKDNA()` | Load index/compact/scenario/full profiles directly from `.kdna`. |
|
|
56
|
+
| `renderForAgent()` | Render a loaded asset into agent prompt context. |
|
|
57
|
+
| `verifyAsset()` | Run full asset verification: digest, content digest, signature, and trust metadata. |
|
|
58
|
+
| `verifyDigest()` | Check whole-file `asset_digest`. |
|
|
59
|
+
| `verifySignature()` | Require Ed25519 signature verification. |
|
|
60
|
+
| `matchDomain()` | Rank candidate assets for a task string. |
|
|
61
|
+
| `composeKDNA()` | Compose multiple assets with attribution and conflict reporting. |
|
|
62
|
+
|
|
63
|
+
## Lower-level usage
|
|
12
64
|
|
|
13
65
|
```js
|
|
14
66
|
const {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "KDNA core library — pure logic for loading, validating, linting, and rendering KDNA domain cognition packages. Zero Node.js dependencies.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"scenes": {
|
|
35
|
-
"description": "
|
|
35
|
+
"description": "Not part of KDNA v1.0. Use scenarios.",
|
|
36
36
|
"type": "array",
|
|
37
37
|
"items": {
|
|
38
38
|
"type": "object",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"name": { "type": "string" },
|
|
43
43
|
"trigger_signal": {
|
|
44
44
|
"type": "string",
|
|
45
|
-
"description": "
|
|
45
|
+
"description": "Not part of KDNA v1.0. Use trigger_signals."
|
|
46
46
|
},
|
|
47
47
|
"trigger_signals": {
|
|
48
48
|
"type": "array",
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://aikdna.com/schema/kdna-file.schema.json",
|
|
4
|
-
"title": "KDNA Single-File Format",
|
|
5
|
-
"description": "
|
|
4
|
+
"title": "KDNA Legacy Merged Single-File Format (Rejected)",
|
|
5
|
+
"description": "Legacy v0.x merged JSON/YAML single-file format. This format is not valid for KDNA v1.0-rc; conforming v1.0 tools MUST reject it and use the ZIP .kdna container with root mimetype and kdna.json.",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"
|
|
7
|
+
"not": {},
|
|
8
|
+
"required": ["format", "format_version", "spec_version", "meta", "core", "patterns"],
|
|
8
9
|
"properties": {
|
|
9
|
-
"
|
|
10
|
+
"format": {
|
|
10
11
|
"type": "string",
|
|
11
|
-
"
|
|
12
|
+
"const": "kdna",
|
|
13
|
+
"description": "KDNA format marker."
|
|
14
|
+
},
|
|
15
|
+
"format_version": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"const": "1.0",
|
|
18
|
+
"description": "KDNA single-file manifest format version."
|
|
19
|
+
},
|
|
20
|
+
"spec_version": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "KDNA spec version this file conforms to."
|
|
12
23
|
},
|
|
13
24
|
"meta": {
|
|
14
25
|
"type": "object",
|
|
@@ -23,7 +34,6 @@
|
|
|
23
34
|
"updated": { "type": "string", "description": "ISO date of last update." },
|
|
24
35
|
"purpose": { "type": "string", "description": "What judgment this domain improves." },
|
|
25
36
|
"description": { "type": "string" },
|
|
26
|
-
"language": { "type": "string" },
|
|
27
37
|
"languages": { "type": "array", "items": { "type": "string" } }
|
|
28
38
|
},
|
|
29
39
|
"additionalProperties": true
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://aikdna.com/schema/kdna-manifest-v1rc.json",
|
|
4
|
+
"title": "KDNA Domain Manifest (kdna.json)",
|
|
5
|
+
"description": "Canonical JSON Schema for the kdna.json manifest inside a .kdna asset internal domain tree or dev source workspace. This is the single source of truth: CLI, Registry, Studio, and compatible loaders MUST validate against this schema.",
|
|
6
|
+
|
|
7
|
+
"type": "object",
|
|
8
|
+
"required": [
|
|
9
|
+
"format",
|
|
10
|
+
"format_version",
|
|
11
|
+
"spec_version",
|
|
12
|
+
"name",
|
|
13
|
+
"version",
|
|
14
|
+
"judgment_version",
|
|
15
|
+
"description",
|
|
16
|
+
"author",
|
|
17
|
+
"license",
|
|
18
|
+
"status",
|
|
19
|
+
"quality_badge",
|
|
20
|
+
"access",
|
|
21
|
+
"languages",
|
|
22
|
+
"default_language"
|
|
23
|
+
],
|
|
24
|
+
"properties": {
|
|
25
|
+
|
|
26
|
+
"format": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"const": "kdna",
|
|
29
|
+
"description": "KDNA container format marker."
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
"format_version": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"const": "1.0",
|
|
35
|
+
"description": "KDNA container manifest format version."
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
"spec_version": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "KDNA SPEC version this asset conforms to. Required.",
|
|
41
|
+
"examples": ["1.0-rc"]
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
"name": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"pattern": "^@[a-z][a-z0-9-]*/[a-z][a-z0-9_]*$",
|
|
47
|
+
"description": "Scoped domain identifier. Format: @scope/name. scope matches the GitHub org. name uses snake_case.",
|
|
48
|
+
"examples": ["@aikdna/writing", "@aikdna/agent_safety"]
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
"version": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Semantic version (MAJOR.MINOR.PATCH) of the domain asset. Tracks packaging and metadata changes. Independent of judgment_version.",
|
|
54
|
+
"examples": ["0.7.2"]
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
"judgment_version": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "Version of the domain's judgment content (axioms, ontology, misunderstandings). MUST be incremented when any judgment-relevant content changes. Uses YYYY.MM format or semver.",
|
|
60
|
+
"examples": ["2026.05"]
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
"description": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"minLength": 20,
|
|
66
|
+
"description": "What judgment this domain improves. One sentence that answers: what does loading this domain change about how an agent thinks?"
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
"core_insight": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"minLength": 10,
|
|
72
|
+
"description": "Optional. The single most important cognitive shift this domain creates. Used for display in Compare Mode and registry previews."
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
"author": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"required": ["name", "id"],
|
|
78
|
+
"properties": {
|
|
79
|
+
"name": { "type": "string", "description": "Author display name." },
|
|
80
|
+
"id": { "type": "string", "description": "Author identifier (e.g., GitHub handle or email prefix)." },
|
|
81
|
+
"pubkey": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"pattern": "^ed25519:[0-9a-f]{64}$",
|
|
84
|
+
"description": "Ed25519 public key for signature verification. Required for signed domains."
|
|
85
|
+
},
|
|
86
|
+
"public_key_pem": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"description": "PEM-encoded Ed25519 public key used by verifiers."
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
"license": {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"required": ["type"],
|
|
96
|
+
"properties": {
|
|
97
|
+
"type": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "License identifier. CC-BY-4.0 for open domains, KCL-1.0 for commercial/pro domains.",
|
|
100
|
+
"examples": ["CC-BY-4.0", "KCL-1.0", "MIT"]
|
|
101
|
+
},
|
|
102
|
+
"url": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"format": "uri",
|
|
105
|
+
"description": "URL to the full license text."
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
"status": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"enum": ["draft", "experimental", "stable", "deprecated", "staging"],
|
|
113
|
+
"description": "Domain maturity level. draft = early work in progress. experimental = complete but not yet proven in practice. stable = structure frozen, content mature. deprecated = superseded by another domain. staging = non-public pre-release (for pro/commercial domains)."
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
"quality_badge": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"enum": ["untested", "tested", "validated", "expert_reviewed", "production_ready"],
|
|
119
|
+
"description": "Evidence level for judgment quality. untested = schema validation only. tested = >= 10 eval cases with manual verification. validated = >= 30 eval cases with automated scoring and raw outputs. expert_reviewed = validated evidence plus external domain expert review. production_ready = expert_reviewed evidence plus real-world deployment evidence."
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
"access": {
|
|
123
|
+
"type": "string",
|
|
124
|
+
"enum": ["open", "licensed", "runtime"],
|
|
125
|
+
"description": "Distribution mode. open = plaintext, freely available. licensed = encrypted, requires local license. runtime = not distributed, server-side API only."
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
"languages": {
|
|
129
|
+
"type": "array",
|
|
130
|
+
"items": { "type": "string" },
|
|
131
|
+
"description": "All languages this domain supports (ISO 639-1 codes).",
|
|
132
|
+
"examples": [["en", "zh-CN"]]
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
"default_language": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"description": "Default language for agent loading when none specified."
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
"i18n_level": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"enum": ["L0", "L1", "L2", "L3"],
|
|
143
|
+
"description": "Internationalization maturity. L0 = single language only. L1 = basic translation of axioms. L2 = full bilingual (en + zh-CN minimum). L3 = multi-language with locale-aware content."
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
"keywords": {
|
|
147
|
+
"type": "array",
|
|
148
|
+
"items": { "type": "string" },
|
|
149
|
+
"description": "Discovery keywords for search and categorization."
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
"file_count": {
|
|
153
|
+
"type": "integer",
|
|
154
|
+
"minimum": 2,
|
|
155
|
+
"maximum": 6,
|
|
156
|
+
"description": "Number of standard KDNA JSON entries in the asset internal tree or dev source workspace (excluding kdna.json). Minimum 2 (Core + Patterns). Maximum 6."
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
"risk_level": {
|
|
160
|
+
"type": "string",
|
|
161
|
+
"enum": ["R0", "R1", "R2", "R3"],
|
|
162
|
+
"description": "Risk level for the domain. R0 = low risk (writing style, aesthetics). R1 = medium-low (product decisions, management). R2 = medium (safety, finance, legal). R3 = high (security strategy, code execution). Determines load warnings and certification requirements."
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
"privacy_level": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"enum": ["public", "private", "sensitive", "regulated"],
|
|
168
|
+
"description": "Privacy classification. Proposed for v1.1; optional in v1.0."
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
"asset_type": {
|
|
172
|
+
"type": "string",
|
|
173
|
+
"enum": [
|
|
174
|
+
"domain_judgment",
|
|
175
|
+
"personal_judgment",
|
|
176
|
+
"organization_standard",
|
|
177
|
+
"team_policy",
|
|
178
|
+
"creator_style",
|
|
179
|
+
"risk_guard"
|
|
180
|
+
],
|
|
181
|
+
"description": "Judgment asset category. Proposed for v1.1; optional in v1.0."
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
"fitness_for_purpose": {
|
|
185
|
+
"type": "object",
|
|
186
|
+
"description": "Applicability boundary for runtime policy and human review.",
|
|
187
|
+
"properties": {
|
|
188
|
+
"intended_use": { "type": "array", "items": { "type": "string" } },
|
|
189
|
+
"not_for": { "type": "array", "items": { "type": "string" } },
|
|
190
|
+
"requires_human_review_when": { "type": "array", "items": { "type": "string" } },
|
|
191
|
+
"known_failure_modes": { "type": "array", "items": { "type": "string" } }
|
|
192
|
+
},
|
|
193
|
+
"additionalProperties": false
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
"signature": {
|
|
197
|
+
"type": "string",
|
|
198
|
+
"pattern": "^ed25519:[0-9a-f]{128}$",
|
|
199
|
+
"description": "Ed25519 signature over the canonical payload. Required for domains with quality_badge >= tested."
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
"created": {
|
|
203
|
+
"type": "string",
|
|
204
|
+
"format": "date",
|
|
205
|
+
"description": "ISO date of first creation."
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
"updated": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"format": "date",
|
|
211
|
+
"description": "ISO date of last modification."
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
"replaced_by": {
|
|
215
|
+
"type": "string",
|
|
216
|
+
"description": "Required when status is deprecated. The name of the successor domain."
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
"allOf": [
|
|
221
|
+
{
|
|
222
|
+
"if": { "properties": { "status": { "const": "deprecated" } } },
|
|
223
|
+
"then": { "required": ["replaced_by"] }
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"if": {
|
|
227
|
+
"properties": {
|
|
228
|
+
"quality_badge": { "enum": ["tested", "validated", "expert_reviewed", "production_ready"] }
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
"then": {
|
|
232
|
+
"required": ["signature"],
|
|
233
|
+
"properties": {
|
|
234
|
+
"author": { "required": ["pubkey"] }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
],
|
|
239
|
+
|
|
240
|
+
"additionalProperties": false
|
|
241
|
+
}
|
package/src/asset-reader.js
CHANGED
|
@@ -22,6 +22,7 @@ const STANDARD_ENTRIES = [
|
|
|
22
22
|
];
|
|
23
23
|
|
|
24
24
|
const JSON_ENTRY_RE = /\.json$/i;
|
|
25
|
+
const KDNA_MEDIA_TYPE = 'application/vnd.aikdna.kdna+zip';
|
|
25
26
|
|
|
26
27
|
function sha256Hex(buf) {
|
|
27
28
|
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
@@ -181,8 +182,10 @@ function buildContentDigest(asset) {
|
|
|
181
182
|
if (entryName === '.DS_Store' || entryName === 'signature.json') continue;
|
|
182
183
|
const entryBuf = asset.readEntry(entryName);
|
|
183
184
|
let digestBuf = entryBuf;
|
|
184
|
-
if (entryName
|
|
185
|
-
|
|
185
|
+
if (JSON_ENTRY_RE.test(entryName)) {
|
|
186
|
+
const parsed = parseJson(entryBuf, entryName);
|
|
187
|
+
const value = entryName === 'kdna.json' ? manifestForDigest(parsed) : parsed;
|
|
188
|
+
digestBuf = Buffer.from(stableStringify(value));
|
|
186
189
|
}
|
|
187
190
|
parts.push(`${entryName}:${sha256Hex(digestBuf)}`);
|
|
188
191
|
}
|
|
@@ -201,16 +204,20 @@ function manifestForSignature(manifest, { stripDigestFields = true } = {}) {
|
|
|
201
204
|
return copy;
|
|
202
205
|
}
|
|
203
206
|
|
|
207
|
+
function canonicalJsonEntry(entryName, entryBuf, options = {}) {
|
|
208
|
+
const parsed = parseJson(entryBuf, entryName);
|
|
209
|
+
const value = entryName === 'kdna.json' ? manifestForSignature(parsed, options) : parsed;
|
|
210
|
+
return Buffer.from(stableStringify(value));
|
|
211
|
+
}
|
|
212
|
+
|
|
204
213
|
function buildSigningPayload(asset, options = {}) {
|
|
205
214
|
const parts = [];
|
|
206
|
-
for (const entryName of [...asset.entries.keys()].
|
|
207
|
-
if (entryName === 'signature.json') continue;
|
|
215
|
+
for (const entryName of [...asset.entries.keys()].sort()) {
|
|
216
|
+
if (entryName === '.DS_Store' || entryName === 'signature.json') continue;
|
|
208
217
|
const entryBuf = asset.readEntry(entryName);
|
|
209
218
|
let payloadBuf = entryBuf;
|
|
210
|
-
if (entryName
|
|
211
|
-
payloadBuf =
|
|
212
|
-
JSON.stringify(manifestForSignature(parseJson(entryBuf, entryName), options)),
|
|
213
|
-
);
|
|
219
|
+
if (JSON_ENTRY_RE.test(entryName)) {
|
|
220
|
+
payloadBuf = canonicalJsonEntry(entryName, entryBuf, options);
|
|
214
221
|
}
|
|
215
222
|
parts.push(`${entryName}:${sha256Hex(payloadBuf)}`);
|
|
216
223
|
}
|
|
@@ -240,15 +247,7 @@ function verifySignature(asset, manifest, errors, warnings) {
|
|
|
240
247
|
try {
|
|
241
248
|
const signature = Buffer.from(String(manifest.signature).replace(/^ed25519:/, ''), 'hex');
|
|
242
249
|
const publicKey = crypto.createPublicKey(manifest.author.public_key_pem);
|
|
243
|
-
|
|
244
|
-
if (!ok) {
|
|
245
|
-
ok = crypto.verify(
|
|
246
|
-
null,
|
|
247
|
-
Buffer.from(buildSigningPayload(asset, { stripDigestFields: false })),
|
|
248
|
-
publicKey,
|
|
249
|
-
signature,
|
|
250
|
-
);
|
|
251
|
-
}
|
|
250
|
+
const ok = crypto.verify(null, Buffer.from(buildSigningPayload(asset)), publicKey, signature);
|
|
252
251
|
if (!ok) errors.push('Ed25519 signature invalid');
|
|
253
252
|
return ok;
|
|
254
253
|
} catch (e) {
|
|
@@ -257,6 +256,37 @@ function verifySignature(asset, manifest, errors, warnings) {
|
|
|
257
256
|
}
|
|
258
257
|
}
|
|
259
258
|
|
|
259
|
+
function verifyMediaType(asset, errors) {
|
|
260
|
+
if (!asset.entries.has('mimetype')) {
|
|
261
|
+
errors.push('required entry missing: mimetype');
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const value = asset.readEntry('mimetype').toString('utf8');
|
|
265
|
+
if (value !== KDNA_MEDIA_TYPE) {
|
|
266
|
+
errors.push(`mimetype: expected ${KDNA_MEDIA_TYPE}, got ${JSON.stringify(value)}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function validateManifestIdentity(manifest, errors, _warnings) {
|
|
271
|
+
if (manifest.kdna_spec) {
|
|
272
|
+
errors.push('kdna.json: kdna_spec is not allowed. Use spec_version.');
|
|
273
|
+
}
|
|
274
|
+
if (manifest.format && manifest.format !== 'kdna') {
|
|
275
|
+
errors.push(`kdna.json.format: invalid value "${manifest.format}". Expected "kdna".`);
|
|
276
|
+
}
|
|
277
|
+
if (manifest.format_version && manifest.format_version !== '1.0') {
|
|
278
|
+
errors.push(
|
|
279
|
+
`kdna.json.format_version: invalid value "${manifest.format_version}". Expected "1.0".`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
if (!manifest.spec_version) errors.push('kdna.json: missing required field "spec_version"');
|
|
283
|
+
if (!manifest.format) errors.push('kdna.json: missing required field "format"');
|
|
284
|
+
if (!manifest.format_version) errors.push('kdna.json: missing required field "format_version"');
|
|
285
|
+
if (manifest.language) {
|
|
286
|
+
errors.push('kdna.json: language is not allowed. Use default_language and languages.');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
260
290
|
function openAsset(input) {
|
|
261
291
|
const { buffer, path } = normalizeInput(input);
|
|
262
292
|
const entries = parseZipEntries(buffer);
|
|
@@ -307,6 +337,7 @@ function verifySync(asset, options = {}) {
|
|
|
307
337
|
const entries = listEntries(asset);
|
|
308
338
|
|
|
309
339
|
if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
|
|
340
|
+
verifyMediaType(asset, errors);
|
|
310
341
|
if (!asset.entries.has('KDNA_Core.json')) errors.push('required entry missing: KDNA_Core.json');
|
|
311
342
|
if (!asset.entries.has('KDNA_Patterns.json')) {
|
|
312
343
|
errors.push('required entry missing: KDNA_Patterns.json');
|
|
@@ -326,6 +357,7 @@ function verifySync(asset, options = {}) {
|
|
|
326
357
|
if (asset.entries.has('kdna.json')) {
|
|
327
358
|
try {
|
|
328
359
|
manifest = readManifest(asset);
|
|
360
|
+
validateManifestIdentity(manifest, errors, warnings);
|
|
329
361
|
const encrypted = encryptedEntries(manifest);
|
|
330
362
|
if (encrypted.length) {
|
|
331
363
|
warnings.push(`encrypted entries present: ${encrypted.join(', ')}`);
|
|
@@ -480,6 +512,7 @@ function createKdnaAssetReader() {
|
|
|
480
512
|
const entries = [...asset.entries.keys()].sort();
|
|
481
513
|
|
|
482
514
|
if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
|
|
515
|
+
verifyMediaType(asset, errors);
|
|
483
516
|
if (!asset.entries.has('KDNA_Core.json')) errors.push('required entry missing: KDNA_Core.json');
|
|
484
517
|
if (!asset.entries.has('KDNA_Patterns.json')) {
|
|
485
518
|
errors.push('required entry missing: KDNA_Patterns.json');
|
|
@@ -501,6 +534,7 @@ function createKdnaAssetReader() {
|
|
|
501
534
|
if (asset.entries.has('kdna.json')) {
|
|
502
535
|
try {
|
|
503
536
|
manifest = parseJson(asset.readEntry('kdna.json'), 'kdna.json');
|
|
537
|
+
validateManifestIdentity(manifest, errors, warnings);
|
|
504
538
|
const encrypted = encryptedEntries(manifest);
|
|
505
539
|
if (encrypted.length) {
|
|
506
540
|
warnings.push(`encrypted entries present: ${encrypted.join(', ')}`);
|
package/src/index.js
CHANGED
|
@@ -8,8 +8,10 @@ const render = require('./render');
|
|
|
8
8
|
const compose = require('./compose');
|
|
9
9
|
const assetReader = require('./asset-reader');
|
|
10
10
|
const cryptoProfile = require('./crypto-profile');
|
|
11
|
+
const publicApi = require('./public-api');
|
|
11
12
|
|
|
12
13
|
module.exports = {
|
|
14
|
+
...publicApi,
|
|
13
15
|
...loader,
|
|
14
16
|
...lint,
|
|
15
17
|
...validate,
|
package/src/lint-pure.js
CHANGED
|
@@ -261,11 +261,20 @@ const VALID_BADGE = new Set(['untested', 'tested', 'validated', 'expert_reviewed
|
|
|
261
261
|
const VALID_ACCESS = new Set(['open', 'licensed', 'runtime']);
|
|
262
262
|
const VALID_RISK = new Set(['R0', 'R1', 'R2', 'R3']);
|
|
263
263
|
const VALID_I18N = new Set(['L0', 'L1', 'L2', 'L3']);
|
|
264
|
+
const VALID_PRIVACY = new Set(['public', 'private', 'sensitive', 'regulated']);
|
|
265
|
+
const VALID_ASSET_TYPE = new Set([
|
|
266
|
+
'domain_judgment',
|
|
267
|
+
'personal_judgment',
|
|
268
|
+
'organization_standard',
|
|
269
|
+
'team_policy',
|
|
270
|
+
'creator_style',
|
|
271
|
+
'risk_guard',
|
|
272
|
+
]);
|
|
264
273
|
|
|
265
274
|
const MANIFEST_REQUIRED = [
|
|
266
|
-
'
|
|
267
|
-
'description', 'author', 'license', 'status',
|
|
268
|
-
'quality_badge', 'access', '
|
|
275
|
+
'format', 'format_version', 'spec_version', 'name', 'version',
|
|
276
|
+
'judgment_version', 'description', 'author', 'license', 'status',
|
|
277
|
+
'quality_badge', 'access', 'languages', 'default_language',
|
|
269
278
|
];
|
|
270
279
|
|
|
271
280
|
/**
|
|
@@ -283,13 +292,15 @@ function validateManifest(manifest) {
|
|
|
283
292
|
return { errors, warnings };
|
|
284
293
|
}
|
|
285
294
|
|
|
286
|
-
// 1. Check
|
|
287
|
-
if ('
|
|
295
|
+
// 1. Check disallowed pre-v1.0 manifest aliases
|
|
296
|
+
if ('kdna_spec' in manifest) {
|
|
288
297
|
errors.push(
|
|
289
|
-
'kdna.json:
|
|
290
|
-
'(spec_version is reserved for .kdna container manifests only.)',
|
|
298
|
+
'kdna.json: kdna_spec is not allowed. Use spec_version.',
|
|
291
299
|
);
|
|
292
300
|
}
|
|
301
|
+
if ('language' in manifest) {
|
|
302
|
+
errors.push('kdna.json: language is not allowed. Use default_language and languages.');
|
|
303
|
+
}
|
|
293
304
|
|
|
294
305
|
// 2. Check required fields
|
|
295
306
|
for (const field of MANIFEST_REQUIRED) {
|
|
@@ -304,6 +315,14 @@ function validateManifest(manifest) {
|
|
|
304
315
|
}
|
|
305
316
|
|
|
306
317
|
// 4. Validate enum fields
|
|
318
|
+
if (manifest.format && manifest.format !== 'kdna') {
|
|
319
|
+
errors.push(`kdna.json.format: invalid value "${manifest.format}". Expected "kdna".`);
|
|
320
|
+
}
|
|
321
|
+
if (manifest.format_version && manifest.format_version !== '1.0') {
|
|
322
|
+
errors.push(
|
|
323
|
+
`kdna.json.format_version: invalid value "${manifest.format_version}". Expected "1.0".`,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
307
326
|
if (manifest.status && !VALID_STATUS.has(manifest.status)) {
|
|
308
327
|
errors.push(
|
|
309
328
|
`kdna.json.status: invalid value "${manifest.status}". ` +
|
|
@@ -334,6 +353,18 @@ function validateManifest(manifest) {
|
|
|
334
353
|
`Valid: ${[...VALID_I18N].join(', ')}`,
|
|
335
354
|
);
|
|
336
355
|
}
|
|
356
|
+
if (manifest.privacy_level && !VALID_PRIVACY.has(manifest.privacy_level)) {
|
|
357
|
+
warnings.push(
|
|
358
|
+
`kdna.json.privacy_level: non-standard value "${manifest.privacy_level}". ` +
|
|
359
|
+
`Valid: ${[...VALID_PRIVACY].join(', ')}`,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
if (manifest.asset_type && !VALID_ASSET_TYPE.has(manifest.asset_type)) {
|
|
363
|
+
warnings.push(
|
|
364
|
+
`kdna.json.asset_type: non-standard value "${manifest.asset_type}". ` +
|
|
365
|
+
`Valid: ${[...VALID_ASSET_TYPE].join(', ')}`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
337
368
|
|
|
338
369
|
// 5. Deprecated status must have replaced_by
|
|
339
370
|
if (manifest.status === 'deprecated' && !manifest.replaced_by) {
|
|
@@ -362,10 +393,10 @@ function validateManifest(manifest) {
|
|
|
362
393
|
errors.push('kdna.json.license: missing "type"');
|
|
363
394
|
}
|
|
364
395
|
|
|
365
|
-
// 9. Validate
|
|
366
|
-
if (manifest.
|
|
396
|
+
// 9. Validate spec_version value
|
|
397
|
+
if (manifest.spec_version && manifest.spec_version !== '1.0-rc') {
|
|
367
398
|
warnings.push(
|
|
368
|
-
`kdna.json.
|
|
399
|
+
`kdna.json.spec_version: non-standard value "${manifest.spec_version}". Expected "1.0-rc".`,
|
|
369
400
|
);
|
|
370
401
|
}
|
|
371
402
|
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable public API for third-party KDNA integrations.
|
|
3
|
+
*
|
|
4
|
+
* Lower-level modules remain exported for advanced runtimes, but external
|
|
5
|
+
* adapters should prefer these names. They encode the asset-first contract:
|
|
6
|
+
* callers pass .kdna files or bytes, and no persistent directory extraction is
|
|
7
|
+
* required.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { createKdnaAssetReader } = require('./asset-reader');
|
|
11
|
+
const { lintDomain } = require('./lint-pure');
|
|
12
|
+
const { validateCrossFile, validateDomainSchema } = require('./validate-pure');
|
|
13
|
+
const { formatContext } = require('./loader');
|
|
14
|
+
const {
|
|
15
|
+
composeContextWithAttribution,
|
|
16
|
+
detectDomainConflicts,
|
|
17
|
+
classifySignalsAcrossDomains,
|
|
18
|
+
generateClusterTrace,
|
|
19
|
+
} = require('./compose');
|
|
20
|
+
|
|
21
|
+
function readerFrom(options = {}) {
|
|
22
|
+
return options.reader || createKdnaAssetReader();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isAsset(value) {
|
|
26
|
+
return value && typeof value === 'object' && value.entries instanceof Map && typeof value.readEntry === 'function';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function asAsset(input, options = {}) {
|
|
30
|
+
if (isAsset(input)) return input;
|
|
31
|
+
return readerFrom(options).open(input);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function asAssetSync(input, options = {}) {
|
|
35
|
+
if (isAsset(input)) return input;
|
|
36
|
+
return readerFrom(options).openSync(input);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function openKDNA(input, options = {}) {
|
|
40
|
+
return asAsset(input, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function openKDNASync(input, options = {}) {
|
|
44
|
+
return asAssetSync(input, options);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function inspectKDNA(input, options = {}) {
|
|
48
|
+
const reader = readerFrom(options);
|
|
49
|
+
const asset = await asAsset(input, { ...options, reader });
|
|
50
|
+
const profile = await reader.loadProfile(asset, 'index', options);
|
|
51
|
+
const verification = options.verify === false ? null : await reader.verify(asset, options);
|
|
52
|
+
return {
|
|
53
|
+
name: profile.name,
|
|
54
|
+
version: profile.version,
|
|
55
|
+
judgment_version: profile.judgment_version,
|
|
56
|
+
access: profile.manifest.access || 'open',
|
|
57
|
+
status: profile.manifest.status || null,
|
|
58
|
+
quality_badge: profile.manifest.quality_badge || null,
|
|
59
|
+
risk_level: profile.manifest.risk_level || null,
|
|
60
|
+
keywords: profile.keywords,
|
|
61
|
+
entries: profile.entries,
|
|
62
|
+
asset_digest: profile.asset_digest,
|
|
63
|
+
content_digest: profile.content_digest,
|
|
64
|
+
signature_valid: verification ? verification.signature_valid : null,
|
|
65
|
+
ok: verification ? verification.ok : null,
|
|
66
|
+
errors: verification ? verification.errors : [],
|
|
67
|
+
warnings: verification ? verification.warnings : [],
|
|
68
|
+
manifest: profile.manifest,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function inspectKDNASync(input, options = {}) {
|
|
73
|
+
const reader = readerFrom(options);
|
|
74
|
+
const asset = asAssetSync(input, { ...options, reader });
|
|
75
|
+
const profile = reader.loadProfileSync(asset, 'index', options);
|
|
76
|
+
const verification = options.verify === false ? null : reader.verifySync(asset, options);
|
|
77
|
+
return {
|
|
78
|
+
name: profile.name,
|
|
79
|
+
version: profile.version,
|
|
80
|
+
judgment_version: profile.judgment_version,
|
|
81
|
+
access: profile.manifest.access || 'open',
|
|
82
|
+
status: profile.manifest.status || null,
|
|
83
|
+
quality_badge: profile.manifest.quality_badge || null,
|
|
84
|
+
risk_level: profile.manifest.risk_level || null,
|
|
85
|
+
keywords: profile.keywords,
|
|
86
|
+
entries: profile.entries,
|
|
87
|
+
asset_digest: profile.asset_digest,
|
|
88
|
+
content_digest: profile.content_digest,
|
|
89
|
+
signature_valid: verification ? verification.signature_valid : null,
|
|
90
|
+
ok: verification ? verification.ok : null,
|
|
91
|
+
errors: verification ? verification.errors : [],
|
|
92
|
+
warnings: verification ? verification.warnings : [],
|
|
93
|
+
manifest: profile.manifest,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function loadKDNA(input, options = {}) {
|
|
98
|
+
const reader = readerFrom(options);
|
|
99
|
+
const asset = await asAsset(input, { ...options, reader });
|
|
100
|
+
return reader.loadProfile(asset, options.profile || 'compact', options);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function loadKDNASync(input, options = {}) {
|
|
104
|
+
const reader = readerFrom(options);
|
|
105
|
+
const asset = asAssetSync(input, { ...options, reader });
|
|
106
|
+
return reader.loadProfileSync(asset, options.profile || 'compact', options);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function validateKDNA(input, options = {}) {
|
|
110
|
+
const reader = readerFrom(options);
|
|
111
|
+
const asset = await asAsset(input, { ...options, reader });
|
|
112
|
+
const assetResult = await reader.verify(asset, options);
|
|
113
|
+
let dataMap = null;
|
|
114
|
+
let lintResult = { errors: [], warnings: [] };
|
|
115
|
+
let schemaResult = { errors: [], warnings: [] };
|
|
116
|
+
let crossFileResult = { errors: [], warnings: [] };
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
dataMap = await reader.readDataMap(asset, undefined, options);
|
|
120
|
+
lintResult = lintDomain(dataMap);
|
|
121
|
+
schemaResult = validateDomainSchema(dataMap, options.schemas || {});
|
|
122
|
+
crossFileResult = validateCrossFile(dataMap);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
lintResult.errors.push(e.message);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const errors = [
|
|
128
|
+
...assetResult.errors,
|
|
129
|
+
...lintResult.errors,
|
|
130
|
+
...schemaResult.errors,
|
|
131
|
+
...crossFileResult.errors,
|
|
132
|
+
];
|
|
133
|
+
const warnings = [
|
|
134
|
+
...assetResult.warnings,
|
|
135
|
+
...lintResult.warnings,
|
|
136
|
+
...schemaResult.warnings,
|
|
137
|
+
...crossFileResult.warnings,
|
|
138
|
+
];
|
|
139
|
+
return {
|
|
140
|
+
ok: errors.length === 0,
|
|
141
|
+
errors,
|
|
142
|
+
warnings,
|
|
143
|
+
asset: assetResult,
|
|
144
|
+
lint: lintResult,
|
|
145
|
+
schema: schemaResult,
|
|
146
|
+
cross_file: crossFileResult,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function validateKDNASync(input, options = {}) {
|
|
151
|
+
const reader = readerFrom(options);
|
|
152
|
+
const asset = asAssetSync(input, { ...options, reader });
|
|
153
|
+
const assetResult = reader.verifySync(asset, options);
|
|
154
|
+
let lintResult = { errors: [], warnings: [] };
|
|
155
|
+
let schemaResult = { errors: [], warnings: [] };
|
|
156
|
+
let crossFileResult = { errors: [], warnings: [] };
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const dataMap = reader.readDataMapSync(asset, undefined, options);
|
|
160
|
+
lintResult = lintDomain(dataMap);
|
|
161
|
+
schemaResult = validateDomainSchema(dataMap, options.schemas || {});
|
|
162
|
+
crossFileResult = validateCrossFile(dataMap);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
lintResult.errors.push(e.message);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const errors = [
|
|
168
|
+
...assetResult.errors,
|
|
169
|
+
...lintResult.errors,
|
|
170
|
+
...schemaResult.errors,
|
|
171
|
+
...crossFileResult.errors,
|
|
172
|
+
];
|
|
173
|
+
const warnings = [
|
|
174
|
+
...assetResult.warnings,
|
|
175
|
+
...lintResult.warnings,
|
|
176
|
+
...schemaResult.warnings,
|
|
177
|
+
...crossFileResult.warnings,
|
|
178
|
+
];
|
|
179
|
+
return {
|
|
180
|
+
ok: errors.length === 0,
|
|
181
|
+
errors,
|
|
182
|
+
warnings,
|
|
183
|
+
asset: assetResult,
|
|
184
|
+
lint: lintResult,
|
|
185
|
+
schema: schemaResult,
|
|
186
|
+
cross_file: crossFileResult,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function renderForAgent(input, options = {}) {
|
|
191
|
+
const loaded = await loadKDNA(input, options);
|
|
192
|
+
if (loaded.context != null) return loaded.context;
|
|
193
|
+
return loaded.domain ? formatContext(loaded.domain) : '';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function renderForAgentSync(input, options = {}) {
|
|
197
|
+
const loaded = loadKDNASync(input, options);
|
|
198
|
+
if (loaded.context != null) return loaded.context;
|
|
199
|
+
return loaded.domain ? formatContext(loaded.domain) : '';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function verifyAsset(input, options = {}) {
|
|
203
|
+
const reader = readerFrom(options);
|
|
204
|
+
const asset = await asAsset(input, { ...options, reader });
|
|
205
|
+
return reader.verify(asset, options);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function verifyAssetSync(input, options = {}) {
|
|
209
|
+
const reader = readerFrom(options);
|
|
210
|
+
const asset = asAssetSync(input, { ...options, reader });
|
|
211
|
+
return reader.verifySync(asset, options);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function verifyDigest(input, expectedDigest, options = {}) {
|
|
215
|
+
return verifyAsset(input, { ...options, asset_digest: expectedDigest });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function verifyDigestSync(input, expectedDigest, options = {}) {
|
|
219
|
+
return verifyAssetSync(input, { ...options, asset_digest: expectedDigest });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function verifySignature(input, options = {}) {
|
|
223
|
+
return verifyAsset(input, { ...options, requireSignature: true });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function verifySignatureSync(input, options = {}) {
|
|
227
|
+
return verifyAssetSync(input, { ...options, requireSignature: true });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function scoreMatch(input, info) {
|
|
231
|
+
const haystack = String(input || '').toLowerCase();
|
|
232
|
+
const terms = [
|
|
233
|
+
info.name,
|
|
234
|
+
...(info.keywords || []),
|
|
235
|
+
info.manifest?.description,
|
|
236
|
+
info.manifest?.core_insight,
|
|
237
|
+
]
|
|
238
|
+
.filter(Boolean)
|
|
239
|
+
.map((v) => String(v).toLowerCase());
|
|
240
|
+
const matched = terms.filter((term) => term && haystack.includes(term.replace(/^@[^/]+\//, '')));
|
|
241
|
+
return { score: matched.length, matched };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function matchDomain(input, candidates, options = {}) {
|
|
245
|
+
const results = [];
|
|
246
|
+
for (const candidate of candidates || []) {
|
|
247
|
+
const info = typeof candidate === 'string' || candidate instanceof Uint8Array || isAsset(candidate)
|
|
248
|
+
? await inspectKDNA(candidate, { ...options, verify: false })
|
|
249
|
+
: candidate;
|
|
250
|
+
const match = scoreMatch(input, info);
|
|
251
|
+
if (match.score > 0) results.push({ ...info, score: match.score, matched: match.matched });
|
|
252
|
+
}
|
|
253
|
+
return results.sort((a, b) => b.score - a.score || String(a.name).localeCompare(String(b.name)));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function matchDomainSync(input, candidates, options = {}) {
|
|
257
|
+
const results = [];
|
|
258
|
+
for (const candidate of candidates || []) {
|
|
259
|
+
const info = typeof candidate === 'string' || candidate instanceof Uint8Array || isAsset(candidate)
|
|
260
|
+
? inspectKDNASync(candidate, { ...options, verify: false })
|
|
261
|
+
: candidate;
|
|
262
|
+
const match = scoreMatch(input, info);
|
|
263
|
+
if (match.score > 0) results.push({ ...info, score: match.score, matched: match.matched });
|
|
264
|
+
}
|
|
265
|
+
return results.sort((a, b) => b.score - a.score || String(a.name).localeCompare(String(b.name)));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function composeKDNA(inputs, options = {}) {
|
|
269
|
+
const loaded = [];
|
|
270
|
+
for (const input of inputs || []) {
|
|
271
|
+
const profile = await loadKDNA(input, { ...options, profile: options.profile || 'compact', context: false });
|
|
272
|
+
if (profile.domain) {
|
|
273
|
+
loaded.push({
|
|
274
|
+
id: profile.manifest.name || profile.domain.core?.meta?.domain,
|
|
275
|
+
name: profile.manifest.name || profile.domain.core?.meta?.domain,
|
|
276
|
+
manifest: profile.manifest,
|
|
277
|
+
...profile.domain,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const { selected, excluded } = classifySignalsAcrossDomains(options.input || '', loaded);
|
|
282
|
+
const selectedIds = new Set(selected.map((d) => d.id));
|
|
283
|
+
const activeDomains = options.input ? loaded.filter((d) => selectedIds.has(d.id)) : loaded;
|
|
284
|
+
const conflicts = detectDomainConflicts(activeDomains);
|
|
285
|
+
const { context, attributionMap } = composeContextWithAttribution(activeDomains, options);
|
|
286
|
+
return {
|
|
287
|
+
domains: loaded,
|
|
288
|
+
activeDomains,
|
|
289
|
+
selected,
|
|
290
|
+
excluded,
|
|
291
|
+
conflicts,
|
|
292
|
+
context,
|
|
293
|
+
attributionMap,
|
|
294
|
+
trace: generateClusterTrace({
|
|
295
|
+
input: options.input || '',
|
|
296
|
+
loadedDomains: loaded,
|
|
297
|
+
activeDomains,
|
|
298
|
+
conflicts,
|
|
299
|
+
}),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = {
|
|
304
|
+
openKDNA,
|
|
305
|
+
openKDNASync,
|
|
306
|
+
inspectKDNA,
|
|
307
|
+
inspectKDNASync,
|
|
308
|
+
loadKDNA,
|
|
309
|
+
loadKDNASync,
|
|
310
|
+
validateKDNA,
|
|
311
|
+
validateKDNASync,
|
|
312
|
+
renderForAgent,
|
|
313
|
+
renderForAgentSync,
|
|
314
|
+
verifyAsset,
|
|
315
|
+
verifyAssetSync,
|
|
316
|
+
verifyDigest,
|
|
317
|
+
verifyDigestSync,
|
|
318
|
+
verifySignature,
|
|
319
|
+
verifySignatureSync,
|
|
320
|
+
matchDomain,
|
|
321
|
+
matchDomainSync,
|
|
322
|
+
composeKDNA,
|
|
323
|
+
};
|
package/src/types.d.ts
CHANGED
|
@@ -203,15 +203,18 @@ export interface KDNAFileDataMap {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
export interface KDNAManifest {
|
|
206
|
-
|
|
206
|
+
format: 'kdna';
|
|
207
|
+
format_version: '1.0';
|
|
208
|
+
spec_version: string;
|
|
207
209
|
name: string;
|
|
208
210
|
version: string;
|
|
209
|
-
judgment_version
|
|
210
|
-
status: 'draft' | 'experimental' | 'stable' | 'deprecated' | '
|
|
211
|
+
judgment_version: string;
|
|
212
|
+
status: 'draft' | 'experimental' | 'stable' | 'deprecated' | 'staging';
|
|
213
|
+
quality_badge: 'untested' | 'tested' | 'validated' | 'expert_reviewed' | 'production_ready';
|
|
211
214
|
access: 'open' | 'licensed' | 'runtime';
|
|
212
215
|
language?: string;
|
|
213
|
-
default_language
|
|
214
|
-
languages
|
|
216
|
+
default_language: string;
|
|
217
|
+
languages: string[];
|
|
215
218
|
author: { name: string; id?: string; pubkey?: string; public_key_pem?: string };
|
|
216
219
|
license: { type: string; url?: string };
|
|
217
220
|
description: string;
|
|
@@ -413,6 +416,74 @@ export function createLicensedDecryptEntry(options: {
|
|
|
413
416
|
machineFingerprint: string;
|
|
414
417
|
}): NonNullable<KdnaDecryptOptions['decryptEntry']>;
|
|
415
418
|
|
|
419
|
+
// Stable public API — preferred entry points for third-party integrations.
|
|
420
|
+
export type KDNAAssetInput = string | Uint8Array | KdnaAsset;
|
|
421
|
+
|
|
422
|
+
export interface KDNAInspectResult {
|
|
423
|
+
name: string | null;
|
|
424
|
+
version: string | null;
|
|
425
|
+
judgment_version: string | null;
|
|
426
|
+
access: string;
|
|
427
|
+
status: string | null;
|
|
428
|
+
quality_badge: string | null;
|
|
429
|
+
risk_level: string | null;
|
|
430
|
+
keywords: string[];
|
|
431
|
+
entries: string[];
|
|
432
|
+
asset_digest: string;
|
|
433
|
+
content_digest: string;
|
|
434
|
+
signature_valid: boolean | null;
|
|
435
|
+
ok: boolean | null;
|
|
436
|
+
errors: string[];
|
|
437
|
+
warnings: string[];
|
|
438
|
+
manifest: KDNAManifest;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export interface KDNAValidationReport {
|
|
442
|
+
ok: boolean;
|
|
443
|
+
errors: string[];
|
|
444
|
+
warnings: string[];
|
|
445
|
+
asset: KdnaAssetVerifyResult;
|
|
446
|
+
lint: LintResult;
|
|
447
|
+
schema: ValidationResult;
|
|
448
|
+
cross_file: ValidationResult;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export interface KDNAMatchResult extends KDNAInspectResult {
|
|
452
|
+
score: number;
|
|
453
|
+
matched: string[];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export interface KDNAComposeResult {
|
|
457
|
+
domains: Array<LoadedDomain & { id?: string; name?: string; manifest?: KDNAManifest }>;
|
|
458
|
+
activeDomains: Array<LoadedDomain & { id?: string; name?: string; manifest?: KDNAManifest }>;
|
|
459
|
+
selected: Array<{ id: string; name?: string; role?: string; reason: string }>;
|
|
460
|
+
excluded: Array<{ id: string; name?: string; role?: string; reason: string }>;
|
|
461
|
+
conflicts: Array<{ type: string; domains: string[]; description: string }>;
|
|
462
|
+
context: string;
|
|
463
|
+
attributionMap: Record<string, any>;
|
|
464
|
+
trace: Record<string, any>;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function openKDNA(input: KDNAAssetInput): Promise<KdnaAsset>;
|
|
468
|
+
export function openKDNASync(input: KDNAAssetInput): KdnaAsset;
|
|
469
|
+
export function inspectKDNA(input: KDNAAssetInput, options?: { verify?: boolean } & KdnaDecryptOptions): Promise<KDNAInspectResult>;
|
|
470
|
+
export function inspectKDNASync(input: KDNAAssetInput, options?: { verify?: boolean } & KdnaDecryptOptions): KDNAInspectResult;
|
|
471
|
+
export function loadKDNA(input: KDNAAssetInput, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; input?: string; context?: boolean } & KdnaDecryptOptions): Promise<KdnaAssetIndexProfile | KdnaAssetLoadProfile>;
|
|
472
|
+
export function loadKDNASync(input: KDNAAssetInput, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; input?: string; context?: boolean } & KdnaDecryptOptions): KdnaAssetIndexProfile | KdnaAssetLoadProfile;
|
|
473
|
+
export function validateKDNA(input: KDNAAssetInput, options?: { schemas?: Record<string, any>; requireSignature?: boolean; requireDecryption?: boolean } & KdnaDecryptOptions): Promise<KDNAValidationReport>;
|
|
474
|
+
export function validateKDNASync(input: KDNAAssetInput, options?: { schemas?: Record<string, any>; requireSignature?: boolean; requireDecryption?: boolean } & KdnaDecryptOptions): KDNAValidationReport;
|
|
475
|
+
export function renderForAgent(input: KDNAAssetInput, options?: { profile?: 'compact' | 'scenario' | 'full' | string; input?: string } & KdnaDecryptOptions): Promise<string>;
|
|
476
|
+
export function renderForAgentSync(input: KDNAAssetInput, options?: { profile?: 'compact' | 'scenario' | 'full' | string; input?: string } & KdnaDecryptOptions): string;
|
|
477
|
+
export function verifyAsset(input: KDNAAssetInput, options?: { asset_digest?: string; content_digest?: string; requireSignature?: boolean; requireDecryption?: boolean } & KdnaDecryptOptions): Promise<KdnaAssetVerifyResult>;
|
|
478
|
+
export function verifyAssetSync(input: KDNAAssetInput, options?: { asset_digest?: string; content_digest?: string; requireSignature?: boolean; requireDecryption?: boolean } & KdnaDecryptOptions): KdnaAssetVerifyResult;
|
|
479
|
+
export function verifyDigest(input: KDNAAssetInput, expectedDigest: string, options?: KdnaDecryptOptions): Promise<KdnaAssetVerifyResult>;
|
|
480
|
+
export function verifyDigestSync(input: KDNAAssetInput, expectedDigest: string, options?: KdnaDecryptOptions): KdnaAssetVerifyResult;
|
|
481
|
+
export function verifySignature(input: KDNAAssetInput, options?: KdnaDecryptOptions): Promise<KdnaAssetVerifyResult>;
|
|
482
|
+
export function verifySignatureSync(input: KDNAAssetInput, options?: KdnaDecryptOptions): KdnaAssetVerifyResult;
|
|
483
|
+
export function matchDomain(input: string, candidates: Array<KDNAAssetInput | KDNAInspectResult>, options?: KdnaDecryptOptions): Promise<KDNAMatchResult[]>;
|
|
484
|
+
export function matchDomainSync(input: string, candidates: Array<KDNAAssetInput | KDNAInspectResult>, options?: KdnaDecryptOptions): KDNAMatchResult[];
|
|
485
|
+
export function composeKDNA(inputs: KDNAAssetInput[], options?: { input?: string; profile?: 'compact' | 'scenario' | 'full' | string; separator?: string } & KdnaDecryptOptions): Promise<KDNAComposeResult>;
|
|
486
|
+
|
|
416
487
|
// Lint
|
|
417
488
|
export function lintDomain(dataMap: KDNAFileDataMap): LintResult;
|
|
418
489
|
|