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,215 @@
|
|
|
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
|
+
// ─── Severity ordering (lower = more severe) ─────────────────────────
|
|
12
|
+
const SEVERITY_ORDER = {
|
|
13
|
+
critical: 0,
|
|
14
|
+
high: 1,
|
|
15
|
+
medium: 2,
|
|
16
|
+
low: 3,
|
|
17
|
+
};
|
|
18
|
+
// ─── Compliance mappings ─────────────────────────────────────────────
|
|
19
|
+
const COMPLIANCE_MAPPINGS = {
|
|
20
|
+
lgpd: {
|
|
21
|
+
critical: ["Art. 6 (tratamento de dados)", "Art. 46 (seguranca)"],
|
|
22
|
+
high: ["Art. 50 (boas praticas)"],
|
|
23
|
+
},
|
|
24
|
+
gdpr: {
|
|
25
|
+
critical: ["Art. 25 (data protection by design)", "Art. 35 (DPIA)"],
|
|
26
|
+
high: ["Art. 32 (security of processing)"],
|
|
27
|
+
},
|
|
28
|
+
nist_ai_rmf: {
|
|
29
|
+
critical: ["MAP-1.1 (intended purpose)", "MEASURE-2.6 (bias)"],
|
|
30
|
+
high: ["MANAGE-2.2 (risk response)"],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
function frontmatter(reportType, scope) {
|
|
34
|
+
const now = new Date().toISOString();
|
|
35
|
+
return `---\nreport: ${reportType}\ngenerated_at: ${now}\nscope: ${scope}\n---\n\n`;
|
|
36
|
+
}
|
|
37
|
+
export class ReportBuilder {
|
|
38
|
+
_mi;
|
|
39
|
+
constructor(mi) {
|
|
40
|
+
this._mi = mi;
|
|
41
|
+
}
|
|
42
|
+
// ─── evalSummary ──────────────────────────────────────────────────
|
|
43
|
+
/** Generate an eval summary report grouped by suite.
|
|
44
|
+
* If `suite` is given, only that suite is included. */
|
|
45
|
+
evalSummary(suite) {
|
|
46
|
+
let runs = this._mi._all("EvalRun");
|
|
47
|
+
if (suite) {
|
|
48
|
+
runs = runs.filter((r) => r.spec.suite === suite);
|
|
49
|
+
}
|
|
50
|
+
let md = frontmatter("eval_summary", this._mi.scope);
|
|
51
|
+
md += "# Eval Summary\n\n";
|
|
52
|
+
if (runs.length === 0) {
|
|
53
|
+
md += "_No eval runs found._\n";
|
|
54
|
+
return md;
|
|
55
|
+
}
|
|
56
|
+
// Group by suite, keep latest per suite (by saved_at)
|
|
57
|
+
const bySuite = new Map();
|
|
58
|
+
for (const run of runs) {
|
|
59
|
+
const spec = run.spec;
|
|
60
|
+
const s = spec.suite ?? "unknown";
|
|
61
|
+
const existing = bySuite.get(s);
|
|
62
|
+
if (!existing) {
|
|
63
|
+
bySuite.set(s, run);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const newTs = spec.saved_at ?? "";
|
|
67
|
+
const oldTs = existing.spec.saved_at ?? "";
|
|
68
|
+
if (newTs > oldTs) {
|
|
69
|
+
bySuite.set(s, run);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const sortedSuites = [...bySuite.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
74
|
+
for (const [suiteName, run] of sortedSuites) {
|
|
75
|
+
const spec = run.spec;
|
|
76
|
+
const total = spec.total ?? 0;
|
|
77
|
+
const passed = spec.passed ?? 0;
|
|
78
|
+
const rate = total ? `${((passed / total) * 100).toFixed(1)}%` : "N/A";
|
|
79
|
+
const agg = spec.aggregate_score;
|
|
80
|
+
const aggStr = agg != null ? agg.toFixed(2) : "N/A";
|
|
81
|
+
const cost = spec.total_cost_usd;
|
|
82
|
+
const costStr = cost != null ? `$${cost.toFixed(4)}` : "N/A";
|
|
83
|
+
const tokens = spec.total_tokens;
|
|
84
|
+
const tokensStr = tokens != null ? String(tokens) : "N/A";
|
|
85
|
+
md += `## Suite: ${suiteName}\n\n`;
|
|
86
|
+
md += `- **Pass rate:** ${rate}\n`;
|
|
87
|
+
md += `- **Aggregate score:** ${aggStr}\n`;
|
|
88
|
+
md += `- **Cost:** ${costStr}\n`;
|
|
89
|
+
md += `- **Tokens:** ${tokensStr}\n\n`;
|
|
90
|
+
const results = (spec.results ?? []);
|
|
91
|
+
if (results.length > 0) {
|
|
92
|
+
md += "| Case | Status | Score |\n";
|
|
93
|
+
md += "|------|--------|-------|\n";
|
|
94
|
+
for (const r of results) {
|
|
95
|
+
const caseName = r.case ?? "";
|
|
96
|
+
const status = r.status ?? "";
|
|
97
|
+
const score = r.score;
|
|
98
|
+
const scoreStr = score != null ? score.toFixed(2) : "-";
|
|
99
|
+
md += `| ${caseName} | ${status} | ${scoreStr} |\n`;
|
|
100
|
+
}
|
|
101
|
+
md += "\n";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return md;
|
|
105
|
+
}
|
|
106
|
+
// ─── findingsSummary ──────────────────────────────────────────────
|
|
107
|
+
/** Generate a findings summary grouped by severity.
|
|
108
|
+
* Filters out findings with severity below `minSeverity`. */
|
|
109
|
+
findingsSummary(minSeverity = "low") {
|
|
110
|
+
const findings = this._mi._all("Finding");
|
|
111
|
+
const minLevel = SEVERITY_ORDER[minSeverity] ?? 3;
|
|
112
|
+
const filtered = findings.filter((f) => {
|
|
113
|
+
const sev = f.spec.severity;
|
|
114
|
+
return (SEVERITY_ORDER[sev ?? "low"] ?? 3) <= minLevel;
|
|
115
|
+
});
|
|
116
|
+
let md = frontmatter("findings_summary", this._mi.scope);
|
|
117
|
+
md += "# Findings Summary\n\n";
|
|
118
|
+
if (filtered.length === 0) {
|
|
119
|
+
md += "_No findings found._\n";
|
|
120
|
+
return md;
|
|
121
|
+
}
|
|
122
|
+
// Group by severity
|
|
123
|
+
const bySeverity = new Map();
|
|
124
|
+
for (const f of filtered) {
|
|
125
|
+
const sev = f.spec.severity ?? "low";
|
|
126
|
+
const arr = bySeverity.get(sev) ?? [];
|
|
127
|
+
arr.push(f);
|
|
128
|
+
bySeverity.set(sev, arr);
|
|
129
|
+
}
|
|
130
|
+
for (const sev of ["critical", "high", "medium", "low"]) {
|
|
131
|
+
const group = bySeverity.get(sev);
|
|
132
|
+
if (!group || group.length === 0)
|
|
133
|
+
continue;
|
|
134
|
+
md += `## ${sev.toUpperCase()} (${group.length})\n\n`;
|
|
135
|
+
for (const f of group) {
|
|
136
|
+
const spec = f.spec;
|
|
137
|
+
const title = spec.title ?? f.name;
|
|
138
|
+
const status = spec.status ?? "open";
|
|
139
|
+
const recommendation = spec.recommendation ?? "";
|
|
140
|
+
md += `### ${title}\n\n`;
|
|
141
|
+
md += `- **Status:** ${status}\n`;
|
|
142
|
+
if (recommendation) {
|
|
143
|
+
md += `- **Recommendation:** ${recommendation}\n`;
|
|
144
|
+
}
|
|
145
|
+
md += "\n";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return md;
|
|
149
|
+
}
|
|
150
|
+
// ─── evidenceManifest ─────────────────────────────────────────────
|
|
151
|
+
/** Generate a manifest of all evidence documents. */
|
|
152
|
+
evidenceManifest() {
|
|
153
|
+
const evidence = this._mi._all("Evidence");
|
|
154
|
+
let md = frontmatter("evidence_manifest", this._mi.scope);
|
|
155
|
+
md += "# Evidence Manifest\n\n";
|
|
156
|
+
if (evidence.length === 0) {
|
|
157
|
+
md += "_No evidence documents found._\n";
|
|
158
|
+
return md;
|
|
159
|
+
}
|
|
160
|
+
md += "| Event Type | Document Ref | SHA-256 | Captured At |\n";
|
|
161
|
+
md += "|------------|-------------|---------|-------------|\n";
|
|
162
|
+
for (const e of evidence) {
|
|
163
|
+
const spec = e.spec;
|
|
164
|
+
const eventType = spec.event_type ?? "";
|
|
165
|
+
const docRef = spec.document_ref ?? "";
|
|
166
|
+
const sha = spec.sha256 ?? "";
|
|
167
|
+
const shaShort = sha.length > 12 ? sha.slice(0, 12) + "..." : sha;
|
|
168
|
+
const captured = spec.captured_at ?? "";
|
|
169
|
+
md += `| ${eventType} | ${docRef} | ${shaShort} | ${captured} |\n`;
|
|
170
|
+
}
|
|
171
|
+
md += "\n";
|
|
172
|
+
return md;
|
|
173
|
+
}
|
|
174
|
+
// ─── complianceMatrix ─────────────────────────────────────────────
|
|
175
|
+
/** Generate a compliance matrix mapping findings to regulatory articles.
|
|
176
|
+
* Supported frameworks: lgpd, gdpr, nist_ai_rmf. */
|
|
177
|
+
complianceMatrix(framework) {
|
|
178
|
+
let md = frontmatter("compliance_matrix", this._mi.scope);
|
|
179
|
+
md += `# Compliance Matrix: ${framework.toUpperCase()}\n\n`;
|
|
180
|
+
const mapping = COMPLIANCE_MAPPINGS[framework];
|
|
181
|
+
if (!mapping) {
|
|
182
|
+
const supported = Object.keys(COMPLIANCE_MAPPINGS).sort().join(", ");
|
|
183
|
+
md += `_Unknown framework: ${framework}. Supported: ${supported}._\n`;
|
|
184
|
+
return md;
|
|
185
|
+
}
|
|
186
|
+
const findings = this._mi._all("Finding");
|
|
187
|
+
md += "| Severity | Count | Regulatory Articles |\n";
|
|
188
|
+
md += "|----------|-------|--------------------|\n";
|
|
189
|
+
for (const sev of ["critical", "high", "medium", "low"]) {
|
|
190
|
+
const articles = mapping[sev] ?? [];
|
|
191
|
+
const count = findings.filter((f) => f.spec.severity === sev).length;
|
|
192
|
+
const articlesStr = articles.length > 0 ? articles.join(", ") : "-";
|
|
193
|
+
md += `| ${sev.toUpperCase()} | ${count} | ${articlesStr} |\n`;
|
|
194
|
+
}
|
|
195
|
+
md += "\n";
|
|
196
|
+
if (findings.length > 0) {
|
|
197
|
+
md += "## Findings by Article\n\n";
|
|
198
|
+
for (const sev of ["critical", "high"]) {
|
|
199
|
+
const articles = mapping[sev] ?? [];
|
|
200
|
+
const sevFindings = findings.filter((f) => f.spec.severity === sev);
|
|
201
|
+
if (sevFindings.length > 0 && articles.length > 0) {
|
|
202
|
+
for (const article of articles) {
|
|
203
|
+
md += `### ${article}\n\n`;
|
|
204
|
+
for (const f of sevFindings) {
|
|
205
|
+
const title = f.spec.title ?? f.name;
|
|
206
|
+
md += `- ${title}\n`;
|
|
207
|
+
}
|
|
208
|
+
md += "\n";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return md;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composition Engine V2 — resolution types + merge utilities (TS twin).
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with python/dna/kernel/resolver.py
|
|
5
|
+
* (Phase 17, Story s-comp-f2-resolver / s-composition-v2-ts-twin).
|
|
6
|
+
*
|
|
7
|
+
* NB: This is the V2 *resolver* — distinct from `composition.ts` (which defines
|
|
8
|
+
* CompositionProfile dependency wiring between Kinds). The two coexist with
|
|
9
|
+
* clear naming until a future Phase 17 cleanup unifies them.
|
|
10
|
+
*
|
|
11
|
+
* This module defines the data shapes returned by `kernel.resolveDocument` plus
|
|
12
|
+
* the pure merge utilities. The actual resolver *method* lives on the Kernel
|
|
13
|
+
* class on the Python side (`Kernel.resolve_document`); porting that full
|
|
14
|
+
* orchestration (parent-chain walk + observers + granular cache) is a separate,
|
|
15
|
+
* larger concern. What the story names — "the resolver" (provenance + field
|
|
16
|
+
* merge) — is this module.
|
|
17
|
+
*
|
|
18
|
+
* Wire parity: `serialize()` emits the SAME snake_case keys as Python
|
|
19
|
+
* (scope/tenant/found/contributed/version_sha/effective_layer/
|
|
20
|
+
* contributions_by_field/is_inherited) so an HTTP consumer cannot tell which
|
|
21
|
+
* runtime produced the response.
|
|
22
|
+
*
|
|
23
|
+
* Resolution chain for (scope=S, tenant=T, kind=K, name=N):
|
|
24
|
+
* walk Genome.parent_scope transitively, building ordered layers:
|
|
25
|
+
* L0 = (S, T) local + tenant overlay
|
|
26
|
+
* L1 = (S, null) local base
|
|
27
|
+
* L2 = (P, T) parent + tenant overlay (if rule allows)
|
|
28
|
+
* L3 = (P, null) parent base
|
|
29
|
+
* ...
|
|
30
|
+
* Apply merge strategy from LayerPolicy.composition_rules[K]:
|
|
31
|
+
* - override_full → first non-null layer wins entirely
|
|
32
|
+
* - field_level → deep-merge specs; later layers contribute individual
|
|
33
|
+
* fields and provenance tracks which layer set each field.
|
|
34
|
+
*
|
|
35
|
+
* Bootstrap Kinds (Genome, LayerPolicy, KindDefinition) are NEVER inherited.
|
|
36
|
+
*/
|
|
37
|
+
/** Kinds excluded from inheritance regardless of LayerPolicy. Structural
|
|
38
|
+
* (Genome = scope identity; LayerPolicy = the policy Kind itself;
|
|
39
|
+
* KindDefinition = registered before docs parse, can't be overlaid). */
|
|
40
|
+
export declare const BOOTSTRAP_KINDS: ReadonlySet<string>;
|
|
41
|
+
/** Scope inheritance default = DENYLIST (s-platform-inherit-by-default).
|
|
42
|
+
* When a scope has NO LayerPolicy with composition_rules, EVERY Kind defaults
|
|
43
|
+
* to scope_inheritance=enabled EXCEPT the per-scope ledger + structural Kinds
|
|
44
|
+
* below. Mirrors the kernel's `_NON_INHERITABLE_KINDS`. */
|
|
45
|
+
export declare const DEFAULT_NON_INHERITABLE_KINDS_V1: ReadonlySet<string>;
|
|
46
|
+
/** Display set ONLY (NOT the inheritance source of truth anymore): the
|
|
47
|
+
* composition-summary endpoint iterates this to surface per-Kind
|
|
48
|
+
* local/inherited counts in the Studio sidebar. Membership semantics live in
|
|
49
|
+
* the denylist above. */
|
|
50
|
+
export declare const DEFAULT_INHERITABLE_KINDS_V1: ReadonlySet<string>;
|
|
51
|
+
/** Hard limit on parent_scope chain depth — guards against runaway loops
|
|
52
|
+
* (cycle detection runs first, but this is a belt-and-suspenders cap). */
|
|
53
|
+
export declare const MAX_RESOLUTION_DEPTH = 16;
|
|
54
|
+
/** A raw manifest document dict (pre-`Document` parse) — the payload half
|
|
55
|
+
* of a {@link Contribution}. Twin of the Py `Raw = dict[str, Any]` alias. */
|
|
56
|
+
export type Raw = Record<string, unknown>;
|
|
57
|
+
/** One step in the resolution chain — a single (scope, tenant) pair consulted
|
|
58
|
+
* by the resolver.
|
|
59
|
+
*
|
|
60
|
+
* - `found` records whether the source had ANY doc at this layer.
|
|
61
|
+
* - `contributed` flips true when this layer ACTUALLY influenced the final
|
|
62
|
+
* merged doc (override_full: only the winning layer; field_level: possibly
|
|
63
|
+
* several). */
|
|
64
|
+
export declare class ResolutionLayer {
|
|
65
|
+
readonly scope: string;
|
|
66
|
+
readonly tenant: string | null;
|
|
67
|
+
readonly found: boolean;
|
|
68
|
+
readonly contributed: boolean;
|
|
69
|
+
/** Version sha or content hash if the source exposes it (best-effort). */
|
|
70
|
+
readonly versionSha: string | null;
|
|
71
|
+
constructor(opts: {
|
|
72
|
+
scope: string;
|
|
73
|
+
tenant?: string | null;
|
|
74
|
+
found: boolean;
|
|
75
|
+
contributed?: boolean;
|
|
76
|
+
versionSha?: string | null;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Ordered list of layers consulted, highest-priority-first.
|
|
80
|
+
*
|
|
81
|
+
* Layer order is:
|
|
82
|
+
* [local+tenant, local+base, parent+tenant, parent+base,
|
|
83
|
+
* grandparent+tenant, grandparent+base, ...]
|
|
84
|
+
* The HIGHEST priority layer is first — local-tenant beats everything else. */
|
|
85
|
+
export declare class ResolutionPath {
|
|
86
|
+
steps: ResolutionLayer[];
|
|
87
|
+
constructor(steps?: ResolutionLayer[]);
|
|
88
|
+
/** The single layer that became the doc's primary origin. For override_full
|
|
89
|
+
* this is the layer whose doc was returned wholesale; for field_level it is
|
|
90
|
+
* the highest-priority layer that contributed metadata/envelope (semantic
|
|
91
|
+
* primary owner). */
|
|
92
|
+
get effectiveLayer(): ResolutionLayer | null;
|
|
93
|
+
/** JSON-friendly serialization for HTTP responses (snake_case = Python). */
|
|
94
|
+
serialize(): Record<string, unknown>;
|
|
95
|
+
}
|
|
96
|
+
/** Result of `kernel.resolveDocument` — the doc plus full provenance.
|
|
97
|
+
*
|
|
98
|
+
* Studio renders banner/badge directly from `provenance` + `isInherited` —
|
|
99
|
+
* no client-side detection logic needed. */
|
|
100
|
+
export declare class ResolvedDocument {
|
|
101
|
+
/** The merged document (or null if not found in any layer). */
|
|
102
|
+
doc: Raw | null;
|
|
103
|
+
/** Full ordered resolution path. Includes layers consulted but not
|
|
104
|
+
* contributing. */
|
|
105
|
+
provenance: ResolutionPath;
|
|
106
|
+
/** True when `effectiveLayer.scope != requestedScope`. Convenience derived
|
|
107
|
+
* from provenance; Studio uses this for badge/banner toggle. */
|
|
108
|
+
isInherited: boolean;
|
|
109
|
+
/** Field-path → scope name. Populated when merge_strategy=field_level. Lets
|
|
110
|
+
* the Detail page show `spec.persona ← _lib` annotations. */
|
|
111
|
+
contributionsByField: Record<string, string>;
|
|
112
|
+
constructor(opts: {
|
|
113
|
+
doc: Raw | null;
|
|
114
|
+
provenance: ResolutionPath;
|
|
115
|
+
isInherited: boolean;
|
|
116
|
+
contributionsByField?: Record<string, string>;
|
|
117
|
+
});
|
|
118
|
+
serialize(): Record<string, unknown>;
|
|
119
|
+
}
|
|
120
|
+
/** A (layer, raw-doc-or-null) pair — one contribution, highest-priority-first. */
|
|
121
|
+
export type Contribution = [ResolutionLayer, Raw | null];
|
|
122
|
+
/** First non-null contribution wins entirely.
|
|
123
|
+
*
|
|
124
|
+
* Used for assetic Kinds (LottieAsset, ImagePrompt) where partial override
|
|
125
|
+
* makes no sense (binary payload is atomic).
|
|
126
|
+
*
|
|
127
|
+
* Input: contributions highest-priority-first.
|
|
128
|
+
* Output: [winningRawDoc, winningLayer] or [null, null] if all miss. */
|
|
129
|
+
export declare function mergeOverrideFull(contributions: Contribution[]): [Raw | null, ResolutionLayer | null];
|
|
130
|
+
/** Deep-merge `spec` dicts. Higher-priority layers shadow lower per-field.
|
|
131
|
+
* Returns [mergedDoc, primaryOriginLayer, fieldsByOrigin].
|
|
132
|
+
*
|
|
133
|
+
* Algorithm:
|
|
134
|
+
* - First pass: find the PRIMARY layer (highest-priority hit). Its metadata
|
|
135
|
+
* + envelope (apiVersion, kind) carry over wholesale.
|
|
136
|
+
* - Second pass: iterate contributions LOWEST → HIGHEST priority, overwriting
|
|
137
|
+
* spec keys in a fresh merged dict. After the loop the highest-priority
|
|
138
|
+
* layer's values win per-field.
|
|
139
|
+
* - Track which layer each FINAL spec field came from so the UI can render
|
|
140
|
+
* `spec.persona ← _lib` annotations.
|
|
141
|
+
*
|
|
142
|
+
* Edge cases:
|
|
143
|
+
* - All-null contributions → [null, null, {}].
|
|
144
|
+
* - Single hit → equivalent to override_full.
|
|
145
|
+
* - Spec missing or non-object → that layer skipped silently. */
|
|
146
|
+
export declare function mergeFieldLevel(contributions: Contribution[]): [Raw | null, ResolutionLayer | null, Record<string, string>];
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composition Engine V2 — resolution types + merge utilities (TS twin).
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with python/dna/kernel/resolver.py
|
|
5
|
+
* (Phase 17, Story s-comp-f2-resolver / s-composition-v2-ts-twin).
|
|
6
|
+
*
|
|
7
|
+
* NB: This is the V2 *resolver* — distinct from `composition.ts` (which defines
|
|
8
|
+
* CompositionProfile dependency wiring between Kinds). The two coexist with
|
|
9
|
+
* clear naming until a future Phase 17 cleanup unifies them.
|
|
10
|
+
*
|
|
11
|
+
* This module defines the data shapes returned by `kernel.resolveDocument` plus
|
|
12
|
+
* the pure merge utilities. The actual resolver *method* lives on the Kernel
|
|
13
|
+
* class on the Python side (`Kernel.resolve_document`); porting that full
|
|
14
|
+
* orchestration (parent-chain walk + observers + granular cache) is a separate,
|
|
15
|
+
* larger concern. What the story names — "the resolver" (provenance + field
|
|
16
|
+
* merge) — is this module.
|
|
17
|
+
*
|
|
18
|
+
* Wire parity: `serialize()` emits the SAME snake_case keys as Python
|
|
19
|
+
* (scope/tenant/found/contributed/version_sha/effective_layer/
|
|
20
|
+
* contributions_by_field/is_inherited) so an HTTP consumer cannot tell which
|
|
21
|
+
* runtime produced the response.
|
|
22
|
+
*
|
|
23
|
+
* Resolution chain for (scope=S, tenant=T, kind=K, name=N):
|
|
24
|
+
* walk Genome.parent_scope transitively, building ordered layers:
|
|
25
|
+
* L0 = (S, T) local + tenant overlay
|
|
26
|
+
* L1 = (S, null) local base
|
|
27
|
+
* L2 = (P, T) parent + tenant overlay (if rule allows)
|
|
28
|
+
* L3 = (P, null) parent base
|
|
29
|
+
* ...
|
|
30
|
+
* Apply merge strategy from LayerPolicy.composition_rules[K]:
|
|
31
|
+
* - override_full → first non-null layer wins entirely
|
|
32
|
+
* - field_level → deep-merge specs; later layers contribute individual
|
|
33
|
+
* fields and provenance tracks which layer set each field.
|
|
34
|
+
*
|
|
35
|
+
* Bootstrap Kinds (Genome, LayerPolicy, KindDefinition) are NEVER inherited.
|
|
36
|
+
*/
|
|
37
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
38
|
+
// Module-level constants — sensible defaults (V1 → V2 transition)
|
|
39
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
40
|
+
/** Kinds excluded from inheritance regardless of LayerPolicy. Structural
|
|
41
|
+
* (Genome = scope identity; LayerPolicy = the policy Kind itself;
|
|
42
|
+
* KindDefinition = registered before docs parse, can't be overlaid). */
|
|
43
|
+
export const BOOTSTRAP_KINDS = new Set([
|
|
44
|
+
"Genome",
|
|
45
|
+
"LayerPolicy",
|
|
46
|
+
"KindDefinition",
|
|
47
|
+
]);
|
|
48
|
+
/** Scope inheritance default = DENYLIST (s-platform-inherit-by-default).
|
|
49
|
+
* When a scope has NO LayerPolicy with composition_rules, EVERY Kind defaults
|
|
50
|
+
* to scope_inheritance=enabled EXCEPT the per-scope ledger + structural Kinds
|
|
51
|
+
* below. Mirrors the kernel's `_NON_INHERITABLE_KINDS`. */
|
|
52
|
+
export const DEFAULT_NON_INHERITABLE_KINDS_V1 = new Set([
|
|
53
|
+
"Story", "Issue", "Feature", "Milestone", "Roadmap",
|
|
54
|
+
"Narrative", "VibeSession", "LessonLearned", "Plan",
|
|
55
|
+
"Genome", "KindDefinition", "LayerPolicy",
|
|
56
|
+
]);
|
|
57
|
+
/** Display set ONLY (NOT the inheritance source of truth anymore): the
|
|
58
|
+
* composition-summary endpoint iterates this to surface per-Kind
|
|
59
|
+
* local/inherited counts in the Studio sidebar. Membership semantics live in
|
|
60
|
+
* the denylist above. */
|
|
61
|
+
export const DEFAULT_INHERITABLE_KINDS_V1 = new Set([
|
|
62
|
+
"Agent",
|
|
63
|
+
"LottieAsset",
|
|
64
|
+
"HtmlTemplate",
|
|
65
|
+
"Skill",
|
|
66
|
+
"ImagePrompt",
|
|
67
|
+
"Theme",
|
|
68
|
+
"PromptTemplate",
|
|
69
|
+
// s-automation-trio-extinction: JobType/HookType/ScheduleType foram extintos
|
|
70
|
+
// e unificados no Kind Automation (o inheritable herdável).
|
|
71
|
+
"Automation",
|
|
72
|
+
]);
|
|
73
|
+
/** Hard limit on parent_scope chain depth — guards against runaway loops
|
|
74
|
+
* (cycle detection runs first, but this is a belt-and-suspenders cap). */
|
|
75
|
+
export const MAX_RESOLUTION_DEPTH = 16;
|
|
76
|
+
/** One step in the resolution chain — a single (scope, tenant) pair consulted
|
|
77
|
+
* by the resolver.
|
|
78
|
+
*
|
|
79
|
+
* - `found` records whether the source had ANY doc at this layer.
|
|
80
|
+
* - `contributed` flips true when this layer ACTUALLY influenced the final
|
|
81
|
+
* merged doc (override_full: only the winning layer; field_level: possibly
|
|
82
|
+
* several). */
|
|
83
|
+
export class ResolutionLayer {
|
|
84
|
+
scope;
|
|
85
|
+
tenant;
|
|
86
|
+
found;
|
|
87
|
+
contributed;
|
|
88
|
+
/** Version sha or content hash if the source exposes it (best-effort). */
|
|
89
|
+
versionSha;
|
|
90
|
+
constructor(opts) {
|
|
91
|
+
this.scope = opts.scope;
|
|
92
|
+
this.tenant = opts.tenant ?? null;
|
|
93
|
+
this.found = opts.found;
|
|
94
|
+
this.contributed = opts.contributed ?? false;
|
|
95
|
+
this.versionSha = opts.versionSha ?? null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Ordered list of layers consulted, highest-priority-first.
|
|
99
|
+
*
|
|
100
|
+
* Layer order is:
|
|
101
|
+
* [local+tenant, local+base, parent+tenant, parent+base,
|
|
102
|
+
* grandparent+tenant, grandparent+base, ...]
|
|
103
|
+
* The HIGHEST priority layer is first — local-tenant beats everything else. */
|
|
104
|
+
export class ResolutionPath {
|
|
105
|
+
steps;
|
|
106
|
+
constructor(steps = []) {
|
|
107
|
+
this.steps = steps;
|
|
108
|
+
}
|
|
109
|
+
/** The single layer that became the doc's primary origin. For override_full
|
|
110
|
+
* this is the layer whose doc was returned wholesale; for field_level it is
|
|
111
|
+
* the highest-priority layer that contributed metadata/envelope (semantic
|
|
112
|
+
* primary owner). */
|
|
113
|
+
get effectiveLayer() {
|
|
114
|
+
for (const s of this.steps) {
|
|
115
|
+
if (s.found)
|
|
116
|
+
return s;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
/** JSON-friendly serialization for HTTP responses (snake_case = Python). */
|
|
121
|
+
serialize() {
|
|
122
|
+
const eff = this.effectiveLayer;
|
|
123
|
+
return {
|
|
124
|
+
steps: this.steps.map((s) => ({
|
|
125
|
+
scope: s.scope,
|
|
126
|
+
tenant: s.tenant,
|
|
127
|
+
found: s.found,
|
|
128
|
+
contributed: s.contributed,
|
|
129
|
+
version_sha: s.versionSha,
|
|
130
|
+
})),
|
|
131
|
+
effective_layer: eff ? { scope: eff.scope, tenant: eff.tenant } : null,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Result of `kernel.resolveDocument` — the doc plus full provenance.
|
|
136
|
+
*
|
|
137
|
+
* Studio renders banner/badge directly from `provenance` + `isInherited` —
|
|
138
|
+
* no client-side detection logic needed. */
|
|
139
|
+
export class ResolvedDocument {
|
|
140
|
+
/** The merged document (or null if not found in any layer). */
|
|
141
|
+
doc;
|
|
142
|
+
/** Full ordered resolution path. Includes layers consulted but not
|
|
143
|
+
* contributing. */
|
|
144
|
+
provenance;
|
|
145
|
+
/** True when `effectiveLayer.scope != requestedScope`. Convenience derived
|
|
146
|
+
* from provenance; Studio uses this for badge/banner toggle. */
|
|
147
|
+
isInherited;
|
|
148
|
+
/** Field-path → scope name. Populated when merge_strategy=field_level. Lets
|
|
149
|
+
* the Detail page show `spec.persona ← _lib` annotations. */
|
|
150
|
+
contributionsByField;
|
|
151
|
+
constructor(opts) {
|
|
152
|
+
this.doc = opts.doc;
|
|
153
|
+
this.provenance = opts.provenance;
|
|
154
|
+
this.isInherited = opts.isInherited;
|
|
155
|
+
this.contributionsByField = opts.contributionsByField ?? {};
|
|
156
|
+
}
|
|
157
|
+
serialize() {
|
|
158
|
+
return {
|
|
159
|
+
doc: this.doc,
|
|
160
|
+
provenance: this.provenance.serialize(),
|
|
161
|
+
is_inherited: this.isInherited,
|
|
162
|
+
contributions_by_field: this.contributionsByField,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** First non-null contribution wins entirely.
|
|
167
|
+
*
|
|
168
|
+
* Used for assetic Kinds (LottieAsset, ImagePrompt) where partial override
|
|
169
|
+
* makes no sense (binary payload is atomic).
|
|
170
|
+
*
|
|
171
|
+
* Input: contributions highest-priority-first.
|
|
172
|
+
* Output: [winningRawDoc, winningLayer] or [null, null] if all miss. */
|
|
173
|
+
export function mergeOverrideFull(contributions) {
|
|
174
|
+
for (const [layer, raw] of contributions) {
|
|
175
|
+
if (raw !== null && raw !== undefined)
|
|
176
|
+
return [raw, layer];
|
|
177
|
+
}
|
|
178
|
+
return [null, null];
|
|
179
|
+
}
|
|
180
|
+
/** Deep-merge `spec` dicts. Higher-priority layers shadow lower per-field.
|
|
181
|
+
* Returns [mergedDoc, primaryOriginLayer, fieldsByOrigin].
|
|
182
|
+
*
|
|
183
|
+
* Algorithm:
|
|
184
|
+
* - First pass: find the PRIMARY layer (highest-priority hit). Its metadata
|
|
185
|
+
* + envelope (apiVersion, kind) carry over wholesale.
|
|
186
|
+
* - Second pass: iterate contributions LOWEST → HIGHEST priority, overwriting
|
|
187
|
+
* spec keys in a fresh merged dict. After the loop the highest-priority
|
|
188
|
+
* layer's values win per-field.
|
|
189
|
+
* - Track which layer each FINAL spec field came from so the UI can render
|
|
190
|
+
* `spec.persona ← _lib` annotations.
|
|
191
|
+
*
|
|
192
|
+
* Edge cases:
|
|
193
|
+
* - All-null contributions → [null, null, {}].
|
|
194
|
+
* - Single hit → equivalent to override_full.
|
|
195
|
+
* - Spec missing or non-object → that layer skipped silently. */
|
|
196
|
+
export function mergeFieldLevel(contributions) {
|
|
197
|
+
// First pass — find primary (highest priority hit).
|
|
198
|
+
let primary = null;
|
|
199
|
+
let primaryRaw = null;
|
|
200
|
+
for (const [layer, raw] of contributions) {
|
|
201
|
+
if (raw !== null && raw !== undefined) {
|
|
202
|
+
primary = layer;
|
|
203
|
+
primaryRaw = raw;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (primary === null || primaryRaw === null)
|
|
208
|
+
return [null, null, {}];
|
|
209
|
+
// Second pass — merge specs LOWEST to HIGHEST so highest wins per-key.
|
|
210
|
+
const mergedSpec = {};
|
|
211
|
+
const fieldsByOrigin = {};
|
|
212
|
+
for (let i = contributions.length - 1; i >= 0; i--) {
|
|
213
|
+
const [layer, raw] = contributions[i];
|
|
214
|
+
if (raw === null || raw === undefined)
|
|
215
|
+
continue;
|
|
216
|
+
const spec = raw.spec;
|
|
217
|
+
if (spec === null || spec === undefined || typeof spec !== "object" || Array.isArray(spec)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
for (const [k, v] of Object.entries(spec)) {
|
|
221
|
+
mergedSpec[k] = v;
|
|
222
|
+
fieldsByOrigin[`spec.${k}`] = layer.scope;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const final = {
|
|
226
|
+
apiVersion: primaryRaw.apiVersion ?? null,
|
|
227
|
+
kind: primaryRaw.kind ?? null,
|
|
228
|
+
metadata: primaryRaw.metadata ?? {},
|
|
229
|
+
spec: mergedSpec,
|
|
230
|
+
};
|
|
231
|
+
return [final, primary, fieldsByOrigin];
|
|
232
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource — self-aware document wrapper that knows its own dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Replaces Document with added kindRef linkage: a Resource can resolve
|
|
5
|
+
* its own dep_filters via `deps()` without needing the full ManifestInstance.
|
|
6
|
+
*/
|
|
7
|
+
export interface KindLike {
|
|
8
|
+
readonly apiVersion: string;
|
|
9
|
+
readonly kind: string;
|
|
10
|
+
readonly alias: string;
|
|
11
|
+
depFilters(): Record<string, string> | null;
|
|
12
|
+
}
|
|
13
|
+
export interface ResourceDep {
|
|
14
|
+
/** The spec field name (e.g. "skills", "soul"). */
|
|
15
|
+
field: string;
|
|
16
|
+
/** The target kind alias (e.g. "agentskills-skill"). */
|
|
17
|
+
targetAlias: string;
|
|
18
|
+
/** Concrete names referenced (e.g. ["greet", "search"]). */
|
|
19
|
+
names: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare class Resource {
|
|
22
|
+
readonly apiVersion: string;
|
|
23
|
+
readonly kind: string;
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly raw: Record<string, unknown>;
|
|
26
|
+
readonly typed: unknown;
|
|
27
|
+
readonly origin: string;
|
|
28
|
+
readonly kindRef: KindLike | null;
|
|
29
|
+
private readonly _metadataRaw;
|
|
30
|
+
private readonly _specRaw;
|
|
31
|
+
constructor(opts: {
|
|
32
|
+
apiVersion: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
name: string;
|
|
35
|
+
metadata?: Record<string, unknown>;
|
|
36
|
+
spec?: Record<string, unknown>;
|
|
37
|
+
raw?: Record<string, unknown>;
|
|
38
|
+
typed?: unknown;
|
|
39
|
+
origin?: string;
|
|
40
|
+
kindRef?: KindLike | null;
|
|
41
|
+
});
|
|
42
|
+
/** Always returns Record<string, unknown> — typed metadata when available, raw dict otherwise. */
|
|
43
|
+
get metadata(): Record<string, unknown>;
|
|
44
|
+
/** Always returns Record<string, unknown> — typed spec when available, raw dict otherwise. */
|
|
45
|
+
get spec(): Record<string, unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve this resource's outgoing dependency edges using kindRef.depFilters().
|
|
48
|
+
*
|
|
49
|
+
* Returns one entry per dep_filter field that has a non-empty value in spec.
|
|
50
|
+
* Scalar spec values (e.g. `soul: "brad"`) become single-element name lists.
|
|
51
|
+
* Returns empty array when kindRef is null or depFilters() returns null.
|
|
52
|
+
*/
|
|
53
|
+
deps(): ResourceDep[];
|
|
54
|
+
/** Create a Resource from a raw dict. */
|
|
55
|
+
static fromRaw(raw: Record<string, unknown>, typed?: unknown, origin?: string, kindRef?: KindLike | null): Resource;
|
|
56
|
+
toString(): string;
|
|
57
|
+
}
|