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,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v3 Kernel Protocols — the 5 ports + shared types.
|
|
3
|
+
*
|
|
4
|
+
* Parity with Python dna.kernel.protocols is SURFACE-TRACKED, not
|
|
5
|
+
* assumed (s-dna-port-surface-parity): every port's member list — and every
|
|
6
|
+
* INTENTIONAL asymmetry, with its justification — lives in the shared
|
|
7
|
+
* fixture `tests/parity-fixtures/port-surface-parity.json`, enforced by
|
|
8
|
+
* `tests/port-surface-parity.test.ts` (via the keyof-bound PORT_SURFACE
|
|
9
|
+
* manifest in `./port-surface.ts`) and by the Python twin
|
|
10
|
+
* `packages/sdk-py/tests/test_port_surface_parity.py` (real Protocol
|
|
11
|
+
* introspection). Adding/removing a member on either side without updating
|
|
12
|
+
* the fixture turns the suites red.
|
|
13
|
+
*/
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Layer policy
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export const LayerPolicy = {
|
|
18
|
+
OPEN: "open",
|
|
19
|
+
RESTRICTED: "restricted",
|
|
20
|
+
LOCKED: "locked",
|
|
21
|
+
};
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Tenant scope (Kubernetes-style: each KindPort declares its scope)
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* Whether a Kind's documents belong to a tenant or are globally shared.
|
|
27
|
+
*
|
|
28
|
+
* Mirrors the Kubernetes CRD `scope: Namespaced | Cluster` model. Each
|
|
29
|
+
* KindPort declares its scope; the kernel enforces it on every write.
|
|
30
|
+
*
|
|
31
|
+
* - `TENANTED` (default): documents belong to one tenant. Writing
|
|
32
|
+
* requires a tenant arg; reading is filtered by tenant.
|
|
33
|
+
* Agent, EvalCase, EvalRun, AssessmentRun, Finding, etc.
|
|
34
|
+
* - `GLOBAL`: documents are shared across all tenants. Writes must
|
|
35
|
+
* not pass a tenant; reads ignore the bound tenant.
|
|
36
|
+
* Doc, KindDefinition, Module-level configs, etc.
|
|
37
|
+
*/
|
|
38
|
+
export const TenantScope = {
|
|
39
|
+
TENANTED: "tenanted",
|
|
40
|
+
GLOBAL: "global",
|
|
41
|
+
};
|
|
42
|
+
/** Reserved tenant slugs — never accepted as user input. */
|
|
43
|
+
export const RESERVED_TENANT_SLUGS = new Set([
|
|
44
|
+
"_global",
|
|
45
|
+
"_legacy",
|
|
46
|
+
"_system",
|
|
47
|
+
"",
|
|
48
|
+
]);
|
|
49
|
+
/**
|
|
50
|
+
* Raised when a TENANTED kind is written without a tenant arg.
|
|
51
|
+
*
|
|
52
|
+
* Bind a tenant on construction (`new Kernel({ tenant: X })`) or
|
|
53
|
+
* per-call (`kernel.withTenant(X).writeDocument(...)`).
|
|
54
|
+
*/
|
|
55
|
+
export class TenantRequired extends Error {
|
|
56
|
+
constructor(message) {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = "TenantRequired";
|
|
59
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Raised when a GLOBAL kind is written with a tenant arg.
|
|
64
|
+
*
|
|
65
|
+
* Global kinds (Doc, KindDefinition, ...) are shared across
|
|
66
|
+
* tenants. Writes must explicitly omit `tenant`.
|
|
67
|
+
*/
|
|
68
|
+
export class TenantNotAllowed extends Error {
|
|
69
|
+
constructor(message) {
|
|
70
|
+
super(message);
|
|
71
|
+
this.name = "TenantNotAllowed";
|
|
72
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Raised when `writeDocument` vetoes a doc whose `spec` violates the Kind's
|
|
77
|
+
* declared JSON Schema (`KindPort.schema()`).
|
|
78
|
+
*
|
|
79
|
+
* s-write-path-validation (i-008): the kernel used to validate schemas only
|
|
80
|
+
* at SCAN/read (the fail-soft `parse_error` channel) — a shape-broken doc
|
|
81
|
+
* would persist and explode later, far from the author. Now every
|
|
82
|
+
* `writeDocument` validates the spec at write time when the Kind declares a
|
|
83
|
+
* schema; Kinds without a schema stay permissive.
|
|
84
|
+
* Mode knob: `DNA_WRITE_VALIDATION=enforce|warn|off`.
|
|
85
|
+
*/
|
|
86
|
+
export class SpecValidationError extends Error {
|
|
87
|
+
constructor(message) {
|
|
88
|
+
super(message);
|
|
89
|
+
this.name = "SpecValidationError";
|
|
90
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Raised when a tenant slug is reserved or empty. */
|
|
94
|
+
export class InvalidTenantSlug extends Error {
|
|
95
|
+
constructor(message) {
|
|
96
|
+
super(message);
|
|
97
|
+
this.name = "InvalidTenantSlug";
|
|
98
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Validate a tenant slug.
|
|
103
|
+
*
|
|
104
|
+
* Phase 1 only checks the reserved set + non-empty/length. Character
|
|
105
|
+
* rules (DNS-label, lowercase) are NOT enforced at the kernel boundary
|
|
106
|
+
* so existing tests/data using uppercase ("T1", "Acme") keep working.
|
|
107
|
+
* Path-traversal safety lives in the adapter.
|
|
108
|
+
*
|
|
109
|
+
* Phase 2 may tighten to k8s namespace rules (`[a-z0-9-]{1,63}`) once
|
|
110
|
+
* the migration is complete.
|
|
111
|
+
*/
|
|
112
|
+
export function validateTenantSlug(tenant) {
|
|
113
|
+
if (tenant === null || tenant === undefined)
|
|
114
|
+
return;
|
|
115
|
+
if (RESERVED_TENANT_SLUGS.has(tenant)) {
|
|
116
|
+
const reserved = Array.from(RESERVED_TENANT_SLUGS).sort();
|
|
117
|
+
throw new InvalidTenantSlug(`tenant slug ${JSON.stringify(tenant)} is reserved (one of ${JSON.stringify(reserved)})`);
|
|
118
|
+
}
|
|
119
|
+
if (tenant.length < 1 || tenant.length > 253) {
|
|
120
|
+
throw new InvalidTenantSlug(`tenant slug ${JSON.stringify(tenant)} must be 1-253 chars (got ${tenant.length})`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Raised when a write to a layer violates the declared LayerPolicy in
|
|
125
|
+
* `Module.spec.layers`. Thrown by `Kernel.writeDocument` before the adapter
|
|
126
|
+
* is touched. Harness endpoints translate to HTTP 403.
|
|
127
|
+
*/
|
|
128
|
+
export class LayerPolicyViolationError extends Error {
|
|
129
|
+
constructor(message) {
|
|
130
|
+
super(message);
|
|
131
|
+
this.name = "LayerPolicyViolationError";
|
|
132
|
+
Object.setPrototypeOf(this, LayerPolicyViolationError.prototype);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export function isCompositionValid(result) {
|
|
136
|
+
return result.missing.length === 0;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Raised when a query filter / order_by is malformed in a way the
|
|
140
|
+
* adapter can detect statically (unknown operator, …). 1:1 with the Py
|
|
141
|
+
* `QueryError`.
|
|
142
|
+
*/
|
|
143
|
+
export class QueryError extends Error {
|
|
144
|
+
constructor(message) {
|
|
145
|
+
super(message);
|
|
146
|
+
this.name = "QueryError";
|
|
147
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** Operators recognized by `matchFilter` — the Py `_PG_OP_MAP` set + `in`. */
|
|
151
|
+
export const QUERY_OPS = new Set([
|
|
152
|
+
"eq", "in", "like", "gt", "gte", "lt", "lte", "neq",
|
|
153
|
+
]);
|
|
154
|
+
function isPlainObject(v) {
|
|
155
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Deep structural equality for filter matching. Py uses `==` which
|
|
159
|
+
* deep-compares dicts/lists; mirror that. Best-effort divergence: Py
|
|
160
|
+
* `1 == 1.0 == True` numeric cross-type equality is NOT replicated for
|
|
161
|
+
* booleans (JS `1 !== true`) — filters should use the field's real type.
|
|
162
|
+
*/
|
|
163
|
+
function deepEqual(a, b) {
|
|
164
|
+
if (a === b)
|
|
165
|
+
return true;
|
|
166
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
167
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
168
|
+
}
|
|
169
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
170
|
+
const ka = Object.keys(a);
|
|
171
|
+
const kb = Object.keys(b);
|
|
172
|
+
return (ka.length === kb.length
|
|
173
|
+
&& ka.every((k) => k in b && deepEqual(a[k], b[k])));
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Walk a dotted `fieldPath` through `doc`. Unprefixed paths resolve under
|
|
179
|
+
* `spec.`; `name` is a reserved short for `metadata.name`. Returns `null`
|
|
180
|
+
* when any segment is missing (never `undefined` — parity with Py `None`).
|
|
181
|
+
*/
|
|
182
|
+
export function resolveFieldPath(doc, path) {
|
|
183
|
+
if (path === "name") {
|
|
184
|
+
const meta = doc.metadata;
|
|
185
|
+
return isPlainObject(meta) ? (meta.name ?? null) : null;
|
|
186
|
+
}
|
|
187
|
+
if (path === "kind")
|
|
188
|
+
return doc.kind ?? null;
|
|
189
|
+
if (path === "apiVersion")
|
|
190
|
+
return doc.apiVersion ?? null;
|
|
191
|
+
const segments = path.startsWith("metadata.") || path.startsWith("spec.") || path.startsWith("apiVersion.")
|
|
192
|
+
? path.split(".")
|
|
193
|
+
: ["spec", ...path.split(".")];
|
|
194
|
+
let cur = doc;
|
|
195
|
+
for (const seg of segments) {
|
|
196
|
+
if (!isPlainObject(cur))
|
|
197
|
+
return null;
|
|
198
|
+
cur = cur[seg];
|
|
199
|
+
if (cur === null || cur === undefined)
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return cur ?? null;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Evaluate a `QueryFilter` against a single doc. Unknown operators throw
|
|
206
|
+
* `QueryError` so callers can debug instead of silently matching. 1:1
|
|
207
|
+
* port of the Py `_match_filter` (only single-key dicts enter the
|
|
208
|
+
* operator branch; anything else is shorthand equality).
|
|
209
|
+
*/
|
|
210
|
+
export function matchFilter(doc, filter) {
|
|
211
|
+
for (const [path, expected] of Object.entries(filter)) {
|
|
212
|
+
const actual = resolveFieldPath(doc, path);
|
|
213
|
+
if (isPlainObject(expected) && Object.keys(expected).length === 1) {
|
|
214
|
+
const [op, val] = Object.entries(expected)[0];
|
|
215
|
+
if (!QUERY_OPS.has(op)) {
|
|
216
|
+
throw new QueryError(`unknown query operator ${JSON.stringify(op)} on field `
|
|
217
|
+
+ `${JSON.stringify(path)}; valid: ${[...QUERY_OPS].sort().join(", ")}`);
|
|
218
|
+
}
|
|
219
|
+
if (op === "eq" && !deepEqual(actual, val))
|
|
220
|
+
return false;
|
|
221
|
+
if (op === "neq" && deepEqual(actual, val))
|
|
222
|
+
return false;
|
|
223
|
+
if (op === "in") {
|
|
224
|
+
// Py: `actual not in (val or ())` — null/empty list matches nothing.
|
|
225
|
+
// DIVERGENCE vs Py: Py `actual in <str>` does substring membership on
|
|
226
|
+
// a malformed string operand; TS coerces non-array to [] (matches nothing).
|
|
227
|
+
const candidates = Array.isArray(val) ? val : [];
|
|
228
|
+
if (!candidates.some((v) => deepEqual(actual, v)))
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
if (op === "like") {
|
|
232
|
+
if (actual === null || typeof val !== "string")
|
|
233
|
+
return false;
|
|
234
|
+
// SQL LIKE: % = any, _ = single char. Tokenize so ONLY literals
|
|
235
|
+
// are regex-escaped (same approach as the Py fallback).
|
|
236
|
+
const parts = [];
|
|
237
|
+
for (const ch of val) {
|
|
238
|
+
if (ch === "%")
|
|
239
|
+
parts.push(".*");
|
|
240
|
+
else if (ch === "_")
|
|
241
|
+
parts.push(".");
|
|
242
|
+
else
|
|
243
|
+
parts.push(ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
244
|
+
}
|
|
245
|
+
if (!new RegExp(`^${parts.join("")}$`).test(String(actual)))
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
// Range ops: null never matches (Py: `actual is not None and …`).
|
|
249
|
+
// DIVERGENCE vs Py: Py raises TypeError on cross-type range compares;
|
|
250
|
+
// TS coerces (NaN) and silently excludes the row — don't rely on
|
|
251
|
+
// cross-type range filters.
|
|
252
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
253
|
+
if (op === "gt" && !(actual !== null && actual > val))
|
|
254
|
+
return false;
|
|
255
|
+
if (op === "gte" && !(actual !== null && actual >= val))
|
|
256
|
+
return false;
|
|
257
|
+
if (op === "lt" && !(actual !== null && actual < val))
|
|
258
|
+
return false;
|
|
259
|
+
if (op === "lte" && !(actual !== null && actual <= val))
|
|
260
|
+
return false;
|
|
261
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
262
|
+
}
|
|
263
|
+
else if (!deepEqual(actual, expected)) {
|
|
264
|
+
// Shorthand: {"status": "in-progress"} == {"status": {"eq": ...}}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Stable sort `rows` by each `orderBy` field, last-first to achieve the
|
|
272
|
+
* desired primary/secondary precedence. Prefixed `-` means descending.
|
|
273
|
+
* `null` values sort LAST regardless of direction: the None-flag is
|
|
274
|
+
* `(v === null) !== descending` — immune to the reversed comparator
|
|
275
|
+
* (i-121: parity with PG `DESC NULLS LAST`; a plain `v === null` flag
|
|
276
|
+
* would flip under reversal and shove nulls to the FRONT in DESC).
|
|
277
|
+
*
|
|
278
|
+
* Mixed-type values (number + string across rows on the same field) fall
|
|
279
|
+
* back to stringified compare to mirror the Py TypeError fallback. Does
|
|
280
|
+
* not mutate the input. Best-effort like the Py protocol-default —
|
|
281
|
+
* adapters with native push-down use the backend's type semantics.
|
|
282
|
+
*/
|
|
283
|
+
export function applyOrderBy(rows, orderBy) {
|
|
284
|
+
let out = rows.slice();
|
|
285
|
+
for (const spec of [...orderBy].reverse()) {
|
|
286
|
+
const descending = spec.startsWith("-");
|
|
287
|
+
const path = descending ? spec.slice(1) : spec;
|
|
288
|
+
const decorated = out.map((row) => {
|
|
289
|
+
const v = resolveFieldPath(row, path);
|
|
290
|
+
return { row, v, flag: (v === null) !== descending };
|
|
291
|
+
});
|
|
292
|
+
// Py first pass keeps int/float (and bool, a Py int subtype) numeric
|
|
293
|
+
// and stringifies the rest; a mixed numeric/string field raises
|
|
294
|
+
// TypeError there → stringify-everything fallback. Detect the mix
|
|
295
|
+
// upfront instead of try/catch.
|
|
296
|
+
const isNumeric = (v) => typeof v === "number" || typeof v === "boolean";
|
|
297
|
+
const nonNull = decorated.filter((d) => d.v !== null);
|
|
298
|
+
const mixed = nonNull.some((d) => isNumeric(d.v)) && nonNull.some((d) => !isNumeric(d.v));
|
|
299
|
+
const sortable = (v) => {
|
|
300
|
+
if (v === null)
|
|
301
|
+
return "";
|
|
302
|
+
if (mixed)
|
|
303
|
+
return v ? String(v) : ""; // Py fallback: str(v or "")
|
|
304
|
+
return isNumeric(v) ? Number(v) : String(v);
|
|
305
|
+
};
|
|
306
|
+
const keyed = decorated.map((d) => ({ ...d, key: sortable(d.v) }));
|
|
307
|
+
const cmp = (a, b) => {
|
|
308
|
+
if (a.flag !== b.flag)
|
|
309
|
+
return a.flag ? 1 : -1;
|
|
310
|
+
if (typeof a.key === "number" && typeof b.key === "number") {
|
|
311
|
+
return a.key - b.key;
|
|
312
|
+
}
|
|
313
|
+
const sa = String(a.key);
|
|
314
|
+
const sb = String(b.key);
|
|
315
|
+
return sa < sb ? -1 : sa > sb ? 1 : 0;
|
|
316
|
+
};
|
|
317
|
+
// JS sort is stable; the negated comparator under `descending`
|
|
318
|
+
// preserves original order for ties — same as Py `reverse=True`.
|
|
319
|
+
keyed.sort((a, b) => (descending ? -cmp(a, b) : cmp(a, b)));
|
|
320
|
+
out = keyed.map((d) => d.row);
|
|
321
|
+
}
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* In-memory query core — mirror of the Py `SourcePort.query`
|
|
326
|
+
* protocol-default minus the IO: kind filter first (cheap), then
|
|
327
|
+
* `matchFilter`, then `applyOrderBy`, then limit/offset paging.
|
|
328
|
+
* `FilesystemSource.query` is `loadAll` + this; the shared parity
|
|
329
|
+
* fixture drives it directly.
|
|
330
|
+
*/
|
|
331
|
+
export function queryDocs(docs, kind, opts = {}) {
|
|
332
|
+
let kindDocs = docs.filter((d) => d.kind === kind);
|
|
333
|
+
if (opts.filter && Object.keys(opts.filter).length > 0) {
|
|
334
|
+
kindDocs = kindDocs.filter((d) => matchFilter(d, opts.filter));
|
|
335
|
+
}
|
|
336
|
+
if (opts.orderBy && opts.orderBy.length > 0) {
|
|
337
|
+
kindDocs = applyOrderBy(kindDocs, opts.orderBy);
|
|
338
|
+
}
|
|
339
|
+
const start = opts.offset ?? 0;
|
|
340
|
+
const end = opts.limit != null ? start + opts.limit : undefined;
|
|
341
|
+
return kindDocs.slice(start, end);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* In-memory count core — mirror of the Py `SourcePort.count`
|
|
345
|
+
* protocol-default: total of docs matching the filter, optionally
|
|
346
|
+
* grouped by a field path. Groups ordered by count DESC, then key ASC
|
|
347
|
+
* with `null` LAST (matches PG `ORDER BY count DESC, key ASC NULLS
|
|
348
|
+
* LAST` and the spirit of i-121).
|
|
349
|
+
*/
|
|
350
|
+
export function countDocs(docs, kind, opts = {}) {
|
|
351
|
+
const rows = queryDocs(docs, kind, { filter: opts.filter });
|
|
352
|
+
const total = rows.length;
|
|
353
|
+
let groups = null;
|
|
354
|
+
if (opts.groupBy != null) {
|
|
355
|
+
const counter = new Map();
|
|
356
|
+
for (const row of rows) {
|
|
357
|
+
const v = resolveFieldPath(row, opts.groupBy);
|
|
358
|
+
// DIVERGENCE vs Py (unreachable with primitive keys, pinned for record):
|
|
359
|
+
// Py str(True)="True" / str(dict)="{'k': 1}"; JS String(true)="true" /
|
|
360
|
+
// String({})="[object Object]". Complex group keys should be avoided.
|
|
361
|
+
const key = v === null || ["string", "number", "boolean"].includes(typeof v)
|
|
362
|
+
? v
|
|
363
|
+
: String(v);
|
|
364
|
+
counter.set(key, (counter.get(key) ?? 0) + 1);
|
|
365
|
+
}
|
|
366
|
+
groups = [...counter.entries()]
|
|
367
|
+
.map(([key, count]) => ({ key, count }))
|
|
368
|
+
.sort((a, b) => {
|
|
369
|
+
if (a.count !== b.count)
|
|
370
|
+
return b.count - a.count;
|
|
371
|
+
if ((a.key === null) !== (b.key === null))
|
|
372
|
+
return a.key === null ? 1 : -1;
|
|
373
|
+
const sa = String(a.key);
|
|
374
|
+
const sb = String(b.key);
|
|
375
|
+
return sa < sb ? -1 : sa > sb ? 1 : 0;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return { total, groups };
|
|
379
|
+
}
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
// Error hierarchy
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
export class ResolveError extends Error {
|
|
384
|
+
constructor(message) {
|
|
385
|
+
super(message);
|
|
386
|
+
this.name = "ResolveError";
|
|
387
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
export class ResolveNotFoundError extends ResolveError {
|
|
391
|
+
constructor(message) {
|
|
392
|
+
super(message);
|
|
393
|
+
this.name = "ResolveNotFoundError";
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
export class ResolveAuthError extends ResolveError {
|
|
397
|
+
constructor(message) {
|
|
398
|
+
super(message);
|
|
399
|
+
this.name = "ResolveAuthError";
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
export class ResolveNetworkError extends ResolveError {
|
|
403
|
+
constructor(message) {
|
|
404
|
+
super(message);
|
|
405
|
+
this.name = "ResolveNetworkError";
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
export const SD = {
|
|
409
|
+
bundle(container, marker, bodyAs = "text", bodyField = "instruction") {
|
|
410
|
+
return { container, pattern: "bundle", marker, bodyAs, bodyField };
|
|
411
|
+
},
|
|
412
|
+
yaml(container) {
|
|
413
|
+
return { container, pattern: "yaml" };
|
|
414
|
+
},
|
|
415
|
+
root(filename = "manifest.yaml") {
|
|
416
|
+
return { container: "", pattern: "root", marker: filename };
|
|
417
|
+
},
|
|
418
|
+
standalone(filename, bodyAs = "text", bodyField = "content") {
|
|
419
|
+
return { container: "", pattern: "standalone", marker: filename, bodyAs, bodyField };
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
export function defaultVisibleInBackend(storage) {
|
|
423
|
+
if (!storage)
|
|
424
|
+
return false;
|
|
425
|
+
if (storage.pattern === "bundle" || storage.pattern === "standalone")
|
|
426
|
+
return true;
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
export function resolveVisibleInBackend(kp) {
|
|
430
|
+
// `visibleInBackend` is a declared optional member of the
|
|
431
|
+
// KindPresentation slice (s-dna-kindport-descriptor-schema) — typed
|
|
432
|
+
// access, no `as any`. Unset/null falls back to the storage default.
|
|
433
|
+
const explicit = kp.visibleInBackend;
|
|
434
|
+
if (explicit !== undefined && explicit !== null)
|
|
435
|
+
return Boolean(explicit);
|
|
436
|
+
return defaultVisibleInBackend(kp.storage ?? null);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Phase 16 — Kinds the kernel needs registered/parsed BEFORE
|
|
440
|
+
* ``loadAll`` fires. Order is meaningful: KindDefinition first
|
|
441
|
+
* (custom Kinds need to be registered before parsing other docs),
|
|
442
|
+
* LayerPolicy next (kernel reads at write-time), Genome last
|
|
443
|
+
* (root identity).
|
|
444
|
+
*/
|
|
445
|
+
export const BOOTSTRAP_KIND_NAMES = ["KindDefinition", "LayerPolicy", "Genome"];
|
|
446
|
+
/**
|
|
447
|
+
* Phase 16 helper — return the Genome doc for ``scope`` (or ``null``
|
|
448
|
+
* if missing). Pulls bootstrap docs and filters for the Genome Kind.
|
|
449
|
+
* Tenant-aware: when ``opts.tenant`` is set, the underlying adapter
|
|
450
|
+
* applies tenant-overlay routing (tenant-published Genome shadows
|
|
451
|
+
* platform).
|
|
452
|
+
*
|
|
453
|
+
* 1:1 parity with Python ``package_doc_for_scope``.
|
|
454
|
+
*/
|
|
455
|
+
export async function packageDocForScope(source, scope, opts) {
|
|
456
|
+
const bootstrap = await source.loadBootstrapDocs(scope, opts);
|
|
457
|
+
for (const d of bootstrap) {
|
|
458
|
+
if (d.kind === "Genome")
|
|
459
|
+
return d;
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Concrete ToolPort implementation, stored in the kernel's ToolRegistry.
|
|
465
|
+
* Twin of the Py `ToolDefinition` dataclass — the `callable` init field
|
|
466
|
+
* holds the framework-native invocable; `getCallable()` is the canonical
|
|
467
|
+
* accessor (never serialize or wrap-replace it).
|
|
468
|
+
*/
|
|
469
|
+
export class ToolDefinition {
|
|
470
|
+
name;
|
|
471
|
+
group;
|
|
472
|
+
description;
|
|
473
|
+
summary;
|
|
474
|
+
argsSchema;
|
|
475
|
+
hitl;
|
|
476
|
+
scope;
|
|
477
|
+
source;
|
|
478
|
+
_callable;
|
|
479
|
+
constructor(init) {
|
|
480
|
+
this.name = init.name;
|
|
481
|
+
this.group = init.group ?? null;
|
|
482
|
+
this.description = init.description ?? "";
|
|
483
|
+
this.summary = init.summary ?? "";
|
|
484
|
+
this.argsSchema = init.argsSchema ?? {};
|
|
485
|
+
this.hitl = init.hitl ?? false;
|
|
486
|
+
this.scope = init.scope ?? null;
|
|
487
|
+
this.source = init.source ?? "";
|
|
488
|
+
this._callable = init.callable ?? null;
|
|
489
|
+
}
|
|
490
|
+
getCallable() {
|
|
491
|
+
return this._callable;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReportBuilder — structured markdown reports for eval, findings, evidence, compliance.
|
|
3
|
+
*
|
|
4
|
+
* Accessed via `mi.reports.evalSummary()`, `mi.reports.findingsSummary()`,
|
|
5
|
+
* `mi.reports.evidenceManifest()`, `mi.reports.complianceMatrix(framework)`.
|
|
6
|
+
*
|
|
7
|
+
* Each report returns Markdown with YAML frontmatter.
|
|
8
|
+
*
|
|
9
|
+
* 1:1 parity with Python dna.kernel.reports.
|
|
10
|
+
*/
|
|
11
|
+
import type { ManifestInstance } from "./instance.js";
|
|
12
|
+
export declare class ReportBuilder {
|
|
13
|
+
private readonly _mi;
|
|
14
|
+
constructor(mi: ManifestInstance);
|
|
15
|
+
/** Generate an eval summary report grouped by suite.
|
|
16
|
+
* If `suite` is given, only that suite is included. */
|
|
17
|
+
evalSummary(suite?: string): string;
|
|
18
|
+
/** Generate a findings summary grouped by severity.
|
|
19
|
+
* Filters out findings with severity below `minSeverity`. */
|
|
20
|
+
findingsSummary(minSeverity?: string): string;
|
|
21
|
+
/** Generate a manifest of all evidence documents. */
|
|
22
|
+
evidenceManifest(): string;
|
|
23
|
+
/** Generate a compliance matrix mapping findings to regulatory articles.
|
|
24
|
+
* Supported frameworks: lgpd, gdpr, nist_ai_rmf. */
|
|
25
|
+
complianceMatrix(framework: string): string;
|
|
26
|
+
}
|