@aikdna/kdna-core 0.15.7 → 0.15.9
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 +41 -0
- package/package.json +1 -1
- package/schema/manifest.schema.json +18 -0
- package/src/v1/index.js +130 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.15.9 (2026-06-28)
|
|
4
|
+
|
|
5
|
+
Story 12 — Asset inheritance (`extends` field, RFC #148 v2.x Phase 3).
|
|
6
|
+
|
|
7
|
+
- **`extends` field in `manifest.schema.json`**: assets may declare
|
|
8
|
+
`extends: "@scope/name@^1.0.0"` (string) or `extends: { name, version }`
|
|
9
|
+
(object). Distinct from `dependencies` (peer composition): `extends`
|
|
10
|
+
creates a single-inheritance hierarchy where the child specialises the parent.
|
|
11
|
+
- **`planLoad` extends resolution**: when a `resolveAsset` callback is provided
|
|
12
|
+
and the manifest declares `extends`, `planLoad` resolves the base asset and
|
|
13
|
+
records `extends_chain` in the plan output. Non-blocking — missing base
|
|
14
|
+
emits a WARNING issue, not a blocking error.
|
|
15
|
+
- **`loadAuthorized` passes `extends_chain`** from the plan to `loadV1Unsafe`
|
|
16
|
+
so the inheritance merge is applied at load time.
|
|
17
|
+
- **`loadV1Unsafe` inheritance merge**: when `extendsChain` is non-empty, the
|
|
18
|
+
base asset's content is loaded and merged with the child's content. Merge
|
|
19
|
+
rules: child axioms override parent axioms with the same `id`; parent axioms
|
|
20
|
+
not in child are inherited; same for boundaries (by `scope` text);
|
|
21
|
+
`highest_question` falls back to parent if child omits it. Non-blocking.
|
|
22
|
+
- **`result.extends_chain`** and **`result.inheritance_applied`** fields added
|
|
23
|
+
to load output when inheritance is applied.
|
|
24
|
+
- Blocks Story 13 (trust levels) per Section 6 sequencing.
|
|
25
|
+
|
|
26
|
+
## v0.15.8 (2026-06-28)
|
|
27
|
+
|
|
28
|
+
Story 11 — RAG namespace isolation (RFC #148 v2.x Phase 3).
|
|
29
|
+
|
|
30
|
+
- **`rag_namespace`** field added to each entry in `resolved_dependencies`.
|
|
31
|
+
Format: `name@version` (or bare `name` when version is absent). Provides a
|
|
32
|
+
stable scoped identifier for per-component namespace isolation.
|
|
33
|
+
- **`rag_isolation_policy`** object added to load output when
|
|
34
|
+
`resolved_dependencies` is non-empty:
|
|
35
|
+
`{ default: "fenced", cross_namespace_blocked: true, namespaces: [...] }`.
|
|
36
|
+
Consumers MUST NOT mix content across namespaces without explicit permission.
|
|
37
|
+
- **`--as=prompt` namespace header**: each component section in multi-asset
|
|
38
|
+
prompt output is now prefixed with `[NAMESPACE: name@version]` so RAG
|
|
39
|
+
systems can attribute and isolate content per source component (per SPEC
|
|
40
|
+
§13.8 source attribution).
|
|
41
|
+
- No breaking changes to existing single-asset load output or to
|
|
42
|
+
`resolved_dependencies` shape (fields are additive only).
|
|
43
|
+
|
|
3
44
|
## v0.15.7 (2026-06-28)
|
|
4
45
|
|
|
5
46
|
Story 6 — dependencies runtime. Supports semver-matching local/registry dependency resolution and topological sort-based loading order.
|
package/package.json
CHANGED
|
@@ -201,6 +201,24 @@
|
|
|
201
201
|
"type": "string"
|
|
202
202
|
}
|
|
203
203
|
},
|
|
204
|
+
"extends": {
|
|
205
|
+
"description": "Asset inheritance declaration (Story 12). 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.",
|
|
206
|
+
"oneOf": [
|
|
207
|
+
{
|
|
208
|
+
"type": "string",
|
|
209
|
+
"description": "Scoped asset name with optional version, e.g. '@aikdna/sales@^1.0.0'."
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"type": "object",
|
|
213
|
+
"required": ["name"],
|
|
214
|
+
"additionalProperties": false,
|
|
215
|
+
"properties": {
|
|
216
|
+
"name": { "type": "string", "description": "Scoped asset name, e.g. '@aikdna/sales'." },
|
|
217
|
+
"version": { "type": "string", "description": "Semver range, e.g. '^1.0.0'. Defaults to '*' if absent." }
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
},
|
|
204
222
|
"encryption": { "type": "object" },
|
|
205
223
|
"load_contract": { "$ref": "load-contract.schema.json" },
|
|
206
224
|
"scope": { "type": "object" },
|
package/src/v1/index.js
CHANGED
|
@@ -1273,6 +1273,55 @@ function planLoad(inputPath, opts = {}) {
|
|
|
1273
1273
|
}
|
|
1274
1274
|
}
|
|
1275
1275
|
|
|
1276
|
+
// Resolve extends chain if declared (Story 12)
|
|
1277
|
+
// 'extends' is single-inheritance: child overrides parent's cards with same ID.
|
|
1278
|
+
// Distinct from 'dependencies' (peer composition).
|
|
1279
|
+
if (manifest.extends) {
|
|
1280
|
+
const extendsDecl = manifest.extends;
|
|
1281
|
+
const extendsName = typeof extendsDecl === 'string'
|
|
1282
|
+
? extendsDecl.replace(/@[^@]+$/, '') || extendsDecl // strip @version for resolver key
|
|
1283
|
+
: extendsDecl.name;
|
|
1284
|
+
const extendsVersion = typeof extendsDecl === 'string'
|
|
1285
|
+
? (extendsDecl.match(/@([^@]+)$/) || [])[1] || '*'
|
|
1286
|
+
: (extendsDecl.version || '*');
|
|
1287
|
+
|
|
1288
|
+
if (typeof opts.resolveAsset === 'function') {
|
|
1289
|
+
try {
|
|
1290
|
+
const baseAsset = opts.resolveAsset(extendsName);
|
|
1291
|
+
if (!baseAsset) {
|
|
1292
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1293
|
+
'KDNA_EXTENDS_NOT_FOUND',
|
|
1294
|
+
'warning',
|
|
1295
|
+
`Base asset "${extendsName}" declared in 'extends' could not be resolved. Inheritance chain incomplete.`
|
|
1296
|
+
));
|
|
1297
|
+
} else if (baseAsset.manifest && !satisfies(baseAsset.version, extendsVersion)) {
|
|
1298
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1299
|
+
'KDNA_EXTENDS_VERSION_MISMATCH',
|
|
1300
|
+
'warning',
|
|
1301
|
+
`Base asset "${extendsName}@${baseAsset.version}" does not satisfy extends range "${extendsVersion}".`
|
|
1302
|
+
));
|
|
1303
|
+
} else if (baseAsset.path) {
|
|
1304
|
+
plan.extends_chain = [
|
|
1305
|
+
{ name: extendsName, version: baseAsset.version || extendsVersion, path: baseAsset.path },
|
|
1306
|
+
];
|
|
1307
|
+
}
|
|
1308
|
+
} catch (err) {
|
|
1309
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1310
|
+
'KDNA_EXTENDS_RESOLUTION_FAILED',
|
|
1311
|
+
'warning',
|
|
1312
|
+
`extends resolution failed: ${err.message}`
|
|
1313
|
+
));
|
|
1314
|
+
}
|
|
1315
|
+
} else {
|
|
1316
|
+
// No resolver: extends chain unresolvable — warn, don't block
|
|
1317
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1318
|
+
'KDNA_EXTENDS_RESOLVER_MISSING',
|
|
1319
|
+
'warning',
|
|
1320
|
+
`Asset declares 'extends: ${JSON.stringify(manifest.extends)}' but no resolver was provided. Inheritance chain will not be applied.`
|
|
1321
|
+
));
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1276
1325
|
if (resolvedDeps.length > 0) {
|
|
1277
1326
|
plan.resolved_dependencies = resolvedDeps.map(dep => ({
|
|
1278
1327
|
name: dep.name,
|
|
@@ -1662,7 +1711,12 @@ function loadAuthorized(inputPath, opts = {}) {
|
|
|
1662
1711
|
err.plan = plan;
|
|
1663
1712
|
throw err;
|
|
1664
1713
|
}
|
|
1665
|
-
const mergedOpts = {
|
|
1714
|
+
const mergedOpts = {
|
|
1715
|
+
...opts,
|
|
1716
|
+
resolvedDependencies: plan.resolved_dependencies,
|
|
1717
|
+
// Pass extends_chain from plan to loadV1Unsafe so inheritance is applied (Story 12)
|
|
1718
|
+
extendsChain: plan.extends_chain || [],
|
|
1719
|
+
};
|
|
1666
1720
|
return loadV1Unsafe(inputPath, mergedOpts);
|
|
1667
1721
|
}
|
|
1668
1722
|
|
|
@@ -1844,18 +1898,84 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1844
1898
|
throw new Error(`unknown load profile: ${profile}`);
|
|
1845
1899
|
}
|
|
1846
1900
|
|
|
1847
|
-
|
|
1901
|
+
// Asset inheritance (Story 12): if the manifest declares 'extends' and
|
|
1902
|
+
// the plan resolved an extends_chain, load the base asset and merge its
|
|
1903
|
+
// content with the child's content. Child cards with the same ID override
|
|
1904
|
+
// the parent's; parent cards not overridden are inherited.
|
|
1905
|
+
if (opts.extendsChain && opts.extendsChain.length > 0 && result.content) {
|
|
1906
|
+
try {
|
|
1907
|
+
const base = opts.extendsChain[0];
|
|
1908
|
+
const baseLoaded = loadV1Unsafe(base.path, {
|
|
1909
|
+
...opts,
|
|
1910
|
+
resolvedDependencies: [],
|
|
1911
|
+
extendsChain: [],
|
|
1912
|
+
});
|
|
1913
|
+
if (baseLoaded && baseLoaded.content) {
|
|
1914
|
+
const bc = baseLoaded.content;
|
|
1915
|
+
const cc = result.content;
|
|
1916
|
+
// Merge axioms: child overrides by id; parent axioms not in child are added
|
|
1917
|
+
if (bc.axioms || cc.axioms) {
|
|
1918
|
+
const childIds = new Set((cc.axioms || []).map((a) => a.id).filter(Boolean));
|
|
1919
|
+
const inheritedAxioms = (bc.axioms || []).filter((a) => a.id && !childIds.has(a.id));
|
|
1920
|
+
cc.axioms = [...inheritedAxioms, ...(cc.axioms || [])];
|
|
1921
|
+
}
|
|
1922
|
+
// Merge boundaries: child overrides by scope text; parent not overridden are inherited
|
|
1923
|
+
if (bc.boundaries || cc.boundaries) {
|
|
1924
|
+
const childScopes = new Set(
|
|
1925
|
+
(cc.boundaries || []).map((b) => (b.scope || '').toLowerCase().trim()),
|
|
1926
|
+
);
|
|
1927
|
+
const inheritedBoundaries = (bc.boundaries || []).filter(
|
|
1928
|
+
(b) => !childScopes.has((b.scope || '').toLowerCase().trim()),
|
|
1929
|
+
);
|
|
1930
|
+
cc.boundaries = [...inheritedBoundaries, ...(cc.boundaries || [])];
|
|
1931
|
+
}
|
|
1932
|
+
// Inherit highest_question from parent if child doesn't declare one
|
|
1933
|
+
if (!cc.highest_question && bc.highest_question) {
|
|
1934
|
+
cc.highest_question = bc.highest_question;
|
|
1935
|
+
}
|
|
1936
|
+
result.extends_chain = opts.extendsChain.map((e) => ({
|
|
1937
|
+
name: e.name,
|
|
1938
|
+
version: e.version,
|
|
1939
|
+
path: e.path,
|
|
1940
|
+
}));
|
|
1941
|
+
result.inheritance_applied = true;
|
|
1942
|
+
}
|
|
1943
|
+
} catch (_) {
|
|
1944
|
+
// extends merge is best-effort — never block load
|
|
1945
|
+
result.inheritance_applied = false;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
if (opts.resolvedDependencies && opts.resolvedDependencies.length > 0) {
|
|
1848
1950
|
result.resolved_dependencies = opts.resolvedDependencies.map(dep => {
|
|
1849
1951
|
const depLoaded = loadV1Unsafe(dep.path, { ...opts, resolvedDependencies: [] });
|
|
1952
|
+
// RAG namespace (Story 11): scoped identifier for namespace isolation.
|
|
1953
|
+
// Format: name@version (or just name when version is absent).
|
|
1954
|
+
const ragNamespace = dep.name
|
|
1955
|
+
? (dep.version ? `${dep.name}@${dep.version}` : dep.name)
|
|
1956
|
+
: null;
|
|
1850
1957
|
return {
|
|
1851
1958
|
name: dep.name,
|
|
1852
1959
|
version: dep.version,
|
|
1853
1960
|
path: dep.path,
|
|
1961
|
+
rag_namespace: ragNamespace,
|
|
1854
1962
|
status: depLoaded.status,
|
|
1855
1963
|
profile: depLoaded.profile,
|
|
1856
1964
|
content: depLoaded.content
|
|
1857
1965
|
};
|
|
1858
1966
|
});
|
|
1967
|
+
|
|
1968
|
+
// RAG isolation policy (Story 11): consumers MUST NOT mix content
|
|
1969
|
+
// across namespaces without explicit permission. cross_namespace_blocked
|
|
1970
|
+
// is the default; a consumer may opt in to cross-namespace access by
|
|
1971
|
+
// setting its own policy, but Core never does it silently.
|
|
1972
|
+
result.rag_isolation_policy = {
|
|
1973
|
+
default: 'fenced',
|
|
1974
|
+
cross_namespace_blocked: true,
|
|
1975
|
+
namespaces: result.resolved_dependencies
|
|
1976
|
+
.map(d => d.rag_namespace)
|
|
1977
|
+
.filter(Boolean),
|
|
1978
|
+
};
|
|
1859
1979
|
}
|
|
1860
1980
|
|
|
1861
1981
|
if (as === 'prompt') {
|
|
@@ -1883,11 +2003,17 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1883
2003
|
if (c.note) text += 'Note: ' + c.note + '\n';
|
|
1884
2004
|
|
|
1885
2005
|
let textOut = text.trim();
|
|
1886
|
-
|
|
2006
|
+
if (opts.resolvedDependencies && opts.resolvedDependencies.length > 0) {
|
|
1887
2007
|
for (const dep of opts.resolvedDependencies) {
|
|
1888
2008
|
const depLoaded = loadV1Unsafe(dep.path, { ...opts, resolvedDependencies: [] });
|
|
1889
2009
|
if (depLoaded.text) {
|
|
1890
|
-
|
|
2010
|
+
// Namespace header (Story 11): each component section is prefixed
|
|
2011
|
+
// with [NAMESPACE: id] so consumers can isolate RAG content per source.
|
|
2012
|
+
const ns = dep.name
|
|
2013
|
+
? (dep.version ? `${dep.name}@${dep.version}` : dep.name)
|
|
2014
|
+
: null;
|
|
2015
|
+
const nsHeader = ns ? `[NAMESPACE: ${ns}]\n` : '';
|
|
2016
|
+
textOut += '\n\n---\n\n' + nsHeader + depLoaded.text;
|
|
1891
2017
|
}
|
|
1892
2018
|
}
|
|
1893
2019
|
}
|