dna-sdk 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/bootstrap.js +6 -0
  2. package/dist/extensions/cloud/kinds/tenant-plan.kind.yaml +105 -0
  3. package/dist/extensions/cloud/kinds/tier.kind.yaml +135 -0
  4. package/dist/extensions/cloud.d.ts +31 -0
  5. package/dist/extensions/cloud.js +39 -0
  6. package/dist/extensions/guardrails.js +4 -0
  7. package/dist/extensions/helix.js +19 -6
  8. package/dist/extensions/intel/kinds/insight.kind.yaml +148 -0
  9. package/dist/extensions/intel/kinds/intel-source.kind.yaml +117 -0
  10. package/dist/extensions/intel.d.ts +30 -0
  11. package/dist/extensions/intel.js +38 -0
  12. package/dist/extensions/portfolio/kinds/membership.kind.yaml +114 -0
  13. package/dist/extensions/portfolio/kinds/organization.kind.yaml +99 -0
  14. package/dist/extensions/portfolio/kinds/project.kind.yaml +124 -0
  15. package/dist/extensions/portfolio/kinds/repo.kind.yaml +99 -0
  16. package/dist/extensions/portfolio/kinds/role.kind.yaml +96 -0
  17. package/dist/extensions/portfolio.d.ts +45 -0
  18. package/dist/extensions/portfolio.js +53 -0
  19. package/dist/extensions/sdlc/kinds/lesson-learned.kind.yaml +0 -1
  20. package/dist/index.d.ts +1 -0
  21. package/dist/kernel/composition-resolver.js +17 -1
  22. package/dist/kernel/index.d.ts +50 -0
  23. package/dist/kernel/index.js +98 -0
  24. package/dist/kernel/instance.d.ts +8 -0
  25. package/dist/kernel/instance.js +9 -0
  26. package/dist/kernel/kind_base.d.ts +10 -0
  27. package/dist/kernel/kind_base.js +36 -18
  28. package/dist/kernel/meta.d.ts +1 -0
  29. package/dist/kernel/meta.js +11 -1
  30. package/dist/kernel/models.d.ts +23 -23
  31. package/dist/kernel/models.js +8 -2
  32. package/dist/kernel/prompt-builder.d.ts +64 -0
  33. package/dist/kernel/prompt-builder.js +208 -3
  34. package/dist/kernel/write-pipeline.d.ts +6 -0
  35. package/dist/kernel/write-pipeline.js +7 -0
  36. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export type { PreviewBlock } from "./kernel/preview.js";
12
12
  export { Document } from "./kernel/document.js";
13
13
  export { Resource, type ResourceDep } from "./kernel/resource.js";
14
14
  export { PromptBuilder } from "./kernel/prompt-builder.js";
15
+ export type { SectionProvenance, PromptExplanation, ExplainOpts, } from "./kernel/prompt-builder.js";
15
16
  export { CompositionEngine } from "./kernel/composition-resolver.js";
16
17
  export { BOOTSTRAP_KINDS, DEFAULT_NON_INHERITABLE_KINDS_V1, DEFAULT_INHERITABLE_KINDS_V1, MAX_RESOLUTION_DEPTH, ResolutionLayer, ResolutionPath, ResolvedDocument, mergeOverrideFull, mergeFieldLevel, type Contribution, type Raw, } from "./kernel/resolver.js";
17
18
  export { CompositionResolver } from "./kernel/composition-resolver.js";
