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_encryption

Owns the X25519 ECIES primitive this module reuses verbatim, in both directions.

_review_package_counter_sign

Owns the counter-signed receipt this module’s feedback optionally carries and verifies on import.

_review_package_collab_audit

Owns 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: AeatError

Base error for review-package feedback round-trip failures.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
exception FeedbackCounterSignatureInvalidError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: ReviewPackageFeedbackError

Raised 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:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
class FeedbackPackage(**data)[source]

Bases: BaseModel

A 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 the ReviewPackageManifest the 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 resulting RecipientEncryptedPackage envelope 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 FeedbackPackage document, no I/O.

Parameters:
  • bucket_id (str) – The originator’s bucket the reviewed package was built from (from ReviewPackageManifest).

  • 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) – Optional CounterSignedReceipt produced by counter_sign_review_package(), when the recipient formally counter-signed the operator’s original signature. None for 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’s submitted_at timestamp (tests only); defaults to the current UTC time.

Return type:

FeedbackPackage

encrypt_feedback_package_for_originator(feedback, *, originator_public_key_hex, review_only=False, valid_for=None, issued_at=None)[source]

Seal feedback so 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 (see recipient_encryption_public_key(), minted for the originator via ensure_recipient_encryption_keypair(), exactly as it is minted for an accountant in the forward direction).

Parameters:
Return type:

RecipientEncryptedPackage

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 a FeedbackPackage. A tampered envelope, wrong private key, or expired envelope raises exactly as the forward direction does; a recovered payload that is not valid FeedbackPackage JSON raises ReviewPackageFeedbackError.

Parameters:
Raises:
Return type:

FeedbackPackage

class ImportedFeedback(**data)[source]

Bases: BaseModel

Result of import_feedback_package(): the parsed feedback plus its verification outcome.

counter_signature_verified is None when the feedback carried no CounterSignedReceipt (unstructured feedback), and a real bool – 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
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 recovered FeedbackPackage carries a CounterSignedReceipt, re-verify BOTH signature layers against reviewed_package_path – the ORIGINAL review-package archive the originator built and signed, still held locally – via verify_counter_signed_receipt(). A feedback package with no counter-signed receipt is accepted as unstructured feedback with counter_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:

ImportedFeedback

Returns:

An ImportedFeedback carrying the parsed feedback and the countersignature verification outcome.

Raises: