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.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/adapters/embedding/onnx.d.ts +32 -0
- package/dist/adapters/embedding/onnx.js +51 -0
- package/dist/adapters/filesystem/cache.d.ts +16 -0
- package/dist/adapters/filesystem/cache.js +113 -0
- package/dist/adapters/filesystem/index.d.ts +2 -0
- package/dist/adapters/filesystem/index.js +2 -0
- package/dist/adapters/filesystem/source.d.ts +82 -0
- package/dist/adapters/filesystem/source.js +357 -0
- package/dist/adapters/postgres/index.d.ts +2 -0
- package/dist/adapters/postgres/index.js +2 -0
- package/dist/adapters/postgres/migrations.d.ts +14 -0
- package/dist/adapters/postgres/migrations.js +88 -0
- package/dist/adapters/postgres/source.d.ts +172 -0
- package/dist/adapters/postgres/source.js +569 -0
- package/dist/adapters/resolvers/github.d.ts +12 -0
- package/dist/adapters/resolvers/github.js +47 -0
- package/dist/adapters/resolvers/http.d.ts +22 -0
- package/dist/adapters/resolvers/http.js +145 -0
- package/dist/adapters/resolvers/index.d.ts +3 -0
- package/dist/adapters/resolvers/index.js +3 -0
- package/dist/adapters/resolvers/local.d.ts +24 -0
- package/dist/adapters/resolvers/local.js +132 -0
- package/dist/adapters/search/driver.d.ts +45 -0
- package/dist/adapters/search/driver.js +138 -0
- package/dist/adapters/search/migrations.d.ts +26 -0
- package/dist/adapters/search/migrations.js +58 -0
- package/dist/adapters/search/rrf.d.ts +23 -0
- package/dist/adapters/search/rrf.js +42 -0
- package/dist/adapters/search/sqlite-vec.d.ts +66 -0
- package/dist/adapters/search/sqlite-vec.js +294 -0
- package/dist/bootstrap.d.ts +45 -0
- package/dist/bootstrap.js +134 -0
- package/dist/extensions/agentskills/DOCS.md +18 -0
- package/dist/extensions/agentskills.d.ts +28 -0
- package/dist/extensions/agentskills.js +252 -0
- package/dist/extensions/agentsmd/DOCS.md +18 -0
- package/dist/extensions/agentsmd.d.ts +22 -0
- package/dist/extensions/agentsmd.js +170 -0
- package/dist/extensions/audit/kinds/audit-log.kind.yaml +148 -0
- package/dist/extensions/audit.d.ts +25 -0
- package/dist/extensions/audit.js +62 -0
- package/dist/extensions/automation/kinds/automation.kind.yaml +296 -0
- package/dist/extensions/automation/query.d.ts +49 -0
- package/dist/extensions/automation/query.js +77 -0
- package/dist/extensions/automation/write-guards.d.ts +59 -0
- package/dist/extensions/automation/write-guards.js +153 -0
- package/dist/extensions/automation.d.ts +41 -0
- package/dist/extensions/automation.js +54 -0
- package/dist/extensions/collab.d.ts +16 -0
- package/dist/extensions/collab.js +79 -0
- package/dist/extensions/doc/kinds/doc.kind.yaml +118 -0
- package/dist/extensions/doc.d.ts +25 -0
- package/dist/extensions/doc.js +10 -0
- package/dist/extensions/eval/kinds/eval-baseline.kind.yaml +65 -0
- package/dist/extensions/eval/kinds/eval-case.kind.yaml +140 -0
- package/dist/extensions/eval/kinds/eval-run.kind.yaml +134 -0
- package/dist/extensions/eval/kinds/eval-suite.kind.yaml +87 -0
- package/dist/extensions/eval.d.ts +24 -0
- package/dist/extensions/eval.js +31 -0
- package/dist/extensions/evidence/kinds/evidence.kind.yaml +92 -0
- package/dist/extensions/evidence.d.ts +17 -0
- package/dist/extensions/evidence.js +94 -0
- package/dist/extensions/federation.d.ts +22 -0
- package/dist/extensions/federation.js +173 -0
- package/dist/extensions/guardrails/DOCS.md +20 -0
- package/dist/extensions/guardrails.d.ts +31 -0
- package/dist/extensions/guardrails.js +228 -0
- package/dist/extensions/helix/DOCS-Actor.md +20 -0
- package/dist/extensions/helix/DOCS-Agent.md +20 -0
- package/dist/extensions/helix/DOCS-UseCase.md +20 -0
- package/dist/extensions/helix/layers.d.ts +12 -0
- package/dist/extensions/helix/layers.js +18 -0
- package/dist/extensions/helix/write-guards.d.ts +70 -0
- package/dist/extensions/helix/write-guards.js +172 -0
- package/dist/extensions/helix.d.ts +29 -0
- package/dist/extensions/helix.js +963 -0
- package/dist/extensions/helix_extras.d.ts +124 -0
- package/dist/extensions/helix_extras.js +328 -0
- package/dist/extensions/hooks.d.ts +31 -0
- package/dist/extensions/hooks.js +247 -0
- package/dist/extensions/kinddef/DOCS.md +24 -0
- package/dist/extensions/kinddef.d.ts +40 -0
- package/dist/extensions/kinddef.js +171 -0
- package/dist/extensions/lesson.d.ts +6 -0
- package/dist/extensions/lesson.js +194 -0
- package/dist/extensions/modelreg/kinds/model-profile.kind.yaml +155 -0
- package/dist/extensions/modelreg.d.ts +30 -0
- package/dist/extensions/modelreg.js +38 -0
- package/dist/extensions/recognizer.d.ts +34 -0
- package/dist/extensions/recognizer.js +272 -0
- package/dist/extensions/research.d.ts +32 -0
- package/dist/extensions/research.js +292 -0
- package/dist/extensions/safety.d.ts +33 -0
- package/dist/extensions/safety.js +230 -0
- package/dist/extensions/sdlc/kinds/adr.kind.yaml +136 -0
- package/dist/extensions/sdlc/kinds/archive-proposal.kind.yaml +107 -0
- package/dist/extensions/sdlc/kinds/changelog.kind.yaml +108 -0
- package/dist/extensions/sdlc/kinds/cognitive-policy.kind.yaml +470 -0
- package/dist/extensions/sdlc/kinds/forecast.kind.yaml +125 -0
- package/dist/extensions/sdlc/kinds/insight.kind.yaml +87 -0
- package/dist/extensions/sdlc/kinds/kaizen.kind.yaml +106 -0
- package/dist/extensions/sdlc/kinds/lesson-learned.kind.yaml +270 -0
- package/dist/extensions/sdlc/kinds/narrative.kind.yaml +189 -0
- package/dist/extensions/sdlc/kinds/pattern-insight.kind.yaml +204 -0
- package/dist/extensions/sdlc/kinds/postmortem.kind.yaml +168 -0
- package/dist/extensions/sdlc/kinds/pre-mortem.kind.yaml +145 -0
- package/dist/extensions/sdlc/kinds/prompt-template.kind.yaml +109 -0
- package/dist/extensions/sdlc/kinds/retrospective.kind.yaml +186 -0
- package/dist/extensions/sdlc/kinds/risk-register.kind.yaml +178 -0
- package/dist/extensions/sdlc/kinds/saved-view.kind.yaml +127 -0
- package/dist/extensions/sdlc/kinds/status-report.kind.yaml +129 -0
- package/dist/extensions/sdlc/kinds/synthesis-run.kind.yaml +279 -0
- package/dist/extensions/sdlc/kinds/synthesizer-state.kind.yaml +196 -0
- package/dist/extensions/sdlc/kinds/workflow-event.kind.yaml +195 -0
- package/dist/extensions/sdlc.d.ts +32 -0
- package/dist/extensions/sdlc.js +1318 -0
- package/dist/extensions/soulspec/DOCS.md +19 -0
- package/dist/extensions/soulspec.d.ts +22 -0
- package/dist/extensions/soulspec.js +287 -0
- package/dist/extensions/tenant.d.ts +6 -0
- package/dist/extensions/tenant.js +291 -0
- package/dist/extensions/testkit.d.ts +24 -0
- package/dist/extensions/testkit.js +211 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +102 -0
- package/dist/kernel/_text.d.ts +28 -0
- package/dist/kernel/_text.js +42 -0
- package/dist/kernel/bundle-handle.d.ts +83 -0
- package/dist/kernel/bundle-handle.js +184 -0
- package/dist/kernel/capabilities.d.ts +157 -0
- package/dist/kernel/capabilities.js +109 -0
- package/dist/kernel/collaborator-ports.d.ts +91 -0
- package/dist/kernel/collaborator-ports.js +1 -0
- package/dist/kernel/composition-resolver.d.ts +215 -0
- package/dist/kernel/composition-resolver.js +558 -0
- package/dist/kernel/composition.d.ts +10 -0
- package/dist/kernel/composition.js +1 -0
- package/dist/kernel/descriptor-loader.d.ts +13 -0
- package/dist/kernel/descriptor-loader.js +48 -0
- package/dist/kernel/document.d.ts +43 -0
- package/dist/kernel/document.js +71 -0
- package/dist/kernel/embedding.d.ts +28 -0
- package/dist/kernel/embedding.js +78 -0
- package/dist/kernel/errors.d.ts +62 -0
- package/dist/kernel/errors.js +82 -0
- package/dist/kernel/events.d.ts +6 -0
- package/dist/kernel/events.js +21 -0
- package/dist/kernel/evidence-capture.d.ts +69 -0
- package/dist/kernel/evidence-capture.js +138 -0
- package/dist/kernel/fs.d.ts +20 -0
- package/dist/kernel/fs.js +128 -0
- package/dist/kernel/generic-rw.d.ts +33 -0
- package/dist/kernel/generic-rw.js +235 -0
- package/dist/kernel/hooks.d.ts +172 -0
- package/dist/kernel/hooks.js +256 -0
- package/dist/kernel/index.d.ts +624 -0
- package/dist/kernel/index.js +1638 -0
- package/dist/kernel/instance.d.ts +211 -0
- package/dist/kernel/instance.js +487 -0
- package/dist/kernel/kind-registry.d.ts +183 -0
- package/dist/kernel/kind-registry.js +710 -0
- package/dist/kernel/kind_base.d.ts +106 -0
- package/dist/kernel/kind_base.js +244 -0
- package/dist/kernel/layer-resolver.d.ts +48 -0
- package/dist/kernel/layer-resolver.js +284 -0
- package/dist/kernel/lock-manager.d.ts +21 -0
- package/dist/kernel/lock-manager.js +43 -0
- package/dist/kernel/lock.d.ts +38 -0
- package/dist/kernel/lock.js +115 -0
- package/dist/kernel/meta.d.ts +135 -0
- package/dist/kernel/meta.js +817 -0
- package/dist/kernel/models.d.ts +3089 -0
- package/dist/kernel/models.js +764 -0
- package/dist/kernel/navigator.d.ts +36 -0
- package/dist/kernel/navigator.js +132 -0
- package/dist/kernel/port-surface.d.ts +33 -0
- package/dist/kernel/port-surface.js +180 -0
- package/dist/kernel/preview.d.ts +60 -0
- package/dist/kernel/preview.js +54 -0
- package/dist/kernel/prompt-budget.d.ts +39 -0
- package/dist/kernel/prompt-budget.js +43 -0
- package/dist/kernel/prompt-builder.d.ts +25 -0
- package/dist/kernel/prompt-builder.js +237 -0
- package/dist/kernel/protocols.d.ts +751 -0
- package/dist/kernel/protocols.js +493 -0
- package/dist/kernel/reports.d.ts +26 -0
- package/dist/kernel/reports.js +215 -0
- package/dist/kernel/resolver.d.ts +146 -0
- package/dist/kernel/resolver.js +232 -0
- package/dist/kernel/resource.d.ts +57 -0
- package/dist/kernel/resource.js +97 -0
- package/dist/kernel/runtime.d.ts +16 -0
- package/dist/kernel/runtime.js +18 -0
- package/dist/kernel/safety-scanner.d.ts +38 -0
- package/dist/kernel/safety-scanner.js +228 -0
- package/dist/kernel/serialize-to-files.d.ts +29 -0
- package/dist/kernel/serialize-to-files.js +23 -0
- package/dist/kernel/spec-access.d.ts +23 -0
- package/dist/kernel/spec-access.js +40 -0
- package/dist/kernel/studio_ui.d.ts +88 -0
- package/dist/kernel/studio_ui.js +135 -0
- package/dist/kernel/templates.d.ts +77 -0
- package/dist/kernel/templates.js +116 -0
- package/dist/kernel/tool-registry.d.ts +52 -0
- package/dist/kernel/tool-registry.js +89 -0
- package/dist/kernel/write-pipeline.d.ts +65 -0
- package/dist/kernel/write-pipeline.js +271 -0
- package/dist/kernel/writer-helpers.d.ts +24 -0
- package/dist/kernel/writer-helpers.js +42 -0
- package/dist/memory/decay.d.ts +31 -0
- package/dist/memory/decay.js +97 -0
- package/dist/memory/ecphory.d.ts +25 -0
- package/dist/memory/ecphory.js +178 -0
- package/dist/memory/encodingContext.d.ts +21 -0
- package/dist/memory/encodingContext.js +62 -0
- package/dist/memory/index.d.ts +23 -0
- package/dist/memory/index.js +23 -0
- package/dist/memory/memoryType.d.ts +7 -0
- package/dist/memory/memoryType.js +24 -0
- package/dist/memory/policy.d.ts +35 -0
- package/dist/memory/policy.js +35 -0
- package/dist/memory/retrieval.d.ts +21 -0
- package/dist/memory/retrieval.js +145 -0
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.js +8 -0
- package/dist/testing/recordSearchConformance.d.ts +43 -0
- package/dist/testing/recordSearchConformance.js +147 -0
- package/dist/viz/ascii.d.ts +7 -0
- package/dist/viz/ascii.js +60 -0
- package/dist/viz/health.d.ts +8 -0
- package/dist/viz/health.js +161 -0
- package/dist/viz/index.d.ts +10 -0
- package/dist/viz/index.js +10 -0
- package/dist/viz/matrix.d.ts +8 -0
- package/dist/viz/matrix.js +84 -0
- package/dist/viz/mermaid.d.ts +38 -0
- package/dist/viz/mermaid.js +706 -0
- 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
|
+
}
|