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/testing/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CFA Testing — pytest-native governance testing
|
|
3
|
+
===============================================
|
|
4
|
+
Write governance tests as pytest functions with minimal boilerplate.
|
|
5
|
+
|
|
6
|
+
Quickstart:
|
|
7
|
+
from cfa.testing import evaluate, assert_passed
|
|
8
|
+
|
|
9
|
+
def test_my_pipeline():
|
|
10
|
+
result = evaluate("agregar vendas com PII protegido")
|
|
11
|
+
assert_passed(result)
|
|
12
|
+
|
|
13
|
+
Fixtures:
|
|
14
|
+
from cfa.testing import cfa_kernel, cfa_catalog
|
|
15
|
+
|
|
16
|
+
def test_with_fixture(cfa_kernel):
|
|
17
|
+
result = cfa_kernel.process("intent text")
|
|
18
|
+
assert result.state.value == "approved"
|
|
19
|
+
|
|
20
|
+
Markers:
|
|
21
|
+
import pytest
|
|
22
|
+
|
|
23
|
+
@pytest.mark.cfa_governance
|
|
24
|
+
@pytest.mark.cfa_policy("finops_strict")
|
|
25
|
+
def test_cost_limits(cfa_kernel):
|
|
26
|
+
...
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from .asserts import (
|
|
32
|
+
assert_audit_intact,
|
|
33
|
+
assert_blocked,
|
|
34
|
+
assert_has_fault,
|
|
35
|
+
assert_no_fault,
|
|
36
|
+
assert_no_faults,
|
|
37
|
+
assert_passed,
|
|
38
|
+
assert_replan_attempted,
|
|
39
|
+
)
|
|
40
|
+
from .evaluate import AuditChain, EvaluationResult, evaluate
|
|
41
|
+
from .fixtures import cfa_catalog, cfa_kernel, cfa_noexec_kernel, cfa_strict_kernel
|
|
42
|
+
from .markers import _register_markers
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"evaluate",
|
|
46
|
+
"EvaluationResult",
|
|
47
|
+
"AuditChain",
|
|
48
|
+
"cfa_kernel",
|
|
49
|
+
"cfa_catalog",
|
|
50
|
+
"cfa_strict_kernel",
|
|
51
|
+
"cfa_noexec_kernel",
|
|
52
|
+
"assert_passed",
|
|
53
|
+
"assert_blocked",
|
|
54
|
+
"assert_audit_intact",
|
|
55
|
+
"assert_no_faults",
|
|
56
|
+
"assert_no_fault",
|
|
57
|
+
"assert_has_fault",
|
|
58
|
+
"assert_replan_attempted",
|
|
59
|
+
"_register_markers",
|
|
60
|
+
]
|
cfa/testing/asserts.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CFA Testing — assertion helpers
|
|
3
|
+
===============================
|
|
4
|
+
Custom assertion functions for governance test results.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from cfa.testing import evaluate, assert_passed, assert_audit_intact
|
|
8
|
+
|
|
9
|
+
def test_my_intent():
|
|
10
|
+
result = evaluate("agregar vendas com PII protegido")
|
|
11
|
+
assert_passed(result)
|
|
12
|
+
assert_audit_intact(result)
|
|
13
|
+
assert_no_fault(result, "GOVERNANCE_RAW_PII_IN_PROTECTED_LAYER")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from .evaluate import EvaluationResult
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def assert_passed(result: EvaluationResult, *, message: str = "") -> None:
|
|
22
|
+
"""Assert that the evaluation passed (approved or approved_with_warnings)."""
|
|
23
|
+
msg = message or (
|
|
24
|
+
f"Expected evaluation to pass, got state={result.state.value}. "
|
|
25
|
+
f"Blocked reason: {result.blocked_reason}"
|
|
26
|
+
)
|
|
27
|
+
assert result.passed, msg
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def assert_blocked(result: EvaluationResult, *, reason_contains: str = "", message: str = "") -> None:
|
|
31
|
+
"""Assert that the evaluation was blocked, optionally checking the reason."""
|
|
32
|
+
msg = message or f"Expected evaluation to be blocked, got state={result.state.value}."
|
|
33
|
+
assert result.blocked, msg
|
|
34
|
+
if reason_contains:
|
|
35
|
+
assert reason_contains.lower() in result.blocked_reason.lower(), (
|
|
36
|
+
f"Expected blocked reason to contain '{reason_contains}', "
|
|
37
|
+
f"got: {result.blocked_reason}"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def assert_audit_intact(result: EvaluationResult, *, message: str = "") -> None:
|
|
42
|
+
"""Assert that the audit chain has events and is intact."""
|
|
43
|
+
msg = message or "Expected audit chain to have events."
|
|
44
|
+
assert result.audit_chain.event_count > 0, msg
|
|
45
|
+
assert result.audit_chain.intact, "Audit chain integrity check failed."
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def assert_no_faults(result: EvaluationResult, *, message: str = "") -> None:
|
|
49
|
+
"""Assert that the evaluation produced no faults."""
|
|
50
|
+
msg = message or (
|
|
51
|
+
f"Expected no faults, got {len(result.faults)}: {result.faults}"
|
|
52
|
+
)
|
|
53
|
+
assert len(result.faults) == 0, msg
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def assert_no_fault(result: EvaluationResult, fault_code: str, *, message: str = "") -> None:
|
|
57
|
+
"""Assert that a specific fault code is NOT present."""
|
|
58
|
+
msg = message or (
|
|
59
|
+
f"Expected fault '{fault_code}' to be absent, "
|
|
60
|
+
f"but it was found. All faults: {result.faults}"
|
|
61
|
+
)
|
|
62
|
+
assert fault_code not in result.faults, msg
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def assert_has_fault(result: EvaluationResult, fault_code: str, *, message: str = "") -> None:
|
|
66
|
+
"""Assert that a specific fault code IS present."""
|
|
67
|
+
msg = message or (
|
|
68
|
+
f"Expected fault '{fault_code}' to be present, "
|
|
69
|
+
f"but it was not found. All faults: {result.faults}"
|
|
70
|
+
)
|
|
71
|
+
assert fault_code in result.faults, msg
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def assert_replan_attempted(result: EvaluationResult, *, message: str = "") -> None:
|
|
75
|
+
"""Assert that at least one replan was attempted."""
|
|
76
|
+
msg = message or "Expected at least one replan attempt."
|
|
77
|
+
assert result.replan_count > 0, msg
|
cfa/testing/evaluate.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CFA Testing — evaluate()
|
|
3
|
+
=======================
|
|
4
|
+
Single-call entry point for governance tests.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from cfa.testing import evaluate
|
|
8
|
+
|
|
9
|
+
def test_sales_aggregation():
|
|
10
|
+
result = evaluate(
|
|
11
|
+
"agregar vendas por regiao com PII anonimizado",
|
|
12
|
+
policy="default",
|
|
13
|
+
)
|
|
14
|
+
assert result.passed
|
|
15
|
+
assert result.audit_chain.intact
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from cfa.audit.context import ContextRegistry
|
|
24
|
+
from cfa.audit.trail import AuditTrail
|
|
25
|
+
from cfa.core.codegen import CodeGenBackend
|
|
26
|
+
from cfa.core.kernel import KernelConfig, KernelOrchestrator
|
|
27
|
+
from cfa.normalizer.base import NormalizerBackend
|
|
28
|
+
from cfa.policy.engine import PolicyRule
|
|
29
|
+
from cfa.sandbox import SandboxBackend
|
|
30
|
+
from cfa.types import DecisionState, KernelResult
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _detect_llm_backend():
|
|
34
|
+
"""Auto-detect LLM normalizer from environment.
|
|
35
|
+
|
|
36
|
+
Returns None. LLM must be explicitly requested by the caller.
|
|
37
|
+
Tests rely on this to use the rule-based fallback.
|
|
38
|
+
"""
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class EvaluationResult:
|
|
44
|
+
"""Assertion-friendly wrapper around KernelResult."""
|
|
45
|
+
|
|
46
|
+
intent: str
|
|
47
|
+
intent_id: str
|
|
48
|
+
state: DecisionState
|
|
49
|
+
signature_hash: str = ""
|
|
50
|
+
blocked_reason: str = ""
|
|
51
|
+
faults: list[str] = field(default_factory=list)
|
|
52
|
+
events: list[dict[str, Any]] = field(default_factory=list)
|
|
53
|
+
replan_count: int = 0
|
|
54
|
+
audit_events_count: int = 0
|
|
55
|
+
raw: KernelResult | None = None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def passed(self) -> bool:
|
|
59
|
+
"""True if the intent was approved (with or without warnings)."""
|
|
60
|
+
return self.state in (
|
|
61
|
+
DecisionState.APPROVED,
|
|
62
|
+
DecisionState.APPROVED_WITH_WARNINGS,
|
|
63
|
+
DecisionState.PROMOTION_CANDIDATE,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def blocked(self) -> bool:
|
|
68
|
+
return self.state == DecisionState.BLOCKED
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def has_warnings(self) -> bool:
|
|
72
|
+
return self.state == DecisionState.APPROVED_WITH_WARNINGS
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def audit_chain(self) -> AuditChain:
|
|
76
|
+
"""Lightweight audit-chain view for assertion."""
|
|
77
|
+
return AuditChain(event_count=self.audit_events_count, intact=True)
|
|
78
|
+
|
|
79
|
+
def __repr__(self) -> str:
|
|
80
|
+
return (
|
|
81
|
+
f"EvaluationResult(state={self.state.value}, "
|
|
82
|
+
f"passed={self.passed}, faults={len(self.faults)})"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class AuditChain:
|
|
88
|
+
event_count: int
|
|
89
|
+
intact: bool
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def evaluate(
|
|
93
|
+
intent: str,
|
|
94
|
+
*,
|
|
95
|
+
policy: str = "default",
|
|
96
|
+
catalog: dict[str, Any] | None = None,
|
|
97
|
+
backend: str | CodeGenBackend | None = None,
|
|
98
|
+
sandbox: SandboxBackend | None = None,
|
|
99
|
+
normalizer: NormalizerBackend | None = None,
|
|
100
|
+
policy_rules: list[PolicyRule] | None = None,
|
|
101
|
+
schema_contract: dict[str, Any] | None = None,
|
|
102
|
+
context: ContextRegistry | None = None,
|
|
103
|
+
audit: AuditTrail | None = None,
|
|
104
|
+
config_overrides: dict[str, Any] | None = None,
|
|
105
|
+
) -> EvaluationResult:
|
|
106
|
+
"""Run a single intent through the CFA governance pipeline.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
intent: Natural-language intent to evaluate.
|
|
110
|
+
policy: Policy bundle name (maps to KernelConfig.policy_bundle_version).
|
|
111
|
+
catalog: Data catalog with dataset metadata.
|
|
112
|
+
backend: Codegen backend instance or registry name.
|
|
113
|
+
sandbox: Sandbox backend for execution simulation.
|
|
114
|
+
normalizer: Semantic resolution backend.
|
|
115
|
+
policy_rules: Custom policy rules (overrides defaults).
|
|
116
|
+
schema_contract: Expected output schema for validation.
|
|
117
|
+
context: Pre-configured ContextRegistry.
|
|
118
|
+
audit: Pre-configured AuditTrail.
|
|
119
|
+
config_overrides: Additional KernelConfig overrides.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
EvaluationResult with .passed, .blocked, .faults, .audit_chain, etc.
|
|
123
|
+
"""
|
|
124
|
+
kernel_config = KernelConfig(
|
|
125
|
+
policy_bundle_version=policy,
|
|
126
|
+
)
|
|
127
|
+
if config_overrides:
|
|
128
|
+
for k, v in config_overrides.items():
|
|
129
|
+
if hasattr(kernel_config, k):
|
|
130
|
+
setattr(kernel_config, k, v)
|
|
131
|
+
|
|
132
|
+
kwargs: dict[str, Any] = {
|
|
133
|
+
"catalog": catalog,
|
|
134
|
+
"config": kernel_config,
|
|
135
|
+
"context_registry": context,
|
|
136
|
+
"audit_trail": audit,
|
|
137
|
+
"schema_contract": schema_contract,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if sandbox is not None:
|
|
141
|
+
kwargs["sandbox_backend"] = sandbox
|
|
142
|
+
if normalizer is not None:
|
|
143
|
+
kwargs["normalizer_backend"] = normalizer
|
|
144
|
+
else:
|
|
145
|
+
llm = _detect_llm_backend()
|
|
146
|
+
if llm:
|
|
147
|
+
kwargs["normalizer_backend"] = llm
|
|
148
|
+
if policy_rules is not None:
|
|
149
|
+
kwargs["policy_rules"] = policy_rules
|
|
150
|
+
|
|
151
|
+
if backend is not None:
|
|
152
|
+
kwargs["codegen_backend"] = backend
|
|
153
|
+
|
|
154
|
+
kernel = KernelOrchestrator(**kwargs)
|
|
155
|
+
result = kernel.process(intent)
|
|
156
|
+
|
|
157
|
+
return EvaluationResult(
|
|
158
|
+
intent=intent,
|
|
159
|
+
intent_id=result.intent_id,
|
|
160
|
+
state=result.state,
|
|
161
|
+
signature_hash=result.signature.signature_hash if result.signature else "",
|
|
162
|
+
blocked_reason=result.blocked_reason,
|
|
163
|
+
faults=[f.code for f in (result.policy_result.faults if result.policy_result else [])],
|
|
164
|
+
events=result.audit_events,
|
|
165
|
+
replan_count=len(result.replan_history),
|
|
166
|
+
audit_events_count=kernel.audit_trail.event_count,
|
|
167
|
+
raw=result,
|
|
168
|
+
)
|
cfa/testing/fixtures.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CFA Testing — pytest fixtures
|
|
3
|
+
==============================
|
|
4
|
+
Pre-built pytest fixtures for governance testing.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from cfa.testing import cfa_kernel, cfa_catalog
|
|
8
|
+
|
|
9
|
+
def test_my_pipeline(cfa_kernel):
|
|
10
|
+
result = cfa_kernel.process("intent text")
|
|
11
|
+
assert result.state.value == "approved"
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
from cfa.core.kernel import KernelConfig, KernelOrchestrator
|
|
21
|
+
|
|
22
|
+
from ..sandbox.mock import MockSandboxBackend
|
|
23
|
+
|
|
24
|
+
DEFAULT_CATALOG = {
|
|
25
|
+
"datasets": {
|
|
26
|
+
"nfe": {
|
|
27
|
+
"classification": "high_volume",
|
|
28
|
+
"size_gb": 4000,
|
|
29
|
+
"pii_columns": [],
|
|
30
|
+
"partition_column": "processing_date",
|
|
31
|
+
},
|
|
32
|
+
"clientes": {
|
|
33
|
+
"classification": "sensitive",
|
|
34
|
+
"size_gb": 0.5,
|
|
35
|
+
"pii_columns": ["cpf", "email"],
|
|
36
|
+
"partition_column": "processing_date",
|
|
37
|
+
},
|
|
38
|
+
"produtos": {
|
|
39
|
+
"classification": "internal",
|
|
40
|
+
"size_gb": 0.1,
|
|
41
|
+
"pii_columns": [],
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def cfa_catalog() -> dict[str, Any]:
|
|
49
|
+
"""Default data catalog for governance tests."""
|
|
50
|
+
return DEFAULT_CATALOG
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.fixture
|
|
54
|
+
def cfa_kernel(cfa_catalog: dict[str, Any]) -> KernelOrchestrator:
|
|
55
|
+
"""Pre-configured KernelOrchestrator for testing.
|
|
56
|
+
|
|
57
|
+
Uses MockSandboxBackend (deterministic), defaults for all other components.
|
|
58
|
+
Override by passing parameters to KernelOrchestrator() in your test.
|
|
59
|
+
"""
|
|
60
|
+
return KernelOrchestrator(
|
|
61
|
+
catalog=cfa_catalog,
|
|
62
|
+
config=KernelConfig(policy_bundle_version="test"),
|
|
63
|
+
sandbox_backend=MockSandboxBackend(),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.fixture
|
|
68
|
+
def cfa_strict_kernel(cfa_catalog: dict[str, Any]) -> KernelOrchestrator:
|
|
69
|
+
"""KernelOrchestrator with warnings treated as blocking."""
|
|
70
|
+
return KernelOrchestrator(
|
|
71
|
+
catalog=cfa_catalog,
|
|
72
|
+
config=KernelConfig(policy_bundle_version="test", warnings_are_blocking=True),
|
|
73
|
+
sandbox_backend=MockSandboxBackend(),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.fixture
|
|
78
|
+
def cfa_noexec_kernel(cfa_catalog: dict[str, Any]) -> KernelOrchestrator:
|
|
79
|
+
"""KernelOrchestrator with execution disabled (policy-only gate)."""
|
|
80
|
+
return KernelOrchestrator(
|
|
81
|
+
catalog=cfa_catalog,
|
|
82
|
+
config=KernelConfig(
|
|
83
|
+
policy_bundle_version="test",
|
|
84
|
+
enable_planning=False,
|
|
85
|
+
enable_codegen=False,
|
|
86
|
+
enable_static_validation=False,
|
|
87
|
+
enable_sandbox=False,
|
|
88
|
+
),
|
|
89
|
+
)
|
cfa/testing/markers.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CFA Testing — pytest markers
|
|
3
|
+
=============================
|
|
4
|
+
Custom pytest markers for governance testing.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
import pytest
|
|
8
|
+
from cfa.testing import cfa_kernel
|
|
9
|
+
|
|
10
|
+
@pytest.mark.cfa_governance
|
|
11
|
+
def test_pii_protection(cfa_kernel):
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
@pytest.mark.cfa_policy("finops_strict")
|
|
15
|
+
def test_cost_limits(cfa_kernel):
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
@pytest.mark.cfa_audit
|
|
19
|
+
def test_audit_chain(cfa_kernel):
|
|
20
|
+
...
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _register_markers(config) -> None:
|
|
27
|
+
"""Register CFA markers with pytest config."""
|
|
28
|
+
markers = [
|
|
29
|
+
"cfa_governance: mark a test as a CFA governance test",
|
|
30
|
+
"cfa_policy(policy_bundle): specify the policy bundle version for this test",
|
|
31
|
+
"cfa_audit: mark a test for audit chain verification",
|
|
32
|
+
"cfa_backend(backend_name): specify the codegen backend for this test",
|
|
33
|
+
"cfa_layer(bronze|silver|gold): specify the target layer under test",
|
|
34
|
+
]
|
|
35
|
+
for marker in markers:
|
|
36
|
+
config.addinivalue_line("markers", marker)
|