Superseded by ADR-0122. Consent is now recorded per (participant, embargo), so there is no scalar PEC state for
embargo_adherenceto derive from, and the field is removed. The diagnosis below (a stored projection can drift from the state it projects) is the same argument ADR-0122 applies one level down.
ADR-0056: embargo_adherence Is a Computed Property Derived from PEC State
Context and Problem Statement
ParticipantStatus.embargo_adherence: bool was declared as a stored field
defaulting to True. The draft protocol specification (§6.4.6) defined it as
a derived property — True iff PEC state is SIGNATORY, False otherwise
— but flagged the derived-vs.-stored choice as unresolved (open question 14).
Two risks followed from the stored implementation:
-
Drift. The field can disagree with the PEC state it is meant to project.
_sync_latest_status_metadata()updatedParticipantStatus.consenton every PEC write, but never updatedembargo_adherence. A participant who transitioned fromSIGNATORYtoLAPSEDstill carriedembargo_adherence=True. -
Fail-open default. The default
Truemeans a freshly constructedParticipantStatus— withconsent=Noneorconsent.state=UNBOUND— reads as "is a signatory". The MV-10-005 full-case-delivery gate depends on this field; a wrong default bypasses the gate silently.
The question is: should embargo_adherence be a stored field or a computed
property, and what is the enforcement point?
Decision Drivers
embargo_adherencehas exactly one correct value for any given PEC state; there is no scenario in which it should differ- A stored field that can be set independently of PEC provides no value and creates two sources of truth for the same fact
- The fail-open default is a correctness hazard at the MV-10-005 gate
- Pydantic v2
@computed_fieldmakes the derived nature explicit and serialization-compatible, requiring no changes to DataLayer consumers
Considered Options
-
@computed_fieldderived fromconsent.state(chosen): remove the stored field; add a@computed_fieldthat returnsself.consent is not None and self.consent.state == PEC.SIGNATORY. The wire projection (as_ParticipantStatus.from_core()) computes the value instead of copying it. -
Stored field +
model_validator(mode='after'): keep the stored field; add a validator that sets it fromconsent.stateon every construction. Guards initial construction, but still allows a caller to write the field after construction via direct assignment. -
Stored field + fix
_sync_latest_status_metadata: update the sync method to also writeembargo_adherence. Does not guard initial construction; a freshly constructedParticipantStatusstill startsTrue.
Decision Outcome
Chosen option: @computed_field derived from consent.state (Option 1).
Consequences
ParticipantStatus.embargo_adherencebecomes a Pydantic@computed_field. It appears inmodel_dump()output and the Pydantic schema, exactly as a regular field, but it cannot be set directly.- The default is effectively
False— a participant withconsent=Noneis not a signatory. This is fail-closed. as_ParticipantStatus.from_core()computes the value ascore_obj.consent is not None and core_obj.consent.state == PEC.SIGNATORY. The stored wire field default changes fromTruetoFalse.- Departed participants who were
SIGNATORYwhen they left will transition toLAPSEDon embargo revision (the normal PEC machine path). The computed field correctly reflectsFalseforLAPSED. - Good, because drift is eliminated permanently.
- Good, because the fail-open default is replaced with a fail-closed one.
- Good, because the MV-10-005 full-case-delivery gate is now correctly
enforced for all construction paths, not just those that call
apply_pec_transition(). - Bad, because callers that previously set
embargo_adherencedirectly will fail at the Pydantic validation layer. Any such site must instead change the PEC state viaapply_pec_transition(). (A grep sweep at implementation time will surface these sites — expected count: zero in production code, as CM-18-005 already requires all consent writes to go throughapply_pec_transition().)
Validation
When implemented (tracked in #2189), the following MUST hold:
- Unit tests MUST confirm
embargo_adherenceisTruewhenconsent.state==SIGNATORYandFalsefor all other PEC states and forconsent=None. - Existing tests that assert
embargo_adherence is Truefor a SIGNATORY participant MUST continue to pass. - Wire round-trip:
as_ParticipantStatus.from_core(core_status).to_core()MUST produce the correctembargo_adherencevalue.
More Information
- Closes open question 14 in
docs/reference/draft-vultron-spec.md(§6.4.6). - Builds on ADR-0048 (PEC NO_EMBARGO is absence of embargo) and ADR-0036 (per-machine dimension objects).
- Generated spec requirements:
specs/case-management.yamlCM-18-008. - Source: Concern #2091.