cfa-kernel 0.1.0__py3-none-any.whl
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.
- cfa/__init__.py +39 -0
- cfa/_lazy.py +39 -0
- cfa/adapters/__init__.py +104 -0
- cfa/adapters/autogen.py +19 -0
- cfa/adapters/crewai.py +19 -0
- cfa/adapters/dspy.py +19 -0
- cfa/adapters/langgraph.py +19 -0
- cfa/adapters/openai_agents.py +19 -0
- cfa/audit/__init__.py +15 -0
- cfa/audit/context.py +205 -0
- cfa/audit/hashing.py +41 -0
- cfa/audit/trail.py +194 -0
- cfa/backends/__init__.py +132 -0
- cfa/backends/dbt.py +338 -0
- cfa/backends/pyspark.py +240 -0
- cfa/backends/sql.py +270 -0
- cfa/behavior/__init__.py +49 -0
- cfa/behavior/llm.py +244 -0
- cfa/behavior/spec.py +235 -0
- cfa/behavior/systematizer.py +222 -0
- cfa/cli/__init__.py +296 -0
- cfa/cli/__main__.py +6 -0
- cfa/cli/_helpers.py +109 -0
- cfa/cli/core/__init__.py +0 -0
- cfa/cli/core/evaluate.py +72 -0
- cfa/cli/core/validate.py +29 -0
- cfa/cli/formatters.py +280 -0
- cfa/cli/governance/__init__.py +0 -0
- cfa/cli/governance/audit.py +65 -0
- cfa/cli/governance/catalog.py +28 -0
- cfa/cli/governance/policy.py +119 -0
- cfa/cli/governance/rules.py +42 -0
- cfa/cli/governance/signature.py +31 -0
- cfa/cli/infrastructure/__init__.py +0 -0
- cfa/cli/infrastructure/backend_list.py +24 -0
- cfa/cli/infrastructure/storage.py +87 -0
- cfa/cli/project/__init__.py +0 -0
- cfa/cli/project/init.py +73 -0
- cfa/cli/project/lifecycle.py +92 -0
- cfa/cli/project/status.py +75 -0
- cfa/cli/project/taxonomy.py +38 -0
- cfa/cli/reporting/__init__.py +0 -0
- cfa/cli/reporting/report.py +109 -0
- cfa/cli/reporting/serve.py +43 -0
- cfa/config.py +103 -0
- cfa/core/__init__.py +19 -0
- cfa/core/codegen.py +65 -0
- cfa/core/conditions.py +129 -0
- cfa/core/kernel.py +224 -0
- cfa/core/phases/__init__.py +0 -0
- cfa/core/phases/runner.py +477 -0
- cfa/core/planner.py +290 -0
- cfa/execution/__init__.py +12 -0
- cfa/execution/partial.py +339 -0
- cfa/execution/state_projection.py +216 -0
- cfa/governance/__init__.py +76 -0
- cfa/lifecycle/__init__.py +51 -0
- cfa/mcp/__init__.py +347 -0
- cfa/mcp/__main__.py +4 -0
- cfa/normalizer/__init__.py +15 -0
- cfa/normalizer/base.py +441 -0
- cfa/normalizer/llm.py +426 -0
- cfa/observability/__init__.py +14 -0
- cfa/observability/indices.py +177 -0
- cfa/observability/metrics.py +91 -0
- cfa/observability/notify.py +79 -0
- cfa/observability/otel.py +81 -0
- cfa/observability/promotion.py +367 -0
- cfa/policy/__init__.py +12 -0
- cfa/policy/bundle.py +317 -0
- cfa/policy/catalog.py +117 -0
- cfa/policy/engine.py +306 -0
- cfa/reporting/__init__.py +42 -0
- cfa/reporting/charts.py +223 -0
- cfa/reporting/engine.py +456 -0
- cfa/resolution/__init__.py +62 -0
- cfa/runtime/__init__.py +13 -0
- cfa/runtime/gate.py +287 -0
- cfa/sandbox/__init__.py +189 -0
- cfa/sandbox/executor.py +92 -0
- cfa/sandbox/mock.py +89 -0
- cfa/sandbox/panic.py +52 -0
- cfa/storage/__init__.py +591 -0
- cfa/testing/__init__.py +60 -0
- cfa/testing/asserts.py +77 -0
- cfa/testing/evaluate.py +168 -0
- cfa/testing/fixtures.py +89 -0
- cfa/testing/markers.py +36 -0
- cfa/types.py +489 -0
- cfa/validation/__init__.py +14 -0
- cfa/validation/runtime.py +285 -0
- cfa/validation/signature.py +146 -0
- cfa/validation/static.py +252 -0
- cfa_kernel-0.1.0.dist-info/METADATA +32 -0
- cfa_kernel-0.1.0.dist-info/RECORD +98 -0
- cfa_kernel-0.1.0.dist-info/WHEEL +4 -0
- cfa_kernel-0.1.0.dist-info/entry_points.txt +3 -0
- cfa_kernel-0.1.0.dist-info/licenses/LICENSE +21 -0
cfa/reporting/engine.py
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CFA Reporting Engine v2
|
|
3
|
+
=======================
|
|
4
|
+
Rich, descriptive HTML reports with professional layout.
|
|
5
|
+
Inspired by modern review tool designs — user needs to understand
|
|
6
|
+
WHAT happened and WHY.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import html as _html
|
|
12
|
+
from datetime import UTC
|
|
13
|
+
|
|
14
|
+
_CHARTJS = "https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"
|
|
15
|
+
_INTEGRITY = "sha256-..." # placeholder for future SRI
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_REPORT_STYLE = """
|
|
19
|
+
:root {{
|
|
20
|
+
--bg: #f5f1e8;
|
|
21
|
+
--ink: #162c3a;
|
|
22
|
+
--muted: #5e6b73;
|
|
23
|
+
--surface: rgba(255,255,255,.92);
|
|
24
|
+
--line: #e4dacb;
|
|
25
|
+
--navy: #173b57;
|
|
26
|
+
--copper: #b7792f;
|
|
27
|
+
--green: #2f765c;
|
|
28
|
+
--red: #a84436;
|
|
29
|
+
--blue-soft: #e8f0f4;
|
|
30
|
+
--amber-soft: #fbf0dc;
|
|
31
|
+
--green-soft: #e4f2ec;
|
|
32
|
+
--green-bright: #22c55e;
|
|
33
|
+
--red-soft: #f8e8e4;
|
|
34
|
+
}}
|
|
35
|
+
* {{ box-sizing: border-box; }}
|
|
36
|
+
body {{
|
|
37
|
+
margin: 0;
|
|
38
|
+
background:
|
|
39
|
+
radial-gradient(circle at 10% 0%, rgba(183,121,47,.10), transparent 32rem),
|
|
40
|
+
radial-gradient(circle at 92% 10%, rgba(23,59,87,.08), transparent 30rem),
|
|
41
|
+
linear-gradient(135deg, #f9f6ef 0%, var(--bg) 48%, #edf3f5 100%);
|
|
42
|
+
color: var(--ink);
|
|
43
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
44
|
+
line-height: 1.56;
|
|
45
|
+
}}
|
|
46
|
+
main {{
|
|
47
|
+
width: min(1240px, calc(100vw - 36px));
|
|
48
|
+
margin: 34px auto 72px;
|
|
49
|
+
}}
|
|
50
|
+
.hero {{
|
|
51
|
+
background: linear-gradient(135deg, rgba(255,255,255,.95), rgba(248,244,236,.92));
|
|
52
|
+
border: 1px solid var(--line);
|
|
53
|
+
border-radius: 30px;
|
|
54
|
+
box-shadow: 0 26px 80px rgba(22,44,58,.13);
|
|
55
|
+
padding: 38px;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
position: relative;
|
|
58
|
+
}}
|
|
59
|
+
.hero::after {{
|
|
60
|
+
content: "";
|
|
61
|
+
position: absolute;
|
|
62
|
+
right: -100px; top: -100px;
|
|
63
|
+
width: 320px; height: 320px;
|
|
64
|
+
background: radial-gradient(circle, rgba(183,121,47,.12), transparent 70%);
|
|
65
|
+
}}
|
|
66
|
+
h1 {{
|
|
67
|
+
margin: 0; max-width: 900px;
|
|
68
|
+
font-size: clamp(2.1rem, 5vw, 4.2rem);
|
|
69
|
+
line-height: .98; letter-spacing: -.065em;
|
|
70
|
+
color: var(--navy);
|
|
71
|
+
}}
|
|
72
|
+
.subtitle {{
|
|
73
|
+
max-width: 850px; color: var(--muted);
|
|
74
|
+
font-size: 1.06rem; margin: 16px 0 0;
|
|
75
|
+
}}
|
|
76
|
+
.badge-row {{
|
|
77
|
+
display: flex; flex-wrap: wrap; gap: 10px; margin-top: 24px;
|
|
78
|
+
}}
|
|
79
|
+
.badge {{
|
|
80
|
+
border: 1px solid var(--line);
|
|
81
|
+
background: #fff; color: var(--navy);
|
|
82
|
+
border-radius: 999px; padding: 8px 12px;
|
|
83
|
+
font-size: .88rem; font-weight: 700;
|
|
84
|
+
}}
|
|
85
|
+
.badge-accept {{ border-color: var(--green); background: var(--green-soft); color: var(--green); }}
|
|
86
|
+
.badge-block {{ border-color: var(--red); background: var(--red-soft); color: var(--red); }}
|
|
87
|
+
.badge-warn {{ border-color: var(--copper); background: var(--amber-soft); color: var(--copper); }}
|
|
88
|
+
.badge-info {{ border-color: #3b82f6; background: #e8f0fe; color: #3b82f6; }}
|
|
89
|
+
.grid2 {{ display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }}
|
|
90
|
+
.grid3 {{ display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }}
|
|
91
|
+
.grid4 {{ display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin: 22px 0; }}
|
|
92
|
+
.card {{
|
|
93
|
+
background: var(--surface);
|
|
94
|
+
border: 1px solid var(--line);
|
|
95
|
+
border-radius: 22px; padding: 20px;
|
|
96
|
+
box-shadow: 0 16px 44px rgba(22,44,58,.08);
|
|
97
|
+
}}
|
|
98
|
+
.section {{
|
|
99
|
+
background: var(--surface);
|
|
100
|
+
border: 1px solid var(--line);
|
|
101
|
+
border-radius: 24px; margin: 18px 0; padding: 24px;
|
|
102
|
+
box-shadow: 0 16px 44px rgba(22,44,58,.07);
|
|
103
|
+
}}
|
|
104
|
+
.section h2 {{
|
|
105
|
+
margin: 0 0 14px; color: var(--navy);
|
|
106
|
+
font-size: 1.35rem; letter-spacing: -.025em;
|
|
107
|
+
}}
|
|
108
|
+
.kv {{
|
|
109
|
+
display: grid; grid-template-columns: 200px 1fr;
|
|
110
|
+
gap: 8px 16px; color: var(--muted);
|
|
111
|
+
}}
|
|
112
|
+
.kv strong {{ color: var(--ink); }}
|
|
113
|
+
.metric {{
|
|
114
|
+
text-align: center; font-size: 2rem; line-height: 1;
|
|
115
|
+
font-weight: 850; letter-spacing: -.04em; color: var(--navy);
|
|
116
|
+
}}
|
|
117
|
+
.metric .label {{
|
|
118
|
+
display: block; font-size: .75rem; font-weight: 600;
|
|
119
|
+
color: var(--muted); text-transform: uppercase;
|
|
120
|
+
letter-spacing: .08em; margin-top: 4px;
|
|
121
|
+
}}
|
|
122
|
+
table {{
|
|
123
|
+
width: 100%; border-collapse: collapse;
|
|
124
|
+
overflow: hidden; border-radius: 14px; font-size: .92rem;
|
|
125
|
+
}}
|
|
126
|
+
th, td {{
|
|
127
|
+
border-bottom: 1px solid #ebe3d8;
|
|
128
|
+
padding: 11px 12px; text-align: left; vertical-align: top;
|
|
129
|
+
}}
|
|
130
|
+
th {{
|
|
131
|
+
color: var(--navy); background: #f4ede2;
|
|
132
|
+
font-size: .78rem; text-transform: uppercase;
|
|
133
|
+
letter-spacing: .06em;
|
|
134
|
+
}}
|
|
135
|
+
tr:last-child td {{ border-bottom: 0; }}
|
|
136
|
+
code {{
|
|
137
|
+
background: #f1ede5; border: 1px solid #e1d8c9;
|
|
138
|
+
border-radius: 7px; padding: .1rem .32rem;
|
|
139
|
+
color: #102d42; font-size: .9em;
|
|
140
|
+
}}
|
|
141
|
+
.timeline {{ position: relative; padding-left: 24px; }}
|
|
142
|
+
.timeline-item {{
|
|
143
|
+
padding: 10px 0 10px 20px;
|
|
144
|
+
border-left: 3px solid var(--line);
|
|
145
|
+
position: relative; margin-bottom: 4px;
|
|
146
|
+
}}
|
|
147
|
+
.timeline-item::before {{
|
|
148
|
+
content: ''; position: absolute; left: -7px; top: 16px;
|
|
149
|
+
width: 11px; height: 11px; border-radius: 50%;
|
|
150
|
+
}}
|
|
151
|
+
.tl-ok::before {{ background: var(--green); }}
|
|
152
|
+
.tl-warn::before {{ background: var(--copper); }}
|
|
153
|
+
.tl-err::before {{ background: var(--red); }}
|
|
154
|
+
.callout {{
|
|
155
|
+
border-left: 5px solid var(--copper);
|
|
156
|
+
background: var(--amber-soft);
|
|
157
|
+
padding: 14px 16px; border-radius: 14px;
|
|
158
|
+
color: #5c4630; margin: 12px 0;
|
|
159
|
+
}}
|
|
160
|
+
.callout-green {{
|
|
161
|
+
border-left-color: var(--green);
|
|
162
|
+
background: var(--green-soft); color: #1a4a38;
|
|
163
|
+
}}
|
|
164
|
+
.callout-red {{
|
|
165
|
+
border-left-color: var(--red);
|
|
166
|
+
background: var(--red-soft); color: #5c2020;
|
|
167
|
+
}}
|
|
168
|
+
.reasoning {{
|
|
169
|
+
font-size: 1.05rem; color: var(--ink);
|
|
170
|
+
background: var(--amber-soft);
|
|
171
|
+
border-radius: 14px; padding: 14px 18px;
|
|
172
|
+
margin: 16px 0; border: 1px solid #e8d8b0;
|
|
173
|
+
}}
|
|
174
|
+
.footer {{
|
|
175
|
+
text-align: center; color: var(--muted);
|
|
176
|
+
font-size: .8rem; padding: 24px 0;
|
|
177
|
+
border-top: 2px solid var(--line); margin-top: 32px;
|
|
178
|
+
}}
|
|
179
|
+
@media print {{
|
|
180
|
+
body {{ background: #fff; }}
|
|
181
|
+
.section, .card, .hero {{ box-shadow: none; border-color: #ddd; }}
|
|
182
|
+
}}
|
|
183
|
+
@media (max-width: 768px) {{
|
|
184
|
+
.grid2, .grid3, .grid4 {{ grid-template-columns: 1fr; }}
|
|
185
|
+
}}
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _now():
|
|
190
|
+
from datetime import datetime
|
|
191
|
+
return datetime.now(UTC).strftime("%Y-%m-%d %H:%M UTC")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _badge(text: str, kind: str = "info") -> str:
|
|
195
|
+
cls = {"accept": "badge-accept", "block": "badge-block", "warn": "badge-warn", "info": "badge-info"}
|
|
196
|
+
return f'<span class="badge {cls.get(kind, "badge-info")}">{text}</span>'
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _severity_badge(sev: str) -> str:
|
|
200
|
+
m = {"critical": "badge-block", "high": "badge-block", "medium": "badge-warn", "warning": "badge-warn", "info": "badge-info"}
|
|
201
|
+
return f'<span class="badge {m.get(sev, "badge-info")}">{sev.upper()}</span>'
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _header(intent: str, state: str) -> str:
|
|
205
|
+
state_kind = "accept" if state in ("approved", "approved_with_warnings", "promotion_candidate") else \
|
|
206
|
+
"block" if state in ("blocked", "rolled_back") else "warn"
|
|
207
|
+
return f"""<div class="hero">
|
|
208
|
+
<h1>CFA Governance Report</h1>
|
|
209
|
+
<p class="subtitle">{_html.escape(intent[:120])}</p>
|
|
210
|
+
<div class="badge-row">
|
|
211
|
+
{_badge(state.upper(), state_kind)}
|
|
212
|
+
{_badge("GOVERNED EXECUTION", "info")}
|
|
213
|
+
</div>
|
|
214
|
+
</div>"""
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _html_page(title: str, body: str, scripts: str = "") -> str:
|
|
218
|
+
ts = _now()
|
|
219
|
+
return f"""<!doctype html>
|
|
220
|
+
<html lang="en">
|
|
221
|
+
<head>
|
|
222
|
+
<meta charset="utf-8">
|
|
223
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
224
|
+
<title>{title}</title>
|
|
225
|
+
<style>{_REPORT_STYLE.format(timestamp=ts)}</style>
|
|
226
|
+
</head>
|
|
227
|
+
<body>
|
|
228
|
+
<main>
|
|
229
|
+
{body}
|
|
230
|
+
<div class="footer">CFA v0.1.0 — Contextual Flux Architecture — Generated {ts}</div>
|
|
231
|
+
</main>
|
|
232
|
+
<script src="{_CHARTJS}"></script>
|
|
233
|
+
{scripts}
|
|
234
|
+
</body>
|
|
235
|
+
</html>"""
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class ReportEngine:
|
|
239
|
+
"""Generates rich, descriptive HTML governance reports."""
|
|
240
|
+
|
|
241
|
+
def execution_report(
|
|
242
|
+
self, intent: str, intent_id: str, state: str, signature_hash: str,
|
|
243
|
+
policy_bundle: str, replan_count: int, events: list, faults: list,
|
|
244
|
+
sandbox_metrics: dict | None = None,
|
|
245
|
+
domain: str = "", intent_type: str = "", target_layer: str = "",
|
|
246
|
+
datasets: list | None = None, constraints: dict | None = None,
|
|
247
|
+
confidence: float = 0.0, reasoning: str = "",
|
|
248
|
+
) -> str:
|
|
249
|
+
parts: list[str] = []
|
|
250
|
+
parts.append(_header(intent, state))
|
|
251
|
+
|
|
252
|
+
parts.append('<div class="grid4">')
|
|
253
|
+
parts.append(f'<div class="card"><div class="metric">{signature_hash[:12] if signature_hash else "N/A"}</div><div class="label">Signature Hash</div></div>')
|
|
254
|
+
parts.append(f'<div class="card"><div class="metric">{policy_bundle}</div><div class="label">Policy Bundle</div></div>')
|
|
255
|
+
parts.append(f'<div class="card"><div class="metric">{replan_count}</div><div class="label">Replans</div></div>')
|
|
256
|
+
parts.append(f'<div class="card"><div class="metric">{len(events)}</div><div class="label">Audit Events</div></div>')
|
|
257
|
+
parts.append('</div>')
|
|
258
|
+
|
|
259
|
+
if reasoning or confidence > 0:
|
|
260
|
+
parts.append('<div class="section"><h2>Intent Analysis</h2>')
|
|
261
|
+
if reasoning:
|
|
262
|
+
parts.append(f'<div class="reasoning">{_html.escape(reasoning)}</div>')
|
|
263
|
+
parts.append('<div class="kv">')
|
|
264
|
+
parts.append(f'<strong>Domain</strong><span>{_html.escape(domain or "general")}</span>')
|
|
265
|
+
parts.append(f'<strong>Intent type</strong><span>{_html.escape(intent_type or "unknown")}</span>')
|
|
266
|
+
parts.append(f'<strong>Target layer</strong><span style="font-weight:800;color:var(--navy)">{target_layer.upper() if target_layer else "?"}</span>')
|
|
267
|
+
parts.append(f'<strong>Confidence</strong><span style="font-weight:800">{confidence:.0%}</span>')
|
|
268
|
+
parts.append('</div></div>')
|
|
269
|
+
|
|
270
|
+
if constraints or datasets:
|
|
271
|
+
parts.append('<div class="section"><h2>State Signature</h2><div class="grid2">')
|
|
272
|
+
if constraints:
|
|
273
|
+
c = constraints
|
|
274
|
+
parts.append('<div><table>')
|
|
275
|
+
parts.append('<tr><th colspan="2">Constraints</th></tr>')
|
|
276
|
+
parts.append(f'<tr><td>PII Protection</td><td>{_badge("ENABLED", "accept") if c.get("no_pii_raw") else _badge("RAW PII REQUESTED", "block")}</td></tr>')
|
|
277
|
+
parts.append(f'<tr><td>Merge Key</td><td>{_badge("REQUIRED", "accept") if c.get("merge_key_required") else _badge("MISSING", "block")}</td></tr>')
|
|
278
|
+
parts.append(f'<tr><td>Type Check</td><td>{_badge("ENABLED", "accept") if c.get("enforce_types") else _badge("DISABLED", "warn")}</td></tr>')
|
|
279
|
+
p = c.get("partition_by", [])
|
|
280
|
+
parts.append(f'<tr><td>Partition</td><td><code>{", ".join(p) if p else "NONE"}</code></td></tr>')
|
|
281
|
+
cb = c.get("max_cost_dbu")
|
|
282
|
+
parts.append(f'<tr><td>Cost limit</td><td><code>{cb if cb else "Unlimited"}</code></td></tr>')
|
|
283
|
+
parts.append('</table></div>')
|
|
284
|
+
if datasets:
|
|
285
|
+
parts.append('<div><table><tr><th>Datasets</th></tr>')
|
|
286
|
+
for ds in datasets:
|
|
287
|
+
parts.append(f'<tr><td><code>{ds}</code></td></tr>')
|
|
288
|
+
parts.append('</table></div>')
|
|
289
|
+
parts.append('</div></div>')
|
|
290
|
+
|
|
291
|
+
if sandbox_metrics:
|
|
292
|
+
parts.append('<div class="section"><h2>Execution Metrics</h2><div class="grid4">')
|
|
293
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--green)">{sandbox_metrics.get("rows_output", 0):,}</div><div class="label">Rows Output</div></div>')
|
|
294
|
+
parts.append(f'<div class="card"><div class="metric">{sandbox_metrics.get("shuffle_mb", 0):.0f} MB</div><div class="label">Shuffle</div></div>')
|
|
295
|
+
parts.append(f'<div class="card"><div class="metric">{sandbox_metrics.get("duration_seconds", 0):.1f}s</div><div class="label">Duration</div></div>')
|
|
296
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--copper)">{sandbox_metrics.get("cost_dbu", 0):.1f} DBU</div><div class="label">Cost</div></div>')
|
|
297
|
+
parts.append('</div></div>')
|
|
298
|
+
|
|
299
|
+
parts.append('<div class="section"><h2>Pipeline Phases</h2><div class="timeline">')
|
|
300
|
+
for e in events:
|
|
301
|
+
css_class = "tl-ok" if e.get("outcome", "") in ("ok", "approved", "resolved", "passed") else \
|
|
302
|
+
"tl-warn" if e.get("outcome", "") in ("replanned", "warning") else "tl-err"
|
|
303
|
+
parts.append(f'<div class="timeline-item {css_class}"><strong>{e.get("phase", e.get("stage", "?"))}</strong> — {e.get("event_type", "")} <span style="color:var(--muted)">({e.get("outcome", "?")})</span></div>')
|
|
304
|
+
parts.append('</div></div>')
|
|
305
|
+
|
|
306
|
+
if faults:
|
|
307
|
+
parts.append('<div class="section"><h2>Policy Violations</h2><table>')
|
|
308
|
+
parts.append('<tr><th>Severity</th><th>Code</th><th>Description</th><th>How to Fix</th></tr>')
|
|
309
|
+
for f in faults:
|
|
310
|
+
parts.append(f'<tr><td>{_severity_badge(f.get("severity", "high"))}</td>'
|
|
311
|
+
f'<td><code>{_html.escape(str(f.get("code", "")))}</code></td>'
|
|
312
|
+
f'<td>{_html.escape(str(f.get("message", "")))}</td>'
|
|
313
|
+
f'<td>{"<br>".join(_html.escape(str(r)) for r in f.get("remediation", []))}</td></tr>')
|
|
314
|
+
parts.append('</table></div>')
|
|
315
|
+
|
|
316
|
+
state_kind = "accept" if state in ("approved", "approved_with_warnings", "promotion_candidate") else "block" if state in ("blocked", "rolled_back") else "warn"
|
|
317
|
+
callout_class = "callout-green" if state_kind == "accept" else "callout-red" if state_kind == "block" else "callout"
|
|
318
|
+
parts.append(f'<div class="section"><h2>Decision</h2><div class="{callout_class}">')
|
|
319
|
+
if state == "approved":
|
|
320
|
+
parts.append("<strong>APPROVED:</strong> All governance checks passed. Execution can proceed safely.")
|
|
321
|
+
elif state == "blocked":
|
|
322
|
+
parts.append(f"<strong>BLOCKED:</strong> Governance prevented execution. {len(faults)} violations found. See above for remediation.")
|
|
323
|
+
else:
|
|
324
|
+
parts.append(f"<strong>{state.upper()}:</strong> Refer to timeline and faults for details.")
|
|
325
|
+
parts.append('</div></div>')
|
|
326
|
+
|
|
327
|
+
return _html_page(f"CFA — {intent[:50]}", "\n".join(parts))
|
|
328
|
+
|
|
329
|
+
def audit_report(self, intent_id: str, events: list, chain_intact: bool, policy_bundle: str = "") -> str:
|
|
330
|
+
parts: list[str] = []
|
|
331
|
+
parts.append(f"""<div class="hero">
|
|
332
|
+
<h1>CFA Audit Trail</h1>
|
|
333
|
+
<p class="subtitle">Intent: {_html.escape(intent_id)} | Bundle: {_html.escape(policy_bundle)}</p>
|
|
334
|
+
<div class="badge-row">
|
|
335
|
+
{_badge(f"CHAIN {'INTACT' if chain_intact else 'BROKEN'}", "accept" if chain_intact else "block")}
|
|
336
|
+
{_badge(f"{len(events)} EVENTS", "info")}
|
|
337
|
+
</div></div>""")
|
|
338
|
+
|
|
339
|
+
parts.append('<div class="section"><h2>Event Timeline</h2><table>')
|
|
340
|
+
parts.append('<tr><th>#</th><th>Timestamp</th><th>Phase</th><th>Event</th><th>Outcome</th></tr>')
|
|
341
|
+
for i, e in enumerate(events[:100]):
|
|
342
|
+
ts = str(e.get("timestamp", ""))[:19]
|
|
343
|
+
parts.append(f'<tr><td>{i+1}</td><td style="font-size:12px">{ts}</td>'
|
|
344
|
+
f'<td>{e.get("phase", e.get("stage", ""))}</td>'
|
|
345
|
+
f'<td>{e.get("event_type", "")}</td>'
|
|
346
|
+
f'<td>{e.get("outcome", "")}</td></tr>')
|
|
347
|
+
parts.append('</table></div>')
|
|
348
|
+
|
|
349
|
+
return _html_page(f"CFA Audit — {intent_id[:16]}", "\n".join(parts))
|
|
350
|
+
|
|
351
|
+
def compliance_report(self, policy_bundle: str, total_evaluations: int,
|
|
352
|
+
approved: int, replanned: int, blocked: int,
|
|
353
|
+
rules: list, pii_incidents_prevented: int,
|
|
354
|
+
audit_events_total: int, chain_intact: bool) -> str:
|
|
355
|
+
pct = (approved / max(total_evaluations, 1)) * 100
|
|
356
|
+
parts: list[str] = []
|
|
357
|
+
parts.append(f"""<div class="hero">
|
|
358
|
+
<h1>CFA Compliance Summary</h1>
|
|
359
|
+
<p class="subtitle">Policy: {policy_bundle} | {total_evaluations} evaluations</p>
|
|
360
|
+
<div class="badge-row">
|
|
361
|
+
{_badge(f"{pct:.0f}% COMPLIANT", "accept")}
|
|
362
|
+
{_badge(f"{pii_incidents_prevented} PII PREVENTED", "info")}
|
|
363
|
+
{_badge(f"CHAIN {'INTACT' if chain_intact else 'BROKEN'}", "accept" if chain_intact else "block")}
|
|
364
|
+
</div></div>""")
|
|
365
|
+
|
|
366
|
+
parts.append('<div class="grid4">')
|
|
367
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--green)">{approved}</div><div class="label">Approved</div></div>')
|
|
368
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--copper)">{replanned}</div><div class="label">Replanned</div></div>')
|
|
369
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--red)">{blocked}</div><div class="label">Blocked</div></div>')
|
|
370
|
+
parts.append(f'<div class="card"><div class="metric">{audit_events_total}</div><div class="label">Audit Events</div></div>')
|
|
371
|
+
parts.append('</div>')
|
|
372
|
+
|
|
373
|
+
parts.append('<div class="section"><h2>Policy Rules</h2><table>')
|
|
374
|
+
parts.append('<tr><th>Rule</th><th>Action</th><th>Code</th><th>Severity</th></tr>')
|
|
375
|
+
for r in rules:
|
|
376
|
+
parts.append(f'<tr><td>{r.get("name", "")}</td>'
|
|
377
|
+
f'<td>{r.get("action", "").upper()}</td>'
|
|
378
|
+
f'<td><code>{r.get("fault_code", "")}</code></td>'
|
|
379
|
+
f'<td>{_severity_badge(r.get("severity", "high"))}</td></tr>')
|
|
380
|
+
parts.append('</table></div>')
|
|
381
|
+
|
|
382
|
+
return _html_page("CFA Compliance", "\n".join(parts))
|
|
383
|
+
|
|
384
|
+
def lifecycle_report(self, period_days: int, skills: list, trend_dates: list,
|
|
385
|
+
ifo_vals: list, ifs_vals: list, idi_vals: list, ifg_vals: list,
|
|
386
|
+
cost_dates: list, cost_vals: list, decisions: dict) -> str:
|
|
387
|
+
parts: list[str] = []
|
|
388
|
+
active_count = sum(1 for s in skills if s.get("state") == "active")
|
|
389
|
+
watch_count = sum(1 for s in skills if s.get("state") == "watchlist")
|
|
390
|
+
parts.append(f"""<div class="hero">
|
|
391
|
+
<h1>CFA Lifecycle Dashboard</h1>
|
|
392
|
+
<p class="subtitle">Last {period_days} days | {len(skills)} pipelines</p>
|
|
393
|
+
<div class="badge-row">
|
|
394
|
+
{_badge(f"{active_count} ACTIVE", "accept")}
|
|
395
|
+
{_badge(f"{watch_count} WATCHLIST", "warn")}
|
|
396
|
+
</div></div>""")
|
|
397
|
+
|
|
398
|
+
parts.append('<div class="grid3">')
|
|
399
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--green)">{decisions.get("approved", 0)}</div><div class="label">Approved</div></div>')
|
|
400
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--copper)">{decisions.get("replanned", 0)}</div><div class="label">Replanned</div></div>')
|
|
401
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--red)">{decisions.get("blocked", 0)}</div><div class="label">Blocked</div></div>')
|
|
402
|
+
parts.append('</div>')
|
|
403
|
+
|
|
404
|
+
parts.append('<div class="section"><h2>Pipeline Skills</h2><table>')
|
|
405
|
+
parts.append('<tr><th>Pipeline</th><th>IFo</th><th>IFs</th><th>IFg</th><th>IDI</th><th>State</th></tr>')
|
|
406
|
+
for s in skills:
|
|
407
|
+
st = s.get("state", "candidate")
|
|
408
|
+
st_kind = "accept" if st == "active" else "warn" if st == "watchlist" else "block"
|
|
409
|
+
parts.append(f'<tr><td><code>{s.get("hash", "?")[:16]}</code></td>'
|
|
410
|
+
f'<td>{s.get("ifo", 0):.2f}</td><td>{s.get("ifs", 0):.2f}</td>'
|
|
411
|
+
f'<td>{s.get("ifg", 0):.2f}</td><td>{s.get("idi", 0):.2f}</td>'
|
|
412
|
+
f'<td>{_badge(st.upper(), st_kind)}</td></tr>')
|
|
413
|
+
parts.append('</table></div>')
|
|
414
|
+
|
|
415
|
+
return _html_page("CFA Lifecycle", "\n".join(parts))
|
|
416
|
+
|
|
417
|
+
def dashboard(self, period_days: int, skills: list, trend_dates: list,
|
|
418
|
+
ifo_vals: list, faults_summary: dict, decisions: dict) -> str:
|
|
419
|
+
parts: list[str] = []
|
|
420
|
+
parts.append(f"""<div class="hero">
|
|
421
|
+
<h1>CFA Dashboard</h1>
|
|
422
|
+
<p class="subtitle">{len(skills)} pipelines | Last {period_days} days</p>
|
|
423
|
+
</div>""")
|
|
424
|
+
|
|
425
|
+
parts.append('<div class="grid3">')
|
|
426
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--green)">{decisions.get("approved", 0)}</div><div class="label">Approved</div></div>')
|
|
427
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--copper)">{decisions.get("replanned", 0)}</div><div class="label">Replanned</div></div>')
|
|
428
|
+
parts.append(f'<div class="card"><div class="metric" style="color:var(--red)">{decisions.get("blocked", 0)}</div><div class="label">Blocked</div></div>')
|
|
429
|
+
parts.append('</div>')
|
|
430
|
+
|
|
431
|
+
parts.append('<div class="section"><h2>Top Faults</h2><table>')
|
|
432
|
+
parts.append('<tr><th>Fault</th><th>Count</th></tr>')
|
|
433
|
+
for code, count in sorted(faults_summary.items(), key=lambda x: x[1], reverse=True)[:10]:
|
|
434
|
+
parts.append(f'<tr><td><code>{code}</code></td><td>{count}</td></tr>')
|
|
435
|
+
parts.append('</table></div>')
|
|
436
|
+
|
|
437
|
+
return _html_page("CFA Dashboard", "\n".join(parts))
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def generate_report(report_type: str, output_path: str, **kwargs) -> str:
|
|
441
|
+
engine = ReportEngine()
|
|
442
|
+
dispatch = {
|
|
443
|
+
"execution": engine.execution_report,
|
|
444
|
+
"audit": engine.audit_report,
|
|
445
|
+
"lifecycle": engine.lifecycle_report,
|
|
446
|
+
"compliance": engine.compliance_report,
|
|
447
|
+
"dashboard": engine.dashboard,
|
|
448
|
+
}
|
|
449
|
+
handler = dispatch.get(report_type)
|
|
450
|
+
if handler is None:
|
|
451
|
+
raise ValueError(f"Unknown report type '{report_type}'. Choose: {', '.join(dispatch)}")
|
|
452
|
+
html = handler(**kwargs)
|
|
453
|
+
from pathlib import Path
|
|
454
|
+
out = Path(output_path)
|
|
455
|
+
out.write_text(html, encoding="utf-8")
|
|
456
|
+
return str(out)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cfa.resolution -- Resolucao de intencoes
|
|
3
|
+
=========================================
|
|
4
|
+
Transforma linguagem natural em StateSignature tipada.
|
|
5
|
+
Requer um NormalizerBackend (LLM ou rule-based).
|
|
6
|
+
|
|
7
|
+
Uso com mock (para teste):
|
|
8
|
+
from cfa.resolution import IntentNormalizer, MockNormalizerBackend
|
|
9
|
+
|
|
10
|
+
normalizer = IntentNormalizer(backend=MockNormalizerBackend())
|
|
11
|
+
resolution = normalizer.normalize(
|
|
12
|
+
raw_intent="Join NFe com Clientes na Silver",
|
|
13
|
+
catalog=CATALOG,
|
|
14
|
+
)
|
|
15
|
+
print(resolution.signature)
|
|
16
|
+
print(resolution.confidence_score)
|
|
17
|
+
|
|
18
|
+
Uso com LLM real:
|
|
19
|
+
class MeuLLMBackend(NormalizerBackend):
|
|
20
|
+
def resolve(self, inp: NormalizerInput) -> NormalizerOutput:
|
|
21
|
+
# Chamar seu LLM aqui
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
normalizer = IntentNormalizer(backend=MeuLLMBackend())
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from cfa.normalizer.base import (
|
|
28
|
+
AutoApproveHandler,
|
|
29
|
+
AutoRejectHandler,
|
|
30
|
+
ConfirmationHandler,
|
|
31
|
+
ConfirmationOrchestrator,
|
|
32
|
+
IntentNormalizer,
|
|
33
|
+
MockNormalizerBackend,
|
|
34
|
+
NormalizerBackend,
|
|
35
|
+
NormalizerInput,
|
|
36
|
+
NormalizerOutput,
|
|
37
|
+
)
|
|
38
|
+
from cfa.types import (
|
|
39
|
+
AmbiguityLevel,
|
|
40
|
+
ConfirmationMode,
|
|
41
|
+
SemanticResolution,
|
|
42
|
+
StateSignature,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
# Normalizer
|
|
47
|
+
"IntentNormalizer",
|
|
48
|
+
"NormalizerBackend",
|
|
49
|
+
"NormalizerInput",
|
|
50
|
+
"NormalizerOutput",
|
|
51
|
+
"MockNormalizerBackend",
|
|
52
|
+
# Confirmation
|
|
53
|
+
"ConfirmationOrchestrator",
|
|
54
|
+
"ConfirmationHandler",
|
|
55
|
+
"AutoApproveHandler",
|
|
56
|
+
"AutoRejectHandler",
|
|
57
|
+
# Types
|
|
58
|
+
"AmbiguityLevel",
|
|
59
|
+
"ConfirmationMode",
|
|
60
|
+
"SemanticResolution",
|
|
61
|
+
"StateSignature",
|
|
62
|
+
]
|
cfa/runtime/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""CFA Runtime — all imports are lazy to avoid circular dependencies."""
|
|
2
|
+
from cfa._lazy import LazyLoader
|
|
3
|
+
|
|
4
|
+
__all__ = ["RuntimeGate", "GateConfig", "GateResult", "GateViolation", "GovernanceViolation", "runtime_gate"]
|
|
5
|
+
|
|
6
|
+
__getattr__ = LazyLoader({
|
|
7
|
+
"RuntimeGate": ("cfa.runtime.gate", "RuntimeGate"),
|
|
8
|
+
"GateConfig": ("cfa.runtime.gate", "GateConfig"),
|
|
9
|
+
"GateResult": ("cfa.runtime.gate", "GateResult"),
|
|
10
|
+
"GateViolation": ("cfa.runtime.gate", "GateViolation"),
|
|
11
|
+
"GovernanceViolation": ("cfa.runtime.gate", "GovernanceViolation"),
|
|
12
|
+
"runtime_gate": ("cfa.runtime.gate", "runtime_gate"),
|
|
13
|
+
})
|