app.domain.approval¶
Approval policy, approval-state evaluation and reviewer eligibility.
Approval evidence is scoped by the logical identity
(source_id, project, record_key) and the content digest. The caller
passes only the decision events of one identity; evaluate() then
derives the state of the record’s current digest. Rules, applied in
this order (storage queries that filter by state must mirror them):
no valid policy ->
policy_missing(needs approval);policy
required: false->not_required(no approval needed);the latest event for the current digest is
accepted->accepted(no approval needed);the latest event for the current digest is
rejected->rejected(needs approval);an
acceptedevent exists for another digest ->changed_since_acceptance(needs approval);otherwise ->
pending(needs approval).
approvable (see app.domain.records.ParsedRecord.approvable)
does not change the state; it only controls
ApprovalState.can_decide. A record without a valid UUID or with
bad encoding therefore shows pending and cannot be decided.
Public API: PolicyStatus, Policy,
DecisionAction, DecisionEventView,
ApprovalStateCode, ApprovalState, evaluate(),
last_decided(), is_eligible(), principal(),
is_self_approval().
- class app.domain.approval.PolicyStatus(*values)¶
Bases:
StrEnumConfiguration status of the global approval policy.
- CONFIGURED = 'configured'¶
- MISSING = 'missing'¶
- INVALID = 'invalid'¶
- class app.domain.approval.Policy(id, version, required=True, reviewer_users=frozenset({}), reviewer_roles=frozenset({}), allow_self_approval=False)¶
Bases:
objectThe single global approval policy (Phase 1).
- Parameters:
id (
str) – Policy identifier recorded with every decision.version (
int) – Policy version recorded with every decision.required (
bool) – Whether approval is required at all.reviewer_users (
frozenset[str]) – Eligible principals asissuer|subject.allow_self_approval (
bool) – Whether authors may decide their own records.
- class app.domain.approval.DecisionAction(*values)¶
Bases:
StrEnumFormal decision recorded by a reviewer.
- ACCEPTED = 'accepted'¶
- REJECTED = 'rejected'¶
- class app.domain.approval.DecisionEventView(id, digest, action, created_at)¶
Bases:
objectThe fields of a decision event needed for state evaluation.
- Parameters:
id (
str) – Stable event id.digest (
str) – Content digest string the decision applies to.action (
DecisionAction) – Accepted or rejected.created_at (
datetime) – Server timestamp of the decision.
- action: DecisionAction¶
- class app.domain.approval.ApprovalStateCode(*values)¶
Bases:
StrEnumThe six approval states of a record’s current content.
- POLICY_MISSING = 'policy_missing'¶
- NOT_REQUIRED = 'not_required'¶
- ACCEPTED = 'accepted'¶
- REJECTED = 'rejected'¶
- CHANGED_SINCE_ACCEPTANCE = 'changed_since_acceptance'¶
- PENDING = 'pending'¶
- class app.domain.approval.ApprovalState(state, needs_approval, current_event=None, last_accepted=None, can_decide=False)¶
Bases:
objectEvaluated approval state of a record’s current digest.
- Parameters:
state (
ApprovalStateCode) – The state code.needs_approval (
bool) – Whether the record belongs in the “Needs approval” filter.current_event (
DecisionEventView|None) – Latest decision for the current digest, if any.last_accepted (
DecisionEventView|None) – Latest acceptance for another digest, set forchanged_since_acceptance.can_decide (
bool) – Whether a reviewer may record a decision now.
- state: ApprovalStateCode¶
- current_event: DecisionEventView | None¶
- last_accepted: DecisionEventView | None¶
- app.domain.approval.evaluate(policy, identity_events, current_digest, approvable)¶
Evaluate the approval state of a record’s current digest.
- Parameters:
policy (
Policy|None) – The configured policy, orNonewhen it is missing or invalid.identity_events (
Iterable[DecisionEventView]) – All decision events of the record’s logical identity (any digest).current_digest (
str) – Digest string of the record’s current content.approvable (
bool) – Whether the content may be decided at all.
- Return type:
- Returns:
The state; see the module docstring for the rules.
- app.domain.approval.last_decided(identity_events, *, exclude_digest=None)¶
Pick the default “from” revision for a diff.
- Parameters:
identity_events (
Iterable[DecisionEventView]) – All decision events of one identity.exclude_digest (
str|None) – Digest to ignore, typically the current one.
- Return type:
- Returns:
The latest accepted event, else the latest rejected event, else
None.
- app.domain.approval.principal(issuer, subject)¶
Build the stable principal string used in reviewer allowlists.
- app.domain.approval.is_eligible(policy, issuer, subject, roles)¶
Tell whether a caller may record decisions under the policy.
Read permission never implies review: only the policy’s reviewer users and roles are eligible.
- Parameters:
- Return type:
- Returns:
Trueif the caller is a listed user or holds a listed role; alwaysFalsewithout a policy.
- app.domain.approval.is_self_approval(policy, reviewer_email, author)¶
Tell whether a decision would be a forbidden self-approval.
- Parameters:
- Return type:
- Returns:
Truewhen self-approval is disallowed and the verified email equals the author (case-insensitive).