Source code for cadrumo.application.evidence.models
"""Pydantic models for operator-facing evidence bundles.An evidence bundle is a content-addressed manifest that groups relatedpersisted records (calculation revisions, filing records, and theirattached objects) under a single verifiable identity. The bundle's``bundle_id`` is a SHA-256 hash of the canonical manifest inputs so itis stable across re-imports of the same data and globally unique perbucket + work-unit + manifest-version triple.Key types:* :class:`EvidenceBundle` — the persisted manifest record.* :class:`EvidenceRecordRef` — one record reference inside the manifest.* :class:`BundleVerificationState` — closed lifecycle states for offline bundle verification.* :func:`derive_bundle_id` — content-addressed id derivation.Bundle manifests reference work-unit, calculation-revision, andfiling-record payloads without replacing those catalogues as the sourceof truth. Verification replays the manifest against supplied objectbytes and reports reachability and digest results without contacting AEAT."""from__future__importannotationsfromdatetimeimportdatetimefromenumimportStrEnumfromtypingimportAnnotatedfrompydanticimportBaseModel,Field,NonNegativeInt,field_serializerfrom...core.errors.hierarchyimportCadrumoErrorfrom...core.external_constantsimportUTF_8_ENCODINGfrom...core.hashingimportsha256_hexfrom...core.heximportHex64Strfrom...core.identity.bucketimportBucketIdfrom...core.identity.digestimportContentDigestfrom...core.identity.hex_idsimportCalculationRevisionId,FilingRecordId,WorkUnitIdfrom...core.modelsimportSTRICT_FROZEN_CONFIGfrom...core.prose_elisionimportElidedProsefrom...core.unit_proportionimportUnitFractionfrom...domain.buckets.eventimportBucketEventObjectTypefrom.bundle_textimportEvidenceBundleNotes
[docs]classEvidenceBundleNotFoundError(CadrumoError):"""Raised when an evidence-bundle lookup misses by id."""
[docs]classEvidenceBundleVerificationError(CadrumoError):"""Raised when bundle verification fails and the caller refuses ``--force-incomplete``. The verification service raises this when one or more :class:`VerificationCheck` items fail and the operator has not passed an override flag that permits incomplete bundles. """
[docs]classBundleVerificationState(StrEnum):"""Closed lifecycle states for a :class:`EvidenceBundle`'s verification status. ``PENDING`` is the initial state when a bundle is first written. ``VERIFIED`` means all :class:`VerificationCheck` items passed. ``INCOMPLETE`` means reachability or digest checks found missing records but the operator accepted the partial bundle. ``FAILED`` means at least one blocking integrity check failed. """PENDING="pending"VERIFIED="verified"INCOMPLETE="incomplete"FAILED="failed"
[docs]classVerificationCheck(StrEnum):"""Named integrity checks a bundle's offline verification pass runs. Each member identifies one distinct check the verifier executes against the bundle manifest and the backing object store: * ``MANIFEST_DIGEST`` — recompute ``bundle_id`` and compare. * ``RECORD_DIGESTS`` — re-hash each referenced object payload. * ``BUCKET_BINDING`` — confirm the bundle's ``bucket_id`` matches the repository it was loaded from. * ``WORK_UNIT_BINDING`` — confirm the ``work_unit_id`` exists. * ``OBJECT_REACHABILITY`` — confirm every referenced object is present in the bucket's object store. """MANIFEST_DIGEST="manifest_digest"RECORD_DIGESTS="record_digests"BUCKET_BINDING="bucket_binding"WORK_UNIT_BINDING="work_unit_binding"OBJECT_REACHABILITY="object_reachability"
#: The bundle-check ``detail`` annotation: elides rather than refusing.#:#: A failed check explains itself by interpolating the object ids it could not#: reach. Refusing the RESULT over the length of that explanation would lose the#: verification outcome as well as its reason. Empty is the documented default#: for a check that passed with nothing to add._CheckDetail=Annotated[str,ElidedProse(500,min_length=0)]
[docs]classEvidenceBundleCheckResult(BaseModel):"""One :class:`VerificationCheck` outcome from a bundle verification pass. ``passed`` is ``True`` when the check succeeded. ``detail`` carries a human-readable explanation when the check failed or when extra diagnostic context is available. """model_config=STRICT_FROZEN_CONFIGcheck:VerificationCheckpassed:booldetail:_CheckDetail=""
[docs]classEvidenceRecordRef(BaseModel):"""One referenced record entry inside an :class:`EvidenceBundle` manifest. ``object_type`` names the :class:`BucketEventObjectType` of the record; ``object_id`` is its stable store key; ``content_sha256`` is the canonical :data:`~core.identity.digest.ContentDigest` of the record's raw payload bytes, so a non-hex digest is refused on construction rather than at the later verification pass that recomputes it; ``payload_size_bytes`` is the byte count used for completeness-ratio calculation. """model_config=STRICT_FROZEN_CONFIGobject_type:BucketEventObjectTypeobject_id:str=Field(min_length=1,max_length=128)content_sha256:ContentDigestpayload_size_bytes:NonNegativeInt
[docs]classEvidenceBundle(BaseModel):"""One persisted evidence bundle. The manifest is content-addressed by ``bundle_id`` (SHA-256 of the canonical manifest payload). All record references carry their own content digest so bundle verification is offline-pure: given a bucket-scoped object store, a verifier can recompute digests and confirm reachability without contacting AEAT or any remote service. """model_config=STRICT_FROZEN_CONFIGbundle_id:Hex64Strmanifest_version:int=Field(ge=1)bucket_id:BucketIdwork_unit_id:WorkUnitIdcalculation_revision_id:CalculationRevisionId|None=Field(default=None)filing_record_id:FilingRecordId|None=Field(default=None)records:tuple[EvidenceRecordRef,...]=Field(default_factory=tuple)verification_state:BundleVerificationState=BundleVerificationState.PENDINGcompleteness_ratio:UnitFraction=1.0created_at:datetimenotes:EvidenceBundleNotes=""@field_serializer("created_at",when_used="json")def_serialize_dt(self,value:datetime)->str:returnvalue.isoformat()
[docs]defderive_bundle_id(*,bucket_id:str,work_unit_id:str,manifest_version:int,records:tuple[EvidenceRecordRef,...],calculation_revision_id:CalculationRevisionId|None=None,filing_record_id:str|None=None,)->str:"""Compute the content-addressed ``bundle_id`` for canonical inputs. The digest covers manifest version, bucket id, work-unit id, optional calculation revision id, optional filing record id, and the ordered :class:`EvidenceRecordRef` object type/id/content-digest triples. """payload_parts:list[str]=[f"version={manifest_version}",f"bucket={bucket_id}",f"work_unit={work_unit_id}",f"revision={calculation_revision_idor''}",f"filing={filing_record_idor''}",]forrecordinrecords:payload_parts.append(f"record={record.object_type.value}:{record.object_id}:{record.content_sha256}",)canonical="\n".join(payload_parts).encode(UTF_8_ENCODING)returnsha256_hex(canonical)