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,710 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KindRegistry — the kernel's registered-Kind identity map + the full
|
|
3
|
+
* registration funnel, extracted from the Kernel god-object (kernel
|
|
4
|
+
* decomposition, Fase 3 — `s-kernel-decomp-ts-parity`; Py twin:
|
|
5
|
+
* `dna/kernel/kind_registry.py`).
|
|
6
|
+
*
|
|
7
|
+
* Two surfaces live here:
|
|
8
|
+
*
|
|
9
|
+
* - **Lookup** (pre-F3): the `kinds` map + the read surface (port lookup,
|
|
10
|
+
* alias, storage/container resolution, container→kind, describe). Also
|
|
11
|
+
* exported as the PURE functions `kindPortForOver` / `resolveDepFilterTargetOver`
|
|
12
|
+
* so a registry VIEW over an existing map (CompositionEngine / nav_kernel /
|
|
13
|
+
* the ManifestInstance) shares the ONE canonical resolver without holding a
|
|
14
|
+
* class instance.
|
|
15
|
+
* - **Registration** (this slice): `registerKind` (the H1 validation funnel —
|
|
16
|
+
* interface/dup-key/dup-alias/BUNDLE-marker/plane/i-195-name-collision +
|
|
17
|
+
* alias generation), the static `_lintPlane` helper, `registerFromDescriptor`
|
|
18
|
+
* (builtin `*.kind.yaml` descriptors) and `registerKindDefinitions` (the
|
|
19
|
+
* per-scope KindDefinition funnel — warn+skip instead of throw). The kernel
|
|
20
|
+
* keeps `kind()` / `kindFromDescriptor()` / `_registerKindDefinitions()` /
|
|
21
|
+
* `_registerCustomKinds()` as THIN facades delegating here.
|
|
22
|
+
*
|
|
23
|
+
* Registration mutates `this.kinds` directly (the registry OWNS the map; the
|
|
24
|
+
* kernel's `_kinds` getter proxies to it, so the ~20 inline `this._kinds` read
|
|
25
|
+
* sites across the kernel keep working unchanged). Side effects that touch the
|
|
26
|
+
* wider kernel — hooks (`kinddef_conflict` / `parse_error` events), the
|
|
27
|
+
* `_readers` list (the rescan return gate), the generic reader/writer wiring,
|
|
28
|
+
* the `_genericsResolved` flag, and the `_loadingExtOwner` alias-owner context —
|
|
29
|
+
* route through a NARROW `RegistryHost` back-ref (the anti-cosmetic rule, spec
|
|
30
|
+
* §3.1): the kernel satisfies it structurally. View-only registries pass no host
|
|
31
|
+
* and use only the lookup surface / pure functions.
|
|
32
|
+
*
|
|
33
|
+
* One registry per kernel, shared across `withTenant` shallow copies (Kinds are
|
|
34
|
+
* global — registered once at boot on the base kernel).
|
|
35
|
+
*
|
|
36
|
+
* The map is keyed `${apiVersion}\0${kind}` — the same shape held by both the
|
|
37
|
+
* Kernel and the ManifestInstance.
|
|
38
|
+
*/
|
|
39
|
+
import { fileURLToPath } from "node:url";
|
|
40
|
+
import { dirname, basename, join } from "node:path";
|
|
41
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
42
|
+
import { KindRegistrationError } from "./errors.js";
|
|
43
|
+
import { SD } from "./protocols.js";
|
|
44
|
+
import { DeclarativeKindPort } from "./meta.js";
|
|
45
|
+
import { documentHash } from "./lock.js";
|
|
46
|
+
import { KindDefinitionSchema, KIND_DEFINITION_API_VERSION, KIND_DEFINITION_KIND, } from "./models.js";
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Kind documentation resolution (Py twin: `kind_registry._load_kind_docs`).
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a kind's prose documentation.
|
|
52
|
+
*
|
|
53
|
+
* Resolution order:
|
|
54
|
+
* 1. DOCS-<KindName>.md next to the extension's source file
|
|
55
|
+
* (inside `<basename>/`, where basename is the extension .ts stem)
|
|
56
|
+
* 2. DOCS.md in that same directory
|
|
57
|
+
* 3. The `docs` attribute on the KindPort
|
|
58
|
+
* 4. null
|
|
59
|
+
*
|
|
60
|
+
* Browser consumers (Tauri Studio / Vite) stub node:fs/path/url via
|
|
61
|
+
* `nodeStubPlugin` in vite.config.ts — filesystem reads return empty and
|
|
62
|
+
* the loader transparently falls through to `kp.docs`.
|
|
63
|
+
*/
|
|
64
|
+
export function loadKindDocs(kp) {
|
|
65
|
+
const sourceUrl = kp._sourceUrl;
|
|
66
|
+
if (sourceUrl) {
|
|
67
|
+
try {
|
|
68
|
+
const file = fileURLToPath(sourceUrl);
|
|
69
|
+
const dir = dirname(file);
|
|
70
|
+
const stem = basename(file).replace(/\.[tj]s$/, "");
|
|
71
|
+
const extDir = join(dir, stem);
|
|
72
|
+
const candidates = [join(extDir, `DOCS-${kp.kind}.md`), join(extDir, "DOCS.md")];
|
|
73
|
+
for (const candidate of candidates) {
|
|
74
|
+
if (existsSync(candidate)) {
|
|
75
|
+
const text = readFileSync(candidate, "utf-8").trim();
|
|
76
|
+
if (text)
|
|
77
|
+
return text;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// fall through to docs attr
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return kp.docs ?? null;
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// s-alias-generated-not-typed — alias generation + canonical dep_filter
|
|
89
|
+
// resolution. Aliases used to be hand-typed strings on every Kind class
|
|
90
|
+
// (~46 divergences from the <owner>-<kebab(kind)> convention + one
|
|
91
|
+
// recorded bug: "policy-layer" reversed/truncated). New Kinds OMIT the
|
|
92
|
+
// alias and get it generated; legacy aliases stay untouched (live wire
|
|
93
|
+
// format in dep_filters / Mustache / LayerPolicy docs). Py twin:
|
|
94
|
+
// dna.kernel.kind_registry.
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
/** CamelCase kind name → kebab-case: EvalCase → eval-case, ADR → adr,
|
|
97
|
+
* HTMLThing → html-thing. */
|
|
98
|
+
export function kebabKindName(kind) {
|
|
99
|
+
let s = kind.replace(/(?<=[a-z0-9])(?=[A-Z])/g, "-");
|
|
100
|
+
s = s.replace(/(?<=[A-Z])(?=[A-Z][a-z])/g, "-");
|
|
101
|
+
return s.toLowerCase();
|
|
102
|
+
}
|
|
103
|
+
/** The canonical alias for a Kind: `<owner>-<kebab(kind)>`. */
|
|
104
|
+
export function generateAlias(owner, kind) {
|
|
105
|
+
return `${owner}-${kebabKindName(kind)}`;
|
|
106
|
+
}
|
|
107
|
+
/** Ratchet (shrink-only): every builtin CLASS Kind that still hand-types
|
|
108
|
+
* its alias — the live wire format that CANNOT be renamed without a doc
|
|
109
|
+
* migration. New Kinds must OMIT the alias (generation). Entries leave
|
|
110
|
+
* as classes migrate to generation/descriptors — NEVER add one
|
|
111
|
+
* (tests/alias-generation.test.ts, ratchet test). Py twin:
|
|
112
|
+
* dna.kernel.kind_registry.EXPLICIT_ALIAS_ALLOWLIST — the TS set
|
|
113
|
+
* excludes the Py-only extensions (gaia-*, collab-comment) and includes
|
|
114
|
+
* every TS class port, per kind-registry-parity.json. */
|
|
115
|
+
export const EXPLICIT_ALIAS_ALLOWLIST = new Set([
|
|
116
|
+
// helix
|
|
117
|
+
"helix-genome", "helix-agent", "helix-actor",
|
|
118
|
+
"helix-usecase", "helix-tool", "policy-layer-policy",
|
|
119
|
+
"helix-canvas",
|
|
120
|
+
"helix-setting", "helix-theme", "helix-user-profile",
|
|
121
|
+
// sdlc (classes; descriptors are outside the ratchet)
|
|
122
|
+
"sdlc-roadmap", "sdlc-epic", "sdlc-feature", "sdlc-story",
|
|
123
|
+
"sdlc-issue", "sdlc-bug", "sdlc-task", "sdlc-spike",
|
|
124
|
+
"sdlc-initiative", "sdlc-spec", "sdlc-plan", "sdlc-agent-session",
|
|
125
|
+
"sdlc-reference",
|
|
126
|
+
// eval
|
|
127
|
+
// blocks
|
|
128
|
+
// single-kind extensions
|
|
129
|
+
"agentskills-skill", "soulspec-soul", "agentsmd-agent",
|
|
130
|
+
"guardrails-guardrail", "helix-hook",
|
|
131
|
+
"helix-safety-policy", "presidio-recognizer",
|
|
132
|
+
"kinddef-kinddefinition",
|
|
133
|
+
"evidence-policy", "federation-mcp",
|
|
134
|
+
// s-automation-trio-extinction: jobs-jobtype / hooktype-hooktype /
|
|
135
|
+
// scheduletype-scheduletype extintos (unificados no Kind Automation).
|
|
136
|
+
"lesson-lesson",
|
|
137
|
+
"tenant-tenant", "tenant-membership",
|
|
138
|
+
"audit-userroleassignment",
|
|
139
|
+
"testkit-test-guide", "testkit-test-run",
|
|
140
|
+
]);
|
|
141
|
+
/** i-195 — kind names allowed to exist under MULTIPLE apiVersions in the
|
|
142
|
+
* extension/builtin funnel. SHRINK-ONLY ratchet: the Reference pair
|
|
143
|
+
* (github.com/ruinosus/dna/research/v1 + github.com/ruinosus/dna/sdlc/v1) predates the guard and is scheduled
|
|
144
|
+
* to be merged by the Reference-family unification follow-up; when that
|
|
145
|
+
* lands, empty this set. NEVER add a name here — rename the new Kind
|
|
146
|
+
* instead (bare-name lookups become ambiguous the moment two
|
|
147
|
+
* apiVersions share a kind name). Py twin:
|
|
148
|
+
* dna.kernel.kind_registry.KIND_NAME_COLLISION_ALLOWLIST. */
|
|
149
|
+
export const KIND_NAME_COLLISION_ALLOWLIST = new Set(["Reference"]);
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Pure lookup functions — shared with registry VIEWS (CompositionEngine /
|
|
152
|
+
// nav_kernel / the ManifestInstance) that wrap a kinds map WITHOUT a class
|
|
153
|
+
// instance. The KindRegistry methods below delegate to these so there is ONE
|
|
154
|
+
// canonical implementation. Py twin: `KindRegistry.port_for` /
|
|
155
|
+
// `KindRegistry.resolve_dep_filter_target`.
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
/**
|
|
158
|
+
* Lookup a registered KindPort by kind name (case-sensitive) over `kinds`.
|
|
159
|
+
* With `apiVersion` the lookup is EXACT on (apiVersion, kind) — bare
|
|
160
|
+
* lookups on a name shared by multiple apiVersions resolve
|
|
161
|
+
* extension-first then registration order (i-195, Py twin:
|
|
162
|
+
* `KindRegistry.port_for`).
|
|
163
|
+
*/
|
|
164
|
+
export function kindPortForOver(kinds, kind, apiVersion) {
|
|
165
|
+
if (apiVersion !== undefined) {
|
|
166
|
+
return kinds.get(`${apiVersion}\0${kind}`) ?? null;
|
|
167
|
+
}
|
|
168
|
+
const matches = [];
|
|
169
|
+
for (const kp of kinds.values()) {
|
|
170
|
+
if (kp.kind === kind)
|
|
171
|
+
matches.push(kp);
|
|
172
|
+
}
|
|
173
|
+
if (matches.length === 0)
|
|
174
|
+
return null;
|
|
175
|
+
if (matches.length > 1) {
|
|
176
|
+
// per-scope DeclarativeKindPorts carry __declarative__ WITHOUT
|
|
177
|
+
// __builtin_descriptor__; extension classes + builtin descriptors
|
|
178
|
+
// must win the bare name (i-195).
|
|
179
|
+
const extensionFirst = matches.filter((kp) => {
|
|
180
|
+
const p = kp;
|
|
181
|
+
return p.__declarative__ !== true || p.__builtin_descriptor__ === true;
|
|
182
|
+
});
|
|
183
|
+
if (extensionFirst.length > 0)
|
|
184
|
+
return extensionFirst[0];
|
|
185
|
+
}
|
|
186
|
+
return matches[0];
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Canonical dep_filter target resolution (s-alias-generated-not-typed).
|
|
190
|
+
*
|
|
191
|
+
* The CONTRACT is alias-valued dep_filters (`"soulspec-soul"`). The
|
|
192
|
+
* legacy `"kind=<Name>"` format resolves through a DEPRECATED shim so
|
|
193
|
+
* per-scope KindDefinition docs keep working (builtin extensions must be
|
|
194
|
+
* alias-pure — `validateDepFilters` rejects `kind=` there). Since
|
|
195
|
+
* s-unify-composition-subsystems this is THE resolver for every
|
|
196
|
+
* dep_filter reader — `validateRefs` / `mi.composition` and the Kernel.
|
|
197
|
+
* Py twin: `KindRegistry.resolve_dep_filter_target`.
|
|
198
|
+
*/
|
|
199
|
+
export function resolveDepFilterTargetOver(kinds, value) {
|
|
200
|
+
if (value.startsWith("kind=")) {
|
|
201
|
+
console.warn(`dep_filter value ${JSON.stringify(value)} uses the legacy 'kind=' ` +
|
|
202
|
+
`format — use the target Kind's alias instead ` +
|
|
203
|
+
`(s-alias-generated-not-typed).`);
|
|
204
|
+
return kindPortForOver(kinds, value.slice("kind=".length));
|
|
205
|
+
}
|
|
206
|
+
for (const kp of kinds.values()) {
|
|
207
|
+
if (kp.alias === value)
|
|
208
|
+
return kp;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Holds the registered KindPorts + the lookups + the registration funnel.
|
|
214
|
+
*/
|
|
215
|
+
export class KindRegistry {
|
|
216
|
+
/** Key: "apiVersion\0kind" for uniqueness. OWNED here; the kernel's
|
|
217
|
+
* `_kinds` getter proxies to it. */
|
|
218
|
+
kinds;
|
|
219
|
+
/** The NARROW `RegistryHost` back-ref used ONLY by the registration
|
|
220
|
+
* funnel (hooks fan-out, the `_readers` rescan gate, generic
|
|
221
|
+
* reader/writer wiring, `_genericsResolved`, the `_loadingExtOwner`
|
|
222
|
+
* alias-owner context). Only read at registration time (boot) — so the
|
|
223
|
+
* kernel may pass `this` before its `hooks`/`_readers` are fully wired.
|
|
224
|
+
* View-only registries pass `null` and never call `register*`. */
|
|
225
|
+
_host;
|
|
226
|
+
constructor(kinds, host) {
|
|
227
|
+
this.kinds = kinds ?? new Map();
|
|
228
|
+
this._host = host ?? null;
|
|
229
|
+
}
|
|
230
|
+
// -- Lookup ---------------------------------------------------------------
|
|
231
|
+
/** Lookup a registered KindPort by kind name (case-sensitive). With
|
|
232
|
+
* apiVersion the lookup is EXACT on (apiVersion, kind); bare lookups on
|
|
233
|
+
* an ambiguous name resolve extension-first then registration order
|
|
234
|
+
* (i-195). Py twin: `KindRegistry.port_for`. */
|
|
235
|
+
portFor(kind, apiVersion) {
|
|
236
|
+
return kindPortForOver(this.kinds, kind, apiVersion);
|
|
237
|
+
}
|
|
238
|
+
/** All registered KindPorts. Order matches registration. */
|
|
239
|
+
allPorts() {
|
|
240
|
+
return Array.from(this.kinds.values());
|
|
241
|
+
}
|
|
242
|
+
/** Resolve a kind name to its globally-unique alias (`<owner>-<kind>`).
|
|
243
|
+
* Falls back to `kind.toLowerCase()` when no registered port provides
|
|
244
|
+
* one. Py twin: `KindRegistry.alias_for`. */
|
|
245
|
+
aliasFor(kind, apiVersion) {
|
|
246
|
+
const port = this.portFor(kind, apiVersion);
|
|
247
|
+
const alias = port ? port.alias : null;
|
|
248
|
+
return alias ? alias : kind.toLowerCase();
|
|
249
|
+
}
|
|
250
|
+
/** Return the storage container directory for a kind, or null. */
|
|
251
|
+
containerFor(kindName) {
|
|
252
|
+
const kp = this.portFor(kindName);
|
|
253
|
+
if (!kp)
|
|
254
|
+
return null;
|
|
255
|
+
return kp.storage?.container ?? null;
|
|
256
|
+
}
|
|
257
|
+
/** Return the StorageDescriptor for a kind, or null if unknown. */
|
|
258
|
+
storageFor(kindName) {
|
|
259
|
+
const kp = this.portFor(kindName);
|
|
260
|
+
return kp ? (kp.storage ?? null) : null;
|
|
261
|
+
}
|
|
262
|
+
/** Return the kind name whose StorageDescriptor.container matches.
|
|
263
|
+
* null for empty container (ROOT kinds) or unregistered containers. */
|
|
264
|
+
byContainer(container) {
|
|
265
|
+
if (!container)
|
|
266
|
+
return null;
|
|
267
|
+
for (const kp of this.kinds.values()) {
|
|
268
|
+
const sd = kp.storage ?? null;
|
|
269
|
+
if (sd !== null && sd.container === container)
|
|
270
|
+
return kp.kind;
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
/** Canonical dep_filter target resolution (s-alias-generated-not-typed).
|
|
275
|
+
* Delegates to the shared `resolveDepFilterTargetOver`. Py twin:
|
|
276
|
+
* `KindRegistry.resolve_dep_filter_target`. */
|
|
277
|
+
resolveDepFilterTarget(value) {
|
|
278
|
+
return resolveDepFilterTargetOver(this.kinds, value);
|
|
279
|
+
}
|
|
280
|
+
/** Summary dict for a registered kind, including resolved docs. Py twin:
|
|
281
|
+
* `KindRegistry.describe`. */
|
|
282
|
+
describe(kindName) {
|
|
283
|
+
for (const kp of this.kinds.values()) {
|
|
284
|
+
if (kp.kind === kindName) {
|
|
285
|
+
const resolved = kp._resolvedDocs;
|
|
286
|
+
return {
|
|
287
|
+
kind: kp.kind,
|
|
288
|
+
alias: kp.alias,
|
|
289
|
+
apiVersion: kp.apiVersion,
|
|
290
|
+
isRoot: kp.isRoot,
|
|
291
|
+
isPromptTarget: kp.isPromptTarget,
|
|
292
|
+
docs: resolved ?? kp.docs ?? null,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
/** s-alias-generated-not-typed — every dep_filter target of an
|
|
299
|
+
* EXTENSION-registered Kind must resolve to a registered alias.
|
|
300
|
+
*
|
|
301
|
+
* Aliases are the wire key of dep_filters / Mustache sections /
|
|
302
|
+
* LayerPolicy — a typo used to degrade the prompt SILENTLY (the dep
|
|
303
|
+
* just vanished from the context, warning buried in logs). Called at
|
|
304
|
+
* the end of `loadBuiltins()` (the TS twin of `Kernel.auto()`).
|
|
305
|
+
*
|
|
306
|
+
* - Extension/builtin port with an unknown alias OR the legacy
|
|
307
|
+
* `kind=` format → `KindRegistrationError` (boot fails loud).
|
|
308
|
+
* - Per-scope declarative ports (user KindDefinition docs) only WARN
|
|
309
|
+
* — user docs never take the boot down (same posture as the
|
|
310
|
+
* parse_error / plane-lint funnels).
|
|
311
|
+
* Py twin: `KindRegistry.validate_dep_filters`. */
|
|
312
|
+
validateDepFilters() {
|
|
313
|
+
const problems = [];
|
|
314
|
+
for (const kp of this.allPorts()) {
|
|
315
|
+
let filters;
|
|
316
|
+
try {
|
|
317
|
+
filters = kp.depFilters();
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// port quebrado não derruba os demais
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (!filters)
|
|
324
|
+
continue;
|
|
325
|
+
const p = kp;
|
|
326
|
+
const isDeclarative = p.__declarative__ === true && p.__builtin_descriptor__ !== true;
|
|
327
|
+
for (const [field, value] of Object.entries(filters)) {
|
|
328
|
+
if (typeof value !== "string")
|
|
329
|
+
continue;
|
|
330
|
+
let msg;
|
|
331
|
+
if (value.startsWith("kind=")) {
|
|
332
|
+
msg =
|
|
333
|
+
`${kp.kind}.depFilters[${JSON.stringify(field)}] uses the ` +
|
|
334
|
+
`legacy 'kind=' format (${JSON.stringify(value)}) — use the ` +
|
|
335
|
+
`target Kind's alias (builtin extensions are alias-pure).`;
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
// Polymorphic refs (WorkflowEvent.ref) declare a PIPE-UNION of
|
|
339
|
+
// aliases — validate each term.
|
|
340
|
+
const unknown = value
|
|
341
|
+
.split("|")
|
|
342
|
+
.filter((part) => this.resolveDepFilterTarget(part) === null);
|
|
343
|
+
if (unknown.length === 0)
|
|
344
|
+
continue;
|
|
345
|
+
msg =
|
|
346
|
+
`${kp.kind}.depFilters[${JSON.stringify(field)}] points at ` +
|
|
347
|
+
`unknown alias(es) ${JSON.stringify(unknown)} — the dep would ` +
|
|
348
|
+
`silently vanish from prompts/composition.`;
|
|
349
|
+
}
|
|
350
|
+
if (isDeclarative) {
|
|
351
|
+
console.warn(`[kernel] dep_filter (per-scope): ${msg}`);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
problems.push(msg);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (problems.length > 0) {
|
|
359
|
+
throw new KindRegistrationError("dep_filter validation failed (s-alias-generated-not-typed):\n " +
|
|
360
|
+
problems.join("\n "));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// -- Registration funnel (Fase 3) -----------------------------------------
|
|
364
|
+
/** H1 registration funnel — moved verbatim from `Kernel.kind()`. The
|
|
365
|
+
* kernel keeps `kind()` as a thin facade delegating here. Py twin:
|
|
366
|
+
* `KindRegistry.register_kind`. */
|
|
367
|
+
registerKind(k) {
|
|
368
|
+
// H1 — Boot-time validation (interface + uniqueness + marker collision).
|
|
369
|
+
// Catches the failure modes that previously surfaced at runtime as
|
|
370
|
+
// silent overwrites or first-match-wins scanner bugs:
|
|
371
|
+
// 1. Object doesn't satisfy KindPort interface → registration error
|
|
372
|
+
// 2. Duplicate (apiVersion, kind) tuple → registration error
|
|
373
|
+
// 3. Duplicate alias across registered Kinds → registration error
|
|
374
|
+
// 4. BUNDLE-pattern (container, marker) clash → registration error
|
|
375
|
+
const required = [
|
|
376
|
+
"apiVersion", "kind", "alias", "isRoot", "isPromptTarget",
|
|
377
|
+
"promptTargetPriority", "flattenInContext", "storage",
|
|
378
|
+
];
|
|
379
|
+
for (const attr of required) {
|
|
380
|
+
if (!(attr in k)) {
|
|
381
|
+
throw new KindRegistrationError(`Kind ${k?.constructor?.name ?? typeof k} ` +
|
|
382
|
+
`does not satisfy KindPort interface (missing ${attr}). See ` +
|
|
383
|
+
`typescript/src/kernel/protocols.ts.`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const requiredMethods = [
|
|
387
|
+
"depFilters", "getDefaultAgentName", "getLayerPolicies",
|
|
388
|
+
"parse", "describe", "summary", "promptTemplate",
|
|
389
|
+
];
|
|
390
|
+
for (const m of requiredMethods) {
|
|
391
|
+
if (typeof k[m] !== "function") {
|
|
392
|
+
throw new KindRegistrationError(`Kind ${k?.constructor?.name ?? typeof k} ` +
|
|
393
|
+
`does not satisfy KindPort interface (missing ${m}() method). See ` +
|
|
394
|
+
`typescript/src/kernel/protocols.ts.`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// s-alias-generated-not-typed — Kind sem alias declarado ganha o
|
|
398
|
+
// gerado <owner>-<kebab(kind)>. Owner: attr explícito no port →
|
|
399
|
+
// contexto da Extension sendo carregada (kernel.load) → 1º token
|
|
400
|
+
// do apiVersion. Aliases legados digitados ficam intocados (wire
|
|
401
|
+
// format vivo); o ratchet EXPLICIT_ALIAS_ALLOWLIST impede Kind
|
|
402
|
+
// NOVO de digitar um. Py twin: Kernel.kind().
|
|
403
|
+
if (!k.alias) {
|
|
404
|
+
const owner = k.aliasOwner ??
|
|
405
|
+
this._host?._loadingExtOwner ??
|
|
406
|
+
k.apiVersion.split(".")[0].split("/")[0];
|
|
407
|
+
k.alias = generateAlias(owner, k.kind);
|
|
408
|
+
k.__alias_generated__ = true;
|
|
409
|
+
}
|
|
410
|
+
// Two-planes lint (spec D1) — extracted to a helper (F3 spec D3) so
|
|
411
|
+
// the per-scope KindDefinition funnel (registerKindDefinitions)
|
|
412
|
+
// runs the SAME validation.
|
|
413
|
+
KindRegistry._lintPlane(k);
|
|
414
|
+
const key = `${k.apiVersion}\0${k.kind}`;
|
|
415
|
+
if (this.kinds.has(key)) {
|
|
416
|
+
const existing = this.kinds.get(key);
|
|
417
|
+
// F3 (spec D3) — declarative ports are ALL the same class
|
|
418
|
+
// (DeclarativeKindPort), so the constructor check below would
|
|
419
|
+
// silently no-op two DIFFERENT descriptors claiming the same key.
|
|
420
|
+
// Real identity on the declarative path is the descriptor digest:
|
|
421
|
+
// same digest → idempotent no-op; different digest → error.
|
|
422
|
+
const existingDecl = existing.__declarative__ === true;
|
|
423
|
+
const newDecl = k.__declarative__ === true;
|
|
424
|
+
if (existingDecl && newDecl) {
|
|
425
|
+
const existingDigest = existing.__descriptor_digest__ ?? null;
|
|
426
|
+
const newDigest = k.__descriptor_digest__ ?? null;
|
|
427
|
+
if (existingDigest === newDigest) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
throw new KindRegistrationError(`Kind (${JSON.stringify(k.apiVersion)}, ${JSON.stringify(k.kind)}) ` +
|
|
431
|
+
`already registered from a DIFFERENT descriptor (existing alias ` +
|
|
432
|
+
`${JSON.stringify(existing.alias)}, new alias ${JSON.stringify(k.alias)}). ` +
|
|
433
|
+
`Two descriptors cannot claim the same (apiVersion, kind) key — ` +
|
|
434
|
+
`pick a distinct apiVersion namespace.`);
|
|
435
|
+
}
|
|
436
|
+
// Idempotent re-registration: same class re-registering is a
|
|
437
|
+
// silent no-op + debug log. A *different* class trying to claim
|
|
438
|
+
// the same key IS still a registration error.
|
|
439
|
+
if (existing.constructor === k.constructor) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
throw new KindRegistrationError(`Kind (${JSON.stringify(k.apiVersion)}, ${JSON.stringify(k.kind)}) ` +
|
|
443
|
+
`already registered by ${existing?.constructor?.name}; ` +
|
|
444
|
+
`refusing to overwrite with ` +
|
|
445
|
+
`${k?.constructor?.name}. ` +
|
|
446
|
+
`Two extensions cannot share the same (apiVersion, kind) pair.`);
|
|
447
|
+
}
|
|
448
|
+
if (k.alias) {
|
|
449
|
+
for (const [existingKey, existingKind] of this.kinds) {
|
|
450
|
+
if (existingKind.alias === k.alias) {
|
|
451
|
+
throw new KindRegistrationError(`Kind alias ${JSON.stringify(k.alias)} already registered by ` +
|
|
452
|
+
`${existingKind?.constructor?.name} ` +
|
|
453
|
+
`(${existingKey.replace("\0", ", ")}); refusing to register ` +
|
|
454
|
+
`${k?.constructor?.name}.`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// 6. Kind-NAME collision across apiVersions (i-195, Py twin in
|
|
459
|
+
// Kernel.kind()). Bare-name lookups become ambiguous the moment two
|
|
460
|
+
// apiVersions share a kind name — new extension Kinds must pick a
|
|
461
|
+
// unique name; the legacy Reference pair is allowlisted (shrink-only
|
|
462
|
+
// ratchet, emptied by the Reference-family merge). Collisions where
|
|
463
|
+
// the EXISTING port is a per-scope declarative shadow don't block
|
|
464
|
+
// the extension from claiming its canonical name.
|
|
465
|
+
if (!KIND_NAME_COLLISION_ALLOWLIST.has(k.kind)) {
|
|
466
|
+
for (const [, existingKind] of this.kinds) {
|
|
467
|
+
const p = existingKind;
|
|
468
|
+
const isExtensionPort = p.__declarative__ !== true || p.__builtin_descriptor__ === true;
|
|
469
|
+
if (existingKind.kind === k.kind &&
|
|
470
|
+
existingKind.apiVersion !== k.apiVersion &&
|
|
471
|
+
isExtensionPort) {
|
|
472
|
+
throw new KindRegistrationError(`Kind NAME ${JSON.stringify(k.kind)} already registered under ` +
|
|
473
|
+
`apiVersion ${JSON.stringify(existingKind.apiVersion)} (alias ` +
|
|
474
|
+
`${JSON.stringify(existingKind.alias)}); refusing ` +
|
|
475
|
+
`${JSON.stringify(k.apiVersion)}. Two apiVersions sharing a ` +
|
|
476
|
+
`kind name makes every bare-name lookup ambiguous — pick a ` +
|
|
477
|
+
`distinct kind name (i-195).`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const sd = k.storage;
|
|
482
|
+
if (sd && sd.pattern === "bundle") {
|
|
483
|
+
const newPair = `${sd.container}\0${sd.marker}`;
|
|
484
|
+
const newSharedOk = Boolean(k.markerSharedAllowed);
|
|
485
|
+
for (const [existingKey, existingKind] of this.kinds) {
|
|
486
|
+
const existingSd = existingKind.storage;
|
|
487
|
+
if (!existingSd || existingSd.pattern !== "bundle")
|
|
488
|
+
continue;
|
|
489
|
+
const existingPair = `${existingSd.container}\0${existingSd.marker}`;
|
|
490
|
+
if (existingPair !== newPair)
|
|
491
|
+
continue;
|
|
492
|
+
// H1 — explicit opt-in: only allow shared marker if BOTH
|
|
493
|
+
// colliding Kinds set markerSharedAllowed = true.
|
|
494
|
+
const existingSharedOk = Boolean(existingKind.markerSharedAllowed);
|
|
495
|
+
if (newSharedOk && existingSharedOk) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
throw new KindRegistrationError(`BUNDLE storage (container=${JSON.stringify(sd.container)}, ` +
|
|
499
|
+
`marker=${JSON.stringify(sd.marker)}) already registered by ` +
|
|
500
|
+
`${existingKind?.constructor?.name} ` +
|
|
501
|
+
`(${existingKey.replace("\0", ", ")}); refusing to register ` +
|
|
502
|
+
`${k?.constructor?.name}. ` +
|
|
503
|
+
`Pick a unique container OR marker — OR set ` +
|
|
504
|
+
`markerSharedAllowed = true on BOTH Kinds AND ensure their ` +
|
|
505
|
+
`Reader.detect() implementations disambiguate at read time.`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
this.kinds.set(key, k);
|
|
509
|
+
if (this._host)
|
|
510
|
+
this._host._genericsResolved = false;
|
|
511
|
+
try {
|
|
512
|
+
k._resolvedDocs = loadKindDocs(k);
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
k._resolvedDocs = k.docs ?? null;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/** Two-planes lint (spec D1) — plane is explicit and validated, never
|
|
519
|
+
* derived. A "record" Kind cannot carry any composition signal;
|
|
520
|
+
* contradictions fail registration loudly instead of silently
|
|
521
|
+
* mis-routing the write path.
|
|
522
|
+
*
|
|
523
|
+
* F3 (spec D3): extracted from `registerKind()` so BOTH funnels run it —
|
|
524
|
+
* `registerKind()` (extension classes + builtin descriptors) throws;
|
|
525
|
+
* `registerKindDefinitions` (per-scope) catches → warn + skip
|
|
526
|
+
* (per-scope docs never take the boot down). Mirrors Python
|
|
527
|
+
* KindRegistry._lint_plane. */
|
|
528
|
+
static _lintPlane(k) {
|
|
529
|
+
const plane = k.plane ?? "composition";
|
|
530
|
+
if (plane !== "composition" && plane !== "record") {
|
|
531
|
+
throw new KindRegistrationError(`Kind ${k.kind} has invalid plane=${JSON.stringify(plane)}; ` +
|
|
532
|
+
`expected 'composition' or 'record'.`);
|
|
533
|
+
}
|
|
534
|
+
if (plane === "record") {
|
|
535
|
+
const contradictions = [];
|
|
536
|
+
if (k.isPromptTarget)
|
|
537
|
+
contradictions.push("isPromptTarget=true");
|
|
538
|
+
if (k.flattenInContext)
|
|
539
|
+
contradictions.push("flattenInContext=true");
|
|
540
|
+
if (k.isSchemaAffecting) {
|
|
541
|
+
contradictions.push("isSchemaAffecting=true");
|
|
542
|
+
}
|
|
543
|
+
if (k.isRoot)
|
|
544
|
+
contradictions.push("storage.pattern==root");
|
|
545
|
+
if (contradictions.length > 0) {
|
|
546
|
+
throw new KindRegistrationError(`Kind ${k.kind} declares plane="record" but carries composition ` +
|
|
547
|
+
`signals: ${contradictions.join(", ")}. Records never compose ` +
|
|
548
|
+
`into agent prompts — drop the signal or remove plane="record".`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/** F3 (spec D3): register a BUILTIN Kind from a KindDefinition
|
|
553
|
+
* descriptor (`kinds/*.kind.yaml` package data). Same format + same
|
|
554
|
+
* funnel as everything else. Py twin:
|
|
555
|
+
* `KindRegistry.register_from_descriptor`. */
|
|
556
|
+
registerFromDescriptor(raw) {
|
|
557
|
+
const typed = KindDefinitionSchema.parse(raw);
|
|
558
|
+
const port = DeclarativeKindPort.fromTyped(typed);
|
|
559
|
+
port.__builtin_descriptor__ = true;
|
|
560
|
+
port.__descriptor_digest__ =
|
|
561
|
+
documentHash(raw.spec ?? {});
|
|
562
|
+
this.registerKind(port);
|
|
563
|
+
// registerKind() no-ops on an idempotent re-register — hand back
|
|
564
|
+
// whatever is actually registered for the key.
|
|
565
|
+
return this.kinds.get(`${port.apiVersion}\0${port.kind}`);
|
|
566
|
+
}
|
|
567
|
+
/** Phase 1 of 2-phase loading: parse KindDefinition docs + register
|
|
568
|
+
* synthetic DeclarativeKindPorts. Extension-registered kinds win on
|
|
569
|
+
* conflict. Returns true iff new BUNDLE readers were added (the rescan
|
|
570
|
+
* gate). Py twin: `KindRegistry.register_kind_definitions`. */
|
|
571
|
+
registerKindDefinitions(rawDocs) {
|
|
572
|
+
const host = this._host;
|
|
573
|
+
const readersBefore = host._readers.length;
|
|
574
|
+
let registered = false;
|
|
575
|
+
for (const raw of rawDocs) {
|
|
576
|
+
if (raw.apiVersion !== KIND_DEFINITION_API_VERSION)
|
|
577
|
+
continue;
|
|
578
|
+
if (raw.kind !== KIND_DEFINITION_KIND)
|
|
579
|
+
continue;
|
|
580
|
+
let typed;
|
|
581
|
+
try {
|
|
582
|
+
typed = KindDefinitionSchema.parse(raw);
|
|
583
|
+
}
|
|
584
|
+
catch (e) {
|
|
585
|
+
console.warn(`Failed to parse KindDefinition: ${e}`);
|
|
586
|
+
if (host.hooks.has("parse_error")) {
|
|
587
|
+
host.hooks.emit("parse_error", {
|
|
588
|
+
scope: "",
|
|
589
|
+
kind: KIND_DEFINITION_KIND,
|
|
590
|
+
name: raw.metadata?.name ?? "",
|
|
591
|
+
data: { error: String(e) },
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
// Use typed.spec.<field> here: we just Zod-validated the KindDefinition
|
|
597
|
+
// so the typed model is authoritative. The readSpec* helpers exist for
|
|
598
|
+
// code that operates across kinds without knowing the type.
|
|
599
|
+
const key = `${typed.spec.target_api_version}\0${typed.spec.target_kind}`;
|
|
600
|
+
if (this.kinds.has(key)) {
|
|
601
|
+
const existing = this.kinds.get(key);
|
|
602
|
+
const isDeclarative = existing.__declarative__ === true;
|
|
603
|
+
const isBuiltinDescriptor = existing.__builtin_descriptor__ === true;
|
|
604
|
+
if (!isDeclarative || isBuiltinDescriptor) {
|
|
605
|
+
// Extension-registered kind wins on conflict — and so does a
|
|
606
|
+
// BUILTIN descriptor (F3 spec D3: builtin descriptors are
|
|
607
|
+
// extension-registered Kinds that happen to be declarative).
|
|
608
|
+
// PARITY FIX: before F3 this branch fell through for any
|
|
609
|
+
// declarative `existing` and OVERWROTE it — a per-scope
|
|
610
|
+
// KindDefinition would silently replace a builtin (Python
|
|
611
|
+
// skipped instead). Now: skip + warn + kinddef_conflict event.
|
|
612
|
+
console.warn(`KindDefinition ${typed.spec.target_api_version}/${typed.spec.target_kind} ` +
|
|
613
|
+
`conflicts with ${isBuiltinDescriptor ? "builtin-descriptor" : "extension-registered"} kind; ` +
|
|
614
|
+
`keeping it and skipping the per-scope declarative port.`);
|
|
615
|
+
host.hooks.emit("kinddef_conflict", {
|
|
616
|
+
scope: "",
|
|
617
|
+
kind: typed.spec.target_kind,
|
|
618
|
+
name: typed.metadata.name,
|
|
619
|
+
data: {
|
|
620
|
+
apiVersion: typed.spec.target_api_version,
|
|
621
|
+
reason: isBuiltinDescriptor ? "builtin_descriptor_wins" : "extension_wins",
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
let port;
|
|
628
|
+
try {
|
|
629
|
+
port = DeclarativeKindPort.fromTyped(typed);
|
|
630
|
+
}
|
|
631
|
+
catch (e) {
|
|
632
|
+
console.error(`Failed to synthesize DeclarativeKindPort for ${typed.spec.target_api_version}/${typed.spec.target_kind}: ${e}`);
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
// F3 (spec D3): the per-scope funnel writes straight into
|
|
636
|
+
// this.kinds (bypassing registerKind()), so the F1 plane lint never
|
|
637
|
+
// ran here. Run the SAME helper — but warn + skip instead of
|
|
638
|
+
// throwing: per-scope docs never take the boot down (same contract
|
|
639
|
+
// as the parse_error path above).
|
|
640
|
+
try {
|
|
641
|
+
KindRegistry._lintPlane(port);
|
|
642
|
+
}
|
|
643
|
+
catch (e) {
|
|
644
|
+
console.warn(`KindDefinition ${typed.spec.target_api_version}/${typed.spec.target_kind} ` +
|
|
645
|
+
`failed the plane lint: ${e} — skipping registration.`);
|
|
646
|
+
if (host.hooks.has("parse_error")) {
|
|
647
|
+
host.hooks.emit("parse_error", {
|
|
648
|
+
scope: "",
|
|
649
|
+
kind: KIND_DEFINITION_KIND,
|
|
650
|
+
name: typed.metadata.name,
|
|
651
|
+
data: { error: String(e) },
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
this.kinds.set(key, port);
|
|
657
|
+
host._genericsResolved = false;
|
|
658
|
+
registered = true;
|
|
659
|
+
try {
|
|
660
|
+
port._resolvedDocs = loadKindDocs(port);
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
port._resolvedDocs = port.docs ?? null;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
// Re-resolve generic readers/writers for newly declared BUNDLE kinds
|
|
667
|
+
host._ensureGenericReadersWriters();
|
|
668
|
+
return registered && host._readers.length > readersBefore;
|
|
669
|
+
}
|
|
670
|
+
/** Register dynamic kinds from `Module.spec.custom_kinds`. Py twin:
|
|
671
|
+
* `KindRegistry.register_custom_kinds`. */
|
|
672
|
+
registerCustomKinds(manifest) {
|
|
673
|
+
const spec = manifest.spec ?? {};
|
|
674
|
+
const customKinds = spec.custom_kinds ?? [];
|
|
675
|
+
for (const ck of customKinds) {
|
|
676
|
+
const av = ck.apiVersion ?? "custom/v1";
|
|
677
|
+
const kn = ck.kind ?? "";
|
|
678
|
+
const alias = ck.alias ?? kn.toLowerCase();
|
|
679
|
+
if (!kn)
|
|
680
|
+
continue;
|
|
681
|
+
const key = `${av}\0${kn}`;
|
|
682
|
+
if (this.kinds.has(key))
|
|
683
|
+
continue;
|
|
684
|
+
this.kinds.set(key, KindRegistry._makeDynamicKind(av, kn, alias));
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
static _makeDynamicKind(av, kn, al) {
|
|
688
|
+
return {
|
|
689
|
+
apiVersion: av,
|
|
690
|
+
kind: kn,
|
|
691
|
+
alias: al,
|
|
692
|
+
origin: "custom",
|
|
693
|
+
isRoot: false,
|
|
694
|
+
isPromptTarget: false,
|
|
695
|
+
promptTargetPriority: 0,
|
|
696
|
+
flattenInContext: false,
|
|
697
|
+
isRuntimeArtifact: false,
|
|
698
|
+
storage: SD.yaml(""),
|
|
699
|
+
depFilters: () => null,
|
|
700
|
+
dependencies: () => null,
|
|
701
|
+
schema: () => null,
|
|
702
|
+
getDefaultAgentName: () => null,
|
|
703
|
+
getLayerPolicies: () => null,
|
|
704
|
+
parse: (raw) => raw,
|
|
705
|
+
describe: () => null,
|
|
706
|
+
summary: () => null,
|
|
707
|
+
promptTemplate: () => null,
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
}
|