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,558 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The kernel's unified composition motor — TS twin of
|
|
3
|
+
* `packages/sdk-py/dna/kernel/composition_resolver.py`
|
|
4
|
+
* (s-unify-composition-subsystems). One module, four composition
|
|
5
|
+
* concerns:
|
|
6
|
+
*
|
|
7
|
+
* 1. CompositionProfile (V1) — declarative wiring + UI hints
|
|
8
|
+
* registered by extensions (moved here from `composition.ts`,
|
|
9
|
+
* now a deprecated re-export shim).
|
|
10
|
+
* 2. `validateRefs` — THE shared cross-kind ref validation core;
|
|
11
|
+
* target resolution via `resolveDepFilterTargetOver`
|
|
12
|
+
* (kind-registry.ts) — the same canonical resolver the Kernel
|
|
13
|
+
* uses, so alias + legacy `kind=` resolve identically everywhere.
|
|
14
|
+
* 3. CompositionEngine — the `mi.composition` namespace (moved here
|
|
15
|
+
* from `composition-engine.ts`, deleted).
|
|
16
|
+
* 4. CompositionResolver — the Composition-V2 chain-resolution engine.
|
|
17
|
+
*
|
|
18
|
+
* Pure resolution types + merges stay in `resolver.ts` (Py:
|
|
19
|
+
* `kernel/resolver.py`).
|
|
20
|
+
*
|
|
21
|
+
* Record-plane rule (two-planes F2.5) — ONE rule for every reader: a ref
|
|
22
|
+
* whose TARGET Kind is plane="record" resolves when present in the doc
|
|
23
|
+
* index and is DEFERRED otherwise (never false-missing).
|
|
24
|
+
*
|
|
25
|
+
* # CompositionResolver — Composition-V2 (Phase 17 /
|
|
26
|
+
* s-ts-composition-v2-port).
|
|
27
|
+
*
|
|
28
|
+
* Behavior parity is 1:1 with the Python engine on the RESOLUTION SEMANTICS:
|
|
29
|
+
* chain walk (`computeResolutionChain` — cycle guard + MAX_RESOLUTION_DEPTH +
|
|
30
|
+
* V1 fallback to `_lib`), composition-rule lookup (`getCompositionRule` —
|
|
31
|
+
* LayerPolicy.composition_rules else the inherit-by-default denylist),
|
|
32
|
+
* `resolveDocument` (bootstrap bypass, tenant overlay, Catalog splice
|
|
33
|
+
* Local > Catalog > Base, override_full / field_level merge, provenance) and
|
|
34
|
+
* `personalizeDocument`. The behavioral gate is the shared fixture harness
|
|
35
|
+
* `tests/parity-fixtures/composition/` (s-parity-behavioral-harness), executed
|
|
36
|
+
* by BOTH `packages/sdk-py/tests/test_composition_parity_fixtures.py` and
|
|
37
|
+
* `packages/sdk-ts/tests/composition-parity-fixtures.test.ts`.
|
|
38
|
+
*
|
|
39
|
+
* Documented divergences from Python — PERF / infra, NOT semantics:
|
|
40
|
+
*
|
|
41
|
+
* 1. **No granular layer cache.** The Py kernel reads each layer through
|
|
42
|
+
* `_granular_doc_cached` (LRU 2000 / TTL 60s / single-flight). The TS
|
|
43
|
+
* kernel has no kernel-level doc cache, so `Kernel._granularDoc` hits the
|
|
44
|
+
* source directly on every layer read. Same inputs → same outputs; each
|
|
45
|
+
* resolve just costs O(chain) source reads instead of cache hits.
|
|
46
|
+
* 2. **No `_layer_observers` reverse-dep graph.** That structure exists purely
|
|
47
|
+
* to invalidate the Py granular cache across scopes on parent/catalog
|
|
48
|
+
* writes; with no cache there is nothing to invalidate. It is intentionally
|
|
49
|
+
* NOT ported (would be dead state).
|
|
50
|
+
* 3. **Catalog tier surface.** The TS kernel has no catalog machinery yet
|
|
51
|
+
* (package scan + tenant lockfile — deferred as `i-185`). The splice HOOK
|
|
52
|
+
* is fully implemented here (identical to Py: insert after the local
|
|
53
|
+
* entries, before the first parent, conflict surfacing on ≥2 hits) but
|
|
54
|
+
* `Kernel._catalogScopes` returns `[]` until i-185 lands, so the Catalog
|
|
55
|
+
* tier contributes no layers on TS today.
|
|
56
|
+
* 4. **`personalizeDocument` clones spec + envelope only.** The Py engine also
|
|
57
|
+
* clones binary bundle entries via `source._load_bundle_entries` +
|
|
58
|
+
* `kernel.write_bundle_entry_async`; the TS kernel has no bundle-entry
|
|
59
|
+
* write surface yet, so bundle payload cloning is Py-only (best-effort in
|
|
60
|
+
* Py anyway — failures there are swallowed).
|
|
61
|
+
*/
|
|
62
|
+
import { sourceCapabilities } from "./capabilities.js";
|
|
63
|
+
import { BOOTSTRAP_KINDS, DEFAULT_NON_INHERITABLE_KINDS_V1, MAX_RESOLUTION_DEPTH, ResolutionLayer, ResolutionPath, ResolvedDocument, mergeFieldLevel, mergeOverrideFull, } from "./resolver.js";
|
|
64
|
+
import { resolveDepFilterTargetOver } from "./kind-registry.js";
|
|
65
|
+
import { findConsumers } from "./preview.js";
|
|
66
|
+
/**
|
|
67
|
+
* Find the profile whose orchestrator matches a given alias.
|
|
68
|
+
* Returns null if none registered. Pure helper — no kernel dependency.
|
|
69
|
+
*/
|
|
70
|
+
export function profileForOrchestrator(profiles, orchestratorAlias) {
|
|
71
|
+
return profiles.find((p) => p.orchestratorAlias === orchestratorAlias) ?? null;
|
|
72
|
+
}
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// 2. validateRefs — the shared cross-kind ref validation core
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
/**
|
|
77
|
+
* Validate declared dep_filter refs of `docs` against `docIndex`
|
|
78
|
+
* (`${kind}\0${name}` keys). The ONE implementation behind
|
|
79
|
+
* `mi.composition.validate()` — target resolution via
|
|
80
|
+
* `resolveDepFilterTargetOver` (canonical s-alias resolver: alias
|
|
81
|
+
* contract + deprecated `kind=` shim). Unresolvable target → warnings;
|
|
82
|
+
* ref in index → resolved; absent + record-plane target → deferred
|
|
83
|
+
* (records resolve lazily via the kernel record plane — never
|
|
84
|
+
* false-missing); absent otherwise → missing.
|
|
85
|
+
* Py twin: `composition_resolver.validate_refs`.
|
|
86
|
+
*/
|
|
87
|
+
export function validateRefs(docs, docIndex, kinds) {
|
|
88
|
+
const resolved = [];
|
|
89
|
+
const missing = [];
|
|
90
|
+
const warnings = [];
|
|
91
|
+
const deferred = [];
|
|
92
|
+
for (const doc of docs) {
|
|
93
|
+
const kp = kinds.get(`${doc.apiVersion}\0${doc.kind}`);
|
|
94
|
+
if (!kp)
|
|
95
|
+
continue;
|
|
96
|
+
const filters = kp.depFilters();
|
|
97
|
+
if (!filters)
|
|
98
|
+
continue;
|
|
99
|
+
const spec = doc.spec;
|
|
100
|
+
for (const [specField, targetValue] of Object.entries(filters)) {
|
|
101
|
+
const targetKp = resolveDepFilterTargetOver(kinds, targetValue);
|
|
102
|
+
if (!targetKp) {
|
|
103
|
+
warnings.push(`${doc.kind}/${doc.name}: unknown alias '${targetValue}' in depFilters`);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const targetKind = targetKp.kind;
|
|
107
|
+
const declared = spec[specField] ?? null;
|
|
108
|
+
if (declared == null)
|
|
109
|
+
continue;
|
|
110
|
+
const refs = Array.isArray(declared)
|
|
111
|
+
? declared
|
|
112
|
+
: typeof declared === "string"
|
|
113
|
+
? [declared]
|
|
114
|
+
: [];
|
|
115
|
+
const targetIsRecord = (targetKp.plane ?? "composition") === "record";
|
|
116
|
+
for (const refName of refs) {
|
|
117
|
+
const label = `${doc.kind}/${doc.name}.${specField}=${refName} -> ${targetKind}/${refName}`;
|
|
118
|
+
if (docIndex.has(`${targetKind}\0${refName}`)) {
|
|
119
|
+
resolved.push(label);
|
|
120
|
+
}
|
|
121
|
+
else if (targetIsRecord) {
|
|
122
|
+
deferred.push(label + " (record — resolved lazily)");
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
missing.push(label + " NOT FOUND");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { resolved, missing, warnings, deferred };
|
|
131
|
+
}
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// 3. CompositionEngine — the `mi.composition` namespace
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
export class CompositionEngine {
|
|
136
|
+
host;
|
|
137
|
+
constructor(host) {
|
|
138
|
+
this.host = host;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Validate all composition references over the MI plane.
|
|
142
|
+
* Equivalent to `mi.compositionResult`. Delegates to `validateRefs` —
|
|
143
|
+
* records are excluded from the MI materialization, so record-target
|
|
144
|
+
* refs land in `deferred` (they resolve lazily via the kernel record
|
|
145
|
+
* plane at read time).
|
|
146
|
+
*/
|
|
147
|
+
validate() {
|
|
148
|
+
const kinds = this.host._kinds;
|
|
149
|
+
const docIndex = new Set();
|
|
150
|
+
for (const d of this.host.documents) {
|
|
151
|
+
docIndex.add(`${d.kind}\0${d.name}`);
|
|
152
|
+
}
|
|
153
|
+
return validateRefs(this.host.documents, docIndex, kinds);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Iterate a document's declared dep_filters dynamically.
|
|
157
|
+
* Equivalent to `mi.iterDocDeps(doc)`.
|
|
158
|
+
*/
|
|
159
|
+
iterDocDeps(doc) {
|
|
160
|
+
const kinds = this.host._kinds;
|
|
161
|
+
const sourceKp = kinds.get(`${doc.apiVersion}\0${doc.kind}`);
|
|
162
|
+
if (!sourceKp)
|
|
163
|
+
return [];
|
|
164
|
+
const filters = sourceKp.depFilters();
|
|
165
|
+
if (!filters)
|
|
166
|
+
return [];
|
|
167
|
+
const spec = doc.spec ?? {};
|
|
168
|
+
const result = [];
|
|
169
|
+
for (const [label, targetValue] of Object.entries(filters)) {
|
|
170
|
+
const targetKp = resolveDepFilterTargetOver(kinds, targetValue);
|
|
171
|
+
if (!targetKp)
|
|
172
|
+
continue;
|
|
173
|
+
const value = spec[label];
|
|
174
|
+
let names = [];
|
|
175
|
+
if (Array.isArray(value)) {
|
|
176
|
+
names = value.filter((v) => typeof v === "string");
|
|
177
|
+
}
|
|
178
|
+
else if (typeof value === "string" && value) {
|
|
179
|
+
names = [value];
|
|
180
|
+
}
|
|
181
|
+
if (names.length === 0)
|
|
182
|
+
continue;
|
|
183
|
+
result.push({ label, targetKind: targetKp.kind, names });
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Walk the manifest and return every doc that references this one.
|
|
189
|
+
* Equivalent to `mi.consumersOf(kind, name)`.
|
|
190
|
+
*/
|
|
191
|
+
consumersOf(kind, name) {
|
|
192
|
+
return findConsumers(this.host, { kind, name });
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Build a dependency tree for the manifest.
|
|
196
|
+
* Equivalent to `mi.dependencyTree()`.
|
|
197
|
+
*/
|
|
198
|
+
dependencyTree() {
|
|
199
|
+
const kinds = this.host._kinds;
|
|
200
|
+
const docIndex = new Map();
|
|
201
|
+
for (const d of this.host.documents) {
|
|
202
|
+
docIndex.set(`${d.kind}\0${d.name}`, d);
|
|
203
|
+
}
|
|
204
|
+
const tree = {};
|
|
205
|
+
for (const doc of this.host.documents) {
|
|
206
|
+
const key = `${doc.apiVersion}\0${doc.kind}`;
|
|
207
|
+
const kp = kinds.get(key);
|
|
208
|
+
if (!kp)
|
|
209
|
+
continue;
|
|
210
|
+
const filters = kp.depFilters();
|
|
211
|
+
if (!filters)
|
|
212
|
+
continue;
|
|
213
|
+
const dependsOn = {};
|
|
214
|
+
const spec = doc.spec;
|
|
215
|
+
for (const [specField, targetValue] of Object.entries(filters)) {
|
|
216
|
+
const targetKp = resolveDepFilterTargetOver(kinds, targetValue);
|
|
217
|
+
if (!targetKp)
|
|
218
|
+
continue;
|
|
219
|
+
const targetKind = targetKp.kind;
|
|
220
|
+
const declared = spec[specField];
|
|
221
|
+
if (!declared)
|
|
222
|
+
continue;
|
|
223
|
+
const refs = Array.isArray(declared)
|
|
224
|
+
? declared
|
|
225
|
+
: typeof declared === "string"
|
|
226
|
+
? [declared]
|
|
227
|
+
: [];
|
|
228
|
+
const deps = {};
|
|
229
|
+
for (const refName of refs) {
|
|
230
|
+
const depEntry = { kind: targetKind };
|
|
231
|
+
const refDoc = docIndex.get(`${targetKind}\0${refName}`);
|
|
232
|
+
if (refDoc) {
|
|
233
|
+
depEntry.found = true;
|
|
234
|
+
const desc = refDoc.metadata.description ?? "";
|
|
235
|
+
if (desc)
|
|
236
|
+
depEntry.description = desc;
|
|
237
|
+
const extra = targetKp.summary(refDoc);
|
|
238
|
+
if (extra)
|
|
239
|
+
Object.assign(depEntry, extra);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
depEntry.found = false;
|
|
243
|
+
}
|
|
244
|
+
deps[refName] = depEntry;
|
|
245
|
+
}
|
|
246
|
+
if (Object.keys(deps).length > 0) {
|
|
247
|
+
dependsOn[specField] = deps;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (Object.keys(dependsOn).length > 0) {
|
|
251
|
+
tree[doc.name] = {
|
|
252
|
+
kind: doc.kind,
|
|
253
|
+
description: doc.metadata.description ?? "",
|
|
254
|
+
depends_on: dependsOn,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return tree;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// 4. CompositionResolver — the Composition-V2 chain-resolution engine
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
/** Structural layer equality — mirrors the Python frozen-dataclass `==`
|
|
265
|
+
* (field-by-field), which `resolve_document` relies on to flag the
|
|
266
|
+
* contributing layer. Reference equality would diverge when the same
|
|
267
|
+
* (scope, tenant) pair appears twice in a spliced chain. */
|
|
268
|
+
function layerEquals(a, b) {
|
|
269
|
+
return (a.scope === b.scope
|
|
270
|
+
&& a.tenant === b.tenant
|
|
271
|
+
&& a.found === b.found
|
|
272
|
+
&& a.contributed === b.contributed
|
|
273
|
+
&& a.versionSha === b.versionSha);
|
|
274
|
+
}
|
|
275
|
+
function specOf(raw) {
|
|
276
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
277
|
+
return null;
|
|
278
|
+
const spec = raw.spec;
|
|
279
|
+
if (spec === null || spec === undefined || typeof spec !== "object" || Array.isArray(spec)) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
return spec;
|
|
283
|
+
}
|
|
284
|
+
export class CompositionResolver {
|
|
285
|
+
_k;
|
|
286
|
+
constructor(kernel) {
|
|
287
|
+
this._k = kernel;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Walk `Genome.spec.parent_scope` transitively → ordered chain of
|
|
291
|
+
* `[scope, tenant]` pairs, HIGHEST priority first. Cycle-guarded; depth
|
|
292
|
+
* capped at MAX_RESOLUTION_DEPTH; missing Genome terminates the walk.
|
|
293
|
+
* 1:1 with Py `compute_resolution_chain`.
|
|
294
|
+
*/
|
|
295
|
+
async computeResolutionChain(scope, tenant) {
|
|
296
|
+
const k = this._k;
|
|
297
|
+
const inheritParent = k.constructor.INHERIT_PARENT_SCOPE;
|
|
298
|
+
const chain = [];
|
|
299
|
+
const visited = new Set();
|
|
300
|
+
let current = scope;
|
|
301
|
+
let depth = 0;
|
|
302
|
+
while (current && !visited.has(current) && depth < MAX_RESOLUTION_DEPTH) {
|
|
303
|
+
visited.add(current);
|
|
304
|
+
if (tenant)
|
|
305
|
+
chain.push([current, tenant]);
|
|
306
|
+
chain.push([current, null]);
|
|
307
|
+
// Fail-soft: a scope absent on the source contributes no parent
|
|
308
|
+
// (chain ends here instead of throwing) — critical under the
|
|
309
|
+
// inherit-by-default denylist where every read computes a chain.
|
|
310
|
+
let pkgRaw = null;
|
|
311
|
+
try {
|
|
312
|
+
pkgRaw = await k._granularDoc([current, "Genome", current, ""]);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
pkgRaw = null;
|
|
316
|
+
}
|
|
317
|
+
let parent = null;
|
|
318
|
+
const spec = specOf(pkgRaw);
|
|
319
|
+
if (spec) {
|
|
320
|
+
const parentVal = spec.parent_scope;
|
|
321
|
+
if (typeof parentVal === "string" && parentVal)
|
|
322
|
+
parent = parentVal;
|
|
323
|
+
}
|
|
324
|
+
// V1 back-compat: when Genome omits parent_scope, escalate to the
|
|
325
|
+
// legacy INHERIT_PARENT_SCOPE (default _lib) so existing scopes
|
|
326
|
+
// inherit without migration. Overridden once parent_scope is declared.
|
|
327
|
+
if (parent === null
|
|
328
|
+
&& current !== inheritParent
|
|
329
|
+
&& !visited.has(inheritParent)) {
|
|
330
|
+
parent = inheritParent;
|
|
331
|
+
}
|
|
332
|
+
current = parent;
|
|
333
|
+
depth += 1;
|
|
334
|
+
}
|
|
335
|
+
return chain;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Resolve `[scope_inheritance, merge_strategy, tenant_overlay]` for
|
|
339
|
+
* (scope, kind) — from the scope's LayerPolicy composition_rules, else
|
|
340
|
+
* the inherit-by-default denylist (everything inherits from _lib except
|
|
341
|
+
* the per-scope ledger + structural Kinds). 1:1 with Py
|
|
342
|
+
* `get_composition_rule`.
|
|
343
|
+
*/
|
|
344
|
+
async getCompositionRule(scope, kind) {
|
|
345
|
+
const src = this._k.activeSource;
|
|
346
|
+
// s-sourceport-contract-cleanup: declared capabilities, not typeof.
|
|
347
|
+
if (src !== null && sourceCapabilities(src).queryPushdown && typeof src.query === "function") {
|
|
348
|
+
try {
|
|
349
|
+
for await (const raw of src.query(scope, "LayerPolicy", {})) {
|
|
350
|
+
const spec = specOf(raw);
|
|
351
|
+
if (!spec)
|
|
352
|
+
continue;
|
|
353
|
+
const rules = spec.composition_rules;
|
|
354
|
+
if (rules === null || typeof rules !== "object" || Array.isArray(rules))
|
|
355
|
+
continue;
|
|
356
|
+
const rule = rules[kind];
|
|
357
|
+
if (rule !== null && typeof rule === "object" && !Array.isArray(rule)) {
|
|
358
|
+
const r = rule;
|
|
359
|
+
return [
|
|
360
|
+
String(r.scope_inheritance || "disabled").toLowerCase(),
|
|
361
|
+
String(r.merge_strategy || "override_full").toLowerCase(),
|
|
362
|
+
String(r.tenant_overlay || "none").toLowerCase(),
|
|
363
|
+
];
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
// Fail-soft — a missing scope / query error falls through to defaults.
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (!DEFAULT_NON_INHERITABLE_KINDS_V1.has(kind)) {
|
|
372
|
+
return ["enabled", "override_full", "field_level"];
|
|
373
|
+
}
|
|
374
|
+
// Non-inheritable Kinds STILL honor tenant overlay (TENANTED Canvas,
|
|
375
|
+
// VoiceEpisode, Story must read tenant=X correctly).
|
|
376
|
+
return ["disabled", "override_full", "field_level"];
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Resolve a doc through the composition chain (Phase 17). Returns a
|
|
380
|
+
* ResolvedDocument with merged doc + full provenance. Bootstrap Kinds
|
|
381
|
+
* bypass inheritance (local-only). 1:1 with Py `resolve_document`.
|
|
382
|
+
*/
|
|
383
|
+
async resolveDocument(scope, kind, name, opts) {
|
|
384
|
+
const k = this._k;
|
|
385
|
+
const tenant = opts?.tenant ?? null;
|
|
386
|
+
// ── Bootstrap Kinds — local-only ─────────────────────────────
|
|
387
|
+
if (BOOTSTRAP_KINDS.has(kind)) {
|
|
388
|
+
const raw = await k._granularDoc([scope, kind, name, tenant ?? ""]);
|
|
389
|
+
const layer = new ResolutionLayer({
|
|
390
|
+
scope,
|
|
391
|
+
tenant,
|
|
392
|
+
found: raw !== null,
|
|
393
|
+
contributed: raw !== null,
|
|
394
|
+
});
|
|
395
|
+
return new ResolvedDocument({
|
|
396
|
+
doc: raw,
|
|
397
|
+
provenance: new ResolutionPath([layer]),
|
|
398
|
+
isInherited: false,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
// ── Resolve composition rule ─────────────────────────────────
|
|
402
|
+
const [scopeInh, mergeStrat, tenantOv] = await this.getCompositionRule(scope, kind);
|
|
403
|
+
// ── Build resolution chain ───────────────────────────────────
|
|
404
|
+
// Catalog scopes that contributed THIS (kind, name) — used to surface
|
|
405
|
+
// multi-package conflicts after the merge (Phase 3b ch4, i-112).
|
|
406
|
+
let catalogLayerScopes = new Set();
|
|
407
|
+
let chain;
|
|
408
|
+
if (scopeInh === "disabled") {
|
|
409
|
+
// Local-only: bootstrap/structural Kinds never inherit AND never
|
|
410
|
+
// pick up the Catalog tier (matches today's behavior exactly).
|
|
411
|
+
chain = [];
|
|
412
|
+
if (tenant && tenantOv !== "none")
|
|
413
|
+
chain.push([scope, tenant]);
|
|
414
|
+
chain.push([scope, null]);
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
chain = await this.computeResolutionChain(scope, tenantOv !== "none" ? tenant : null);
|
|
418
|
+
// ── Splice the Catalog tier: Local > Catalog > Base ──────────
|
|
419
|
+
// Insert the tenant's Catalog scopes IMMEDIATELY AFTER the local
|
|
420
|
+
// scope's entries (the leading (scope, …) pairs) and BEFORE the
|
|
421
|
+
// first parent — so the positional merge yields Local > Catalog >
|
|
422
|
+
// Base while preserving mergeFieldLevel (first contributor =
|
|
423
|
+
// primary). Fail-soft: a Catalog glitch must never crash a resolve.
|
|
424
|
+
let catalogScopes;
|
|
425
|
+
try {
|
|
426
|
+
catalogScopes = await k._catalogScopes(tenant, { exclude: new Set([scope]) });
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
catalogScopes = []; // fail-soft, never crash a resolve
|
|
430
|
+
}
|
|
431
|
+
if (catalogScopes.length > 0) {
|
|
432
|
+
let localLen = 0;
|
|
433
|
+
for (const [cs] of chain) {
|
|
434
|
+
if (cs === scope)
|
|
435
|
+
localLen += 1;
|
|
436
|
+
else
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
const localPart = chain.slice(0, localLen);
|
|
440
|
+
const rest = chain.slice(localLen);
|
|
441
|
+
const catalogEntries = catalogScopes.map(([cs, ct]) => [cs, ct]);
|
|
442
|
+
catalogLayerScopes = new Set(catalogEntries.map(([cs]) => cs));
|
|
443
|
+
chain = [...localPart, ...catalogEntries, ...rest];
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// ── Query each layer (direct source reads — see PERF note in the
|
|
447
|
+
// module docstring: the Py twin caches here; TS does not) ──────
|
|
448
|
+
const contributions = [];
|
|
449
|
+
// Catalog scopes that actually held this (kind, name) — for the
|
|
450
|
+
// multi-package conflict surface below.
|
|
451
|
+
const catalogHits = [];
|
|
452
|
+
for (const [layerScope, layerTenant] of chain) {
|
|
453
|
+
const raw = await k._granularDoc([layerScope, kind, name, layerTenant ?? ""]);
|
|
454
|
+
// NOTE: the Py twin registers `_layer_observers` reverse-deps here
|
|
455
|
+
// (cache invalidation infra). Intentionally not ported — the TS
|
|
456
|
+
// kernel has no granular cache to invalidate.
|
|
457
|
+
if (raw !== null && catalogLayerScopes.has(layerScope)) {
|
|
458
|
+
catalogHits.push(layerScope);
|
|
459
|
+
}
|
|
460
|
+
contributions.push([
|
|
461
|
+
new ResolutionLayer({
|
|
462
|
+
scope: layerScope,
|
|
463
|
+
tenant: layerTenant,
|
|
464
|
+
found: raw !== null,
|
|
465
|
+
}),
|
|
466
|
+
raw,
|
|
467
|
+
]);
|
|
468
|
+
}
|
|
469
|
+
// Surface (don't fail) when ≥2 Catalog packages provide the same
|
|
470
|
+
// (kind, name): the sorted-first catalog scope wins positionally; the
|
|
471
|
+
// rest are shadowed. Determinism is guaranteed by _catalogScopes' sort.
|
|
472
|
+
if (catalogHits.length >= 2) {
|
|
473
|
+
console.info(`catalog tier conflict: ${kind}/${name} provided by ${catalogHits.length} `
|
|
474
|
+
+ `catalog packages ${JSON.stringify(catalogHits)} for scope=${JSON.stringify(scope)} `
|
|
475
|
+
+ `tenant=${JSON.stringify(tenant)} — ${JSON.stringify(catalogHits[0])} wins `
|
|
476
|
+
+ `(sorted-first); others shadowed.`);
|
|
477
|
+
}
|
|
478
|
+
// ── Apply merge strategy ─────────────────────────────────────
|
|
479
|
+
let mergedDoc;
|
|
480
|
+
let primary;
|
|
481
|
+
let contributionsByField = {};
|
|
482
|
+
if (mergeStrat === "field_level") {
|
|
483
|
+
[mergedDoc, primary, contributionsByField] = mergeFieldLevel(contributions);
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
[mergedDoc, primary] = mergeOverrideFull(contributions);
|
|
487
|
+
}
|
|
488
|
+
// ── Build provenance with contributed flag ───────────────────
|
|
489
|
+
const stepsWithContributed = [];
|
|
490
|
+
for (const [layer, raw] of contributions) {
|
|
491
|
+
const contributed = ((mergeStrat === "field_level" && raw !== null)
|
|
492
|
+
|| (primary !== null && layerEquals(layer, primary)));
|
|
493
|
+
stepsWithContributed.push(new ResolutionLayer({
|
|
494
|
+
scope: layer.scope,
|
|
495
|
+
tenant: layer.tenant,
|
|
496
|
+
found: layer.found,
|
|
497
|
+
contributed,
|
|
498
|
+
versionSha: layer.versionSha,
|
|
499
|
+
}));
|
|
500
|
+
}
|
|
501
|
+
const provenance = new ResolutionPath(stepsWithContributed);
|
|
502
|
+
const isInherited = primary !== null && primary.scope !== scope;
|
|
503
|
+
return new ResolvedDocument({
|
|
504
|
+
doc: mergedDoc,
|
|
505
|
+
provenance,
|
|
506
|
+
isInherited,
|
|
507
|
+
contributionsByField,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Clone an inherited doc into `targetScope` as a local override
|
|
512
|
+
* (Phase 17). Throws if the doc isn't inherited / target exists (without
|
|
513
|
+
* overwrite). Clones spec + envelope atomically via `writeDocument`.
|
|
514
|
+
* 1:1 with Py `personalize_document` EXCEPT bundle-entry payload cloning
|
|
515
|
+
* (Py-only — see divergence #4 in the module docstring).
|
|
516
|
+
*/
|
|
517
|
+
async personalizeDocument(targetScope, kind, name, opts) {
|
|
518
|
+
const k = this._k;
|
|
519
|
+
const tenant = opts?.tenant ?? null;
|
|
520
|
+
const overwrite = opts?.overwrite ?? false;
|
|
521
|
+
if (BOOTSTRAP_KINDS.has(kind)) {
|
|
522
|
+
throw new Error(`Kind ${JSON.stringify(kind)} is bootstrap and cannot be personalized.`);
|
|
523
|
+
}
|
|
524
|
+
const resolved = await this.resolveDocument(targetScope, kind, name, { tenant });
|
|
525
|
+
if (resolved.doc === null) {
|
|
526
|
+
throw new Error(`${kind}/${name} not found in any scope via composition chain from ${targetScope}.`);
|
|
527
|
+
}
|
|
528
|
+
if (!resolved.isInherited) {
|
|
529
|
+
throw new Error(`${kind}/${name} is already local to ${targetScope} — no need to personalize.`);
|
|
530
|
+
}
|
|
531
|
+
// Check targetScope local existence (fresh source state — no cache
|
|
532
|
+
// on TS anyway; the loadOne feature-test mirrors Py's getattr guard).
|
|
533
|
+
const src = k.activeSource;
|
|
534
|
+
if (!overwrite && src !== null && sourceCapabilities(src).granularOne && typeof src.loadOne === "function") {
|
|
535
|
+
const existing = await src.loadOne(targetScope, kind, name, { tenant });
|
|
536
|
+
if (existing !== null) {
|
|
537
|
+
throw new Error(`${kind}/${name} already exists locally in ${targetScope}. `
|
|
538
|
+
+ `Pass overwrite=true to replace.`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const eff = resolved.provenance.effectiveLayer;
|
|
542
|
+
if (eff === null) {
|
|
543
|
+
throw new Error("Cannot personalize: provenance has no effective layer.");
|
|
544
|
+
}
|
|
545
|
+
const doc = resolved.doc;
|
|
546
|
+
const clonedRaw = {
|
|
547
|
+
apiVersion: doc.apiVersion ?? null,
|
|
548
|
+
kind,
|
|
549
|
+
metadata: { ...(doc.metadata ?? {}), name },
|
|
550
|
+
spec: { ...(doc.spec ?? {}) },
|
|
551
|
+
};
|
|
552
|
+
await k.writeDocument(targetScope, kind, name, clonedRaw, { tenant });
|
|
553
|
+
// Bundle-entry (binary payload) cloning is Py-only today — the TS
|
|
554
|
+
// kernel has no bundle-entry write surface (divergence #4).
|
|
555
|
+
// Return fresh resolution (now local).
|
|
556
|
+
return await this.resolveDocument(targetScope, kind, name, { tenant });
|
|
557
|
+
}
|
|
558
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated shim — import from `./composition-resolver.js` instead.
|
|
3
|
+
*
|
|
4
|
+
* The CompositionProfile (V1) types moved into the unified composition
|
|
5
|
+
* motor (s-unify-composition-subsystems); this module remains only so
|
|
6
|
+
* external callers keep importing. It will be removed in a future major.
|
|
7
|
+
* Py twin: `dna/kernel/composition.py`.
|
|
8
|
+
*/
|
|
9
|
+
export type { CompositionProfile, CompositionSlot, HealthCheckHint, QuadrantHint, TimelineHint, } from "./composition-resolver.js";
|
|
10
|
+
export { profileForOrchestrator } from "./composition-resolver.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { profileForOrchestrator } from "./composition-resolver.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse every `<relKindsDir>/*.kind.yaml` next to the calling module.
|
|
3
|
+
*
|
|
4
|
+
* @param moduleUrl the caller's `import.meta.url`
|
|
5
|
+
* @param relKindsDir dir relative to the module, e.g. `"sdlc/kinds"` for
|
|
6
|
+
* `src/extensions/sdlc.ts` → `src/extensions/sdlc/kinds/`
|
|
7
|
+
*
|
|
8
|
+
* Returns the raw objects sorted by filename (deterministic registration
|
|
9
|
+
* order). A missing dir returns `[]` — extensions can call this
|
|
10
|
+
* unconditionally. A descriptor that isn't a YAML mapping throws (a broken
|
|
11
|
+
* packaged descriptor is a packaging bug, never a silent skip).
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadDescriptors(moduleUrl: string, relKindsDir: string): Record<string, unknown>[];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F3 (spec D3): builtin Kind descriptors as package data.
|
|
3
|
+
*
|
|
4
|
+
* Extensions ship builtin record Kinds as `kinds/*.kind.yaml` files next to
|
|
5
|
+
* their module (same KindDefinition format as per-scope KIND.yaml docs — one
|
|
6
|
+
* format, one funnel). `loadDescriptors` reads them relative to the calling
|
|
7
|
+
* module's `import.meta.url` (the same pattern the template-owning extensions
|
|
8
|
+
* use; package.json `files` ships `src/extensions/* /kinds/**`) and hands the
|
|
9
|
+
* parsed raws to `kernel.kindFromDescriptor`.
|
|
10
|
+
*
|
|
11
|
+
* The Python twin is `dna/kernel/descriptor_loader.py`; the
|
|
12
|
+
* descriptor FILES are parity-critical (byte-identical Py↔TS — see
|
|
13
|
+
* `tests/descriptor-hash-parity.test.ts`).
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { load as yamlLoad } from "js-yaml";
|
|
19
|
+
const SUFFIX = ".kind.yaml";
|
|
20
|
+
/**
|
|
21
|
+
* Parse every `<relKindsDir>/*.kind.yaml` next to the calling module.
|
|
22
|
+
*
|
|
23
|
+
* @param moduleUrl the caller's `import.meta.url`
|
|
24
|
+
* @param relKindsDir dir relative to the module, e.g. `"sdlc/kinds"` for
|
|
25
|
+
* `src/extensions/sdlc.ts` → `src/extensions/sdlc/kinds/`
|
|
26
|
+
*
|
|
27
|
+
* Returns the raw objects sorted by filename (deterministic registration
|
|
28
|
+
* order). A missing dir returns `[]` — extensions can call this
|
|
29
|
+
* unconditionally. A descriptor that isn't a YAML mapping throws (a broken
|
|
30
|
+
* packaged descriptor is a packaging bug, never a silent skip).
|
|
31
|
+
*/
|
|
32
|
+
export function loadDescriptors(moduleUrl, relKindsDir) {
|
|
33
|
+
const kindsDir = join(dirname(fileURLToPath(moduleUrl)), relKindsDir);
|
|
34
|
+
if (!existsSync(kindsDir))
|
|
35
|
+
return [];
|
|
36
|
+
const raws = [];
|
|
37
|
+
for (const name of readdirSync(kindsDir).sort()) {
|
|
38
|
+
if (!name.endsWith(SUFFIX))
|
|
39
|
+
continue;
|
|
40
|
+
const raw = yamlLoad(readFileSync(join(kindsDir, name), "utf-8"));
|
|
41
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
42
|
+
throw new Error(`descriptor ${relKindsDir}/${name} must be a YAML mapping ` +
|
|
43
|
+
`(KindDefinition), got ${Array.isArray(raw) ? "array" : typeof raw}`);
|
|
44
|
+
}
|
|
45
|
+
raws.push(raw);
|
|
46
|
+
}
|
|
47
|
+
return raws;
|
|
48
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document — unified wrapper for all manifest documents.
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with Python dna.v3.kernel.document.
|
|
5
|
+
*
|
|
6
|
+
* v1.0 — `Document<SpecT>` generic typing. Consumers can declare
|
|
7
|
+
* spec type at the call site without a runtime cost:
|
|
8
|
+
*
|
|
9
|
+
* const doc = mi.documents.find((d) => d.kind === "Asset" && d.name === "x") as Document<AssetSpec>;
|
|
10
|
+
* doc.spec.summary?.byte_count; // type-checker validated
|
|
11
|
+
*
|
|
12
|
+
* Bare `Document` defaults to `Record<string, unknown>` so existing
|
|
13
|
+
* untyped code continues working. Purely additive — no API change
|
|
14
|
+
* for callers that don't opt in.
|
|
15
|
+
*/
|
|
16
|
+
export declare class Document<SpecT extends Record<string, unknown> = Record<string, unknown>> {
|
|
17
|
+
readonly apiVersion: string;
|
|
18
|
+
readonly kind: string;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly raw: Record<string, unknown>;
|
|
21
|
+
readonly typed: unknown;
|
|
22
|
+
readonly origin: string;
|
|
23
|
+
private readonly _metadataRaw;
|
|
24
|
+
private readonly _specRaw;
|
|
25
|
+
constructor(opts: {
|
|
26
|
+
apiVersion: string;
|
|
27
|
+
kind: string;
|
|
28
|
+
name: string;
|
|
29
|
+
metadata?: Record<string, unknown>;
|
|
30
|
+
spec?: Record<string, unknown>;
|
|
31
|
+
raw?: Record<string, unknown>;
|
|
32
|
+
typed?: unknown;
|
|
33
|
+
origin?: string;
|
|
34
|
+
});
|
|
35
|
+
/** Always returns Record<string, unknown> — typed metadata when available, raw dict otherwise. */
|
|
36
|
+
get metadata(): Record<string, unknown>;
|
|
37
|
+
/** Returns the spec, typed as `SpecT` when consumers parameterize
|
|
38
|
+
* the Document. Runtime: still a plain dict. */
|
|
39
|
+
get spec(): SpecT;
|
|
40
|
+
/** Create a Document from a raw dict. */
|
|
41
|
+
static fromRaw(raw: Record<string, unknown>, typed?: unknown, origin?: string): Document;
|
|
42
|
+
toString(): string;
|
|
43
|
+
}
|