@aikdna/kdna-core 0.4.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 +119 -3
- package/package.json +3 -3
- 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 +619 -0
- package/src/crypto-profile.js +106 -0
- package/src/index.js +6 -0
- package/src/index.mjs +11 -0
- package/src/lint-pure.js +41 -10
- package/src/public-api.js +323 -0
- package/src/types.d.ts +247 -4
- package/src/validate-pure.js +1 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @aikdna/kdna-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Core library for loading, validating, linting, rendering, composing, and directly reading KDNA `.kdna` cognition assets. It has zero npm runtime dependencies.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,10 +8,67 @@ Pure logic library (zero dependencies) for loading, validating, linting, renderi
|
|
|
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
|
-
const {
|
|
66
|
+
const {
|
|
67
|
+
createKdnaAssetReader,
|
|
68
|
+
lintDomain,
|
|
69
|
+
validateDomainSchema,
|
|
70
|
+
validateCrossFile
|
|
71
|
+
} = require('@aikdna/kdna-core');
|
|
15
72
|
|
|
16
73
|
// Validate a domain
|
|
17
74
|
const dataMap = {
|
|
@@ -26,6 +83,65 @@ const crossResult = validateCrossFile(dataMap);
|
|
|
26
83
|
|
|
27
84
|
## API
|
|
28
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
|
+
}
|
|
123
|
+
}
|
|
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
|
+
|
|
132
|
+
const decryptEntry = createLicensedDecryptEntry({
|
|
133
|
+
licenseKey: activation.license_key,
|
|
134
|
+
machineFingerprint: activation.machine_fingerprint
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const loaded = await reader.loadProfile(asset, 'compact', { decryptEntry });
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The profile uses AES-256-GCM over each protected entry and derives the entry key
|
|
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
|
+
|
|
29
145
|
### `lintDomain(dataMap)`
|
|
30
146
|
Structural linting — checks required files, field presence, unique IDs, yes/no answerable self-checks, cross-file references, and flags potentially vague axioms.
|
|
31
147
|
|
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",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"types": "src/types.d.ts",
|
|
18
18
|
"scripts": {
|
|
19
|
-
"test": "node -e \"const m = require('./src/index.js'); console.log('kdna-core exports:', Object.keys(m).join(', '));\""
|
|
19
|
+
"test": "node --test test/*.test.js && node -e \"const m = require('./src/index.js'); console.log('kdna-core exports:', Object.keys(m).join(', '));\""
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"src/",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"license": "Apache-2.0",
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/aikdna/
|
|
35
|
+
"url": "git+https://github.com/aikdna/kdna.git",
|
|
36
36
|
"directory": "packages/kdna-core"
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://aikdna.com",
|
|
@@ -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
|
+
}
|