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,357 @@
1
+ /**
2
+ * FilesystemSource — SourcePort backed by local .dna/ directories.
3
+ *
4
+ * 1:1 parity with Python dna.v3.adapters.filesystem.source.
5
+ */
6
+ import { access, readFile, readdir, stat } from "node:fs/promises";
7
+ import { resolve, join } from "node:path";
8
+ async function pathExists(p) {
9
+ try {
10
+ await access(p);
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ import yaml from "js-yaml";
18
+ import { countDocs, queryDocs } from "../../kernel/protocols.js";
19
+ import { FilesystemBundleHandle } from "../../kernel/bundle-handle.js";
20
+ export class FilesystemSource {
21
+ baseDir;
22
+ supportsReaders = true;
23
+ constructor(baseDir) {
24
+ this.baseDir = resolve(baseDir);
25
+ }
26
+ // v1.0 — true async via fs/promises. Concurrent reads don't block
27
+ // the event loop, which matters for repos with many bundles or any
28
+ // parallel scope-load workload.
29
+ async loadBootstrapDocs(scope, opts) {
30
+ // Phase 16 — filesystem walks ``loadAll`` and filters by Kind
31
+ // name. SQL adapters override with a fast WHERE filter.
32
+ //
33
+ // Tenant semantics: when ``opts.tenant`` is set, the
34
+ // tenant-published Genome shadows the platform Genome (Phase 9).
35
+ // KindDefinition + LayerPolicy are non-overlayable per Phase 16 —
36
+ // always read from platform.
37
+ //
38
+ // 1:1 parity with Python ``FilesystemSource.load_bootstrap_docs``.
39
+ const { BOOTSTRAP_KIND_NAMES } = await import("../../kernel/protocols.js");
40
+ const bootstrapSet = new Set(BOOTSTRAP_KIND_NAMES);
41
+ let allRaws;
42
+ try {
43
+ allRaws = await this.loadAll(scope);
44
+ }
45
+ catch {
46
+ // Platform scope dir absent — tenant-only scopes still resolve.
47
+ allRaws = [];
48
+ }
49
+ let out = allRaws.filter((d) => bootstrapSet.has(d.kind ?? ""));
50
+ const tenant = opts?.tenant;
51
+ if (tenant) {
52
+ const tenantPath = join(this.baseDir, "tenants", tenant, "scopes", scope, "Genome.yaml");
53
+ if (await pathExists(tenantPath)) {
54
+ const tenantPkg = yaml.load(await readFile(tenantPath, "utf-8"));
55
+ if (tenantPkg
56
+ && typeof tenantPkg === "object"
57
+ && tenantPkg.kind === "Genome") {
58
+ out = out.filter((d) => d.kind !== "Genome");
59
+ out.push(tenantPkg);
60
+ }
61
+ }
62
+ }
63
+ return out;
64
+ }
65
+ async loadAll(scope, readers) {
66
+ const scopeDir = join(this.baseDir, scope);
67
+ if (!(await pathExists(scopeDir)))
68
+ throw new Error(`Scope not found: ${scopeDir}`);
69
+ return await this._loadDir(scopeDir, readers ?? [], new Set(["layers", "tenants"]));
70
+ }
71
+ /**
72
+ * Two-planes F2 — record-plane query over `loadAll` + the shared pure
73
+ * helpers (mirror of the Py `SourcePort.query` protocol-default). FS
74
+ * is dev-mode with small scopes; native push-down is the purview of
75
+ * the SQL adapters.
76
+ *
77
+ * `opts.tenant` is a documented NO-OP here: the FS TS adapter has no
78
+ * tenant-aware overlay merge in `loadAll` (divergence from the Py FS
79
+ * source, which unions base + tenant layer with shadowing) — overlay
80
+ * support is an F2.5 candidate alongside the writable FS source.
81
+ *
82
+ * Bundle-format kinds are not visible to this query (no kernel reader
83
+ * back-ref on the TS source) — record-plane docs are plain YAML.
84
+ */
85
+ async *query(scope, kind, opts = {}) {
86
+ const docs = await this.loadAll(scope);
87
+ for (const row of queryDocs(docs, kind, opts))
88
+ yield row;
89
+ }
90
+ /**
91
+ * Two-planes F2 — record-plane count over `loadAll` + `countDocs`
92
+ * (mirror of the Py protocol-default). Same `opts.tenant` NO-OP
93
+ * caveat as `query`.
94
+ */
95
+ async count(scope, kind, opts = {}) {
96
+ const docs = await this.loadAll(scope);
97
+ return countDocs(docs, kind, opts);
98
+ }
99
+ /**
100
+ * Explicit contract declaration (s-sourceport-contract-cleanup) — kept
101
+ * honest by the adapter conformance test (declaration == structural
102
+ * derivation). Read-only FS source: in-memory query/count + the L1
103
+ * granular reads (loadOne + listDocRefs, s-dna-port-surface-parity);
104
+ * no bundle/write surface on the TS twin yet.
105
+ */
106
+ capabilities() {
107
+ return {
108
+ source: "filesystem",
109
+ drafts: false,
110
+ versions: false,
111
+ layers: true,
112
+ bundleRead: false,
113
+ bundleWrite: false,
114
+ kernelAttachable: false,
115
+ granularList: true,
116
+ granularOne: true,
117
+ queryPushdown: true,
118
+ };
119
+ }
120
+ /**
121
+ * Documented NO-OP — the FS source holds no pooled resources (each read
122
+ * opens/closes its own file handles via fs/promises). The member exists
123
+ * for SourcePort surface parity with Python, where `close` is a
124
+ * SOURCE_PORT_CORE_MEMBERS boot-gate entry.
125
+ */
126
+ async close() {
127
+ // nothing to release
128
+ }
129
+ /**
130
+ * L1 granular access — FS impl projects from `loadAll` (mirror of the
131
+ * Py `FilesystemSource.list_doc_refs`): `[kind, name]` refs only, no
132
+ * bundle rehydration. No perf gain over `loadAll` on FS, but keeps the
133
+ * SourcePort contract consistent across adapters (PG is where the gain
134
+ * lives). Tenant: union of base + overlay with the overlay shadowing
135
+ * base, same as the Py twin. Result sorted by (kind, name).
136
+ */
137
+ async listDocRefs(scope, opts) {
138
+ const refOf = (d) => {
139
+ const meta = d.metadata;
140
+ return [
141
+ typeof d.kind === "string" ? d.kind : "",
142
+ String(meta?.name ?? d.name ?? ""),
143
+ ];
144
+ };
145
+ let docs;
146
+ if (opts?.tenant) {
147
+ const overlay = await this.loadLayer(scope, "tenant", opts.tenant);
148
+ const overlayKeys = new Set(overlay.map((d) => refOf(d).join("\0")));
149
+ const base = (await this.loadAll(scope)).filter((d) => !overlayKeys.has(refOf(d).join("\0")));
150
+ docs = [...overlay, ...base];
151
+ }
152
+ else {
153
+ docs = await this.loadAll(scope);
154
+ }
155
+ const refs = [];
156
+ for (const d of docs) {
157
+ const [k, n] = refOf(d);
158
+ if (!k || !n)
159
+ continue;
160
+ if (opts?.kind && k !== opts.kind)
161
+ continue;
162
+ refs.push([k, n]);
163
+ }
164
+ refs.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0);
165
+ return refs;
166
+ }
167
+ async resolveRef(scope, ref) {
168
+ const path = join(this.baseDir, scope, ref);
169
+ if (!(await pathExists(path)))
170
+ return "";
171
+ return await readFile(path, "utf-8");
172
+ }
173
+ /**
174
+ * L1 granular access — FS impl projects from `loadAll` (mirror of the
175
+ * Py `FilesystemSource.load_one`). No perf gain over `loadAll` on FS
176
+ * (cheap in-process disk reads) but keeps the SourcePort contract
177
+ * consistent across adapters. Tenant overlay shadows base: when
178
+ * `opts.tenant` is set the tenant layer is consulted first and the
179
+ * BASE layer is the fallback (same as Python — a tenant read of a doc
180
+ * with no overlay still returns the base doc).
181
+ */
182
+ async loadOne(scope, kind, name, opts) {
183
+ const readers = opts?.readers ?? [];
184
+ const matches = (d) => {
185
+ if (d.kind !== kind)
186
+ return false;
187
+ const meta = d.metadata;
188
+ return meta?.name === name || d.name === name;
189
+ };
190
+ if (opts?.tenant) {
191
+ const overlay = await this.loadLayer(scope, "tenant", opts.tenant, readers);
192
+ for (const d of overlay)
193
+ if (matches(d))
194
+ return d;
195
+ }
196
+ const base = await this.loadAll(scope, readers);
197
+ for (const d of base)
198
+ if (matches(d))
199
+ return d;
200
+ return null;
201
+ }
202
+ async loadLayer(scope, layerId, layerValue, readers) {
203
+ // Phase 2b (parity with the Py FS source): tenant layers live at
204
+ // tenants/<X>/scopes/<S>/; other layers (branch, region, user) keep
205
+ // the legacy <scope>/layers/ path. Tenant reads check the new path
206
+ // first, falling back to the legacy layers/tenant/<X>/ for
207
+ // pre-migration data.
208
+ if (layerId === "tenant") {
209
+ const newDir = join(this.baseDir, "tenants", layerValue, "scopes", scope);
210
+ if (await pathExists(newDir)) {
211
+ return await this._loadDir(newDir, readers ?? [], new Set());
212
+ }
213
+ const legacyDir = join(this.baseDir, scope, "layers", "tenant", layerValue);
214
+ if (await pathExists(legacyDir)) {
215
+ return await this._loadDir(legacyDir, readers ?? [], new Set());
216
+ }
217
+ return [];
218
+ }
219
+ const layerDir = join(this.baseDir, scope, "layers", layerId, layerValue);
220
+ if (!(await pathExists(layerDir)))
221
+ return [];
222
+ return await this._loadDir(layerDir, readers ?? [], new Set());
223
+ }
224
+ async _loadDir(directory, readers, skip) {
225
+ const documents = [];
226
+ const readerMatched = new Set();
227
+ // 1. Readers first (bundles take priority over YAML)
228
+ if (readers.length > 0) {
229
+ // Readers on the scope root directory itself (Python parity — a
230
+ // standalone marker like AGENTS.md at the scope root is a document
231
+ // of the scope; agents.md/v1 uses exactly that layout).
232
+ const rootHandle = new FilesystemBundleHandle(directory);
233
+ for (const reader of readers) {
234
+ try {
235
+ if (await reader.detect(rootHandle)) {
236
+ const doc = await reader.read(rootHandle);
237
+ if (doc != null && typeof doc === "object" && "kind" in doc) {
238
+ documents.push(doc);
239
+ }
240
+ readerMatched.add(directory.split("/").filter(Boolean).pop() ?? "");
241
+ }
242
+ }
243
+ catch {
244
+ // Skip reader errors (Python logs a warning and continues)
245
+ }
246
+ }
247
+ await this._readRecursive(directory, readers, documents, skip, readerMatched);
248
+ }
249
+ // 2. YAML files, skipping stems already matched by readers
250
+ await this._collectYaml(directory, directory, skip, documents, readerMatched);
251
+ // 3. Deduplicate by kind/name — readers (first) take priority over YAML (later)
252
+ const seen = new Set();
253
+ const deduped = [];
254
+ for (const doc of documents) {
255
+ const meta = doc.metadata ?? {};
256
+ const key = `${doc.kind}/${meta.name ?? ""}`;
257
+ if (!seen.has(key)) {
258
+ seen.add(key);
259
+ deduped.push(doc);
260
+ }
261
+ }
262
+ return deduped;
263
+ }
264
+ async _collectYaml(root, directory, skip, documents, readerMatched) {
265
+ const entries = (await readdir(directory)).sort();
266
+ for (const entry of entries) {
267
+ const full = join(directory, entry);
268
+ const relParts = full.slice(root.length + 1).split("/");
269
+ if (relParts.some((p) => skip.has(p)))
270
+ continue;
271
+ const st = await stat(full);
272
+ if (st.isDirectory()) {
273
+ await this._collectYaml(root, full, skip, documents, readerMatched);
274
+ }
275
+ else if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
276
+ // Dedup of reader-loaded bundles vs same-named YAML siblings is
277
+ // handled by step 3 ((kind, name) key) in _loadDir. Stem-only
278
+ // skip here would drop YAMLs of a DIFFERENT kind that happen to
279
+ // share a name with a bundle (e.g. Skill/create-initiative +
280
+ // UseCase/create-initiative — both valid, different kinds).
281
+ try {
282
+ const content = yaml.load(await readFile(full, "utf-8"));
283
+ if (content != null &&
284
+ typeof content === "object" &&
285
+ "kind" in content) {
286
+ documents.push(content);
287
+ }
288
+ }
289
+ catch {
290
+ // Skip unparseable YAML
291
+ }
292
+ }
293
+ }
294
+ }
295
+ async _readRecursive(directory, readers, documents, skip, readerMatched) {
296
+ // H3 — container-aware reader routing.
297
+ //
298
+ // The scanner walks subdirs of `directory`. Each subdir IS a
299
+ // bundle (or a non-bundle subdir). The PARENT directory's name
300
+ // equals the bundle's *container* (e.g.
301
+ // `pageindex-documents/frank-hutter-cv/` → container =
302
+ // `pageindex-documents`).
303
+ //
304
+ // Pre-H3, we tried every registered reader in order and stopped
305
+ // at first match. That broke when two readers detected the same
306
+ // marker file (PageIndex + Graphify both used MANIFEST.md): the
307
+ // alphabetically-first extension's reader silently captured the
308
+ // other's bundles.
309
+ //
310
+ // H3 fix: prefer readers whose `_ownerContainer` member matches the
311
+ // parent dir name. Unscoped readers (undefined — a formal ReaderPort
312
+ // member since s-dna-rw-roundtrip-suite, no longer duck-typed) are
313
+ // tried only as fallback.
314
+ const containerName = directory.split("/").filter(Boolean).pop() ?? "";
315
+ const ownedReaders = readers.filter((r) => r._ownerContainer === containerName);
316
+ const globalReaders = readers.filter((r) => r._ownerContainer == null);
317
+ const orderedReaders = [...ownedReaders, ...globalReaders];
318
+ const entries = (await readdir(directory)).sort();
319
+ for (const entry of entries) {
320
+ const full = join(directory, entry);
321
+ if (!(await stat(full)).isDirectory() || skip.has(entry))
322
+ continue;
323
+ let matched = false;
324
+ const handle = new FilesystemBundleHandle(full);
325
+ for (const reader of orderedReaders) {
326
+ try {
327
+ if (await reader.detect(handle)) {
328
+ const doc = await reader.read(handle);
329
+ if (doc != null &&
330
+ typeof doc === "object" &&
331
+ "kind" in doc) {
332
+ documents.push(doc);
333
+ }
334
+ readerMatched.add(entry);
335
+ matched = true;
336
+ break;
337
+ }
338
+ }
339
+ catch {
340
+ // Skip reader errors
341
+ }
342
+ }
343
+ if (!matched) {
344
+ await this._readRecursive(full, readers, documents, skip, readerMatched);
345
+ }
346
+ }
347
+ }
348
+ }
349
+ // Two-planes F2 — RecordStorePort conformance BY COMPOSITION (compile-time;
350
+ // lives in src because tsconfig excludes tests/ from `tsc --noEmit`). The
351
+ // FS TS adapter provides the READ half (`query`|`count`) — it has no
352
+ // save/delete yet; full conformance arrives with the writable FS source
353
+ // (F2.5+). Function is never called — it exists for the typecheck.
354
+ function _fsRecordStoreReadHalfConformance(src) {
355
+ return src;
356
+ }
357
+ void _fsRecordStoreReadHalfConformance;
@@ -0,0 +1,2 @@
1
+ export { PostgresSource, type PostgresSourceOptions } from "./source.js";
2
+ export { MIGRATIONS } from "./migrations.js";
@@ -0,0 +1,2 @@
1
+ export { PostgresSource } from "./source.js";
2
+ export { MIGRATIONS } from "./migrations.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Postgres schema migrations for the SDK adapter.
3
+ *
4
+ * 1:1 parity (subset) with python/dna/adapters/postgres/source.py.
5
+ * v1.0 cut ships migrations 1-3:
6
+ *
7
+ * v1 — initial: dna_documents, dna_bundle_entries
8
+ * v2 — dna_layer_documents (layer overlays)
9
+ * v3 — tenant column on documents + bundle_entries (Phase 2c)
10
+ *
11
+ * Future migrations (versions table, lockfile, eventbus) land here as
12
+ * the TS Postgres adapter grows toward full Python parity.
13
+ */
14
+ export declare const MIGRATIONS: Record<number, string[]>;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Postgres schema migrations for the SDK adapter.
3
+ *
4
+ * 1:1 parity (subset) with python/dna/adapters/postgres/source.py.
5
+ * v1.0 cut ships migrations 1-3:
6
+ *
7
+ * v1 — initial: dna_documents, dna_bundle_entries
8
+ * v2 — dna_layer_documents (layer overlays)
9
+ * v3 — tenant column on documents + bundle_entries (Phase 2c)
10
+ *
11
+ * Future migrations (versions table, lockfile, eventbus) land here as
12
+ * the TS Postgres adapter grows toward full Python parity.
13
+ */
14
+ export const MIGRATIONS = {
15
+ 1: [
16
+ `CREATE TABLE IF NOT EXISTS {schema}.dna_documents (
17
+ scope TEXT NOT NULL,
18
+ kind TEXT NOT NULL,
19
+ name TEXT NOT NULL,
20
+ content JSONB NOT NULL,
21
+ version INTEGER NOT NULL DEFAULT 1,
22
+ updated_at TEXT NOT NULL,
23
+ PRIMARY KEY (scope, kind, name)
24
+ )`,
25
+ `CREATE TABLE IF NOT EXISTS {schema}.dna_bundle_entries (
26
+ scope TEXT NOT NULL,
27
+ kind TEXT NOT NULL,
28
+ name TEXT NOT NULL,
29
+ entry_path TEXT NOT NULL,
30
+ content TEXT NOT NULL,
31
+ updated_at TEXT NOT NULL,
32
+ PRIMARY KEY (scope, kind, name, entry_path)
33
+ )`,
34
+ `CREATE INDEX IF NOT EXISTS dna_bundle_entries_scope_kind_idx
35
+ ON {schema}.dna_bundle_entries (scope, kind)`,
36
+ ],
37
+ 2: [
38
+ `CREATE TABLE IF NOT EXISTS {schema}.dna_layer_documents (
39
+ scope TEXT NOT NULL,
40
+ layer_id TEXT NOT NULL,
41
+ layer_value TEXT NOT NULL,
42
+ kind TEXT NOT NULL,
43
+ name TEXT NOT NULL,
44
+ content JSONB NOT NULL,
45
+ updated_at TEXT NOT NULL,
46
+ PRIMARY KEY (scope, layer_id, layer_value, kind, name)
47
+ )`,
48
+ ],
49
+ 3: [
50
+ `ALTER TABLE {schema}.dna_documents ADD COLUMN IF NOT EXISTS tenant TEXT NOT NULL DEFAULT ''`,
51
+ `ALTER TABLE {schema}.dna_bundle_entries ADD COLUMN IF NOT EXISTS tenant TEXT NOT NULL DEFAULT ''`,
52
+ `ALTER TABLE {schema}.dna_documents DROP CONSTRAINT IF EXISTS dna_documents_pkey`,
53
+ `ALTER TABLE {schema}.dna_documents ADD PRIMARY KEY (scope, kind, name, tenant)`,
54
+ `ALTER TABLE {schema}.dna_bundle_entries DROP CONSTRAINT IF EXISTS dna_bundle_entries_pkey`,
55
+ `ALTER TABLE {schema}.dna_bundle_entries ADD PRIMARY KEY (scope, kind, name, entry_path, tenant)`,
56
+ ],
57
+ 4: [
58
+ // v1.0 — Module catalog versioning (Phase 10 parity).
59
+ //
60
+ // `dna_versions` stores immutable per-version snapshots. Module
61
+ // catalog publish flow (publishModuleVersion) inserts here; list/
62
+ // deprecation queries read from here. Same semver pinning rules
63
+ // as the Python adapter — `(scope, kind, name, tenant, semver)`
64
+ // is unique when semver is non-NULL (immutable releases).
65
+ `CREATE TABLE IF NOT EXISTS {schema}.dna_versions (
66
+ id BIGSERIAL PRIMARY KEY,
67
+ scope TEXT NOT NULL,
68
+ kind TEXT NOT NULL,
69
+ name TEXT NOT NULL,
70
+ content JSONB NOT NULL,
71
+ version INTEGER NOT NULL,
72
+ semver TEXT,
73
+ deprecated BOOLEAN NOT NULL DEFAULT false,
74
+ deprecation_message TEXT,
75
+ author TEXT,
76
+ created_at TEXT NOT NULL,
77
+ tenant TEXT NOT NULL DEFAULT ''
78
+ )`,
79
+ `CREATE UNIQUE INDEX IF NOT EXISTS dna_versions_semver_unique
80
+ ON {schema}.dna_versions (scope, kind, name, tenant, semver)
81
+ WHERE semver IS NOT NULL`,
82
+ `CREATE INDEX IF NOT EXISTS dna_versions_module_lookup
83
+ ON {schema}.dna_versions (kind, scope, tenant, semver)
84
+ WHERE kind = 'Module' AND semver IS NOT NULL`,
85
+ `CREATE INDEX IF NOT EXISTS dna_versions_scope_kind_name_idx
86
+ ON {schema}.dna_versions (scope, kind, name, tenant)`,
87
+ ],
88
+ };
@@ -0,0 +1,172 @@
1
+ /**
2
+ * PostgresSource — TypeScript adapter for the SDK's WritableSourcePort.
3
+ *
4
+ * 1:1 parity (MVP) with python/dna/adapters/postgres/source.py.
5
+ * Implements:
6
+ * - WritableSourcePort: loadBootstrapDocs, loadAll, resolveRef, loadLayer,
7
+ * saveDocument, deleteDocument, publish (no-op for MVP), close
8
+ * - BundleEntryReadable: fetchBundleEntry — port-aware byte fetch
9
+ * - KernelAttachable: attachKernel — uniform writer/reader wiring
10
+ * - Versionable: getVersion — basic single-version support
11
+ *
12
+ * Out of scope for v1.0 (lands in v1.1 follow-up):
13
+ * - Module versioning catalog (Phase 10 lockfile flow)
14
+ * - Tenant overlay routing (multi-tenant scopes endpoint)
15
+ * - LISTEN/NOTIFY event bus (Phase 15.1)
16
+ * - Draft → published two-step write flow
17
+ *
18
+ * The schema mirrors Python's `dna_documents` / `dna_bundle_entries` /
19
+ * `dna_layer_documents` — already-existing Python deployments are
20
+ * read-compatible with this adapter.
21
+ */
22
+ import { Pool, type PoolConfig } from "pg";
23
+ import type { Kernel } from "../../kernel/index.js";
24
+ import type { ReaderPort, WritableSourcePort, WriterPort } from "../../kernel/protocols.js";
25
+ import type { SourceCapabilities } from "../../kernel/capabilities.js";
26
+ export interface PostgresSourceOptions {
27
+ /** Postgres connection string (postgresql://user:pass@host/db). */
28
+ connectionString?: string;
29
+ /** Schema to create tables in. Defaults to "public". */
30
+ schema?: string;
31
+ /**
32
+ * Optional pre-built Pool. When provided the adapter uses it
33
+ * verbatim and does NOT close it on `close()`. Useful for sharing
34
+ * pools across adapters or test fixtures.
35
+ */
36
+ pool?: Pool;
37
+ /** Initial writers (filled by attachKernel if empty). */
38
+ writers?: WriterPort[];
39
+ /** Initial readers (filled by attachKernel if empty). */
40
+ readers?: ReaderPort[];
41
+ /** Extra Pool options (max, idleTimeoutMillis, ...). */
42
+ poolOptions?: PoolConfig;
43
+ }
44
+ export declare class PostgresSource implements WritableSourcePort {
45
+ readonly supportsReaders = false;
46
+ private _pool;
47
+ private readonly _ownsPool;
48
+ private readonly _schema;
49
+ private _migrated;
50
+ _writers: WriterPort[];
51
+ _readers: ReaderPort[];
52
+ private _kernel;
53
+ constructor(opts: PostgresSourceOptions);
54
+ init(): Promise<void>;
55
+ /**
56
+ * Explicit contract declaration (s-sourceport-contract-cleanup) — kept
57
+ * honest by the adapter conformance test. The TS PG adapter has the
58
+ * write half + versions/bundle-read + the granular ref list
59
+ * (listDocRefs, s-dna-port-surface-parity); query/count push-down and
60
+ * loadOne are Py-only this phase (F2.5+ candidates).
61
+ */
62
+ capabilities(): SourceCapabilities;
63
+ close(): Promise<void>;
64
+ /**
65
+ * KernelAttachable interface impl. Idempotent: copies the kernel's
66
+ * registered writers + readers into this source so bundle writes
67
+ * (which depend on `_writers` producing serialised entries via
68
+ * `WriterPort.serialize`) work even when this source was
69
+ * instantiated without explicit injection.
70
+ */
71
+ attachKernel(kernel: Kernel): void;
72
+ private _runMigrations;
73
+ loadBootstrapDocs(scope: string, opts?: {
74
+ tenant?: string;
75
+ }): Promise<Record<string, unknown>[]>;
76
+ loadAll(scope: string, readers?: ReaderPort[]): Promise<Record<string, unknown>[]>;
77
+ resolveRef(_scope: string, ref: string): Promise<string>;
78
+ /**
79
+ * L1 granular access (s-dna-port-surface-parity — mirror of the Py
80
+ * `PostgresSource.list_doc_refs`): one indexed SELECT of `[kind, name]`
81
+ * refs, metadata only — no bundle entries, no reader rehydration. This
82
+ * is where the granular read actually pays off vs `loadAll` (which is
83
+ * an N+1 over bundle entries). Tenant: union of the base layer
84
+ * (`tenant=''`) with the overlay; refs are DISTINCT so an overlay
85
+ * shadow contributes a single entry.
86
+ */
87
+ listDocRefs(scope: string, opts?: {
88
+ kind?: string | null;
89
+ tenant?: string | null;
90
+ }): Promise<Array<[string, string]>>;
91
+ loadLayer(scope: string, layerId: string, layerValue: string, _readers?: ReaderPort[]): Promise<Record<string, unknown>[]>;
92
+ private _loadBundleEntries;
93
+ /**
94
+ * Fetch a single bundle entry by name. Mirrors Python.
95
+ *
96
+ * Disambiguation: when the kernel supplies ``kind`` (e.g.
97
+ * ``"GraphifyArtifact"``), the WHERE clause includes ``kind=$N``
98
+ * so a Skill ``foo`` and a GraphifyArtifact ``foo`` in the same
99
+ * scope don't collide. Without ``kind`` we fall back to
100
+ * ``(scope, name, entry_path)`` and accept the rare collision
101
+ * risk — older callers that built the kernel before the
102
+ * protocol gained the kwarg fall through this path.
103
+ */
104
+ fetchBundleEntry(scope: string, container: string, name: string, entry: string, options?: {
105
+ tenant?: string | null;
106
+ kind?: string | null;
107
+ }): Promise<Uint8Array>;
108
+ saveDocument(scope: string, kind: string, name: string, raw: Record<string, unknown>, options?: {
109
+ author?: string;
110
+ tenant?: string | null;
111
+ layer?: [string, string];
112
+ versionRetention?: number | null;
113
+ }): Promise<string>;
114
+ deleteDocument(scope: string, kind: string, name: string, options?: {
115
+ author?: string;
116
+ tenant?: string | null;
117
+ layer?: [string, string];
118
+ }): Promise<void>;
119
+ /**
120
+ * MVP no-op: PostgresSource saves directly to dna_documents
121
+ * (no draft state). Future v1.1 may introduce drafts → publish
122
+ * promotion; the method exists today so callers expecting it
123
+ * (e.g. cross-language migrations from Python) don't break.
124
+ */
125
+ publish(_scope: string, _kind: string, _name: string): Promise<string>;
126
+ getVersion(scope: string, kind: string, name: string, versionId: string): Promise<Record<string, unknown>>;
127
+ /**
128
+ * Publish an immutable Module version with semver. Mirrors the Python
129
+ * Phase 10 `publishModuleVersion` flow:
130
+ *
131
+ * 1. Reject re-publish of an existing (scope, name, semver, tenant).
132
+ * 2. Insert the row into dna_versions with semver set.
133
+ * 3. Mirror the row to dna_documents (latest-stable view) so
134
+ * `loadAll` continues returning the freshest content.
135
+ *
136
+ * Throws `VersionAlreadyPublished` when the (scope, kind, name,
137
+ * tenant, semver) tuple already exists. This is the immutability
138
+ * guarantee — once published, that version is frozen.
139
+ */
140
+ publishModuleVersion(scope: string, name: string, raw: Record<string, unknown>, semver: string, options?: {
141
+ tenant?: string | null;
142
+ author?: string;
143
+ }): Promise<{
144
+ id: number;
145
+ semver: string;
146
+ }>;
147
+ /**
148
+ * List published versions of a Module. Returns newest-first by
149
+ * semver lexicographic order (good enough for typical X.Y.Z;
150
+ * v1.1 may add proper semver comparison).
151
+ */
152
+ listModuleVersions(scope: string, name: string, options?: {
153
+ tenant?: string | null;
154
+ includeDeprecated?: boolean;
155
+ }): Promise<Array<{
156
+ id: number;
157
+ semver: string;
158
+ version: number;
159
+ deprecated: boolean;
160
+ deprecationMessage: string | null;
161
+ author: string | null;
162
+ createdAt: string;
163
+ }>>;
164
+ /**
165
+ * Mark a published Module version as deprecated. The row stays
166
+ * (immutability), but listModuleVersions can filter it out via
167
+ * `includeDeprecated: false`.
168
+ */
169
+ deprecateModuleVersion(scope: string, name: string, semver: string, message?: string, options?: {
170
+ tenant?: string | null;
171
+ }): Promise<void>;
172
+ }