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:
|
) -> str:
|
||||||
"""Deterministic hash over the canonical concatenation of each
|
"""Deterministic hash over the canonical concatenation of each
|
||||||
observation's JSONL serialisation."""
|
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(
|
payload = json.dumps(
|
||||||
[obs.as_dict() for obs in observations],
|
[obs.as_dict() for obs in observations],
|
||||||
sort_keys=True,
|
sort_keys=True,
|
||||||
separators=(",", ":"),
|
separators=(",", ":"),
|
||||||
default=str,
|
|
||||||
)
|
)
|
||||||
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]
|
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def mine_articulation_observations(
|
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:
|
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
|
Used as the ``substrate_hash`` on every emitted finding so two
|
||||||
contemplation passes over byte-equal plans produce byte-equal
|
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:
|
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:
|
def _content_digest(payload: dict[str, Any]) -> str:
|
||||||
return hashlib.sha256(_canonical_json(payload).encode("utf-8")).hexdigest()[:16]
|
# 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)
|
# BOUNDARY (vs teaching/discovery.py:EvidencePointer)
|
||||||
|
|
@ -134,7 +139,7 @@ class ContemplationFinding:
|
||||||
if not self.evidence_refs:
|
if not self.evidence_refs:
|
||||||
raise ValueError("ContemplationFinding requires at least one evidence ref")
|
raise ValueError("ContemplationFinding requires at least one evidence ref")
|
||||||
if not self.finding_id:
|
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]:
|
def _identity_dict(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
|
|
@ -194,7 +199,7 @@ class ContemplationRun:
|
||||||
if finding.epistemic_status is not EpistemicStatus.SPECULATIVE:
|
if finding.epistemic_status is not EpistemicStatus.SPECULATIVE:
|
||||||
raise ValueError("ContemplationRun cannot contain non-SPECULATIVE findings")
|
raise ValueError("ContemplationRun cannot contain non-SPECULATIVE findings")
|
||||||
if not self.run_id:
|
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]:
|
def _identity_dict(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,13 @@ def _as_mv(psi: np.ndarray, name: str = "ψ") -> np.ndarray:
|
||||||
|
|
||||||
|
|
||||||
def _default_mode_id(psi: np.ndarray) -> str:
|
def _default_mode_id(psi: np.ndarray) -> str:
|
||||||
digest = hashlib.sha256(np.asarray(psi, dtype=np.float64).tobytes()).hexdigest()
|
# Full 256-bit digest over canonical little-endian float64 bytes (ADR-0244
|
||||||
return f"mode-{digest[:16]}"
|
# §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:
|
class HolographicVaultStore:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue