Add institutional provenance and approval gates

This commit is contained in:
2026-08-01 20:57:26 +02:00
parent cec3d17bff
commit 2afdd38128
16 changed files with 2472 additions and 509 deletions
@@ -13,7 +13,9 @@ CAMPAIGN_MAIL_SERVER_KEYS = frozenset(
"imap_credential_id",
}
)
CAMPAIGN_CLIENT_EDITOR_STATE_KEYS = frozenset({"created_from", "field_overrides", "opt_ins"})
CAMPAIGN_CLIENT_EDITOR_STATE_KEYS = frozenset(
{"created_from", "field_overrides", "opt_ins"}
)
CAMPAIGN_OPT_IN_KEYS = frozenset(
{"campaign_address_suggestions", "remember_used_addresses", "inline_guidance"}
)
@@ -27,6 +29,16 @@ CAMPAIGN_REVIEW_STATE_KEYS = frozenset(
"updated_by_user_id",
}
)
CAMPAIGN_APPROVAL_GATE_KEYS = frozenset(
{
"request_id",
"request_revision",
"subject_version",
"subject_digest",
"requested_at",
"requested_by_user_id",
}
)
class CampaignMailProfileBoundaryError(ValueError):
@@ -94,10 +106,13 @@ def validate_campaign_editor_state(
if value is None:
return {}
if not isinstance(value, dict):
raise CampaignMailProfileBoundaryError("Campaign editor state must be an object")
raise CampaignMailProfileBoundaryError(
"Campaign editor state must be an object"
)
allowed = set(CAMPAIGN_CLIENT_EDITOR_STATE_KEYS)
if allow_server_review_state:
allowed.add("review_send")
allowed.add("approval_gate")
if any(key not in allowed for key in value):
raise CampaignMailProfileBoundaryError(
"Campaign editor state contains unsupported or transport-owned fields"
@@ -113,11 +128,13 @@ def validate_campaign_editor_state(
if "opt_ins" in value:
result["opt_ins"] = _validated_opt_ins(value["opt_ins"])
if "field_overrides" in value:
result["field_overrides"] = _validated_field_overrides(
value["field_overrides"]
)
result["field_overrides"] = _validated_field_overrides(value["field_overrides"])
if "review_send" in value:
result["review_send"] = _validated_server_review_state(value["review_send"])
if "approval_gate" in value:
result["approval_gate"] = _validated_server_approval_gate(
value["approval_gate"]
)
return result
@@ -162,6 +179,13 @@ def public_campaign_editor_state(
result["review_send"] = review_state
except CampaignMailProfileBoundaryError:
pass
if "approval_gate" in value:
try:
result["approval_gate"] = _validated_server_approval_gate(
value["approval_gate"]
)
except CampaignMailProfileBoundaryError:
pass
return result
@@ -170,9 +194,48 @@ def campaign_editor_state_for_edit(value: Any) -> dict[str, Any]:
state = public_campaign_editor_state(value)
state.pop("review_send", None)
state.pop("approval_gate", None)
return state
def _validated_server_approval_gate(value: Any) -> dict[str, Any]:
if not isinstance(value, dict) or any(
key not in CAMPAIGN_APPROVAL_GATE_KEYS for key in value
):
raise CampaignMailProfileBoundaryError("Campaign approval gate is invalid")
required_strings = (
"request_id",
"subject_version",
"subject_digest",
"requested_at",
)
if any(
not isinstance(value.get(key), str) or not str(value.get(key)).strip()
for key in required_strings
):
raise CampaignMailProfileBoundaryError(
"Campaign approval gate references are invalid"
)
digest = str(value["subject_digest"])
if len(digest) != 64 or any(
character not in "0123456789abcdef" for character in digest
):
raise CampaignMailProfileBoundaryError(
"Campaign approval gate digest is invalid"
)
revision = value.get("request_revision")
if not isinstance(revision, int) or revision < 1:
raise CampaignMailProfileBoundaryError(
"Campaign approval gate revision is invalid"
)
requested_by = value.get("requested_by_user_id")
if requested_by is not None and not isinstance(requested_by, str):
raise CampaignMailProfileBoundaryError(
"Campaign approval gate actor is invalid"
)
return copy.deepcopy(value)
def _is_valid_reviewed_message_key(value: Any) -> bool:
return isinstance(value, str) and bool(value.strip()) and len(value) <= 512
@@ -214,9 +277,7 @@ def _validated_issue_decisions(value: Any) -> list[dict[str, Any]]:
"issue_codes",
}
for item in value:
if not isinstance(item, dict) or any(
key not in allowed_keys for key in item
):
if not isinstance(item, dict) or any(key not in allowed_keys for key in item):
raise CampaignMailProfileBoundaryError(
"Campaign review issue decision is invalid"
)
@@ -237,9 +298,7 @@ def _validated_issue_decisions(value: Any) -> list[dict[str, Any]]:
error="Campaign review decision is invalid",
),
"reason": item.get("reason"),
"actor_user_id": _validated_review_actor(
item.get("actor_user_id")
),
"actor_user_id": _validated_review_actor(item.get("actor_user_id")),
"decided_at": _validated_required_string(
item.get("decided_at"),
max_length=128,
@@ -364,12 +423,18 @@ def campaign_mail_resource_ids(
}
def campaign_mail_profile_boundary_violations(raw_json: dict[str, Any] | None) -> tuple[str, ...]:
def campaign_mail_profile_boundary_violations(
raw_json: dict[str, Any] | None,
) -> tuple[str, ...]:
server = raw_json.get("server") if isinstance(raw_json, dict) else None
if not isinstance(server, dict):
return ()
violations = [f"/server/{key}" for key in sorted(server) if key not in CAMPAIGN_MAIL_SERVER_KEYS]
violations = [
f"/server/{key}"
for key in sorted(server)
if key not in CAMPAIGN_MAIL_SERVER_KEYS
]
for key in CAMPAIGN_MAIL_SERVER_KEYS:
if key not in server:
continue
@@ -378,9 +443,7 @@ def campaign_mail_profile_boundary_violations(raw_json: dict[str, Any] | None) -
violations.append(f"/server/{key}")
references = campaign_mail_resource_ids(raw_json)
if references["mail_profile_id"] is None and any(
references[key]
for key in references
if key != "mail_profile_id"
references[key] for key in references if key != "mail_profile_id"
):
violations.append("/server/mail_profile_id")
for protocol in ("smtp", "imap"):