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,710 @@
1
+ /**
2
+ * KindRegistry — the kernel's registered-Kind identity map + the full
3
+ * registration funnel, extracted from the Kernel god-object (kernel
4
+ * decomposition, Fase 3 — `s-kernel-decomp-ts-parity`; Py twin:
5
+ * `dna/kernel/kind_registry.py`).
6
+ *
7
+ * Two surfaces live here:
8
+ *
9
+ * - **Lookup** (pre-F3): the `kinds` map + the read surface (port lookup,
10
+ * alias, storage/container resolution, container→kind, describe). Also
11
+ * exported as the PURE functions `kindPortForOver` / `resolveDepFilterTargetOver`
12
+ * so a registry VIEW over an existing map (CompositionEngine / nav_kernel /
13
+ * the ManifestInstance) shares the ONE canonical resolver without holding a
14
+ * class instance.
15
+ * - **Registration** (this slice): `registerKind` (the H1 validation funnel —
16
+ * interface/dup-key/dup-alias/BUNDLE-marker/plane/i-195-name-collision +
17
+ * alias generation), the static `_lintPlane` helper, `registerFromDescriptor`
18
+ * (builtin `*.kind.yaml` descriptors) and `registerKindDefinitions` (the
19
+ * per-scope KindDefinition funnel — warn+skip instead of throw). The kernel
20
+ * keeps `kind()` / `kindFromDescriptor()` / `_registerKindDefinitions()` /
21
+ * `_registerCustomKinds()` as THIN facades delegating here.
22
+ *
23
+ * Registration mutates `this.kinds` directly (the registry OWNS the map; the
24
+ * kernel's `_kinds` getter proxies to it, so the ~20 inline `this._kinds` read
25
+ * sites across the kernel keep working unchanged). Side effects that touch the
26
+ * wider kernel — hooks (`kinddef_conflict` / `parse_error` events), the
27
+ * `_readers` list (the rescan return gate), the generic reader/writer wiring,
28
+ * the `_genericsResolved` flag, and the `_loadingExtOwner` alias-owner context —
29
+ * route through a NARROW `RegistryHost` back-ref (the anti-cosmetic rule, spec
30
+ * §3.1): the kernel satisfies it structurally. View-only registries pass no host
31
+ * and use only the lookup surface / pure functions.
32
+ *
33
+ * One registry per kernel, shared across `withTenant` shallow copies (Kinds are
34
+ * global — registered once at boot on the base kernel).
35
+ *
36
+ * The map is keyed `${apiVersion}\0${kind}` — the same shape held by both the
37
+ * Kernel and the ManifestInstance.
38
+ */
39
+ import { fileURLToPath } from "node:url";
40
+ import { dirname, basename, join } from "node:path";
41
+ import { existsSync, readFileSync } from "node:fs";
42
+ import { KindRegistrationError } from "./errors.js";
43
+ import { SD } from "./protocols.js";
44
+ import { DeclarativeKindPort } from "./meta.js";
45
+ import { documentHash } from "./lock.js";
46
+ import { KindDefinitionSchema, KIND_DEFINITION_API_VERSION, KIND_DEFINITION_KIND, } from "./models.js";
47
+ // ---------------------------------------------------------------------------
48
+ // Kind documentation resolution (Py twin: `kind_registry._load_kind_docs`).
49
+ // ---------------------------------------------------------------------------
50
+ /**
51
+ * Resolve a kind's prose documentation.
52
+ *
53
+ * Resolution order:
54
+ * 1. DOCS-<KindName>.md next to the extension's source file
55
+ * (inside `<basename>/`, where basename is the extension .ts stem)
56
+ * 2. DOCS.md in that same directory
57
+ * 3. The `docs` attribute on the KindPort
58
+ * 4. null
59
+ *
60
+ * Browser consumers (Tauri Studio / Vite) stub node:fs/path/url via
61
+ * `nodeStubPlugin` in vite.config.ts — filesystem reads return empty and
62
+ * the loader transparently falls through to `kp.docs`.
63
+ */
64
+ export function loadKindDocs(kp) {
65
+ const sourceUrl = kp._sourceUrl;
66
+ if (sourceUrl) {
67
+ try {
68
+ const file = fileURLToPath(sourceUrl);
69
+ const dir = dirname(file);
70
+ const stem = basename(file).replace(/\.[tj]s$/, "");
71
+ const extDir = join(dir, stem);
72
+ const candidates = [join(extDir, `DOCS-${kp.kind}.md`), join(extDir, "DOCS.md")];
73
+ for (const candidate of candidates) {
74
+ if (existsSync(candidate)) {
75
+ const text = readFileSync(candidate, "utf-8").trim();
76
+ if (text)
77
+ return text;
78
+ }
79
+ }
80
+ }
81
+ catch {
82
+ // fall through to docs attr
83
+ }
84
+ }
85
+ return kp.docs ?? null;
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // s-alias-generated-not-typed — alias generation + canonical dep_filter
89
+ // resolution. Aliases used to be hand-typed strings on every Kind class
90
+ // (~46 divergences from the <owner>-<kebab(kind)> convention + one
91
+ // recorded bug: "policy-layer" reversed/truncated). New Kinds OMIT the
92
+ // alias and get it generated; legacy aliases stay untouched (live wire
93
+ // format in dep_filters / Mustache / LayerPolicy docs). Py twin:
94
+ // dna.kernel.kind_registry.
95
+ // ---------------------------------------------------------------------------
96
+ /** CamelCase kind name → kebab-case: EvalCase → eval-case, ADR → adr,
97
+ * HTMLThing → html-thing. */
98
+ export function kebabKindName(kind) {
99
+ let s = kind.replace(/(?<=[a-z0-9])(?=[A-Z])/g, "-");
100
+ s = s.replace(/(?<=[A-Z])(?=[A-Z][a-z])/g, "-");
101
+ return s.toLowerCase();
102
+ }
103
+ /** The canonical alias for a Kind: `<owner>-<kebab(kind)>`. */
104
+ export function generateAlias(owner, kind) {
105
+ return `${owner}-${kebabKindName(kind)}`;
106
+ }
107
+ /** Ratchet (shrink-only): every builtin CLASS Kind that still hand-types
108
+ * its alias — the live wire format that CANNOT be renamed without a doc
109
+ * migration. New Kinds must OMIT the alias (generation). Entries leave
110
+ * as classes migrate to generation/descriptors — NEVER add one
111
+ * (tests/alias-generation.test.ts, ratchet test). Py twin:
112
+ * dna.kernel.kind_registry.EXPLICIT_ALIAS_ALLOWLIST — the TS set
113
+ * excludes the Py-only extensions (gaia-*, collab-comment) and includes
114
+ * every TS class port, per kind-registry-parity.json. */
115
+ export const EXPLICIT_ALIAS_ALLOWLIST = new Set([
116
+ // helix
117
+ "helix-genome", "helix-agent", "helix-actor",
118
+ "helix-usecase", "helix-tool", "policy-layer-policy",
119
+ "helix-canvas",
120
+ "helix-setting", "helix-theme", "helix-user-profile",
121
+ // sdlc (classes; descriptors are outside the ratchet)
122
+ "sdlc-roadmap", "sdlc-epic", "sdlc-feature", "sdlc-story",
123
+ "sdlc-issue", "sdlc-bug", "sdlc-task", "sdlc-spike",
124
+ "sdlc-initiative", "sdlc-spec", "sdlc-plan", "sdlc-agent-session",
125
+ "sdlc-reference",
126
+ // eval
127
+ // blocks
128
+ // single-kind extensions
129
+ "agentskills-skill", "soulspec-soul", "agentsmd-agent",
130
+ "guardrails-guardrail", "helix-hook",
131
+ "helix-safety-policy", "presidio-recognizer",
132
+ "kinddef-kinddefinition",
133
+ "evidence-policy", "federation-mcp",
134
+ // s-automation-trio-extinction: jobs-jobtype / hooktype-hooktype /
135
+ // scheduletype-scheduletype extintos (unificados no Kind Automation).
136
+ "lesson-lesson",
137
+ "tenant-tenant", "tenant-membership",
138
+ "audit-userroleassignment",
139
+ "testkit-test-guide", "testkit-test-run",
140
+ ]);
141
+ /** i-195 — kind names allowed to exist under MULTIPLE apiVersions in the
142
+ * extension/builtin funnel. SHRINK-ONLY ratchet: the Reference pair
143
+ * (github.com/ruinosus/dna/research/v1 + github.com/ruinosus/dna/sdlc/v1) predates the guard and is scheduled
144
+ * to be merged by the Reference-family unification follow-up; when that
145
+ * lands, empty this set. NEVER add a name here — rename the new Kind
146
+ * instead (bare-name lookups become ambiguous the moment two
147
+ * apiVersions share a kind name). Py twin:
148
+ * dna.kernel.kind_registry.KIND_NAME_COLLISION_ALLOWLIST. */
149
+ export const KIND_NAME_COLLISION_ALLOWLIST = new Set(["Reference"]);
150
+ // ---------------------------------------------------------------------------
151
+ // Pure lookup functions — shared with registry VIEWS (CompositionEngine /
152
+ // nav_kernel / the ManifestInstance) that wrap a kinds map WITHOUT a class
153
+ // instance. The KindRegistry methods below delegate to these so there is ONE
154
+ // canonical implementation. Py twin: `KindRegistry.port_for` /
155
+ // `KindRegistry.resolve_dep_filter_target`.
156
+ // ---------------------------------------------------------------------------
157
+ /**
158
+ * Lookup a registered KindPort by kind name (case-sensitive) over `kinds`.
159
+ * With `apiVersion` the lookup is EXACT on (apiVersion, kind) — bare
160
+ * lookups on a name shared by multiple apiVersions resolve
161
+ * extension-first then registration order (i-195, Py twin:
162
+ * `KindRegistry.port_for`).
163
+ */
164
+ export function kindPortForOver(kinds, kind, apiVersion) {
165
+ if (apiVersion !== undefined) {
166
+ return kinds.get(`${apiVersion}\0${kind}`) ?? null;
167
+ }
168
+ const matches = [];
169
+ for (const kp of kinds.values()) {
170
+ if (kp.kind === kind)
171
+ matches.push(kp);
172
+ }
173
+ if (matches.length === 0)
174
+ return null;
175
+ if (matches.length > 1) {
176
+ // per-scope DeclarativeKindPorts carry __declarative__ WITHOUT
177
+ // __builtin_descriptor__; extension classes + builtin descriptors
178
+ // must win the bare name (i-195).
179
+ const extensionFirst = matches.filter((kp) => {
180
+ const p = kp;
181
+ return p.__declarative__ !== true || p.__builtin_descriptor__ === true;
182
+ });
183
+ if (extensionFirst.length > 0)
184
+ return extensionFirst[0];
185
+ }
186
+ return matches[0];
187
+ }
188
+ /**
189
+ * Canonical dep_filter target resolution (s-alias-generated-not-typed).
190
+ *
191
+ * The CONTRACT is alias-valued dep_filters (`"soulspec-soul"`). The
192
+ * legacy `"kind=<Name>"` format resolves through a DEPRECATED shim so
193
+ * per-scope KindDefinition docs keep working (builtin extensions must be
194
+ * alias-pure — `validateDepFilters` rejects `kind=` there). Since
195
+ * s-unify-composition-subsystems this is THE resolver for every
196
+ * dep_filter reader — `validateRefs` / `mi.composition` and the Kernel.
197
+ * Py twin: `KindRegistry.resolve_dep_filter_target`.
198
+ */
199
+ export function resolveDepFilterTargetOver(kinds, value) {
200
+ if (value.startsWith("kind=")) {
201
+ console.warn(`dep_filter value ${JSON.stringify(value)} uses the legacy 'kind=' ` +
202
+ `format — use the target Kind's alias instead ` +
203
+ `(s-alias-generated-not-typed).`);
204
+ return kindPortForOver(kinds, value.slice("kind=".length));
205
+ }
206
+ for (const kp of kinds.values()) {
207
+ if (kp.alias === value)
208
+ return kp;
209
+ }
210
+ return null;
211
+ }
212
+ /**
213
+ * Holds the registered KindPorts + the lookups + the registration funnel.
214
+ */
215
+ export class KindRegistry {
216
+ /** Key: "apiVersion\0kind" for uniqueness. OWNED here; the kernel's
217
+ * `_kinds` getter proxies to it. */
218
+ kinds;
219
+ /** The NARROW `RegistryHost` back-ref used ONLY by the registration
220
+ * funnel (hooks fan-out, the `_readers` rescan gate, generic
221
+ * reader/writer wiring, `_genericsResolved`, the `_loadingExtOwner`
222
+ * alias-owner context). Only read at registration time (boot) — so the
223
+ * kernel may pass `this` before its `hooks`/`_readers` are fully wired.
224
+ * View-only registries pass `null` and never call `register*`. */
225
+ _host;
226
+ constructor(kinds, host) {
227
+ this.kinds = kinds ?? new Map();
228
+ this._host = host ?? null;
229
+ }
230
+ // -- Lookup ---------------------------------------------------------------
231
+ /** Lookup a registered KindPort by kind name (case-sensitive). With
232
+ * apiVersion the lookup is EXACT on (apiVersion, kind); bare lookups on
233
+ * an ambiguous name resolve extension-first then registration order
234
+ * (i-195). Py twin: `KindRegistry.port_for`. */
235
+ portFor(kind, apiVersion) {
236
+ return kindPortForOver(this.kinds, kind, apiVersion);
237
+ }
238
+ /** All registered KindPorts. Order matches registration. */
239
+ allPorts() {
240
+ return Array.from(this.kinds.values());
241
+ }
242
+ /** Resolve a kind name to its globally-unique alias (`<owner>-<kind>`).
243
+ * Falls back to `kind.toLowerCase()` when no registered port provides
244
+ * one. Py twin: `KindRegistry.alias_for`. */
245
+ aliasFor(kind, apiVersion) {
246
+ const port = this.portFor(kind, apiVersion);
247
+ const alias = port ? port.alias : null;
248
+ return alias ? alias : kind.toLowerCase();
249
+ }
250
+ /** Return the storage container directory for a kind, or null. */
251
+ containerFor(kindName) {
252
+ const kp = this.portFor(kindName);
253
+ if (!kp)
254
+ return null;
255
+ return kp.storage?.container ?? null;
256
+ }
257
+ /** Return the StorageDescriptor for a kind, or null if unknown. */
258
+ storageFor(kindName) {
259
+ const kp = this.portFor(kindName);
260
+ return kp ? (kp.storage ?? null) : null;
261
+ }
262
+ /** Return the kind name whose StorageDescriptor.container matches.
263
+ * null for empty container (ROOT kinds) or unregistered containers. */
264
+ byContainer(container) {
265
+ if (!container)
266
+ return null;
267
+ for (const kp of this.kinds.values()) {
268
+ const sd = kp.storage ?? null;
269
+ if (sd !== null && sd.container === container)
270
+ return kp.kind;
271
+ }
272
+ return null;
273
+ }
274
+ /** Canonical dep_filter target resolution (s-alias-generated-not-typed).
275
+ * Delegates to the shared `resolveDepFilterTargetOver`. Py twin:
276
+ * `KindRegistry.resolve_dep_filter_target`. */
277
+ resolveDepFilterTarget(value) {
278
+ return resolveDepFilterTargetOver(this.kinds, value);
279
+ }
280
+ /** Summary dict for a registered kind, including resolved docs. Py twin:
281
+ * `KindRegistry.describe`. */
282
+ describe(kindName) {
283
+ for (const kp of this.kinds.values()) {
284
+ if (kp.kind === kindName) {
285
+ const resolved = kp._resolvedDocs;
286
+ return {
287
+ kind: kp.kind,
288
+ alias: kp.alias,
289
+ apiVersion: kp.apiVersion,
290
+ isRoot: kp.isRoot,
291
+ isPromptTarget: kp.isPromptTarget,
292
+ docs: resolved ?? kp.docs ?? null,
293
+ };
294
+ }
295
+ }
296
+ return null;
297
+ }
298
+ /** s-alias-generated-not-typed — every dep_filter target of an
299
+ * EXTENSION-registered Kind must resolve to a registered alias.
300
+ *
301
+ * Aliases are the wire key of dep_filters / Mustache sections /
302
+ * LayerPolicy — a typo used to degrade the prompt SILENTLY (the dep
303
+ * just vanished from the context, warning buried in logs). Called at
304
+ * the end of `loadBuiltins()` (the TS twin of `Kernel.auto()`).
305
+ *
306
+ * - Extension/builtin port with an unknown alias OR the legacy
307
+ * `kind=` format → `KindRegistrationError` (boot fails loud).
308
+ * - Per-scope declarative ports (user KindDefinition docs) only WARN
309
+ * — user docs never take the boot down (same posture as the
310
+ * parse_error / plane-lint funnels).
311
+ * Py twin: `KindRegistry.validate_dep_filters`. */
312
+ validateDepFilters() {
313
+ const problems = [];
314
+ for (const kp of this.allPorts()) {
315
+ let filters;
316
+ try {
317
+ filters = kp.depFilters();
318
+ }
319
+ catch {
320
+ // port quebrado não derruba os demais
321
+ continue;
322
+ }
323
+ if (!filters)
324
+ continue;
325
+ const p = kp;
326
+ const isDeclarative = p.__declarative__ === true && p.__builtin_descriptor__ !== true;
327
+ for (const [field, value] of Object.entries(filters)) {
328
+ if (typeof value !== "string")
329
+ continue;
330
+ let msg;
331
+ if (value.startsWith("kind=")) {
332
+ msg =
333
+ `${kp.kind}.depFilters[${JSON.stringify(field)}] uses the ` +
334
+ `legacy 'kind=' format (${JSON.stringify(value)}) — use the ` +
335
+ `target Kind's alias (builtin extensions are alias-pure).`;
336
+ }
337
+ else {
338
+ // Polymorphic refs (WorkflowEvent.ref) declare a PIPE-UNION of
339
+ // aliases — validate each term.
340
+ const unknown = value
341
+ .split("|")
342
+ .filter((part) => this.resolveDepFilterTarget(part) === null);
343
+ if (unknown.length === 0)
344
+ continue;
345
+ msg =
346
+ `${kp.kind}.depFilters[${JSON.stringify(field)}] points at ` +
347
+ `unknown alias(es) ${JSON.stringify(unknown)} — the dep would ` +
348
+ `silently vanish from prompts/composition.`;
349
+ }
350
+ if (isDeclarative) {
351
+ console.warn(`[kernel] dep_filter (per-scope): ${msg}`);
352
+ }
353
+ else {
354
+ problems.push(msg);
355
+ }
356
+ }
357
+ }
358
+ if (problems.length > 0) {
359
+ throw new KindRegistrationError("dep_filter validation failed (s-alias-generated-not-typed):\n " +
360
+ problems.join("\n "));
361
+ }
362
+ }
363
+ // -- Registration funnel (Fase 3) -----------------------------------------
364
+ /** H1 registration funnel — moved verbatim from `Kernel.kind()`. The
365
+ * kernel keeps `kind()` as a thin facade delegating here. Py twin:
366
+ * `KindRegistry.register_kind`. */
367
+ registerKind(k) {
368
+ // H1 — Boot-time validation (interface + uniqueness + marker collision).
369
+ // Catches the failure modes that previously surfaced at runtime as
370
+ // silent overwrites or first-match-wins scanner bugs:
371
+ // 1. Object doesn't satisfy KindPort interface → registration error
372
+ // 2. Duplicate (apiVersion, kind) tuple → registration error
373
+ // 3. Duplicate alias across registered Kinds → registration error
374
+ // 4. BUNDLE-pattern (container, marker) clash → registration error
375
+ const required = [
376
+ "apiVersion", "kind", "alias", "isRoot", "isPromptTarget",
377
+ "promptTargetPriority", "flattenInContext", "storage",
378
+ ];
379
+ for (const attr of required) {
380
+ if (!(attr in k)) {
381
+ throw new KindRegistrationError(`Kind ${k?.constructor?.name ?? typeof k} ` +
382
+ `does not satisfy KindPort interface (missing ${attr}). See ` +
383
+ `typescript/src/kernel/protocols.ts.`);
384
+ }
385
+ }
386
+ const requiredMethods = [
387
+ "depFilters", "getDefaultAgentName", "getLayerPolicies",
388
+ "parse", "describe", "summary", "promptTemplate",
389
+ ];
390
+ for (const m of requiredMethods) {
391
+ if (typeof k[m] !== "function") {
392
+ throw new KindRegistrationError(`Kind ${k?.constructor?.name ?? typeof k} ` +
393
+ `does not satisfy KindPort interface (missing ${m}() method). See ` +
394
+ `typescript/src/kernel/protocols.ts.`);
395
+ }
396
+ }
397
+ // s-alias-generated-not-typed — Kind sem alias declarado ganha o
398
+ // gerado <owner>-<kebab(kind)>. Owner: attr explícito no port →
399
+ // contexto da Extension sendo carregada (kernel.load) → 1º token
400
+ // do apiVersion. Aliases legados digitados ficam intocados (wire
401
+ // format vivo); o ratchet EXPLICIT_ALIAS_ALLOWLIST impede Kind
402
+ // NOVO de digitar um. Py twin: Kernel.kind().
403
+ if (!k.alias) {
404
+ const owner = k.aliasOwner ??
405
+ this._host?._loadingExtOwner ??
406
+ k.apiVersion.split(".")[0].split("/")[0];
407
+ k.alias = generateAlias(owner, k.kind);
408
+ k.__alias_generated__ = true;
409
+ }
410
+ // Two-planes lint (spec D1) — extracted to a helper (F3 spec D3) so
411
+ // the per-scope KindDefinition funnel (registerKindDefinitions)
412
+ // runs the SAME validation.
413
+ KindRegistry._lintPlane(k);
414
+ const key = `${k.apiVersion}\0${k.kind}`;
415
+ if (this.kinds.has(key)) {
416
+ const existing = this.kinds.get(key);
417
+ // F3 (spec D3) — declarative ports are ALL the same class
418
+ // (DeclarativeKindPort), so the constructor check below would
419
+ // silently no-op two DIFFERENT descriptors claiming the same key.
420
+ // Real identity on the declarative path is the descriptor digest:
421
+ // same digest → idempotent no-op; different digest → error.
422
+ const existingDecl = existing.__declarative__ === true;
423
+ const newDecl = k.__declarative__ === true;
424
+ if (existingDecl && newDecl) {
425
+ const existingDigest = existing.__descriptor_digest__ ?? null;
426
+ const newDigest = k.__descriptor_digest__ ?? null;
427
+ if (existingDigest === newDigest) {
428
+ return;
429
+ }
430
+ throw new KindRegistrationError(`Kind (${JSON.stringify(k.apiVersion)}, ${JSON.stringify(k.kind)}) ` +
431
+ `already registered from a DIFFERENT descriptor (existing alias ` +
432
+ `${JSON.stringify(existing.alias)}, new alias ${JSON.stringify(k.alias)}). ` +
433
+ `Two descriptors cannot claim the same (apiVersion, kind) key — ` +
434
+ `pick a distinct apiVersion namespace.`);
435
+ }
436
+ // Idempotent re-registration: same class re-registering is a
437
+ // silent no-op + debug log. A *different* class trying to claim
438
+ // the same key IS still a registration error.
439
+ if (existing.constructor === k.constructor) {
440
+ return;
441
+ }
442
+ throw new KindRegistrationError(`Kind (${JSON.stringify(k.apiVersion)}, ${JSON.stringify(k.kind)}) ` +
443
+ `already registered by ${existing?.constructor?.name}; ` +
444
+ `refusing to overwrite with ` +
445
+ `${k?.constructor?.name}. ` +
446
+ `Two extensions cannot share the same (apiVersion, kind) pair.`);
447
+ }
448
+ if (k.alias) {
449
+ for (const [existingKey, existingKind] of this.kinds) {
450
+ if (existingKind.alias === k.alias) {
451
+ throw new KindRegistrationError(`Kind alias ${JSON.stringify(k.alias)} already registered by ` +
452
+ `${existingKind?.constructor?.name} ` +
453
+ `(${existingKey.replace("\0", ", ")}); refusing to register ` +
454
+ `${k?.constructor?.name}.`);
455
+ }
456
+ }
457
+ }
458
+ // 6. Kind-NAME collision across apiVersions (i-195, Py twin in
459
+ // Kernel.kind()). Bare-name lookups become ambiguous the moment two
460
+ // apiVersions share a kind name — new extension Kinds must pick a
461
+ // unique name; the legacy Reference pair is allowlisted (shrink-only
462
+ // ratchet, emptied by the Reference-family merge). Collisions where
463
+ // the EXISTING port is a per-scope declarative shadow don't block
464
+ // the extension from claiming its canonical name.
465
+ if (!KIND_NAME_COLLISION_ALLOWLIST.has(k.kind)) {
466
+ for (const [, existingKind] of this.kinds) {
467
+ const p = existingKind;
468
+ const isExtensionPort = p.__declarative__ !== true || p.__builtin_descriptor__ === true;
469
+ if (existingKind.kind === k.kind &&
470
+ existingKind.apiVersion !== k.apiVersion &&
471
+ isExtensionPort) {
472
+ throw new KindRegistrationError(`Kind NAME ${JSON.stringify(k.kind)} already registered under ` +
473
+ `apiVersion ${JSON.stringify(existingKind.apiVersion)} (alias ` +
474
+ `${JSON.stringify(existingKind.alias)}); refusing ` +
475
+ `${JSON.stringify(k.apiVersion)}. Two apiVersions sharing a ` +
476
+ `kind name makes every bare-name lookup ambiguous — pick a ` +
477
+ `distinct kind name (i-195).`);
478
+ }
479
+ }
480
+ }
481
+ const sd = k.storage;
482
+ if (sd && sd.pattern === "bundle") {
483
+ const newPair = `${sd.container}\0${sd.marker}`;
484
+ const newSharedOk = Boolean(k.markerSharedAllowed);
485
+ for (const [existingKey, existingKind] of this.kinds) {
486
+ const existingSd = existingKind.storage;
487
+ if (!existingSd || existingSd.pattern !== "bundle")
488
+ continue;
489
+ const existingPair = `${existingSd.container}\0${existingSd.marker}`;
490
+ if (existingPair !== newPair)
491
+ continue;
492
+ // H1 — explicit opt-in: only allow shared marker if BOTH
493
+ // colliding Kinds set markerSharedAllowed = true.
494
+ const existingSharedOk = Boolean(existingKind.markerSharedAllowed);
495
+ if (newSharedOk && existingSharedOk) {
496
+ continue;
497
+ }
498
+ throw new KindRegistrationError(`BUNDLE storage (container=${JSON.stringify(sd.container)}, ` +
499
+ `marker=${JSON.stringify(sd.marker)}) already registered by ` +
500
+ `${existingKind?.constructor?.name} ` +
501
+ `(${existingKey.replace("\0", ", ")}); refusing to register ` +
502
+ `${k?.constructor?.name}. ` +
503
+ `Pick a unique container OR marker — OR set ` +
504
+ `markerSharedAllowed = true on BOTH Kinds AND ensure their ` +
505
+ `Reader.detect() implementations disambiguate at read time.`);
506
+ }
507
+ }
508
+ this.kinds.set(key, k);
509
+ if (this._host)
510
+ this._host._genericsResolved = false;
511
+ try {
512
+ k._resolvedDocs = loadKindDocs(k);
513
+ }
514
+ catch {
515
+ k._resolvedDocs = k.docs ?? null;
516
+ }
517
+ }
518
+ /** Two-planes lint (spec D1) — plane is explicit and validated, never
519
+ * derived. A "record" Kind cannot carry any composition signal;
520
+ * contradictions fail registration loudly instead of silently
521
+ * mis-routing the write path.
522
+ *
523
+ * F3 (spec D3): extracted from `registerKind()` so BOTH funnels run it —
524
+ * `registerKind()` (extension classes + builtin descriptors) throws;
525
+ * `registerKindDefinitions` (per-scope) catches → warn + skip
526
+ * (per-scope docs never take the boot down). Mirrors Python
527
+ * KindRegistry._lint_plane. */
528
+ static _lintPlane(k) {
529
+ const plane = k.plane ?? "composition";
530
+ if (plane !== "composition" && plane !== "record") {
531
+ throw new KindRegistrationError(`Kind ${k.kind} has invalid plane=${JSON.stringify(plane)}; ` +
532
+ `expected 'composition' or 'record'.`);
533
+ }
534
+ if (plane === "record") {
535
+ const contradictions = [];
536
+ if (k.isPromptTarget)
537
+ contradictions.push("isPromptTarget=true");
538
+ if (k.flattenInContext)
539
+ contradictions.push("flattenInContext=true");
540
+ if (k.isSchemaAffecting) {
541
+ contradictions.push("isSchemaAffecting=true");
542
+ }
543
+ if (k.isRoot)
544
+ contradictions.push("storage.pattern==root");
545
+ if (contradictions.length > 0) {
546
+ throw new KindRegistrationError(`Kind ${k.kind} declares plane="record" but carries composition ` +
547
+ `signals: ${contradictions.join(", ")}. Records never compose ` +
548
+ `into agent prompts — drop the signal or remove plane="record".`);
549
+ }
550
+ }
551
+ }
552
+ /** F3 (spec D3): register a BUILTIN Kind from a KindDefinition
553
+ * descriptor (`kinds/*.kind.yaml` package data). Same format + same
554
+ * funnel as everything else. Py twin:
555
+ * `KindRegistry.register_from_descriptor`. */
556
+ registerFromDescriptor(raw) {
557
+ const typed = KindDefinitionSchema.parse(raw);
558
+ const port = DeclarativeKindPort.fromTyped(typed);
559
+ port.__builtin_descriptor__ = true;
560
+ port.__descriptor_digest__ =
561
+ documentHash(raw.spec ?? {});
562
+ this.registerKind(port);
563
+ // registerKind() no-ops on an idempotent re-register — hand back
564
+ // whatever is actually registered for the key.
565
+ return this.kinds.get(`${port.apiVersion}\0${port.kind}`);
566
+ }
567
+ /** Phase 1 of 2-phase loading: parse KindDefinition docs + register
568
+ * synthetic DeclarativeKindPorts. Extension-registered kinds win on
569
+ * conflict. Returns true iff new BUNDLE readers were added (the rescan
570
+ * gate). Py twin: `KindRegistry.register_kind_definitions`. */
571
+ registerKindDefinitions(rawDocs) {
572
+ const host = this._host;
573
+ const readersBefore = host._readers.length;
574
+ let registered = false;
575
+ for (const raw of rawDocs) {
576
+ if (raw.apiVersion !== KIND_DEFINITION_API_VERSION)
577
+ continue;
578
+ if (raw.kind !== KIND_DEFINITION_KIND)
579
+ continue;
580
+ let typed;
581
+ try {
582
+ typed = KindDefinitionSchema.parse(raw);
583
+ }
584
+ catch (e) {
585
+ console.warn(`Failed to parse KindDefinition: ${e}`);
586
+ if (host.hooks.has("parse_error")) {
587
+ host.hooks.emit("parse_error", {
588
+ scope: "",
589
+ kind: KIND_DEFINITION_KIND,
590
+ name: raw.metadata?.name ?? "",
591
+ data: { error: String(e) },
592
+ });
593
+ }
594
+ continue;
595
+ }
596
+ // Use typed.spec.<field> here: we just Zod-validated the KindDefinition
597
+ // so the typed model is authoritative. The readSpec* helpers exist for
598
+ // code that operates across kinds without knowing the type.
599
+ const key = `${typed.spec.target_api_version}\0${typed.spec.target_kind}`;
600
+ if (this.kinds.has(key)) {
601
+ const existing = this.kinds.get(key);
602
+ const isDeclarative = existing.__declarative__ === true;
603
+ const isBuiltinDescriptor = existing.__builtin_descriptor__ === true;
604
+ if (!isDeclarative || isBuiltinDescriptor) {
605
+ // Extension-registered kind wins on conflict — and so does a
606
+ // BUILTIN descriptor (F3 spec D3: builtin descriptors are
607
+ // extension-registered Kinds that happen to be declarative).
608
+ // PARITY FIX: before F3 this branch fell through for any
609
+ // declarative `existing` and OVERWROTE it — a per-scope
610
+ // KindDefinition would silently replace a builtin (Python
611
+ // skipped instead). Now: skip + warn + kinddef_conflict event.
612
+ console.warn(`KindDefinition ${typed.spec.target_api_version}/${typed.spec.target_kind} ` +
613
+ `conflicts with ${isBuiltinDescriptor ? "builtin-descriptor" : "extension-registered"} kind; ` +
614
+ `keeping it and skipping the per-scope declarative port.`);
615
+ host.hooks.emit("kinddef_conflict", {
616
+ scope: "",
617
+ kind: typed.spec.target_kind,
618
+ name: typed.metadata.name,
619
+ data: {
620
+ apiVersion: typed.spec.target_api_version,
621
+ reason: isBuiltinDescriptor ? "builtin_descriptor_wins" : "extension_wins",
622
+ },
623
+ });
624
+ continue;
625
+ }
626
+ }
627
+ let port;
628
+ try {
629
+ port = DeclarativeKindPort.fromTyped(typed);
630
+ }
631
+ catch (e) {
632
+ console.error(`Failed to synthesize DeclarativeKindPort for ${typed.spec.target_api_version}/${typed.spec.target_kind}: ${e}`);
633
+ continue;
634
+ }
635
+ // F3 (spec D3): the per-scope funnel writes straight into
636
+ // this.kinds (bypassing registerKind()), so the F1 plane lint never
637
+ // ran here. Run the SAME helper — but warn + skip instead of
638
+ // throwing: per-scope docs never take the boot down (same contract
639
+ // as the parse_error path above).
640
+ try {
641
+ KindRegistry._lintPlane(port);
642
+ }
643
+ catch (e) {
644
+ console.warn(`KindDefinition ${typed.spec.target_api_version}/${typed.spec.target_kind} ` +
645
+ `failed the plane lint: ${e} — skipping registration.`);
646
+ if (host.hooks.has("parse_error")) {
647
+ host.hooks.emit("parse_error", {
648
+ scope: "",
649
+ kind: KIND_DEFINITION_KIND,
650
+ name: typed.metadata.name,
651
+ data: { error: String(e) },
652
+ });
653
+ }
654
+ continue;
655
+ }
656
+ this.kinds.set(key, port);
657
+ host._genericsResolved = false;
658
+ registered = true;
659
+ try {
660
+ port._resolvedDocs = loadKindDocs(port);
661
+ }
662
+ catch {
663
+ port._resolvedDocs = port.docs ?? null;
664
+ }
665
+ }
666
+ // Re-resolve generic readers/writers for newly declared BUNDLE kinds
667
+ host._ensureGenericReadersWriters();
668
+ return registered && host._readers.length > readersBefore;
669
+ }
670
+ /** Register dynamic kinds from `Module.spec.custom_kinds`. Py twin:
671
+ * `KindRegistry.register_custom_kinds`. */
672
+ registerCustomKinds(manifest) {
673
+ const spec = manifest.spec ?? {};
674
+ const customKinds = spec.custom_kinds ?? [];
675
+ for (const ck of customKinds) {
676
+ const av = ck.apiVersion ?? "custom/v1";
677
+ const kn = ck.kind ?? "";
678
+ const alias = ck.alias ?? kn.toLowerCase();
679
+ if (!kn)
680
+ continue;
681
+ const key = `${av}\0${kn}`;
682
+ if (this.kinds.has(key))
683
+ continue;
684
+ this.kinds.set(key, KindRegistry._makeDynamicKind(av, kn, alias));
685
+ }
686
+ }
687
+ static _makeDynamicKind(av, kn, al) {
688
+ return {
689
+ apiVersion: av,
690
+ kind: kn,
691
+ alias: al,
692
+ origin: "custom",
693
+ isRoot: false,
694
+ isPromptTarget: false,
695
+ promptTargetPriority: 0,
696
+ flattenInContext: false,
697
+ isRuntimeArtifact: false,
698
+ storage: SD.yaml(""),
699
+ depFilters: () => null,
700
+ dependencies: () => null,
701
+ schema: () => null,
702
+ getDefaultAgentName: () => null,
703
+ getLayerPolicies: () => null,
704
+ parse: (raw) => raw,
705
+ describe: () => null,
706
+ summary: () => null,
707
+ promptTemplate: () => null,
708
+ };
709
+ }
710
+ }