@aikdna/kdna-core 0.19.0 → 0.21.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/CHANGELOG.md +62 -2
- package/README.md +10 -2
- package/package.json +1 -1
- package/schema/manifest.schema.json +32 -14
- package/schema/payload-profile.schema.json +156 -6
- package/schema/runtime-capsule.schema.json +45 -6
- package/src/asset-reader.js +9 -100
- package/src/container/index.js +275 -36
- package/src/crypto-profile.js +18 -3
- package/src/external-key-grant.js +4 -8
- package/src/index.mjs +0 -2
- package/src/public-api.js +0 -12
- package/src/runtime-capsule.js +19 -2
- package/src/types.d.ts +9 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Validate decrypted external-grant and password payload plaintext against the
|
|
6
|
+
same declared Payload schema used for ordinary assets. Correct credentials
|
|
7
|
+
can no longer load schema-invalid judgment bytes.
|
|
8
|
+
- Close `core_structure` relation objects to the existing public relation
|
|
9
|
+
fields and project only that allowlist in compact Runtime Capsules, blocking
|
|
10
|
+
private Creation state hidden in arbitrary relation properties.
|
|
11
|
+
- Make the external-grant conformance golden carry one deterministic, actual
|
|
12
|
+
encrypted `.kdna` byte snapshot. The regression now checks the exact package
|
|
13
|
+
digest, Core validation, signed authorization, LoadPlan and authorized load
|
|
14
|
+
against that same artifact instead of deriving grant identity from
|
|
15
|
+
zlib-dependent repacking.
|
|
16
|
+
- Warn with `KDNA_DISPATCHER_DEGRADED` when the container dispatcher fails to
|
|
17
|
+
load for any reason other than the dispatcher file itself being absent,
|
|
18
|
+
instead of silently degrading to the legacy reader. The absence check now
|
|
19
|
+
resolves the dispatcher path up front, so a present dispatcher with a
|
|
20
|
+
missing internal dependency, a syntax or initialization error, or a
|
|
21
|
+
permission error is reported instead of being mistaken for a missing
|
|
22
|
+
module.
|
|
23
|
+
- Fail closed on hostile Argon2id parameters in untrusted password envelopes:
|
|
24
|
+
`memory_kib`, `iterations`, and `parallelism` must be integers within
|
|
25
|
+
defensive bounds (256 MiB / 16 / 8), removing a remote denial-of-service
|
|
26
|
+
path when loading third-party protected assets.
|
|
27
|
+
|
|
28
|
+
## 0.21.0 (2026-07-20)
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Keep `planLoad` fail-closed for password-protected assets until the supplied
|
|
33
|
+
credential is actually verified by `load`; a mere password-present signal
|
|
34
|
+
no longer reports `ready` or `can_load_now: true`.
|
|
35
|
+
- Require at least one non-empty, author-neutral judgment unit and explicit
|
|
36
|
+
scope or boundary semantics while keeping optional creator provenance
|
|
37
|
+
separate from format validity.
|
|
38
|
+
- Preserve every declared pattern in compact projection instead of silently
|
|
39
|
+
applying a prefix budget.
|
|
40
|
+
- Report every non-empty payload path intentionally omitted by compact
|
|
41
|
+
projection, including deterministic counts, in Runtime Capsule trace and
|
|
42
|
+
prompt output; preserve complete axiom applicability lists and stop trimming
|
|
43
|
+
a full-statement fallback.
|
|
44
|
+
- Make `checksums.json` optional under one container authority and verify it
|
|
45
|
+
whenever it is present.
|
|
46
|
+
- Withdraw asset-signature containers from the Development Preview contract;
|
|
47
|
+
malformed or legacy signature declarations now fail closed. External grants
|
|
48
|
+
and Human Lock provenance remain separate responsibilities.
|
|
49
|
+
- Close licensed entitlement validation around the declared `password`,
|
|
50
|
+
`local_receipt`, `account`, and `org` profiles.
|
|
51
|
+
- Load external-grant Schemas through static package imports so bundler-based
|
|
52
|
+
ecosystem adapters retain the exact Core validators instead of failing at
|
|
53
|
+
activation time.
|
|
54
|
+
|
|
55
|
+
## 0.20.0 (2026-07-17)
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- Keep Core authority limited to technical facts: Runtime manifests reject
|
|
60
|
+
intrinsic quality, risk, trust, recommendation, certification, and
|
|
61
|
+
production-readiness fields; Runtime Capsules and generic Judgment Reports
|
|
62
|
+
no longer project `risk_level` or `quality_badge`. External assessment and
|
|
63
|
+
caller adoption policy remain outside Core conformance.
|
|
64
|
+
|
|
65
|
+
## 0.19.0 (2026-07-16)
|
|
66
|
+
|
|
5
67
|
### Added
|
|
6
68
|
|
|
7
69
|
- Add the separate `@aikdna/kdna-core/remote-runtime` package subpath for a
|
|
@@ -10,8 +72,6 @@
|
|
|
10
72
|
package-root consumer loaders continue to return
|
|
11
73
|
`KDNA_AUTH_REMOTE_RUNTIME_REQUIRED`.
|
|
12
74
|
|
|
13
|
-
## 0.19.0 (2026-07-16)
|
|
14
|
-
|
|
15
75
|
### Fixed
|
|
16
76
|
|
|
17
77
|
- Enforce `compatibility.min_loader_version` as a strict `x.y.z` loader package
|
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @aikdna/kdna-core
|
|
2
2
|
|
|
3
|
+
> **Status:** Pre-release. This source tree contains the unreleased `0.21.0`
|
|
4
|
+
> corrective candidate; the published incumbent is `0.20.0`. Candidate source,
|
|
5
|
+
> APIs, and evidence must not be described as already published.
|
|
6
|
+
|
|
3
7
|
Core library for packaged `.kdna` judgment assets. It implements one current
|
|
4
8
|
container contract, authorization planning, integrity evidence, Runtime Capsule
|
|
5
9
|
projection, and the correlated Agent Host execution boundary.
|
|
@@ -7,9 +11,13 @@ projection, and the correlated Agent Host execution boundary.
|
|
|
7
11
|
## Install
|
|
8
12
|
|
|
9
13
|
```bash
|
|
10
|
-
npm install @aikdna/kdna-core
|
|
14
|
+
npm install @aikdna/kdna-core@0.20.0
|
|
11
15
|
```
|
|
12
16
|
|
|
17
|
+
That command installs the published incumbent. Use its release notes for the
|
|
18
|
+
exact `0.20.0` contract. The examples below describe the current source
|
|
19
|
+
candidate and require its final release coordinate before registry use.
|
|
20
|
+
|
|
13
21
|
## Pack, validate, plan, and load
|
|
14
22
|
|
|
15
23
|
Use the package root. Version-qualified entry points and parallel generation
|
|
@@ -58,7 +66,7 @@ separate from loadability: `planLoad` and every load path block a valid asset
|
|
|
58
66
|
that requires a newer loader with `KDNA_LOADER_VERSION_UNSUPPORTED` before
|
|
59
67
|
projection.
|
|
60
68
|
|
|
61
|
-
##
|
|
69
|
+
## Current candidate Runtime contracts
|
|
62
70
|
|
|
63
71
|
There is one current responsibility chain:
|
|
64
72
|
|
package/package.json
CHANGED
|
@@ -40,6 +40,14 @@
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"if": { "properties": { "access": { "const": "licensed" } }, "required": ["access"] },
|
|
46
|
+
"then": { "required": ["entitlement"] }
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"if": { "required": ["entitlement"] },
|
|
50
|
+
"then": { "properties": { "access": { "const": "licensed" } }, "required": ["access"] }
|
|
43
51
|
}
|
|
44
52
|
],
|
|
45
53
|
"properties": {
|
|
@@ -139,10 +147,31 @@
|
|
|
139
147
|
"enum": ["public", "licensed", "remote"],
|
|
140
148
|
"description": "Stable delivery and authorization mode for the asset."
|
|
141
149
|
},
|
|
150
|
+
"entitlement": {
|
|
151
|
+
"type": "object",
|
|
152
|
+
"required": ["profile"],
|
|
153
|
+
"additionalProperties": true,
|
|
154
|
+
"properties": {
|
|
155
|
+
"profile": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"enum": ["password", "local_receipt", "account", "org"]
|
|
158
|
+
},
|
|
159
|
+
"offline": { "type": "boolean" },
|
|
160
|
+
"revocable": { "type": "boolean" }
|
|
161
|
+
}
|
|
162
|
+
},
|
|
142
163
|
"summary": { "type": "string" },
|
|
143
164
|
"description": { "type": "string" },
|
|
144
165
|
"language": { "type": "string" },
|
|
145
166
|
"languages": { "type": "array", "items": { "type": "string" } },
|
|
167
|
+
"quality_badge": false,
|
|
168
|
+
"risk_level": false,
|
|
169
|
+
"trusted": false,
|
|
170
|
+
"recommended": false,
|
|
171
|
+
"high_quality": false,
|
|
172
|
+
"expert_reviewed": false,
|
|
173
|
+
"production_ready": false,
|
|
174
|
+
"officially_approved": false,
|
|
146
175
|
"license": {
|
|
147
176
|
"oneOf": [
|
|
148
177
|
{ "type": "string" },
|
|
@@ -215,19 +244,8 @@
|
|
|
215
244
|
}
|
|
216
245
|
}
|
|
217
246
|
},
|
|
218
|
-
"
|
|
219
|
-
|
|
220
|
-
"items": {
|
|
221
|
-
"type": "object",
|
|
222
|
-
"required": ["role", "path", "algorithm"],
|
|
223
|
-
"properties": {
|
|
224
|
-
"role": { "type": "string" },
|
|
225
|
-
"path": { "type": "string" },
|
|
226
|
-
"algorithm": { "type": "string" },
|
|
227
|
-
"key_id": { "type": "string" }
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
},
|
|
247
|
+
"signature": false,
|
|
248
|
+
"signatures": false,
|
|
231
249
|
"dependencies": {
|
|
232
250
|
"type": "object",
|
|
233
251
|
"additionalProperties": {
|
|
@@ -235,7 +253,7 @@
|
|
|
235
253
|
}
|
|
236
254
|
},
|
|
237
255
|
"extends": {
|
|
238
|
-
"description": "Asset inheritance declaration
|
|
256
|
+
"description": "Asset inheritance declaration. This asset inherits judgment content from the named base asset. Distinct from 'dependencies' (peer composition): 'extends' creates a single-inheritance hierarchy where the child specialises the parent. The child's cards override the parent's cards with the same ID; parent cards not overridden are inherited. Format: '@scope/name@semver-range' string, or an object with 'name' and optional 'version' fields.",
|
|
239
257
|
"oneOf": [
|
|
240
258
|
{
|
|
241
259
|
"type": "string",
|
|
@@ -18,11 +18,63 @@
|
|
|
18
18
|
},
|
|
19
19
|
"core": {
|
|
20
20
|
"type": "object",
|
|
21
|
-
"required": ["
|
|
21
|
+
"required": ["axioms"],
|
|
22
22
|
"additionalProperties": true,
|
|
23
|
+
"anyOf": [
|
|
24
|
+
{ "required": ["highest_question"] },
|
|
25
|
+
{
|
|
26
|
+
"required": ["judgment_role"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"judgment_role": {
|
|
29
|
+
"anyOf": [
|
|
30
|
+
{ "required": ["acts_as"] },
|
|
31
|
+
{ "required": ["does_not_act_as"] },
|
|
32
|
+
{ "required": ["responsibility"] }
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"required": ["boundaries"],
|
|
39
|
+
"properties": { "boundaries": { "minItems": 1 } }
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"properties": {
|
|
43
|
+
"axioms": {
|
|
44
|
+
"contains": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"anyOf": [
|
|
47
|
+
{
|
|
48
|
+
"required": ["applies_when"],
|
|
49
|
+
"properties": {
|
|
50
|
+
"applies_when": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"minItems": 1,
|
|
53
|
+
"items": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"required": ["does_not_apply_when"],
|
|
59
|
+
"properties": {
|
|
60
|
+
"does_not_apply_when": {
|
|
61
|
+
"type": "array",
|
|
62
|
+
"minItems": 1,
|
|
63
|
+
"items": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
],
|
|
23
73
|
"properties": {
|
|
24
74
|
"highest_question": {
|
|
25
75
|
"type": "string",
|
|
76
|
+
"minLength": 1,
|
|
77
|
+
"pattern": "\\S",
|
|
26
78
|
"description": "The single most important question this judgment system answers."
|
|
27
79
|
},
|
|
28
80
|
"worldview": {
|
|
@@ -39,23 +91,121 @@
|
|
|
39
91
|
"type": "object",
|
|
40
92
|
"additionalProperties": true,
|
|
41
93
|
"properties": {
|
|
42
|
-
"acts_as": { "type": "string" },
|
|
94
|
+
"acts_as": { "type": "string", "minLength": 1, "pattern": "\\S" },
|
|
43
95
|
"does_not_act_as": {
|
|
44
96
|
"oneOf": [
|
|
45
|
-
{ "type": "string" },
|
|
46
|
-
{
|
|
97
|
+
{ "type": "string", "minLength": 1, "pattern": "\\S" },
|
|
98
|
+
{
|
|
99
|
+
"type": "array",
|
|
100
|
+
"minItems": 1,
|
|
101
|
+
"items": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
|
102
|
+
}
|
|
47
103
|
]
|
|
48
104
|
},
|
|
49
|
-
"responsibility": { "type": "string" }
|
|
105
|
+
"responsibility": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
|
50
106
|
},
|
|
51
107
|
"description": "The authority this asset exercises and the responsibilities explicitly outside it."
|
|
52
108
|
},
|
|
109
|
+
"core_structure": {
|
|
110
|
+
"type": "array",
|
|
111
|
+
"items": {
|
|
112
|
+
"type": "object",
|
|
113
|
+
"additionalProperties": false,
|
|
114
|
+
"required": ["from", "to", "via"],
|
|
115
|
+
"properties": {
|
|
116
|
+
"from": { "type": "string", "minLength": 1, "pattern": "\\S" },
|
|
117
|
+
"to": { "type": "string", "minLength": 1, "pattern": "\\S" },
|
|
118
|
+
"via": { "type": "string", "enum": ["priority", "exception"] },
|
|
119
|
+
"applies_when": {
|
|
120
|
+
"type": "array",
|
|
121
|
+
"items": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
|
122
|
+
},
|
|
123
|
+
"does_not_apply_when": {
|
|
124
|
+
"type": "array",
|
|
125
|
+
"items": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"description": "Ordered public judgment relations. Relation objects are closed so private authoring state cannot enter Runtime projection."
|
|
130
|
+
},
|
|
53
131
|
"axioms": {
|
|
54
132
|
"type": "array",
|
|
55
|
-
"
|
|
133
|
+
"minItems": 1,
|
|
134
|
+
"items": {
|
|
135
|
+
"oneOf": [
|
|
136
|
+
{ "type": "string", "minLength": 1, "pattern": "\\S" },
|
|
137
|
+
{
|
|
138
|
+
"type": "object",
|
|
139
|
+
"minProperties": 1,
|
|
140
|
+
"properties": {
|
|
141
|
+
"applies_when": {
|
|
142
|
+
"type": "array",
|
|
143
|
+
"items": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
|
144
|
+
},
|
|
145
|
+
"does_not_apply_when": {
|
|
146
|
+
"type": "array",
|
|
147
|
+
"items": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"anyOf": [
|
|
151
|
+
{
|
|
152
|
+
"required": ["statement"],
|
|
153
|
+
"properties": { "statement": { "type": "string", "minLength": 1, "pattern": "\\S" } }
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"required": ["one_sentence"],
|
|
157
|
+
"properties": { "one_sentence": { "type": "string", "minLength": 1, "pattern": "\\S" } }
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"required": ["full_statement"],
|
|
161
|
+
"properties": { "full_statement": { "type": "string", "minLength": 1, "pattern": "\\S" } }
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
"description": "At least one non-empty judgment statement with an actual selection meaning."
|
|
56
168
|
},
|
|
57
169
|
"boundaries": {
|
|
58
170
|
"type": "array",
|
|
171
|
+
"items": {
|
|
172
|
+
"oneOf": [
|
|
173
|
+
{ "type": "string", "minLength": 1, "pattern": "\\S" },
|
|
174
|
+
{
|
|
175
|
+
"type": "object",
|
|
176
|
+
"minProperties": 1,
|
|
177
|
+
"anyOf": [
|
|
178
|
+
{ "required": ["scope"], "properties": { "scope": { "type": "string", "minLength": 1, "pattern": "\\S" } } },
|
|
179
|
+
{ "required": ["out_of_scope"], "properties": { "out_of_scope": { "type": "string", "minLength": 1, "pattern": "\\S" } } },
|
|
180
|
+
{ "required": ["text"], "properties": { "text": { "type": "string", "minLength": 1, "pattern": "\\S" } } },
|
|
181
|
+
{ "required": ["statement"], "properties": { "statement": { "type": "string", "minLength": 1, "pattern": "\\S" } } },
|
|
182
|
+
{ "required": ["one_sentence"], "properties": { "one_sentence": { "type": "string", "minLength": 1, "pattern": "\\S" } } },
|
|
183
|
+
{ "required": ["boundary"], "properties": { "boundary": { "type": "string", "minLength": 1, "pattern": "\\S" } } },
|
|
184
|
+
{ "required": ["stance"], "properties": { "stance": { "type": "string", "minLength": 1, "pattern": "\\S" } } },
|
|
185
|
+
{
|
|
186
|
+
"required": ["applies_when"],
|
|
187
|
+
"properties": {
|
|
188
|
+
"applies_when": {
|
|
189
|
+
"type": "array",
|
|
190
|
+
"minItems": 1,
|
|
191
|
+
"items": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"required": ["does_not_apply_when"],
|
|
197
|
+
"properties": {
|
|
198
|
+
"does_not_apply_when": {
|
|
199
|
+
"type": "array",
|
|
200
|
+
"minItems": 1,
|
|
201
|
+
"items": { "type": "string", "minLength": 1, "pattern": "\\S" }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
},
|
|
59
209
|
"description": "Explicit out-of-scope statements."
|
|
60
210
|
},
|
|
61
211
|
"risk_model": {
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"digests",
|
|
12
12
|
"signature",
|
|
13
13
|
"access",
|
|
14
|
-
"risk_level",
|
|
15
14
|
"profile",
|
|
16
15
|
"context",
|
|
17
16
|
"trace"
|
|
@@ -37,7 +36,6 @@
|
|
|
37
36
|
},
|
|
38
37
|
"signature": { "$ref": "#/$defs/signature" },
|
|
39
38
|
"access": { "enum": ["public", "licensed", "remote"] },
|
|
40
|
-
"risk_level": { "type": ["string", "null"] },
|
|
41
39
|
"profile": { "enum": ["index", "compact", "scenario", "full"] },
|
|
42
40
|
"context": { "type": "object" },
|
|
43
41
|
"trace": { "$ref": "#/$defs/trace" }
|
|
@@ -84,8 +82,7 @@
|
|
|
84
82
|
"required": ["state"],
|
|
85
83
|
"additionalProperties": false,
|
|
86
84
|
"properties": {
|
|
87
|
-
"state": { "
|
|
88
|
-
"issuer": { "type": "string", "minLength": 1 }
|
|
85
|
+
"state": { "const": "absent" }
|
|
89
86
|
}
|
|
90
87
|
},
|
|
91
88
|
"trace": {
|
|
@@ -108,9 +105,51 @@
|
|
|
108
105
|
"input_kind": { "enum": ["packaged_file", "packaged_bytes"] },
|
|
109
106
|
"runtime_eligible": { "const": true },
|
|
110
107
|
"schema_valid": { "const": true },
|
|
111
|
-
"signature_state": { "
|
|
112
|
-
"profile": { "enum": ["index", "compact", "scenario", "full"] }
|
|
108
|
+
"signature_state": { "const": "absent" },
|
|
109
|
+
"profile": { "enum": ["index", "compact", "scenario", "full"] },
|
|
110
|
+
"projection_report": { "$ref": "#/$defs/projectionReport" }
|
|
113
111
|
}
|
|
112
|
+
},
|
|
113
|
+
"projectionReport": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"required": ["status", "omitted", "omitted_total"],
|
|
116
|
+
"additionalProperties": false,
|
|
117
|
+
"properties": {
|
|
118
|
+
"status": { "enum": ["complete", "partial"] },
|
|
119
|
+
"omitted": {
|
|
120
|
+
"type": "array",
|
|
121
|
+
"items": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"required": ["path", "count"],
|
|
124
|
+
"additionalProperties": false,
|
|
125
|
+
"properties": {
|
|
126
|
+
"path": { "type": "string", "pattern": "^/" },
|
|
127
|
+
"count": { "type": "integer", "minimum": 1 }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"omitted_total": { "type": "integer", "minimum": 0 }
|
|
132
|
+
},
|
|
133
|
+
"allOf": [
|
|
134
|
+
{
|
|
135
|
+
"if": { "properties": { "status": { "const": "complete" } } },
|
|
136
|
+
"then": {
|
|
137
|
+
"properties": {
|
|
138
|
+
"omitted": { "maxItems": 0 },
|
|
139
|
+
"omitted_total": { "const": 0 }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"if": { "properties": { "status": { "const": "partial" } } },
|
|
145
|
+
"then": {
|
|
146
|
+
"properties": {
|
|
147
|
+
"omitted": { "minItems": 1 },
|
|
148
|
+
"omitted_total": { "minimum": 1 }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
]
|
|
114
153
|
}
|
|
115
154
|
}
|
|
116
155
|
}
|
package/src/asset-reader.js
CHANGED
|
@@ -190,7 +190,6 @@ function normalizeInput(input) {
|
|
|
190
190
|
|
|
191
191
|
function manifestForDigest(manifest) {
|
|
192
192
|
const copy = { ...(manifest || {}) };
|
|
193
|
-
delete copy.signature;
|
|
194
193
|
delete copy.asset_digest;
|
|
195
194
|
delete copy.container_sha256;
|
|
196
195
|
delete copy.content_digest;
|
|
@@ -205,7 +204,7 @@ function manifestForDigest(manifest) {
|
|
|
205
204
|
|
|
206
205
|
function buildContentDigest(asset) {
|
|
207
206
|
const parts = [];
|
|
208
|
-
const excluded = new Set(['.DS_Store', '
|
|
207
|
+
const excluded = new Set(['.DS_Store', 'build-receipt.json']);
|
|
209
208
|
for (const entryName of sortedEntryNames(asset.entries)) {
|
|
210
209
|
if (excluded.has(entryName)) continue;
|
|
211
210
|
if (entryName.startsWith('reports/')) continue;
|
|
@@ -221,92 +220,6 @@ function buildContentDigest(asset) {
|
|
|
221
220
|
return `sha256:${sha256Hex(Buffer.from(parts.join('\n')))}`;
|
|
222
221
|
}
|
|
223
222
|
|
|
224
|
-
function manifestForSignature(manifest, { stripDigestFields = true } = {}) {
|
|
225
|
-
// Mirrors manifestForDigest above. The two must agree on every field
|
|
226
|
-
// they strip, or the signing and digest paths will hash different
|
|
227
|
-
// representations of the same manifest and verifiers will report a
|
|
228
|
-
// mismatch on otherwise-valid assets.
|
|
229
|
-
//
|
|
230
|
-
// Bug: prior version omitted the `authoring.content_digest` strip
|
|
231
|
-
// that manifestForDigest performs, so the signing payload included
|
|
232
|
-
// a digest field that the digest payload did not.
|
|
233
|
-
const copy = { ...(manifest || {}) };
|
|
234
|
-
delete copy.signature;
|
|
235
|
-
delete copy._source;
|
|
236
|
-
if (stripDigestFields) {
|
|
237
|
-
delete copy.asset_digest;
|
|
238
|
-
delete copy.container_sha256;
|
|
239
|
-
delete copy.content_digest;
|
|
240
|
-
if (copy.authoring && typeof copy.authoring === 'object') {
|
|
241
|
-
const auth = { ...copy.authoring };
|
|
242
|
-
delete auth.content_digest;
|
|
243
|
-
copy.authoring = auth;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return copy;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function canonicalJsonEntry(entryName, entryBuf, options = {}) {
|
|
250
|
-
const parsed = parseJson(entryBuf, entryName);
|
|
251
|
-
const value = entryName === 'kdna.json' ? manifestForSignature(parsed, options) : parsed;
|
|
252
|
-
return Buffer.from(stableStringify(value));
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function buildSigningPayload(asset, options = {}) {
|
|
256
|
-
// Bug (#147): the prior version only excluded `.DS_Store` and
|
|
257
|
-
// `signature.json`, while `buildContentDigest` above also excludes
|
|
258
|
-
// `build-receipt.json` and any entry under `reports/`. A producer
|
|
259
|
-
// that signed with this code and a verifier that digested the
|
|
260
|
-
// asset with `buildContentDigest` would compute two different
|
|
261
|
-
// byte strings and the signature would fail to verify. The fix
|
|
262
|
-
// mirrors `buildContentDigest`'s exclusion set exactly.
|
|
263
|
-
const parts = [];
|
|
264
|
-
for (const entryName of sortedEntryNames(asset.entries)) {
|
|
265
|
-
if (entryName === '.DS_Store' || entryName === 'signature.json') continue;
|
|
266
|
-
if (entryName === 'build-receipt.json') continue;
|
|
267
|
-
if (entryName.startsWith('reports/')) continue;
|
|
268
|
-
const entryBuf = asset.readEntry(entryName);
|
|
269
|
-
let payloadBuf = entryBuf;
|
|
270
|
-
if (JSON_ENTRY_RE.test(entryName)) {
|
|
271
|
-
payloadBuf = canonicalJsonEntry(entryName, entryBuf, options);
|
|
272
|
-
}
|
|
273
|
-
parts.push(`${entryName}:${sha256Hex(payloadBuf)}`);
|
|
274
|
-
}
|
|
275
|
-
return parts.join('\n');
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function verifySignature(asset, manifest, errors, warnings) {
|
|
279
|
-
if (!manifest.signature) {
|
|
280
|
-
warnings.push('kdna.json.signature missing');
|
|
281
|
-
return null;
|
|
282
|
-
}
|
|
283
|
-
if (!manifest.author?.public_key_pem) {
|
|
284
|
-
errors.push('kdna.json.author.public_key_pem missing');
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
if (!manifest.author?.pubkey) {
|
|
288
|
-
errors.push('kdna.json.author.pubkey missing');
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const fingerprint = `ed25519:${sha256Hex(Buffer.from(manifest.author.public_key_pem))}`;
|
|
293
|
-
if (fingerprint !== manifest.author.pubkey) {
|
|
294
|
-
errors.push('author.public_key_pem fingerprint does not match author.pubkey');
|
|
295
|
-
return false;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
try {
|
|
299
|
-
const signature = Buffer.from(String(manifest.signature).replace(/^ed25519:/, ''), 'hex');
|
|
300
|
-
const publicKey = crypto.createPublicKey(manifest.author.public_key_pem);
|
|
301
|
-
const ok = crypto.verify(null, Buffer.from(buildSigningPayload(asset)), publicKey, signature);
|
|
302
|
-
if (!ok) errors.push('Ed25519 signature invalid');
|
|
303
|
-
return ok;
|
|
304
|
-
} catch (e) {
|
|
305
|
-
errors.push(`signature verification failed: ${e.message}`);
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
223
|
function verifyMediaType(asset, errors) {
|
|
311
224
|
if (!asset.entries.has('mimetype')) {
|
|
312
225
|
errors.push('required entry missing: mimetype');
|
|
@@ -464,9 +377,12 @@ function verifySync(asset, options = {}) {
|
|
|
464
377
|
|
|
465
378
|
// The current implementation is the sole authority for container,
|
|
466
379
|
// manifest, CBOR payload, checksum, and load-contract validity. The reader
|
|
467
|
-
// adds transport identity
|
|
468
|
-
//
|
|
380
|
+
// adds transport identity; it must not maintain a second manifest or payload
|
|
381
|
+
// dialect.
|
|
469
382
|
appendCurrentValidationErrors(asset, errors);
|
|
383
|
+
if (options.requireSignature === true) {
|
|
384
|
+
errors.push('asset signatures are outside the current Preview contract');
|
|
385
|
+
}
|
|
470
386
|
|
|
471
387
|
if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
|
|
472
388
|
verifyMediaType(asset, errors);
|
|
@@ -484,7 +400,6 @@ function verifySync(asset, options = {}) {
|
|
|
484
400
|
}
|
|
485
401
|
|
|
486
402
|
let manifest = null;
|
|
487
|
-
let signature_valid = null;
|
|
488
403
|
if (asset.entries.has('kdna.json')) {
|
|
489
404
|
try {
|
|
490
405
|
manifest = readManifest(asset);
|
|
@@ -515,9 +430,6 @@ function verifySync(asset, options = {}) {
|
|
|
515
430
|
}
|
|
516
431
|
}
|
|
517
432
|
}
|
|
518
|
-
if (options.requireSignature || manifest.signature) {
|
|
519
|
-
signature_valid = verifySignature(asset, manifest, errors, warnings);
|
|
520
|
-
}
|
|
521
433
|
// ── Human Lock signature verification ──────────────────
|
|
522
434
|
if (asset.entries.has('KDNA_Core.json')) {
|
|
523
435
|
try {
|
|
@@ -540,7 +452,6 @@ function verifySync(asset, options = {}) {
|
|
|
540
452
|
manifest,
|
|
541
453
|
asset_digest,
|
|
542
454
|
content_digest,
|
|
543
|
-
signature_valid,
|
|
544
455
|
};
|
|
545
456
|
}
|
|
546
457
|
|
|
@@ -630,6 +541,9 @@ function createKdnaAssetReader() {
|
|
|
630
541
|
const entries = [...asset.entries.keys()].sort();
|
|
631
542
|
|
|
632
543
|
appendCurrentValidationErrors(asset, errors);
|
|
544
|
+
if (options.requireSignature === true) {
|
|
545
|
+
errors.push('asset signatures are outside the current Preview contract');
|
|
546
|
+
}
|
|
633
547
|
|
|
634
548
|
if (!asset.entries.has('kdna.json')) errors.push('required entry missing: kdna.json');
|
|
635
549
|
verifyMediaType(asset, errors);
|
|
@@ -647,7 +561,6 @@ function createKdnaAssetReader() {
|
|
|
647
561
|
}
|
|
648
562
|
|
|
649
563
|
let manifest = null;
|
|
650
|
-
let signature_valid = null;
|
|
651
564
|
if (asset.entries.has('kdna.json')) {
|
|
652
565
|
try {
|
|
653
566
|
manifest = parseJson(asset.readEntry('kdna.json'), 'kdna.json');
|
|
@@ -678,9 +591,6 @@ function createKdnaAssetReader() {
|
|
|
678
591
|
}
|
|
679
592
|
}
|
|
680
593
|
}
|
|
681
|
-
if (options.requireSignature || manifest.signature) {
|
|
682
|
-
signature_valid = verifySignature(asset, manifest, errors, warnings);
|
|
683
|
-
}
|
|
684
594
|
} catch (e) {
|
|
685
595
|
errors.push(e.message);
|
|
686
596
|
}
|
|
@@ -694,7 +604,6 @@ function createKdnaAssetReader() {
|
|
|
694
604
|
manifest,
|
|
695
605
|
asset_digest,
|
|
696
606
|
content_digest,
|
|
697
|
-
signature_valid,
|
|
698
607
|
};
|
|
699
608
|
},
|
|
700
609
|
|