Skip to content

Commit 8295de3

Browse files
authored
Keep feature discovery reports compact and retain full audit artifacts (#303)
A full JiuwenSwarm discovery scan produces a projected 12–13 MB report before its final boundary decisions, with repeated receipt metadata and thousands of candidate descriptions. Feature discovery now publishes a compact summary and retains the complete report as an immutable, content-addressed artifact outside Git. The checkpoint and summary bind its exact path, byte size and SHA-256; cached and prepared publication reject missing, altered or escaped artifacts. Completed unpublished legacy previews re-render through the native stage using their saved scans, independent reviews and boundary audit without calling either model again. Published historical catalogs remain immutable. Source scope, prompts, model configuration, feature definitions, budgets and correction limits are unchanged. Validation: 176 focused tests pass in both implementation and independent runs; native receipt/input assertions now inspect the real complete artifact. Regressions cover large-report size, canonical bytes across recovery, unchanged catalogs, containment, missing/tampered archives and exact restoration, prepared publication, native boundary failures, and legacy preview migration. Independent code review found no remaining actionable issue. Full CI is required before merge.
1 parent 4dc819b commit 8295de3

3 files changed

Lines changed: 328 additions & 7 deletions

File tree

‎src/infermatrix_copilot/kb_service/init_feature_discovery.py‎

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import os
1717
from pathlib import Path
1818
import re
19+
import tempfile
1920
import threading
2021
import unicodedata
2122

@@ -34,6 +35,7 @@
3435
MAX_ATTEMPTS = 4
3536
MAX_BASELINE_CHARS = 16000
3637
CATALOG_BOUNDARY_VERSION = "catalog-boundary-v1"
38+
COMPACT_REPORT_VERSION = "feature-discovery-compact-v1"
3739
RELATIONS = {"new", "implementation_supplement", "alias", "subcapability",
3840
"shared_component", "outdated", "unknown"}
3941
_SLUG = re.compile(r"[a-z0-9][a-z0-9-]{0,39}\Z")
@@ -110,6 +112,89 @@ def _json(value):
110112
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
111113

112114

115+
def _report_archive_root(state_dir):
116+
state_root = Path(state_dir).resolve()
117+
root = state_root / "init" / "artifacts" / "feature-discovery"
118+
if not root.resolve().is_relative_to(state_root):
119+
raise InitError("discovery report archive escapes its state directory")
120+
return root.resolve()
121+
122+
123+
def write_full_discovery_report(state_dir, report):
124+
"""Atomically create immutable full evidence outside publication files."""
125+
data = (json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n").encode()
126+
digest = hashlib.sha256(data).hexdigest()
127+
root = _report_archive_root(state_dir)
128+
root.mkdir(parents=True, exist_ok=True)
129+
path = root / (digest + ".json")
130+
with tempfile.NamedTemporaryFile(dir=root, delete=False) as handle:
131+
temporary = Path(handle.name)
132+
handle.write(data)
133+
handle.flush()
134+
os.fsync(handle.fileno())
135+
try:
136+
try:
137+
os.link(temporary, path) # Exclusive atomic creation, never replacement.
138+
except FileExistsError:
139+
pass
140+
if path.is_symlink() or path.read_bytes() != data:
141+
raise InitError("immutable discovery report artifact differs; restore its original bytes")
142+
finally:
143+
temporary.unlink()
144+
return {"path": str(path), "sha256": digest, "size_bytes": len(data)}
145+
146+
147+
def compact_discovery_report(report, artifact):
148+
keys = ("schema_version", "repo", "pin", "run_config", "complete", "done", "catalog_sha256",
149+
"feature_ids", "features", "owner_requests", "counts", "statistics", "scan_summary",
150+
"index_sha256", "limits", "historical_denominator", "facet_denominator", "pacing")
151+
compact = {key: deepcopy(report[key]) for key in keys if key in report}
152+
audit = report["catalog_boundary_audit"]
153+
compact["catalog_boundary_audit"] = {key: deepcopy(audit[key]) for key in ("identity", "done", "counts") if key in audit}
154+
compact["catalog_boundary_audit"]["candidate_count"] = len(audit.get("candidate_ids", []))
155+
compact.update(report_format=COMPACT_REPORT_VERSION, full_artifact=deepcopy(artifact),
156+
candidate_count=len(report.get("candidates", [])),
157+
unassociated_implementation_count=len(report.get("unassociated_implementation_paths", [])),
158+
unassociated_entry_lead_count=len(report.get("unassociated_entry_leads", [])))
159+
for key in ("failures", "scope_suggestions", "candidate_limit_task_ids", "invalid_candidates"):
160+
values = report.get(key, [])
161+
compact[key] = {"count": len(values), "examples": deepcopy(values[:5]),
162+
"complete_list_in_full_artifact": True}
163+
return compact
164+
165+
166+
def verify_full_discovery_report(state_dir, report, state):
167+
"""Check byte identity, containment and the genuine checkpoint binding."""
168+
try:
169+
if report.get("report_format") != COMPACT_REPORT_VERSION or state.get("report_format") != COMPACT_REPORT_VERSION:
170+
raise ValueError("compact report marker is missing")
171+
artifact = report["full_artifact"]
172+
if artifact != state.get("full_artifact") or set(artifact) != {"path", "sha256", "size_bytes"}:
173+
raise ValueError("report and checkpoint artifact bindings differ")
174+
digest, size = artifact["sha256"], artifact["size_bytes"]
175+
if not isinstance(digest, str) or not re.fullmatch(r"[0-9a-f]{64}", digest) \
176+
or type(size) is not int or size <= 0:
177+
raise ValueError("artifact hash or size is invalid")
178+
path = Path(artifact["path"])
179+
expected = _report_archive_root(state_dir) / (digest + ".json")
180+
if not path.is_absolute() or path != expected or path.is_symlink() or path.resolve() != expected:
181+
raise ValueError("artifact path escapes the immutable report archive")
182+
data = path.read_bytes()
183+
if len(data) != size or hashlib.sha256(data).hexdigest() != digest:
184+
raise ValueError("artifact bytes or hash differ")
185+
full = json.loads(data)
186+
if compact_discovery_report(full, artifact) != report:
187+
raise ValueError("compact report differs from its full artifact")
188+
for key in ("pin", "catalog_sha256", "index_sha256", "run_config"):
189+
if full.get(key) != state.get(key):
190+
raise ValueError("full report and checkpoint identity differ")
191+
if full.get("complete") is not True or full.get("done") is not True:
192+
raise ValueError("full report is unfinished")
193+
return full
194+
except (OSError, ValueError, TypeError, KeyError) as exc:
195+
raise InitError(f"discovery full report artifact could not be verified: {exc}") from exc
196+
197+
113198
def _hash(value):
114199
return hashlib.sha256(_json(value).encode()).hexdigest()
115200

@@ -765,13 +850,53 @@ def _chain(self):
765850
return super()._chain()
766851

767852
def _cache_reusable(self, previous):
768-
if previous.discovery.get("done") and self._validate_saved_archives(previous.discovery):
853+
audited = deepcopy(previous)
854+
if previous.discovery.get("done") and (self._validate_saved_archives(audited.discovery)
855+
or previous.discovery.get("catalog_boundary_audit", {}).get("done")
856+
and not audited.discovery.get("catalog_boundary_audit", {}).get("done")):
769857
raise InitError("completed discovery native archives are unavailable; restore the archive before reuse")
770858
if not previous.discovery.get("done") or self.retry_unfinished:
771859
return False
860+
if previous.discovery.get("report_format") == COMPACT_REPORT_VERSION:
861+
self._verify_report_record(previous)
862+
elif previous.status != "published":
863+
return False # Re-render saved unpublished results through the official stage.
772864
# Published historical catalogs remain immutable. Unpublished previews
773865
# must acquire the additive audit before publication, reusing their scan.
774-
return previous.status == "published" or self._boundary_record_ready(previous)
866+
return previous.status == "published" or self._boundary_record_ready(audited)
867+
868+
def _verify_report_record(self, record, files=None):
869+
from .init_stages import _dry_run_files
870+
from .init_support import load_prepared
871+
if files is None:
872+
if record.pr.get("prepared"):
873+
files = load_prepared(record.pr["prepared"])["files"]
874+
elif record.pr.get("dry_run_dir"):
875+
files = _dry_run_files(record)
876+
else:
877+
files = {path: self.rt.knowledge.show(record.kb_base_sha, path) for path in
878+
(record.discovery["report_path"], self._coverage_policy_path())}
879+
text = files.get(record.discovery["report_path"])
880+
catalog = files.get(self._coverage_policy_path())
881+
if catalog is None:
882+
catalog = self.rt.knowledge.show(record.kb_base_sha, self._coverage_policy_path())
883+
if not isinstance(text, str) or hashlib.sha256(text.encode()).hexdigest() != record.discovery.get("report_sha256") \
884+
or not isinstance(catalog, str) or hashlib.sha256(catalog.encode()).hexdigest() != record.discovery.get("catalog_sha256"):
885+
raise InitError("discovery checkpoint and compact catalog/report bytes differ")
886+
try:
887+
report = json.loads(text)
888+
except ValueError as exc:
889+
raise InitError("discovery compact report is not JSON") from exc
890+
if not isinstance(report, dict) or report.get("repo") != record.repo or report.get("pin") != record.pin:
891+
raise InitError("discovery compact report identity differs")
892+
return verify_full_discovery_report(self.rt.state_dir, report, record.discovery)
893+
894+
def _publish(self, changed):
895+
try:
896+
self._verify_report_record(self.record, changed)
897+
except InitError as exc:
898+
return self._blocked([str(exc)])
899+
return super()._publish(changed)
775900

776901
def _boundary_record_ready(self, previous):
777902
state = previous.discovery
@@ -811,6 +936,10 @@ def _resume_input_problems(self, previous, digest):
811936
if not self._boundary_record_ready(audited):
812937
return ["prepared discovery has no complete catalog boundary audit; preserve its publication and checkpoint "
813938
"and audit a copy before creating a new publication"]
939+
try:
940+
self._verify_report_record(previous)
941+
except InitError as exc:
942+
return [str(exc)]
814943
return []
815944

816945
def _init_identity(self):
@@ -1139,8 +1268,15 @@ def save():
11391268
"limits": "Declared inventory processed; not proof all repository capabilities were found. Tests not executed.",
11401269
"historical_denominator": len(seeds) * 7, "facet_denominator": len(features) * 7}
11411270
report_path = f"eval/feature-discovery/{self.lifecycle.repo}-{self.record.pin[:12]}.json"
1142-
report_text = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
1271+
try:
1272+
if _report_archive_root(self.rt.state_dir).is_relative_to(Path(self.rt.knowledge.path).resolve()):
1273+
raise InitError("full discovery reports require a state directory outside the knowledge checkout")
1274+
artifact = write_full_discovery_report(self.rt.state_dir, report)
1275+
except (InitError, OSError) as exc:
1276+
return self._blocked([str(exc)])
1277+
report_text = json.dumps(compact_discovery_report(report, artifact), ensure_ascii=False, indent=2) + "\n"
11431278
state.update(done=True, pin=self.record.pin, catalog_sha256=catalog_sha, report_path=report_path,
1279+
report_format=COMPACT_REPORT_VERSION, full_artifact=artifact,
11441280
report_sha256=hashlib.sha256(report_text.encode()).hexdigest())
11451281
other = {path: (original_text, rendered), report_path: (self.rt.knowledge.show(self._base_sha, report_path), report_text)}
11461282

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""Compact publication preserves full evidence in an immutable external file."""
2+
from copy import deepcopy
3+
import hashlib
4+
import json
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
from infermatrix_copilot.kb_service.init_feature_discovery import (
10+
compact_discovery_report, write_full_discovery_report, verify_full_discovery_report,
11+
)
12+
from infermatrix_copilot.kb_service.init_support import InitError
13+
14+
15+
def full_report():
16+
return {'schema_version': 1, 'repo': 'library', 'pin': 'a' * 40,
17+
'complete': True, 'done': True, 'catalog_sha256': 'b' * 64,
18+
'index_sha256': 'c' * 64, 'run_config': {'packet_chars': 96000},
19+
'feature_ids': ['library'], 'features': [{'id': 'library', 'owner': 'core', 'title': 'Library'}],
20+
'owner_requests': [], 'counts': {'unknown': 5000}, 'statistics': {'unknown': 5000},
21+
'catalog_boundary_audit': {'done': True, 'identity': {'version': 'boundary-v1'},
22+
'candidate_ids': ['proposal'] * 5000, 'counts': {'yes': 5000}},
23+
'candidates': [{'id': f'proposal-{n}', 'description': 'evidence ' * 250,
24+
'reason': 'unknown ' * 250, 'generator_receipts': [{'trace_id': 'native'}],
25+
'evidence': [{'path': f'src/file-{n}.py', 'start': 1, 'end': 2}]} for n in range(5000)],
26+
'unassociated_implementation_paths': [f'src/unmapped-{n}.py' for n in range(5000)],
27+
'unassociated_entry_leads': [{'path': f'src/unmapped-{n}.py', 'symbols': ['entry']} for n in range(5000)],
28+
'failures': [], 'scope_suggestions': [{'path': f'outside/file-{n}.py'} for n in range(5000)],
29+
'candidate_limit_task_ids': [f'source:{n}' for n in range(500)],
30+
'invalid_candidates': [{'reason': 'invalid citation'} for n in range(5000)]}
31+
32+
33+
def test_large_report_is_compact_and_full_bytes_are_preserved(tmp_path):
34+
full = full_report()
35+
artifact = write_full_discovery_report(tmp_path, full)
36+
compact = compact_discovery_report(full, artifact)
37+
assert len(json.dumps(compact, ensure_ascii=False).encode()) < 100_000
38+
assert artifact['size_bytes'] > 10_000_000
39+
assert compact['candidate_count'] == 5000 and 'candidates' not in compact
40+
assert compact['catalog_boundary_audit']['candidate_count'] == 5000
41+
assert 'candidate_ids' not in compact['catalog_boundary_audit']
42+
assert compact['scope_suggestions']['count'] == 5000
43+
state = {**{k: full[k] for k in ('pin', 'catalog_sha256', 'index_sha256', 'run_config')},
44+
'report_format': compact['report_format'], 'full_artifact': artifact}
45+
assert verify_full_discovery_report(tmp_path, compact, state) == full
46+
raw = Path(artifact['path']).read_bytes()
47+
assert hashlib.sha256(raw).hexdigest() == artifact['sha256']
48+
assert write_full_discovery_report(tmp_path, full) == artifact
49+
assert Path(artifact['path']).read_bytes() == raw
50+
51+
52+
@pytest.mark.parametrize('damage', ['escape', 'symlink', 'checkpoint_binding', 'summary', 'size', 'identity'])
53+
def test_full_report_binding_and_containment_refuse_tampering(tmp_path, damage):
54+
full = full_report(); full['candidates'] = []
55+
artifact = write_full_discovery_report(tmp_path, full)
56+
compact = compact_discovery_report(full, artifact)
57+
state = {**{k: full[k] for k in ('pin', 'catalog_sha256', 'index_sha256', 'run_config')},
58+
'report_format': compact['report_format'], 'full_artifact': deepcopy(artifact)}
59+
if damage == 'escape':
60+
outside = tmp_path / 'elsewhere.json'; outside.write_bytes(Path(artifact['path']).read_bytes())
61+
compact['full_artifact']['path'] = str(outside); state['full_artifact'] = deepcopy(compact['full_artifact'])
62+
elif damage == 'symlink':
63+
path = Path(artifact['path']); outside = tmp_path / 'elsewhere.json'
64+
path.rename(outside); path.symlink_to(outside)
65+
elif damage == 'checkpoint_binding':
66+
state['full_artifact']['sha256'] = 'd' * 64
67+
elif damage == 'summary':
68+
compact['counts']['unknown'] = 0
69+
elif damage == 'size':
70+
compact['full_artifact']['size_bytes'] += 1; state['full_artifact'] = deepcopy(compact['full_artifact'])
71+
else:
72+
state['catalog_sha256'] = 'd' * 64
73+
with pytest.raises(InitError, match='full report artifact'):
74+
verify_full_discovery_report(tmp_path, compact, state)

0 commit comments

Comments
 (0)