Phase 9: Admin completion, reconcile, notifications, integrity, backups
Admin: Departments (name and stations; add shows the three groups IT must create), Profiles (welder stamp, station limits, active) with a CSV import that previews and applies only when clean, Checklists (weld and plain), Security (session, lockout and rate-limit settings with bounds, alerts for the last 24 hours, the integrity result), Access log with filters and a guarded CSV, Who is in, Stamp check, Notifications, and the employeeID flags. Every Admin change is audited. Worker: ad-reconcile (nightly and on "refresh from AD now"), session-refresh (15 minutes), integrity (seals, asset hashes, missing and orphan files, the audit chain), backup-check, notify-send and notify-digest. Notifications route per event type to AD groups, users, mailboxes or the department Supervisor group, immediately or in a daily digest, over STARTTLS verified against the PESCO CA, with dry run on the test stack. Backups: scripts/backup/ps-backup.sh and ps-restore.sh with a systemd timer at 02:00 America/Denver. Restore rehearsed on the test stack 2026-10-21. CI: pip-audit and npm audit. pypdf 6.21.1 and certifi 2026.10.19. Migration 0010. Closes the app part of I6, M3, O1, O2 and S3. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"""The notification event types. One routing rule per type lives in `notification_rules`.
|
||||
|
||||
A security type cannot be opted out of on Account settings, and the Admin cannot exclude anyone from it either.
|
||||
A department-scoped type carries the department of its packet, so it can go to that department's Supervisor group.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EventType:
|
||||
key: str
|
||||
label: str
|
||||
description: str
|
||||
security: bool = False
|
||||
dept_scoped: bool = False
|
||||
param: str | None = None # "hours" or "days"
|
||||
param_default: int | None = None
|
||||
|
||||
|
||||
_TYPES = (
|
||||
EventType(
|
||||
"security_alert",
|
||||
"Security alert",
|
||||
"An account cooldown started, a PIN was disabled after five wrong tries, or a session ended after three"
|
||||
" wrong unlock or signature PINs.",
|
||||
security=True,
|
||||
),
|
||||
EventType(
|
||||
"signin_burst",
|
||||
"Sign-in burst",
|
||||
"The per-address sign-in limit tripped, or many sign-ins failed across accounts in five minutes.",
|
||||
security=True,
|
||||
),
|
||||
EventType(
|
||||
"integrity_failure",
|
||||
"Integrity check failure",
|
||||
"The nightly integrity check found a broken seal, a changed or missing file, or a break in the audit chain.",
|
||||
security=True,
|
||||
),
|
||||
EventType("feed_failed", "Feed run failed", "A VISUAL feed or drawing lookup run failed."),
|
||||
EventType("feed_degraded", "Feed run degraded", "A VISUAL feed run finished with errors."),
|
||||
EventType(
|
||||
"drawing_failing",
|
||||
"Drawing lookup failing",
|
||||
"Drawing lookups in M-Files have failed for more than the set number of hours.",
|
||||
param="hours",
|
||||
param_default=24,
|
||||
),
|
||||
EventType(
|
||||
"ad_reconcile_report",
|
||||
"AD reconcile report",
|
||||
"The nightly AD reconcile added or deactivated people, found extra groups, or flagged employee IDs.",
|
||||
),
|
||||
EventType("group_missing", "Group missing from the OU", "A mapped PS group is not in the PacketStation OU."),
|
||||
EventType("backup_stale", "Backup failed or stale", "The newest database backup is missing, empty, old or failed."),
|
||||
EventType(
|
||||
"co_waiting",
|
||||
"Change order waiting",
|
||||
"A change order has waited for review for more than the set number of days.",
|
||||
dept_scoped=True,
|
||||
param="days",
|
||||
param_default=3,
|
||||
),
|
||||
EventType(
|
||||
"qc_reject",
|
||||
"Hold-point or ITP reject",
|
||||
"Q.C. rejected a packet, or an ITP line was rejected.",
|
||||
dept_scoped=True,
|
||||
),
|
||||
EventType("version_deployed", "Version deployed", "The worker started on a new build."),
|
||||
EventType("assets_quota", "Assets storage warning", "The assets storage passed its warning threshold."),
|
||||
)
|
||||
|
||||
EVENT_TYPES: dict[str, EventType] = {t.key: t for t in _TYPES}
|
||||
SECURITY_TYPES: frozenset[str] = frozenset(t.key for t in _TYPES if t.security)
|
||||
|
||||
|
||||
def is_known(event_type: str) -> bool:
|
||||
return event_type in EVENT_TYPES
|
||||
Reference in New Issue
Block a user