Implement P05 checked services and safe selected delivery recovery
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
This commit is contained in:
parent
99618488e0
commit
ac0eb14b75
15 changed files with 735 additions and 88 deletions
143
src/user_engine/operations_status.py
Normal file
143
src/user_engine/operations_status.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
"""Bounded, non-sending diagnostics rendered only after platform authorization."""
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime, timezone
|
||||
from html import escape
|
||||
import json
|
||||
from urllib.request import Request, build_opener, HTTPRedirectHandler
|
||||
from urllib.error import HTTPError
|
||||
|
||||
|
||||
class NoRedirect(HTTPRedirectHandler):
|
||||
def redirect_request(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
def sign_in_status(base):
|
||||
try:
|
||||
response = build_opener(NoRedirect()).open(
|
||||
base.rstrip("/") + "/readyz", timeout=5
|
||||
)
|
||||
except HTTPError as exc:
|
||||
response = exc
|
||||
with response:
|
||||
raw = response.read(16385)
|
||||
if len(raw) > 16384:
|
||||
raise ValueError("oversized status")
|
||||
return json.loads(raw)
|
||||
|
||||
|
||||
def check_services(oidc, delivery):
|
||||
def safe(call):
|
||||
try:
|
||||
return call()
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
sign = pool.submit(
|
||||
safe, lambda: sign_in_status(oidc.backend_url) if oidc else {}
|
||||
)
|
||||
mail = pool.submit(
|
||||
safe,
|
||||
lambda: delivery.mail_status() if hasattr(delivery, "mail_status") else {},
|
||||
)
|
||||
sign, mail = sign.result(), mail.result()
|
||||
values = {}
|
||||
sign_checks = sign.get("checks", []) if isinstance(sign, dict) else []
|
||||
for row in sign_checks if isinstance(sign_checks, list) else []:
|
||||
if isinstance(row, dict):
|
||||
values[row.get("name")] = row.get("status")
|
||||
checks = [
|
||||
(
|
||||
"Directory sign-in",
|
||||
values.get("lldap"),
|
||||
"Sign-in and identity lookup",
|
||||
"Retry the check; if it still fails, ask the identity service owner to restore directory connectivity.",
|
||||
),
|
||||
(
|
||||
"Primary authentication",
|
||||
values.get("authelia"),
|
||||
"Fresh sign-in",
|
||||
"Retry after the authentication service recovers. Existing sessions do not prove new sign-in works.",
|
||||
),
|
||||
(
|
||||
"Authenticator provider",
|
||||
values.get("privacyidea"),
|
||||
"MFA verification and enrollment",
|
||||
"Retry after the MFA service recovers. Do not disable MFA to work around an outage.",
|
||||
),
|
||||
(
|
||||
"Factor-read credential",
|
||||
values.get("factor_reader"),
|
||||
"Enrollment decisions and MFA verification",
|
||||
"Renewal is automatic. A failure can mean expiry, lost permissions or projection failure; ask the credential owner to run the scoped renewal check.",
|
||||
),
|
||||
]
|
||||
statuses = mail.get("checks", {}) if isinstance(mail, dict) else {}
|
||||
if not isinstance(statuses, dict):
|
||||
statuses = {}
|
||||
checks += [
|
||||
(
|
||||
"Mail record store",
|
||||
statuses.get("database"),
|
||||
"Durable delivery tracking",
|
||||
"Restore the mail store before resuming delivery.",
|
||||
),
|
||||
(
|
||||
"SMTP authentication",
|
||||
statuses.get("smtp_authentication"),
|
||||
"Invitation and verification mail",
|
||||
"Check the configured mail lane. Use assisted password setup when the person cannot receive email.",
|
||||
),
|
||||
]
|
||||
rows = ""
|
||||
for name, status, impact, recovery in checks:
|
||||
label = {
|
||||
"ok": "Check passed",
|
||||
"failed": "Unavailable",
|
||||
"unconfigured": "Not configured",
|
||||
}.get(status, "Unknown — check unavailable")
|
||||
rows += (
|
||||
"<tr><td>"
|
||||
+ escape(name)
|
||||
+ "</td><td>"
|
||||
+ escape(label)
|
||||
+ "</td><td>"
|
||||
+ escape(impact)
|
||||
+ "</td><td>"
|
||||
+ escape(recovery)
|
||||
+ "</td></tr>"
|
||||
)
|
||||
return (
|
||||
"<section><h2>Checked service state</h2><p>Checked at "
|
||||
+ escape(datetime.now(timezone.utc).isoformat(timespec="seconds"))
|
||||
+ ". SMTP authentication may be cached for 30 seconds. These checks send no email and do not prove inbox receipt.</p><table><thead><tr><th>Component</th><th>State</th><th>Affected journeys</th><th>Recovery</th></tr></thead><tbody>"
|
||||
+ rows
|
||||
+ '</tbody></table><p><a href="/platform/operations">Check services again</a></p></section>'
|
||||
)
|
||||
|
||||
|
||||
def selected_mail_status(delivery, event):
|
||||
if event.event_type not in {"family_member.invited", "family_invitation.resent"}:
|
||||
return ""
|
||||
try:
|
||||
receipt = delivery.mail_delivery_status(event.event_id)
|
||||
state = receipt.get("state") if isinstance(receipt, dict) else None
|
||||
except Exception:
|
||||
state = None
|
||||
detail = {
|
||||
"provider_accepted": "The mail provider accepted this event. This does not prove inbox receipt. Retrying this same record reconciles the remaining event delivery without submitting another invitation.",
|
||||
"unknown": "The SMTP outcome is uncertain. Automatic resending is blocked. Ask the mail service owner to reconcile the provider record using this delivery ID; use assisted password setup if access is urgent.",
|
||||
"failed": "The previous mail attempt definitely failed. Review a delivery attempt to retry this same record.",
|
||||
"not_found": "No mail submission is recorded. Review a delivery attempt to submit this selected record.",
|
||||
"unconfigured": "The mail lane is not configured. Use assisted password setup or restore the mail lane.",
|
||||
}.get(
|
||||
state,
|
||||
"Mail evidence is unavailable. Check the mail service and retry this status check; do not infer that a message was sent or received.",
|
||||
)
|
||||
return (
|
||||
"<section><h2>Selected mail evidence</h2><p>"
|
||||
+ escape(detail)
|
||||
+ "</p></section>"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue