Metadata
| Field |
Value |
| CodeClone version |
2.1.0a1 (reproduced on release/2.1.0a2, commit 932d4b73) |
| Affected area |
Canonical report / JSON |
What happened?
The canonical report digest (integrity.digest.value) — which is the MCP run_id — includes volatile execution provenance and presentation data. Two runs over byte-identical facts produce different run identities, and that churn cascades through the whole change-control and memory plane.
Volatile inputs proven to be inside the digest (codeclone/report/document/integrity.py:15-48 strips only meta.runtime, display_facts, and volatile file counters):
meta.cache.used / meta.cache.status — a cache-off run vs a warm cache-reuse run over identical facts hash differently ({"used": false, "status": "missing"} vs {"used": true, "status": "ok"}).
meta.project_name — literally scan_root.name (codeclone/report/meta.py:67). The same repo checked out at codeclone/ vs codeclone-copy/ gets different digests, even though scan_root itself is hardcoded to "." and all file paths are relative.
meta.analysis_thresholds — evaluator policy (complexity/coupling/cohesion thresholds) changes semantic identity without any fact changing.
Cascade (all bindings verified):
- MCP
run_id = this digest (surfaces/mcp/session.py:472);
- active change intents expire when the digest changes (
_session_intent_mixin.py:777-791) — so re-running analysis with a warm cache can invalidate an intent mid-edit;
- review receipts (
_session_review_receipt_mixin.py:284), patch trail, audit rows (audit/schema.py:33), and Engineering Memory ingestion keys (memory/project.py:99-116) all record the volatile digest.
What did you expect instead?
A digest hierarchy separating semantic identity from execution/presentation:
facts_digest — canonical facts only; excludes cache usage, project display name, timestamps, absolute paths, evaluator thresholds;
evaluation_digest — gate policy applied at run time;
envelope_digest — full-document artifact identity (current semantics, kept for exact-artifact binding).
MCP run_id and intent freshness bind to facts_digest; receipts/audit/memory record both tiers. Cache execution path and checkout dirname must not change run identity; real content/config changes must.
Minimal reproduction
# Run 1 (cold): codeclone . --json out1.json (cache missing)
# Run 2 (warm): codeclone . --json out2.json (cache reused, zero file changes)
# Compare integrity.digest.value: DIFFERENT, though findings/metrics/inventory
# are byte-identical. Renaming the checkout directory reproduces it too.
Additional context
REPORT_SCHEMA_VERSION is stamped at write time but never enforced on read anywhere — a report from a different build is silently accepted (same digest-trust family of gaps).
- Fix contract: Phase 39 spec
specs/phase-39-baseline-container-lanes.md §10.8 + invariant I-18 (volatile-free run identity), §6.3 (read-side schema enforcement).
- Note for triage:
get_report_section does not re-hash per call (in-memory document); this issue is about digest composition, not hashing cost.
Metadata
release/2.1.0a2, commit932d4b73)What happened?
The canonical report digest (
integrity.digest.value) — which is the MCPrun_id— includes volatile execution provenance and presentation data. Two runs over byte-identical facts produce different run identities, and that churn cascades through the whole change-control and memory plane.Volatile inputs proven to be inside the digest (
codeclone/report/document/integrity.py:15-48strips onlymeta.runtime,display_facts, and volatile file counters):meta.cache.used/meta.cache.status— a cache-off run vs a warm cache-reuse run over identical facts hash differently ({"used": false, "status": "missing"}vs{"used": true, "status": "ok"}).meta.project_name— literallyscan_root.name(codeclone/report/meta.py:67). The same repo checked out atcodeclone/vscodeclone-copy/gets different digests, even thoughscan_rootitself is hardcoded to"."and all file paths are relative.meta.analysis_thresholds— evaluator policy (complexity/coupling/cohesion thresholds) changes semantic identity without any fact changing.Cascade (all bindings verified):
run_id= this digest (surfaces/mcp/session.py:472);_session_intent_mixin.py:777-791) — so re-running analysis with a warm cache can invalidate an intent mid-edit;_session_review_receipt_mixin.py:284), patch trail, audit rows (audit/schema.py:33), and Engineering Memory ingestion keys (memory/project.py:99-116) all record the volatile digest.What did you expect instead?
A digest hierarchy separating semantic identity from execution/presentation:
facts_digest— canonical facts only; excludes cache usage, project display name, timestamps, absolute paths, evaluator thresholds;evaluation_digest— gate policy applied at run time;envelope_digest— full-document artifact identity (current semantics, kept for exact-artifact binding).MCP
run_idand intent freshness bind tofacts_digest; receipts/audit/memory record both tiers. Cache execution path and checkout dirname must not change run identity; real content/config changes must.Minimal reproduction
Additional context
REPORT_SCHEMA_VERSIONis stamped at write time but never enforced on read anywhere — a report from a different build is silently accepted (same digest-trust family of gaps).specs/phase-39-baseline-container-lanes.md§10.8 + invariant I-18 (volatile-free run identity), §6.3 (read-side schema enforcement).get_report_sectiondoes not re-hash per call (in-memory document); this issue is about digest composition, not hashing cost.