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,569 @@
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 } from "pg";
23
+ import { DictBundleHandle } from "../../kernel/bundle-handle.js";
24
+ import { MIGRATIONS } from "./migrations.js";
25
+ function nowIso() {
26
+ return new Date().toISOString();
27
+ }
28
+ // Two-planes F2: query/count push-down is Py-only — the TS PG adapter
29
+ // doesn't even have `query` yet (the optional SourcePort methods stay
30
+ // unimplemented; the Kernel raises a clear capability error). Candidate
31
+ // for F2.5+ alongside the Py `_build_pg_where`/`_build_pg_order` port.
32
+ export class PostgresSource {
33
+ supportsReaders = false;
34
+ _pool;
35
+ _ownsPool;
36
+ _schema;
37
+ _migrated = false;
38
+ _writers;
39
+ _readers;
40
+ _kernel = null;
41
+ constructor(opts) {
42
+ this._schema = opts.schema ?? "public";
43
+ if (opts.pool) {
44
+ this._pool = opts.pool;
45
+ this._ownsPool = false;
46
+ }
47
+ else {
48
+ if (!opts.connectionString) {
49
+ throw new Error("PostgresSource: provide either `connectionString` or `pool` option.");
50
+ }
51
+ this._pool = new Pool({ connectionString: opts.connectionString, ...opts.poolOptions });
52
+ this._ownsPool = true;
53
+ }
54
+ this._writers = opts.writers ?? [];
55
+ this._readers = opts.readers ?? [];
56
+ }
57
+ // ---------------------------------------------------------------------
58
+ // Lifecycle
59
+ // ---------------------------------------------------------------------
60
+ async init() {
61
+ await this._runMigrations();
62
+ }
63
+ /**
64
+ * Explicit contract declaration (s-sourceport-contract-cleanup) — kept
65
+ * honest by the adapter conformance test. The TS PG adapter has the
66
+ * write half + versions/bundle-read + the granular ref list
67
+ * (listDocRefs, s-dna-port-surface-parity); query/count push-down and
68
+ * loadOne are Py-only this phase (F2.5+ candidates).
69
+ */
70
+ capabilities() {
71
+ return {
72
+ source: "postgres",
73
+ drafts: false,
74
+ versions: true,
75
+ layers: true,
76
+ bundleRead: true,
77
+ bundleWrite: false,
78
+ kernelAttachable: true,
79
+ granularList: true,
80
+ granularOne: false,
81
+ queryPushdown: false,
82
+ };
83
+ }
84
+ async close() {
85
+ if (this._ownsPool)
86
+ await this._pool.end();
87
+ }
88
+ /**
89
+ * KernelAttachable interface impl. Idempotent: copies the kernel's
90
+ * registered writers + readers into this source so bundle writes
91
+ * (which depend on `_writers` producing serialised entries via
92
+ * `WriterPort.serialize`) work even when this source was
93
+ * instantiated without explicit injection.
94
+ */
95
+ attachKernel(kernel) {
96
+ this._kernel = kernel;
97
+ if (this._writers.length === 0) {
98
+ this._writers = [...(kernel._writers ?? [])];
99
+ }
100
+ if (this._readers.length === 0) {
101
+ this._readers = [...(kernel._readers ?? [])];
102
+ }
103
+ }
104
+ // ---------------------------------------------------------------------
105
+ // Migrations
106
+ // ---------------------------------------------------------------------
107
+ async _runMigrations() {
108
+ if (this._migrated)
109
+ return;
110
+ const client = await this._pool.connect();
111
+ try {
112
+ await client.query(`CREATE SCHEMA IF NOT EXISTS "${this._schema}"`);
113
+ await client.query(`
114
+ CREATE TABLE IF NOT EXISTS "${this._schema}".dna_schema_migrations (
115
+ version INTEGER PRIMARY KEY,
116
+ applied_at TEXT NOT NULL
117
+ )
118
+ `);
119
+ const { rows } = await client.query(`SELECT version FROM "${this._schema}".dna_schema_migrations`);
120
+ const applied = new Set(rows.map((r) => r.version));
121
+ const versions = Object.keys(MIGRATIONS)
122
+ .map((v) => Number(v))
123
+ .sort((a, b) => a - b);
124
+ for (const version of versions) {
125
+ if (applied.has(version))
126
+ continue;
127
+ const stmts = MIGRATIONS[version];
128
+ await client.query("BEGIN");
129
+ try {
130
+ for (const stmt of stmts) {
131
+ const sql = stmt.replace(/\{schema\}/g, `"${this._schema}"`);
132
+ await client.query(sql);
133
+ }
134
+ await client.query(`INSERT INTO "${this._schema}".dna_schema_migrations (version, applied_at) VALUES ($1, $2)`, [version, nowIso()]);
135
+ await client.query("COMMIT");
136
+ }
137
+ catch (e) {
138
+ await client.query("ROLLBACK");
139
+ throw e;
140
+ }
141
+ }
142
+ }
143
+ finally {
144
+ client.release();
145
+ }
146
+ this._migrated = true;
147
+ }
148
+ // ---------------------------------------------------------------------
149
+ // SourcePort (read)
150
+ // ---------------------------------------------------------------------
151
+ async loadBootstrapDocs(scope, opts) {
152
+ // Phase 16 — fast WHERE filter over Kind name set. 1:1 parity
153
+ // with Python ``PostgresSource.load_bootstrap_docs``.
154
+ //
155
+ // Tenant semantics: when ``opts.tenant`` is set, the
156
+ // tenant-published Genome shadows the platform Genome (Phase 9).
157
+ // KindDefinition + LayerPolicy are non-overlayable per Phase 16 —
158
+ // always read from platform (tenant='').
159
+ await this._runMigrations();
160
+ const { BOOTSTRAP_KIND_NAMES } = await import("../../kernel/protocols.js");
161
+ const platform = await this._pool.query(`SELECT content FROM "${this._schema}".dna_documents
162
+ WHERE scope=$1 AND kind = ANY($2::text[]) AND tenant=''`, [scope, [...BOOTSTRAP_KIND_NAMES]]);
163
+ let out = platform.rows.map((r) => parseJsonish(r.content));
164
+ const tenant = opts?.tenant;
165
+ if (tenant) {
166
+ const tpkg = await this._pool.query(`SELECT content FROM "${this._schema}".dna_documents
167
+ WHERE scope=$1 AND kind='Genome' AND tenant=$2 LIMIT 1`, [scope, tenant]);
168
+ if (tpkg.rows.length > 0) {
169
+ const tenantPkg = parseJsonish(tpkg.rows[0].content);
170
+ out = out.filter((d) => d.kind !== "Genome");
171
+ out.push(tenantPkg);
172
+ }
173
+ }
174
+ return out;
175
+ }
176
+ async loadAll(scope, readers) {
177
+ await this._runMigrations();
178
+ const effectiveReaders = [...this._readers];
179
+ for (const r of readers ?? []) {
180
+ if (!effectiveReaders.includes(r))
181
+ effectiveReaders.push(r);
182
+ }
183
+ const { rows } = await this._pool.query(`SELECT kind, name, content FROM "${this._schema}".dna_documents
184
+ WHERE scope=$1 AND tenant=''`, [scope]);
185
+ const out = [];
186
+ for (const row of rows) {
187
+ const entries = await this._loadBundleEntries(scope, row.kind, row.name, "");
188
+ if (Object.keys(entries).length > 0 && effectiveReaders.length > 0) {
189
+ // Rehydrate via DictBundleHandle so Reader pipeline runs on
190
+ // SQL-stored bundles identically to filesystem-stored ones.
191
+ const handle = new DictBundleHandle(row.name, entries);
192
+ let matched = false;
193
+ for (const reader of effectiveReaders) {
194
+ try {
195
+ if (await reader.detect(handle)) {
196
+ out.push(await reader.read(handle));
197
+ matched = true;
198
+ break;
199
+ }
200
+ }
201
+ catch {
202
+ continue;
203
+ }
204
+ }
205
+ if (matched)
206
+ continue;
207
+ }
208
+ out.push(parseJsonish(row.content));
209
+ }
210
+ return out;
211
+ }
212
+ async resolveRef(_scope, ref) {
213
+ return ref;
214
+ }
215
+ /**
216
+ * L1 granular access (s-dna-port-surface-parity — mirror of the Py
217
+ * `PostgresSource.list_doc_refs`): one indexed SELECT of `[kind, name]`
218
+ * refs, metadata only — no bundle entries, no reader rehydration. This
219
+ * is where the granular read actually pays off vs `loadAll` (which is
220
+ * an N+1 over bundle entries). Tenant: union of the base layer
221
+ * (`tenant=''`) with the overlay; refs are DISTINCT so an overlay
222
+ * shadow contributes a single entry.
223
+ */
224
+ async listDocRefs(scope, opts) {
225
+ await this._runMigrations();
226
+ const params = [scope];
227
+ let tenantClause = "tenant=''";
228
+ if (opts?.tenant) {
229
+ params.push(opts.tenant);
230
+ tenantClause = `tenant IN ('', $${params.length})`;
231
+ }
232
+ let kindClause = "";
233
+ if (opts?.kind) {
234
+ params.push(opts.kind);
235
+ kindClause = ` AND kind=$${params.length}`;
236
+ }
237
+ const { rows } = await this._pool.query(`SELECT DISTINCT kind, name FROM "${this._schema}".dna_documents
238
+ WHERE scope=$1 AND ${tenantClause}${kindClause}
239
+ ORDER BY kind, name`, params);
240
+ return rows.map((r) => [r.kind, r.name]);
241
+ }
242
+ async loadLayer(scope, layerId, layerValue, _readers) {
243
+ await this._runMigrations();
244
+ const { rows } = await this._pool.query(`SELECT content FROM "${this._schema}".dna_layer_documents
245
+ WHERE scope=$1 AND layer_id=$2 AND layer_value=$3`, [scope, layerId, layerValue]);
246
+ return rows.map((r) => parseJsonish(r.content));
247
+ }
248
+ // ---------------------------------------------------------------------
249
+ // BundleEntryReadable capability
250
+ // ---------------------------------------------------------------------
251
+ async _loadBundleEntries(scope, kind, name, tenant) {
252
+ const { rows } = await this._pool.query(`SELECT entry_path, content FROM "${this._schema}".dna_bundle_entries
253
+ WHERE scope=$1 AND kind=$2 AND name=$3 AND tenant=$4`, [scope, kind, name, tenant]);
254
+ const out = {};
255
+ for (const r of rows)
256
+ out[r.entry_path] = r.content;
257
+ return out;
258
+ }
259
+ /**
260
+ * Fetch a single bundle entry by name. Mirrors Python.
261
+ *
262
+ * Disambiguation: when the kernel supplies ``kind`` (e.g.
263
+ * ``"GraphifyArtifact"``), the WHERE clause includes ``kind=$N``
264
+ * so a Skill ``foo`` and a GraphifyArtifact ``foo`` in the same
265
+ * scope don't collide. Without ``kind`` we fall back to
266
+ * ``(scope, name, entry_path)`` and accept the rare collision
267
+ * risk — older callers that built the kernel before the
268
+ * protocol gained the kwarg fall through this path.
269
+ */
270
+ async fetchBundleEntry(scope, container, name, entry, options) {
271
+ await this._runMigrations();
272
+ const tenant = options?.tenant ?? null;
273
+ const kind = options?.kind ?? null;
274
+ const candidates = tenant ? [tenant, ""] : [""];
275
+ for (const tval of candidates) {
276
+ const { rows } = kind
277
+ ? await this._pool.query(`SELECT content FROM "${this._schema}".dna_bundle_entries
278
+ WHERE scope=$1 AND kind=$2 AND name=$3
279
+ AND entry_path=$4 AND tenant=$5 LIMIT 1`, [scope, kind, name, entry, tval])
280
+ : await this._pool.query(`SELECT content FROM "${this._schema}".dna_bundle_entries
281
+ WHERE scope=$1 AND name=$2 AND entry_path=$3
282
+ AND tenant=$4 LIMIT 1`, [scope, name, entry, tval]);
283
+ if (rows.length > 0) {
284
+ return new TextEncoder().encode(rows[0].content);
285
+ }
286
+ }
287
+ const err = new Error(`Bundle entry not found: scope=${JSON.stringify(scope)} ` +
288
+ `container=${JSON.stringify(container)} ` +
289
+ `kind=${JSON.stringify(kind)} ` +
290
+ `name=${JSON.stringify(name)} entry=${JSON.stringify(entry)} ` +
291
+ `tenant=${JSON.stringify(tenant)}`);
292
+ err.code = "ENOENT";
293
+ throw err;
294
+ }
295
+ // ---------------------------------------------------------------------
296
+ // WritableSourcePort (write)
297
+ // ---------------------------------------------------------------------
298
+ async saveDocument(scope, kind, name, raw,
299
+ // versionRetention (s-version-prune-record-plane-churn / i-182): mirrors the
300
+ // Py adapter param. No-op TODAY because this MVP adapter doesn't keep a
301
+ // general version history (see the MVP return below) — so there's no
302
+ // record-plane churn to cap. When the versions table lands (the v1.1 TODO),
303
+ // apply it here: keep only the last N snapshots for churn Kinds.
304
+ options) {
305
+ await this._runMigrations();
306
+ let tenant = options?.tenant ?? "";
307
+ if (tenant === null)
308
+ tenant = "";
309
+ if (options?.layer && options.layer[0] === "tenant" && tenant === "") {
310
+ tenant = options.layer[1];
311
+ }
312
+ else if (options?.layer && options.layer[0] !== "tenant") {
313
+ throw new Error(`PostgresSource MVP does not yet support non-tenant layers ` +
314
+ `(got layer=${JSON.stringify(options.layer)}). v1.1 follow-up.`);
315
+ }
316
+ // 1. Try registered writers — bundle path
317
+ let bundleEntries = null;
318
+ // L3 (s-writer-binary-entries 2026-05-25): binary entries
319
+ // accumulate alongside text. Adapter writes content_binary when
320
+ // contentBytes is set, content when content is a string.
321
+ let bundleBinaryEntries = null;
322
+ for (const w of this._writers) {
323
+ if (w.canWrite(raw)) {
324
+ if (typeof w.serialize === "function") {
325
+ bundleEntries = {};
326
+ bundleBinaryEntries = {};
327
+ for (const f of w.serialize(raw)) {
328
+ if (f.contentBytes !== undefined) {
329
+ bundleBinaryEntries[f.relativePath] = f.contentBytes;
330
+ }
331
+ else {
332
+ bundleEntries[f.relativePath] = f.content ?? "";
333
+ }
334
+ }
335
+ }
336
+ else {
337
+ // Writer doesn't implement serialize() — use a virtual
338
+ // DictBundleHandle to capture the entries written.
339
+ const handle = new DictBundleHandle(name, {});
340
+ await w.write(handle, raw);
341
+ bundleEntries = {};
342
+ for (const ep of await handle.iterEntries(true)) {
343
+ bundleEntries[ep] = await handle.readText(ep);
344
+ }
345
+ }
346
+ break;
347
+ }
348
+ }
349
+ const client = await this._pool.connect();
350
+ try {
351
+ await client.query("BEGIN");
352
+ // 2. UPSERT into dna_documents
353
+ await client.query(`INSERT INTO "${this._schema}".dna_documents
354
+ (scope, kind, name, content, version, updated_at, tenant)
355
+ VALUES ($1, $2, $3, $4::jsonb, 1, $5, $6)
356
+ ON CONFLICT (scope, kind, name, tenant) DO UPDATE SET
357
+ content = EXCLUDED.content,
358
+ version = "${this._schema}".dna_documents.version + 1,
359
+ updated_at = EXCLUDED.updated_at`, [scope, kind, name, JSON.stringify(raw), nowIso(), tenant]);
360
+ // 3. Replace bundle entries (full-replace semantics)
361
+ if (bundleEntries !== null) {
362
+ await client.query(`DELETE FROM "${this._schema}".dna_bundle_entries
363
+ WHERE scope=$1 AND kind=$2 AND name=$3 AND tenant=$4`, [scope, kind, name, tenant]);
364
+ const ts = nowIso();
365
+ for (const [entryPath, body] of Object.entries(bundleEntries)) {
366
+ await client.query(`INSERT INTO "${this._schema}".dna_bundle_entries
367
+ (scope, kind, name, entry_path, content, updated_at, tenant)
368
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [scope, kind, name, entryPath, body, ts, tenant]);
369
+ }
370
+ }
371
+ await client.query("COMMIT");
372
+ }
373
+ catch (e) {
374
+ await client.query("ROLLBACK");
375
+ throw e;
376
+ }
377
+ finally {
378
+ client.release();
379
+ }
380
+ // MVP: no general version history yet (v1.1 adds the versions table). When
381
+ // it lands, insert the snapshot here AND honor options.versionRetention —
382
+ // cap record-plane churn Kinds to the last N (mirror the Py guard +
383
+ // VERSION_CHURN_KINDS; i-182 / s-version-prune-record-plane-churn).
384
+ return "1";
385
+ }
386
+ async deleteDocument(scope, kind, name, options) {
387
+ await this._runMigrations();
388
+ let tenant = options?.tenant ?? "";
389
+ if (tenant === null)
390
+ tenant = "";
391
+ if (options?.layer && options.layer[0] === "tenant" && tenant === "") {
392
+ tenant = options.layer[1];
393
+ }
394
+ const client = await this._pool.connect();
395
+ try {
396
+ await client.query("BEGIN");
397
+ await client.query(`DELETE FROM "${this._schema}".dna_bundle_entries
398
+ WHERE scope=$1 AND kind=$2 AND name=$3 AND tenant=$4`, [scope, kind, name, tenant]);
399
+ await client.query(`DELETE FROM "${this._schema}".dna_documents
400
+ WHERE scope=$1 AND kind=$2 AND name=$3 AND tenant=$4`, [scope, kind, name, tenant]);
401
+ await client.query("COMMIT");
402
+ }
403
+ catch (e) {
404
+ await client.query("ROLLBACK");
405
+ throw e;
406
+ }
407
+ finally {
408
+ client.release();
409
+ }
410
+ }
411
+ /**
412
+ * MVP no-op: PostgresSource saves directly to dna_documents
413
+ * (no draft state). Future v1.1 may introduce drafts → publish
414
+ * promotion; the method exists today so callers expecting it
415
+ * (e.g. cross-language migrations from Python) don't break.
416
+ */
417
+ async publish(_scope, _kind, _name) {
418
+ return "1";
419
+ }
420
+ // ---------------------------------------------------------------------
421
+ // Versionable capability (basic)
422
+ // ---------------------------------------------------------------------
423
+ async getVersion(scope, kind, name, versionId) {
424
+ await this._runMigrations();
425
+ // v1.0 — versionId can be either the BIGSERIAL id (numeric) or a
426
+ // semver string. Numeric → exact version row; otherwise → latest
427
+ // matching the semver. Falls back to current dna_documents when
428
+ // no dna_versions row matches (back-compat with pre-Phase-10
429
+ // unversioned writes).
430
+ const isNumeric = /^\d+$/.test(versionId);
431
+ if (isNumeric) {
432
+ const { rows } = await this._pool.query(`SELECT content FROM "${this._schema}".dna_versions
433
+ WHERE id=$1 AND scope=$2 AND kind=$3 AND name=$4 LIMIT 1`, [Number(versionId), scope, kind, name]);
434
+ if (rows.length > 0)
435
+ return parseJsonish(rows[0].content);
436
+ }
437
+ else {
438
+ const { rows } = await this._pool.query(`SELECT content FROM "${this._schema}".dna_versions
439
+ WHERE scope=$1 AND kind=$2 AND name=$3 AND semver=$4 AND tenant=''
440
+ LIMIT 1`, [scope, kind, name, versionId]);
441
+ if (rows.length > 0)
442
+ return parseJsonish(rows[0].content);
443
+ }
444
+ // Back-compat: fall through to latest dna_documents row
445
+ const { rows: cur } = await this._pool.query(`SELECT content FROM "${this._schema}".dna_documents
446
+ WHERE scope=$1 AND kind=$2 AND name=$3 AND tenant='' LIMIT 1`, [scope, kind, name]);
447
+ if (cur.length === 0) {
448
+ throw new Error(`Version not found: ${scope}/${kind}/${name}@${versionId}`);
449
+ }
450
+ return parseJsonish(cur[0].content);
451
+ }
452
+ // ---------------------------------------------------------------------
453
+ // Module catalog versioning (Phase 10 parity)
454
+ // ---------------------------------------------------------------------
455
+ /**
456
+ * Publish an immutable Module version with semver. Mirrors the Python
457
+ * Phase 10 `publishModuleVersion` flow:
458
+ *
459
+ * 1. Reject re-publish of an existing (scope, name, semver, tenant).
460
+ * 2. Insert the row into dna_versions with semver set.
461
+ * 3. Mirror the row to dna_documents (latest-stable view) so
462
+ * `loadAll` continues returning the freshest content.
463
+ *
464
+ * Throws `VersionAlreadyPublished` when the (scope, kind, name,
465
+ * tenant, semver) tuple already exists. This is the immutability
466
+ * guarantee — once published, that version is frozen.
467
+ */
468
+ async publishModuleVersion(scope, name, raw, semver, options) {
469
+ await this._runMigrations();
470
+ const tenant = (options?.tenant ?? "") || "";
471
+ const author = options?.author ?? null;
472
+ const client = await this._pool.connect();
473
+ try {
474
+ await client.query("BEGIN");
475
+ // 1. Immutability check
476
+ const { rows: existing } = await client.query(`SELECT id FROM "${this._schema}".dna_versions
477
+ WHERE scope=$1 AND kind='Module' AND name=$2 AND tenant=$3 AND semver=$4
478
+ LIMIT 1`, [scope, name, tenant, semver]);
479
+ if (existing.length > 0) {
480
+ const err = new Error(`Module ${name}@${semver} already published to scope ${scope} ` +
481
+ `(tenant=${tenant || "''"}). Bump and republish.`);
482
+ err.code = "VERSION_ALREADY_PUBLISHED";
483
+ throw err;
484
+ }
485
+ // 2. Determine next monotonic version number
486
+ const { rows: maxRow } = await client.query(`SELECT MAX(version) AS max FROM "${this._schema}".dna_versions
487
+ WHERE scope=$1 AND kind='Module' AND name=$2 AND tenant=$3`, [scope, name, tenant]);
488
+ const nextVersion = (maxRow[0]?.max ?? 0) + 1;
489
+ // 3. Insert immutable version row
490
+ const { rows: ins } = await client.query(`INSERT INTO "${this._schema}".dna_versions
491
+ (scope, kind, name, content, version, semver, author, created_at, tenant)
492
+ VALUES ($1, 'Module', $2, $3::jsonb, $4, $5, $6, $7, $8)
493
+ RETURNING id`, [scope, name, JSON.stringify(raw), nextVersion, semver, author, nowIso(), tenant]);
494
+ // 4. Mirror to dna_documents as latest-stable
495
+ await client.query(`INSERT INTO "${this._schema}".dna_documents
496
+ (scope, kind, name, content, version, updated_at, tenant)
497
+ VALUES ($1, 'Module', $2, $3::jsonb, $4, $5, $6)
498
+ ON CONFLICT (scope, kind, name, tenant) DO UPDATE SET
499
+ content = EXCLUDED.content,
500
+ version = EXCLUDED.version,
501
+ updated_at = EXCLUDED.updated_at`, [scope, name, JSON.stringify(raw), nextVersion, nowIso(), tenant]);
502
+ await client.query("COMMIT");
503
+ return { id: ins[0].id, semver };
504
+ }
505
+ catch (e) {
506
+ await client.query("ROLLBACK");
507
+ throw e;
508
+ }
509
+ finally {
510
+ client.release();
511
+ }
512
+ }
513
+ /**
514
+ * List published versions of a Module. Returns newest-first by
515
+ * semver lexicographic order (good enough for typical X.Y.Z;
516
+ * v1.1 may add proper semver comparison).
517
+ */
518
+ async listModuleVersions(scope, name, options) {
519
+ await this._runMigrations();
520
+ const tenant = (options?.tenant ?? "") || "";
521
+ const includeDeprecated = options?.includeDeprecated ?? true;
522
+ const where = includeDeprecated
523
+ ? `WHERE scope=$1 AND kind='Module' AND name=$2 AND tenant=$3 AND semver IS NOT NULL`
524
+ : `WHERE scope=$1 AND kind='Module' AND name=$2 AND tenant=$3 AND semver IS NOT NULL AND deprecated=false`;
525
+ const { rows } = await this._pool.query(`SELECT id, semver, version, deprecated, deprecation_message, author, created_at
526
+ FROM "${this._schema}".dna_versions ${where}
527
+ ORDER BY semver DESC`, [scope, name, tenant]);
528
+ return rows.map((r) => ({
529
+ id: r.id,
530
+ semver: r.semver,
531
+ version: r.version,
532
+ deprecated: r.deprecated,
533
+ deprecationMessage: r.deprecation_message,
534
+ author: r.author,
535
+ createdAt: r.created_at,
536
+ }));
537
+ }
538
+ /**
539
+ * Mark a published Module version as deprecated. The row stays
540
+ * (immutability), but listModuleVersions can filter it out via
541
+ * `includeDeprecated: false`.
542
+ */
543
+ async deprecateModuleVersion(scope, name, semver, message, options) {
544
+ await this._runMigrations();
545
+ const tenant = (options?.tenant ?? "") || "";
546
+ const result = await this._pool.query(`UPDATE "${this._schema}".dna_versions
547
+ SET deprecated=true, deprecation_message=$1
548
+ WHERE scope=$2 AND kind='Module' AND name=$3 AND tenant=$4 AND semver=$5`, [message ?? null, scope, name, tenant, semver]);
549
+ if ((result.rowCount ?? 0) === 0) {
550
+ throw new Error(`Module ${name}@${semver} not found in scope ${scope} ` +
551
+ `(tenant=${tenant || "''"}).`);
552
+ }
553
+ }
554
+ }
555
+ /** Postgres returns JSONB as parsed object; legacy text columns as string. */
556
+ function parseJsonish(c) {
557
+ if (typeof c === "string")
558
+ return JSON.parse(c);
559
+ return c;
560
+ }
561
+ // Two-planes F2 — RecordStorePort conformance BY COMPOSITION (compile-time;
562
+ // lives in src because tsconfig excludes tests/ from `tsc --noEmit`). The
563
+ // PG TS adapter provides the WRITE half (`saveDocument`|`deleteDocument`);
564
+ // the read half (`query`|`count` push-down) is Py-only this phase — full
565
+ // conformance arrives with the PG TS push-down (F2.5+). Never called.
566
+ function _pgRecordStoreWriteHalfConformance(src) {
567
+ return src;
568
+ }
569
+ void _pgRecordStoreWriteHalfConformance;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * GitHubResolver — ResolverPort for github: URIs.
3
+ *
4
+ * 1:1 parity with Python dna.v3.adapters.resolvers.github.
5
+ *
6
+ * URI format: github:owner/repo[@ref][/path]
7
+ */
8
+ import type { ResolvedItem, ResolverPort } from "../../kernel/protocols.js";
9
+ export declare class GitHubResolver implements ResolverPort {
10
+ cacheKey(uri: string): string;
11
+ resolve(uri: string, dep: Record<string, unknown>): Promise<ResolvedItem[]>;
12
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * GitHubResolver — ResolverPort for github: URIs.
3
+ *
4
+ * 1:1 parity with Python dna.v3.adapters.resolvers.github.
5
+ *
6
+ * URI format: github:owner/repo[@ref][/path]
7
+ */
8
+ import { mkdtempSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { tmpdir } from "node:os";
11
+ import { ResolveError, ResolveNotFoundError } from "../../kernel/protocols.js";
12
+ import { LocalResolver, rejectLegacyShorthand } from "./local.js";
13
+ export class GitHubResolver {
14
+ cacheKey(uri) {
15
+ const raw = uri.replace(/^github:/, "");
16
+ const safe = raw.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
17
+ return `github-${safe}`;
18
+ }
19
+ async resolve(uri, dep) {
20
+ // i-010 — reject the dead legacy shorthand (`skills: [...]`)
21
+ // unconditionally, like the Py twin. Py rejects after fetch_tree (it
22
+ // funnels through LocalResolver._collect_requested); here it fires
23
+ // BEFORE cloning — same contract (loud ResolveError with the rewrite
24
+ // recipe), no wasted network round-trip.
25
+ rejectLegacyShorthand(dep);
26
+ const raw = uri.replace(/^github:/, "");
27
+ const match = raw.match(/^(?<owner>[^/]+)\/(?<repo>[^/@]+)(?:\/(?<path>[^@]+))?(?:@(?<ref>.+))?$/);
28
+ if (!match || !match.groups) {
29
+ throw new ResolveError(`Invalid github URI: ${uri}`);
30
+ }
31
+ const { owner, repo, path: subPath, ref } = match.groups;
32
+ const cloneUrl = `https://github.com/${owner}/${repo}.git`;
33
+ const tmpDir = mkdtempSync(join(tmpdir(), "dna-github-"));
34
+ const args = ["git", "clone", "--depth", "1"];
35
+ if (ref) {
36
+ args.push("--branch", ref);
37
+ }
38
+ args.push(cloneUrl, tmpDir);
39
+ const result = Bun.spawnSync(args, { timeout: 60_000 });
40
+ if (result.exitCode !== 0) {
41
+ throw new ResolveNotFoundError(`Git clone failed for ${cloneUrl}: exit code ${result.exitCode}`);
42
+ }
43
+ const source = subPath ? join(tmpDir, subPath) : tmpDir;
44
+ const local = new LocalResolver(source);
45
+ return local.resolve(subPath ? `local:.` : `local:`, dep);
46
+ }
47
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * HttpResolver — ResolverPort for http:/https: URIs.
3
+ *
4
+ * 1:1 parity with Python dna.v3.adapters.resolvers.http.
5
+ *
6
+ * Fetches manifest documents from HTTP endpoints. Supports two modes:
7
+ *
8
+ * 1. **Index mode** (default): GET {uri}/index.json → list of {kind, name, path}.
9
+ * Each item is fetched individually: GET {uri}/{path} → raw dict.
10
+ *
11
+ * 2. **Bundle mode** (fallback): GET {uri} → list of raw dicts directly.
12
+ */
13
+ import type { ResolvedItem, ResolverPort } from "../../kernel/protocols.js";
14
+ export declare class HttpResolver implements ResolverPort {
15
+ cacheKey(uri: string): string;
16
+ resolve(uri: string, dep: Record<string, unknown>): Promise<ResolvedItem[]>;
17
+ private _fetchJson;
18
+ private _resolveFromIndex;
19
+ private _resolveFromBundle;
20
+ private _collectRequested;
21
+ private _matchesRequest;
22
+ }