feat(adr-0244): Phase 5a — §2.7 content-id semantic rigor (4 sites)

Widen the residual contemplation/vault content-ids to full 256-bit digests and
remove the last default=str / bare-tobytes byte-order gaps (ADR-0244 §2.7 /
ADR-0245 §2.3). Content-addresses must not floor collision resistance or silently
collapse distinct payloads:

- core/contemplation/schema.py: _sha256_16 → _content_digest (full 64-hex);
  finding_id + run_id no longer 16-hex truncated (was 2^32 birthday floor).
- core/contemplation/plan_preflight.py: _plan_substrate_hash full digest.
- core/contemplation/miners/articulation_quality.py: drop default=str (as_dict is
  JSON-native — ints/str/python-float ratios/None — so a non-serializable field
  now fails closed instead of str()-collapsing) + full digest.
- core/physics/holographic_vault.py: _default_mode_id full digest AND explicit
  little-endian float64 coercion (was bare .tobytes() — implicit host endianness).

Content-addresses only; no persisted-id migration (old ids stay valid, new ones
widen). Tests assert determinism + content, not id length, so no cascade.

Verified: 334 contemplation/vault/miner tests pass (determinism + content intact).
Full smoke + fast lane run at Phase 5 close.
This commit is contained in:
Shay 2026-07-17 19:48:11 -07:00
parent 918aa843b1
commit 5c69b74138
4 changed files with 26 additions and 11 deletions

View file

@ -297,13 +297,16 @@ def _substrate_hash_for_observations(
) -> str:
"""Deterministic hash over the canonical concatenation of each
observation's JSONL serialisation."""
# No ``default=str``: ``ArticulationObservation.as_dict()`` is JSON-native
# (ints, str, python-float ratios, None), so a non-serializable field fails
# closed rather than silently str()-collapsing distinct objects onto one id.
# Full 256-bit digest — no 16-hex truncation (ADR-0244 §2.7 / ADR-0245 §2.3).
payload = json.dumps(
[obs.as_dict() for obs in observations],
sort_keys=True,
separators=(",", ":"),
default=str,
)
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def mine_articulation_observations(

View file

@ -73,13 +73,15 @@ twice for two domain memberships). Three or more turns mechanical.
def _plan_substrate_hash(plan: DiscoursePlan) -> str:
"""SHA-256-16 of the plan's canonical JSON.
"""Full SHA-256 (64 hex) of the plan's canonical JSON.
Used as the ``substrate_hash`` on every emitted finding so two
contemplation passes over byte-equal plans produce byte-equal
finding IDs.
finding IDs. Full digest no 16-hex truncation (ADR-0244 §2.7 /
ADR-0245 §2.3): the substrate hash content-addresses the finding, so a
truncated digest would floor collision resistance.
"""
return hashlib.sha256(plan.to_json().encode("utf-8")).hexdigest()[:16]
return hashlib.sha256(plan.to_json().encode("utf-8")).hexdigest()
def _evidence_ref_for_plan(plan: DiscoursePlan) -> ContemplationEvidenceRef:

View file

@ -38,8 +38,13 @@ def _canonical_json(payload: dict[str, Any]) -> str:
)
def _sha256_16(payload: dict[str, Any]) -> str:
return hashlib.sha256(_canonical_json(payload).encode("utf-8")).hexdigest()[:16]
def _content_digest(payload: dict[str, Any]) -> str:
# Full 256-bit digest (64 hex) over canonical JSON — no 16-hex truncation
# (which floored birthday-collision resistance at 2^32). ADR-0244 §2.7 /
# ADR-0245 §2.3 semantic rigor: a content-address must not collide two
# distinct findings into the same id. ``_canonical_json`` already forbids
# ``default=str``, so a non-serializable payload fails closed here.
return hashlib.sha256(_canonical_json(payload).encode("utf-8")).hexdigest()
# BOUNDARY (vs teaching/discovery.py:EvidencePointer)
@ -134,7 +139,7 @@ class ContemplationFinding:
if not self.evidence_refs:
raise ValueError("ContemplationFinding requires at least one evidence ref")
if not self.finding_id:
object.__setattr__(self, "finding_id", _sha256_16(self._identity_dict()))
object.__setattr__(self, "finding_id", _content_digest(self._identity_dict()))
def _identity_dict(self) -> dict[str, Any]:
return {
@ -194,7 +199,7 @@ class ContemplationRun:
if finding.epistemic_status is not EpistemicStatus.SPECULATIVE:
raise ValueError("ContemplationRun cannot contain non-SPECULATIVE findings")
if not self.run_id:
object.__setattr__(self, "run_id", _sha256_16(self._identity_dict()))
object.__setattr__(self, "run_id", _content_digest(self._identity_dict()))
def _identity_dict(self) -> dict[str, Any]:
return {

View file

@ -71,8 +71,13 @@ def _as_mv(psi: np.ndarray, name: str = "ψ") -> np.ndarray:
def _default_mode_id(psi: np.ndarray) -> str:
digest = hashlib.sha256(np.asarray(psi, dtype=np.float64).tobytes()).hexdigest()
return f"mode-{digest[:16]}"
# Full 256-bit digest over canonical little-endian float64 bytes (ADR-0244
# §2.7 / ADR-0245 §2.3): no 16-hex truncation (which floored the durable
# mode-id's collision resistance at 2^32) and an explicit LE byte-order
# coercion so the id is platform-stable, not an implicit host-endianness
# assumption via bare ``.tobytes()``.
le_bytes = np.ascontiguousarray(psi, dtype=np.dtype("<f8")).tobytes()
return f"mode-{hashlib.sha256(le_bytes).hexdigest()}"
class HolographicVaultStore: