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
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
"""CFA Kernel Phases — the 5-phase governance pipeline executor.
|
|
2
|
+
|
|
3
|
+
Extracted from KernelOrchestrator to keep the orchestrator thin.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import uuid
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from cfa.audit.context import ContextRegistry
|
|
12
|
+
from cfa.audit.trail import AuditTrail
|
|
13
|
+
from cfa.core.codegen import CodeGenBackend
|
|
14
|
+
from cfa.core.kernel import KernelConfig, PipelinePhase
|
|
15
|
+
from cfa.core.planner import ExecutionPlanner
|
|
16
|
+
from cfa.execution.partial import (
|
|
17
|
+
PartialExecutionManager,
|
|
18
|
+
PublishState,
|
|
19
|
+
)
|
|
20
|
+
from cfa.execution.state_projection import StateProjectionProtocol
|
|
21
|
+
from cfa.normalizer.base import (
|
|
22
|
+
ConfirmationOrchestrator,
|
|
23
|
+
IntentNormalizer,
|
|
24
|
+
)
|
|
25
|
+
from cfa.observability.indices import ExecutionRecord
|
|
26
|
+
from cfa.observability.promotion import PromotionEngine, SkillState
|
|
27
|
+
from cfa.policy.catalog import validate_catalog
|
|
28
|
+
from cfa.policy.engine import PolicyEngine
|
|
29
|
+
from cfa.sandbox.executor import SandboxExecutor
|
|
30
|
+
from cfa.types import (
|
|
31
|
+
DecisionState,
|
|
32
|
+
Fault,
|
|
33
|
+
FaultFamily,
|
|
34
|
+
FaultSeverity,
|
|
35
|
+
KernelResult,
|
|
36
|
+
PolicyAction,
|
|
37
|
+
PolicyResult,
|
|
38
|
+
SemanticResolution,
|
|
39
|
+
StateSignature,
|
|
40
|
+
_utcnow,
|
|
41
|
+
)
|
|
42
|
+
from cfa.validation.runtime import RuntimeValidator
|
|
43
|
+
from cfa.validation.static import StaticValidator
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class KernelPhases:
|
|
47
|
+
"""Executes the 5-phase CFA governance pipeline.
|
|
48
|
+
|
|
49
|
+
Created by KernelOrchestrator with all dependencies injected.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
config: KernelConfig,
|
|
55
|
+
context_registry: ContextRegistry,
|
|
56
|
+
audit_trail: AuditTrail,
|
|
57
|
+
catalog: dict[str, Any],
|
|
58
|
+
schema_contract: dict[str, Any] | None,
|
|
59
|
+
normalizer: IntentNormalizer,
|
|
60
|
+
confirmation: ConfirmationOrchestrator,
|
|
61
|
+
policy: PolicyEngine,
|
|
62
|
+
planner: ExecutionPlanner,
|
|
63
|
+
codegen: CodeGenBackend,
|
|
64
|
+
static_validator: StaticValidator,
|
|
65
|
+
sandbox_executor: SandboxExecutor,
|
|
66
|
+
runtime_validator: RuntimeValidator,
|
|
67
|
+
partial_execution_manager: PartialExecutionManager,
|
|
68
|
+
state_projection: StateProjectionProtocol,
|
|
69
|
+
promotion_engine: PromotionEngine,
|
|
70
|
+
) -> None:
|
|
71
|
+
self.config = config
|
|
72
|
+
self.context_registry = context_registry
|
|
73
|
+
self.audit_trail = audit_trail
|
|
74
|
+
self.catalog = catalog
|
|
75
|
+
self.schema_contract = schema_contract
|
|
76
|
+
self.normalizer = normalizer
|
|
77
|
+
self.confirmation = confirmation
|
|
78
|
+
self.policy = policy
|
|
79
|
+
self.planner = planner
|
|
80
|
+
self.codegen = codegen
|
|
81
|
+
self.static_validator = static_validator
|
|
82
|
+
self.sandbox_executor = sandbox_executor
|
|
83
|
+
self.runtime_validator = runtime_validator
|
|
84
|
+
self.partial_execution_manager = partial_execution_manager
|
|
85
|
+
self.state_projection = state_projection
|
|
86
|
+
self.promotion_engine = promotion_engine
|
|
87
|
+
|
|
88
|
+
# ── Public API ────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
def process(self, raw_intent: str) -> KernelResult:
|
|
91
|
+
intent_id = str(uuid.uuid4())
|
|
92
|
+
result = KernelResult(intent_id=intent_id, state=DecisionState.BLOCKED)
|
|
93
|
+
|
|
94
|
+
if self.config.phase_formalize:
|
|
95
|
+
signature, early = self._phase_formalize(intent_id, raw_intent, result)
|
|
96
|
+
if early:
|
|
97
|
+
return result
|
|
98
|
+
result.signature = signature
|
|
99
|
+
else:
|
|
100
|
+
return result
|
|
101
|
+
|
|
102
|
+
if self.config.phase_govern:
|
|
103
|
+
signature, policy_ok, replan_count = self._phase_govern(intent_id, result)
|
|
104
|
+
if not policy_ok:
|
|
105
|
+
return result
|
|
106
|
+
else:
|
|
107
|
+
replan_count = 0
|
|
108
|
+
|
|
109
|
+
if self.config.phase_generate and self.config.enable_planning:
|
|
110
|
+
early = self._phase_generate(intent_id, signature, result)
|
|
111
|
+
if early:
|
|
112
|
+
return result
|
|
113
|
+
if self.config.phase_execute and self.config.enable_sandbox:
|
|
114
|
+
early = self._phase_execute(intent_id, signature, result, replan_count)
|
|
115
|
+
if early:
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
self._phase_validate(intent_id, signature, result, replan_count)
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
# ── Phase 1: Formalize ────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
def _phase_formalize(
|
|
124
|
+
self, intent_id: str, raw_intent: str, result: KernelResult
|
|
125
|
+
) -> tuple[StateSignature | None, bool]:
|
|
126
|
+
if self.config.strict_normalization:
|
|
127
|
+
catalog_result = validate_catalog(self.catalog, require_datasets=True)
|
|
128
|
+
if not catalog_result.valid:
|
|
129
|
+
fault = catalog_result.to_fault()
|
|
130
|
+
result.state = DecisionState.BLOCKED
|
|
131
|
+
result.blocked_reason = fault.message
|
|
132
|
+
result.policy_result = PolicyResult(
|
|
133
|
+
action=PolicyAction.BLOCK, faults=[fault],
|
|
134
|
+
reasoning="; ".join(catalog_result.messages),
|
|
135
|
+
)
|
|
136
|
+
self._audit(intent_id, PipelinePhase.FORMALIZE, "catalog_validation", "blocked",
|
|
137
|
+
issues=catalog_result.messages)
|
|
138
|
+
result.add_event(PipelinePhase.FORMALIZE, "catalog_validation", "blocked",
|
|
139
|
+
issues=catalog_result.messages)
|
|
140
|
+
return None, True
|
|
141
|
+
|
|
142
|
+
environment_state = self.context_registry.get_environment_state()
|
|
143
|
+
self._audit(intent_id, PipelinePhase.FORMALIZE, "environment_state_consulted", "ok",
|
|
144
|
+
version_id=self.context_registry.version_id)
|
|
145
|
+
result.add_event(PipelinePhase.FORMALIZE, "environment_state_consulted", "ok",
|
|
146
|
+
version_id=self.context_registry.version_id)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
resolution = self.normalizer.normalize(
|
|
150
|
+
raw_intent=raw_intent, environment_state=environment_state,
|
|
151
|
+
catalog=self.catalog,
|
|
152
|
+
context_registry_version_id=self.context_registry.version_id,
|
|
153
|
+
)
|
|
154
|
+
result.resolution = resolution
|
|
155
|
+
self._audit(intent_id, PipelinePhase.FORMALIZE, "semantic_resolution", "resolved",
|
|
156
|
+
confidence=resolution.confidence_score,
|
|
157
|
+
confirmation_mode=resolution.confirmation_mode.value)
|
|
158
|
+
result.add_event(PipelinePhase.FORMALIZE, "semantic_resolution", "resolved",
|
|
159
|
+
confidence=resolution.confidence_score,
|
|
160
|
+
confirmation_mode=resolution.confirmation_mode.value)
|
|
161
|
+
except (ValueError, TypeError, ImportError, RuntimeError, KeyError) as e:
|
|
162
|
+
result.blocked_reason = f"Normalization failed: {e}"
|
|
163
|
+
self._audit(intent_id, PipelinePhase.FORMALIZE, "normalization_error", "error", error=str(e))
|
|
164
|
+
result.add_event(PipelinePhase.FORMALIZE, "normalization_error", "error", error=str(e))
|
|
165
|
+
return None, True
|
|
166
|
+
|
|
167
|
+
if self.config.strict_normalization:
|
|
168
|
+
strict_fault = self._strict_normalization_fault(resolution)
|
|
169
|
+
if strict_fault:
|
|
170
|
+
result.state = DecisionState.BLOCKED
|
|
171
|
+
result.blocked_reason = strict_fault.message
|
|
172
|
+
result.policy_result = PolicyResult(
|
|
173
|
+
action=PolicyAction.BLOCK, faults=[strict_fault],
|
|
174
|
+
reasoning=strict_fault.message,
|
|
175
|
+
)
|
|
176
|
+
self._audit(intent_id, PipelinePhase.FORMALIZE, "strict_normalization", "blocked",
|
|
177
|
+
fault=strict_fault.code)
|
|
178
|
+
result.add_event(PipelinePhase.FORMALIZE, "strict_normalization", "blocked",
|
|
179
|
+
fault=strict_fault.code)
|
|
180
|
+
return None, True
|
|
181
|
+
|
|
182
|
+
approved, confirm_reason, confirm_fault = self.confirmation.process(resolution)
|
|
183
|
+
self._audit(intent_id, PipelinePhase.FORMALIZE, "confirmation",
|
|
184
|
+
"approved" if approved else "rejected",
|
|
185
|
+
mode=resolution.confirmation_mode.value, reason=confirm_reason)
|
|
186
|
+
result.add_event(PipelinePhase.FORMALIZE, "confirmation",
|
|
187
|
+
"approved" if approved else "rejected",
|
|
188
|
+
mode=resolution.confirmation_mode.value, reason=confirm_reason)
|
|
189
|
+
|
|
190
|
+
if not approved:
|
|
191
|
+
result.state = DecisionState.BLOCKED
|
|
192
|
+
result.blocked_reason = confirm_reason
|
|
193
|
+
if confirm_fault:
|
|
194
|
+
result.policy_result = PolicyResult(
|
|
195
|
+
action=PolicyAction.BLOCK, faults=[confirm_fault],
|
|
196
|
+
reasoning=confirm_reason,
|
|
197
|
+
)
|
|
198
|
+
return None, True
|
|
199
|
+
|
|
200
|
+
return resolution.signature, False
|
|
201
|
+
|
|
202
|
+
# ── Phase 2: Govern ───────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
def _phase_govern(
|
|
205
|
+
self, intent_id: str, result: KernelResult
|
|
206
|
+
) -> tuple[StateSignature, bool, int]:
|
|
207
|
+
signature = result.resolution.signature # type: ignore[union-attr]
|
|
208
|
+
replan_count = 0
|
|
209
|
+
|
|
210
|
+
while True:
|
|
211
|
+
policy_result = self.policy.evaluate(signature, replan_count=replan_count)
|
|
212
|
+
result.policy_result = policy_result
|
|
213
|
+
self._audit(intent_id, PipelinePhase.GOVERN, "policy_evaluation",
|
|
214
|
+
policy_result.action.value, replan_count=replan_count,
|
|
215
|
+
faults=[f.code for f in policy_result.faults])
|
|
216
|
+
result.add_event(PipelinePhase.GOVERN, "policy_evaluation",
|
|
217
|
+
policy_result.action.value, replan_count=replan_count,
|
|
218
|
+
faults=[f.code for f in policy_result.faults])
|
|
219
|
+
|
|
220
|
+
if policy_result.action == PolicyAction.APPROVE:
|
|
221
|
+
return signature, True, replan_count
|
|
222
|
+
if policy_result.action == PolicyAction.BLOCK:
|
|
223
|
+
result.state = DecisionState.BLOCKED
|
|
224
|
+
result.blocked_reason = policy_result.reasoning
|
|
225
|
+
return signature, False, replan_count
|
|
226
|
+
|
|
227
|
+
result.replan_history.append(policy_result)
|
|
228
|
+
replan_count += 1
|
|
229
|
+
new_signature = _apply_interventions(signature, policy_result)
|
|
230
|
+
if new_signature is None:
|
|
231
|
+
result.state = DecisionState.BLOCKED
|
|
232
|
+
result.blocked_reason = "Replan failed: interventions not applicable."
|
|
233
|
+
return signature, False, replan_count
|
|
234
|
+
|
|
235
|
+
signature = new_signature
|
|
236
|
+
self._audit(intent_id, PipelinePhase.GOVERN, "replan_applied", "replanned",
|
|
237
|
+
replan_count=replan_count, interventions=policy_result.interventions)
|
|
238
|
+
result.add_event(PipelinePhase.GOVERN, "replan_applied", "replanned",
|
|
239
|
+
replan_count=replan_count, interventions=policy_result.interventions)
|
|
240
|
+
|
|
241
|
+
# ── Phase 3: Generate ─────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
def _phase_generate(
|
|
244
|
+
self, intent_id: str, signature: StateSignature, result: KernelResult
|
|
245
|
+
) -> bool:
|
|
246
|
+
plan = self.planner.plan(signature)
|
|
247
|
+
result.execution_plan = plan
|
|
248
|
+
self._audit(intent_id, PipelinePhase.GENERATE, "plan_generated", "ok",
|
|
249
|
+
step_count=plan.step_count, consistency_unit=plan.consistency_unit.value,
|
|
250
|
+
write_mode=plan.write_mode.value)
|
|
251
|
+
result.add_event(PipelinePhase.GENERATE, "plan_generated", "ok",
|
|
252
|
+
step_count=plan.step_count, consistency_unit=plan.consistency_unit.value,
|
|
253
|
+
write_mode=plan.write_mode.value)
|
|
254
|
+
|
|
255
|
+
if not self.config.enable_codegen:
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
generated = self.codegen.generate(plan)
|
|
259
|
+
result.generated_code = generated
|
|
260
|
+
self._audit(intent_id, PipelinePhase.GENERATE, "code_generated", "ok",
|
|
261
|
+
language=generated.language, line_count=generated.line_count)
|
|
262
|
+
result.add_event(PipelinePhase.GENERATE, "code_generated", "ok",
|
|
263
|
+
language=generated.language, line_count=generated.line_count)
|
|
264
|
+
|
|
265
|
+
if not self.config.enable_static_validation:
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
sv_result = self.static_validator.validate(
|
|
269
|
+
generated, signature, self.schema_contract, backend=self.codegen)
|
|
270
|
+
result.static_validation = sv_result
|
|
271
|
+
sv_outcome = "passed" if sv_result.passed else "blocked"
|
|
272
|
+
self._audit(intent_id, PipelinePhase.GENERATE, "static_validation", sv_outcome,
|
|
273
|
+
checks=sv_result.checks_performed, faults=[f.code for f in sv_result.faults])
|
|
274
|
+
result.add_event(PipelinePhase.GENERATE, "static_validation", sv_outcome,
|
|
275
|
+
checks=sv_result.checks_performed, faults=[f.code for f in sv_result.faults])
|
|
276
|
+
|
|
277
|
+
if not sv_result.passed:
|
|
278
|
+
result.state = DecisionState.BLOCKED
|
|
279
|
+
result.blocked_reason = f"Static validation failed: {', '.join(sv_result.fault_codes)}"
|
|
280
|
+
result.signature = signature
|
|
281
|
+
return True
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
# ── Phase 4: Execute ──────────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
def _phase_execute(
|
|
287
|
+
self, intent_id: str, signature: StateSignature, result: KernelResult, replan_count: int,
|
|
288
|
+
) -> bool:
|
|
289
|
+
plan = result.execution_plan
|
|
290
|
+
generated = result.generated_code
|
|
291
|
+
exec_state = self.partial_execution_manager.execute(
|
|
292
|
+
plan, generated, signature, self.schema_contract)
|
|
293
|
+
result.execution_state = exec_state
|
|
294
|
+
result.sandbox_result = exec_state.sandbox_result
|
|
295
|
+
result.runtime_validation = exec_state.runtime_validation
|
|
296
|
+
|
|
297
|
+
self._audit(intent_id, PipelinePhase.EXECUTE, "execution_completed",
|
|
298
|
+
exec_state.publish_state.value, publish_state=exec_state.publish_state.value,
|
|
299
|
+
quarantined=exec_state.quarantined_steps,
|
|
300
|
+
committed=exec_state.committed_steps)
|
|
301
|
+
result.add_event(PipelinePhase.EXECUTE, "execution_completed",
|
|
302
|
+
exec_state.publish_state.value, publish_state=exec_state.publish_state.value,
|
|
303
|
+
quarantined=exec_state.quarantined_steps,
|
|
304
|
+
committed=exec_state.committed_steps)
|
|
305
|
+
|
|
306
|
+
projection = self.state_projection.project(signature, exec_state)
|
|
307
|
+
self._audit(intent_id, PipelinePhase.EXECUTE, "state_projected",
|
|
308
|
+
projection.projection_type, snapshot_version=projection.snapshot_version,
|
|
309
|
+
datasets_updated=projection.dataset_states_updated,
|
|
310
|
+
projected=projection.projected, audit_only=projection.audit_only)
|
|
311
|
+
result.add_event(PipelinePhase.EXECUTE, "state_projected",
|
|
312
|
+
projection.projection_type, snapshot_version=projection.snapshot_version,
|
|
313
|
+
datasets_updated=projection.dataset_states_updated,
|
|
314
|
+
projected=projection.projected, audit_only=projection.audit_only)
|
|
315
|
+
|
|
316
|
+
if exec_state.publish_state in (PublishState.ROLLED_BACK, PublishState.QUARANTINED,
|
|
317
|
+
PublishState.COMMITTED_NOT_PUBLISHED):
|
|
318
|
+
state_map = {
|
|
319
|
+
PublishState.ROLLED_BACK: (DecisionState.ROLLED_BACK, "Execution rolled back."),
|
|
320
|
+
PublishState.QUARANTINED: (DecisionState.QUARANTINED,
|
|
321
|
+
f"Steps quarantined: {exec_state.quarantined_steps}"),
|
|
322
|
+
PublishState.COMMITTED_NOT_PUBLISHED: (DecisionState.PARTIALLY_COMMITTED, ""),
|
|
323
|
+
}
|
|
324
|
+
d_state, reason = state_map[exec_state.publish_state]
|
|
325
|
+
result.state = d_state
|
|
326
|
+
result.blocked_reason = reason
|
|
327
|
+
result.signature = signature
|
|
328
|
+
self._finalize_execution_result(result, signature, replan_count)
|
|
329
|
+
return True
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
# ── Phase 5: Validate ─────────────────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
def _phase_validate(
|
|
335
|
+
self, intent_id: str, signature: StateSignature, result: KernelResult, replan_count: int,
|
|
336
|
+
) -> None:
|
|
337
|
+
if not self.config.phase_validate:
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
policy_result = result.policy_result
|
|
341
|
+
has_warnings = (
|
|
342
|
+
policy_result is not None
|
|
343
|
+
and any(f.severity == FaultSeverity.WARNING for f in policy_result.faults)
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
if has_warnings and self.config.warnings_are_blocking:
|
|
347
|
+
result.state = DecisionState.BLOCKED
|
|
348
|
+
result.blocked_reason = "Warnings treated as blocking (config)."
|
|
349
|
+
elif has_warnings or replan_count > 0:
|
|
350
|
+
result.state = DecisionState.APPROVED_WITH_WARNINGS
|
|
351
|
+
else:
|
|
352
|
+
result.state = DecisionState.APPROVED
|
|
353
|
+
|
|
354
|
+
result.signature = signature
|
|
355
|
+
self._finalize_execution_result(result, signature, replan_count)
|
|
356
|
+
|
|
357
|
+
# ── Internal helpers ──────────────────────────────────────────────────────
|
|
358
|
+
|
|
359
|
+
def _strict_normalization_fault(self, resolution: SemanticResolution) -> Fault | None:
|
|
360
|
+
sig = resolution.signature
|
|
361
|
+
if not sig.datasets:
|
|
362
|
+
return Fault(
|
|
363
|
+
code="NORMALIZATION_NO_CATALOG_DATASET_MATCH", family=FaultFamily.SEMANTIC,
|
|
364
|
+
severity=FaultSeverity.CRITICAL, stage="intent_normalizer",
|
|
365
|
+
message="Strict normalization blocked the intent because no catalog dataset was matched.",
|
|
366
|
+
mandatory_action=PolicyAction.BLOCK,
|
|
367
|
+
remediation=("Reference datasets that exist in the catalog or update the catalog.",),
|
|
368
|
+
)
|
|
369
|
+
if resolution.confidence_score < self.config.min_normalizer_confidence:
|
|
370
|
+
return Fault(
|
|
371
|
+
code="NORMALIZATION_LOW_CONFIDENCE", family=FaultFamily.SEMANTIC,
|
|
372
|
+
severity=FaultSeverity.HIGH, stage="intent_normalizer",
|
|
373
|
+
message=(
|
|
374
|
+
f"Strict normalization blocked: confidence {resolution.confidence_score:.2f} "
|
|
375
|
+
f"below {self.config.min_normalizer_confidence:.2f}."
|
|
376
|
+
),
|
|
377
|
+
mandatory_action=PolicyAction.BLOCK,
|
|
378
|
+
remediation=("Make the requested datasets, target layer, and operation explicit.",),
|
|
379
|
+
)
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
def _audit(
|
|
383
|
+
self, intent_id: str, stage: str, event_type: str, outcome: str, **details: Any
|
|
384
|
+
) -> None:
|
|
385
|
+
self.audit_trail.record(
|
|
386
|
+
intent_id=intent_id, stage=str(stage), event_type=event_type, outcome=outcome,
|
|
387
|
+
policy_bundle_version=self.config.policy_bundle_version, **details,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
def _finalize_execution_result(
|
|
391
|
+
self, result: KernelResult, signature: StateSignature, replan_count: int,
|
|
392
|
+
) -> None:
|
|
393
|
+
intent_id = result.intent_id
|
|
394
|
+
final_state = result.state
|
|
395
|
+
exec_state = result.execution_state
|
|
396
|
+
policy_result = result.policy_result
|
|
397
|
+
|
|
398
|
+
self.context_registry.record_execution(
|
|
399
|
+
intent_id=intent_id, outcome=final_state.value,
|
|
400
|
+
signature_hash=signature.signature_hash,
|
|
401
|
+
)
|
|
402
|
+
self._audit(intent_id, PipelinePhase.VALIDATE, "final_decision",
|
|
403
|
+
final_state.value, signature_hash=signature.signature_hash,
|
|
404
|
+
replan_count=replan_count,
|
|
405
|
+
warnings=final_state == DecisionState.APPROVED_WITH_WARNINGS)
|
|
406
|
+
result.add_event(PipelinePhase.VALIDATE, "final_decision",
|
|
407
|
+
final_state.value, signature_hash=signature.signature_hash,
|
|
408
|
+
replan_count=replan_count,
|
|
409
|
+
warnings=final_state == DecisionState.APPROVED_WITH_WARNINGS)
|
|
410
|
+
|
|
411
|
+
if not self.config.enable_promotion:
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
all_faults: list[str] = []
|
|
415
|
+
if policy_result:
|
|
416
|
+
all_faults.extend(f.code for f in policy_result.faults)
|
|
417
|
+
if exec_state:
|
|
418
|
+
all_faults.extend(f.code for f in exec_state.faults)
|
|
419
|
+
|
|
420
|
+
cost_dbu = 0.0
|
|
421
|
+
duration_seconds = 0.0
|
|
422
|
+
if exec_state and exec_state.sandbox_result:
|
|
423
|
+
cost_dbu = exec_state.sandbox_result.aggregate_metrics.cost_dbu
|
|
424
|
+
duration_seconds = exec_state.sandbox_result.aggregate_metrics.duration_seconds
|
|
425
|
+
|
|
426
|
+
exec_record = ExecutionRecord(
|
|
427
|
+
signature_hash=signature.signature_hash, timestamp=_utcnow(),
|
|
428
|
+
success=final_state in (DecisionState.APPROVED, DecisionState.APPROVED_WITH_WARNINGS),
|
|
429
|
+
replanned=replan_count > 0, cost_dbu=cost_dbu, duration_seconds=duration_seconds,
|
|
430
|
+
faults=all_faults,
|
|
431
|
+
policy_compliant=final_state not in (DecisionState.ROLLED_BACK, DecisionState.QUARANTINED),
|
|
432
|
+
pii_exposure=any("PII" in code for code in all_faults),
|
|
433
|
+
layer_adherent=final_state != DecisionState.ROLLED_BACK,
|
|
434
|
+
)
|
|
435
|
+
self.promotion_engine.record_execution(exec_record)
|
|
436
|
+
|
|
437
|
+
skill, scores = self.promotion_engine.evaluate(
|
|
438
|
+
signature.signature_hash,
|
|
439
|
+
policy_bundle_version=self.config.policy_bundle_version,
|
|
440
|
+
catalog_snapshot_version=self.config.catalog_snapshot_version,
|
|
441
|
+
)
|
|
442
|
+
self._audit(intent_id, PipelinePhase.VALIDATE, "promotion_evaluation",
|
|
443
|
+
skill.state.value, ifo=scores.ifo, ifs=scores.ifs,
|
|
444
|
+
ifg=scores.ifg, idi=scores.idi, execution_count=scores.execution_count,
|
|
445
|
+
skill_state=skill.state.value)
|
|
446
|
+
result.add_event(PipelinePhase.VALIDATE, "promotion_evaluation",
|
|
447
|
+
skill.state.value, ifo=scores.ifo, ifs=scores.ifs,
|
|
448
|
+
ifg=scores.ifg, idi=scores.idi, execution_count=scores.execution_count,
|
|
449
|
+
skill_state=skill.state.value)
|
|
450
|
+
|
|
451
|
+
if skill.state == SkillState.ACTIVE and final_state == DecisionState.APPROVED:
|
|
452
|
+
result.state = DecisionState.PROMOTION_CANDIDATE
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
# ── Standalone helpers ───────────────────────────────────────────────────────
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _apply_interventions(
|
|
459
|
+
signature: StateSignature, policy_result: PolicyResult,
|
|
460
|
+
) -> StateSignature | None:
|
|
461
|
+
fault_codes = {f.code for f in policy_result.faults}
|
|
462
|
+
overrides: dict[str, Any] = {}
|
|
463
|
+
if "GOVERNANCE_RAW_PII_IN_PROTECTED_LAYER" in fault_codes:
|
|
464
|
+
overrides["no_pii_raw"] = True
|
|
465
|
+
if "FINOPS_MISSING_TEMPORAL_PREDICATE" in fault_codes:
|
|
466
|
+
if not signature.constraints.partition_by:
|
|
467
|
+
overrides["partition_by"] = ("processing_date",)
|
|
468
|
+
if "CONTRACT_TYPE_ENFORCEMENT_DISABLED" in fault_codes:
|
|
469
|
+
overrides["enforce_types"] = True
|
|
470
|
+
if "CONTRACT_MISSING_MERGE_KEY" in fault_codes:
|
|
471
|
+
overrides["merge_key_required"] = True
|
|
472
|
+
if "FINOPS_SENSITIVE_WITHOUT_PARTITION" in fault_codes:
|
|
473
|
+
if not signature.constraints.partition_by:
|
|
474
|
+
overrides["partition_by"] = ("processing_date",)
|
|
475
|
+
if not overrides:
|
|
476
|
+
return None
|
|
477
|
+
return signature.with_constraints(**overrides)
|