Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions plugins/cfg_impact/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[project]
name = "cfgit-impact"
version = "0.1.2"
version = "0.1.3"
description = "Optional cfgit plugin for deterministic system-impact summaries and opt-in LLM narration of database record diffs"
readme = "README.md"
requires-python = ">=3.11"
license = "Apache-2.0"
authors = [{ name = "Mohammad Ausaf" }]
dependencies = [
"cfgit>=0.1.2,<0.2.0",
"cfgit>=0.1.3,<0.2.0",
"httpx>=0.27",
]

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "cfgit"
version = "0.1.2"
version = "0.1.3"
description = "Git-style history, diff, drift detection, and rollback for live database records without migrating or owning your datastore"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
8 changes: 1 addition & 7 deletions src/cfg/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,13 +308,7 @@ def _resolve_uri(raw: str, *, env_name: str) -> str:
if raw.startswith("env:"):
key = raw[4:]
value = os.environ.get(key)
if value:
return value
if env_name == "dev":
fallback = os.environ.get("MONGODB_URI")
if fallback:
return fallback
return ""
return value or ""
return raw


Expand Down
8 changes: 7 additions & 1 deletion src/cfg/core/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,7 @@ def resolve_ref(self, ref: RecordRef, value: str) -> dict[str, Any]:
else:
rows = self.adapter.query_history(collection=ref.collection, record_id=ref.record_id, ref=value, with_doc=True)
if not rows:
raise NoSuchConfig(f"ref not found: {value}")
raise NoSuchConfig(_ref_not_found_message(value))
if len(rows) > 1:
raise ValueError(f"ambiguous ref: {value}")
return rows[0]
Expand Down Expand Up @@ -1358,6 +1358,12 @@ def _branch_plan_result(plan: dict[str, Any], *, state: str | None = None) -> di
return result


def _ref_not_found_message(value: str) -> str:
if value.isdecimal():
return f"ref not found: {value} (sequence refs use @{value}; bare values are oid prefixes)"
return f"ref not found: {value}"


def _parse_branch_range(value: str, default_branch: str) -> tuple[str, str]:
raw = str(value or "").strip()
if ".." not in raw:
Expand Down
26 changes: 26 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,29 @@ def test_load_config_reads_branch_settings(tmp_path) -> None:
assert project.branches.enabled is True
assert project.branches.refs_collection == "cfgit_refs_custom"
assert project.branches.default_branch == "main"


def test_env_uri_does_not_fallback_to_generic_mongodb_uri(tmp_path, monkeypatch) -> None:
monkeypatch.delenv("DEV_MONGODB_URI", raising=False)
monkeypatch.setenv("MONGODB_URI", "mongodb://wrong.example")
path = tmp_path / ".cfg.toml"
path.write_text(
"""
[project]
name = "test"

[[collection]]
name = "demo"
id_field = "id"

[env.dev]
database = "mongo"
uri = "env:DEV_MONGODB_URI"
db = "cfgit-test"
""",
encoding="utf-8",
)

project = load_config(path)

assert project.envs["dev"].uri == ""
26 changes: 24 additions & 2 deletions tests/test_engine_safety.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,22 @@ def test_single_record_valid_time_ref_resolves_date_syntax() -> None:
assert resolved["doc"]["value"] == "old"


def test_bare_numeric_ref_not_found_points_to_sequence_syntax() -> None:
coll = CollectionConfig(name="demo", id_field="id")
row = _history_row(
coll,
{"id": "alpha", "value": "old"},
seq=12,
valid_from=datetime(2026, 6, 1, tzinfo=timezone.utc),
)
engine, _adapter = _engine(collection=coll, history=[row], heads={("demo", "alpha"): row})

with pytest.raises(NoSuchConfig, match=r"sequence refs use @12"):
engine.resolve_ref(RecordRef("demo", "alpha"), "12")

assert engine.resolve_ref(RecordRef("demo", "alpha"), "@12")["seq"] == 12


def test_empty_message_is_rejected_in_engine() -> None:
engine, _adapter = _engine(records={("demo", "alpha"): {"id": "alpha", "value": 1}})

Expand Down Expand Up @@ -589,8 +605,14 @@ def query_history(
continue
if record_id is not None and row["record_id"] != record_id:
continue
if ref is not None and ref.startswith("@") and row["seq"] != int(ref[1:]):
continue
if ref is not None:
if ref.startswith("@"):
if row["seq"] != int(ref[1:]):
continue
else:
oid = ref.removeprefix("sha256:").removeprefix("#")
if not str(row["oid"]).startswith(oid):
continue
if as_of_recorded is not None and row["recorded_at"] > as_of_recorded:
continue
if as_of_valid is not None:
Expand Down
Loading