diff --git a/plugins/cfg_impact/pyproject.toml b/plugins/cfg_impact/pyproject.toml index 0450ae9..ba37c16 100644 --- a/plugins/cfg_impact/pyproject.toml +++ b/plugins/cfg_impact/pyproject.toml @@ -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", ] diff --git a/pyproject.toml b/pyproject.toml index 64903b9..e421544 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/src/cfg/core/config.py b/src/cfg/core/config.py index a2998e8..31e66eb 100644 --- a/src/cfg/core/config.py +++ b/src/cfg/core/config.py @@ -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 diff --git a/src/cfg/core/engine.py b/src/cfg/core/engine.py index 5027e8c..93014d3 100644 --- a/src/cfg/core/engine.py +++ b/src/cfg/core/engine.py @@ -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] @@ -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: diff --git a/tests/test_config.py b/tests/test_config.py index 316bb60..b47e918 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -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 == "" diff --git a/tests/test_engine_safety.py b/tests/test_engine_safety.py index 2ba7fa0..1d341ec 100644 --- a/tests/test_engine_safety.py +++ b/tests/test_engine_safety.py @@ -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}}) @@ -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: