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,106 @@
1
+ /**
2
+ * Kind base class with sensible defaults.
3
+ *
4
+ * 1:1 parity with python/dna/kernel/kind_base.py.
5
+ *
6
+ * Cuts the boilerplate that every Kind implementation duplicated.
7
+ * Subclassing pattern:
8
+ *
9
+ * class AssetKind extends KindBase {
10
+ * readonly apiVersion = "github.com/ruinosus/dna/asset/v1";
11
+ * readonly kind = "Asset";
12
+ * readonly alias = "asset-asset";
13
+ * readonly storage = StorageDescriptorImpl.bundle(
14
+ * "assets", "ASSET.md",
15
+ * );
16
+ * readonly asciiIcon = "🖼️";
17
+ * readonly displayLabel = "Assets";
18
+ * }
19
+ *
20
+ * That's the minimum-viable Kind. Everything else inherits sensible
21
+ * defaults from this base.
22
+ *
23
+ * The base is **purely additive**: existing Kind impls keep working
24
+ * as plain objects (they declare every field explicitly). Migrating
25
+ * a Kind to `KindBase` is a strict simplification — no API changes.
26
+ *
27
+ * Mandatory overrides (no default available):
28
+ * - `apiVersion` — globally unique namespace string
29
+ * - `kind` — CamelCase Kind name
30
+ * - `alias` — globally unique alias
31
+ * - `storage` — StorageDescriptor (call StorageDescriptorImpl.bundle/yaml/root)
32
+ *
33
+ * Everything else has defaults that match the most common case.
34
+ */
35
+ import Ajv from "ajv";
36
+ import type { Document } from "./document.js";
37
+ import type { KindPort, LayerPolicy, StorageDescriptor } from "./protocols.js";
38
+ /** Default volatile spec fields shared by every Kind (s-sync-s1). Exported
39
+ * so DeclarativeKindPort (meta.ts) unions the SAME set instead of
40
+ * re-hardcoding it — twin of Python's KindBase.VOLATILE_SPEC_FIELDS
41
+ * class attribute (C1 review carry-over). */
42
+ export declare const DEFAULT_VOLATILE_SPEC_FIELDS: ReadonlySet<string>;
43
+ /** JSON Schema `format` values that appear in real Kind schemas across the
44
+ * repo (builtin kind descriptors, KindDefinition YAMLs, tests). ajv v8 ships
45
+ * ZERO built-in formats, and the Python twin (`jsonschema`) treats `format`
46
+ * as annotation-only by default — so parity is "formats never validate".
47
+ * Registering each as `true` keeps exactly that semantics (always-valid
48
+ * annotation) while silencing ajv's per-compile
49
+ * `unknown format "X" ignored in schema` warning (s-public-ci).
50
+ * NOTE: deliberately NOT ajv-formats — that would start rejecting values and
51
+ * break Py↔TS validation parity. */
52
+ export declare const SCHEMA_ANNOTATION_FORMATS: readonly string[];
53
+ /** Canonical Ajv factory for the SDK — every `new Ajv(...)` in the kernel
54
+ * goes through here so options stay in one place. strict:false accepts the
55
+ * lenient JSON Schema shapes users author in YAML. */
56
+ export declare function createAjv(): Ajv;
57
+ export declare abstract class KindBase implements Omit<KindPort, "apiVersion" | "kind" | "alias" | "storage"> {
58
+ readonly origin?: string;
59
+ readonly isPromptTarget: boolean;
60
+ readonly promptTargetPriority: number;
61
+ readonly flattenInContext: boolean;
62
+ get isRoot(): boolean;
63
+ readonly isRuntimeArtifact: boolean;
64
+ readonly plane: "record" | "composition";
65
+ readonly isSchemaAffecting: boolean;
66
+ readonly isOverlayable: boolean;
67
+ readonly scopeInheritable: boolean;
68
+ readonly isCatalogIdentity: boolean;
69
+ readonly embedFields: string[] | null;
70
+ readonly validateOnParse: boolean;
71
+ private _parseValidator?;
72
+ private _parseAjv?;
73
+ readonly docs?: string;
74
+ readonly descriptionFallbackField?: string;
75
+ readonly graphStyle?: {
76
+ fill: string;
77
+ stroke: string;
78
+ textColor: string;
79
+ };
80
+ readonly asciiIcon?: string;
81
+ readonly displayLabel?: string;
82
+ readonly uiSchema?: Record<string, Record<string, unknown>>;
83
+ readonly markerSharedAllowed: boolean;
84
+ readonly volatileSpecFields: ReadonlySet<string>;
85
+ depFilters(): Record<string, string> | null;
86
+ dependencies(): Record<string, string> | null;
87
+ schema(): Record<string, unknown> | null;
88
+ getDefaultAgentName(_doc: Document): string | null;
89
+ getLayerPolicies(_doc: Document): Record<string, LayerPolicy | string> | null;
90
+ parse(raw: Record<string, unknown>): unknown;
91
+ /** Validate `raw.spec` against `schema()` (ajv). No-op when no schema.
92
+ * Throws a clear error for a malformed spec (s-typed-models-for-dict-kinds). */
93
+ private _validateSpec;
94
+ describe(_doc: Document): string | null;
95
+ summary(doc: Document): Record<string, unknown> | null;
96
+ promptTemplate(): string | null;
97
+ protected canonicalSpec(spec: Record<string, unknown>): Record<string, unknown>;
98
+ /** Stable SHA-256 of the doc's authored identity — basis for source
99
+ * diff/sync. Invariant to key order, formatting, volatile stamps, and
100
+ * instruction_file-vs-inline; sensitive to real content. */
101
+ canonicalDigest(doc: Document): string;
102
+ abstract readonly apiVersion: string;
103
+ abstract readonly kind: string;
104
+ abstract readonly alias: string;
105
+ abstract readonly storage: StorageDescriptor;
106
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Kind base class with sensible defaults.
3
+ *
4
+ * 1:1 parity with python/dna/kernel/kind_base.py.
5
+ *
6
+ * Cuts the boilerplate that every Kind implementation duplicated.
7
+ * Subclassing pattern:
8
+ *
9
+ * class AssetKind extends KindBase {
10
+ * readonly apiVersion = "github.com/ruinosus/dna/asset/v1";
11
+ * readonly kind = "Asset";
12
+ * readonly alias = "asset-asset";
13
+ * readonly storage = StorageDescriptorImpl.bundle(
14
+ * "assets", "ASSET.md",
15
+ * );
16
+ * readonly asciiIcon = "🖼️";
17
+ * readonly displayLabel = "Assets";
18
+ * }
19
+ *
20
+ * That's the minimum-viable Kind. Everything else inherits sensible
21
+ * defaults from this base.
22
+ *
23
+ * The base is **purely additive**: existing Kind impls keep working
24
+ * as plain objects (they declare every field explicitly). Migrating
25
+ * a Kind to `KindBase` is a strict simplification — no API changes.
26
+ *
27
+ * Mandatory overrides (no default available):
28
+ * - `apiVersion` — globally unique namespace string
29
+ * - `kind` — CamelCase Kind name
30
+ * - `alias` — globally unique alias
31
+ * - `storage` — StorageDescriptor (call StorageDescriptorImpl.bundle/yaml/root)
32
+ *
33
+ * Everything else has defaults that match the most common case.
34
+ */
35
+ import { createHash } from "node:crypto";
36
+ import Ajv from "ajv";
37
+ /** Default volatile spec fields shared by every Kind (s-sync-s1). Exported
38
+ * so DeclarativeKindPort (meta.ts) unions the SAME set instead of
39
+ * re-hardcoding it — twin of Python's KindBase.VOLATILE_SPEC_FIELDS
40
+ * class attribute (C1 review carry-over). */
41
+ export const DEFAULT_VOLATILE_SPEC_FIELDS = new Set([
42
+ "updated_at",
43
+ "version",
44
+ "created_at",
45
+ ]);
46
+ /** JSON Schema `format` values that appear in real Kind schemas across the
47
+ * repo (builtin kind descriptors, KindDefinition YAMLs, tests). ajv v8 ships
48
+ * ZERO built-in formats, and the Python twin (`jsonschema`) treats `format`
49
+ * as annotation-only by default — so parity is "formats never validate".
50
+ * Registering each as `true` keeps exactly that semantics (always-valid
51
+ * annotation) while silencing ajv's per-compile
52
+ * `unknown format "X" ignored in schema` warning (s-public-ci).
53
+ * NOTE: deliberately NOT ajv-formats — that would start rejecting values and
54
+ * break Py↔TS validation parity. */
55
+ export const SCHEMA_ANNOTATION_FORMATS = [
56
+ "date-time",
57
+ "date",
58
+ "email",
59
+ "markdown",
60
+ ];
61
+ /** Canonical Ajv factory for the SDK — every `new Ajv(...)` in the kernel
62
+ * goes through here so options stay in one place. strict:false accepts the
63
+ * lenient JSON Schema shapes users author in YAML. */
64
+ export function createAjv() {
65
+ const formats = {};
66
+ for (const f of SCHEMA_ANNOTATION_FORMATS)
67
+ formats[f] = true;
68
+ return new Ajv({ strict: false, allErrors: true, formats });
69
+ }
70
+ /** Stable, sorted-key, compact JSON — twin of Python's
71
+ * `json.dumps(sort_keys=True, separators=(",",":"))`. Logically-equal objects
72
+ * serialize identically regardless of insertion order (s-sync-s1). */
73
+ function _stableStringify(value) {
74
+ if (value === null || typeof value !== "object") {
75
+ return JSON.stringify(value) ?? "null";
76
+ }
77
+ if (Array.isArray(value)) {
78
+ return "[" + value.map(_stableStringify).join(",") + "]";
79
+ }
80
+ const obj = value;
81
+ const keys = Object.keys(obj).sort();
82
+ return ("{" +
83
+ keys.map((k) => JSON.stringify(k) + ":" + _stableStringify(obj[k])).join(",") +
84
+ "}");
85
+ }
86
+ export class KindBase {
87
+ // ---- Identity (subclasses MUST set these) -----------------------
88
+ // abstract apiVersion: string;
89
+ // abstract kind: string;
90
+ // abstract alias: string;
91
+ // abstract storage: StorageDescriptor;
92
+ // ---- Optional identity ------------------------------------------
93
+ origin;
94
+ // ---- Behavior flags (sensible defaults) -------------------------
95
+ // ``isRoot`` was an explicit boolean Phase 0–15. Phase 16 derives
96
+ // it from the storage descriptor: a Kind is the scope root iff its
97
+ // storage pattern is ROOT (single file at scope root). GenomeKind
98
+ // is the only one today; the getter keeps the existing read API
99
+ // (``kp.isRoot``) working for all consumers.
100
+ isPromptTarget = false;
101
+ promptTargetPriority = 0;
102
+ flattenInContext = false;
103
+ get isRoot() {
104
+ const storage = this.storage;
105
+ return storage?.pattern === "root";
106
+ }
107
+ // See KindPort.isRuntimeArtifact in protocols.ts. Override to `true`
108
+ // only on Kinds whose documents are generated by runtime workflows
109
+ // (EvalRun, Finding, Evidence, AssessmentRun, ...).
110
+ isRuntimeArtifact = false;
111
+ // ---- Two-planes (spec 2026-06-09-kinds-two-planes-design) --------
112
+ // "composition" (default) — participates in agent composition.
113
+ // "record" — pure typed document; writes never invalidate scope
114
+ // caches (Python kernel branches on this; TS has no cache machinery,
115
+ // the attr exists for 1:1 parity + the registration lint below).
116
+ plane = "composition";
117
+ // ---- Kernel classification (s-kernel-kindport-classification-attrs) ----
118
+ // The kernel DERIVES its classification sets from these per-Kind attributes
119
+ // instead of matching hardcoded Kind names. 1:1 parity with the Python
120
+ // KindBase. Defaults match the common case; the few Kinds that differ override.
121
+ // `isSchemaAffecting`: writing this Kind invalidates the schema cache.
122
+ isSchemaAffecting = false;
123
+ // `isOverlayable`: a tenant overlay may fork this Kind. False only for the
124
+ // structural bootstrap Kinds (scope identity / schema / policy).
125
+ isOverlayable = true;
126
+ // `scopeInheritable`: documents inherit across scopes by default. False for the
127
+ // per-scope SDLC ledger + structural Kinds.
128
+ scopeInheritable = true;
129
+ // `isCatalogIdentity`: writing a doc of this Kind changes the Catalog
130
+ // tier's scope/mandatory set (s-write-path-despecialize — replaces the
131
+ // hardcoded `kind == "Genome"` check in the Python write path; the TS
132
+ // kernel keeps no catalog cache, the attribute exists for 1:1 Kind
133
+ // metadata parity). True only on the scope-root identity Kind.
134
+ isCatalogIdentity = false;
135
+ // ---- Embedding source fields (F3 spec D4) ------------------------
136
+ // Fields of `spec` that compose the doc's embedding text. null =
137
+ // not embeddable (or still covered by the legacy EMBEDDABLE_KINDS
138
+ // frozenset in harness-shared). Declared here so the D4 derivation
139
+ // covers still-class Kinds as well as descriptor-synthesized ports.
140
+ embedFields = null;
141
+ // ---- Validation (s-typed-models-for-dict-kinds) -----------------
142
+ // When true, parse() validates the doc's `spec` against schema() (ajv) before
143
+ // returning — a malformed doc throws a clear error. 1:1 parity with the
144
+ // Python KindBase.validate_on_parse. Opt-in (default false).
145
+ validateOnParse = false;
146
+ _parseValidator;
147
+ _parseAjv;
148
+ // ---- Optional rendering hints (duck-typed by Studio) -----------
149
+ docs;
150
+ descriptionFallbackField;
151
+ graphStyle;
152
+ asciiIcon;
153
+ displayLabel;
154
+ uiSchema;
155
+ // ---- H1 opt-in for shared bundle markers -----------------------
156
+ markerSharedAllowed = false;
157
+ // ---- Source-sync identity (s-sync-s1) --------------------------
158
+ // Write-/runtime-stamped spec fields, NOT part of authored identity —
159
+ // excluded from canonicalDigest so the same doc in two sources hashes
160
+ // identically. Kinds override to extend (e.g. a Forecast adds generated_at).
161
+ volatileSpecFields = DEFAULT_VOLATILE_SPEC_FIELDS;
162
+ // ---- Behavior methods (default implementations) ----------------
163
+ depFilters() {
164
+ return null;
165
+ }
166
+ dependencies() {
167
+ return this.depFilters();
168
+ }
169
+ schema() {
170
+ return null;
171
+ }
172
+ getDefaultAgentName(_doc) {
173
+ return null;
174
+ }
175
+ getLayerPolicies(_doc) {
176
+ return null;
177
+ }
178
+ parse(raw) {
179
+ if (this.validateOnParse)
180
+ this._validateSpec(raw);
181
+ return raw;
182
+ }
183
+ /** Validate `raw.spec` against `schema()` (ajv). No-op when no schema.
184
+ * Throws a clear error for a malformed spec (s-typed-models-for-dict-kinds). */
185
+ _validateSpec(raw) {
186
+ const schema = this.schema();
187
+ if (!schema)
188
+ return;
189
+ if (this._parseValidator === undefined) {
190
+ this._parseAjv = createAjv();
191
+ this._parseValidator = this._parseAjv.compile(schema);
192
+ }
193
+ if (!this._parseValidator)
194
+ return;
195
+ // Accept a full envelope ({apiVersion, kind, spec}) OR a flat spec dict
196
+ // (mirrors the gaia Kinds' defensive parse + the Python twin).
197
+ const spec = "apiVersion" in raw && raw.spec && typeof raw.spec === "object"
198
+ ? raw.spec
199
+ : raw;
200
+ if (!this._parseValidator(spec)) {
201
+ const errs = this._parseAjv?.errorsText(this._parseValidator.errors);
202
+ throw new Error(`Kind '${this.kind}' spec validation failed: ${errs}`);
203
+ }
204
+ }
205
+ describe(_doc) {
206
+ return null;
207
+ }
208
+ summary(doc) {
209
+ const spec = doc.spec;
210
+ if (spec && typeof spec === "object" && !Array.isArray(spec)) {
211
+ return { ...spec };
212
+ }
213
+ return null;
214
+ }
215
+ promptTemplate() {
216
+ return null;
217
+ }
218
+ // ---- Source-sync digest (s-sync-s1; twin of Python canonical_digest) ----
219
+ canonicalSpec(spec) {
220
+ const out = {};
221
+ for (const [k, v] of Object.entries(spec ?? {})) {
222
+ if (this.volatileSpecFields.has(k))
223
+ continue;
224
+ out[k] = v;
225
+ }
226
+ delete out["source_files"]; // pure transport, never identity
227
+ if (out["instruction_file"] && out["instruction"]) {
228
+ delete out["instruction_file"]; // file-backed == inline once resolved
229
+ }
230
+ return out;
231
+ }
232
+ /** Stable SHA-256 of the doc's authored identity — basis for source
233
+ * diff/sync. Invariant to key order, formatting, volatile stamps, and
234
+ * instruction_file-vs-inline; sensitive to real content. */
235
+ canonicalDigest(doc) {
236
+ const spec = (doc.spec ?? {});
237
+ const payload = {
238
+ kind: doc.kind ?? this.kind,
239
+ name: doc.name ?? null,
240
+ spec: this.canonicalSpec({ ...spec }),
241
+ };
242
+ return createHash("sha256").update(_stableStringify(payload), "utf8").digest("hex");
243
+ }
244
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * DefaultLayerResolver — overlay resolution with per-kind policies.
3
+ *
4
+ * 1:1 parity with Python dna.kernel.layer_resolver.
5
+ *
6
+ * Kernel-owned since s-invert-layer-resolver-dep (2026-07-07): layer
7
+ * resolution is a core kernel responsibility, so the default resolver
8
+ * lives in the kernel package — the kernel must work with ZERO
9
+ * extensions loaded. Previously at src/extensions/helix/layers.ts
10
+ * (which remains as a deprecated reexport shim).
11
+ *
12
+ * Iterates layer dimensions, loads overlays from the source,
13
+ * and merges with per-kind policies (open/restricted/locked).
14
+ * The resolver is fully generic: it depends only on
15
+ * kernel/protocols.LayerPolicy — no extension models or constants.
16
+ */
17
+ /**
18
+ * Deep merge two plain objects. Overlay wins.
19
+ * Lists are replaced (not concatenated), dicts are merged recursively.
20
+ */
21
+ export declare function deepMerge(base: Record<string, unknown>, overlay: Record<string, unknown>): Record<string, unknown>;
22
+ /**
23
+ * Concat + dedup + sort base.spec.timeline and overlay.spec.timeline
24
+ * (per ADR 2026-05-10). Returns the merged list when EITHER side has a
25
+ * timeline array; ``null`` when neither does.
26
+ *
27
+ * Dedup key: `at + actor + type + (from + to | summary)`.
28
+ * Sort: descending by ``at`` (newest first).
29
+ */
30
+ export declare function mergeTimelineArrays(baseSpec: Record<string, unknown>, overlaySpec: Record<string, unknown>): Record<string, unknown>[] | null;
31
+ export interface LayerSource {
32
+ loadLayer(scope: string, layerId: string, layerValue: string): Record<string, unknown>[];
33
+ }
34
+ /**
35
+ * Merges layer overlay documents into base documents, applying policies by kind alias.
36
+ *
37
+ * Policies map: `{ kind_alias_or_kind_name: LayerPolicy }`
38
+ * - open: deep merge spec (or add new documents)
39
+ * - restricted: only override existing keys in spec
40
+ * - locked: block changes (warn only)
41
+ */
42
+ export declare class DefaultLayerResolver {
43
+ resolve(baseDocuments: Record<string, unknown>[], layers: Record<string, string>, source: LayerSource, scope: string, policies: Record<string, string>): Record<string, unknown>[];
44
+ private _mergeAll;
45
+ private _policyForKind;
46
+ private _applyMerge;
47
+ private _applyRestrictedMerge;
48
+ }
@@ -0,0 +1,284 @@
1
+ /**
2
+ * DefaultLayerResolver — overlay resolution with per-kind policies.
3
+ *
4
+ * 1:1 parity with Python dna.kernel.layer_resolver.
5
+ *
6
+ * Kernel-owned since s-invert-layer-resolver-dep (2026-07-07): layer
7
+ * resolution is a core kernel responsibility, so the default resolver
8
+ * lives in the kernel package — the kernel must work with ZERO
9
+ * extensions loaded. Previously at src/extensions/helix/layers.ts
10
+ * (which remains as a deprecated reexport shim).
11
+ *
12
+ * Iterates layer dimensions, loads overlays from the source,
13
+ * and merges with per-kind policies (open/restricted/locked).
14
+ * The resolver is fully generic: it depends only on
15
+ * kernel/protocols.LayerPolicy — no extension models or constants.
16
+ */
17
+ import { LayerPolicy } from "./protocols.js";
18
+ // ---------------------------------------------------------------------------
19
+ // Deep merge
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Deep merge two plain objects. Overlay wins.
23
+ * Lists are replaced (not concatenated), dicts are merged recursively.
24
+ */
25
+ export function deepMerge(base, overlay) {
26
+ const result = structuredClone(base);
27
+ for (const [key, value] of Object.entries(overlay)) {
28
+ if (key in result &&
29
+ typeof result[key] === "object" &&
30
+ result[key] !== null &&
31
+ !Array.isArray(result[key]) &&
32
+ typeof value === "object" &&
33
+ value !== null &&
34
+ !Array.isArray(value)) {
35
+ result[key] = deepMerge(result[key], value);
36
+ }
37
+ else {
38
+ result[key] = structuredClone(value);
39
+ }
40
+ }
41
+ return result;
42
+ }
43
+ // ---------------------------------------------------------------------------
44
+ // Timeline merger (cross-overlay append-only)
45
+ // ---------------------------------------------------------------------------
46
+ /**
47
+ * Concat + dedup + sort base.spec.timeline and overlay.spec.timeline
48
+ * (per ADR 2026-05-10). Returns the merged list when EITHER side has a
49
+ * timeline array; ``null`` when neither does.
50
+ *
51
+ * Dedup key: `at + actor + type + (from + to | summary)`.
52
+ * Sort: descending by ``at`` (newest first).
53
+ */
54
+ export function mergeTimelineArrays(baseSpec, overlaySpec) {
55
+ const baseTl = Array.isArray(baseSpec.timeline)
56
+ ? baseSpec.timeline
57
+ : [];
58
+ const overlayTl = Array.isArray(overlaySpec.timeline)
59
+ ? overlaySpec.timeline
60
+ : [];
61
+ if (baseTl.length === 0 && overlayTl.length === 0)
62
+ return null;
63
+ const seen = new Set();
64
+ const merged = [];
65
+ for (const ev of [...baseTl, ...overlayTl]) {
66
+ if (typeof ev !== "object" || ev === null || Array.isArray(ev))
67
+ continue;
68
+ const key = JSON.stringify([
69
+ ev.at ?? null,
70
+ ev.actor ?? null,
71
+ ev.type ?? null,
72
+ ev.from ?? null,
73
+ ev.to ?? null,
74
+ ev.summary ?? null,
75
+ ]);
76
+ if (seen.has(key))
77
+ continue;
78
+ seen.add(key);
79
+ merged.push(structuredClone(ev));
80
+ }
81
+ // Stable sort: newest first.
82
+ merged.sort((a, b) => {
83
+ const ax = String(a.at ?? "");
84
+ const bx = String(b.at ?? "");
85
+ if (ax < bx)
86
+ return 1;
87
+ if (ax > bx)
88
+ return -1;
89
+ return 0;
90
+ });
91
+ return merged;
92
+ }
93
+ // ---------------------------------------------------------------------------
94
+ // Overlay metadata stamper (Phase 2 overlay UX)
95
+ // ---------------------------------------------------------------------------
96
+ /**
97
+ * In-place: write ``has_overlay`` + ``overlay_fields`` into ``doc.metadata``.
98
+ * Studio's editor banners + per-field markers read these.
99
+ *
100
+ * ``overlayFields=null`` is the sentinel for "the entire doc came from
101
+ * the overlay" (overlay-only add, no base to diff against). Frontend
102
+ * treats this distinctly from an empty list.
103
+ *
104
+ * Idempotent across multiple layer dimensions: subsequent calls UNION
105
+ * the field list so the user sees every field touched across all
106
+ * applied layers.
107
+ */
108
+ function stampOverlayMetadata(doc, overlayFields) {
109
+ const md = doc.metadata ?? {};
110
+ doc.metadata = md;
111
+ md.has_overlay = true;
112
+ if (overlayFields === null) {
113
+ md.overlay_fields = null;
114
+ return;
115
+ }
116
+ const existing = md.overlay_fields;
117
+ if (existing === undefined || existing === null) {
118
+ md.overlay_fields = [...overlayFields];
119
+ return;
120
+ }
121
+ if (Array.isArray(existing)) {
122
+ const merged = [...existing];
123
+ for (const f of overlayFields) {
124
+ if (!merged.includes(f))
125
+ merged.push(f);
126
+ }
127
+ md.overlay_fields = merged;
128
+ }
129
+ }
130
+ // ---------------------------------------------------------------------------
131
+ // DefaultLayerResolver
132
+ // ---------------------------------------------------------------------------
133
+ /**
134
+ * Merges layer overlay documents into base documents, applying policies by kind alias.
135
+ *
136
+ * Policies map: `{ kind_alias_or_kind_name: LayerPolicy }`
137
+ * - open: deep merge spec (or add new documents)
138
+ * - restricted: only override existing keys in spec
139
+ * - locked: block changes (warn only)
140
+ */
141
+ export class DefaultLayerResolver {
142
+ resolve(baseDocuments, layers, source, scope, policies) {
143
+ let result = baseDocuments.map((d) => structuredClone(d));
144
+ for (const [layerId, value] of Object.entries(layers)) {
145
+ const overlayDocs = source.loadLayer(scope, layerId, value);
146
+ if (overlayDocs.length === 0)
147
+ continue;
148
+ result = this._mergeAll(result, overlayDocs, policies, layerId, value);
149
+ }
150
+ return result;
151
+ }
152
+ // -------------------------------------------------------------------------
153
+ _mergeAll(baseDocs, overlayDocs, policies, layerId, value) {
154
+ const result = baseDocs.map((d) => structuredClone(d));
155
+ // Build index: "kind\0name" -> position in result
156
+ const baseIndex = new Map();
157
+ for (let i = 0; i < result.length; i++) {
158
+ const kind = result[i].kind ?? "";
159
+ const name = result[i].metadata?.name ?? "";
160
+ if (kind && name)
161
+ baseIndex.set(`${kind}\0${name}`, i);
162
+ }
163
+ for (const overlay of overlayDocs) {
164
+ const oKind = overlay.kind ?? "";
165
+ const oName = overlay.metadata?.name ?? "";
166
+ const policy = this._policyForKind(oKind, policies);
167
+ const key = `${oKind}\0${oName}`;
168
+ if (baseIndex.has(key)) {
169
+ const idx = baseIndex.get(key);
170
+ result[idx] = this._applyMerge(result[idx], overlay, policy, layerId, value);
171
+ }
172
+ else {
173
+ // New document — only allowed under OPEN policy
174
+ if (policy === LayerPolicy.LOCKED ||
175
+ policy === LayerPolicy.RESTRICTED) {
176
+ console.warn(`Layer '${layerId}=${value}' tried to add '${oKind}/${oName}' but policy is ${policy}. Ignored.`);
177
+ }
178
+ else {
179
+ // Overlay-only add — stamp metadata so the frontend knows
180
+ // "the whole doc is your overlay" (Phase 2 UX).
181
+ const newDoc = structuredClone(overlay);
182
+ stampOverlayMetadata(newDoc, null);
183
+ result.push(newDoc);
184
+ }
185
+ }
186
+ }
187
+ return result;
188
+ }
189
+ _policyForKind(kind, policies) {
190
+ if (kind in policies)
191
+ return policies[kind];
192
+ const kindLower = kind.toLowerCase();
193
+ for (const [alias, policy] of Object.entries(policies)) {
194
+ if (alias.endsWith(`-${kindLower}`) || alias === kindLower)
195
+ return policy;
196
+ }
197
+ return LayerPolicy.OPEN;
198
+ }
199
+ _applyMerge(base, overlay, policy, layerId, value) {
200
+ const specOverlay = overlay.spec ?? {};
201
+ const specBase = base.spec ?? {};
202
+ // Timeline is append-only across overlays — concat+sort regardless
203
+ // of policy (even LOCKED). ADR 2026-05-10.
204
+ const mergedTimeline = mergeTimelineArrays(specBase, specOverlay);
205
+ if (Object.keys(specOverlay).length === 0 && mergedTimeline === null) {
206
+ return base;
207
+ }
208
+ if (policy === LayerPolicy.LOCKED) {
209
+ if (mergedTimeline === null) {
210
+ const name = base.metadata?.name ?? "";
211
+ console.warn(`Layer '${layerId}=${value}' tried to modify locked document '${name}'. Ignored.`);
212
+ return base;
213
+ }
214
+ // Timeline-only overlay on a LOCKED doc still appends events.
215
+ const result = structuredClone(base);
216
+ const sp = result.spec ?? {};
217
+ sp.timeline = mergedTimeline;
218
+ result.spec = sp;
219
+ return result;
220
+ }
221
+ if (policy === LayerPolicy.RESTRICTED) {
222
+ // Strip `timeline` from the restricted-merge call so the
223
+ // "unknown key" warning doesn't fire for timeline-only changes.
224
+ const specOverlayNoTl = {};
225
+ for (const [k, v] of Object.entries(specOverlay)) {
226
+ if (k !== "timeline")
227
+ specOverlayNoTl[k] = v;
228
+ }
229
+ const result = this._applyRestrictedMerge(base, specOverlayNoTl, layerId, value);
230
+ if (mergedTimeline !== null) {
231
+ const sp = result.spec ?? {};
232
+ sp.timeline = mergedTimeline;
233
+ result.spec = sp;
234
+ }
235
+ return result;
236
+ }
237
+ // OPEN — deep merge spec
238
+ const result = structuredClone(base);
239
+ result.spec = deepMerge(specBase, specOverlay);
240
+ if (mergedTimeline !== null) {
241
+ result.spec.timeline = mergedTimeline;
242
+ }
243
+ if (Object.keys(specOverlay).length > 0) {
244
+ // Phase 2 overlay UX: top-level spec keys the overlay provided
245
+ // are "overridden" under OPEN policy. Exclude `timeline` since
246
+ // it's append-only, not an override.
247
+ const overridden = Object.keys(specOverlay).filter((k) => k !== "timeline");
248
+ if (overridden.length > 0) {
249
+ stampOverlayMetadata(result, overridden);
250
+ }
251
+ }
252
+ return result;
253
+ }
254
+ _applyRestrictedMerge(base, specOverlay, layerId, value) {
255
+ const result = structuredClone(base);
256
+ const specBase = result.spec ?? {};
257
+ const applied = [];
258
+ for (const [key, val] of Object.entries(specOverlay)) {
259
+ if (!(key in specBase)) {
260
+ console.warn(`Layer '${layerId}=${value}' tried to add key '${key}' to restricted document. Ignored.`);
261
+ continue;
262
+ }
263
+ if (typeof specBase[key] === "object" &&
264
+ specBase[key] !== null &&
265
+ !Array.isArray(specBase[key]) &&
266
+ typeof val === "object" &&
267
+ val !== null &&
268
+ !Array.isArray(val)) {
269
+ specBase[key] = deepMerge(specBase[key], val);
270
+ }
271
+ else {
272
+ specBase[key] = structuredClone(val);
273
+ }
274
+ applied.push(key);
275
+ }
276
+ result.spec = specBase;
277
+ // Restricted policy drops unknown keys — stamp only the ones
278
+ // that actually merged.
279
+ if (applied.length > 0) {
280
+ stampOverlayMetadata(result, applied);
281
+ }
282
+ return result;
283
+ }
284
+ }