This commit is contained in:
1 parent
0e1a2ad008
commit
34f4923566
28 files changed
+3932
-81
No files matched your search
@@ -6,7 +6,9 @@ import hashlib
|
||||
import json
|
||||
import unicodedata
|
||||
from collections.abc import Iterable
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from gewu.domain.contracts import (
|
||||
AutoAcceptPolicy,
|
||||
@@ -59,6 +61,73 @@ def proposal_payload_digest(proposal: ProposalRevision) -> str:
|
||||
return hashlib.sha256(canonical_json(operations)).hexdigest()
|
||||
|
||||
|
||||
def prepare_proposal_digests(draft: dict[str, Any]) -> ProposalRevision:
|
||||
"""Validate a proposal draft, fill its dependent evidence digests, and pin its payload."""
|
||||
|
||||
value = deepcopy(draft)
|
||||
operations = value.get("operations")
|
||||
if not isinstance(operations, list) or not operations:
|
||||
raise ValueError("Proposal must contain a non-empty operations array")
|
||||
|
||||
for operation in operations:
|
||||
if not isinstance(operation, dict):
|
||||
raise ValueError("Proposal operations must be objects")
|
||||
node = operation.get("node")
|
||||
if isinstance(node, dict):
|
||||
for group in ("claims", "assumptions", "limitations"):
|
||||
claims = node.get(group, [])
|
||||
if not isinstance(claims, list):
|
||||
raise ValueError(f"Node {group} must be an array")
|
||||
for index, claim in enumerate(claims):
|
||||
if not isinstance(claim, dict) or not isinstance(claim.get("text"), str):
|
||||
raise ValueError(f"Node {group}[{index}] must contain claim text")
|
||||
digest = assertion_digest(claim["text"])
|
||||
evidence = claim.get("evidence")
|
||||
if not isinstance(evidence, list) or not evidence:
|
||||
raise ValueError(f"Node {group}[{index}] must contain evidence")
|
||||
path = f"{group}[{index}].text"
|
||||
evidence_digests = []
|
||||
for item in evidence:
|
||||
if not isinstance(item, dict):
|
||||
raise ValueError("Evidence references must be objects")
|
||||
item["assertion_path"] = path
|
||||
item["assertion_digest"] = digest
|
||||
parsed_evidence = EvidenceReference.model_validate(item, strict=True)
|
||||
item.clear()
|
||||
item.update(parsed_evidence.model_dump(mode="json"))
|
||||
evidence_digests.append(evidence_digest(parsed_evidence))
|
||||
assessment = claim.get("assessment")
|
||||
if not isinstance(assessment, dict):
|
||||
raise ValueError(f"Node {group}[{index}] must contain an assessment")
|
||||
assessment["assertion_digest"] = digest
|
||||
assessment["evidence_digests"] = evidence_digests
|
||||
|
||||
edge = operation.get("edge")
|
||||
if isinstance(edge, dict):
|
||||
edge_evidence = edge.get("evidence", [])
|
||||
if not isinstance(edge_evidence, list):
|
||||
raise ValueError("Edge evidence must be an array")
|
||||
for item in edge_evidence:
|
||||
if not isinstance(item, dict):
|
||||
raise ValueError("Evidence references must be objects")
|
||||
item["assertion_path"] = "edge"
|
||||
item["assertion_digest"] = "0" * 64
|
||||
parsed_edge = TypedEdge.model_validate(edge, strict=True)
|
||||
digest = edge_assertion_digest(parsed_edge)
|
||||
for item in edge_evidence:
|
||||
item["assertion_digest"] = digest
|
||||
parsed_evidence = EvidenceReference.model_validate(item, strict=True)
|
||||
item.clear()
|
||||
item.update(parsed_evidence.model_dump(mode="json"))
|
||||
edge["evidence"] = edge_evidence
|
||||
|
||||
value["payload_digest"] = "0" * 64
|
||||
provisional = ProposalRevision.model_validate_json(
|
||||
json.dumps(value, ensure_ascii=False, allow_nan=False), strict=True
|
||||
)
|
||||
return provisional.model_copy(update={"payload_digest": proposal_payload_digest(provisional)})
|
||||
|
||||
|
||||
def edge_assertion_digest(edge: TypedEdge) -> str:
|
||||
"""Bind edge evidence to the relationship, conditions and rationale."""
|
||||
|
||||
|
||||
Reference in new issue
Block a user