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,215 @@
1
+ /**
2
+ * ReportBuilder — structured markdown reports for eval, findings, evidence, compliance.
3
+ *
4
+ * Accessed via `mi.reports.evalSummary()`, `mi.reports.findingsSummary()`,
5
+ * `mi.reports.evidenceManifest()`, `mi.reports.complianceMatrix(framework)`.
6
+ *
7
+ * Each report returns Markdown with YAML frontmatter.
8
+ *
9
+ * 1:1 parity with Python dna.kernel.reports.
10
+ */
11
+ // ─── Severity ordering (lower = more severe) ─────────────────────────
12
+ const SEVERITY_ORDER = {
13
+ critical: 0,
14
+ high: 1,
15
+ medium: 2,
16
+ low: 3,
17
+ };
18
+ // ─── Compliance mappings ─────────────────────────────────────────────
19
+ const COMPLIANCE_MAPPINGS = {
20
+ lgpd: {
21
+ critical: ["Art. 6 (tratamento de dados)", "Art. 46 (seguranca)"],
22
+ high: ["Art. 50 (boas praticas)"],
23
+ },
24
+ gdpr: {
25
+ critical: ["Art. 25 (data protection by design)", "Art. 35 (DPIA)"],
26
+ high: ["Art. 32 (security of processing)"],
27
+ },
28
+ nist_ai_rmf: {
29
+ critical: ["MAP-1.1 (intended purpose)", "MEASURE-2.6 (bias)"],
30
+ high: ["MANAGE-2.2 (risk response)"],
31
+ },
32
+ };
33
+ function frontmatter(reportType, scope) {
34
+ const now = new Date().toISOString();
35
+ return `---\nreport: ${reportType}\ngenerated_at: ${now}\nscope: ${scope}\n---\n\n`;
36
+ }
37
+ export class ReportBuilder {
38
+ _mi;
39
+ constructor(mi) {
40
+ this._mi = mi;
41
+ }
42
+ // ─── evalSummary ──────────────────────────────────────────────────
43
+ /** Generate an eval summary report grouped by suite.
44
+ * If `suite` is given, only that suite is included. */
45
+ evalSummary(suite) {
46
+ let runs = this._mi._all("EvalRun");
47
+ if (suite) {
48
+ runs = runs.filter((r) => r.spec.suite === suite);
49
+ }
50
+ let md = frontmatter("eval_summary", this._mi.scope);
51
+ md += "# Eval Summary\n\n";
52
+ if (runs.length === 0) {
53
+ md += "_No eval runs found._\n";
54
+ return md;
55
+ }
56
+ // Group by suite, keep latest per suite (by saved_at)
57
+ const bySuite = new Map();
58
+ for (const run of runs) {
59
+ const spec = run.spec;
60
+ const s = spec.suite ?? "unknown";
61
+ const existing = bySuite.get(s);
62
+ if (!existing) {
63
+ bySuite.set(s, run);
64
+ }
65
+ else {
66
+ const newTs = spec.saved_at ?? "";
67
+ const oldTs = existing.spec.saved_at ?? "";
68
+ if (newTs > oldTs) {
69
+ bySuite.set(s, run);
70
+ }
71
+ }
72
+ }
73
+ const sortedSuites = [...bySuite.entries()].sort(([a], [b]) => a.localeCompare(b));
74
+ for (const [suiteName, run] of sortedSuites) {
75
+ const spec = run.spec;
76
+ const total = spec.total ?? 0;
77
+ const passed = spec.passed ?? 0;
78
+ const rate = total ? `${((passed / total) * 100).toFixed(1)}%` : "N/A";
79
+ const agg = spec.aggregate_score;
80
+ const aggStr = agg != null ? agg.toFixed(2) : "N/A";
81
+ const cost = spec.total_cost_usd;
82
+ const costStr = cost != null ? `$${cost.toFixed(4)}` : "N/A";
83
+ const tokens = spec.total_tokens;
84
+ const tokensStr = tokens != null ? String(tokens) : "N/A";
85
+ md += `## Suite: ${suiteName}\n\n`;
86
+ md += `- **Pass rate:** ${rate}\n`;
87
+ md += `- **Aggregate score:** ${aggStr}\n`;
88
+ md += `- **Cost:** ${costStr}\n`;
89
+ md += `- **Tokens:** ${tokensStr}\n\n`;
90
+ const results = (spec.results ?? []);
91
+ if (results.length > 0) {
92
+ md += "| Case | Status | Score |\n";
93
+ md += "|------|--------|-------|\n";
94
+ for (const r of results) {
95
+ const caseName = r.case ?? "";
96
+ const status = r.status ?? "";
97
+ const score = r.score;
98
+ const scoreStr = score != null ? score.toFixed(2) : "-";
99
+ md += `| ${caseName} | ${status} | ${scoreStr} |\n`;
100
+ }
101
+ md += "\n";
102
+ }
103
+ }
104
+ return md;
105
+ }
106
+ // ─── findingsSummary ──────────────────────────────────────────────
107
+ /** Generate a findings summary grouped by severity.
108
+ * Filters out findings with severity below `minSeverity`. */
109
+ findingsSummary(minSeverity = "low") {
110
+ const findings = this._mi._all("Finding");
111
+ const minLevel = SEVERITY_ORDER[minSeverity] ?? 3;
112
+ const filtered = findings.filter((f) => {
113
+ const sev = f.spec.severity;
114
+ return (SEVERITY_ORDER[sev ?? "low"] ?? 3) <= minLevel;
115
+ });
116
+ let md = frontmatter("findings_summary", this._mi.scope);
117
+ md += "# Findings Summary\n\n";
118
+ if (filtered.length === 0) {
119
+ md += "_No findings found._\n";
120
+ return md;
121
+ }
122
+ // Group by severity
123
+ const bySeverity = new Map();
124
+ for (const f of filtered) {
125
+ const sev = f.spec.severity ?? "low";
126
+ const arr = bySeverity.get(sev) ?? [];
127
+ arr.push(f);
128
+ bySeverity.set(sev, arr);
129
+ }
130
+ for (const sev of ["critical", "high", "medium", "low"]) {
131
+ const group = bySeverity.get(sev);
132
+ if (!group || group.length === 0)
133
+ continue;
134
+ md += `## ${sev.toUpperCase()} (${group.length})\n\n`;
135
+ for (const f of group) {
136
+ const spec = f.spec;
137
+ const title = spec.title ?? f.name;
138
+ const status = spec.status ?? "open";
139
+ const recommendation = spec.recommendation ?? "";
140
+ md += `### ${title}\n\n`;
141
+ md += `- **Status:** ${status}\n`;
142
+ if (recommendation) {
143
+ md += `- **Recommendation:** ${recommendation}\n`;
144
+ }
145
+ md += "\n";
146
+ }
147
+ }
148
+ return md;
149
+ }
150
+ // ─── evidenceManifest ─────────────────────────────────────────────
151
+ /** Generate a manifest of all evidence documents. */
152
+ evidenceManifest() {
153
+ const evidence = this._mi._all("Evidence");
154
+ let md = frontmatter("evidence_manifest", this._mi.scope);
155
+ md += "# Evidence Manifest\n\n";
156
+ if (evidence.length === 0) {
157
+ md += "_No evidence documents found._\n";
158
+ return md;
159
+ }
160
+ md += "| Event Type | Document Ref | SHA-256 | Captured At |\n";
161
+ md += "|------------|-------------|---------|-------------|\n";
162
+ for (const e of evidence) {
163
+ const spec = e.spec;
164
+ const eventType = spec.event_type ?? "";
165
+ const docRef = spec.document_ref ?? "";
166
+ const sha = spec.sha256 ?? "";
167
+ const shaShort = sha.length > 12 ? sha.slice(0, 12) + "..." : sha;
168
+ const captured = spec.captured_at ?? "";
169
+ md += `| ${eventType} | ${docRef} | ${shaShort} | ${captured} |\n`;
170
+ }
171
+ md += "\n";
172
+ return md;
173
+ }
174
+ // ─── complianceMatrix ─────────────────────────────────────────────
175
+ /** Generate a compliance matrix mapping findings to regulatory articles.
176
+ * Supported frameworks: lgpd, gdpr, nist_ai_rmf. */
177
+ complianceMatrix(framework) {
178
+ let md = frontmatter("compliance_matrix", this._mi.scope);
179
+ md += `# Compliance Matrix: ${framework.toUpperCase()}\n\n`;
180
+ const mapping = COMPLIANCE_MAPPINGS[framework];
181
+ if (!mapping) {
182
+ const supported = Object.keys(COMPLIANCE_MAPPINGS).sort().join(", ");
183
+ md += `_Unknown framework: ${framework}. Supported: ${supported}._\n`;
184
+ return md;
185
+ }
186
+ const findings = this._mi._all("Finding");
187
+ md += "| Severity | Count | Regulatory Articles |\n";
188
+ md += "|----------|-------|--------------------|\n";
189
+ for (const sev of ["critical", "high", "medium", "low"]) {
190
+ const articles = mapping[sev] ?? [];
191
+ const count = findings.filter((f) => f.spec.severity === sev).length;
192
+ const articlesStr = articles.length > 0 ? articles.join(", ") : "-";
193
+ md += `| ${sev.toUpperCase()} | ${count} | ${articlesStr} |\n`;
194
+ }
195
+ md += "\n";
196
+ if (findings.length > 0) {
197
+ md += "## Findings by Article\n\n";
198
+ for (const sev of ["critical", "high"]) {
199
+ const articles = mapping[sev] ?? [];
200
+ const sevFindings = findings.filter((f) => f.spec.severity === sev);
201
+ if (sevFindings.length > 0 && articles.length > 0) {
202
+ for (const article of articles) {
203
+ md += `### ${article}\n\n`;
204
+ for (const f of sevFindings) {
205
+ const title = f.spec.title ?? f.name;
206
+ md += `- ${title}\n`;
207
+ }
208
+ md += "\n";
209
+ }
210
+ }
211
+ }
212
+ }
213
+ return md;
214
+ }
215
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Composition Engine V2 — resolution types + merge utilities (TS twin).
3
+ *
4
+ * 1:1 parity with python/dna/kernel/resolver.py
5
+ * (Phase 17, Story s-comp-f2-resolver / s-composition-v2-ts-twin).
6
+ *
7
+ * NB: This is the V2 *resolver* — distinct from `composition.ts` (which defines
8
+ * CompositionProfile dependency wiring between Kinds). The two coexist with
9
+ * clear naming until a future Phase 17 cleanup unifies them.
10
+ *
11
+ * This module defines the data shapes returned by `kernel.resolveDocument` plus
12
+ * the pure merge utilities. The actual resolver *method* lives on the Kernel
13
+ * class on the Python side (`Kernel.resolve_document`); porting that full
14
+ * orchestration (parent-chain walk + observers + granular cache) is a separate,
15
+ * larger concern. What the story names — "the resolver" (provenance + field
16
+ * merge) — is this module.
17
+ *
18
+ * Wire parity: `serialize()` emits the SAME snake_case keys as Python
19
+ * (scope/tenant/found/contributed/version_sha/effective_layer/
20
+ * contributions_by_field/is_inherited) so an HTTP consumer cannot tell which
21
+ * runtime produced the response.
22
+ *
23
+ * Resolution chain for (scope=S, tenant=T, kind=K, name=N):
24
+ * walk Genome.parent_scope transitively, building ordered layers:
25
+ * L0 = (S, T) local + tenant overlay
26
+ * L1 = (S, null) local base
27
+ * L2 = (P, T) parent + tenant overlay (if rule allows)
28
+ * L3 = (P, null) parent base
29
+ * ...
30
+ * Apply merge strategy from LayerPolicy.composition_rules[K]:
31
+ * - override_full → first non-null layer wins entirely
32
+ * - field_level → deep-merge specs; later layers contribute individual
33
+ * fields and provenance tracks which layer set each field.
34
+ *
35
+ * Bootstrap Kinds (Genome, LayerPolicy, KindDefinition) are NEVER inherited.
36
+ */
37
+ /** Kinds excluded from inheritance regardless of LayerPolicy. Structural
38
+ * (Genome = scope identity; LayerPolicy = the policy Kind itself;
39
+ * KindDefinition = registered before docs parse, can't be overlaid). */
40
+ export declare const BOOTSTRAP_KINDS: ReadonlySet<string>;
41
+ /** Scope inheritance default = DENYLIST (s-platform-inherit-by-default).
42
+ * When a scope has NO LayerPolicy with composition_rules, EVERY Kind defaults
43
+ * to scope_inheritance=enabled EXCEPT the per-scope ledger + structural Kinds
44
+ * below. Mirrors the kernel's `_NON_INHERITABLE_KINDS`. */
45
+ export declare const DEFAULT_NON_INHERITABLE_KINDS_V1: ReadonlySet<string>;
46
+ /** Display set ONLY (NOT the inheritance source of truth anymore): the
47
+ * composition-summary endpoint iterates this to surface per-Kind
48
+ * local/inherited counts in the Studio sidebar. Membership semantics live in
49
+ * the denylist above. */
50
+ export declare const DEFAULT_INHERITABLE_KINDS_V1: ReadonlySet<string>;
51
+ /** Hard limit on parent_scope chain depth — guards against runaway loops
52
+ * (cycle detection runs first, but this is a belt-and-suspenders cap). */
53
+ export declare const MAX_RESOLUTION_DEPTH = 16;
54
+ /** A raw manifest document dict (pre-`Document` parse) — the payload half
55
+ * of a {@link Contribution}. Twin of the Py `Raw = dict[str, Any]` alias. */
56
+ export type Raw = Record<string, unknown>;
57
+ /** One step in the resolution chain — a single (scope, tenant) pair consulted
58
+ * by the resolver.
59
+ *
60
+ * - `found` records whether the source had ANY doc at this layer.
61
+ * - `contributed` flips true when this layer ACTUALLY influenced the final
62
+ * merged doc (override_full: only the winning layer; field_level: possibly
63
+ * several). */
64
+ export declare class ResolutionLayer {
65
+ readonly scope: string;
66
+ readonly tenant: string | null;
67
+ readonly found: boolean;
68
+ readonly contributed: boolean;
69
+ /** Version sha or content hash if the source exposes it (best-effort). */
70
+ readonly versionSha: string | null;
71
+ constructor(opts: {
72
+ scope: string;
73
+ tenant?: string | null;
74
+ found: boolean;
75
+ contributed?: boolean;
76
+ versionSha?: string | null;
77
+ });
78
+ }
79
+ /** Ordered list of layers consulted, highest-priority-first.
80
+ *
81
+ * Layer order is:
82
+ * [local+tenant, local+base, parent+tenant, parent+base,
83
+ * grandparent+tenant, grandparent+base, ...]
84
+ * The HIGHEST priority layer is first — local-tenant beats everything else. */
85
+ export declare class ResolutionPath {
86
+ steps: ResolutionLayer[];
87
+ constructor(steps?: ResolutionLayer[]);
88
+ /** The single layer that became the doc's primary origin. For override_full
89
+ * this is the layer whose doc was returned wholesale; for field_level it is
90
+ * the highest-priority layer that contributed metadata/envelope (semantic
91
+ * primary owner). */
92
+ get effectiveLayer(): ResolutionLayer | null;
93
+ /** JSON-friendly serialization for HTTP responses (snake_case = Python). */
94
+ serialize(): Record<string, unknown>;
95
+ }
96
+ /** Result of `kernel.resolveDocument` — the doc plus full provenance.
97
+ *
98
+ * Studio renders banner/badge directly from `provenance` + `isInherited` —
99
+ * no client-side detection logic needed. */
100
+ export declare class ResolvedDocument {
101
+ /** The merged document (or null if not found in any layer). */
102
+ doc: Raw | null;
103
+ /** Full ordered resolution path. Includes layers consulted but not
104
+ * contributing. */
105
+ provenance: ResolutionPath;
106
+ /** True when `effectiveLayer.scope != requestedScope`. Convenience derived
107
+ * from provenance; Studio uses this for badge/banner toggle. */
108
+ isInherited: boolean;
109
+ /** Field-path → scope name. Populated when merge_strategy=field_level. Lets
110
+ * the Detail page show `spec.persona ← _lib` annotations. */
111
+ contributionsByField: Record<string, string>;
112
+ constructor(opts: {
113
+ doc: Raw | null;
114
+ provenance: ResolutionPath;
115
+ isInherited: boolean;
116
+ contributionsByField?: Record<string, string>;
117
+ });
118
+ serialize(): Record<string, unknown>;
119
+ }
120
+ /** A (layer, raw-doc-or-null) pair — one contribution, highest-priority-first. */
121
+ export type Contribution = [ResolutionLayer, Raw | null];
122
+ /** First non-null contribution wins entirely.
123
+ *
124
+ * Used for assetic Kinds (LottieAsset, ImagePrompt) where partial override
125
+ * makes no sense (binary payload is atomic).
126
+ *
127
+ * Input: contributions highest-priority-first.
128
+ * Output: [winningRawDoc, winningLayer] or [null, null] if all miss. */
129
+ export declare function mergeOverrideFull(contributions: Contribution[]): [Raw | null, ResolutionLayer | null];
130
+ /** Deep-merge `spec` dicts. Higher-priority layers shadow lower per-field.
131
+ * Returns [mergedDoc, primaryOriginLayer, fieldsByOrigin].
132
+ *
133
+ * Algorithm:
134
+ * - First pass: find the PRIMARY layer (highest-priority hit). Its metadata
135
+ * + envelope (apiVersion, kind) carry over wholesale.
136
+ * - Second pass: iterate contributions LOWEST → HIGHEST priority, overwriting
137
+ * spec keys in a fresh merged dict. After the loop the highest-priority
138
+ * layer's values win per-field.
139
+ * - Track which layer each FINAL spec field came from so the UI can render
140
+ * `spec.persona ← _lib` annotations.
141
+ *
142
+ * Edge cases:
143
+ * - All-null contributions → [null, null, {}].
144
+ * - Single hit → equivalent to override_full.
145
+ * - Spec missing or non-object → that layer skipped silently. */
146
+ export declare function mergeFieldLevel(contributions: Contribution[]): [Raw | null, ResolutionLayer | null, Record<string, string>];
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Composition Engine V2 — resolution types + merge utilities (TS twin).
3
+ *
4
+ * 1:1 parity with python/dna/kernel/resolver.py
5
+ * (Phase 17, Story s-comp-f2-resolver / s-composition-v2-ts-twin).
6
+ *
7
+ * NB: This is the V2 *resolver* — distinct from `composition.ts` (which defines
8
+ * CompositionProfile dependency wiring between Kinds). The two coexist with
9
+ * clear naming until a future Phase 17 cleanup unifies them.
10
+ *
11
+ * This module defines the data shapes returned by `kernel.resolveDocument` plus
12
+ * the pure merge utilities. The actual resolver *method* lives on the Kernel
13
+ * class on the Python side (`Kernel.resolve_document`); porting that full
14
+ * orchestration (parent-chain walk + observers + granular cache) is a separate,
15
+ * larger concern. What the story names — "the resolver" (provenance + field
16
+ * merge) — is this module.
17
+ *
18
+ * Wire parity: `serialize()` emits the SAME snake_case keys as Python
19
+ * (scope/tenant/found/contributed/version_sha/effective_layer/
20
+ * contributions_by_field/is_inherited) so an HTTP consumer cannot tell which
21
+ * runtime produced the response.
22
+ *
23
+ * Resolution chain for (scope=S, tenant=T, kind=K, name=N):
24
+ * walk Genome.parent_scope transitively, building ordered layers:
25
+ * L0 = (S, T) local + tenant overlay
26
+ * L1 = (S, null) local base
27
+ * L2 = (P, T) parent + tenant overlay (if rule allows)
28
+ * L3 = (P, null) parent base
29
+ * ...
30
+ * Apply merge strategy from LayerPolicy.composition_rules[K]:
31
+ * - override_full → first non-null layer wins entirely
32
+ * - field_level → deep-merge specs; later layers contribute individual
33
+ * fields and provenance tracks which layer set each field.
34
+ *
35
+ * Bootstrap Kinds (Genome, LayerPolicy, KindDefinition) are NEVER inherited.
36
+ */
37
+ // ──────────────────────────────────────────────────────────────────────
38
+ // Module-level constants — sensible defaults (V1 → V2 transition)
39
+ // ──────────────────────────────────────────────────────────────────────
40
+ /** Kinds excluded from inheritance regardless of LayerPolicy. Structural
41
+ * (Genome = scope identity; LayerPolicy = the policy Kind itself;
42
+ * KindDefinition = registered before docs parse, can't be overlaid). */
43
+ export const BOOTSTRAP_KINDS = new Set([
44
+ "Genome",
45
+ "LayerPolicy",
46
+ "KindDefinition",
47
+ ]);
48
+ /** Scope inheritance default = DENYLIST (s-platform-inherit-by-default).
49
+ * When a scope has NO LayerPolicy with composition_rules, EVERY Kind defaults
50
+ * to scope_inheritance=enabled EXCEPT the per-scope ledger + structural Kinds
51
+ * below. Mirrors the kernel's `_NON_INHERITABLE_KINDS`. */
52
+ export const DEFAULT_NON_INHERITABLE_KINDS_V1 = new Set([
53
+ "Story", "Issue", "Feature", "Milestone", "Roadmap",
54
+ "Narrative", "VibeSession", "LessonLearned", "Plan",
55
+ "Genome", "KindDefinition", "LayerPolicy",
56
+ ]);
57
+ /** Display set ONLY (NOT the inheritance source of truth anymore): the
58
+ * composition-summary endpoint iterates this to surface per-Kind
59
+ * local/inherited counts in the Studio sidebar. Membership semantics live in
60
+ * the denylist above. */
61
+ export const DEFAULT_INHERITABLE_KINDS_V1 = new Set([
62
+ "Agent",
63
+ "LottieAsset",
64
+ "HtmlTemplate",
65
+ "Skill",
66
+ "ImagePrompt",
67
+ "Theme",
68
+ "PromptTemplate",
69
+ // s-automation-trio-extinction: JobType/HookType/ScheduleType foram extintos
70
+ // e unificados no Kind Automation (o inheritable herdável).
71
+ "Automation",
72
+ ]);
73
+ /** Hard limit on parent_scope chain depth — guards against runaway loops
74
+ * (cycle detection runs first, but this is a belt-and-suspenders cap). */
75
+ export const MAX_RESOLUTION_DEPTH = 16;
76
+ /** One step in the resolution chain — a single (scope, tenant) pair consulted
77
+ * by the resolver.
78
+ *
79
+ * - `found` records whether the source had ANY doc at this layer.
80
+ * - `contributed` flips true when this layer ACTUALLY influenced the final
81
+ * merged doc (override_full: only the winning layer; field_level: possibly
82
+ * several). */
83
+ export class ResolutionLayer {
84
+ scope;
85
+ tenant;
86
+ found;
87
+ contributed;
88
+ /** Version sha or content hash if the source exposes it (best-effort). */
89
+ versionSha;
90
+ constructor(opts) {
91
+ this.scope = opts.scope;
92
+ this.tenant = opts.tenant ?? null;
93
+ this.found = opts.found;
94
+ this.contributed = opts.contributed ?? false;
95
+ this.versionSha = opts.versionSha ?? null;
96
+ }
97
+ }
98
+ /** Ordered list of layers consulted, highest-priority-first.
99
+ *
100
+ * Layer order is:
101
+ * [local+tenant, local+base, parent+tenant, parent+base,
102
+ * grandparent+tenant, grandparent+base, ...]
103
+ * The HIGHEST priority layer is first — local-tenant beats everything else. */
104
+ export class ResolutionPath {
105
+ steps;
106
+ constructor(steps = []) {
107
+ this.steps = steps;
108
+ }
109
+ /** The single layer that became the doc's primary origin. For override_full
110
+ * this is the layer whose doc was returned wholesale; for field_level it is
111
+ * the highest-priority layer that contributed metadata/envelope (semantic
112
+ * primary owner). */
113
+ get effectiveLayer() {
114
+ for (const s of this.steps) {
115
+ if (s.found)
116
+ return s;
117
+ }
118
+ return null;
119
+ }
120
+ /** JSON-friendly serialization for HTTP responses (snake_case = Python). */
121
+ serialize() {
122
+ const eff = this.effectiveLayer;
123
+ return {
124
+ steps: this.steps.map((s) => ({
125
+ scope: s.scope,
126
+ tenant: s.tenant,
127
+ found: s.found,
128
+ contributed: s.contributed,
129
+ version_sha: s.versionSha,
130
+ })),
131
+ effective_layer: eff ? { scope: eff.scope, tenant: eff.tenant } : null,
132
+ };
133
+ }
134
+ }
135
+ /** Result of `kernel.resolveDocument` — the doc plus full provenance.
136
+ *
137
+ * Studio renders banner/badge directly from `provenance` + `isInherited` —
138
+ * no client-side detection logic needed. */
139
+ export class ResolvedDocument {
140
+ /** The merged document (or null if not found in any layer). */
141
+ doc;
142
+ /** Full ordered resolution path. Includes layers consulted but not
143
+ * contributing. */
144
+ provenance;
145
+ /** True when `effectiveLayer.scope != requestedScope`. Convenience derived
146
+ * from provenance; Studio uses this for badge/banner toggle. */
147
+ isInherited;
148
+ /** Field-path → scope name. Populated when merge_strategy=field_level. Lets
149
+ * the Detail page show `spec.persona ← _lib` annotations. */
150
+ contributionsByField;
151
+ constructor(opts) {
152
+ this.doc = opts.doc;
153
+ this.provenance = opts.provenance;
154
+ this.isInherited = opts.isInherited;
155
+ this.contributionsByField = opts.contributionsByField ?? {};
156
+ }
157
+ serialize() {
158
+ return {
159
+ doc: this.doc,
160
+ provenance: this.provenance.serialize(),
161
+ is_inherited: this.isInherited,
162
+ contributions_by_field: this.contributionsByField,
163
+ };
164
+ }
165
+ }
166
+ /** First non-null contribution wins entirely.
167
+ *
168
+ * Used for assetic Kinds (LottieAsset, ImagePrompt) where partial override
169
+ * makes no sense (binary payload is atomic).
170
+ *
171
+ * Input: contributions highest-priority-first.
172
+ * Output: [winningRawDoc, winningLayer] or [null, null] if all miss. */
173
+ export function mergeOverrideFull(contributions) {
174
+ for (const [layer, raw] of contributions) {
175
+ if (raw !== null && raw !== undefined)
176
+ return [raw, layer];
177
+ }
178
+ return [null, null];
179
+ }
180
+ /** Deep-merge `spec` dicts. Higher-priority layers shadow lower per-field.
181
+ * Returns [mergedDoc, primaryOriginLayer, fieldsByOrigin].
182
+ *
183
+ * Algorithm:
184
+ * - First pass: find the PRIMARY layer (highest-priority hit). Its metadata
185
+ * + envelope (apiVersion, kind) carry over wholesale.
186
+ * - Second pass: iterate contributions LOWEST → HIGHEST priority, overwriting
187
+ * spec keys in a fresh merged dict. After the loop the highest-priority
188
+ * layer's values win per-field.
189
+ * - Track which layer each FINAL spec field came from so the UI can render
190
+ * `spec.persona ← _lib` annotations.
191
+ *
192
+ * Edge cases:
193
+ * - All-null contributions → [null, null, {}].
194
+ * - Single hit → equivalent to override_full.
195
+ * - Spec missing or non-object → that layer skipped silently. */
196
+ export function mergeFieldLevel(contributions) {
197
+ // First pass — find primary (highest priority hit).
198
+ let primary = null;
199
+ let primaryRaw = null;
200
+ for (const [layer, raw] of contributions) {
201
+ if (raw !== null && raw !== undefined) {
202
+ primary = layer;
203
+ primaryRaw = raw;
204
+ break;
205
+ }
206
+ }
207
+ if (primary === null || primaryRaw === null)
208
+ return [null, null, {}];
209
+ // Second pass — merge specs LOWEST to HIGHEST so highest wins per-key.
210
+ const mergedSpec = {};
211
+ const fieldsByOrigin = {};
212
+ for (let i = contributions.length - 1; i >= 0; i--) {
213
+ const [layer, raw] = contributions[i];
214
+ if (raw === null || raw === undefined)
215
+ continue;
216
+ const spec = raw.spec;
217
+ if (spec === null || spec === undefined || typeof spec !== "object" || Array.isArray(spec)) {
218
+ continue;
219
+ }
220
+ for (const [k, v] of Object.entries(spec)) {
221
+ mergedSpec[k] = v;
222
+ fieldsByOrigin[`spec.${k}`] = layer.scope;
223
+ }
224
+ }
225
+ const final = {
226
+ apiVersion: primaryRaw.apiVersion ?? null,
227
+ kind: primaryRaw.kind ?? null,
228
+ metadata: primaryRaw.metadata ?? {},
229
+ spec: mergedSpec,
230
+ };
231
+ return [final, primary, fieldsByOrigin];
232
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Resource — self-aware document wrapper that knows its own dependencies.
3
+ *
4
+ * Replaces Document with added kindRef linkage: a Resource can resolve
5
+ * its own dep_filters via `deps()` without needing the full ManifestInstance.
6
+ */
7
+ export interface KindLike {
8
+ readonly apiVersion: string;
9
+ readonly kind: string;
10
+ readonly alias: string;
11
+ depFilters(): Record<string, string> | null;
12
+ }
13
+ export interface ResourceDep {
14
+ /** The spec field name (e.g. "skills", "soul"). */
15
+ field: string;
16
+ /** The target kind alias (e.g. "agentskills-skill"). */
17
+ targetAlias: string;
18
+ /** Concrete names referenced (e.g. ["greet", "search"]). */
19
+ names: string[];
20
+ }
21
+ export declare class Resource {
22
+ readonly apiVersion: string;
23
+ readonly kind: string;
24
+ readonly name: string;
25
+ readonly raw: Record<string, unknown>;
26
+ readonly typed: unknown;
27
+ readonly origin: string;
28
+ readonly kindRef: KindLike | null;
29
+ private readonly _metadataRaw;
30
+ private readonly _specRaw;
31
+ constructor(opts: {
32
+ apiVersion: string;
33
+ kind: string;
34
+ name: string;
35
+ metadata?: Record<string, unknown>;
36
+ spec?: Record<string, unknown>;
37
+ raw?: Record<string, unknown>;
38
+ typed?: unknown;
39
+ origin?: string;
40
+ kindRef?: KindLike | null;
41
+ });
42
+ /** Always returns Record<string, unknown> — typed metadata when available, raw dict otherwise. */
43
+ get metadata(): Record<string, unknown>;
44
+ /** Always returns Record<string, unknown> — typed spec when available, raw dict otherwise. */
45
+ get spec(): Record<string, unknown>;
46
+ /**
47
+ * Resolve this resource's outgoing dependency edges using kindRef.depFilters().
48
+ *
49
+ * Returns one entry per dep_filter field that has a non-empty value in spec.
50
+ * Scalar spec values (e.g. `soul: "brad"`) become single-element name lists.
51
+ * Returns empty array when kindRef is null or depFilters() returns null.
52
+ */
53
+ deps(): ResourceDep[];
54
+ /** Create a Resource from a raw dict. */
55
+ static fromRaw(raw: Record<string, unknown>, typed?: unknown, origin?: string, kindRef?: KindLike | null): Resource;
56
+ toString(): string;
57
+ }