aeat.application.modelo._review_package_feedback module¶
Recipient feedback-package round trip: reviewer notes back to the originator.
This module closes the “import/feedback-package round trip” item left open on
issue #421 (2026-07-04-recipient-encryption-adr and its follow-up slices):
the recipient (accountant/gestor) who received a review package via
encrypt_review_package_for_recipient() /
decrypt_review_package_for_recipient() now has a
SYMMETRIC path back to the originator (the taxpayer) – a small structured
FeedbackPackage (a verdict note plus an optional
CounterSignedReceipt) sealed with the EXACT
SAME X25519 ECIES construction, re-encrypted FOR THE ORIGINATOR rather than for
the accountant.
This module invents no new cryptography
(composition-service-no-parallel-write-path /
sensitive-financial-data-secure-storage-only): both
encrypt_feedback_package_for_originator() and
decrypt_feedback_package_from_originator_envelope() are thin
serialise-then-delegate / delegate-then-parse wrappers around
encrypt_review_package_for_recipient() and
decrypt_review_package_for_recipient() – the
“recipient” of a feedback package is simply the ORIGINATOR’s own encryption
keypair (the same ensure_recipient_encryption_keypair()
primitive the accountant used for the forward direction, minted for the
taxpayer instead), and the “package bytes” being sealed are a small JSON
document rather than a review-package ZIP. Every expiry, replay-nonce, and
review-only mechanic the forward direction already proves therefore composes
for free on the return trip.
Import composition (import_feedback_package()) ties the whole round trip
together for the originator: decrypt the envelope, parse the
FeedbackPackage, and – when the feedback carries a
CounterSignedReceipt – verify BOTH signature
layers against the local review-package archive bytes the originator already
holds (via verify_counter_signed_receipt())
before accepting it. A tampered feedback package, a wrong originator key, or an
invalid/forged countersignature refuse loudly rather than silently importing
unverified feedback (no-silent-under-declaration’s spirit applied to
collaboration integrity: an unverifiable countersignature is not evidence of
review).
See also
_review_package_recipient_encryptionOwns the X25519 ECIES primitive this module reuses verbatim, in both directions.
_review_package_counter_signOwns the counter-signed receipt this module’s feedback optionally carries and verifies on import.
_review_package_collab_auditOwns the bucket-event audit-tag emission this module’s import composition appends to the originator’s journal.
- exception ReviewPackageFeedbackError(message=None, *, context=None, suggestion=None, translated_message=None)[source]¶
Bases:
AeatErrorBase error for review-package feedback round-trip failures.
- Parameters:
- Return type:
None
- code: ClassVar[ErrorCode]¶
- exception FeedbackCounterSignatureInvalidError(message=None, *, context=None, suggestion=None, translated_message=None)[source]¶
Bases:
ReviewPackageFeedbackErrorRaised when an imported feedback package’s counter-signed receipt fails verification.
Covers a tampered original signature, a tampered or forged counter-signature, an edited note, or an archive that no longer matches the receipt’s original signature – never distinguished further, mirroring the undifferentiated-failure posture of
RecipientDecryptionError.- Parameters:
- Return type:
None
- code: ClassVar[ErrorCode]¶
- class FeedbackPackage(**data)[source]¶
Bases:
BaseModelA recipient’s structured feedback, sealed and returned to the originator.
Wraps a free-text verdict/note plus an OPTIONAL
CounterSignedReceipt– a recipient may return unstructured feedback alone (counter_signed_receipt=None, e.g. “see attached corrections, no formal sign-off yet”) or a fully counter-signed approval. The identifiers (bucket_id,work_unit_id,calculation_revision_id) are carried verbatim from theReviewPackageManifestthe recipient reviewed, so the originator can address the feedback to the correct work unit / revision without re-parsing the original archive.This model is the PLAINTEXT document sealed by
encrypt_feedback_package_for_originator()– it is never persisted directly; only the resultingRecipientEncryptedPackageenvelope is written to disk by the caller.- Parameters:
feedback_version (int)
bucket_id (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=None, min_length=1, max_length=128, pattern=None, ascii_only=None)])
work_unit_id (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=None, min_length=64, max_length=64, pattern=^[0-9a-f]{64}$, ascii_only=None)])
calculation_revision_id (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=None, min_length=64, max_length=64, pattern=^[0-9a-f]{64}$, ascii_only=None)])
note (str)
counter_signed_receipt (CounterSignedReceipt | None)
submitted_at (datetime)
submitted_by (str)
- feedback_version: int¶
- bucket_id: BucketId¶
- work_unit_id: WorkUnitId¶
- calculation_revision_id: CalculationRevisionId¶
- note: str¶
- counter_signed_receipt: CounterSignedReceipt | None¶
- submitted_at: datetime¶
- submitted_by: str¶
- build_feedback_package(*, bucket_id, work_unit_id, calculation_revision_id, note='', counter_signed_receipt=None, submitted_by, submitted_at=None)[source]¶
Build a
FeedbackPackagedocument, no I/O.- Parameters:
bucket_id (
str) – The originator’s bucket the reviewed package was built from (fromReviewPackageManifest).work_unit_id (
str) – The reviewed work unit’s id.calculation_revision_id (
str) – The reviewed calculation revision’s id.note (
str) – Free-text verdict or note (e.g."reviewed, no changes"or"see attached corrections").counter_signed_receipt (
CounterSignedReceipt|None) – OptionalCounterSignedReceiptproduced bycounter_sign_review_package(), when the recipient formally counter-signed the operator’s original signature.Nonefor unstructured feedback with no formal sign-off.submitted_by (
str) – The recipient’s actor label (e.g. an accountant’s display name).submitted_at (
datetime|None) – Optional override for the document’ssubmitted_attimestamp (tests only); defaults to the current UTC time.
- Return type:
- encrypt_feedback_package_for_originator(feedback, *, originator_public_key_hex, review_only=False, valid_for=None, issued_at=None)[source]¶
Seal
feedbackso only the originator’s private key can open it.A thin serialise-then-delegate wrapper: the recipient’s feedback document is dumped to canonical JSON bytes and sealed via the EXACT SAME
encrypt_review_package_for_recipient()ECIES construction used for the forward (originator-to-recipient) direction – no new cryptographic primitive is introduced for this reverse direction. The “recipient” of this call is the originator’s own encryption public key (seerecipient_encryption_public_key(), minted for the originator viaensure_recipient_encryption_keypair(), exactly as it is minted for an accountant in the forward direction).- Parameters:
feedback (
FeedbackPackage) – TheFeedbackPackagedocument to seal.originator_public_key_hex (
str) – The originator’s raw 32-byte X25519 public key, hex-encoded.review_only (
bool) – Passed straight through toencrypt_review_package_for_recipient(); a review-only feedback envelope carries no filing authority (almost alwaysFalsefor feedback, but exposed for parity with the forward direction).valid_for (
timedelta|None) – Optional validity window; seeencrypt_review_package_for_recipient().issued_at (
datetime|None) – Optional override for the envelope’sissued_attimestamp (tests only); defaults to the current UTC time.
- Return type:
- decrypt_feedback_package_from_originator_envelope(envelope, *, originator_private_key, now=None)[source]¶
Reverse
encrypt_feedback_package_for_originator()and parse the document.Delegates decryption entirely to
decrypt_review_package_for_recipient()(same AEAD authentication, same expiry check, same undifferentiated failure posture) and then parses the recovered bytes as aFeedbackPackage. A tampered envelope, wrong private key, or expired envelope raises exactly as the forward direction does; a recovered payload that is not validFeedbackPackageJSON raisesReviewPackageFeedbackError.- Parameters:
envelope (
RecipientEncryptedPackage) – TheRecipientEncryptedPackageproduced byencrypt_feedback_package_for_originator().originator_private_key (
X25519PrivateKey) – The originator’s own X25519 private key (seeload_recipient_encryption_keypair()).now (
datetime|None) – The instant to evaluate the envelope’s expiry against; defaults to the current UTC time (tests inject an explicit value).
- Raises:
RecipientPackageExpiredError – If the envelope has expired.
RecipientDecryptionError – If decryption fails for any other reason (wrong key, tampered ciphertext).
ReviewPackageFeedbackError – If the recovered plaintext is not valid
FeedbackPackageJSON.
- Return type:
- class ImportedFeedback(**data)[source]¶
Bases:
BaseModelResult of
import_feedback_package(): the parsed feedback plus its verification outcome.counter_signature_verifiedisNonewhen the feedback carried noCounterSignedReceipt(unstructured feedback), and a realbool– never silently omitted – when one was present, so a caller can distinguish “no formal sign-off was offered” from “a sign-off was offered and it verified/failed”.- Parameters:
feedback (FeedbackPackage)
counter_signature_verified (bool | None)
- feedback: FeedbackPackage¶
- counter_signature_verified: bool | None¶
- import_feedback_package(envelope, *, originator_private_key, reviewed_package_path, operator_public_key_hex, counter_signer_public_key_hex=None, now=None)[source]¶
Decrypt, parse, and (when present) verify a recipient’s feedback package.
This is the originator-side composition that ties the whole feedback round trip together: decrypt the envelope (
decrypt_feedback_package_from_originator_envelope()), and when the recoveredFeedbackPackagecarries aCounterSignedReceipt, re-verify BOTH signature layers againstreviewed_package_path– the ORIGINAL review-package archive the originator built and signed, still held locally – viaverify_counter_signed_receipt(). A feedback package with no counter-signed receipt is accepted as unstructured feedback withcounter_signature_verified=None; one that carries an invalid or forged receipt raises rather than silently importing unverified sign-off.- Parameters:
envelope (
RecipientEncryptedPackage) – The sealed feedback envelope received from the recipient.originator_private_key (
X25519PrivateKey) – The originator’s own X25519 private key.reviewed_package_path (
Path) – Path to the ORIGINAL review-package ZIP the originator built and (optionally) signed – required only when the feedback carries a counter-signed receipt; unused for unstructured feedback.operator_public_key_hex (
str) – The originator’s own Ed25519 signing public key (the key the ORIGINAL signature in the receipt must verify against).counter_signer_public_key_hex (
str|None) – The recipient’s Ed25519 signing public key the counter-signature must verify against. Required when the feedback carries a counter-signed receipt; a receipt present without this argument raises.now (
datetime|None) – The instant to evaluate the envelope’s expiry against; defaults to the current UTC time (tests inject an explicit value).
- Return type:
- Returns:
An
ImportedFeedbackcarrying the parsed feedback and the countersignature verification outcome.- Raises:
RecipientPackageExpiredError – If the envelope has expired.
RecipientDecryptionError – If decryption fails for any other reason.
ReviewPackageFeedbackError – If the recovered plaintext is malformed, or if the feedback carries a counter-signed receipt but no
counter_signer_public_key_hexwas supplied to verify it against.FeedbackCounterSignatureInvalidError – If the feedback carries a counter-signed receipt and it fails verification (tampered archive, tampered note, forged/wrong signature on either layer).