cfgit 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.
cfg/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ # Copyright 2026 Mohammad Ausaf. Licensed under the Apache License, Version 2.0.
2
+ """cfg — git-shaped version control for database-resident config documents.
3
+
4
+ See docs/SPEC.md for the specification. Architecture (SPEC §1):
5
+ cfg.core — engine; depends ONLY on StorageAdapter + ApprovalProvider.
6
+ cfg.adapters — StorageAdapter implementations (the DB seam).
7
+ cfg.approval — ApprovalProvider implementations (out-of-band human approval).
8
+ cfg.cli — porcelain + plumbing interfaces.
9
+ cfg.mcp — agent surface (uniform result envelope).
10
+ The optional LLM impact layer lives OUT of this package, in plugins/cfg_impact.
11
+ """
12
+
13
+ __version__ = "0.0.0"
@@ -0,0 +1,25 @@
1
+ # Copyright 2026 Mohammad Ausaf. Licensed under the Apache License, Version 2.0.
2
+ """StorageAdapter implementations (the DB seam, SPEC §2). Mongo is first."""
3
+ from cfg.adapters.base import (
4
+ AmbiguousConfig,
5
+ ApplyResult,
6
+ AtomicityReport,
7
+ HistoryEnvMismatch,
8
+ NoSuchConfig,
9
+ ReconcileReport,
10
+ StaleHead,
11
+ StaleLive,
12
+ StorageAdapter,
13
+ )
14
+
15
+ __all__ = [
16
+ "StorageAdapter",
17
+ "ApplyResult",
18
+ "ReconcileReport",
19
+ "AtomicityReport",
20
+ "StaleHead",
21
+ "StaleLive",
22
+ "AmbiguousConfig",
23
+ "HistoryEnvMismatch",
24
+ "NoSuchConfig",
25
+ ]
cfg/adapters/base.py ADDED
@@ -0,0 +1,127 @@
1
+ # Copyright 2026 Mohammad Ausaf. Licensed under the Apache License, Version 2.0.
2
+ """StorageAdapter: the DB seam (SPEC §2).
3
+
4
+ The single contract every backend implements. The core engine talks ONLY to this
5
+ Protocol; no DB driver is imported in cfg.core (enforced by tests/test_core_purity).
6
+
7
+ The v1 surface is collection-aware: cfgit versions opaque records in live
8
+ collections, not only "configs." Mongo is the first concrete adapter
9
+ (cfg.adapters.mongo).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from datetime import datetime
15
+ from typing import Protocol, runtime_checkable
16
+
17
+
18
+ # --- errors the engine branches on (mapped to CLI exit codes / MCP envelope) ---
19
+ class StaleHead(Exception):
20
+ """A concurrent commit moved HEAD since we read it. Exit 2."""
21
+
22
+
23
+ class StaleLive(Exception):
24
+ """A raw bypass moved the runtime record since we read it. Exit 2."""
25
+
26
+
27
+ class AmbiguousConfig(Exception):
28
+ """More than one live record matched a collection id. Exit 6."""
29
+
30
+
31
+ class HistoryEnvMismatch(AmbiguousConfig):
32
+ """History for this record exists under another configured env name. Exit 6."""
33
+
34
+
35
+ class NoSuchConfig(Exception):
36
+ """No live record matched where one was required. Exit 5."""
37
+
38
+
39
+ class AtomicityUnavailable(Exception):
40
+ """The backend cannot make the requested mutation atomically. Exit 3."""
41
+
42
+
43
+ @dataclass
44
+ class ApplyResult:
45
+ collection: str
46
+ record_id: str
47
+ seq: int
48
+ oid: str
49
+ head_oid: str
50
+
51
+
52
+ @dataclass
53
+ class ReconcileReport:
54
+ rolled_forward: list[str]
55
+ rolled_back: list[str]
56
+
57
+
58
+ @dataclass
59
+ class AtomicityReport:
60
+ """[SPEC V3-1] Whether runtime+history+heads can share one transaction."""
61
+ atomic: bool
62
+ runtime_cluster: str
63
+ history_cluster: str
64
+ reason: str
65
+
66
+
67
+ def history_env_mismatch_message(
68
+ *,
69
+ collection: str,
70
+ record_id: str,
71
+ current_env: str,
72
+ other_envs: list[str],
73
+ ) -> str:
74
+ env_list = ", ".join(other_envs)
75
+ return (
76
+ f"no history found for {collection}:{record_id} under env={current_env!r}, "
77
+ f"but history/head rows exist under env(s): {env_list}. "
78
+ "Run with the env name that originally wrote this history, or update .cfg.toml so "
79
+ "this database is always addressed with one stable env name."
80
+ )
81
+
82
+
83
+ @runtime_checkable
84
+ class StorageAdapter(Protocol):
85
+ # runtime store
86
+ def get_record(self, collection: str, record_id: str) -> dict | None: ...
87
+ def put_record(self, collection: str, record_id: str, doc: dict) -> None: ...
88
+ def seed_record(self, collection: str, record_id: str, doc: dict) -> None: ...
89
+ def list_record_ids(self, collection: str) -> list[str]: ...
90
+
91
+ # history reads
92
+ def get_head(self, collection: str, record_id: str) -> dict | None: ...
93
+ def query_history(self, *, collection: str | None = None, record_id: str | None = None,
94
+ ref: str | None = None,
95
+ as_of_recorded: datetime | None = None, as_of_valid: datetime | None = None,
96
+ tag: str | None = None, git_sha: str | None = None,
97
+ limit: int | None = None, order: str = "desc",
98
+ with_doc: bool = False) -> list[dict]: ...
99
+ def list_tags(self) -> list[dict]: ...
100
+
101
+ # optional typed sidecar refs (branches, branch commits, PRs)
102
+ def put_ref(self, doc: dict) -> None: ...
103
+ def get_ref(self, ref_type: str, ref_id: str) -> dict | None: ...
104
+ def list_refs(self, ref_type: str, **filters) -> list[dict]: ...
105
+ def delete_ref(self, ref_type: str, ref_id: str) -> None: ...
106
+
107
+ # the one atomic mutation
108
+ def apply(self, *, collection: str, record_id: str, new_doc: dict | None, entry: dict,
109
+ expected_head_oid: str | None, expected_live_oid: str | None = None,
110
+ make_head: bool = True, seed_missing: bool = False) -> ApplyResult: ...
111
+
112
+ # labels
113
+ def add_tag(self, *, collection: str, record_id: str, seq: int, tag: str) -> None: ...
114
+ def remove_tag(self, *, collection: str, record_id: str, seq: int, tag: str) -> None: ...
115
+
116
+ # crash recovery / non-atomic fallback
117
+ def list_pending(self) -> list[dict]: ...
118
+ def reconcile(self) -> ReconcileReport: ...
119
+
120
+ # meta
121
+ def ensure_schema(self) -> None: ...
122
+ def check_runtime_invariant(self, collection: str | None = None) -> list[str]: ...
123
+ def check_atomicity_scope(self) -> AtomicityReport: ...
124
+ def backend_name(self) -> str: ...
125
+ def supports_transactions(self) -> bool: ...
126
+ def authenticated_principal(self) -> str | None: ...
127
+ def now(self) -> datetime: ...