projection: admit retired-path classification

The screenshot names remain bounded in reachable history and absent from the current public tree.

Source-Sha: c357423ec432febcfce49adb42b863ee1a309b99
Policy-Sha: c357423ec432febcfce49adb42b863ee1a309b99
Tree-Digest: 2c39e139c90830e419de6510b326b6de9be288d4f5121c94cfb921734f815d55
This commit is contained in:
Fimeg 2026-09-10 09:21:32 -04:00
commit 5a7b122d4e
4 changed files with 60 additions and 16 deletions

View file

@ -59,17 +59,6 @@ RAF/verification/03-key-rotation.md
RAF/verification/04-replay-protection.md
README.md
SECURITY.md
Screenshots/7Zip-Updates-RedFlag-Dependency.png
Screenshots/RedFlag Agent List.png
Screenshots/RedFlag Default Dashboard.png
Screenshots/RedFlag Docker Dashboard.png
Screenshots/RedFlag Heartbeat System.png
Screenshots/RedFlag History Dashboard.png
Screenshots/RedFlag Linux Agent Details.png
Screenshots/RedFlag Live Operations - Failed Dashboard.png
Screenshots/RedFlag Updates Dashboard.png
Screenshots/RedFlag Windows Agent Details.png
Screenshots/Upstream-Version-Tracking.png
THIRD_PARTY_LICENSES.md
agent/NOTICE
agent/cmd/agent/cli.go

View file

@ -9,18 +9,20 @@
"RAF/flows/03-agent-upgrade.md",
"scripts/generate-keypair.go"
],
"review_required": [
"history_only": [
"Screenshots/7Zip-Updates-RedFlag-Dependency.png",
"Screenshots/RedFlag Agent List.png",
"Screenshots/RedFlag Default Dashboard.png",
"Screenshots/RedFlag Docker Dashboard.png",
"Screenshots/RedFlag Heartbeat System.png",
"Screenshots/RedFlag History Dashboard.png",
"Screenshots/RedFlag Linux Agent Details.png",
"Screenshots/RedFlag Live Operations - Failed Dashboard.png",
"Screenshots/RedFlag Agent List.png",
"Screenshots/RedFlag Updates Dashboard.png",
"Screenshots/Upstream-Version-Tracking.png",
"Screenshots/RedFlag Windows Agent Details.png",
"Screenshots/Upstream-Version-Tracking.png"
],
"review_required": [
"RAF/README.md",
"RAF/components/04-helper.md",
"RAF/components/05-desktop.md",

View file

@ -177,6 +177,17 @@ class Manifest:
if len(paths) != len(set(paths)):
raise ValueError(f"{self.path_manifest}: duplicate path")
self.allowed_paths = frozenset(paths)
history_only = list(raw.get("history_only", []))
for candidate in history_only:
validate_policy_path(candidate, "history_only path")
if history_only != sorted(history_only):
raise ValueError(f"{path}: history_only paths must be bytewise sorted")
if len(history_only) != len(set(history_only)):
raise ValueError(f"{path}: duplicate history_only path")
self.history_only_paths = frozenset(history_only)
overlap = self.allowed_paths & self.history_only_paths
if overlap:
raise ValueError(f"{path}: current and history_only paths overlap: {sorted(overlap)[0]}")
self.exclude = list(raw.get("exclude", []))
self.review_required = list(raw.get("review_required", []))
self.forbidden = list(raw.get("forbidden", []))
@ -259,7 +270,9 @@ def gate_path_authority(repo, sha, manifest, findings):
findings.append(Finding(
"path-authority", DENY, "excluded-path", path,
f"{location} contains a path excluded by {excluded_by!r}"))
if path not in manifest.allowed_paths:
admitted = manifest.allowed_paths if origin == sha else (
manifest.allowed_paths | manifest.history_only_paths)
if path not in admitted:
findings.append(Finding(
"path-authority", DENY, "unlisted-path", path,
f"no exact manifest entry admits this path in {location}"))
@ -274,6 +287,11 @@ def gate_path_authority(repo, sha, manifest, findings):
"path-authority", DENY, "missing-path", path,
"exact manifest entry is absent from the candidate tree"))
for path in sorted(manifest.history_only_paths & present):
findings.append(Finding(
"path-authority", DENY, "history-only-path", path,
"path is classified for reachable history but reappears in the candidate tree"))
findings.append(Finding(
"path-authority", NOTE, "exact-census", "-",
f"{len(present)} candidate paths checked against "

View file

@ -23,7 +23,7 @@ class SurfaceGateTests(unittest.TestCase):
def tearDown(self):
self.work.cleanup()
def candidate(self, files, admitted=None, exclude=None):
def candidate(self, files, admitted=None, exclude=None, history_only=None):
for name, content in files.items():
path = self.repo / name
path.parent.mkdir(parents=True, exist_ok=True)
@ -43,6 +43,7 @@ class SurfaceGateTests(unittest.TestCase):
"repository": "Fimeg/RedFlag",
"path_manifest": ".publication/paths.txt",
"exclude": exclude or [],
"history_only": history_only or [],
"review_required": [],
"forbidden": [],
"forbidden_classes": [],
@ -113,6 +114,40 @@ class SurfaceGateTests(unittest.TestCase):
self.assertIn("missing-path", result.stdout)
self.assertIn("product/missing.go", result.stdout)
def test_history_only_path_may_leave_the_current_tree(self):
old = self.candidate({"legacy/screenshot.png": b"old"})
subprocess.run(
["git", "-C", self.repo, "rm", "legacy/screenshot.png"], check=True
)
policy_path = self.repo / ".publication/paths.txt"
policy_path.write_text(
".publication/paths.txt\n.publication/surface.json\n"
)
surface_path = self.repo / ".publication/surface.json"
policy = json.loads(surface_path.read_text())
policy["history_only"] = ["legacy/screenshot.png"]
surface_path.write_text(json.dumps(policy, indent=2) + "\n")
subprocess.run(["git", "-C", self.repo, "add", "."], check=True)
subprocess.run(
["git", "-C", self.repo, "commit", "-q", "-m", "test: retire screenshot"],
check=True,
)
current = subprocess.check_output(
["git", "-C", self.repo, "rev-parse", "HEAD"], text=True
).strip()
result = self.run_gate(current, "--previous", old)
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
def test_history_only_path_cannot_reappear(self):
sha = self.candidate(
{"legacy/screenshot.png": b"still here"},
admitted=set(),
history_only=["legacy/screenshot.png"],
)
result = self.run_gate(sha)
self.assertEqual(1, result.returncode)
self.assertIn("history-only-path", result.stdout)
def test_approved_candidate_inventory_is_deterministic(self):
sha = self.candidate({"a.txt": "a\n", "nested/b.txt": "b\n"})
first = self.repo / "first.manifest"