@aikdna/kdna-core 0.11.0 → 0.12.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 +63 -154
- package/package.json +1 -1
- package/schema/load-plan.schema.json +119 -0
- package/src/types.d.ts +79 -0
- package/src/v1/index.js +396 -5
- package/src/v1/index.mjs +19 -0
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @aikdna/kdna-core
|
|
2
2
|
|
|
3
|
-
Core library for
|
|
3
|
+
Core library for KDNA judgment assets.
|
|
4
|
+
|
|
5
|
+
KDNA Core v1 defines the official `.kdna` source/container contract used by
|
|
6
|
+
the CLI, Studio export, skills, MCP integrations, and downstream agent
|
|
7
|
+
runtimes.
|
|
4
8
|
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
@@ -8,187 +12,92 @@ Core library for loading, validating, linting, rendering, composing, and directl
|
|
|
8
12
|
npm install @aikdna/kdna-core
|
|
9
13
|
```
|
|
10
14
|
|
|
11
|
-
|
|
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.
|
|
15
|
+
If you need full JSON Schema validation through Ajv, also install:
|
|
16
16
|
|
|
17
|
-
```
|
|
18
|
-
|
|
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
|
-
});
|
|
17
|
+
```bash
|
|
18
|
+
npm install ajv ajv-formats
|
|
46
19
|
```
|
|
47
20
|
|
|
48
|
-
|
|
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. |
|
|
21
|
+
## KDNA Core v1 API
|
|
62
22
|
|
|
63
|
-
|
|
23
|
+
Use the `./v1` entrypoint for current KDNA Core v1 tooling:
|
|
64
24
|
|
|
65
25
|
```js
|
|
66
26
|
const {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const lintResult = lintDomain(dataMap);
|
|
80
|
-
const schemaResult = validateDomainSchema(dataMap, schemas);
|
|
81
|
-
const crossResult = validateCrossFile(dataMap);
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## API
|
|
85
|
-
|
|
86
|
-
### `createKdnaAssetReader()`
|
|
87
|
-
|
|
88
|
-
Direct `.kdna` container reader. The reader opens ZIP-backed `.kdna` assets without persistent extraction and exposes:
|
|
89
|
-
|
|
90
|
-
- `open(pathOrBytes)`
|
|
91
|
-
- `listEntries(asset)`
|
|
92
|
-
- `readEntry(asset, entryName)`
|
|
93
|
-
- `readJson(asset, entryName)`
|
|
94
|
-
- `readManifest(asset)`
|
|
95
|
-
- `readDataMap(asset)`
|
|
96
|
-
- `contentDigest(asset)`
|
|
97
|
-
- `verify(asset, { asset_digest?, content_digest?, requireSignature? })`
|
|
98
|
-
- `loadProfile(asset, "index" | "compact" | "scenario" | "full", options?)`
|
|
99
|
-
|
|
100
|
-
Example:
|
|
101
|
-
|
|
102
|
-
```js
|
|
103
|
-
const { createKdnaAssetReader } = require('@aikdna/kdna-core');
|
|
104
|
-
|
|
105
|
-
const reader = createKdnaAssetReader();
|
|
106
|
-
const asset = await reader.open('./writing.kdna');
|
|
107
|
-
const manifest = await reader.readManifest(asset);
|
|
108
|
-
const trust = await reader.verify(asset, { requireSignature: true });
|
|
109
|
-
const loaded = await reader.loadProfile(asset, 'compact');
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
The asset reader treats extraction caches as implementation details. The `.kdna` file remains the identity, install, verification, and loading object.
|
|
113
|
-
|
|
114
|
-
Licensed assets can list encrypted JSON entries in `kdna.json`:
|
|
115
|
-
|
|
116
|
-
```json
|
|
117
|
-
{
|
|
118
|
-
"access": "licensed",
|
|
119
|
-
"encryption": {
|
|
120
|
-
"profile": "kdna-licensed-entry-v1",
|
|
121
|
-
"encrypted_entries": ["KDNA_Core.json", "KDNA_Patterns.json"]
|
|
122
|
-
}
|
|
27
|
+
inspect,
|
|
28
|
+
validate,
|
|
29
|
+
pack,
|
|
30
|
+
unpack,
|
|
31
|
+
loadV1,
|
|
32
|
+
buildChecksumsV1
|
|
33
|
+
} = require('@aikdna/kdna-core/v1');
|
|
34
|
+
|
|
35
|
+
const validation = validate('./asset.kdna');
|
|
36
|
+
if (!validation.overall_valid) {
|
|
37
|
+
throw new Error(validation.problems.join('\n'));
|
|
123
38
|
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
The reader never writes decrypted entries to disk. Callers provide an in-memory
|
|
127
|
-
`decryptEntry` hook when they have already validated license activation:
|
|
128
|
-
|
|
129
|
-
```js
|
|
130
|
-
const { createLicensedDecryptEntry } = require('@aikdna/kdna-core');
|
|
131
39
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
40
|
+
const compact = loadV1('./asset.kdna', {
|
|
41
|
+
profile: 'compact',
|
|
42
|
+
as: 'prompt'
|
|
135
43
|
});
|
|
136
|
-
|
|
137
|
-
const loaded = await reader.loadProfile(asset, 'compact', { decryptEntry });
|
|
138
44
|
```
|
|
139
45
|
|
|
140
|
-
|
|
141
|
-
from the license key plus machine fingerprint using `scrypt-sha256`. This is a
|
|
142
|
-
runtime primitive, not a license activation system; callers must validate license
|
|
143
|
-
status before passing a decrypt hook to the reader.
|
|
144
|
-
|
|
145
|
-
### `lintDomain(dataMap)`
|
|
146
|
-
Structural linting — checks required files, field presence, unique IDs, yes/no answerable self-checks, cross-file references, and flags potentially vague axioms.
|
|
147
|
-
|
|
148
|
-
Returns `{ errors: string[], warnings: string[] }`.
|
|
149
|
-
|
|
150
|
-
### `validateDomainSchema(dataMap, schemaMap)`
|
|
151
|
-
JSON Schema validation against published schemas (KDNA_Core, KDNA_Patterns, KDNA_Scenarios, KDNA_Cases, KDNA_Reasoning, KDNA_Evolution).
|
|
46
|
+
## Supported Runtime Flow
|
|
152
47
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
Renders domain files into a structured context block using a standard template. The rendered context preserves the domain's structure as distinct, named sections suitable for agent system prompts.
|
|
48
|
+
```text
|
|
49
|
+
v1 source directory
|
|
50
|
+
→ buildChecksumsV1
|
|
51
|
+
→ pack
|
|
52
|
+
→ validate
|
|
53
|
+
→ loadV1
|
|
54
|
+
→ agent/runtime context
|
|
55
|
+
```
|
|
162
56
|
|
|
163
|
-
##
|
|
57
|
+
## v1 Source Directory
|
|
164
58
|
|
|
165
|
-
|
|
59
|
+
A v1 source directory contains:
|
|
166
60
|
|
|
167
|
-
|
|
61
|
+
- `mimetype`
|
|
62
|
+
- `kdna.json`
|
|
63
|
+
- `payload.kdnab`
|
|
64
|
+
- `checksums.json`
|
|
168
65
|
|
|
169
|
-
|
|
66
|
+
## v1 Container
|
|
170
67
|
|
|
171
|
-
|
|
68
|
+
A v1 `.kdna` container is a zip package of the same files. `validate()` checks:
|
|
172
69
|
|
|
173
|
-
-
|
|
70
|
+
- format
|
|
71
|
+
- manifest schema
|
|
72
|
+
- payload parseability
|
|
73
|
+
- checksum consistency
|
|
74
|
+
- load-profile contract
|
|
174
75
|
|
|
175
|
-
|
|
76
|
+
## Load Profiles
|
|
176
77
|
|
|
177
|
-
|
|
78
|
+
`loadV1()` supports:
|
|
178
79
|
|
|
179
|
-
-
|
|
80
|
+
- `index`
|
|
81
|
+
- `compact`
|
|
82
|
+
- `scenario`
|
|
83
|
+
- `full`
|
|
180
84
|
|
|
181
|
-
|
|
85
|
+
Output formats:
|
|
182
86
|
|
|
183
|
-
-
|
|
87
|
+
- `json`
|
|
88
|
+
- `prompt`
|
|
184
89
|
|
|
185
|
-
|
|
90
|
+
## Boundary
|
|
186
91
|
|
|
187
|
-
|
|
92
|
+
KDNA Core v1 is content-neutral. It does not recommend assets, assign quality
|
|
93
|
+
badges, run a marketplace, or define a public registry. Future signature,
|
|
94
|
+
encryption, licensing, and entitlement work is gated outside the current v1
|
|
95
|
+
baseline.
|
|
188
96
|
|
|
189
|
-
|
|
97
|
+
## Legacy API
|
|
190
98
|
|
|
191
|
-
|
|
99
|
+
The package root still exports compatibility APIs for older KDNA paths. New
|
|
100
|
+
tooling should prefer `@aikdna/kdna-core/v1`.
|
|
192
101
|
|
|
193
102
|
## License
|
|
194
103
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "KDNA core library — load, validate, lint, and render KDNA domain judgment assets. Supports KDNA Container format (payload.kdnab via CBOR).",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/aikdna/kdna/schema/load-plan.schema.json",
|
|
4
|
+
"title": "KDNA LoadPlan v1",
|
|
5
|
+
"description": "Machine-readable pre-load authorization and runtime planning result for KDNA Core v1 assets.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"kdna_version",
|
|
9
|
+
"asset",
|
|
10
|
+
"access",
|
|
11
|
+
"access_alias",
|
|
12
|
+
"entitlement_profile",
|
|
13
|
+
"state",
|
|
14
|
+
"required_action",
|
|
15
|
+
"can_load_now",
|
|
16
|
+
"projection_policy",
|
|
17
|
+
"checks",
|
|
18
|
+
"issues",
|
|
19
|
+
"source"
|
|
20
|
+
],
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"kdna_version": { "type": ["string", "null"] },
|
|
24
|
+
"asset": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"required": ["asset_id", "asset_uid", "title", "version", "judgment_version"],
|
|
27
|
+
"additionalProperties": false,
|
|
28
|
+
"properties": {
|
|
29
|
+
"asset_id": { "type": ["string", "null"] },
|
|
30
|
+
"asset_uid": { "type": ["string", "null"] },
|
|
31
|
+
"title": { "type": ["string", "null"] },
|
|
32
|
+
"version": { "type": ["string", "null"] },
|
|
33
|
+
"judgment_version": { "type": ["string", "null"] }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"access": {
|
|
37
|
+
"type": ["string", "null"],
|
|
38
|
+
"enum": ["public", "licensed", "remote", null]
|
|
39
|
+
},
|
|
40
|
+
"access_alias": { "type": ["string", "null"] },
|
|
41
|
+
"entitlement_profile": { "type": ["string", "null"] },
|
|
42
|
+
"state": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"enum": [
|
|
45
|
+
"ready",
|
|
46
|
+
"needs_password",
|
|
47
|
+
"needs_license",
|
|
48
|
+
"needs_account",
|
|
49
|
+
"needs_org_auth",
|
|
50
|
+
"needs_runtime",
|
|
51
|
+
"offline_grace",
|
|
52
|
+
"expired",
|
|
53
|
+
"revoked",
|
|
54
|
+
"invalid"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"required_action": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"enum": [
|
|
60
|
+
"none",
|
|
61
|
+
"load",
|
|
62
|
+
"enter_password",
|
|
63
|
+
"install_receipt",
|
|
64
|
+
"sign_in_or_activate",
|
|
65
|
+
"sync",
|
|
66
|
+
"connect_runtime",
|
|
67
|
+
"migrate_legacy",
|
|
68
|
+
"block"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"can_load_now": { "type": "boolean" },
|
|
72
|
+
"projection_policy": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"enum": ["minimal", "remote", "none"]
|
|
75
|
+
},
|
|
76
|
+
"checks": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"required": [
|
|
79
|
+
"format_valid",
|
|
80
|
+
"schema_valid",
|
|
81
|
+
"payload_valid",
|
|
82
|
+
"checksums_valid",
|
|
83
|
+
"load_contract_valid",
|
|
84
|
+
"overall_valid"
|
|
85
|
+
],
|
|
86
|
+
"additionalProperties": false,
|
|
87
|
+
"properties": {
|
|
88
|
+
"format_valid": { "type": "boolean" },
|
|
89
|
+
"schema_valid": { "type": "boolean" },
|
|
90
|
+
"payload_valid": { "type": "boolean" },
|
|
91
|
+
"checksums_valid": { "type": "boolean" },
|
|
92
|
+
"load_contract_valid": { "type": "boolean" },
|
|
93
|
+
"overall_valid": { "type": "boolean" }
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"issues": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"items": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"required": ["code", "severity", "message"],
|
|
101
|
+
"additionalProperties": false,
|
|
102
|
+
"properties": {
|
|
103
|
+
"code": { "type": "string", "pattern": "^KDNA_[A-Z0-9_]+$" },
|
|
104
|
+
"severity": { "type": "string", "enum": ["info", "warning", "blocking"] },
|
|
105
|
+
"message": { "type": "string" }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"source": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"required": ["kind", "path"],
|
|
112
|
+
"additionalProperties": false,
|
|
113
|
+
"properties": {
|
|
114
|
+
"kind": { "type": ["string", "null"], "enum": ["dir", "file", null] },
|
|
115
|
+
"path": { "type": "string" }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
package/src/types.d.ts
CHANGED
|
@@ -484,6 +484,85 @@ export function matchDomain(input: string, candidates: Array<KDNAAssetInput | KD
|
|
|
484
484
|
export function matchDomainSync(input: string, candidates: Array<KDNAAssetInput | KDNAInspectResult>, options?: KdnaDecryptOptions): KDNAMatchResult[];
|
|
485
485
|
export function composeKDNA(inputs: KDNAAssetInput[], options?: { input?: string; profile?: 'compact' | 'scenario' | 'full' | string; separator?: string } & KdnaDecryptOptions): Promise<KDNAComposeResult>;
|
|
486
486
|
|
|
487
|
+
// KDNA Core v1 — source directory / container API
|
|
488
|
+
export const MIMETYPE: string;
|
|
489
|
+
export const MIMETYPE_V1: string;
|
|
490
|
+
export const MIMETYPE_V2: string;
|
|
491
|
+
export const V1_REQUIRED_DIR_ENTRIES: string[];
|
|
492
|
+
|
|
493
|
+
export interface KDNAV1ChecksumEntry {
|
|
494
|
+
algorithm: 'sha256';
|
|
495
|
+
value: string;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export interface KDNAV1Checksums {
|
|
499
|
+
algorithm: 'sha256';
|
|
500
|
+
manifest_digest: string;
|
|
501
|
+
payload_digest: string;
|
|
502
|
+
asset_digest: string;
|
|
503
|
+
entries: Record<string, KDNAV1ChecksumEntry>;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function isV1SourceDir(inputPath: string): boolean;
|
|
507
|
+
export function detectContainerFormat(inputPath: string): 'v1' | 'v2' | null;
|
|
508
|
+
export function inspect(inputPath: string, options?: Record<string, any>): Record<string, any>;
|
|
509
|
+
export function validate(inputPath: string, options?: Record<string, any>): Record<string, any>;
|
|
510
|
+
export interface KDNAV1LoadPlanIssue {
|
|
511
|
+
code: string;
|
|
512
|
+
severity: 'info' | 'warning' | 'blocking' | string;
|
|
513
|
+
message: string;
|
|
514
|
+
}
|
|
515
|
+
export interface KDNAV1LoadPlan {
|
|
516
|
+
kdna_version: string | null;
|
|
517
|
+
asset: {
|
|
518
|
+
asset_id: string | null;
|
|
519
|
+
asset_uid: string | null;
|
|
520
|
+
title: string | null;
|
|
521
|
+
version: string | null;
|
|
522
|
+
judgment_version: string | null;
|
|
523
|
+
};
|
|
524
|
+
access: 'public' | 'licensed' | 'remote' | string | null;
|
|
525
|
+
access_alias: string | null;
|
|
526
|
+
entitlement_profile: string | null;
|
|
527
|
+
state:
|
|
528
|
+
| 'ready'
|
|
529
|
+
| 'needs_password'
|
|
530
|
+
| 'needs_license'
|
|
531
|
+
| 'needs_account'
|
|
532
|
+
| 'needs_org_auth'
|
|
533
|
+
| 'needs_runtime'
|
|
534
|
+
| 'offline_grace'
|
|
535
|
+
| 'expired'
|
|
536
|
+
| 'revoked'
|
|
537
|
+
| 'invalid'
|
|
538
|
+
| string;
|
|
539
|
+
required_action:
|
|
540
|
+
| 'none'
|
|
541
|
+
| 'load'
|
|
542
|
+
| 'enter_password'
|
|
543
|
+
| 'install_receipt'
|
|
544
|
+
| 'sign_in_or_activate'
|
|
545
|
+
| 'sync'
|
|
546
|
+
| 'connect_runtime'
|
|
547
|
+
| 'migrate_legacy'
|
|
548
|
+
| 'block'
|
|
549
|
+
| string;
|
|
550
|
+
can_load_now: boolean;
|
|
551
|
+
projection_policy: 'minimal' | 'remote' | 'none' | string;
|
|
552
|
+
checks: Record<string, boolean>;
|
|
553
|
+
issues: KDNAV1LoadPlanIssue[];
|
|
554
|
+
source: {
|
|
555
|
+
kind: 'dir' | 'file' | string | null;
|
|
556
|
+
path: string;
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
export function planLoad(inputPath: string, options?: { password?: string; hasPassword?: boolean; entitlement?: Record<string, any> }): KDNAV1LoadPlan;
|
|
560
|
+
export function buildChecksumsV1(sourceDir: string): KDNAV1Checksums;
|
|
561
|
+
export function pack(sourceDir: string, outputPath: string): void;
|
|
562
|
+
export function unpack(inputPath: string, outputDir: string): void;
|
|
563
|
+
export function loadV1(inputPath: string, options?: { profile?: 'index' | 'compact' | 'scenario' | 'full' | string; as?: 'json' | 'prompt' | string }): Record<string, any>;
|
|
564
|
+
export const FORBIDDEN_OUTPUT_TERMS: readonly string[];
|
|
565
|
+
|
|
487
566
|
// Lint
|
|
488
567
|
export function lintDomain(dataMap: KDNAFileDataMap): LintResult;
|
|
489
568
|
|
package/src/v1/index.js
CHANGED
|
@@ -580,6 +580,42 @@ function finalizeValidate(result, problems) {
|
|
|
580
580
|
return result;
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
+
function digestEntry(sourceDir, entry) {
|
|
584
|
+
const entryPath = path.join(sourceDir, entry);
|
|
585
|
+
if (!fs.existsSync(entryPath)) {
|
|
586
|
+
throw new Error(`cannot build checksums: missing required entry ${entry}`);
|
|
587
|
+
}
|
|
588
|
+
const bytes = fs.readFileSync(entryPath);
|
|
589
|
+
return {
|
|
590
|
+
algorithm: 'sha256',
|
|
591
|
+
value: crypto.createHash('sha256').update(bytes).digest('hex'),
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function buildChecksumsV1(sourceDir) {
|
|
596
|
+
const absSrc = path.resolve(sourceDir);
|
|
597
|
+
if (!fs.existsSync(absSrc) || !fs.statSync(absSrc).isDirectory()) {
|
|
598
|
+
throw new Error(`not a directory: ${absSrc}`);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const entries = {
|
|
602
|
+
'kdna.json': digestEntry(absSrc, 'kdna.json'),
|
|
603
|
+
'payload.kdnab': digestEntry(absSrc, 'payload.kdnab'),
|
|
604
|
+
};
|
|
605
|
+
const combined = Object.keys(entries)
|
|
606
|
+
.sort()
|
|
607
|
+
.map((name) => `${name}:${entries[name].value}`)
|
|
608
|
+
.join('\n');
|
|
609
|
+
|
|
610
|
+
return {
|
|
611
|
+
algorithm: 'sha256',
|
|
612
|
+
manifest_digest: `sha256:${entries['kdna.json'].value}`,
|
|
613
|
+
payload_digest: `sha256:${entries['payload.kdnab'].value}`,
|
|
614
|
+
asset_digest: `sha256:${crypto.createHash('sha256').update(combined).digest('hex')}`,
|
|
615
|
+
entries,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
583
619
|
// ─── pack ──────────────────────────────────────────────────────────────
|
|
584
620
|
|
|
585
621
|
/**
|
|
@@ -714,6 +750,330 @@ function validate(inputPath, opts = {}) {
|
|
|
714
750
|
return runValidate(v1);
|
|
715
751
|
}
|
|
716
752
|
|
|
753
|
+
function normalizeAccess(access) {
|
|
754
|
+
const value = access || 'public';
|
|
755
|
+
if (value === 'open') return { access: 'public', alias: value };
|
|
756
|
+
if (value === 'protected') return { access: 'licensed', alias: value };
|
|
757
|
+
if (value === 'runtime') return { access: 'remote', alias: value };
|
|
758
|
+
return { access: value, alias: null };
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function inferEntitlementProfile(manifest) {
|
|
762
|
+
if (manifest.entitlement && typeof manifest.entitlement.profile === 'string') {
|
|
763
|
+
return manifest.entitlement.profile;
|
|
764
|
+
}
|
|
765
|
+
if (manifest.encryption && manifest.encryption.profile === 'kdna-password-protected-v1') {
|
|
766
|
+
return 'password';
|
|
767
|
+
}
|
|
768
|
+
if (manifest.access === 'protected') return 'password';
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function buildLoadPlanIssue(code, severity, message) {
|
|
773
|
+
return { code, severity, message };
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function validationProblemCode(problem) {
|
|
777
|
+
if (/checksums?:/i.test(problem)) return 'KDNA_INTEGRITY_DIGEST_FAILED';
|
|
778
|
+
if (/signature/i.test(problem)) return 'KDNA_INTEGRITY_SIGNATURE_FAILED';
|
|
779
|
+
if (/payload:/i.test(problem)) return 'KDNA_FORMAT_INVALID';
|
|
780
|
+
if (/manifest:/i.test(problem)) return 'KDNA_FORMAT_INVALID';
|
|
781
|
+
if (/load_contract:/i.test(problem)) return 'KDNA_FORMAT_INVALID';
|
|
782
|
+
return 'KDNA_FORMAT_INVALID';
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function finalizeLoadPlan(plan) {
|
|
786
|
+
assertNoForbiddenTerms(plan);
|
|
787
|
+
return plan;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function baseLoadPlan(inputPath, v1, validation) {
|
|
791
|
+
const manifest = v1.manifest;
|
|
792
|
+
const accessInfo = normalizeAccess(manifest.access);
|
|
793
|
+
const entitlementProfile = inferEntitlementProfile(manifest);
|
|
794
|
+
const asset = {
|
|
795
|
+
asset_id: manifest.asset_id || null,
|
|
796
|
+
asset_uid: manifest.asset_uid || null,
|
|
797
|
+
title: manifest.title || null,
|
|
798
|
+
version: manifest.version || null,
|
|
799
|
+
judgment_version: manifest.judgment_version || null,
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
const plan = {
|
|
803
|
+
kdna_version: manifest.kdna_version || null,
|
|
804
|
+
asset,
|
|
805
|
+
access: accessInfo.access,
|
|
806
|
+
access_alias: accessInfo.alias,
|
|
807
|
+
entitlement_profile: entitlementProfile,
|
|
808
|
+
state: 'invalid',
|
|
809
|
+
required_action: 'block',
|
|
810
|
+
can_load_now: false,
|
|
811
|
+
projection_policy: 'none',
|
|
812
|
+
checks: {
|
|
813
|
+
format_valid: validation.format_valid,
|
|
814
|
+
schema_valid: validation.schema_valid,
|
|
815
|
+
payload_valid: validation.payload_valid,
|
|
816
|
+
checksums_valid: validation.checksums_valid,
|
|
817
|
+
load_contract_valid: validation.load_contract_valid,
|
|
818
|
+
overall_valid: validation.overall_valid,
|
|
819
|
+
},
|
|
820
|
+
issues: [],
|
|
821
|
+
source: {
|
|
822
|
+
kind: v1.kind,
|
|
823
|
+
path: path.resolve(inputPath),
|
|
824
|
+
},
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
if (accessInfo.alias) {
|
|
828
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
829
|
+
'KDNA_AUTH_ACCESS_ALIAS',
|
|
830
|
+
'info',
|
|
831
|
+
`Access value "${accessInfo.alias}" is treated as "${accessInfo.access}".`,
|
|
832
|
+
));
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
return plan;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Plan a KDNA v1 runtime load without decrypting or emitting judgment content.
|
|
840
|
+
* Product consumers such as Chat should render authorization UI from this
|
|
841
|
+
* result instead of parsing manifest fields directly.
|
|
842
|
+
*/
|
|
843
|
+
function planLoad(inputPath, opts = {}) {
|
|
844
|
+
let v1;
|
|
845
|
+
try {
|
|
846
|
+
v1 = readV1Layout(path.resolve(inputPath));
|
|
847
|
+
} catch (e) {
|
|
848
|
+
return finalizeLoadPlan({
|
|
849
|
+
kdna_version: null,
|
|
850
|
+
asset: {
|
|
851
|
+
asset_id: null,
|
|
852
|
+
asset_uid: null,
|
|
853
|
+
title: null,
|
|
854
|
+
version: null,
|
|
855
|
+
judgment_version: null,
|
|
856
|
+
},
|
|
857
|
+
access: null,
|
|
858
|
+
access_alias: null,
|
|
859
|
+
entitlement_profile: null,
|
|
860
|
+
state: 'invalid',
|
|
861
|
+
required_action: 'block',
|
|
862
|
+
can_load_now: false,
|
|
863
|
+
projection_policy: 'none',
|
|
864
|
+
checks: {
|
|
865
|
+
format_valid: false,
|
|
866
|
+
schema_valid: false,
|
|
867
|
+
payload_valid: false,
|
|
868
|
+
checksums_valid: false,
|
|
869
|
+
load_contract_valid: false,
|
|
870
|
+
overall_valid: false,
|
|
871
|
+
},
|
|
872
|
+
issues: [
|
|
873
|
+
buildLoadPlanIssue('KDNA_FORMAT_INVALID', 'blocking', e.message),
|
|
874
|
+
],
|
|
875
|
+
source: {
|
|
876
|
+
kind: null,
|
|
877
|
+
path: path.resolve(inputPath),
|
|
878
|
+
},
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const validation = runValidate(v1);
|
|
883
|
+
const plan = baseLoadPlan(inputPath, v1, validation);
|
|
884
|
+
|
|
885
|
+
if (!validation.overall_valid) {
|
|
886
|
+
plan.state = 'invalid';
|
|
887
|
+
plan.required_action = 'block';
|
|
888
|
+
plan.can_load_now = false;
|
|
889
|
+
plan.projection_policy = 'none';
|
|
890
|
+
for (const problem of validation.problems) {
|
|
891
|
+
plan.issues.push(buildLoadPlanIssue(validationProblemCode(problem), 'blocking', problem));
|
|
892
|
+
}
|
|
893
|
+
return finalizeLoadPlan(plan);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const manifest = v1.manifest;
|
|
897
|
+
const payloadDeclaredEncrypted =
|
|
898
|
+
manifest.payload && manifest.payload.encrypted === true;
|
|
899
|
+
const encryptedEntries = Array.isArray(manifest.encryption && manifest.encryption.encrypted_entries)
|
|
900
|
+
? manifest.encryption.encrypted_entries
|
|
901
|
+
: [];
|
|
902
|
+
const hasEncryptedPayload = payloadDeclaredEncrypted || encryptedEntries.length > 0;
|
|
903
|
+
|
|
904
|
+
if (!['public', 'licensed', 'remote'].includes(plan.access)) {
|
|
905
|
+
const unknownAccess = plan.access;
|
|
906
|
+
plan.access = null;
|
|
907
|
+
plan.state = 'invalid';
|
|
908
|
+
plan.required_action = 'block';
|
|
909
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
910
|
+
'KDNA_ACCESS_MODE_UNKNOWN',
|
|
911
|
+
'blocking',
|
|
912
|
+
`Unknown access value "${unknownAccess}".`,
|
|
913
|
+
));
|
|
914
|
+
return finalizeLoadPlan(plan);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (plan.access === 'remote') {
|
|
918
|
+
plan.state = 'needs_runtime';
|
|
919
|
+
plan.required_action = 'connect_runtime';
|
|
920
|
+
plan.can_load_now = false;
|
|
921
|
+
plan.projection_policy = 'remote';
|
|
922
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
923
|
+
'KDNA_AUTH_REMOTE_RUNTIME_REQUIRED',
|
|
924
|
+
'blocking',
|
|
925
|
+
'Remote assets require a runtime projection endpoint.',
|
|
926
|
+
));
|
|
927
|
+
return finalizeLoadPlan(plan);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (plan.access === 'licensed') {
|
|
931
|
+
const knownProfiles = new Set([
|
|
932
|
+
'password',
|
|
933
|
+
'local_receipt',
|
|
934
|
+
'account',
|
|
935
|
+
'org',
|
|
936
|
+
'purchase_receipt',
|
|
937
|
+
'device_bound',
|
|
938
|
+
]);
|
|
939
|
+
if (plan.entitlement_profile && !knownProfiles.has(plan.entitlement_profile)) {
|
|
940
|
+
plan.state = 'invalid';
|
|
941
|
+
plan.required_action = 'block';
|
|
942
|
+
plan.can_load_now = false;
|
|
943
|
+
plan.projection_policy = 'none';
|
|
944
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
945
|
+
'KDNA_ENTITLEMENT_PROFILE_UNKNOWN',
|
|
946
|
+
'blocking',
|
|
947
|
+
`Unknown entitlement profile "${plan.entitlement_profile}".`,
|
|
948
|
+
));
|
|
949
|
+
return finalizeLoadPlan(plan);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (plan.entitlement_profile === 'password') {
|
|
953
|
+
if (opts.password || opts.hasPassword === true) {
|
|
954
|
+
plan.state = 'ready';
|
|
955
|
+
plan.required_action = 'load';
|
|
956
|
+
plan.can_load_now = true;
|
|
957
|
+
plan.projection_policy = 'minimal';
|
|
958
|
+
} else {
|
|
959
|
+
plan.state = 'needs_password';
|
|
960
|
+
plan.required_action = 'enter_password';
|
|
961
|
+
plan.can_load_now = false;
|
|
962
|
+
plan.projection_policy = 'none';
|
|
963
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
964
|
+
'KDNA_AUTH_PASSWORD_REQUIRED',
|
|
965
|
+
'blocking',
|
|
966
|
+
'A password is required before this asset can be loaded.',
|
|
967
|
+
));
|
|
968
|
+
}
|
|
969
|
+
return finalizeLoadPlan(plan);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
if (plan.entitlement_profile === 'account') {
|
|
973
|
+
plan.state = 'needs_account';
|
|
974
|
+
plan.required_action = 'sign_in_or_activate';
|
|
975
|
+
plan.can_load_now = false;
|
|
976
|
+
plan.projection_policy = 'none';
|
|
977
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
978
|
+
'KDNA_AUTH_ACCOUNT_REQUIRED',
|
|
979
|
+
'blocking',
|
|
980
|
+
'Account authorization is required before this asset can be loaded.',
|
|
981
|
+
));
|
|
982
|
+
return finalizeLoadPlan(plan);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if (plan.entitlement_profile === 'org') {
|
|
986
|
+
plan.state = 'needs_org_auth';
|
|
987
|
+
plan.required_action = 'sign_in_or_activate';
|
|
988
|
+
plan.can_load_now = false;
|
|
989
|
+
plan.projection_policy = 'none';
|
|
990
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
991
|
+
'KDNA_AUTH_ORG_REQUIRED',
|
|
992
|
+
'blocking',
|
|
993
|
+
'Organization authorization is required before this asset can be loaded.',
|
|
994
|
+
));
|
|
995
|
+
return finalizeLoadPlan(plan);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
if (opts.entitlement && opts.entitlement.status === 'active') {
|
|
999
|
+
plan.state = 'ready';
|
|
1000
|
+
plan.required_action = 'load';
|
|
1001
|
+
plan.can_load_now = true;
|
|
1002
|
+
plan.projection_policy = 'minimal';
|
|
1003
|
+
return finalizeLoadPlan(plan);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (opts.entitlement && opts.entitlement.status === 'expired') {
|
|
1007
|
+
plan.state = 'expired';
|
|
1008
|
+
plan.required_action = 'sync';
|
|
1009
|
+
plan.can_load_now = false;
|
|
1010
|
+
plan.projection_policy = 'none';
|
|
1011
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1012
|
+
'KDNA_AUTH_EXPIRED',
|
|
1013
|
+
'blocking',
|
|
1014
|
+
'The entitlement is expired.',
|
|
1015
|
+
));
|
|
1016
|
+
return finalizeLoadPlan(plan);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
if (opts.entitlement && opts.entitlement.status === 'revoked') {
|
|
1020
|
+
plan.state = 'revoked';
|
|
1021
|
+
plan.required_action = 'block';
|
|
1022
|
+
plan.can_load_now = false;
|
|
1023
|
+
plan.projection_policy = 'none';
|
|
1024
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1025
|
+
'KDNA_AUTH_REVOKED',
|
|
1026
|
+
'blocking',
|
|
1027
|
+
'The entitlement has been revoked.',
|
|
1028
|
+
));
|
|
1029
|
+
return finalizeLoadPlan(plan);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
if (opts.entitlement && opts.entitlement.status === 'offline_grace') {
|
|
1033
|
+
plan.state = 'offline_grace';
|
|
1034
|
+
plan.required_action = 'sync';
|
|
1035
|
+
plan.can_load_now = true;
|
|
1036
|
+
plan.projection_policy = 'minimal';
|
|
1037
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1038
|
+
'KDNA_AUTH_OFFLINE_GRACE_ACTIVE',
|
|
1039
|
+
'warning',
|
|
1040
|
+
'The entitlement can load during offline grace but must sync before grace expires.',
|
|
1041
|
+
));
|
|
1042
|
+
return finalizeLoadPlan(plan);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
plan.state = 'needs_license';
|
|
1046
|
+
plan.required_action = plan.entitlement_profile === 'local_receipt' ? 'install_receipt' : 'sign_in_or_activate';
|
|
1047
|
+
plan.can_load_now = false;
|
|
1048
|
+
plan.projection_policy = 'none';
|
|
1049
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1050
|
+
'KDNA_AUTH_ENTITLEMENT_REQUIRED',
|
|
1051
|
+
'blocking',
|
|
1052
|
+
'A valid entitlement is required before this asset can be loaded.',
|
|
1053
|
+
));
|
|
1054
|
+
return finalizeLoadPlan(plan);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
if (hasEncryptedPayload) {
|
|
1058
|
+
plan.state = 'invalid';
|
|
1059
|
+
plan.required_action = 'block';
|
|
1060
|
+
plan.can_load_now = false;
|
|
1061
|
+
plan.projection_policy = 'none';
|
|
1062
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1063
|
+
'KDNA_CRYPTO_PROFILE_UNSUPPORTED',
|
|
1064
|
+
'blocking',
|
|
1065
|
+
'Encrypted entries require licensed access.',
|
|
1066
|
+
));
|
|
1067
|
+
return finalizeLoadPlan(plan);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
plan.state = 'ready';
|
|
1071
|
+
plan.required_action = 'load';
|
|
1072
|
+
plan.can_load_now = true;
|
|
1073
|
+
plan.projection_policy = 'minimal';
|
|
1074
|
+
return finalizeLoadPlan(plan);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
717
1077
|
function assertNoForbiddenTerms(obj) {
|
|
718
1078
|
const seen = new Set();
|
|
719
1079
|
function walk(o) {
|
|
@@ -745,6 +1105,8 @@ module.exports = {
|
|
|
745
1105
|
readV1Layout,
|
|
746
1106
|
inspect,
|
|
747
1107
|
validate,
|
|
1108
|
+
planLoad,
|
|
1109
|
+
buildChecksumsV1,
|
|
748
1110
|
pack,
|
|
749
1111
|
unpack,
|
|
750
1112
|
loadV1,
|
|
@@ -753,6 +1115,35 @@ module.exports = {
|
|
|
753
1115
|
|
|
754
1116
|
// ─── loadV1 — v1 runtime loading / load contract ──────────────────────
|
|
755
1117
|
|
|
1118
|
+
function renderPromptItem(item) {
|
|
1119
|
+
if (item === undefined || item === null) return '';
|
|
1120
|
+
if (typeof item === 'string') return item;
|
|
1121
|
+
if (typeof item !== 'object') return String(item);
|
|
1122
|
+
|
|
1123
|
+
if (item.type === 'axiom_applicability' && item.one_sentence) {
|
|
1124
|
+
const parts = [item.one_sentence];
|
|
1125
|
+
if (Array.isArray(item.does_not_apply_when) && item.does_not_apply_when.length) {
|
|
1126
|
+
parts.push(`does not apply when: ${item.does_not_apply_when.slice(0, 2).join('; ')}`);
|
|
1127
|
+
}
|
|
1128
|
+
if (item.failure_risk) parts.push(`failure risk: ${item.failure_risk}`);
|
|
1129
|
+
return parts.join(' — ');
|
|
1130
|
+
}
|
|
1131
|
+
if (item.boundary && item.one_sentence) return `${item.one_sentence}: ${item.boundary}`;
|
|
1132
|
+
if (item.stance) return item.stance;
|
|
1133
|
+
if (item.statement) return item.statement;
|
|
1134
|
+
if (item.term && item.definition) return `${item.term}: ${item.definition}`;
|
|
1135
|
+
if (item.term && item.why) return `${item.term}: ${item.why}`;
|
|
1136
|
+
if (item.wrong && item.correct) return `${item.wrong} -> ${item.correct}`;
|
|
1137
|
+
if (item.mode && item.correct) return `${item.mode} -> ${item.correct}`;
|
|
1138
|
+
if (item.name && item.description) return `${item.name}: ${item.description}`;
|
|
1139
|
+
if (item.name) return item.name;
|
|
1140
|
+
if (item.one_sentence) return item.one_sentence;
|
|
1141
|
+
if (item.essence) return item.essence;
|
|
1142
|
+
if (item.question) return item.question;
|
|
1143
|
+
if (item.id) return item.id;
|
|
1144
|
+
return JSON.stringify(item);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
756
1147
|
function loadV1(inputPath, opts = {}) {
|
|
757
1148
|
const v1 = readV1Layout(path.resolve(inputPath));
|
|
758
1149
|
const m = v1.manifest;
|
|
@@ -820,11 +1211,11 @@ function loadV1(inputPath, opts = {}) {
|
|
|
820
1211
|
text += 'Profile: ' + result.profile + '\n';
|
|
821
1212
|
if (result.max_tokens_hint) text += 'Max tokens hint: ' + result.max_tokens_hint + '\n';
|
|
822
1213
|
if (c.highest_question) text += 'Highest question:\n' + c.highest_question + '\n';
|
|
823
|
-
if (c.axioms && c.axioms.length) text += 'Axioms:\n' + c.axioms.map((a) => '- ' + (
|
|
824
|
-
if (c.boundaries && c.boundaries.length) text += 'Boundaries:\n' + c.boundaries.map((b) => '- ' + (
|
|
825
|
-
if (c.self_checks && c.self_checks.length) text += 'Self-checks:\n' + c.self_checks.map((s) => '- ' + (
|
|
826
|
-
if (c.failure_modes && c.failure_modes.length) text += 'Failure modes:\n' + c.failure_modes.map((f) => '- ' + (f
|
|
827
|
-
if (c.patterns && c.patterns.length) text += 'Patterns:\n' + c.patterns.map((p) => '- ' + (p
|
|
1214
|
+
if (c.axioms && c.axioms.length) text += 'Axioms:\n' + c.axioms.map((a) => '- ' + renderPromptItem(a)).join('\n') + '\n';
|
|
1215
|
+
if (c.boundaries && c.boundaries.length) text += 'Boundaries:\n' + c.boundaries.map((b) => '- ' + renderPromptItem(b)).join('\n') + '\n';
|
|
1216
|
+
if (c.self_checks && c.self_checks.length) text += 'Self-checks:\n' + c.self_checks.map((s) => '- ' + renderPromptItem(s)).join('\n') + '\n';
|
|
1217
|
+
if (c.failure_modes && c.failure_modes.length) text += 'Failure modes:\n' + c.failure_modes.map((f) => '- ' + renderPromptItem(f)).join('\n') + '\n';
|
|
1218
|
+
if (c.patterns && c.patterns.length) text += 'Patterns:\n' + c.patterns.map((p) => '- ' + renderPromptItem(p)).join('\n') + '\n';
|
|
828
1219
|
if (c.note) text += 'Note: ' + c.note + '\n';
|
|
829
1220
|
return { status: result.status, profile: result.profile, text: text.trim() };
|
|
830
1221
|
}
|
package/src/v1/index.mjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import v1 from './index.js';
|
|
2
|
+
|
|
3
|
+
export const MIMETYPE = v1.MIMETYPE;
|
|
4
|
+
export const MIMETYPE_V1 = v1.MIMETYPE_V1;
|
|
5
|
+
export const MIMETYPE_V2 = v1.MIMETYPE_V2;
|
|
6
|
+
export const V1_REQUIRED_DIR_ENTRIES = v1.V1_REQUIRED_DIR_ENTRIES;
|
|
7
|
+
export const isV1SourceDir = v1.isV1SourceDir;
|
|
8
|
+
export const detectContainerFormat = v1.detectContainerFormat;
|
|
9
|
+
export const readV1Layout = v1.readV1Layout;
|
|
10
|
+
export const inspect = v1.inspect;
|
|
11
|
+
export const validate = v1.validate;
|
|
12
|
+
export const planLoad = v1.planLoad;
|
|
13
|
+
export const buildChecksumsV1 = v1.buildChecksumsV1;
|
|
14
|
+
export const pack = v1.pack;
|
|
15
|
+
export const unpack = v1.unpack;
|
|
16
|
+
export const loadV1 = v1.loadV1;
|
|
17
|
+
export const FORBIDDEN_OUTPUT_TERMS = v1.FORBIDDEN_OUTPUT_TERMS;
|
|
18
|
+
|
|
19
|
+
export default v1;
|