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:
parent
918aa843b1
commit
5c69b74138
4 changed files with 26 additions and 11 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue