The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
7.7 KiB
Add platform alerting to a product
Use this path when a product needs alerting. Compose the existing platform before adding shared abstractions.
1. Define the product contract
Write down the product-specific inputs before coding:
- What data is evaluated and what constitutes breached, clear, inconclusive, transient error, and permanent error?
- Which lifecycle states and control-plane actions are user-visible?
- Is evaluation fixed-cadence or calendar-aligned?
- Which notification event kinds exist, such as firing, resolved, errored, or broken?
- Which destination types are allowed?
- Does the product need InsightsFunction delivery, email, or both?
- What fields belong in check history and detail pages?
Do not add a new AlertPolicy option merely because the product might need it. First confirm an existing policy cannot express the behavior and that the difference is intentional.
2. Add product persistence and isolation
The product owns its alert configuration and check-history models.
- Every tenant-data model needs
team_idand fail-closed scoping. - Store the fields required to construct
AlertSnapshot, plus product evaluation and scheduling fields. - Keep writes that must agree in a narrow
transaction.atomic()block. - Do not perform notification delivery inside the transaction.
Invoke /django-migrations for model changes.
3. Build the lifecycle adapter
Create one product state-machine module, following products/logs/backend/alert_state_machine.py.
- Select or define the product's
AlertPolicy. - Convert the model and recent history into the shared snapshot.
- Convert the domain evaluation into
CheckInput. - Call
evaluate_alert_check(...)orevaluate_alert_failure(...). - Apply every shared outcome through one product-owned
apply_outcomefunction. - Route control-plane actions through the shared helpers and the same mutator.
- Extend the alert-state semgrep rule to cover the product backend and exclude only its mutator and tests.
Do not mutate state or consecutive_failures elsewhere.
4. Define notification content and destinations
Create a thin product module like products/logs/backend/alert_destinations.py.
- Define one
EventKindSpecper notification action. - Keep event IDs and properties stable because InsightsFunctions filter and render from them.
- Include every template property in the internal event payload.
- Define the product's allowed
DestinationTypevalues explicitly. - Validate, build, create, and delete destination InsightsFunctions through
products.alerts.backend.facade.api. - Scope deletion with the team, alert ID, and allowed event IDs.
Shared support for a destination does not opt the product into it.
5. Make delivery transactional with lifecycle state
For InsightsFunction destinations:
- Evaluate alerts and retain each pre-check snapshot or outcome needed for rollback.
- Produce internal events and retain each
ProduceResult. - Flush once after producing the batch.
- Check producer acknowledgement for each result with
alert_internal_event_delivered(...). - Restore delivery-dependent outcomes for unacknowledged results before persistence.
- Persist acknowledged outcomes, check history, and product scheduling according to the product contract.
Producer acknowledgement does not confirm downstream InsightsFunction execution or final destination delivery. Logs is the reference for reevaluating on the next cadence after an internal event is not acknowledged.
For email, call send_alert_email(...) through the facade. The product must choose authorized recipients, a stable campaign key, subject, template, and context. Decide explicitly whether an email failure blocks a lifecycle transition or is recorded separately.
6. Add scheduling and due selection
Import the product-facing scheduling contract directly from its implementation module:
from products.alerts.backend.scheduling import (
CalendarInterval,
advance_next_check_at,
compute_shard_offset_seconds,
is_utc_datetime_blocked,
is_weekend,
next_calendar_check_time,
parse_blocked_windows_tuples,
scan_next_unblocked_utc,
to_calendar_interval,
validate_and_normalize_schedule_restriction,
)
For fixed-minute checks:
- Reuse
compute_shard_offset_seconds(...)andadvance_next_check_at(...). - Wrap the shard function if the product scheduler interval differs from the shared default.
- Keep one stable shard calculation across create, update, and evaluation paths.
- Implement and test a product-specific due predicate with team scoping, enabled state, broken state, snooze state, and
next_check_atbehavior.
For calendar-aligned checks:
- Convert product interval values with
to_calendar_interval(...), then usenext_calendar_check_time(...)with the team's IANA timezone. - Validate stored quiet-hour payloads with
validate_and_normalize_schedule_restriction(...)and parse them withparse_blocked_windows_tuples(...). - Use
scan_next_unblocked_utc(...),is_utc_datetime_blocked(...), andis_weekend(...)instead of recreating timezone or DST logic. - Keep model access, API error translation, bounded-retry logging, and
next_check_atpersistence in the product adapter.
7. Add product orchestration
The product owns its evaluator, history, due query, batching, Temporal or Celery orchestration, retries, and metrics.
Avoid large Temporal payloads. Pass alert IDs and references, then load data inside activities. Keep notification dispatch out of database transactions.
8. Add the frontend
Read frontend-alerting.md, then choose the appropriate shared path.
Use AlertWizard when the product creates InsightsFunction-backed alerts from trigger and destination templates.
- Configure a unique
logicKey. - Supply supported sub-template IDs, triggers, and destinations.
- Add product-specific configuration outside the shared wizard when it is not a reusable InsightsFunction input.
- Keep the traditional InsightsFunction editor as the advanced fallback when the product already supports it.
Use the shared product alert components in products/alerts/frontend/components/ when the product owns an alert configuration, evaluation rules, lifecycle state, destinations, and history.
- Compose the form with
AlertEditor,AlertEditorFormDetails, andAlertEditorSection. - Use the shared definition, schedule, next-evaluation, timezone, and advanced-options components for presentation.
- Normalize destinations into
AlertNotificationDestinationEditorview models while keeping supported types, payloads, grouping, and persistence product-owned. - Normalize numeric evaluations and current bounds into
AlertEvaluationHistoryChart; keep the product history table or event timeline beside it. - Keep container sizing in the product modal, scene, or embedded section.
A product may use either path or both. Ensure every network-backed action has loading and double-submit protection.
9. Verify the boundaries
Add the lowest-level tests that cover real regressions:
- Shared state-machine decision cases used by the product.
- Product snapshot and single-mutator behavior.
- Destination validation, generated configs, ownership-safe deletion, and event property completeness.
- Delivery success, enqueue failure, flush failure, and rollback before save.
- Due predicate and cadence/shard behavior.
- API schemas and tenant isolation.
- Wizard logic and product entry points when frontend alert creation is added.
Invoke /writing-tests when adding or substantially changing tests. Also invoke /improving-drf-endpoints for serializer or viewset changes, /adopting-generated-api-types when frontend API types are involved, and /writing-kea-logics when adding or changing alert kea logic.