Skip to content

Commit 43e1f04

Browse files
BenBtgCopilot
andcommitted
fix(presets): preserve files with invalid provenance
Use immutable-core legacy migration only when the provenance sidecar is absent. If a sidecar is malformed or its hash does not match the live constitution, treat the file as edited and preserve it. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b2c095d-b45c-4d52-8d56-bd6121d96ab6
1 parent d52098a commit 43e1f04

2 files changed

Lines changed: 33 additions & 4 deletions

File tree

src/specify_cli/presets/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,11 @@ def _constitution_is_generated(
6262
try:
6363
metadata = json.loads(provenance.read_text(encoding="utf-8"))
6464
except (json.JSONDecodeError, UnicodeDecodeError):
65-
metadata = {}
66-
if (
65+
return False
66+
return (
6767
isinstance(metadata, dict)
6868
and metadata.get("sha256") == _content_sha256(content)
69-
):
70-
return True
69+
)
7170

7271
# Older projects have no provenance sidecar. Only the immutable bundled or
7372
# source-checkout core template is safe to treat as generated.

tests/test_presets.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2860,6 +2860,36 @@ def test_self_test_reseeds_exact_core_constitution(self, project_dir):
28602860
assert "preset:self-test" in content, "placeholder constitution was not re-seeded"
28612861
assert "[PROJECT_NAME]" not in content
28622862

2863+
@pytest.mark.parametrize(
2864+
"provenance_content",
2865+
[
2866+
'{"sha256": "does-not-match", "source": "old-preset"}\n',
2867+
"{not valid json",
2868+
],
2869+
ids=["hash-mismatch", "malformed"],
2870+
)
2871+
def test_self_test_preserves_core_content_with_existing_invalid_provenance(
2872+
self, project_dir, provenance_content
2873+
):
2874+
"""A present invalid sidecar disables legacy core-template migration."""
2875+
resolver = PresetResolver(project_dir)
2876+
bundled_core = resolver._find_bundled_core(
2877+
"constitution-template", "template", ".md"
2878+
)
2879+
assert bundled_core is not None
2880+
memory = project_dir / ".specify" / "memory" / "constitution.md"
2881+
memory.parent.mkdir(parents=True, exist_ok=True)
2882+
memory.write_bytes(bundled_core.read_bytes())
2883+
(memory.parent / ".constitution-template.json").write_text(
2884+
provenance_content
2885+
)
2886+
original = memory.read_bytes()
2887+
2888+
manager = PresetManager(project_dir)
2889+
install_self_test_preset(manager)
2890+
2891+
assert memory.read_bytes() == original
2892+
28632893
def test_self_test_preserves_mutable_project_core_copy(self, project_dir):
28642894
"""A project template copy does not establish generated provenance."""
28652895
authored = "# Acme Organization Constitution\n\nOrganization policy.\n"

0 commit comments

Comments
 (0)