contractforge-fabric 0.1.0__tar.gz

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.
@@ -0,0 +1,19 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .uv-cache/
7
+ .venv/
8
+ .tmp/
9
+ dist/
10
+ build/
11
+ *.egg
12
+ .coverage
13
+ .coverage.*
14
+ htmlcov/
15
+ .DS_Store
16
+ .env
17
+ .env.*
18
+ !.env.example
19
+ api-key-*.txt
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ All notable changes to `contractforge-fabric` are documented in this file.
4
+
5
+ ## 0.1.0
6
+
7
+ - Added the initial render-only Fabric Lakehouse adapter package.
8
+ - Added conservative capabilities, source-support classification, review
9
+ artifacts and CLI commands.
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: contractforge-fabric
3
+ Version: 0.1.0
4
+ Summary: Microsoft Fabric adapter for ContractForge Core.
5
+ Project-URL: Homepage, https://github.com/marquesantero/contractforge-core/tree/main/adapters/fabric
6
+ Project-URL: Documentation, https://marquesantero.github.io/contractforge-core/docs/adapters/fabric
7
+ Project-URL: Repository, https://github.com/marquesantero/contractforge-core
8
+ Project-URL: Issues, https://github.com/marquesantero/contractforge-core/issues
9
+ Project-URL: Changelog, https://github.com/marquesantero/contractforge-core/blob/main/adapters/fabric/CHANGELOG.md
10
+ Author: ContractForge contributors
11
+ License: MIT
12
+ Keywords: contractforge,data-contracts,ingestion,lakehouse,microsoft-fabric,onelake
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Database
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: contractforge-core<0.2,>=0.1
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8; extra == 'dev'
27
+ Provides-Extra: runtime
28
+ Description-Content-Type: text/markdown
29
+
30
+ # contractforge-fabric
31
+
32
+ `contractforge-fabric` is the Microsoft Fabric adapter package for
33
+ ContractForge.
34
+
35
+ The first adapter slice is intentionally conservative. It plans contracts
36
+ against a Fabric Lakehouse target, renders review artifacts, publishes a
37
+ machine-readable source-support catalog and documents the runtime boundaries for
38
+ future deployment work.
39
+
40
+ It does not yet call Fabric REST APIs, submit notebooks, create Data Factory
41
+ pipelines or mutate Fabric workspaces.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install contractforge-core contractforge-fabric
47
+ ```
48
+
49
+ ## Use
50
+
51
+ ```python
52
+ from contractforge_fabric import plan_fabric_contract, render_fabric_contract
53
+
54
+ contract = {
55
+ "source": {"type": "parquet", "path": "Files/orders"},
56
+ "target": {"catalog": "workspace", "schema": "bronze", "table": "orders"},
57
+ "mode": "overwrite",
58
+ }
59
+
60
+ planning = plan_fabric_contract(contract)
61
+ artifacts = render_fabric_contract(contract)
62
+ ```
63
+
64
+ CLI:
65
+
66
+ ```bash
67
+ contractforge-fabric plan contracts/orders.ingestion.yaml
68
+ contractforge-fabric render contracts/orders.ingestion.yaml
69
+ contractforge-fabric sources
70
+ ```
71
+
72
+ ## Current scope
73
+
74
+ - Subtarget: `fabric_lakehouse`.
75
+ - Runtime status: render-only.
76
+ - Native concepts: Fabric Workspace, Lakehouse, Warehouse, OneLake, shortcuts,
77
+ notebooks and Data Factory pipelines.
78
+ - Evidence store target: Fabric Lakehouse Delta tables, review-only for now.
79
+
80
+ The adapter returns `REVIEW_REQUIRED` for semantics that need a concrete Fabric
81
+ runtime design before execution can be claimed.
@@ -0,0 +1,52 @@
1
+ # contractforge-fabric
2
+
3
+ `contractforge-fabric` is the Microsoft Fabric adapter package for
4
+ ContractForge.
5
+
6
+ The first adapter slice is intentionally conservative. It plans contracts
7
+ against a Fabric Lakehouse target, renders review artifacts, publishes a
8
+ machine-readable source-support catalog and documents the runtime boundaries for
9
+ future deployment work.
10
+
11
+ It does not yet call Fabric REST APIs, submit notebooks, create Data Factory
12
+ pipelines or mutate Fabric workspaces.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install contractforge-core contractforge-fabric
18
+ ```
19
+
20
+ ## Use
21
+
22
+ ```python
23
+ from contractforge_fabric import plan_fabric_contract, render_fabric_contract
24
+
25
+ contract = {
26
+ "source": {"type": "parquet", "path": "Files/orders"},
27
+ "target": {"catalog": "workspace", "schema": "bronze", "table": "orders"},
28
+ "mode": "overwrite",
29
+ }
30
+
31
+ planning = plan_fabric_contract(contract)
32
+ artifacts = render_fabric_contract(contract)
33
+ ```
34
+
35
+ CLI:
36
+
37
+ ```bash
38
+ contractforge-fabric plan contracts/orders.ingestion.yaml
39
+ contractforge-fabric render contracts/orders.ingestion.yaml
40
+ contractforge-fabric sources
41
+ ```
42
+
43
+ ## Current scope
44
+
45
+ - Subtarget: `fabric_lakehouse`.
46
+ - Runtime status: render-only.
47
+ - Native concepts: Fabric Workspace, Lakehouse, Warehouse, OneLake, shortcuts,
48
+ notebooks and Data Factory pipelines.
49
+ - Evidence store target: Fabric Lakehouse Delta tables, review-only for now.
50
+
51
+ The adapter returns `REVIEW_REQUIRED` for semantics that need a concrete Fabric
52
+ runtime design before execution can be claimed.
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "contractforge-fabric"
7
+ version = "0.1.0"
8
+ description = "Microsoft Fabric adapter for ContractForge Core."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "ContractForge contributors" }]
13
+ keywords = ["contractforge", "microsoft-fabric", "onelake", "lakehouse", "data-contracts", "ingestion"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Database",
24
+ "Topic :: Software Development :: Libraries",
25
+ ]
26
+ dependencies = ["contractforge-core>=0.1,<0.2"]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/marquesantero/contractforge-core/tree/main/adapters/fabric"
30
+ Documentation = "https://marquesantero.github.io/contractforge-core/docs/adapters/fabric"
31
+ Repository = "https://github.com/marquesantero/contractforge-core"
32
+ Issues = "https://github.com/marquesantero/contractforge-core/issues"
33
+ Changelog = "https://github.com/marquesantero/contractforge-core/blob/main/adapters/fabric/CHANGELOG.md"
34
+
35
+ [project.optional-dependencies]
36
+ dev = ["pytest>=8"]
37
+ runtime = []
38
+
39
+ [project.scripts]
40
+ contractforge-fabric = "contractforge_fabric.cli:main"
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["src/contractforge_fabric"]
44
+
45
+ [tool.hatch.build.targets.sdist]
46
+ include = ["/src/contractforge_fabric", "/README.md", "/CHANGELOG.md", "/pyproject.toml"]
@@ -0,0 +1,28 @@
1
+ """Microsoft Fabric adapter for ContractForge."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import PackageNotFoundError, version
6
+
7
+ from contractforge_fabric.adapter import FabricAdapter
8
+ from contractforge_fabric.api import plan_fabric_contract, render_fabric_contract
9
+ from contractforge_fabric.capabilities import FABRIC_SUBTARGET_LAKEHOUSE, fabric_lakehouse_capabilities
10
+ from contractforge_fabric.sources import fabric_source_support, list_fabric_source_support
11
+ from contractforge_fabric.subtargets import adapter_for_subtarget, list_fabric_subtargets
12
+
13
+ try:
14
+ __version__ = version("contractforge-fabric")
15
+ except PackageNotFoundError: # pragma: no cover
16
+ __version__ = "0.0.0"
17
+
18
+ __all__ = [
19
+ "FABRIC_SUBTARGET_LAKEHOUSE",
20
+ "FabricAdapter",
21
+ "adapter_for_subtarget",
22
+ "fabric_lakehouse_capabilities",
23
+ "fabric_source_support",
24
+ "list_fabric_source_support",
25
+ "list_fabric_subtargets",
26
+ "plan_fabric_contract",
27
+ "render_fabric_contract",
28
+ ]
@@ -0,0 +1,63 @@
1
+ """ContractForge adapter implementation for Microsoft Fabric targets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ from contractforge_core.adapters import RenderedArtifacts
9
+ from contractforge_core.capabilities import PlatformCapabilities
10
+ from contractforge_core.planner import ExecutionPlan, PlanningResult, plan_contract
11
+ from contractforge_core.semantic import SemanticContract
12
+ from contractforge_fabric.capabilities import FABRIC_SUBTARGET_LAKEHOUSE, fabric_lakehouse_capabilities
13
+ from contractforge_fabric.diagnostics import fabric_planning_warnings, unsupported_source_blockers
14
+ from contractforge_fabric.environment import FabricEnvironment
15
+ from contractforge_fabric.rendering import render_fabric_review_artifacts
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class FabricAdapter:
20
+ """Fabric Lakehouse adapter for planning and review artifact rendering."""
21
+
22
+ declared_capabilities: PlatformCapabilities
23
+ environment: FabricEnvironment = FabricEnvironment()
24
+ name: str = FABRIC_SUBTARGET_LAKEHOUSE
25
+
26
+ @classmethod
27
+ def lakehouse(cls, environment: dict[str, Any] | None = None) -> "FabricAdapter":
28
+ return cls(
29
+ fabric_lakehouse_capabilities(),
30
+ environment=FabricEnvironment.from_contract(environment),
31
+ )
32
+
33
+ def capabilities(self) -> PlatformCapabilities:
34
+ return self.declared_capabilities
35
+
36
+ def plan(self, contract: SemanticContract) -> PlanningResult:
37
+ source_blockers = unsupported_source_blockers(contract)
38
+ if source_blockers:
39
+ return PlanningResult(status="UNSUPPORTED", plan=None, blockers=source_blockers)
40
+
41
+ result = plan_contract(contract, self.capabilities())
42
+ warnings = result.warnings + fabric_planning_warnings(contract)
43
+ if result.status == "SUPPORTED":
44
+ return PlanningResult(status="SUPPORTED_WITH_WARNINGS", plan=result.plan, warnings=warnings)
45
+ return PlanningResult(
46
+ status=result.status,
47
+ plan=result.plan,
48
+ blockers=result.blockers,
49
+ warnings=warnings,
50
+ )
51
+
52
+ def render(self, plan: ExecutionPlan) -> RenderedArtifacts:
53
+ return render_fabric_review_artifacts(plan=plan, planning=None, environment=self.environment)
54
+
55
+ def render_contract(self, contract: SemanticContract, *, raw_contract: dict[str, Any] | None = None) -> RenderedArtifacts:
56
+ planning = self.plan(contract)
57
+ return render_fabric_review_artifacts(
58
+ plan=planning.plan,
59
+ planning=planning,
60
+ contract=contract,
61
+ raw_contract=raw_contract,
62
+ environment=self.environment,
63
+ )
@@ -0,0 +1,36 @@
1
+ """High-level Fabric adapter API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from contractforge_core.adapters import RenderedArtifacts
8
+ from contractforge_core.contracts import semantic_contract_from_mapping
9
+ from contractforge_core.planner import PlanningResult
10
+ from contractforge_fabric.capabilities import FABRIC_SUBTARGET_LAKEHOUSE
11
+ from contractforge_fabric.subtargets import adapter_for_subtarget
12
+
13
+
14
+ def plan_fabric_contract(
15
+ contract: dict[str, Any],
16
+ *,
17
+ subtarget: str = FABRIC_SUBTARGET_LAKEHOUSE,
18
+ environment: dict[str, Any] | None = None,
19
+ ) -> PlanningResult:
20
+ semantic = semantic_contract_from_mapping(contract)
21
+ adapter = adapter_for_subtarget(subtarget, environment=environment)
22
+ return adapter.plan(semantic)
23
+
24
+
25
+ def render_fabric_contract(
26
+ contract: dict[str, Any],
27
+ *,
28
+ subtarget: str = FABRIC_SUBTARGET_LAKEHOUSE,
29
+ environment: dict[str, Any] | None = None,
30
+ ) -> RenderedArtifacts:
31
+ semantic = semantic_contract_from_mapping(contract)
32
+ adapter = adapter_for_subtarget(subtarget, environment=environment)
33
+ return adapter.render_contract(semantic, raw_contract=contract)
34
+
35
+
36
+ __all__ = ["plan_fabric_contract", "render_fabric_contract"]
@@ -0,0 +1,5 @@
1
+ """Fabric adapter capability declarations."""
2
+
3
+ from contractforge_fabric.capabilities.lakehouse import FABRIC_SUBTARGET_LAKEHOUSE, fabric_lakehouse_capabilities
4
+
5
+ __all__ = ["FABRIC_SUBTARGET_LAKEHOUSE", "fabric_lakehouse_capabilities"]
@@ -0,0 +1,51 @@
1
+ """Capability declaration for the Microsoft Fabric Lakehouse adapter target."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contractforge_core.capabilities import PlatformCapabilities
6
+
7
+ FABRIC_SUBTARGET_LAKEHOUSE = "fabric_lakehouse"
8
+
9
+
10
+ def fabric_lakehouse_capabilities() -> PlatformCapabilities:
11
+ """Return conservative capabilities for the initial Fabric target.
12
+
13
+ Fabric has native Lakehouse, Warehouse, OneLake, notebook and Data Factory
14
+ surfaces. This first adapter slice is render-only, so execution-sensitive
15
+ semantics remain review-required until runtime evidence exists.
16
+ """
17
+
18
+ return PlatformCapabilities(
19
+ platform=FABRIC_SUBTARGET_LAKEHOUSE,
20
+ supports_append=True,
21
+ supports_overwrite=True,
22
+ supports_merge=True,
23
+ supports_hash_diff=False,
24
+ supports_scd2=False,
25
+ supports_snapshot_soft_delete=False,
26
+ supports_schema_evolution=True,
27
+ supports_row_filters=False,
28
+ supports_column_masks=False,
29
+ supports_available_now_streaming=False,
30
+ supports_required_columns_quality=True,
31
+ supports_unique_key_quality=True,
32
+ supports_max_null_ratio_quality=True,
33
+ supports_expression_quality=True,
34
+ supports_shape=True,
35
+ supports_transform=True,
36
+ evidence_stores=("fabric_lakehouse_delta_tables",),
37
+ review_required_semantics=(
38
+ "hash_diff_upsert",
39
+ "scd2_historical",
40
+ "snapshot_soft_delete",
41
+ "available_now_streaming",
42
+ "row_filters",
43
+ "column_masks",
44
+ "source.jdbc",
45
+ "source.rest_api",
46
+ "source.http_file",
47
+ "source.kafka_bounded",
48
+ "source.eventhubs_bounded",
49
+ "source.native_passthrough",
50
+ ),
51
+ )
@@ -0,0 +1,80 @@
1
+ """Minimal CLI entry point for the Fabric adapter package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import yaml
11
+
12
+ from contractforge_fabric.api import plan_fabric_contract, render_fabric_contract
13
+ from contractforge_fabric.sources import list_fabric_source_support
14
+
15
+
16
+ def main(argv: list[str] | None = None) -> int:
17
+ parser = argparse.ArgumentParser(prog="contractforge-fabric")
18
+ subparsers = parser.add_subparsers(dest="command", required=True)
19
+
20
+ plan = subparsers.add_parser("plan", help="Plan a contract against the Fabric Lakehouse target.")
21
+ plan.add_argument("contract", type=Path)
22
+ plan.add_argument("--environment", type=Path)
23
+
24
+ render = subparsers.add_parser("render", help="Render Fabric planning review artifacts.")
25
+ render.add_argument("contract", type=Path)
26
+ render.add_argument("--environment", type=Path)
27
+
28
+ subparsers.add_parser("sources", help="Print Fabric source support metadata.")
29
+
30
+ args = parser.parse_args(argv)
31
+ if args.command == "plan":
32
+ return _handle_plan(args.contract, args.environment)
33
+ if args.command == "render":
34
+ return _handle_render(args.contract, args.environment)
35
+ if args.command == "sources":
36
+ print(json.dumps(list(list_fabric_source_support()), indent=2, sort_keys=True))
37
+ return 0
38
+ return 2
39
+
40
+
41
+ def _handle_plan(contract_path: Path, environment_path: Path | None) -> int:
42
+ result = plan_fabric_contract(_load_yaml(contract_path), environment=_load_optional_yaml(environment_path))
43
+ print(json.dumps(_planning_payload(result), indent=2, sort_keys=True))
44
+ return 0
45
+
46
+
47
+ def _handle_render(contract_path: Path, environment_path: Path | None) -> int:
48
+ artifacts = render_fabric_contract(_load_yaml(contract_path), environment=_load_optional_yaml(environment_path))
49
+ print(json.dumps(artifacts.artifacts, indent=2, sort_keys=True))
50
+ return 0
51
+
52
+
53
+ def _load_optional_yaml(path: Path | None) -> dict[str, Any] | None:
54
+ return None if path is None else _load_yaml(path)
55
+
56
+
57
+ def _load_yaml(path: Path) -> dict[str, Any]:
58
+ payload = yaml.safe_load(path.read_text(encoding="utf-8"))
59
+ if not isinstance(payload, dict):
60
+ raise ValueError(f"Expected YAML object in {path}")
61
+ return payload
62
+
63
+
64
+ def _planning_payload(result) -> dict[str, object]:
65
+ return {
66
+ "status": result.status,
67
+ "blockers": [{"code": item.code, "message": item.message} for item in result.blockers],
68
+ "warnings": [{"code": item.code, "message": item.message} for item in result.warnings],
69
+ "plan": None
70
+ if result.plan is None
71
+ else {
72
+ "platform": result.plan.platform,
73
+ "evidence_required": result.plan.evidence_required,
74
+ "steps": [{"name": step.name, "intent": step.intent} for step in result.plan.steps],
75
+ },
76
+ }
77
+
78
+
79
+ if __name__ == "__main__": # pragma: no cover
80
+ raise SystemExit(main())
@@ -0,0 +1,38 @@
1
+ """Fabric planning diagnostics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contractforge_core.planner import PlanningBlocker, PlanningWarning
6
+ from contractforge_core.semantic import SemanticContract
7
+ from contractforge_fabric.sources import classify_fabric_source
8
+
9
+
10
+ def unsupported_source_blockers(contract: SemanticContract) -> tuple[PlanningBlocker, ...]:
11
+ classification = classify_fabric_source(contract.source.raw)
12
+ if classification.status != "UNSUPPORTED":
13
+ return ()
14
+ return (
15
+ PlanningBlocker(
16
+ code="FABRIC_UNSUPPORTED_SOURCE",
17
+ message=f"Fabric Lakehouse adapter has no declared source mapping for `{classification.source_type}`.",
18
+ ),
19
+ )
20
+
21
+
22
+ def fabric_planning_warnings(contract: SemanticContract) -> tuple[PlanningWarning, ...]:
23
+ warnings: list[PlanningWarning] = []
24
+ classification = classify_fabric_source(contract.source.raw)
25
+ if classification.status in {"SUPPORTED_WITH_WARNINGS", "REVIEW_REQUIRED"}:
26
+ warnings.append(
27
+ PlanningWarning(
28
+ code="FABRIC_SOURCE_REVIEW",
29
+ message=f"`{classification.source_type}` maps to {classification.native_mapping or 'Fabric review'}: {classification.note}",
30
+ )
31
+ )
32
+ warnings.append(
33
+ PlanningWarning(
34
+ code="FABRIC_RENDER_ONLY",
35
+ message="The Fabric adapter currently renders review artifacts only; runtime execution is not yet implemented.",
36
+ )
37
+ )
38
+ return tuple(warnings)
@@ -0,0 +1,48 @@
1
+ """Fabric adapter environment binding."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class FabricEnvironment:
11
+ """Adapter-owned interpretation of the core environment contract."""
12
+
13
+ workspace_id: str | None = None
14
+ workspace_name: str | None = None
15
+ lakehouse_id: str | None = None
16
+ lakehouse_name: str | None = None
17
+ warehouse_id: str | None = None
18
+ warehouse_name: str | None = None
19
+ evidence_lakehouse: str | None = None
20
+ evidence_schema: str | None = None
21
+ artifact_uri: str | None = None
22
+ runtime_kind: str | None = None
23
+ notebook_id: str | None = None
24
+ pipeline_id: str | None = None
25
+
26
+ @classmethod
27
+ def from_contract(cls, environment: dict[str, Any] | None = None) -> "FabricEnvironment":
28
+ if not environment:
29
+ return cls()
30
+ evidence = environment.get("evidence") if isinstance(environment.get("evidence"), dict) else {}
31
+ artifacts = environment.get("artifacts") if isinstance(environment.get("artifacts"), dict) else {}
32
+ runtime = environment.get("runtime") if isinstance(environment.get("runtime"), dict) else {}
33
+ parameters = environment.get("parameters") if isinstance(environment.get("parameters"), dict) else {}
34
+ fabric = parameters.get("fabric") if isinstance(parameters.get("fabric"), dict) else {}
35
+ return cls(
36
+ workspace_id=fabric.get("workspace_id"),
37
+ workspace_name=fabric.get("workspace_name"),
38
+ lakehouse_id=fabric.get("lakehouse_id"),
39
+ lakehouse_name=fabric.get("lakehouse_name"),
40
+ warehouse_id=fabric.get("warehouse_id"),
41
+ warehouse_name=fabric.get("warehouse_name"),
42
+ evidence_lakehouse=evidence.get("lakehouse") or evidence.get("database"),
43
+ evidence_schema=evidence.get("schema"),
44
+ artifact_uri=artifacts.get("uri"),
45
+ runtime_kind=runtime.get("kind"),
46
+ notebook_id=fabric.get("notebook_id"),
47
+ pipeline_id=fabric.get("pipeline_id"),
48
+ )
@@ -0,0 +1,5 @@
1
+ """Fabric artifact rendering."""
2
+
3
+ from contractforge_fabric.rendering.review import render_fabric_review_artifacts
4
+
5
+ __all__ = ["render_fabric_review_artifacts"]
@@ -0,0 +1,222 @@
1
+ """Render Fabric planning review artifacts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ from contractforge_core.adapters import RenderedArtifacts
9
+ from contractforge_core.planner import ExecutionPlan, PlanningResult
10
+ from contractforge_core.semantic import SemanticContract
11
+ from contractforge_fabric.capabilities import FABRIC_SUBTARGET_LAKEHOUSE, fabric_lakehouse_capabilities
12
+ from contractforge_fabric.environment import FabricEnvironment
13
+ from contractforge_fabric.sources import list_fabric_source_support
14
+
15
+ _PUBLIC_REVIEW_REQUIRED_SEMANTICS = {
16
+ "scd2_historical": "historical",
17
+ "snapshot_soft_delete": "snapshot_reconcile_soft_delete",
18
+ }
19
+
20
+
21
+ def render_fabric_review_artifacts(
22
+ *,
23
+ plan: ExecutionPlan | None,
24
+ planning: PlanningResult | None,
25
+ contract: SemanticContract | None = None,
26
+ raw_contract: dict[str, Any] | None = None,
27
+ environment: FabricEnvironment | None = None,
28
+ ) -> RenderedArtifacts:
29
+ env = environment or FabricEnvironment()
30
+ prefix = _artifact_prefix(contract, plan)
31
+ artifacts = {
32
+ f"{prefix}.fabric.review.md": _planning_markdown(
33
+ plan=plan,
34
+ planning=planning,
35
+ contract=contract,
36
+ environment=env,
37
+ ),
38
+ f"{prefix}.fabric.capabilities.json": _capabilities_json(plan=plan, planning=planning, environment=env),
39
+ f"{prefix}.fabric.source_support.json": json.dumps(list(list_fabric_source_support()), indent=2, sort_keys=True),
40
+ f"{prefix}.fabric.runtime.todo.md": _runtime_todo(contract=contract, environment=env),
41
+ }
42
+ if raw_contract is not None:
43
+ artifacts[f"{prefix}.fabric.contract.json"] = json.dumps(raw_contract, indent=2, sort_keys=True)
44
+ manifest_name = f"{prefix}.fabric.manifest.json"
45
+ artifacts[manifest_name] = _manifest_json(
46
+ plan=plan,
47
+ planning=planning,
48
+ artifacts=artifacts,
49
+ manifest_name=manifest_name,
50
+ )
51
+ return RenderedArtifacts(artifacts=artifacts)
52
+
53
+
54
+ def _artifact_prefix(contract: SemanticContract | None, plan: ExecutionPlan | None) -> str:
55
+ if contract is None:
56
+ return (plan.platform if plan else FABRIC_SUBTARGET_LAKEHOUSE).replace(".", "_")
57
+ namespace = (contract.target.namespace or "default").replace(".", "_")
58
+ return f"{namespace}_{contract.target.name}"
59
+
60
+
61
+ def _capabilities_json(
62
+ *,
63
+ plan: ExecutionPlan | None,
64
+ planning: PlanningResult | None,
65
+ environment: FabricEnvironment,
66
+ ) -> str:
67
+ capabilities = fabric_lakehouse_capabilities()
68
+ payload = {
69
+ "adapter": "fabric",
70
+ "subtarget": plan.platform if plan else FABRIC_SUBTARGET_LAKEHOUSE,
71
+ "planning_status": planning.status if planning else None,
72
+ "supports": {
73
+ "append": capabilities.supports_append,
74
+ "overwrite": capabilities.supports_overwrite,
75
+ "upsert": capabilities.supports_merge,
76
+ "hash_diff_upsert": capabilities.supports_hash_diff,
77
+ "historical": capabilities.supports_scd2,
78
+ "snapshot_reconcile_soft_delete": capabilities.supports_snapshot_soft_delete,
79
+ "schema_evolution": capabilities.supports_schema_evolution,
80
+ "row_filters": capabilities.supports_row_filters,
81
+ "column_masks": capabilities.supports_column_masks,
82
+ "available_now_streaming": capabilities.supports_available_now_streaming,
83
+ "shape": capabilities.supports_shape,
84
+ "transform": capabilities.supports_transform,
85
+ "expression_quality": capabilities.supports_expression_quality,
86
+ },
87
+ "evidence": {
88
+ "stores": list(capabilities.evidence_stores),
89
+ "lakehouse": environment.evidence_lakehouse,
90
+ "schema": environment.evidence_schema,
91
+ },
92
+ "runtime": {
93
+ "status": "render_only",
94
+ "workspace_id": environment.workspace_id,
95
+ "workspace_name": environment.workspace_name,
96
+ "lakehouse_id": environment.lakehouse_id,
97
+ "lakehouse_name": environment.lakehouse_name,
98
+ "warehouse_id": environment.warehouse_id,
99
+ "warehouse_name": environment.warehouse_name,
100
+ "artifact_uri": environment.artifact_uri,
101
+ "notebook_id": environment.notebook_id,
102
+ "pipeline_id": environment.pipeline_id,
103
+ },
104
+ "review_required_semantics": [
105
+ _PUBLIC_REVIEW_REQUIRED_SEMANTICS.get(item, item) for item in capabilities.review_required_semantics
106
+ ],
107
+ }
108
+ return json.dumps(payload, indent=2, sort_keys=True)
109
+
110
+
111
+ def _planning_markdown(
112
+ *,
113
+ plan: ExecutionPlan | None,
114
+ planning: PlanningResult | None,
115
+ contract: SemanticContract | None,
116
+ environment: FabricEnvironment,
117
+ ) -> str:
118
+ lines = [
119
+ "# Fabric Lakehouse Planning Review",
120
+ "",
121
+ "This artifact summarizes how a ContractForge contract maps to the Microsoft Fabric Lakehouse surface.",
122
+ "The current adapter is render-only: it does not call Fabric REST APIs, submit notebooks or update Data Factory pipelines.",
123
+ "",
124
+ "## Fabric Binding",
125
+ "",
126
+ f"- Workspace: `{environment.workspace_name or environment.workspace_id or 'UNSPECIFIED'}`",
127
+ f"- Lakehouse: `{environment.lakehouse_name or environment.lakehouse_id or 'UNSPECIFIED'}`",
128
+ f"- Warehouse: `{environment.warehouse_name or environment.warehouse_id or 'UNSPECIFIED'}`",
129
+ f"- Evidence: `{_evidence_name(environment)}`",
130
+ "",
131
+ ]
132
+ if contract:
133
+ lines.extend(
134
+ [
135
+ "## Contract",
136
+ "",
137
+ f"- Source: `{contract.source.kind}`",
138
+ f"- Target: `{contract.target.namespace or 'default'}.{contract.target.name}`",
139
+ f"- Write mode: `{contract.write.mode}`",
140
+ "",
141
+ ]
142
+ )
143
+ if planning:
144
+ lines.extend(["## Planning Result", "", f"- Status: `{planning.status}`", ""])
145
+ if planning.blockers:
146
+ lines.extend(["### Blockers", ""])
147
+ lines.extend(f"- `{blocker.code}`: {blocker.message}" for blocker in planning.blockers)
148
+ lines.append("")
149
+ if planning.warnings:
150
+ lines.extend(["### Warnings", ""])
151
+ lines.extend(f"- `{warning.code}`: {warning.message}" for warning in planning.warnings)
152
+ lines.append("")
153
+ if plan:
154
+ lines.extend(["## Abstract Plan", "", "| Step | Intent |", "| --- | --- |"])
155
+ lines.extend(f"| `{step.name}` | {step.intent} |" for step in plan.steps)
156
+ lines.append("")
157
+ lines.extend(
158
+ [
159
+ "## Expected Fabric Mapping",
160
+ "",
161
+ "| ContractForge intent | Fabric target concept |",
162
+ "| --- | --- |",
163
+ "| Source read | OneLake Files/Tables, Lakehouse/Warehouse SQL endpoint, shortcut, Data Factory activity or notebook step |",
164
+ "| Write target | Lakehouse Delta table or Warehouse table, depending on runtime design |",
165
+ "| Transform | Notebook/Spark or SQL endpoint step |",
166
+ "| Evidence | Lakehouse Delta evidence tables |",
167
+ "| Deployment | Future Fabric REST/CLI/Data Factory pipeline integration |",
168
+ "",
169
+ ]
170
+ )
171
+ return "\n".join(lines)
172
+
173
+
174
+ def _runtime_todo(*, contract: SemanticContract | None, environment: FabricEnvironment) -> str:
175
+ target = f"{contract.target.namespace or 'default'}.{contract.target.name}" if contract else "UNKNOWN"
176
+ return "\n".join(
177
+ [
178
+ "# Fabric Runtime TODO",
179
+ "",
180
+ "This adapter version is render-only. To mature it, implement and validate:",
181
+ "",
182
+ "- Fabric workspace and item resolution.",
183
+ "- OneLake/Lakehouse table name resolution.",
184
+ "- Notebook or Data Factory pipeline artifact generation.",
185
+ "- Runtime submission through Fabric REST API or Fabric CLI.",
186
+ "- Delta/Lakehouse evidence table DDL and writes.",
187
+ "- End-to-end bronze-to-gold contract execution without workaround code.",
188
+ "",
189
+ f"Target contract: `{target}`",
190
+ f"Configured artifact URI: `{environment.artifact_uri or 'UNSPECIFIED'}`",
191
+ "",
192
+ ]
193
+ )
194
+
195
+
196
+ def _manifest_json(
197
+ *,
198
+ plan: ExecutionPlan | None,
199
+ planning: PlanningResult | None,
200
+ artifacts: dict[str, str],
201
+ manifest_name: str,
202
+ ) -> str:
203
+ payload = {
204
+ "adapter": "fabric",
205
+ "subtarget": plan.platform if plan else FABRIC_SUBTARGET_LAKEHOUSE,
206
+ "planning_status": planning.status if planning else None,
207
+ "artifact_summary": {
208
+ "mode": "review_bundle",
209
+ "execution_model": "render_only",
210
+ "deployable": False,
211
+ "count": len(artifacts) + 1,
212
+ "bytes": sum(len(body.encode("utf-8")) for body in artifacts.values()),
213
+ },
214
+ "artifacts": sorted(tuple(artifacts) + (manifest_name,)),
215
+ }
216
+ return json.dumps(payload, indent=2, sort_keys=True)
217
+
218
+
219
+ def _evidence_name(environment: FabricEnvironment) -> str:
220
+ lakehouse = environment.evidence_lakehouse or environment.lakehouse_name or "CONTRACTFORGE_EVIDENCE_LH"
221
+ schema = environment.evidence_schema or "contractforge"
222
+ return f"{lakehouse}.{schema}"
@@ -0,0 +1,16 @@
1
+ """Fabric source mapping helpers."""
2
+
3
+ from contractforge_fabric.sources.classification import (
4
+ FabricSourceClassification,
5
+ classify_fabric_source,
6
+ is_fabric_source_renderable,
7
+ )
8
+ from contractforge_fabric.sources.support import fabric_source_support, list_fabric_source_support
9
+
10
+ __all__ = [
11
+ "FabricSourceClassification",
12
+ "classify_fabric_source",
13
+ "fabric_source_support",
14
+ "is_fabric_source_renderable",
15
+ "list_fabric_source_support",
16
+ ]
@@ -0,0 +1,182 @@
1
+ """Fabric source connector classification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Callable
7
+
8
+ from contractforge_core.connectors import (
9
+ JDBC_CONNECTORS,
10
+ is_available_now_stream_source,
11
+ is_bounded_stream_source,
12
+ is_catalog_source,
13
+ is_delta_share_source,
14
+ is_file_source,
15
+ is_http_file_source,
16
+ is_native_passthrough_source,
17
+ is_rest_api_connector,
18
+ )
19
+
20
+ SUPPORTED = "SUPPORTED"
21
+ SUPPORTED_WITH_WARNINGS = "SUPPORTED_WITH_WARNINGS"
22
+ REVIEW_REQUIRED = "REVIEW_REQUIRED"
23
+ UNSUPPORTED = "UNSUPPORTED"
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class FabricSourceClassification:
28
+ source_type: str
29
+ status: str
30
+ native_mapping: str | None
31
+ note: str
32
+ renderable: bool
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class _SourceRule:
37
+ matches: Callable[[dict[str, Any]], bool]
38
+ classify: Callable[[dict[str, Any], str], FabricSourceClassification]
39
+
40
+
41
+ def classify_fabric_source(source: dict[str, Any] | str) -> FabricSourceClassification:
42
+ payload = {"type": source} if isinstance(source, str) else dict(source)
43
+ source_type = str(payload.get("connector") or payload.get("type") or "").strip().lower()
44
+ for rule in _CLASSIFICATION_RULES:
45
+ if rule.matches(payload):
46
+ return rule.classify(payload, source_type)
47
+ return FabricSourceClassification(
48
+ source_type=source_type,
49
+ status=UNSUPPORTED,
50
+ native_mapping=None,
51
+ note="No Fabric source mapping is declared for this connector.",
52
+ renderable=False,
53
+ )
54
+
55
+
56
+ def is_fabric_source_renderable(source: dict[str, Any]) -> bool:
57
+ return classify_fabric_source(source).renderable
58
+
59
+
60
+ def _is_jdbc_source(source: dict[str, Any]) -> bool:
61
+ connector = source.get("connector") or source.get("type")
62
+ return connector in JDBC_CONNECTORS or source.get("type") == "jdbc"
63
+
64
+
65
+ def _supported(
66
+ source_type: str,
67
+ *,
68
+ native_mapping: str,
69
+ note: str,
70
+ status: str = SUPPORTED,
71
+ renderable: bool = True,
72
+ ) -> FabricSourceClassification:
73
+ return FabricSourceClassification(
74
+ source_type=source_type,
75
+ status=status,
76
+ native_mapping=native_mapping,
77
+ note=note,
78
+ renderable=renderable,
79
+ )
80
+
81
+
82
+ def _catalog_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
83
+ return _supported(
84
+ source_type,
85
+ native_mapping="Fabric Lakehouse/Warehouse table or SQL endpoint",
86
+ note="Logical refs are resolved to Fabric item/table names by the adapter.",
87
+ )
88
+
89
+
90
+ def _file_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
91
+ return _supported(
92
+ source_type,
93
+ native_mapping="OneLake Files/Tables path read",
94
+ note="Batch file mapping for Lakehouse runtime artifacts.",
95
+ )
96
+
97
+
98
+ def _jdbc_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
99
+ return _supported(
100
+ source_type,
101
+ status=REVIEW_REQUIRED,
102
+ native_mapping="Fabric Data Factory pipeline or notebook JDBC read",
103
+ note="Driver, gateway, credential and private-network choices must be reviewed.",
104
+ renderable=False,
105
+ )
106
+
107
+
108
+ def _http_file_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
109
+ return _supported(
110
+ source_type,
111
+ status=REVIEW_REQUIRED,
112
+ native_mapping="Fabric notebook or Data Factory web activity",
113
+ note="Bounded fetch is portable, but Fabric runtime ownership is not implemented yet.",
114
+ renderable=False,
115
+ )
116
+
117
+
118
+ def _rest_api_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
119
+ return _supported(
120
+ source_type,
121
+ status=REVIEW_REQUIRED,
122
+ native_mapping="Fabric Data Factory REST copy or notebook fetch",
123
+ note="Generic bounded REST runtime needs explicit Fabric execution design.",
124
+ renderable=False,
125
+ )
126
+
127
+
128
+ def _incremental_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
129
+ return _supported(
130
+ source_type,
131
+ status=REVIEW_REQUIRED,
132
+ native_mapping="Fabric Data Factory incremental pipeline or notebook checkpoint",
133
+ note="File-discovery and checkpoint semantics require runtime evidence.",
134
+ renderable=False,
135
+ )
136
+
137
+
138
+ def _bounded_stream_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
139
+ return _supported(
140
+ source_type,
141
+ status=REVIEW_REQUIRED,
142
+ native_mapping="Fabric Real-Time Intelligence or notebook bounded replay",
143
+ note="Continuous and bounded replay semantics are not implemented in v0.",
144
+ renderable=False,
145
+ )
146
+
147
+
148
+ def _delta_share_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
149
+ return _supported(
150
+ source_type,
151
+ status=SUPPORTED_WITH_WARNINGS,
152
+ native_mapping="Delta Sharing client materialized into OneLake",
153
+ note="Requires runtime dependency and credential review.",
154
+ )
155
+
156
+
157
+ def _native_passthrough_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
158
+ return _supported(
159
+ source_type,
160
+ status=REVIEW_REQUIRED,
161
+ native_mapping="Fabric native connector, shortcut or Data Factory activity",
162
+ note="Native connector handoff must be reviewed per source system.",
163
+ renderable=False,
164
+ )
165
+
166
+
167
+ def _is_incremental_files(source: dict[str, Any]) -> bool:
168
+ return str(source.get("type") or "").strip().lower() == "incremental_files"
169
+
170
+
171
+ _CLASSIFICATION_RULES = (
172
+ _SourceRule(_is_incremental_files, _incremental_classification),
173
+ _SourceRule(_is_jdbc_source, _jdbc_classification),
174
+ _SourceRule(is_catalog_source, _catalog_classification),
175
+ _SourceRule(is_http_file_source, _http_file_classification),
176
+ _SourceRule(is_rest_api_connector, _rest_api_classification),
177
+ _SourceRule(is_available_now_stream_source, _bounded_stream_classification),
178
+ _SourceRule(is_bounded_stream_source, _bounded_stream_classification),
179
+ _SourceRule(is_delta_share_source, _delta_share_classification),
180
+ _SourceRule(is_file_source, _file_classification),
181
+ _SourceRule(is_native_passthrough_source, _native_passthrough_classification),
182
+ )
@@ -0,0 +1,33 @@
1
+ """Fabric source connector support declarations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from contractforge_core.connectors.registry import CONNECTOR_CATALOG
8
+ from contractforge_fabric.sources.classification import FabricSourceClassification, classify_fabric_source
9
+
10
+ _NON_RUNTIME_SOURCE_TYPES = frozenset({"connection"})
11
+
12
+
13
+ def fabric_source_support(source: dict[str, Any] | str) -> dict[str, Any]:
14
+ """Return Fabric Lakehouse support metadata for a source connector."""
15
+
16
+ return _entry(classify_fabric_source(source))
17
+
18
+
19
+ def list_fabric_source_support() -> tuple[dict[str, Any], ...]:
20
+ sources = tuple(name for name in CONNECTOR_CATALOG if name not in _NON_RUNTIME_SOURCE_TYPES)
21
+ return tuple(fabric_source_support(source) for source in sources)
22
+
23
+
24
+ def _entry(classification: FabricSourceClassification) -> dict[str, Any]:
25
+ entry: dict[str, Any] = {
26
+ "adapter": "fabric",
27
+ "source_type": classification.source_type,
28
+ "status": classification.status,
29
+ "note": classification.note,
30
+ }
31
+ if classification.native_mapping:
32
+ entry["native_mapping"] = classification.native_mapping
33
+ return entry
@@ -0,0 +1,27 @@
1
+ """Fabric adapter subtarget registry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Callable
6
+
7
+ from contractforge_fabric.adapter import FabricAdapter
8
+ from contractforge_fabric.capabilities import FABRIC_SUBTARGET_LAKEHOUSE
9
+
10
+ _ADAPTER_FACTORIES: dict[str, Callable[..., FabricAdapter]] = {
11
+ FABRIC_SUBTARGET_LAKEHOUSE: FabricAdapter.lakehouse,
12
+ }
13
+
14
+
15
+ def adapter_for_subtarget(subtarget: str, *, environment: dict[str, Any] | None = None) -> FabricAdapter:
16
+ try:
17
+ return _ADAPTER_FACTORIES[subtarget](environment=environment)
18
+ except KeyError as exc:
19
+ raise ValueError(f"Unsupported Fabric adapter subtarget: {subtarget}") from exc
20
+
21
+
22
+ def validate_fabric_subtarget(subtarget: str) -> None:
23
+ adapter_for_subtarget(subtarget)
24
+
25
+
26
+ def list_fabric_subtargets() -> tuple[str, ...]:
27
+ return tuple(_ADAPTER_FACTORIES)