dna-sdk 0.1.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.
Files changed (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/adapters/embedding/onnx.d.ts +32 -0
  4. package/dist/adapters/embedding/onnx.js +51 -0
  5. package/dist/adapters/filesystem/cache.d.ts +16 -0
  6. package/dist/adapters/filesystem/cache.js +113 -0
  7. package/dist/adapters/filesystem/index.d.ts +2 -0
  8. package/dist/adapters/filesystem/index.js +2 -0
  9. package/dist/adapters/filesystem/source.d.ts +82 -0
  10. package/dist/adapters/filesystem/source.js +357 -0
  11. package/dist/adapters/postgres/index.d.ts +2 -0
  12. package/dist/adapters/postgres/index.js +2 -0
  13. package/dist/adapters/postgres/migrations.d.ts +14 -0
  14. package/dist/adapters/postgres/migrations.js +88 -0
  15. package/dist/adapters/postgres/source.d.ts +172 -0
  16. package/dist/adapters/postgres/source.js +569 -0
  17. package/dist/adapters/resolvers/github.d.ts +12 -0
  18. package/dist/adapters/resolvers/github.js +47 -0
  19. package/dist/adapters/resolvers/http.d.ts +22 -0
  20. package/dist/adapters/resolvers/http.js +145 -0
  21. package/dist/adapters/resolvers/index.d.ts +3 -0
  22. package/dist/adapters/resolvers/index.js +3 -0
  23. package/dist/adapters/resolvers/local.d.ts +24 -0
  24. package/dist/adapters/resolvers/local.js +132 -0
  25. package/dist/adapters/search/driver.d.ts +45 -0
  26. package/dist/adapters/search/driver.js +138 -0
  27. package/dist/adapters/search/migrations.d.ts +26 -0
  28. package/dist/adapters/search/migrations.js +58 -0
  29. package/dist/adapters/search/rrf.d.ts +23 -0
  30. package/dist/adapters/search/rrf.js +42 -0
  31. package/dist/adapters/search/sqlite-vec.d.ts +66 -0
  32. package/dist/adapters/search/sqlite-vec.js +294 -0
  33. package/dist/bootstrap.d.ts +45 -0
  34. package/dist/bootstrap.js +134 -0
  35. package/dist/extensions/agentskills/DOCS.md +18 -0
  36. package/dist/extensions/agentskills.d.ts +28 -0
  37. package/dist/extensions/agentskills.js +252 -0
  38. package/dist/extensions/agentsmd/DOCS.md +18 -0
  39. package/dist/extensions/agentsmd.d.ts +22 -0
  40. package/dist/extensions/agentsmd.js +170 -0
  41. package/dist/extensions/audit/kinds/audit-log.kind.yaml +148 -0
  42. package/dist/extensions/audit.d.ts +25 -0
  43. package/dist/extensions/audit.js +62 -0
  44. package/dist/extensions/automation/kinds/automation.kind.yaml +296 -0
  45. package/dist/extensions/automation/query.d.ts +49 -0
  46. package/dist/extensions/automation/query.js +77 -0
  47. package/dist/extensions/automation/write-guards.d.ts +59 -0
  48. package/dist/extensions/automation/write-guards.js +153 -0
  49. package/dist/extensions/automation.d.ts +41 -0
  50. package/dist/extensions/automation.js +54 -0
  51. package/dist/extensions/collab.d.ts +16 -0
  52. package/dist/extensions/collab.js +79 -0
  53. package/dist/extensions/doc/kinds/doc.kind.yaml +118 -0
  54. package/dist/extensions/doc.d.ts +25 -0
  55. package/dist/extensions/doc.js +10 -0
  56. package/dist/extensions/eval/kinds/eval-baseline.kind.yaml +65 -0
  57. package/dist/extensions/eval/kinds/eval-case.kind.yaml +140 -0
  58. package/dist/extensions/eval/kinds/eval-run.kind.yaml +134 -0
  59. package/dist/extensions/eval/kinds/eval-suite.kind.yaml +87 -0
  60. package/dist/extensions/eval.d.ts +24 -0
  61. package/dist/extensions/eval.js +31 -0
  62. package/dist/extensions/evidence/kinds/evidence.kind.yaml +92 -0
  63. package/dist/extensions/evidence.d.ts +17 -0
  64. package/dist/extensions/evidence.js +94 -0
  65. package/dist/extensions/federation.d.ts +22 -0
  66. package/dist/extensions/federation.js +173 -0
  67. package/dist/extensions/guardrails/DOCS.md +20 -0
  68. package/dist/extensions/guardrails.d.ts +31 -0
  69. package/dist/extensions/guardrails.js +228 -0
  70. package/dist/extensions/helix/DOCS-Actor.md +20 -0
  71. package/dist/extensions/helix/DOCS-Agent.md +20 -0
  72. package/dist/extensions/helix/DOCS-UseCase.md +20 -0
  73. package/dist/extensions/helix/layers.d.ts +12 -0
  74. package/dist/extensions/helix/layers.js +18 -0
  75. package/dist/extensions/helix/write-guards.d.ts +70 -0
  76. package/dist/extensions/helix/write-guards.js +172 -0
  77. package/dist/extensions/helix.d.ts +29 -0
  78. package/dist/extensions/helix.js +963 -0
  79. package/dist/extensions/helix_extras.d.ts +124 -0
  80. package/dist/extensions/helix_extras.js +328 -0
  81. package/dist/extensions/hooks.d.ts +31 -0
  82. package/dist/extensions/hooks.js +247 -0
  83. package/dist/extensions/kinddef/DOCS.md +24 -0
  84. package/dist/extensions/kinddef.d.ts +40 -0
  85. package/dist/extensions/kinddef.js +171 -0
  86. package/dist/extensions/lesson.d.ts +6 -0
  87. package/dist/extensions/lesson.js +194 -0
  88. package/dist/extensions/modelreg/kinds/model-profile.kind.yaml +155 -0
  89. package/dist/extensions/modelreg.d.ts +30 -0
  90. package/dist/extensions/modelreg.js +38 -0
  91. package/dist/extensions/recognizer.d.ts +34 -0
  92. package/dist/extensions/recognizer.js +272 -0
  93. package/dist/extensions/research.d.ts +32 -0
  94. package/dist/extensions/research.js +292 -0
  95. package/dist/extensions/safety.d.ts +33 -0
  96. package/dist/extensions/safety.js +230 -0
  97. package/dist/extensions/sdlc/kinds/adr.kind.yaml +136 -0
  98. package/dist/extensions/sdlc/kinds/archive-proposal.kind.yaml +107 -0
  99. package/dist/extensions/sdlc/kinds/changelog.kind.yaml +108 -0
  100. package/dist/extensions/sdlc/kinds/cognitive-policy.kind.yaml +470 -0
  101. package/dist/extensions/sdlc/kinds/forecast.kind.yaml +125 -0
  102. package/dist/extensions/sdlc/kinds/insight.kind.yaml +87 -0
  103. package/dist/extensions/sdlc/kinds/kaizen.kind.yaml +106 -0
  104. package/dist/extensions/sdlc/kinds/lesson-learned.kind.yaml +270 -0
  105. package/dist/extensions/sdlc/kinds/narrative.kind.yaml +189 -0
  106. package/dist/extensions/sdlc/kinds/pattern-insight.kind.yaml +204 -0
  107. package/dist/extensions/sdlc/kinds/postmortem.kind.yaml +168 -0
  108. package/dist/extensions/sdlc/kinds/pre-mortem.kind.yaml +145 -0
  109. package/dist/extensions/sdlc/kinds/prompt-template.kind.yaml +109 -0
  110. package/dist/extensions/sdlc/kinds/retrospective.kind.yaml +186 -0
  111. package/dist/extensions/sdlc/kinds/risk-register.kind.yaml +178 -0
  112. package/dist/extensions/sdlc/kinds/saved-view.kind.yaml +127 -0
  113. package/dist/extensions/sdlc/kinds/status-report.kind.yaml +129 -0
  114. package/dist/extensions/sdlc/kinds/synthesis-run.kind.yaml +279 -0
  115. package/dist/extensions/sdlc/kinds/synthesizer-state.kind.yaml +196 -0
  116. package/dist/extensions/sdlc/kinds/workflow-event.kind.yaml +195 -0
  117. package/dist/extensions/sdlc.d.ts +32 -0
  118. package/dist/extensions/sdlc.js +1318 -0
  119. package/dist/extensions/soulspec/DOCS.md +19 -0
  120. package/dist/extensions/soulspec.d.ts +22 -0
  121. package/dist/extensions/soulspec.js +287 -0
  122. package/dist/extensions/tenant.d.ts +6 -0
  123. package/dist/extensions/tenant.js +291 -0
  124. package/dist/extensions/testkit.d.ts +24 -0
  125. package/dist/extensions/testkit.js +211 -0
  126. package/dist/index.d.ts +80 -0
  127. package/dist/index.js +102 -0
  128. package/dist/kernel/_text.d.ts +28 -0
  129. package/dist/kernel/_text.js +42 -0
  130. package/dist/kernel/bundle-handle.d.ts +83 -0
  131. package/dist/kernel/bundle-handle.js +184 -0
  132. package/dist/kernel/capabilities.d.ts +157 -0
  133. package/dist/kernel/capabilities.js +109 -0
  134. package/dist/kernel/collaborator-ports.d.ts +91 -0
  135. package/dist/kernel/collaborator-ports.js +1 -0
  136. package/dist/kernel/composition-resolver.d.ts +215 -0
  137. package/dist/kernel/composition-resolver.js +558 -0
  138. package/dist/kernel/composition.d.ts +10 -0
  139. package/dist/kernel/composition.js +1 -0
  140. package/dist/kernel/descriptor-loader.d.ts +13 -0
  141. package/dist/kernel/descriptor-loader.js +48 -0
  142. package/dist/kernel/document.d.ts +43 -0
  143. package/dist/kernel/document.js +71 -0
  144. package/dist/kernel/embedding.d.ts +28 -0
  145. package/dist/kernel/embedding.js +78 -0
  146. package/dist/kernel/errors.d.ts +62 -0
  147. package/dist/kernel/errors.js +82 -0
  148. package/dist/kernel/events.d.ts +6 -0
  149. package/dist/kernel/events.js +21 -0
  150. package/dist/kernel/evidence-capture.d.ts +69 -0
  151. package/dist/kernel/evidence-capture.js +138 -0
  152. package/dist/kernel/fs.d.ts +20 -0
  153. package/dist/kernel/fs.js +128 -0
  154. package/dist/kernel/generic-rw.d.ts +33 -0
  155. package/dist/kernel/generic-rw.js +235 -0
  156. package/dist/kernel/hooks.d.ts +172 -0
  157. package/dist/kernel/hooks.js +256 -0
  158. package/dist/kernel/index.d.ts +624 -0
  159. package/dist/kernel/index.js +1638 -0
  160. package/dist/kernel/instance.d.ts +211 -0
  161. package/dist/kernel/instance.js +487 -0
  162. package/dist/kernel/kind-registry.d.ts +183 -0
  163. package/dist/kernel/kind-registry.js +710 -0
  164. package/dist/kernel/kind_base.d.ts +106 -0
  165. package/dist/kernel/kind_base.js +244 -0
  166. package/dist/kernel/layer-resolver.d.ts +48 -0
  167. package/dist/kernel/layer-resolver.js +284 -0
  168. package/dist/kernel/lock-manager.d.ts +21 -0
  169. package/dist/kernel/lock-manager.js +43 -0
  170. package/dist/kernel/lock.d.ts +38 -0
  171. package/dist/kernel/lock.js +115 -0
  172. package/dist/kernel/meta.d.ts +135 -0
  173. package/dist/kernel/meta.js +817 -0
  174. package/dist/kernel/models.d.ts +3089 -0
  175. package/dist/kernel/models.js +764 -0
  176. package/dist/kernel/navigator.d.ts +36 -0
  177. package/dist/kernel/navigator.js +132 -0
  178. package/dist/kernel/port-surface.d.ts +33 -0
  179. package/dist/kernel/port-surface.js +180 -0
  180. package/dist/kernel/preview.d.ts +60 -0
  181. package/dist/kernel/preview.js +54 -0
  182. package/dist/kernel/prompt-budget.d.ts +39 -0
  183. package/dist/kernel/prompt-budget.js +43 -0
  184. package/dist/kernel/prompt-builder.d.ts +25 -0
  185. package/dist/kernel/prompt-builder.js +237 -0
  186. package/dist/kernel/protocols.d.ts +751 -0
  187. package/dist/kernel/protocols.js +493 -0
  188. package/dist/kernel/reports.d.ts +26 -0
  189. package/dist/kernel/reports.js +215 -0
  190. package/dist/kernel/resolver.d.ts +146 -0
  191. package/dist/kernel/resolver.js +232 -0
  192. package/dist/kernel/resource.d.ts +57 -0
  193. package/dist/kernel/resource.js +97 -0
  194. package/dist/kernel/runtime.d.ts +16 -0
  195. package/dist/kernel/runtime.js +18 -0
  196. package/dist/kernel/safety-scanner.d.ts +38 -0
  197. package/dist/kernel/safety-scanner.js +228 -0
  198. package/dist/kernel/serialize-to-files.d.ts +29 -0
  199. package/dist/kernel/serialize-to-files.js +23 -0
  200. package/dist/kernel/spec-access.d.ts +23 -0
  201. package/dist/kernel/spec-access.js +40 -0
  202. package/dist/kernel/studio_ui.d.ts +88 -0
  203. package/dist/kernel/studio_ui.js +135 -0
  204. package/dist/kernel/templates.d.ts +77 -0
  205. package/dist/kernel/templates.js +116 -0
  206. package/dist/kernel/tool-registry.d.ts +52 -0
  207. package/dist/kernel/tool-registry.js +89 -0
  208. package/dist/kernel/write-pipeline.d.ts +65 -0
  209. package/dist/kernel/write-pipeline.js +271 -0
  210. package/dist/kernel/writer-helpers.d.ts +24 -0
  211. package/dist/kernel/writer-helpers.js +42 -0
  212. package/dist/memory/decay.d.ts +31 -0
  213. package/dist/memory/decay.js +97 -0
  214. package/dist/memory/ecphory.d.ts +25 -0
  215. package/dist/memory/ecphory.js +178 -0
  216. package/dist/memory/encodingContext.d.ts +21 -0
  217. package/dist/memory/encodingContext.js +62 -0
  218. package/dist/memory/index.d.ts +23 -0
  219. package/dist/memory/index.js +23 -0
  220. package/dist/memory/memoryType.d.ts +7 -0
  221. package/dist/memory/memoryType.js +24 -0
  222. package/dist/memory/policy.d.ts +35 -0
  223. package/dist/memory/policy.js +35 -0
  224. package/dist/memory/retrieval.d.ts +21 -0
  225. package/dist/memory/retrieval.js +145 -0
  226. package/dist/testing/index.d.ts +8 -0
  227. package/dist/testing/index.js +8 -0
  228. package/dist/testing/recordSearchConformance.d.ts +43 -0
  229. package/dist/testing/recordSearchConformance.js +147 -0
  230. package/dist/viz/ascii.d.ts +7 -0
  231. package/dist/viz/ascii.js +60 -0
  232. package/dist/viz/health.d.ts +8 -0
  233. package/dist/viz/health.js +161 -0
  234. package/dist/viz/index.d.ts +10 -0
  235. package/dist/viz/index.js +10 -0
  236. package/dist/viz/matrix.d.ts +8 -0
  237. package/dist/viz/matrix.js +84 -0
  238. package/dist/viz/mermaid.d.ts +38 -0
  239. package/dist/viz/mermaid.js +706 -0
  240. package/package.json +82 -0
@@ -0,0 +1,558 @@
1
+ /**
2
+ * The kernel's unified composition motor — TS twin of
3
+ * `packages/sdk-py/dna/kernel/composition_resolver.py`
4
+ * (s-unify-composition-subsystems). One module, four composition
5
+ * concerns:
6
+ *
7
+ * 1. CompositionProfile (V1) — declarative wiring + UI hints
8
+ * registered by extensions (moved here from `composition.ts`,
9
+ * now a deprecated re-export shim).
10
+ * 2. `validateRefs` — THE shared cross-kind ref validation core;
11
+ * target resolution via `resolveDepFilterTargetOver`
12
+ * (kind-registry.ts) — the same canonical resolver the Kernel
13
+ * uses, so alias + legacy `kind=` resolve identically everywhere.
14
+ * 3. CompositionEngine — the `mi.composition` namespace (moved here
15
+ * from `composition-engine.ts`, deleted).
16
+ * 4. CompositionResolver — the Composition-V2 chain-resolution engine.
17
+ *
18
+ * Pure resolution types + merges stay in `resolver.ts` (Py:
19
+ * `kernel/resolver.py`).
20
+ *
21
+ * Record-plane rule (two-planes F2.5) — ONE rule for every reader: a ref
22
+ * whose TARGET Kind is plane="record" resolves when present in the doc
23
+ * index and is DEFERRED otherwise (never false-missing).
24
+ *
25
+ * # CompositionResolver — Composition-V2 (Phase 17 /
26
+ * s-ts-composition-v2-port).
27
+ *
28
+ * Behavior parity is 1:1 with the Python engine on the RESOLUTION SEMANTICS:
29
+ * chain walk (`computeResolutionChain` — cycle guard + MAX_RESOLUTION_DEPTH +
30
+ * V1 fallback to `_lib`), composition-rule lookup (`getCompositionRule` —
31
+ * LayerPolicy.composition_rules else the inherit-by-default denylist),
32
+ * `resolveDocument` (bootstrap bypass, tenant overlay, Catalog splice
33
+ * Local > Catalog > Base, override_full / field_level merge, provenance) and
34
+ * `personalizeDocument`. The behavioral gate is the shared fixture harness
35
+ * `tests/parity-fixtures/composition/` (s-parity-behavioral-harness), executed
36
+ * by BOTH `packages/sdk-py/tests/test_composition_parity_fixtures.py` and
37
+ * `packages/sdk-ts/tests/composition-parity-fixtures.test.ts`.
38
+ *
39
+ * Documented divergences from Python — PERF / infra, NOT semantics:
40
+ *
41
+ * 1. **No granular layer cache.** The Py kernel reads each layer through
42
+ * `_granular_doc_cached` (LRU 2000 / TTL 60s / single-flight). The TS
43
+ * kernel has no kernel-level doc cache, so `Kernel._granularDoc` hits the
44
+ * source directly on every layer read. Same inputs → same outputs; each
45
+ * resolve just costs O(chain) source reads instead of cache hits.
46
+ * 2. **No `_layer_observers` reverse-dep graph.** That structure exists purely
47
+ * to invalidate the Py granular cache across scopes on parent/catalog
48
+ * writes; with no cache there is nothing to invalidate. It is intentionally
49
+ * NOT ported (would be dead state).
50
+ * 3. **Catalog tier surface.** The TS kernel has no catalog machinery yet
51
+ * (package scan + tenant lockfile — deferred as `i-185`). The splice HOOK
52
+ * is fully implemented here (identical to Py: insert after the local
53
+ * entries, before the first parent, conflict surfacing on ≥2 hits) but
54
+ * `Kernel._catalogScopes` returns `[]` until i-185 lands, so the Catalog
55
+ * tier contributes no layers on TS today.
56
+ * 4. **`personalizeDocument` clones spec + envelope only.** The Py engine also
57
+ * clones binary bundle entries via `source._load_bundle_entries` +
58
+ * `kernel.write_bundle_entry_async`; the TS kernel has no bundle-entry
59
+ * write surface yet, so bundle payload cloning is Py-only (best-effort in
60
+ * Py anyway — failures there are swallowed).
61
+ */
62
+ import { sourceCapabilities } from "./capabilities.js";
63
+ import { BOOTSTRAP_KINDS, DEFAULT_NON_INHERITABLE_KINDS_V1, MAX_RESOLUTION_DEPTH, ResolutionLayer, ResolutionPath, ResolvedDocument, mergeFieldLevel, mergeOverrideFull, } from "./resolver.js";
64
+ import { resolveDepFilterTargetOver } from "./kind-registry.js";
65
+ import { findConsumers } from "./preview.js";
66
+ /**
67
+ * Find the profile whose orchestrator matches a given alias.
68
+ * Returns null if none registered. Pure helper — no kernel dependency.
69
+ */
70
+ export function profileForOrchestrator(profiles, orchestratorAlias) {
71
+ return profiles.find((p) => p.orchestratorAlias === orchestratorAlias) ?? null;
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // 2. validateRefs — the shared cross-kind ref validation core
75
+ // ---------------------------------------------------------------------------
76
+ /**
77
+ * Validate declared dep_filter refs of `docs` against `docIndex`
78
+ * (`${kind}\0${name}` keys). The ONE implementation behind
79
+ * `mi.composition.validate()` — target resolution via
80
+ * `resolveDepFilterTargetOver` (canonical s-alias resolver: alias
81
+ * contract + deprecated `kind=` shim). Unresolvable target → warnings;
82
+ * ref in index → resolved; absent + record-plane target → deferred
83
+ * (records resolve lazily via the kernel record plane — never
84
+ * false-missing); absent otherwise → missing.
85
+ * Py twin: `composition_resolver.validate_refs`.
86
+ */
87
+ export function validateRefs(docs, docIndex, kinds) {
88
+ const resolved = [];
89
+ const missing = [];
90
+ const warnings = [];
91
+ const deferred = [];
92
+ for (const doc of docs) {
93
+ const kp = kinds.get(`${doc.apiVersion}\0${doc.kind}`);
94
+ if (!kp)
95
+ continue;
96
+ const filters = kp.depFilters();
97
+ if (!filters)
98
+ continue;
99
+ const spec = doc.spec;
100
+ for (const [specField, targetValue] of Object.entries(filters)) {
101
+ const targetKp = resolveDepFilterTargetOver(kinds, targetValue);
102
+ if (!targetKp) {
103
+ warnings.push(`${doc.kind}/${doc.name}: unknown alias '${targetValue}' in depFilters`);
104
+ continue;
105
+ }
106
+ const targetKind = targetKp.kind;
107
+ const declared = spec[specField] ?? null;
108
+ if (declared == null)
109
+ continue;
110
+ const refs = Array.isArray(declared)
111
+ ? declared
112
+ : typeof declared === "string"
113
+ ? [declared]
114
+ : [];
115
+ const targetIsRecord = (targetKp.plane ?? "composition") === "record";
116
+ for (const refName of refs) {
117
+ const label = `${doc.kind}/${doc.name}.${specField}=${refName} -> ${targetKind}/${refName}`;
118
+ if (docIndex.has(`${targetKind}\0${refName}`)) {
119
+ resolved.push(label);
120
+ }
121
+ else if (targetIsRecord) {
122
+ deferred.push(label + " (record — resolved lazily)");
123
+ }
124
+ else {
125
+ missing.push(label + " NOT FOUND");
126
+ }
127
+ }
128
+ }
129
+ }
130
+ return { resolved, missing, warnings, deferred };
131
+ }
132
+ // ---------------------------------------------------------------------------
133
+ // 3. CompositionEngine — the `mi.composition` namespace
134
+ // ---------------------------------------------------------------------------
135
+ export class CompositionEngine {
136
+ host;
137
+ constructor(host) {
138
+ this.host = host;
139
+ }
140
+ /**
141
+ * Validate all composition references over the MI plane.
142
+ * Equivalent to `mi.compositionResult`. Delegates to `validateRefs` —
143
+ * records are excluded from the MI materialization, so record-target
144
+ * refs land in `deferred` (they resolve lazily via the kernel record
145
+ * plane at read time).
146
+ */
147
+ validate() {
148
+ const kinds = this.host._kinds;
149
+ const docIndex = new Set();
150
+ for (const d of this.host.documents) {
151
+ docIndex.add(`${d.kind}\0${d.name}`);
152
+ }
153
+ return validateRefs(this.host.documents, docIndex, kinds);
154
+ }
155
+ /**
156
+ * Iterate a document's declared dep_filters dynamically.
157
+ * Equivalent to `mi.iterDocDeps(doc)`.
158
+ */
159
+ iterDocDeps(doc) {
160
+ const kinds = this.host._kinds;
161
+ const sourceKp = kinds.get(`${doc.apiVersion}\0${doc.kind}`);
162
+ if (!sourceKp)
163
+ return [];
164
+ const filters = sourceKp.depFilters();
165
+ if (!filters)
166
+ return [];
167
+ const spec = doc.spec ?? {};
168
+ const result = [];
169
+ for (const [label, targetValue] of Object.entries(filters)) {
170
+ const targetKp = resolveDepFilterTargetOver(kinds, targetValue);
171
+ if (!targetKp)
172
+ continue;
173
+ const value = spec[label];
174
+ let names = [];
175
+ if (Array.isArray(value)) {
176
+ names = value.filter((v) => typeof v === "string");
177
+ }
178
+ else if (typeof value === "string" && value) {
179
+ names = [value];
180
+ }
181
+ if (names.length === 0)
182
+ continue;
183
+ result.push({ label, targetKind: targetKp.kind, names });
184
+ }
185
+ return result;
186
+ }
187
+ /**
188
+ * Walk the manifest and return every doc that references this one.
189
+ * Equivalent to `mi.consumersOf(kind, name)`.
190
+ */
191
+ consumersOf(kind, name) {
192
+ return findConsumers(this.host, { kind, name });
193
+ }
194
+ /**
195
+ * Build a dependency tree for the manifest.
196
+ * Equivalent to `mi.dependencyTree()`.
197
+ */
198
+ dependencyTree() {
199
+ const kinds = this.host._kinds;
200
+ const docIndex = new Map();
201
+ for (const d of this.host.documents) {
202
+ docIndex.set(`${d.kind}\0${d.name}`, d);
203
+ }
204
+ const tree = {};
205
+ for (const doc of this.host.documents) {
206
+ const key = `${doc.apiVersion}\0${doc.kind}`;
207
+ const kp = kinds.get(key);
208
+ if (!kp)
209
+ continue;
210
+ const filters = kp.depFilters();
211
+ if (!filters)
212
+ continue;
213
+ const dependsOn = {};
214
+ const spec = doc.spec;
215
+ for (const [specField, targetValue] of Object.entries(filters)) {
216
+ const targetKp = resolveDepFilterTargetOver(kinds, targetValue);
217
+ if (!targetKp)
218
+ continue;
219
+ const targetKind = targetKp.kind;
220
+ const declared = spec[specField];
221
+ if (!declared)
222
+ continue;
223
+ const refs = Array.isArray(declared)
224
+ ? declared
225
+ : typeof declared === "string"
226
+ ? [declared]
227
+ : [];
228
+ const deps = {};
229
+ for (const refName of refs) {
230
+ const depEntry = { kind: targetKind };
231
+ const refDoc = docIndex.get(`${targetKind}\0${refName}`);
232
+ if (refDoc) {
233
+ depEntry.found = true;
234
+ const desc = refDoc.metadata.description ?? "";
235
+ if (desc)
236
+ depEntry.description = desc;
237
+ const extra = targetKp.summary(refDoc);
238
+ if (extra)
239
+ Object.assign(depEntry, extra);
240
+ }
241
+ else {
242
+ depEntry.found = false;
243
+ }
244
+ deps[refName] = depEntry;
245
+ }
246
+ if (Object.keys(deps).length > 0) {
247
+ dependsOn[specField] = deps;
248
+ }
249
+ }
250
+ if (Object.keys(dependsOn).length > 0) {
251
+ tree[doc.name] = {
252
+ kind: doc.kind,
253
+ description: doc.metadata.description ?? "",
254
+ depends_on: dependsOn,
255
+ };
256
+ }
257
+ }
258
+ return tree;
259
+ }
260
+ }
261
+ // ---------------------------------------------------------------------------
262
+ // 4. CompositionResolver — the Composition-V2 chain-resolution engine
263
+ // ---------------------------------------------------------------------------
264
+ /** Structural layer equality — mirrors the Python frozen-dataclass `==`
265
+ * (field-by-field), which `resolve_document` relies on to flag the
266
+ * contributing layer. Reference equality would diverge when the same
267
+ * (scope, tenant) pair appears twice in a spliced chain. */
268
+ function layerEquals(a, b) {
269
+ return (a.scope === b.scope
270
+ && a.tenant === b.tenant
271
+ && a.found === b.found
272
+ && a.contributed === b.contributed
273
+ && a.versionSha === b.versionSha);
274
+ }
275
+ function specOf(raw) {
276
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
277
+ return null;
278
+ const spec = raw.spec;
279
+ if (spec === null || spec === undefined || typeof spec !== "object" || Array.isArray(spec)) {
280
+ return null;
281
+ }
282
+ return spec;
283
+ }
284
+ export class CompositionResolver {
285
+ _k;
286
+ constructor(kernel) {
287
+ this._k = kernel;
288
+ }
289
+ /**
290
+ * Walk `Genome.spec.parent_scope` transitively → ordered chain of
291
+ * `[scope, tenant]` pairs, HIGHEST priority first. Cycle-guarded; depth
292
+ * capped at MAX_RESOLUTION_DEPTH; missing Genome terminates the walk.
293
+ * 1:1 with Py `compute_resolution_chain`.
294
+ */
295
+ async computeResolutionChain(scope, tenant) {
296
+ const k = this._k;
297
+ const inheritParent = k.constructor.INHERIT_PARENT_SCOPE;
298
+ const chain = [];
299
+ const visited = new Set();
300
+ let current = scope;
301
+ let depth = 0;
302
+ while (current && !visited.has(current) && depth < MAX_RESOLUTION_DEPTH) {
303
+ visited.add(current);
304
+ if (tenant)
305
+ chain.push([current, tenant]);
306
+ chain.push([current, null]);
307
+ // Fail-soft: a scope absent on the source contributes no parent
308
+ // (chain ends here instead of throwing) — critical under the
309
+ // inherit-by-default denylist where every read computes a chain.
310
+ let pkgRaw = null;
311
+ try {
312
+ pkgRaw = await k._granularDoc([current, "Genome", current, ""]);
313
+ }
314
+ catch {
315
+ pkgRaw = null;
316
+ }
317
+ let parent = null;
318
+ const spec = specOf(pkgRaw);
319
+ if (spec) {
320
+ const parentVal = spec.parent_scope;
321
+ if (typeof parentVal === "string" && parentVal)
322
+ parent = parentVal;
323
+ }
324
+ // V1 back-compat: when Genome omits parent_scope, escalate to the
325
+ // legacy INHERIT_PARENT_SCOPE (default _lib) so existing scopes
326
+ // inherit without migration. Overridden once parent_scope is declared.
327
+ if (parent === null
328
+ && current !== inheritParent
329
+ && !visited.has(inheritParent)) {
330
+ parent = inheritParent;
331
+ }
332
+ current = parent;
333
+ depth += 1;
334
+ }
335
+ return chain;
336
+ }
337
+ /**
338
+ * Resolve `[scope_inheritance, merge_strategy, tenant_overlay]` for
339
+ * (scope, kind) — from the scope's LayerPolicy composition_rules, else
340
+ * the inherit-by-default denylist (everything inherits from _lib except
341
+ * the per-scope ledger + structural Kinds). 1:1 with Py
342
+ * `get_composition_rule`.
343
+ */
344
+ async getCompositionRule(scope, kind) {
345
+ const src = this._k.activeSource;
346
+ // s-sourceport-contract-cleanup: declared capabilities, not typeof.
347
+ if (src !== null && sourceCapabilities(src).queryPushdown && typeof src.query === "function") {
348
+ try {
349
+ for await (const raw of src.query(scope, "LayerPolicy", {})) {
350
+ const spec = specOf(raw);
351
+ if (!spec)
352
+ continue;
353
+ const rules = spec.composition_rules;
354
+ if (rules === null || typeof rules !== "object" || Array.isArray(rules))
355
+ continue;
356
+ const rule = rules[kind];
357
+ if (rule !== null && typeof rule === "object" && !Array.isArray(rule)) {
358
+ const r = rule;
359
+ return [
360
+ String(r.scope_inheritance || "disabled").toLowerCase(),
361
+ String(r.merge_strategy || "override_full").toLowerCase(),
362
+ String(r.tenant_overlay || "none").toLowerCase(),
363
+ ];
364
+ }
365
+ }
366
+ }
367
+ catch {
368
+ // Fail-soft — a missing scope / query error falls through to defaults.
369
+ }
370
+ }
371
+ if (!DEFAULT_NON_INHERITABLE_KINDS_V1.has(kind)) {
372
+ return ["enabled", "override_full", "field_level"];
373
+ }
374
+ // Non-inheritable Kinds STILL honor tenant overlay (TENANTED Canvas,
375
+ // VoiceEpisode, Story must read tenant=X correctly).
376
+ return ["disabled", "override_full", "field_level"];
377
+ }
378
+ /**
379
+ * Resolve a doc through the composition chain (Phase 17). Returns a
380
+ * ResolvedDocument with merged doc + full provenance. Bootstrap Kinds
381
+ * bypass inheritance (local-only). 1:1 with Py `resolve_document`.
382
+ */
383
+ async resolveDocument(scope, kind, name, opts) {
384
+ const k = this._k;
385
+ const tenant = opts?.tenant ?? null;
386
+ // ── Bootstrap Kinds — local-only ─────────────────────────────
387
+ if (BOOTSTRAP_KINDS.has(kind)) {
388
+ const raw = await k._granularDoc([scope, kind, name, tenant ?? ""]);
389
+ const layer = new ResolutionLayer({
390
+ scope,
391
+ tenant,
392
+ found: raw !== null,
393
+ contributed: raw !== null,
394
+ });
395
+ return new ResolvedDocument({
396
+ doc: raw,
397
+ provenance: new ResolutionPath([layer]),
398
+ isInherited: false,
399
+ });
400
+ }
401
+ // ── Resolve composition rule ─────────────────────────────────
402
+ const [scopeInh, mergeStrat, tenantOv] = await this.getCompositionRule(scope, kind);
403
+ // ── Build resolution chain ───────────────────────────────────
404
+ // Catalog scopes that contributed THIS (kind, name) — used to surface
405
+ // multi-package conflicts after the merge (Phase 3b ch4, i-112).
406
+ let catalogLayerScopes = new Set();
407
+ let chain;
408
+ if (scopeInh === "disabled") {
409
+ // Local-only: bootstrap/structural Kinds never inherit AND never
410
+ // pick up the Catalog tier (matches today's behavior exactly).
411
+ chain = [];
412
+ if (tenant && tenantOv !== "none")
413
+ chain.push([scope, tenant]);
414
+ chain.push([scope, null]);
415
+ }
416
+ else {
417
+ chain = await this.computeResolutionChain(scope, tenantOv !== "none" ? tenant : null);
418
+ // ── Splice the Catalog tier: Local > Catalog > Base ──────────
419
+ // Insert the tenant's Catalog scopes IMMEDIATELY AFTER the local
420
+ // scope's entries (the leading (scope, …) pairs) and BEFORE the
421
+ // first parent — so the positional merge yields Local > Catalog >
422
+ // Base while preserving mergeFieldLevel (first contributor =
423
+ // primary). Fail-soft: a Catalog glitch must never crash a resolve.
424
+ let catalogScopes;
425
+ try {
426
+ catalogScopes = await k._catalogScopes(tenant, { exclude: new Set([scope]) });
427
+ }
428
+ catch {
429
+ catalogScopes = []; // fail-soft, never crash a resolve
430
+ }
431
+ if (catalogScopes.length > 0) {
432
+ let localLen = 0;
433
+ for (const [cs] of chain) {
434
+ if (cs === scope)
435
+ localLen += 1;
436
+ else
437
+ break;
438
+ }
439
+ const localPart = chain.slice(0, localLen);
440
+ const rest = chain.slice(localLen);
441
+ const catalogEntries = catalogScopes.map(([cs, ct]) => [cs, ct]);
442
+ catalogLayerScopes = new Set(catalogEntries.map(([cs]) => cs));
443
+ chain = [...localPart, ...catalogEntries, ...rest];
444
+ }
445
+ }
446
+ // ── Query each layer (direct source reads — see PERF note in the
447
+ // module docstring: the Py twin caches here; TS does not) ──────
448
+ const contributions = [];
449
+ // Catalog scopes that actually held this (kind, name) — for the
450
+ // multi-package conflict surface below.
451
+ const catalogHits = [];
452
+ for (const [layerScope, layerTenant] of chain) {
453
+ const raw = await k._granularDoc([layerScope, kind, name, layerTenant ?? ""]);
454
+ // NOTE: the Py twin registers `_layer_observers` reverse-deps here
455
+ // (cache invalidation infra). Intentionally not ported — the TS
456
+ // kernel has no granular cache to invalidate.
457
+ if (raw !== null && catalogLayerScopes.has(layerScope)) {
458
+ catalogHits.push(layerScope);
459
+ }
460
+ contributions.push([
461
+ new ResolutionLayer({
462
+ scope: layerScope,
463
+ tenant: layerTenant,
464
+ found: raw !== null,
465
+ }),
466
+ raw,
467
+ ]);
468
+ }
469
+ // Surface (don't fail) when ≥2 Catalog packages provide the same
470
+ // (kind, name): the sorted-first catalog scope wins positionally; the
471
+ // rest are shadowed. Determinism is guaranteed by _catalogScopes' sort.
472
+ if (catalogHits.length >= 2) {
473
+ console.info(`catalog tier conflict: ${kind}/${name} provided by ${catalogHits.length} `
474
+ + `catalog packages ${JSON.stringify(catalogHits)} for scope=${JSON.stringify(scope)} `
475
+ + `tenant=${JSON.stringify(tenant)} — ${JSON.stringify(catalogHits[0])} wins `
476
+ + `(sorted-first); others shadowed.`);
477
+ }
478
+ // ── Apply merge strategy ─────────────────────────────────────
479
+ let mergedDoc;
480
+ let primary;
481
+ let contributionsByField = {};
482
+ if (mergeStrat === "field_level") {
483
+ [mergedDoc, primary, contributionsByField] = mergeFieldLevel(contributions);
484
+ }
485
+ else {
486
+ [mergedDoc, primary] = mergeOverrideFull(contributions);
487
+ }
488
+ // ── Build provenance with contributed flag ───────────────────
489
+ const stepsWithContributed = [];
490
+ for (const [layer, raw] of contributions) {
491
+ const contributed = ((mergeStrat === "field_level" && raw !== null)
492
+ || (primary !== null && layerEquals(layer, primary)));
493
+ stepsWithContributed.push(new ResolutionLayer({
494
+ scope: layer.scope,
495
+ tenant: layer.tenant,
496
+ found: layer.found,
497
+ contributed,
498
+ versionSha: layer.versionSha,
499
+ }));
500
+ }
501
+ const provenance = new ResolutionPath(stepsWithContributed);
502
+ const isInherited = primary !== null && primary.scope !== scope;
503
+ return new ResolvedDocument({
504
+ doc: mergedDoc,
505
+ provenance,
506
+ isInherited,
507
+ contributionsByField,
508
+ });
509
+ }
510
+ /**
511
+ * Clone an inherited doc into `targetScope` as a local override
512
+ * (Phase 17). Throws if the doc isn't inherited / target exists (without
513
+ * overwrite). Clones spec + envelope atomically via `writeDocument`.
514
+ * 1:1 with Py `personalize_document` EXCEPT bundle-entry payload cloning
515
+ * (Py-only — see divergence #4 in the module docstring).
516
+ */
517
+ async personalizeDocument(targetScope, kind, name, opts) {
518
+ const k = this._k;
519
+ const tenant = opts?.tenant ?? null;
520
+ const overwrite = opts?.overwrite ?? false;
521
+ if (BOOTSTRAP_KINDS.has(kind)) {
522
+ throw new Error(`Kind ${JSON.stringify(kind)} is bootstrap and cannot be personalized.`);
523
+ }
524
+ const resolved = await this.resolveDocument(targetScope, kind, name, { tenant });
525
+ if (resolved.doc === null) {
526
+ throw new Error(`${kind}/${name} not found in any scope via composition chain from ${targetScope}.`);
527
+ }
528
+ if (!resolved.isInherited) {
529
+ throw new Error(`${kind}/${name} is already local to ${targetScope} — no need to personalize.`);
530
+ }
531
+ // Check targetScope local existence (fresh source state — no cache
532
+ // on TS anyway; the loadOne feature-test mirrors Py's getattr guard).
533
+ const src = k.activeSource;
534
+ if (!overwrite && src !== null && sourceCapabilities(src).granularOne && typeof src.loadOne === "function") {
535
+ const existing = await src.loadOne(targetScope, kind, name, { tenant });
536
+ if (existing !== null) {
537
+ throw new Error(`${kind}/${name} already exists locally in ${targetScope}. `
538
+ + `Pass overwrite=true to replace.`);
539
+ }
540
+ }
541
+ const eff = resolved.provenance.effectiveLayer;
542
+ if (eff === null) {
543
+ throw new Error("Cannot personalize: provenance has no effective layer.");
544
+ }
545
+ const doc = resolved.doc;
546
+ const clonedRaw = {
547
+ apiVersion: doc.apiVersion ?? null,
548
+ kind,
549
+ metadata: { ...(doc.metadata ?? {}), name },
550
+ spec: { ...(doc.spec ?? {}) },
551
+ };
552
+ await k.writeDocument(targetScope, kind, name, clonedRaw, { tenant });
553
+ // Bundle-entry (binary payload) cloning is Py-only today — the TS
554
+ // kernel has no bundle-entry write surface (divergence #4).
555
+ // Return fresh resolution (now local).
556
+ return await this.resolveDocument(targetScope, kind, name, { tenant });
557
+ }
558
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @deprecated shim — import from `./composition-resolver.js` instead.
3
+ *
4
+ * The CompositionProfile (V1) types moved into the unified composition
5
+ * motor (s-unify-composition-subsystems); this module remains only so
6
+ * external callers keep importing. It will be removed in a future major.
7
+ * Py twin: `dna/kernel/composition.py`.
8
+ */
9
+ export type { CompositionProfile, CompositionSlot, HealthCheckHint, QuadrantHint, TimelineHint, } from "./composition-resolver.js";
10
+ export { profileForOrchestrator } from "./composition-resolver.js";
@@ -0,0 +1 @@
1
+ export { profileForOrchestrator } from "./composition-resolver.js";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Parse every `<relKindsDir>/*.kind.yaml` next to the calling module.
3
+ *
4
+ * @param moduleUrl the caller's `import.meta.url`
5
+ * @param relKindsDir dir relative to the module, e.g. `"sdlc/kinds"` for
6
+ * `src/extensions/sdlc.ts` → `src/extensions/sdlc/kinds/`
7
+ *
8
+ * Returns the raw objects sorted by filename (deterministic registration
9
+ * order). A missing dir returns `[]` — extensions can call this
10
+ * unconditionally. A descriptor that isn't a YAML mapping throws (a broken
11
+ * packaged descriptor is a packaging bug, never a silent skip).
12
+ */
13
+ export declare function loadDescriptors(moduleUrl: string, relKindsDir: string): Record<string, unknown>[];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * F3 (spec D3): builtin Kind descriptors as package data.
3
+ *
4
+ * Extensions ship builtin record Kinds as `kinds/*.kind.yaml` files next to
5
+ * their module (same KindDefinition format as per-scope KIND.yaml docs — one
6
+ * format, one funnel). `loadDescriptors` reads them relative to the calling
7
+ * module's `import.meta.url` (the same pattern the template-owning extensions
8
+ * use; package.json `files` ships `src/extensions/* /kinds/**`) and hands the
9
+ * parsed raws to `kernel.kindFromDescriptor`.
10
+ *
11
+ * The Python twin is `dna/kernel/descriptor_loader.py`; the
12
+ * descriptor FILES are parity-critical (byte-identical Py↔TS — see
13
+ * `tests/descriptor-hash-parity.test.ts`).
14
+ */
15
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
16
+ import { dirname, join } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import { load as yamlLoad } from "js-yaml";
19
+ const SUFFIX = ".kind.yaml";
20
+ /**
21
+ * Parse every `<relKindsDir>/*.kind.yaml` next to the calling module.
22
+ *
23
+ * @param moduleUrl the caller's `import.meta.url`
24
+ * @param relKindsDir dir relative to the module, e.g. `"sdlc/kinds"` for
25
+ * `src/extensions/sdlc.ts` → `src/extensions/sdlc/kinds/`
26
+ *
27
+ * Returns the raw objects sorted by filename (deterministic registration
28
+ * order). A missing dir returns `[]` — extensions can call this
29
+ * unconditionally. A descriptor that isn't a YAML mapping throws (a broken
30
+ * packaged descriptor is a packaging bug, never a silent skip).
31
+ */
32
+ export function loadDescriptors(moduleUrl, relKindsDir) {
33
+ const kindsDir = join(dirname(fileURLToPath(moduleUrl)), relKindsDir);
34
+ if (!existsSync(kindsDir))
35
+ return [];
36
+ const raws = [];
37
+ for (const name of readdirSync(kindsDir).sort()) {
38
+ if (!name.endsWith(SUFFIX))
39
+ continue;
40
+ const raw = yamlLoad(readFileSync(join(kindsDir, name), "utf-8"));
41
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
42
+ throw new Error(`descriptor ${relKindsDir}/${name} must be a YAML mapping ` +
43
+ `(KindDefinition), got ${Array.isArray(raw) ? "array" : typeof raw}`);
44
+ }
45
+ raws.push(raw);
46
+ }
47
+ return raws;
48
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Document — unified wrapper for all manifest documents.
3
+ *
4
+ * 1:1 parity with Python dna.v3.kernel.document.
5
+ *
6
+ * v1.0 — `Document<SpecT>` generic typing. Consumers can declare
7
+ * spec type at the call site without a runtime cost:
8
+ *
9
+ * const doc = mi.documents.find((d) => d.kind === "Asset" && d.name === "x") as Document<AssetSpec>;
10
+ * doc.spec.summary?.byte_count; // type-checker validated
11
+ *
12
+ * Bare `Document` defaults to `Record<string, unknown>` so existing
13
+ * untyped code continues working. Purely additive — no API change
14
+ * for callers that don't opt in.
15
+ */
16
+ export declare class Document<SpecT extends Record<string, unknown> = Record<string, unknown>> {
17
+ readonly apiVersion: string;
18
+ readonly kind: string;
19
+ readonly name: string;
20
+ readonly raw: Record<string, unknown>;
21
+ readonly typed: unknown;
22
+ readonly origin: string;
23
+ private readonly _metadataRaw;
24
+ private readonly _specRaw;
25
+ constructor(opts: {
26
+ apiVersion: string;
27
+ kind: string;
28
+ name: string;
29
+ metadata?: Record<string, unknown>;
30
+ spec?: Record<string, unknown>;
31
+ raw?: Record<string, unknown>;
32
+ typed?: unknown;
33
+ origin?: string;
34
+ });
35
+ /** Always returns Record<string, unknown> — typed metadata when available, raw dict otherwise. */
36
+ get metadata(): Record<string, unknown>;
37
+ /** Returns the spec, typed as `SpecT` when consumers parameterize
38
+ * the Document. Runtime: still a plain dict. */
39
+ get spec(): SpecT;
40
+ /** Create a Document from a raw dict. */
41
+ static fromRaw(raw: Record<string, unknown>, typed?: unknown, origin?: string): Document;
42
+ toString(): string;
43
+ }