mirror of
https://github.com/github/spec-kit.git
synced 2026-07-07 14:39:00 +08:00
fix(manifest): clear recovered marker on managed re-record + reject '..' in is_recovered
Address Copilot Round-7 review comments on PR #2483: 1. record_existing(recovered=False) and record_file now BOTH discard the path from _recovered_files. The marker is meant to flag "we observed this file but cannot vouch it's a managed baseline" — once the same path is re-recorded as managed (either explicitly or by writing fresh bytes), the marker is stale and must clear so refresh_managed and future is_recovered queries return the truthful answer. 2. is_recovered now applies the same canonical-key guard as record_existing (rejects absolute paths and '..' segments lexically before delegating to _validate_rel_path). Such paths can never be stored keys, so the query correctly returns False without depending on _validate_rel_path semantics that diverged from record_existing's stricter contract. record_file docstring updated to mention the side-effect on recovered markers. Tests: 3 new methods in TestManifestRecoveredFiles covering record_existing(false) clearing, record_file clearing, and is_recovered dotdot rejection.
This commit is contained in:
@@ -375,6 +375,37 @@ class TestManifestRecoveredFiles:
|
||||
# Don't record anything — the path is impossible to record anyway.
|
||||
assert m.is_recovered("../escape.txt") is False
|
||||
|
||||
def test_record_existing_clears_recovered_when_false(self, tmp_path):
|
||||
# Finding A: re-recording the same path with recovered=False must
|
||||
# drop the prior recovered marker (transition to managed baseline).
|
||||
f = tmp_path / "x.txt"
|
||||
f.write_text("v1", encoding="utf-8")
|
||||
m = IntegrationManifest("test", tmp_path)
|
||||
m.record_existing("x.txt", recovered=True)
|
||||
assert m.is_recovered("x.txt") is True
|
||||
m.record_existing("x.txt", recovered=False)
|
||||
assert m.is_recovered("x.txt") is False
|
||||
|
||||
def test_record_file_clears_recovered(self, tmp_path):
|
||||
# Finding A: record_file writes produced content; the path can no
|
||||
# longer be considered "merely observed" once we wrote bytes.
|
||||
(tmp_path / "y.txt").write_text("observed", encoding="utf-8")
|
||||
m = IntegrationManifest("test", tmp_path)
|
||||
m.record_existing("y.txt", recovered=True)
|
||||
assert m.is_recovered("y.txt") is True
|
||||
m.record_file("y.txt", "produced")
|
||||
assert m.is_recovered("y.txt") is False
|
||||
|
||||
def test_is_recovered_rejects_dotdot_segment(self, tmp_path):
|
||||
# Finding B: record_existing rejects ``..`` segments via the lexical
|
||||
# pre-check; is_recovered must match that behavior and return False
|
||||
# without raising, mirroring the canonicalization guard.
|
||||
(tmp_path / "z.txt").write_text("v1", encoding="utf-8")
|
||||
m = IntegrationManifest("test", tmp_path)
|
||||
m.record_existing("z.txt", recovered=True)
|
||||
# Same file via dotdot-normalizing path — must be False, not raise.
|
||||
assert m.is_recovered("subdir/../z.txt") is False
|
||||
|
||||
|
||||
class TestRecordExistingNewGuards:
|
||||
"""Coverage for the two new guards added by Copilot's 2026-05-18 review."""
|
||||
|
||||
Reference in New Issue
Block a user