@@ -450,7 +450,23 @@ export class CompositionResolver {
450
450
  // multi-package conflict surface below.
451
451
  const catalogHits = [];
452
452
  for (const [layerScope, layerTenant] of chain) {
453
- const raw = await k._granularDoc([layerScope, kind, name, layerTenant ?? ""]);
453
+ let raw;
454
+ try {
455
+ raw = await k._granularDoc([layerScope, kind, name, layerTenant ?? ""]);
456
+ }
457
+ catch (e) {
458
+ // A parent/catalog scope in the chain may not exist on this source
459
+ // (e.g. a `_lib` library scope absent in a bare checkout). Treat it
460
+ // as "no contribution at this layer" — the same fail-soft the
461
+ // instance builder applies — instead of crashing the whole resolve.
462
+ // (Matches Py resolve_document's FileNotFoundError guard.)
463
+ if (e instanceof Error && /Scope not found/.test(e.message)) {
464
+ raw = null;
465
+ }
466
+ else {
467
+ throw e;
468
+ }
469
+ }
454
470
  // NOTE: the Py twin registers `_layer_observers` reverse-deps here
455
471
  // (cache invalidation infra). Intentionally not ported — the TS
456
472
  // kernel has no granular cache to invalidate.
@@ -55,6 +55,18 @@ export declare const MODEL_REGISTRY_SCOPE = "_lib";
55
55
  * 1:1 parity with Python `Kernel._VOICE_POLICY_SCOPE`.
56
56
  */
57
57
  export declare const VOICE_POLICY_SCOPE = "_lib";
58
+ /**
59
+ * The scope that owns the canonical Tier (DNA Cloud pricing) registry.
60
+ * GLOBAL like ModelProfile — queried directly regardless of the caller's
61
+ * scope. 1:1 parity with Python `RegistryAccessor._TIER_REGISTRY_SCOPE`.
62
+ */
63
+ export declare const TIER_REGISTRY_SCOPE = "_lib";
64
+ /**
65
+ * The scope that owns the canonical TenantPlan (tenant→Tier assignment)
66
+ * registry. GLOBAL like Tier — queried directly regardless of the caller's
67
+ * scope. 1:1 parity with Python `RegistryAccessor._TENANT_PLAN_REGISTRY_SCOPE`.
68
+ */
69
+ export declare const TENANT_PLAN_REGISTRY_SCOPE = "_lib";
58
70
  export declare class Kernel {
59
71
  private static readonly _BOOTSTRAP_KINDS;
60
72
  private static readonly _LEGACY_NON_INHERITABLE;
@@ -181,6 +193,12 @@ export declare class Kernel {
181
193
  * `WritePipeline.validateKindWriter` (Fase 2,
182
194
  * s-kernel-decomp-f2-writepipeline). */
183
195
  _validateKindWriter(spec: Record<string, unknown>): void;
196
+ /** Validate `raw.spec` against the Kind's declared JSON Schema — the SAME
197
+ * check writeDocument enforces on the write path, exposed as a public
198
+ * pre-flight so read-only callers catch a schema-violating doc BEFORE the
199
+ * write (i-validation-shallow). Throws SpecValidationError on violation,
200
+ * honouring DNA_WRITE_VALIDATION. Twin of Python Kernel.validate_document. */
201
+ validateDocument(scope: string, kind: string, name: string, raw: Record<string, unknown>, apiVersion?: string): void;
184
202
  /** All registered KindPorts. Order matches registration. Facade over
185
203
  * `this._kindreg.allPorts()` (Fase 3). */
186
204
  kindPorts(): KindPort[];
@@ -611,6 +629,38 @@ export declare class Kernel {
611
629
  * @returns The matching Document, or null on miss or error.
612
630
  */
613
631
  modelProfile(modelIdOrAlias: string): Promise<import("./document.js").Document | null>;
632
+ /**
633
+ * Resolve a Tier (DNA Cloud pricing plan) from the `_lib` scope by
634
+ * `tier_id` (pass 1) or `aliases[]` (pass 2). Returns the matching
635
+ * Document or null on miss.
636
+ *
637
+ * Always queries `TIER_REGISTRY_SCOPE` ("_lib") directly — Tier is GLOBAL
638
+ * and NOT in `INHERITABLE_KINDS`; any caller scope is irrelevant. The
639
+ * quota enforcer reads the caps from here — never hardcode calls_per_day /
640
+ * rate_per_sec / max_tenants in code.
641
+ *
642
+ * 1:1 parity with Python `Kernel.tier(tier_id_or_alias)`.
643
+ *
644
+ * @param idOrAlias - The tier_id string or an alias declared on the tier.
645
+ * @returns The matching Document, or null on miss or error.
646
+ */
647
+ tier(idOrAlias: string): Promise<import("./document.js").Document | null>;
648
+ /**
649
+ * Resolve a TenantPlan (a tenant→Tier assignment) from the `_lib` scope by
650
+ * `spec.tenant`. Returns the matching Document or null when no assignment
651
+ * exists for `tenant`.
652
+ *
653
+ * Always queries `TENANT_PLAN_REGISTRY_SCOPE` ("_lib") directly — TenantPlan
654
+ * is GLOBAL and NOT in `INHERITABLE_KINDS`; any caller scope is irrelevant.
655
+ * The billing→enforcement bridge: dna-cloud's Stripe webhook writes the doc;
656
+ * the SDK only reads it here. No alias pass — the match is on `tenant`.
657
+ *
658
+ * 1:1 parity with Python `Kernel.tenant_plan(tenant)`.
659
+ *
660
+ * @param tenant - The DNA tenant to resolve the plan assignment for.
661
+ * @returns The matching Document, or null on miss or error.
662
+ */
663
+ tenantPlan(tenant: string): Promise<import("./document.js").Document | null>;
614
664
  /**
615
665
  * Resolve a VoicePolicy from the `_lib` scope by metadata name.
616
666
  * Returns the matching Document, the first policy as a fallback, or null
@@ -59,6 +59,18 @@ export const MODEL_REGISTRY_SCOPE = SYSTEM_SCOPE;
59
59
  * 1:1 parity with Python `Kernel._VOICE_POLICY_SCOPE`.
60
60
  */
61
61
  export const VOICE_POLICY_SCOPE = SYSTEM_SCOPE;
62
+ /**
63
+ * The scope that owns the canonical Tier (DNA Cloud pricing) registry.
64
+ * GLOBAL like ModelProfile — queried directly regardless of the caller's
65
+ * scope. 1:1 parity with Python `RegistryAccessor._TIER_REGISTRY_SCOPE`.
66
+ */
67
+ export const TIER_REGISTRY_SCOPE = SYSTEM_SCOPE;
68
+ /**
69
+ * The scope that owns the canonical TenantPlan (tenant→Tier assignment)
70
+ * registry. GLOBAL like Tier — queried directly regardless of the caller's
71
+ * scope. 1:1 parity with Python `RegistryAccessor._TENANT_PLAN_REGISTRY_SCOPE`.
72
+ */
73
+ export const TENANT_PLAN_REGISTRY_SCOPE = SYSTEM_SCOPE;
62
74
  export class Kernel {
63
75
  // ── Kind classification — DERIVED from KindPort attributes ──────────────
64
76
  // s-kernel-kindport-classification-attrs: the kernel no longer hardcodes
@@ -242,6 +254,19 @@ export class Kernel {
242
254
  _validateKindWriter(spec) {
243
255
  this._writePipeline.validateKindWriter(spec);
244
256
  }
257
+ /** Validate `raw.spec` against the Kind's declared JSON Schema — the SAME
258
+ * check writeDocument enforces on the write path, exposed as a public
259
+ * pre-flight so read-only callers catch a schema-violating doc BEFORE the
260
+ * write (i-validation-shallow). Throws SpecValidationError on violation,
261
+ * honouring DNA_WRITE_VALIDATION. Twin of Python Kernel.validate_document. */
262
+ validateDocument(scope, kind, name, raw, apiVersion) {
263
+ const api = apiVersion ??
264
+ (typeof raw === "object" && raw !== null
265
+ ? raw.apiVersion
266
+ : undefined);
267
+ const port = this._kindPortFor(kind, api);
268
+ this._writePipeline.validateSpecSchema(scope, kind, name, raw, port);
269
+ }
245
270
  /** All registered KindPorts. Order matches registration. Facade over
246
271
  * `this._kindreg.allPorts()` (Fase 3). */
247
272
  kindPorts() {
@@ -1610,6 +1635,79 @@ export class Kernel {
1610
1635
  }
1611
1636
  return null;
1612
1637
  }
1638
+ /**
1639
+ * Resolve a Tier (DNA Cloud pricing plan) from the `_lib` scope by
1640
+ * `tier_id` (pass 1) or `aliases[]` (pass 2). Returns the matching
1641
+ * Document or null on miss.
1642
+ *
1643
+ * Always queries `TIER_REGISTRY_SCOPE` ("_lib") directly — Tier is GLOBAL
1644
+ * and NOT in `INHERITABLE_KINDS`; any caller scope is irrelevant. The
1645
+ * quota enforcer reads the caps from here — never hardcode calls_per_day /
1646
+ * rate_per_sec / max_tenants in code.
1647
+ *
1648
+ * 1:1 parity with Python `Kernel.tier(tier_id_or_alias)`.
1649
+ *
1650
+ * @param idOrAlias - The tier_id string or an alias declared on the tier.
1651
+ * @returns The matching Document, or null on miss or error.
1652
+ */
1653
+ async tier(idOrAlias) {
1654
+ let rows;
1655
+ try {
1656
+ const mi = await this.instance(TIER_REGISTRY_SCOPE);
1657
+ rows = mi._all("Tier");
1658
+ }
1659
+ catch {
1660
+ return null;
1661
+ }
1662
+ // Pass 1: exact tier_id match
1663
+ for (const row of rows) {
1664
+ const spec = (row.spec ?? {});
1665
+ if (spec.tier_id === idOrAlias) {
1666
+ return row;
1667
+ }
1668
+ }
1669
+ // Pass 2: aliases[]
1670
+ for (const row of rows) {
1671
+ const spec = (row.spec ?? {});
1672
+ const aliases = (spec.aliases ?? []);
1673
+ if (aliases.includes(idOrAlias)) {
1674
+ return row;
1675
+ }
1676
+ }
1677
+ return null;
1678
+ }
1679
+ /**
1680
+ * Resolve a TenantPlan (a tenant→Tier assignment) from the `_lib` scope by
1681
+ * `spec.tenant`. Returns the matching Document or null when no assignment
1682
+ * exists for `tenant`.
1683
+ *
1684
+ * Always queries `TENANT_PLAN_REGISTRY_SCOPE` ("_lib") directly — TenantPlan
1685
+ * is GLOBAL and NOT in `INHERITABLE_KINDS`; any caller scope is irrelevant.
1686
+ * The billing→enforcement bridge: dna-cloud's Stripe webhook writes the doc;
1687
+ * the SDK only reads it here. No alias pass — the match is on `tenant`.
1688
+ *
1689
+ * 1:1 parity with Python `Kernel.tenant_plan(tenant)`.
1690
+ *
1691
+ * @param tenant - The DNA tenant to resolve the plan assignment for.
1692
+ * @returns The matching Document, or null on miss or error.
1693
+ */
1694
+ async tenantPlan(tenant) {
1695
+ let rows;
1696
+ try {
1697
+ const mi = await this.instance(TENANT_PLAN_REGISTRY_SCOPE);
1698
+ rows = mi._all("TenantPlan");
1699
+ }
1700
+ catch {
1701
+ return null;
1702
+ }
1703
+ for (const row of rows) {
1704
+ const spec = (row.spec ?? {});
1705
+ if (spec.tenant === tenant) {
1706
+ return row;
1707
+ }
1708
+ }
1709
+ return null;
1710
+ }
1613
1711
  /**
1614
1712
  * Resolve a VoicePolicy from the `_lib` scope by metadata name.
1615
1713
  * Returns the matching Document, the first policy as a fallback, or null
@@ -16,6 +16,7 @@ import type { CompositionResult, KindPort, SourcePort } from "./protocols.js";
16
16
  import type { CompositionProfile } from "./composition-resolver.js";
17
17
  import type { PreviewBlock } from "./preview.js";
18
18
  import { PromptBuilder } from "./prompt-builder.js";
19
+ import type { ExplainOpts, PromptExplanation } from "./prompt-builder.js";
19
20
  import { CompositionEngine } from "./composition-resolver.js";
20
21
  import { Navigator } from "./navigator.js";
21
22
  import { LockManager } from "./lock-manager.js";
@@ -199,6 +200,13 @@ export declare class ManifestInstance {
199
200
  */
200
201
  ref(value: string): Promise<string>;
201
202
  buildPrompt(opts?: BuildPromptOpts): Promise<string>;
203
+ /**
204
+ * Compose the agent AND return per-section provenance. The `prompt` field is
205
+ * byte-identical to {@link buildPrompt}; `sections` attributes each composed
206
+ * section (instruction, soul, skills, guardrails) to its source artifact,
207
+ * hash, version, and layer/overlay origin. See {@link PromptBuilder.explain}.
208
+ */
209
+ explainPrompt(opts?: ExplainOpts): Promise<PromptExplanation>;
202
210
  resolve(layers?: Record<string, string>): ManifestInstance;
203
211
  generateLock(): Lockfile;
204
212
  /** Auto-register Hook documents on the kernel's HookRegistry. */
@@ -351,6 +351,15 @@ export class ManifestInstance {
351
351
  async buildPrompt(opts) {
352
352
  return this.prompt.build(opts);
353
353
  }
354
+ /**
355
+ * Compose the agent AND return per-section provenance. The `prompt` field is
356
+ * byte-identical to {@link buildPrompt}; `sections` attributes each composed
357
+ * section (instruction, soul, skills, guardrails) to its source artifact,
358
+ * hash, version, and layer/overlay origin. See {@link PromptBuilder.explain}.
359
+ */
360
+ async explainPrompt(opts) {
361
+ return this.prompt.explain(opts);
362
+ }
354
363
  // -- Layers ---------------------------------------------------------------
355
364
  resolve(layers) {
356
365
  if (!layers)
@@ -54,6 +54,16 @@ export declare const SCHEMA_ANNOTATION_FORMATS: readonly string[];
54
54
  * goes through here so options stay in one place. strict:false accepts the
55
55
  * lenient JSON Schema shapes users author in YAML. */
56
56
  export declare function createAjv(): Ajv;
57
+ /** Normalize a spec for hashing: drop volatile + transport fields and fold a
58
+ * resolved instruction_file into its inline body (s-sync-s1). Shared by
59
+ * KindBase and DeclarativeKindPort so both digest identically — never a second
60
+ * copy. Twin of Python KindBase._canonical_spec. */
61
+ export declare function canonicalSpecOf(volatileSpecFields: ReadonlySet<string>, spec: Record<string, unknown>): Record<string, unknown>;
62
+ /** Stable SHA-256 of a doc's authored identity — the source diff/sync basis
63
+ * (s-sync-s1). Invariant to key order, formatting, volatile stamps, and
64
+ * instruction_file-vs-inline; sensitive to real content. Twin of Python
65
+ * KindBase.canonical_digest. */
66
+ export declare function canonicalDigestOf(kind: string, volatileSpecFields: ReadonlySet<string>, doc: Document): string;
57
67
  export declare abstract class KindBase implements Omit<KindPort, "apiVersion" | "kind" | "alias" | "storage"> {
58
68
  readonly origin?: string;
59
69
  readonly isPromptTarget: boolean;
@@ -83,6 +83,36 @@ function _stableStringify(value) {
83
83
  keys.map((k) => JSON.stringify(k) + ":" + _stableStringify(obj[k])).join(",") +
84
84
  "}");
85
85
  }
86
+ /** Normalize a spec for hashing: drop volatile + transport fields and fold a
87
+ * resolved instruction_file into its inline body (s-sync-s1). Shared by
88
+ * KindBase and DeclarativeKindPort so both digest identically — never a second
89
+ * copy. Twin of Python KindBase._canonical_spec. */
90
+ export function canonicalSpecOf(volatileSpecFields, spec) {
91
+ const out = {};
92
+ for (const [k, v] of Object.entries(spec ?? {})) {
93
+ if (volatileSpecFields.has(k))
94
+ continue;
95
+ out[k] = v;
96
+ }
97
+ delete out["source_files"]; // pure transport, never identity
98
+ if (out["instruction_file"] && out["instruction"]) {
99
+ delete out["instruction_file"]; // file-backed == inline once resolved
100
+ }
101
+ return out;
102
+ }
103
+ /** Stable SHA-256 of a doc's authored identity — the source diff/sync basis
104
+ * (s-sync-s1). Invariant to key order, formatting, volatile stamps, and
105
+ * instruction_file-vs-inline; sensitive to real content. Twin of Python
106
+ * KindBase.canonical_digest. */
107
+ export function canonicalDigestOf(kind, volatileSpecFields, doc) {
108
+ const spec = (doc.spec ?? {});
109
+ const payload = {
110
+ kind: doc.kind ?? kind,
111
+ name: doc.name ?? null,
112
+ spec: canonicalSpecOf(volatileSpecFields, { ...spec }),
113
+ };
114
+ return createHash("sha256").update(_stableStringify(payload), "utf8").digest("hex");
115
+ }
86
116
  export class KindBase {
87
117
  // ---- Identity (subclasses MUST set these) -----------------------
88
118
  // abstract apiVersion: string;
@@ -233,29 +263,17 @@ export class KindBase {
233
263
  return [];
234
264
  }
235
265
  // ---- Source-sync digest (s-sync-s1; twin of Python canonical_digest) ----
266
+ // The algorithm lives in the module-level `canonicalSpecOf`/`canonicalDigestOf`
267
+ // helpers so DeclarativeKindPort (meta.ts) shares the SAME code instead of a
268
+ // divergent copy — a descriptor Kind digests byte-identically to its
269
+ // hand-written twin (twin of Python meta.py delegating to KindBase).
236
270
  canonicalSpec(spec) {
237
- const out = {};
238
- for (const [k, v] of Object.entries(spec ?? {})) {
239
- if (this.volatileSpecFields.has(k))
240
- continue;
241
- out[k] = v;
242
- }
243
- delete out["source_files"]; // pure transport, never identity
244
- if (out["instruction_file"] && out["instruction"]) {
245
- delete out["instruction_file"]; // file-backed == inline once resolved
246
- }
247
- return out;
271
+ return canonicalSpecOf(this.volatileSpecFields, spec);
248
272
  }
249
273
  /** Stable SHA-256 of the doc's authored identity — basis for source
250
274
  * diff/sync. Invariant to key order, formatting, volatile stamps, and
251
275
  * instruction_file-vs-inline; sensitive to real content. */
252
276
  canonicalDigest(doc) {
253
- const spec = (doc.spec ?? {});
254
- const payload = {
255
- kind: doc.kind ?? this.kind,
256
- name: doc.name ?? null,
257
- spec: this.canonicalSpec({ ...spec }),
258
- };
259
- return createHash("sha256").update(_stableStringify(payload), "utf8").digest("hex");
277
+ return canonicalDigestOf(this.kind, this.volatileSpecFields, doc);
260
278
  }
261
279
  }
@@ -125,6 +125,7 @@ export declare class DeclarativeKindPort implements KindPort, DeclarativeMarker
125
125
  * Required fields surface first, optional after.
126
126
  */
127
127
  preview(doc: Document): PreviewBlock[];
128
+ canonicalDigest(doc: Document): string;
128
129
  static fromTyped(typedDef: TypedKindDefinition): DeclarativeKindPort;
129
130
  }
130
131
  /**
@@ -38,7 +38,7 @@ function hslToHex(h, s, l) {
38
38
  const toHex = (v) => Math.round((v + m) * 255).toString(16).padStart(2, "0");
39
39
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
40
40
  }
41
- import { DEFAULT_VOLATILE_SPEC_FIELDS, createAjv } from "./kind_base.js";
41
+ import { DEFAULT_VOLATILE_SPEC_FIELDS, canonicalDigestOf, createAjv } from "./kind_base.js";
42
42
  import { StudioUIMetadata } from "./studio_ui.js";
43
43
  import { SD, } from "./protocols.js";
44
44
  /** Convert a literal YAML storage dict into a StorageDescriptor. */
@@ -714,6 +714,16 @@ export class DeclarativeKindPort {
714
714
  }
715
715
  return blocks;
716
716
  }
717
+ // ---- Source-sync digest (s-sync-s1) -------------------------------------
718
+ // Share KindBase's canonical algorithm VERBATIM (the same `canonicalDigestOf`
719
+ // helper) so a descriptor (F3) Kind digests byte-identically to its
720
+ // hand-written twin — never a divergent second copy. `this.volatileSpecFields`
721
+ // (KindBase defaults ∪ descriptor extras) + `this.kind` feed it. Twin of the
722
+ // Python fix (meta.py delegating to KindBase); keeps the port digest-capable
723
+ // for a future TS source-sync path (no TS consumer exercises it today).
724
+ canonicalDigest(doc) {
725
+ return canonicalDigestOf(this.kind, this.volatileSpecFields, doc);
726
+ }
717
727
  static fromTyped(typedDef) {
718
728
  return new DeclarativeKindPort(typedDef);
719
729
  }
@@ -517,9 +517,9 @@ export declare const AgentSpecSchema: z.ZodObject<{
517
517
  type?: string | undefined;
518
518
  skills?: string[] | undefined;
519
519
  guardrails?: string[] | undefined;
520
- instruction?: string | undefined;
521
520
  promptTemplate?: string | undefined;
522
521
  layout?: string | undefined;
522
+ instruction?: string | undefined;
523
523
  model?: string | undefined;
524
524
  instruction_file?: string | undefined;
525
525
  tags?: string[] | undefined;
@@ -737,9 +737,9 @@ export declare const AgentSchema: z.ZodObject<{
737
737
  type?: string | undefined;
738
738
  skills?: string[] | undefined;
739
739
  guardrails?: string[] | undefined;
740
- instruction?: string | undefined;
741
740
  promptTemplate?: string | undefined;
742
741
  layout?: string | undefined;
742
+ instruction?: string | undefined;
743
743
  model?: string | undefined;
744
744
  instruction_file?: string | undefined;
745
745
  tags?: string[] | undefined;
@@ -865,9 +865,9 @@ export declare const AgentSchema: z.ZodObject<{
865
865
  type?: string | undefined;
866
866
  skills?: string[] | undefined;
867
867
  guardrails?: string[] | undefined;
868
- instruction?: string | undefined;
869
868
  promptTemplate?: string | undefined;
870
869
  layout?: string | undefined;
870
+ instruction?: string | undefined;
871
871
  model?: string | undefined;
872
872
  instruction_file?: string | undefined;
873
873
  tags?: string[] | undefined;
@@ -1516,15 +1516,15 @@ export declare const AgentDefinitionSchema: z.ZodObject<{
1516
1516
  export type TypedAgentDefinition = z.output<typeof AgentDefinitionSchema>;
1517
1517
  export declare const GuardrailSpecSchema: z.ZodObject<{
1518
1518
  rules: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
1519
- severity: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1520
- scope: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1519
+ severity: z.ZodDefault<z.ZodOptional<z.ZodEnum<["warn", "error", "hard"]>>>;
1520
+ scope: z.ZodDefault<z.ZodOptional<z.ZodEnum<["input", "output", "both"]>>>;
1521
1521
  }, "strip", z.ZodTypeAny, {
1522
- scope: string;
1523
- severity: string;
1522
+ scope: "input" | "both" | "output";
1523
+ severity: "error" | "warn" | "hard";
1524
1524
  rules: string[];
1525
1525
  }, {
1526
- scope?: string | undefined;
1527
- severity?: string | undefined;
1526
+ scope?: "input" | "both" | "output" | undefined;
1527
+ severity?: "error" | "warn" | "hard" | undefined;
1528
1528
  rules?: string[] | undefined;
1529
1529
  }>;
1530
1530
  export declare const GuardrailSchema: z.ZodObject<{
@@ -1554,15 +1554,15 @@ export declare const GuardrailSchema: z.ZodObject<{
1554
1554
  }>;
1555
1555
  spec: z.ZodDefault<z.ZodObject<{
1556
1556
  rules: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
1557
- severity: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1558
- scope: z.ZodDefault<z.ZodOptional<z.ZodString>>;
1557
+ severity: z.ZodDefault<z.ZodOptional<z.ZodEnum<["warn", "error", "hard"]>>>;
1558
+ scope: z.ZodDefault<z.ZodOptional<z.ZodEnum<["input", "output", "both"]>>>;
1559
1559
  }, "strip", z.ZodTypeAny, {
1560
- scope: string;
1561
- severity: string;
1560
+ scope: "input" | "both" | "output";
1561
+ severity: "error" | "warn" | "hard";
1562
1562
  rules: string[];
1563
1563
  }, {
1564
- scope?: string | undefined;
1565
- severity?: string | undefined;
1564
+ scope?: "input" | "both" | "output" | undefined;
1565
+ severity?: "error" | "warn" | "hard" | undefined;
1566
1566
  rules?: string[] | undefined;
1567
1567
  }>>;
1568
1568
  }, "strip", z.ZodTypeAny, {
@@ -1575,8 +1575,8 @@ export declare const GuardrailSchema: z.ZodObject<{
1575
1575
  labels: Record<string, string>;
1576
1576
  };
1577
1577
  spec: {
1578
- scope: string;
1579
- severity: string;
1578
+ scope: "input" | "both" | "output";
1579
+ severity: "error" | "warn" | "hard";
1580
1580
  rules: string[];
1581
1581
  };
1582
1582
  apiVersion: "github.com/ruinosus/dna/v1";
@@ -1593,8 +1593,8 @@ export declare const GuardrailSchema: z.ZodObject<{
1593
1593
  apiVersion: "github.com/ruinosus/dna/v1";
1594
1594
  kind: "Guardrail";
1595
1595
  spec?: {
1596
- scope?: string | undefined;
1597
- severity?: string | undefined;
1596
+ scope?: "input" | "both" | "output" | undefined;
1597
+ severity?: "error" | "warn" | "hard" | undefined;
1598
1598
  rules?: string[] | undefined;
1599
1599
  } | undefined;
1600
1600
  }>;
@@ -1634,9 +1634,9 @@ export declare const KindDefinitionSpecSchema: z.ZodObject<{
1634
1634
  description_fallback_field: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1635
1635
  }, "strip", z.ZodTypeAny, {
1636
1636
  origin: string;
1637
+ alias: string;
1637
1638
  is_root: boolean;
1638
1639
  flatten_in_context: boolean;
1639
- alias: string;
1640
1640
  storage: Record<string, unknown>;
1641
1641
  plane: "composition" | "record";
1642
1642
  schema: Record<string, unknown>;
@@ -1753,9 +1753,9 @@ export declare const KindDefinitionSchema: z.ZodObject<{
1753
1753
  description_fallback_field: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1754
1754
  }, "strip", z.ZodTypeAny, {
1755
1755
  origin: string;
1756
+ alias: string;
1756
1757
  is_root: boolean;
1757
1758
  flatten_in_context: boolean;
1758
- alias: string;
1759
1759
  storage: Record<string, unknown>;
1760
1760
  plane: "composition" | "record";
1761
1761
  schema: Record<string, unknown>;
@@ -1818,9 +1818,9 @@ export declare const KindDefinitionSchema: z.ZodObject<{
1818
1818
  tenant_scope_declared: boolean;
1819
1819
  summary: Record<string, unknown> | null;
1820
1820
  origin: string;
1821
+ alias: string;
1821
1822
  is_root: boolean;
1822
1823
  flatten_in_context: boolean;
1823
- alias: string;
1824
1824
  storage: Record<string, unknown>;
1825
1825
  plane: "composition" | "record";
1826
1826
  schema: Record<string, unknown>;
@@ -1891,9 +1891,9 @@ export declare const KindDefinitionSchema: z.ZodObject<{
1891
1891
  tenant_scope_declared: boolean;
1892
1892
  summary: Record<string, unknown> | null;
1893
1893
  origin: string;
1894
+ alias: string;
1894
1895
  is_root: boolean;
1895
1896
  flatten_in_context: boolean;
1896
- alias: string;
1897
1897
  storage: Record<string, unknown>;
1898
1898
  plane: "composition" | "record";
1899
1899
  schema: Record<string, unknown>;
@@ -396,8 +396,14 @@ export const AgentDefinitionSchema = z.object({
396
396
  // ---------------------------------------------------------------------------
397
397
  export const GuardrailSpecSchema = z.object({
398
398
  rules: z.array(z.string()).default([]),
399
- severity: z.string().optional().default("warn"),
400
- scope: z.string().optional().default("both"),
399
+ // Constrained fields — the documented severity/scope contracts. Typed as
400
+ // z.enum so zodSpecToJsonSchema emits `{type:"string", enum:[...]}`
401
+ // (i-validation-shallow): a bare z.string() degraded to `{type:"string"}` and
402
+ // accepted `severity: critical`/garbage on the write path. Parity: the Python
403
+ // twin's GuardrailSpec uses Literal[...] → the same enum shape. warn lets the
404
+ // turn continue; error fails the turn; hard refuses to answer.
405
+ severity: z.enum(["warn", "error", "hard"]).optional().default("warn"),
406
+ scope: z.enum(["input", "output", "both"]).optional().default("both"),
401
407
  });
402
408
  export const GuardrailSchema = z.object({
403
409
  apiVersion: z.literal("github.com/ruinosus/dna/v1"),
@@ -9,6 +9,41 @@
9
9
  * return identical results.
10
10
  */
11
11
  import type { ManifestInstance, BuildPromptOpts } from "./instance.js";
12
+ /** Provenance for ONE composed prompt section (instruction / soul / skill /
13
+ * guardrail). Reconstructed from the layout template + depFilters +
14
+ * flattenInContext plus the layer provenance `resolveDocument` returns — NOT
15
+ * by re-running composition. */
16
+ export interface SectionProvenance {
17
+ /** Label, e.g. `instruction` / `soul:warm` / `skill:greeting`. */
18
+ section: string;
19
+ /** Contributing Kind (`Agent` / `Soul` / `Skill` / `Guardrail`). */
20
+ kind: string;
21
+ /** Contributing document name. */
22
+ name: string;
23
+ /** Canonical source artifact path, e.g. `skills/greeting/SKILL.md`. */
24
+ source: string;
25
+ /** SHA-256 of the resolved (effective) raw doc, or null. */
26
+ hash: string | null;
27
+ /** `metadata.version` of the resolved doc, when the author set one. */
28
+ version: string | null;
29
+ /** Effective layer scope the section resolved from. */
30
+ origin: string;
31
+ /** True when `origin` differs from the requested scope (inherited). */
32
+ isInherited: boolean;
33
+ /** True when a TENANT overlay changed this section vs the base. */
34
+ overriddenByTenant: boolean;
35
+ }
36
+ /** The composed prompt PLUS a section→provenance map. `prompt` is
37
+ * byte-identical to `buildPrompt` — explain mode never re-renders. */
38
+ export interface PromptExplanation {
39
+ prompt: string;
40
+ sections: SectionProvenance[];
41
+ }
42
+ /** Options for {@link PromptBuilder.explain}. */
43
+ export interface ExplainOpts extends BuildPromptOpts {
44
+ /** Resolve with this tenant's overlays (marks overridden sections). */
45
+ tenant?: string | null;
46
+ }
12
47
  export declare class PromptBuilder {
13
48
  private host;
14
49
  constructor(host: ManifestInstance);
@@ -17,6 +52,35 @@ export declare class PromptBuilder {
17
52
  * Equivalent to `mi.buildPrompt(opts)`.
18
53
  */
19
54
  build(opts?: BuildPromptOpts): Promise<string>;
55
+ /**
56
+ * Compose the agent AND return per-section provenance.
57
+ *
58
+ * Returns the composed prompt (byte-identical to {@link build} — explain
59
+ * mode never re-renders) plus a section→provenance map: the source artifact,
60
+ * content hash, version, and layer/overlay origin of each declared
61
+ * composition input (instruction, soul, skills, guardrails). Provenance is
62
+ * reconstructed from the kernel's own declared blocks (layout template +
63
+ * depFilters + flattenInContext) + the layer provenance `resolveDocument`
64
+ * already returns. 1:1 with Python `PromptBuilder.explain`.
65
+ */
66
+ explain(opts?: ExplainOpts): Promise<PromptExplanation>;
67
+ private _defaultAgentName;
68
+ private _mergeSlots;
69
+ private _effectiveTemplate;
70
+ private _kpByAlias;
71
+ /**
72
+ * Reconstruct the ordered composition inputs → `[label, kp, name]`. A
73
+ * depFilter field contributes a section iff the layout actually renders it —
74
+ * a flatten Kind whose flatten var appears in the template (Soul →
75
+ * soul_content) or a Kind whose alias appears as a Mustache section
76
+ * (Skill/Guardrail). Non-prompt deps (tools, actors) fall out.
77
+ */
78
+ private _sectionSpecs;
79
+ private _contributesToPrompt;
80
+ private _resolve;
81
+ private static _docHash;
82
+ private _sectionProvenance;
83
+ private static _sourcePath;
20
84
  private _findAgent;
21
85
  private _buildContext;
22
86
  private _renderPrompt;