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,764 @@
1
+ /**
2
+ * Typed Zod schemas for each kind — used by KindPort.parse().
3
+ *
4
+ * 1:1 parity with Python dna.v3.kernel.models.
5
+ */
6
+ import { z } from "zod";
7
+ import { UI_METADATA_FIELDS } from "./studio_ui.js";
8
+ // ---------------------------------------------------------------------------
9
+ // Shared
10
+ // ---------------------------------------------------------------------------
11
+ export const MetadataSchema = z.object({
12
+ name: z.string(),
13
+ description: z.string().optional().default(""),
14
+ version: z.string().optional().default(""),
15
+ icon: z.string().optional().default(""),
16
+ group: z.string().optional().default(""),
17
+ labels: z.record(z.string()).default({}),
18
+ });
19
+ // ---------------------------------------------------------------------------
20
+ // Genome (github.com/ruinosus/dna/v1) — Phase 16 (scope segregation)
21
+ //
22
+ // Replaces Module as the scope-root identity + runtime config doc.
23
+ // Catalog identity (owner, version, visibility) lives here. Layer policy
24
+ // moved out to ``LayerPolicy`` Kind. Custom Kinds moved out to
25
+ // ``KindDefinition`` Kind. Bill-of-materials inventory arrays
26
+ // (agents[], skills[], actors[], etc.) deleted — composition validation
27
+ // walks scanner output directly.
28
+ //
29
+ // Tenant overlay applies only to ``OVERLAYABLE_FIELDS`` declared on the
30
+ // GenomeKind class (see typescript/src/extensions/helix.ts). Identity
31
+ // (owner_tenant, version, visibility, deprecated*, repository,
32
+ // dependencies) is structurally non-overlayable.
33
+ //
34
+ // 1:1 parity with Python dna.kernel.models.GenomeSpec.
35
+ // ---------------------------------------------------------------------------
36
+ export const GenomeSpecSchema = z.object({
37
+ // Catalog identity — non-overlayable.
38
+ owner: z.string().nullish(),
39
+ owner_tenant: z.string().nullish(),
40
+ repository: z.string().nullish(),
41
+ visibility: z.enum(["public", "internal", "private"]).default("public"),
42
+ // i-112 OQ1 — a mandatory Catalog capability is installed-by-default +
43
+ // non-removable; global_scope = global runtime lookup (like the model
44
+ // registry). Catalog identity → NOT overlayable.
45
+ mandatory: z.boolean().default(false),
46
+ global_scope: z.boolean().default(false),
47
+ // Composition Engine V2 (Phase 17, s-comp-f1-schema, 2026-05-28):
48
+ // Declarative parent scope for cross-scope inheritance. null = root.
49
+ // Resolution walks the chain. Per-Kind composition_rules in
50
+ // LayerPolicy govern which Kinds inherit + merge_strategy.
51
+ parent_scope: z.string().nullish(),
52
+ // Versioning — non-overlayable.
53
+ version: z.string().nullish(),
54
+ changelog_url: z.string().nullish(),
55
+ deprecated: z.boolean().default(false),
56
+ deprecated_message: z.string().nullish(),
57
+ // Runtime defaults — overlayable per tenant.
58
+ default_agent: z.string().optional(),
59
+ default_llm: z.string().optional(),
60
+ budget: z.record(z.unknown()).nullish(),
61
+ tags: z.array(z.string()).default([]),
62
+ // i-112 ph2 — capability manifest: what this Genome PROVIDES. Each entry
63
+ // {kind, name, location}. Catalog identity → NOT overlayable. The resolver
64
+ // (Phase 3) reads this to load capabilities from installed packages.
65
+ capabilities: z.array(z.record(z.unknown())).default([]),
66
+ // External deps — non-overlayable (lockfile resolves).
67
+ dependencies: z.array(z.record(z.unknown())).default([]),
68
+ });
69
+ export const GenomeSchema = z.object({
70
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
71
+ kind: z.literal("Genome"),
72
+ metadata: MetadataSchema,
73
+ spec: GenomeSpecSchema.default({}),
74
+ });
75
+ // ---------------------------------------------------------------------------
76
+ // LayerPolicy (github.com/ruinosus/dna/policy/v1) — Phase 16
77
+ //
78
+ // One LayerPolicy doc per layer dimension (e.g. tenant, branch, region).
79
+ // Lives at ``<scope>/policies/<id>.yaml``. Replaces ``Module.spec.layers``.
80
+ // Read by the kernel in write-time enforcement; Some Kinds are
81
+ // structurally non-overlayable (Genome, KindDefinition, LayerPolicy
82
+ // itself) — their policy is always locked regardless of doc contents.
83
+ //
84
+ // Policy values: ``open`` (default — never raises),
85
+ // ``restricted`` (only override existing top-level spec keys),
86
+ // ``locked`` (any write raises).
87
+ //
88
+ // 1:1 parity with Python dna.kernel.models.LayerPolicySpec.
89
+ // ---------------------------------------------------------------------------
90
+ // Composition Engine V2 (Phase 17, s-comp-f1-schema, 2026-05-28).
91
+ // 1:1 parity with Python CompositionRule.
92
+ export const CompositionRuleSchema = z.object({
93
+ scope_inheritance: z.enum(["enabled", "disabled"]).default("enabled"),
94
+ merge_strategy: z.enum(["override_full", "field_level"]).default("override_full"),
95
+ tenant_overlay: z.enum(["none", "field_level"]).default("field_level"),
96
+ });
97
+ export const LayerPolicySpecSchema = z.object({
98
+ layer_id: z.string().default(""),
99
+ // Map of kind alias → policy string. Values are normalized to
100
+ // lowercase by the parse hook on the KindPort (TypeScript Zod runs
101
+ // its own coercion via .transform on the LayerPolicyKind to mirror
102
+ // Python's LayerPolicySpec.from_raw normalization).
103
+ policies: z.record(z.string()).default({}),
104
+ // Composition Engine V2: per-Kind composition rules (1:1 parity
105
+ // with Python LayerPolicySpec.composition_rules). Absent Kinds fall
106
+ // back to opt-in defaults (no inheritance, no overlay).
107
+ composition_rules: z.record(CompositionRuleSchema).default({}),
108
+ });
109
+ export const LayerPolicySchema = z.object({
110
+ apiVersion: z.literal("github.com/ruinosus/dna/policy/v1"),
111
+ kind: z.literal("LayerPolicy"),
112
+ metadata: MetadataSchema,
113
+ spec: LayerPolicySpecSchema.default({}),
114
+ });
115
+ // ---------------------------------------------------------------------------
116
+ // Agent (github.com/ruinosus/dna/v1)
117
+ // ---------------------------------------------------------------------------
118
+ export const AgentSpecSchema = z.object({
119
+ instruction: z.string().optional().default(""),
120
+ instruction_file: z.string().optional(),
121
+ objective: z.string().optional().default(""),
122
+ model: z.string().optional(),
123
+ type: z.string().optional(),
124
+ soul: z.string().optional(),
125
+ skills: z.array(z.string()).default([]),
126
+ tools: z.array(z.string()).default([]),
127
+ team_members: z.array(z.string()).default([]),
128
+ tags: z.array(z.string()).default([]),
129
+ guardrails: z.array(z.string()).default([]),
130
+ promptTemplate: z.string().optional(),
131
+ // Phase 14x — tool-group specialization (TS parity with Python).
132
+ tool_groups: z.array(z.string()).default([]),
133
+ // s-mcp-servers-on-agent (2026-07-07, spec
134
+ // 2026-07-07-mcp-first-tools-design.md §5.1) — external MCP servers
135
+ // this agent consumes. Each entry is EITHER a plain string ref
136
+ // ("drawio" ≡ {ref: "drawio"}) OR an object with per-agent overrides:
137
+ // ref (MCPFederation doc name, inherited from _lib), allowed_tools
138
+ // (per-agent allowlist ∩ the doc's own), timeout_s (call-timeout
139
+ // override). Runtime consumption is Python-only (make_mcp_tools);
140
+ // the TS SDK ships the schema for parity + Studio.
141
+ mcp_servers: z
142
+ .array(z.union([
143
+ z.string(),
144
+ z
145
+ .object({
146
+ ref: z.string(),
147
+ allowed_tools: z.array(z.string()).optional(),
148
+ timeout_s: z.number().optional(),
149
+ })
150
+ .passthrough(),
151
+ ]))
152
+ .default([]),
153
+ // Phase 14w follow-up (2026-05-08) — per-agent shell sandbox
154
+ // opt-in (TS parity with Python). ``true`` forces the
155
+ // DeepAgents ``execute`` tool ON for this agent regardless of
156
+ // the scope-wide ``DNA_AGENT_SHELL_SANDBOX`` env. ``false``
157
+ // forces it OFF. Absent (the default) defers to the env.
158
+ shell_sandbox: z.boolean().optional(),
159
+ // Phase 3C (2026-05-15) — reflection pattern opt-in (TS parity
160
+ // with Python). When true, graph.py renders a "Reflection step
161
+ // (before tool calls)" block into the system prompt instructing
162
+ // the UA to enumerate its plan in one turn before issuing any
163
+ // write tool. Pattern from Anthropic Writing Tools for Agents —
164
+ // reduces uncreated rate for UAs with mandatory_tool_calls.
165
+ reflect_before_write: z.boolean().optional(),
166
+ // P2 architectural fix (2026-05-15) — declarative i18n bundle
167
+ // (TS parity with Python). Maps locale → {key: literal-string}.
168
+ // Callers resolving a PromptTemplate look up
169
+ // locale_strings[locale][key] instead of hardcoding strings in code.
170
+ locale_strings: z.record(z.string(), z.record(z.string(), z.string())).optional(),
171
+ // Phase 1.6 (s-toon-agent-prompts) — opt-in token-efficient
172
+ // encoding for context arrays. ``"toon"`` uses TOON (~40-60%
173
+ // fewer tokens for uniform arrays); ``"json"`` (default,
174
+ // back-compat) uses compact JSON. Runtime prompt
175
+ // helpers honor this.
176
+ prompt_format: z.enum(["json", "toon"]).optional(),
177
+ // s-per-agent-max-turns (2026-05-12) — per-agent recursion budget
178
+ // for delegation.call_agent. Single-turn JSON-gen agents
179
+ // (tool_groups: [none]) can ship max_turns: 3. Multi-turn cognitive
180
+ // scribes that call many read tools before write need 25-30.
181
+ // Absent → delegation.py default (25). LangGraph recursion_limit
182
+ // = max_turns * 4.
183
+ max_turns: z.number().int().positive().optional(),
184
+ // s-agent-kind-field-langgraph-react (2026-05-12) — pick agent
185
+ // harness. "deepagent" (default) = full create_deep_agent.
186
+ // "langgraph-react" = lightweight create_react_agent (no filesystem
187
+ // built-ins, no GP subagent). For simple read agents.
188
+ agent_kind: z.enum(["deepagent", "langgraph-react"]).optional(),
189
+ // s-ua-agent-contract-fields (2026-05-13) — structural agent
190
+ // contract. Replaces ad-hoc markdown copy-paste with typed fields
191
+ // validated at parse + graph-build time, rendered into the system
192
+ // prompt automatically.
193
+ //
194
+ // mandatory_tool_calls: tool slugs the UA MUST invoke before
195
+ // stopping. Validated by `s-ua-contract-graph-validation` —
196
+ // warn-loud when a slug isn't in `tools` or available via
197
+ // `tool_groups`. Renders into the system prompt as
198
+ // "Mandatory tool calls" by `s-ua-contract-prompt-injection`.
199
+ //
200
+ // input_schema: expected shape of the input the UA receives.
201
+ // Object = inline JSON schema; string = reference to a Skill
202
+ // or KindDefinition that describes the shape. Renders into the
203
+ // system prompt as "Expected Input" with a JSON example.
204
+ //
205
+ // invoked_by_engine: alias of the CognitiveEngine that
206
+ // typically dispatches this UA. Drives discovery — Studio +
207
+ // eval-lab link agents to their engine.
208
+ mandatory_tool_calls: z.array(z.string()).default([]),
209
+ input_schema: z.union([z.record(z.unknown()), z.string()]).optional(),
210
+ invoked_by_engine: z.string().optional(),
211
+ // JARVIS — opt-in voice persona block (e-jarvis-voice-module).
212
+ // Presence flips the UA from text-only to voice-reachable via
213
+ // POST /voice/sessions. Mirrors Python `VoicePersona` dataclass.
214
+ voice_persona: z.object({
215
+ voice: z.string().default("cedar"),
216
+ style: z.string().optional(),
217
+ archetype: z.string().optional(),
218
+ interruption_tolerance: z.enum(["high", "medium", "low"]).default("high"),
219
+ preamble: z.boolean().default(false),
220
+ mcp_egress: z.boolean().default(false),
221
+ wake_word: z.string().optional(),
222
+ budget: z.number().default(5.0),
223
+ }).optional(),
224
+ // s-jarvis-cross-scope (2026-05-26) — list of scopes this agent's
225
+ // READ tools (recall_*, ecphore, search_documents, list_documents)
226
+ // may iterate. Writes still land in the mounted scope. ``["*"]``
227
+ // means "every scope the source exposes" — used by JARVIS as the
228
+ // user-level personal assistant. Empty/undefined = legacy single-
229
+ // scope behaviour. Mirrors Python ``AgentSpec.target_scopes``.
230
+ target_scopes: z.array(z.string()).optional(),
231
+ // Kind-Writer mode (feat/kind-writer-pilot) — declarative contract for a
232
+ // UA that writes a Kind via structured emission (TS parity with Python).
233
+ // ``writes_kind`` is the target Kind name. ``creative_slots`` are spec
234
+ // fields the LLM fills with generated content. ``system_slots`` maps spec
235
+ // fields to deterministic sources (e.g. {"insight": "input.oracle_id"}).
236
+ // Spec fields only here — no behavior wired yet.
237
+ writes_kind: z.string().optional(),
238
+ creative_slots: z.array(z.string()).default([]),
239
+ system_slots: z.record(z.string(), z.string()).default({}),
240
+ // Multi-Kind mode (feat/kind-writer-multikind) — a UA that writes N Kinds
241
+ // per run (e.g. narrative-scribe → N ADRs + 1 Retrospective). Maps each
242
+ // target Kind name to its OWN {creative_slots, system_slots} block. An agent
243
+ // uses EITHER writes_kind (single) OR writes_kinds (multi), never both.
244
+ // (TS parity with Python AgentSpec.writes_kinds.)
245
+ writes_kinds: z
246
+ .record(z.string(), z.record(z.string(), z.any()))
247
+ .default({}),
248
+ // Declarative reads (feat/scribe-migrate-6) — symmetric to system_slots.
249
+ // ``reads`` maps a read-name to its params, e.g.
250
+ // {"oracle_verdicts": {"n": 3}, "engrams": {"n": 5}}. The SYSTEM fetches the
251
+ // data (reader registry, called directly — not via LLM tool-calls) and
252
+ // injects it into dna_input.reads AND the agent's prompt. The scribe becomes
253
+ // a pure composer (zero read tools).
254
+ reads: z.record(z.string(), z.record(z.string(), z.any())).default({}),
255
+ // s-delegation-declarative (2026-07-07) — declarative opt-in to the
256
+ // delegation surface (TS parity with Python ``DelegationTargetFor``).
257
+ // Replaces the hardcoded DELEGATION_CATALOG that used to live in
258
+ // dna_shared.manifest_tools.delegation_tools: a UA that wants to
259
+ // receive delegated work (e.g. from JARVIS via ``delegate_to``)
260
+ // declares this block — user-installed UAs opt in by declaration.
261
+ //
262
+ // Shape rationale: an object (not a bare list of delegator names)
263
+ // because the old catalog carried per-target metadata — ``format``
264
+ // is load-bearing (drives how delegate_to parses the subagent's
265
+ // output); ``typical_seconds`` + ``use_when`` drive the delegator's
266
+ // narration and target choice.
267
+ // agents: delegator allowlist; ["*"] = any agent may delegate here.
268
+ // format: return contract — "slug" (creates a doc, returns its
269
+ // slug) | "json" (structured JSON in final message) | "text"
270
+ // (free-form narrative, default).
271
+ // typical_seconds: rough wait so the delegator can warn the user.
272
+ // use_when: heuristic for when to pick THIS target.
273
+ // purpose: what the target is good at; consumers fall back to
274
+ // metadata.description when absent.
275
+ // Runtime consumption is Python-only (make_delegation_tools); the
276
+ // TS SDK ships the schema for parity + Studio.
277
+ delegation_target_for: z
278
+ .object({
279
+ agents: z.array(z.string()).default([]),
280
+ format: z.enum(["slug", "json", "text"]).default("text"),
281
+ typical_seconds: z.number().int().positive().optional(),
282
+ use_when: z.string().optional(),
283
+ purpose: z.string().optional(),
284
+ })
285
+ .optional(),
286
+ });
287
+ export const AgentSchema = z.object({
288
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
289
+ kind: z.literal("Agent"),
290
+ metadata: MetadataSchema,
291
+ spec: AgentSpecSchema.default({}),
292
+ });
293
+ // ---------------------------------------------------------------------------
294
+ // Actor (github.com/ruinosus/dna/v1)
295
+ // ---------------------------------------------------------------------------
296
+ export const ActorSpecSchema = z.object({
297
+ instruction: z.string().optional().default(""),
298
+ traits: z.array(z.string()).default([]),
299
+ role: z.string().optional().default(""),
300
+ actorType: z.enum(["human", "system", "time"]).default("human"),
301
+ });
302
+ export const ActorSchema = z.object({
303
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
304
+ kind: z.literal("Actor"),
305
+ metadata: MetadataSchema,
306
+ spec: ActorSpecSchema.default({}),
307
+ });
308
+ // ---------------------------------------------------------------------------
309
+ // UseCase (github.com/ruinosus/dna/v1)
310
+ // ---------------------------------------------------------------------------
311
+ export const UseCaseSpecSchema = z.object({
312
+ primary_actor: z.string().optional(),
313
+ supporting_actors: z.array(z.string()).default([]),
314
+ agents: z.array(z.string()).default([]),
315
+ tools: z.array(z.string()).default([]),
316
+ skills: z.array(z.string()).default([]),
317
+ soul: z.string().optional(),
318
+ guardrails: z.array(z.string()).default([]),
319
+ preconditions: z.array(z.string()).default([]),
320
+ main_flow: z.array(z.string()).default([]),
321
+ alternate_flows: z.array(z.record(z.unknown())).default([]),
322
+ postconditions: z.array(z.string()).default([]),
323
+ success_criteria: z.array(z.string()).default([]),
324
+ });
325
+ export const UseCaseSchema = z.object({
326
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
327
+ kind: z.literal("UseCase"),
328
+ metadata: MetadataSchema,
329
+ spec: UseCaseSpecSchema.default({}),
330
+ });
331
+ // ---------------------------------------------------------------------------
332
+ // Tool (github.com/ruinosus/dna/v1)
333
+ //
334
+ // Declarative, invocable capability an agent can call. Bridges helix with
335
+ // OpenAI/Anthropic tool-calling conventions.
336
+ // ---------------------------------------------------------------------------
337
+ export const ToolTypeEnum = z.enum(["http", "mcp", "python", "shell", "builtin"]);
338
+ export const ToolAuthTypeEnum = z.enum(["none", "api_key", "bearer", "oauth2"]);
339
+ export const ToolSpecSchema = z.object({
340
+ type: ToolTypeEnum.default("builtin"),
341
+ endpoint: z.string().optional().default(""),
342
+ method: z.string().optional().default("POST"),
343
+ mcp_server: z.string().optional().default(""),
344
+ mcp_tool: z.string().optional().default(""),
345
+ python_module: z.string().optional().default(""),
346
+ python_callable: z.string().optional().default(""),
347
+ shell_command: z.string().optional().default(""),
348
+ input_schema: z.record(z.unknown()).default({}),
349
+ output_schema: z.record(z.unknown()).default({}),
350
+ auth_type: ToolAuthTypeEnum.default("none"),
351
+ auth_env_var: z.string().optional().default(""),
352
+ read_only: z.boolean().default(true),
353
+ requires_confirmation: z.boolean().default(false),
354
+ tags: z.array(z.string()).default([]),
355
+ examples: z.array(z.record(z.unknown())).default([]),
356
+ });
357
+ export const ToolSchema = z.object({
358
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
359
+ kind: z.literal("Tool"),
360
+ metadata: MetadataSchema,
361
+ spec: ToolSpecSchema.default({}),
362
+ });
363
+ // ---------------------------------------------------------------------------
364
+ // Skill (agentskills.io/v1)
365
+ // ---------------------------------------------------------------------------
366
+ export const SkillSpecSchema = z.object({
367
+ instruction: z.string().optional().default(""),
368
+ scripts: z.record(z.string()).default({}),
369
+ references: z.record(z.string()).default({}),
370
+ assets: z.record(z.string()).default({}),
371
+ extras: z.record(z.record(z.string())).default({}),
372
+ root_files: z.record(z.string()).default({}),
373
+ });
374
+ export const SkillSchema = z.object({
375
+ apiVersion: z.literal("agentskills.io/v1"),
376
+ kind: z.literal("Skill"),
377
+ metadata: MetadataSchema,
378
+ spec: SkillSpecSchema.default({}),
379
+ });
380
+ // ---------------------------------------------------------------------------
381
+ // Soul (soulspec.org/v1)
382
+ // ---------------------------------------------------------------------------
383
+ export const SoulSpecSchema = z.object({
384
+ soul_content: z.string().optional().default(""),
385
+ soul_json: z.record(z.unknown()).optional(),
386
+ style_content: z.string().optional().default(""),
387
+ agents_content: z.string().optional().default(""),
388
+ });
389
+ export const SoulSchema = z.object({
390
+ apiVersion: z.literal("soulspec.org/v1"),
391
+ kind: z.literal("Soul"),
392
+ metadata: MetadataSchema,
393
+ spec: SoulSpecSchema.default({}),
394
+ });
395
+ // ---------------------------------------------------------------------------
396
+ // AgentDefinition (agents.md/v1)
397
+ // ---------------------------------------------------------------------------
398
+ export const AgentDefinitionSpecSchema = z.object({
399
+ content: z.string().optional().default(""),
400
+ });
401
+ export const AgentDefinitionSchema = z.object({
402
+ apiVersion: z.literal("agents.md/v1"),
403
+ kind: z.literal("AgentDefinition"),
404
+ metadata: MetadataSchema,
405
+ spec: AgentDefinitionSpecSchema.default({}),
406
+ });
407
+ // ---------------------------------------------------------------------------
408
+ // Guardrail (github.com/ruinosus/dna/v1)
409
+ // ---------------------------------------------------------------------------
410
+ export const GuardrailSpecSchema = z.object({
411
+ rules: z.array(z.string()).default([]),
412
+ severity: z.string().optional().default("warn"),
413
+ scope: z.string().optional().default("both"),
414
+ });
415
+ export const GuardrailSchema = z.object({
416
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
417
+ kind: z.literal("Guardrail"),
418
+ metadata: MetadataSchema,
419
+ spec: GuardrailSpecSchema.default({}),
420
+ });
421
+ // ---------------------------------------------------------------------------
422
+ // KindDefinition (github.com/ruinosus/dna/core/v1) — meta-kind
423
+ // ---------------------------------------------------------------------------
424
+ export const KIND_DEFINITION_API_VERSION = "github.com/ruinosus/dna/core/v1";
425
+ export const KIND_DEFINITION_KIND = "KindDefinition";
426
+ export const KindDefinitionSpecSchema = z.object({
427
+ target_api_version: z.string().min(1),
428
+ target_kind: z.string().min(1),
429
+ alias: z.string().min(1),
430
+ origin: z.string().min(1),
431
+ is_root: z.boolean().default(false),
432
+ prompt_target: z.boolean().default(false),
433
+ flatten_in_context: z.boolean().default(false),
434
+ schema: z.record(z.unknown()).default({}),
435
+ docs: z.string().optional(),
436
+ storage: z.record(z.unknown()),
437
+ dep_filters: z.record(z.string()).nullable().optional(),
438
+ default_agent: z.string().nullable().optional(),
439
+ // UI hints — read by DeclarativeKindPort from the parsed spec. Python's
440
+ // KindDefinitionSpec carried these since the UI-hints feature; the TS zod
441
+ // object silently STRIPPED them (latent divergence — per-scope KIND.yaml
442
+ // styles fell back to the derived origin-hash color in TS). Exposed by
443
+ // the F3 descriptor pilot (kaizen.kind.yaml graph_style), fixed here.
444
+ graph_style: z.record(z.string()).nullable().optional(),
445
+ ascii_icon: z.string().nullable().optional(),
446
+ display_label: z.string().nullable().optional(),
447
+ // ---- F3 descriptor fields (spec 2026-06-10-kinds-descriptor-f3, D2) ----
448
+ // These close the gap between hand-written Kind classes and the
449
+ // declarative descriptor so builtin record Kinds can be expressed as
450
+ // `.kind.yaml` package data. Defaults preserve today's behavior.
451
+ // 1:1 parity with Python KindDefinitionSpec (models.py).
452
+ //
453
+ // `plane`: "composition" | "record" — mirrors KindBase.plane.
454
+ plane: z.enum(["composition", "record"]).default("composition"),
455
+ // `tenant_scope`: "tenanted" | "global" — mirrors TenantScope. Optional
456
+ // (no Zod default) so the normalizing transform below can record whether
457
+ // it was EXPLICITLY declared — undeclared kinds stay permissive
458
+ // (Phase 1 back-compat; see `tenant_scope_declared`).
459
+ tenant_scope: z.enum(["tenanted", "global"]).optional(),
460
+ // `summary`: declarative list-endpoint projection — {field: default}.
461
+ // List form ["a", "b"] is normalized by the transform below to a dict
462
+ // with per-schema-type defaults (array→[], boolean→false,
463
+ // number/integer→null, else ""). null/absent = no projection.
464
+ summary: z
465
+ .union([z.record(z.unknown()), z.array(z.string())])
466
+ .nullable()
467
+ .optional(),
468
+ // `embed`: source fields for embedding text (feeds D4 derivation).
469
+ embed: z.array(z.string()).nullable().optional(),
470
+ // `is_runtime_artifact`: docs generated by runtime workflows.
471
+ is_runtime_artifact: z.boolean().default(false),
472
+ // `prompt_target_priority`: was hardcoded 5 in DeclarativeKindPort —
473
+ // default 5 preserves that.
474
+ prompt_target_priority: z.number().int().default(5),
475
+ // Kernel classification flags — mirror KindBase defaults.
476
+ scope_inheritable: z.boolean().default(true),
477
+ is_overlayable: z.boolean().default(true),
478
+ // Extra volatile spec fields, unioned with KindBase volatileSpecFields.
479
+ volatile_spec_fields: z.array(z.string()).nullable().optional(),
480
+ // ---- Descriptor expressiveness fields (spec 2026-06-11, D1/D3-D7) -------
481
+ // All optional; absent → null/undefined preserves today's behavior. 1:1
482
+ // parity with Python KindDefinitionSpec (models.py). Consumed by
483
+ // DeclarativeKindPort (meta.ts).
484
+ //
485
+ // D1 `ui`: raw StudioUIMetadata mapping. Keys are validated ⊆
486
+ // StudioUIMetadata fields (strict — unknown key → ZodError); the allowed
487
+ // set is derived from UI_METADATA_FIELDS, the single source of truth (no
488
+ // second hardcoded list). The port reconstructs the real StudioUIMetadata
489
+ // so /kinds/manifest output is byte-identical to the deleted class.
490
+ ui: z
491
+ .record(z.unknown())
492
+ .refine((m) => Object.keys(m).every((k) => UI_METADATA_FIELDS.includes(k)), (m) => ({
493
+ message: "spec.ui has unknown key(s): " +
494
+ Object.keys(m)
495
+ .filter((k) => !UI_METADATA_FIELDS.includes(k))
496
+ .sort()
497
+ .join(", ") +
498
+ ` (allowed: ${[...UI_METADATA_FIELDS].sort().join(", ")})`,
499
+ }))
500
+ .nullable()
501
+ .optional(),
502
+ // D3 `describe`: template string OR projection mapping ({path: field}).
503
+ describe: z.union([z.string(), z.record(z.unknown())]).nullable().optional(),
504
+ // D4 `ui_schema`: pass-through widget-hint bag, permissive (unknown keys ok).
505
+ ui_schema: z.record(z.unknown()).nullable().optional(),
506
+ // D5 `spec_defaults`: shallow-merge map applied before schema validation.
507
+ spec_defaults: z.record(z.unknown()).nullable().optional(),
508
+ // D6 `default_agent_field`: spec field returned VERBATIM by
509
+ // getDefaultAgentName.
510
+ default_agent_field: z.string().nullable().optional(),
511
+ // D7 `description_fallback_field`: pass-through string attr for Studio.
512
+ description_fallback_field: z.string().nullable().optional(),
513
+ });
514
+ /** Normalize spec.summary to its dict form (F3 spec D2).
515
+ *
516
+ * Dict form `{field: default}` passes through. List form `["a", "b"]`
517
+ * gets a default per the field's declared type in `schema.properties`:
518
+ * array→[], boolean→false, number/integer→null, anything else (incl.
519
+ * fields absent from the schema)→"". Mirrors Python
520
+ * KindDefinitionSpec._normalize_summary. */
521
+ function normalizeKindDefSummary(summary, schema) {
522
+ if (summary == null)
523
+ return null;
524
+ if (!Array.isArray(summary))
525
+ return summary;
526
+ const props = schema.properties ?? {};
527
+ const out = {};
528
+ for (const fieldName of summary) {
529
+ const prop = props[fieldName];
530
+ const ptype = prop != null && typeof prop === "object"
531
+ ? prop.type
532
+ : undefined;
533
+ if (ptype === "array")
534
+ out[fieldName] = [];
535
+ else if (ptype === "boolean")
536
+ out[fieldName] = false;
537
+ else if (ptype === "number" || ptype === "integer")
538
+ out[fieldName] = null;
539
+ else
540
+ out[fieldName] = "";
541
+ }
542
+ return out;
543
+ }
544
+ // F3 (spec D2): normalized spec — adds `tenant_scope_declared` (true iff
545
+ // tenant_scope was explicitly present; NOT a user-facing field), defaults
546
+ // tenant_scope to "tenanted", and normalizes the list form of `summary`.
547
+ // The plain object schema stays exported above because zodSpecToJsonSchema
548
+ // needs `.shape` (ZodObject, not ZodEffects).
549
+ const KindDefinitionSpecNormalizedSchema = KindDefinitionSpecSchema.transform((s) => ({
550
+ ...s,
551
+ tenant_scope: s.tenant_scope ?? "tenanted",
552
+ tenant_scope_declared: s.tenant_scope !== undefined,
553
+ summary: normalizeKindDefSummary(s.summary, s.schema),
554
+ }));
555
+ export const KindDefinitionSchema = z.object({
556
+ apiVersion: z.literal(KIND_DEFINITION_API_VERSION),
557
+ kind: z.literal(KIND_DEFINITION_KIND),
558
+ metadata: MetadataSchema,
559
+ spec: KindDefinitionSpecNormalizedSchema,
560
+ });
561
+ // ---------------------------------------------------------------------------
562
+ // Hook (github.com/ruinosus/dna/v1)
563
+ // ---------------------------------------------------------------------------
564
+ export const HookActionEnum = z.enum(["inject_fields", "log", "script"]);
565
+ export const HookSpecSchema = z.object({
566
+ target: z.string().min(1).default("pre_build_prompt"), // "pre_build_prompt", "post_build_prompt", etc.
567
+ type: z.enum(["middleware", "event"]).default("middleware"),
568
+ action: HookActionEnum.default("inject_fields"),
569
+ fields: z.record(z.unknown()).default({}), // For inject_fields action
570
+ body: z.string().optional().default(""), // Raw body (script code or YAML fields)
571
+ });
572
+ export const HookSchema = z.object({
573
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
574
+ kind: z.literal("Hook"),
575
+ metadata: MetadataSchema,
576
+ spec: HookSpecSchema.default({}),
577
+ });
578
+ // ---------------------------------------------------------------------------
579
+ // Community channel — artifact allowlist
580
+ //
581
+ // Spec: docs/superpowers/specs/2026-05-18-source-as-distribution.md.
582
+ // The CommunityItem Kind was pruned (s-prune-speculative-extensions,
583
+ // recovery: git history); the FS install channel lives on and this
584
+ // allowlist mirrors Python COMMUNITY_ARTIFACT_KINDS. Adding a Kind
585
+ // here requires the same entry in python/dna/kernel/models.py.
586
+ // ---------------------------------------------------------------------------
587
+ export const CommunityArtifactKindEnum = z.enum([
588
+ "Skill",
589
+ "Soul",
590
+ "Agent",
591
+ "Hook",
592
+ "SafetyPolicy",
593
+ "Recognizer",
594
+ "Guardrail",
595
+ ]);
596
+ // ---------------------------------------------------------------------------
597
+ // TextBlock + HtmlBlock (github.com/ruinosus/dna/v1) — generative blocks
598
+ //
599
+ // Spec: s-generative-blocks (2026-05-19). Mirrors Python's
600
+ // TextBlockSpec / HtmlBlockSpec in dna.kernel.models.
601
+ // ---------------------------------------------------------------------------
602
+ export const TextBlockSpecSchema = z.object({
603
+ title: z.string().default(""),
604
+ body: z.string().default(""),
605
+ area: z.string().default(""),
606
+ owner: z.string().nullable().default(null),
607
+ generated_by: z.string().default(""),
608
+ affect: z.string().default(""),
609
+ tags: z.array(z.string()).default([]),
610
+ created_at: z.string().default(""),
611
+ });
612
+ export const TextBlockSchema = z.object({
613
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
614
+ kind: z.literal("TextBlock"),
615
+ metadata: MetadataSchema,
616
+ spec: TextBlockSpecSchema.default({}),
617
+ });
618
+ export const HtmlBlockSpecSchema = z.object({
619
+ title: z.string().default(""),
620
+ body: z.string().default(""),
621
+ area: z.string().default(""),
622
+ owner: z.string().nullable().default(null),
623
+ generated_by: z.string().default(""),
624
+ affect: z.string().default(""),
625
+ tags: z.array(z.string()).default([]),
626
+ created_at: z.string().default(""),
627
+ sandbox_features: z.string().default(""),
628
+ estimated_height_px: z.number().int().nonnegative().default(0),
629
+ });
630
+ export const HtmlBlockSchema = z.object({
631
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
632
+ kind: z.literal("HtmlBlock"),
633
+ metadata: MetadataSchema,
634
+ spec: HtmlBlockSpecSchema.default({}),
635
+ });
636
+ // ---------------------------------------------------------------------------
637
+ // HtmlTemplate (github.com/ruinosus/dna/v1) — Mustache-templated reusable widget.
638
+ // Story: s-html-templates (2026-05-19).
639
+ // ---------------------------------------------------------------------------
640
+ export const HtmlTemplateSpecSchema = z.object({
641
+ title: z.string().default(""),
642
+ description: z.string().default(""),
643
+ body: z.string().default(""),
644
+ version: z.string().default("0.1.0"),
645
+ params: z.record(z.unknown()).default({}),
646
+ example: z.record(z.unknown()).default({}),
647
+ theme: z.string().default(""),
648
+ area: z.string().default(""),
649
+ owner: z.string().nullable().default(null),
650
+ generated_by: z.string().default(""),
651
+ tags: z.array(z.string()).default([]),
652
+ created_at: z.string().default(""),
653
+ });
654
+ export const HtmlTemplateSchema = z.object({
655
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
656
+ kind: z.literal("HtmlTemplate"),
657
+ metadata: MetadataSchema,
658
+ spec: HtmlTemplateSpecSchema.default({}),
659
+ });
660
+ // ---------------------------------------------------------------------------
661
+ // SafetyPolicy (github.com/ruinosus/dna/v1)
662
+ // ---------------------------------------------------------------------------
663
+ export const SafetyRuleSchema = z.object({
664
+ type: z.enum(["pii", "content_safety", "topic_restriction", "prompt_injection", "banned_words", "custom_regex"]),
665
+ tier: z.enum(["regex", "ml", "api", "llm_judge"]).optional(),
666
+ entities: z.array(z.string()).optional(),
667
+ region: z.string().optional(),
668
+ categories: z.array(z.string()).optional(),
669
+ threshold: z.number().optional(),
670
+ allowed: z.array(z.string()).optional(),
671
+ denied: z.array(z.string()).optional(),
672
+ patterns: z.array(z.string()).optional(),
673
+ words: z.array(z.string()).optional(),
674
+ });
675
+ export const SafetyPolicySpecSchema = z.object({
676
+ scope: z.enum(["input", "output", "both"]).default("both"),
677
+ action: z.enum(["mask", "block", "log"]).default("mask"),
678
+ severity: z.enum(["error", "warn"]).default("error"),
679
+ rules: z.array(SafetyRuleSchema).default([]),
680
+ recognizers: z.array(z.string()).default([]),
681
+ // Phase 7 — ml-privacy-filter engine. All optional (backward-compatible).
682
+ engine: z.enum(["presidio", "ml-privacy-filter"]).default("presidio"),
683
+ model: z.string().default("openai/privacy-filter"),
684
+ backend: z.enum(["auto", "transformers", "onnxruntime"]).default("auto"),
685
+ threshold: z.number().min(0).max(1).default(0.8),
686
+ // T1 LOCKED valid values: account_number, private_address, private_email,
687
+ // private_person, private_phone, private_url, private_date, secret
688
+ categories: z.array(z.string()).nullable().default(null),
689
+ mask_char: z.string().default("[REDACTED]"),
690
+ budget_ms: z.number().default(1000),
691
+ });
692
+ export const SafetyPolicySchema = z.object({
693
+ apiVersion: z.literal("github.com/ruinosus/dna/v1"),
694
+ kind: z.literal("SafetyPolicy"),
695
+ metadata: MetadataSchema,
696
+ spec: SafetyPolicySpecSchema.default({}),
697
+ });
698
+ // ---------------------------------------------------------------------------
699
+ // Recognizer (presidio/v1)
700
+ // ---------------------------------------------------------------------------
701
+ export const RecognizerPatternSchema = z.object({
702
+ name: z.string(),
703
+ regex: z.string(),
704
+ score: z.number().min(0).max(1),
705
+ });
706
+ export const RecognizerSpecSchema = z.object({
707
+ entity_type: z.string().min(1),
708
+ language: z.string().default("en"),
709
+ patterns: z.array(RecognizerPatternSchema).default([]),
710
+ deny_list: z.array(z.string()).default([]),
711
+ context: z.array(z.string()).default([]),
712
+ });
713
+ export const RecognizerSchema = z.object({
714
+ apiVersion: z.literal("presidio/v1"),
715
+ kind: z.literal("Recognizer"),
716
+ metadata: MetadataSchema,
717
+ spec: RecognizerSpecSchema,
718
+ });
719
+ // ---------------------------------------------------------------------------
720
+ // Zod → JSON Schema helper
721
+ // ---------------------------------------------------------------------------
722
+ /** Convert a Zod object schema's .shape to a minimal JSON Schema.
723
+ * No external library — walks Zod internals directly. */
724
+ export function zodSpecToJsonSchema(zodSchema) {
725
+ const shape = zodSchema.shape;
726
+ const properties = {};
727
+ function typeOf(z) {
728
+ const def = z?._def;
729
+ const tn = def?.typeName;
730
+ if (tn === "ZodString")
731
+ return { type: "string" };
732
+ if (tn === "ZodNumber" || tn === "ZodInt")
733
+ return { type: "number" };
734
+ if (tn === "ZodBoolean")
735
+ return { type: "boolean" };
736
+ if (tn === "ZodEnum")
737
+ return { type: "string", enum: def.values };
738
+ if (tn === "ZodArray") {
739
+ const items = def.type ? typeOf(def.type) : { type: "string" };
740
+ return { type: "array", items };
741
+ }
742
+ if (tn === "ZodRecord")
743
+ return { type: "object" };
744
+ if (tn === "ZodOptional")
745
+ return typeOf(def.innerType);
746
+ if (tn === "ZodDefault")
747
+ return typeOf(def.innerType);
748
+ if (tn === "ZodObject")
749
+ return { type: "object" };
750
+ if (tn === "ZodUnknown" || tn === "ZodAny")
751
+ return {};
752
+ return {};
753
+ }
754
+ for (const [key, zodType] of Object.entries(shape)) {
755
+ properties[key] = typeOf(zodType);
756
+ }
757
+ return { type: "object", properties };
758
+ }
759
+ // ── DNA namespace ───────────────────────────────────────────────────────────
760
+ // Single authoritative namespace constant (spec §8: swapping the namespace
761
+ // is one commit + a golden regen). NOTE: literal-type positions (z.literal /
762
+ // *.kind.yaml descriptors) must stay in sync with this value — the
763
+ // descriptor + parity suites enforce it.
764
+ export const DNA_NAMESPACE = "github.com/ruinosus/dna";