Skip to content

Commit f86bdd2

Browse files
authored
Feature/email escalation acknowledgement (#25)
* Use text columns for task context * Clarify production product goal in project docs * feat: add escalation acknowledgement backend * test: cover escalation acknowledgement * feat: add acknowledgement confirmation page * docs: mark task context limit ready * fix: patch container scan vulnerabilities
1 parent ef282da commit f86bdd2

35 files changed

Lines changed: 851 additions & 80 deletions

‎.env.example‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH=true
2323
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE=true
2424
SPRING_MAIL_PROPERTIES_MAIL_SMTP_SSL_ENABLE=false
2525
ALERTOPS_EMAIL_FROM=
26+
ALERTOPS_UI_BASE_URL=http://localhost:5173
27+
ALERTOPS_ACKNOWLEDGEMENT_TTL=72h
2628

2729
APP_HOST_PORT=8096
2830
ALERTOPS_CORS_ALLOWED_ORIGINS=http://localhost:5173

‎AGENTS.md‎

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,18 @@
11
# Agent instructions for AlertOps
22

3-
These instructions apply to work in this repository. For UI work, read [docs/ui-implementation-plan.md](docs/ui-implementation-plan.md) before editing. Treat that plan as the implementation checklist and this file as the working rules. Check the current Java controllers and DTOs before relying on an API contract, since the backend can change.
3+
These rules apply to all work in this repository: backend, frontend, database, configuration, and documentation.
44

5-
## Project goal
6-
7-
Build a small UI that demonstrates the real AlertOps workflow and its engineering: team scoped access, ordered flow nodes, delayed queue execution, durable state, and restart recovery. A reviewer should be able to register, choose a team, create a task and flow, start an escalation, and inspect its real state without using curl or the database.
8-
9-
The UI must build and deploy independently from Spring Boot. Put it in `ui/`; do not bundle it into the Java application or require a UI server at runtime.
10-
11-
## Implementation order
12-
13-
Follow the numbered tickets and acceptance checks in the UI plan. In particular:
5+
## Role
146

15-
1. Resolve the backend contract gates that block a working browser demo: successful escalation creation status, safe registration response, and cross origin access for the deployed UI.
16-
2. Scaffold the React app and a single typed API client; implement auth and team selection before team scoped pages.
17-
3. Implement tasks, flows, ordered nodes, then escalations and their saved execution states.
18-
4. Enable node reorder only after the backend's ownership, version, and ordering behavior is verified.
19-
5. Run the end to end demo journey and document independent UI deployment.
7+
Act as a software engineer collaborating with the project owner. Read the relevant existing code and understand the requested behavior before making changes.
208

21-
If a gate is still open, leave its UI control unavailable with an accurate explanation. Do not work around a broken API by treating an error response as success or by inventing local status.
22-
23-
## Stack and code rules
24-
25-
- Use React, TypeScript, Vite, Tailwind CSS, React Router, TanStack Query, and native `fetch` as specified in the plan. Add another dependency only when it removes substantial code or complexity.
26-
- Keep API calls and response normalization in `ui/src/api/`. Keep query hooks near their feature and presentation components free of backend response quirks.
27-
- Use small files with descriptive names. Prefer a numbered CSS timeline for flow nodes over a graph editor. Use controlled inputs and native form validation for the small forms.
28-
- Make all server data team scoped in query keys. Clear cached team data when switching teams or logging out.
29-
- Store the active demo JWT in `sessionStorage`; attach it as a bearer token. Treat the server as the authority for access and status. Never put secrets in `VITE_` variables or committed frontend files.
30-
- Handle loading, empty, error, and retry states. Disable repeated mutation submissions. Show backend errors in plain language without exposing sensitive response bodies.
31-
- Do not add UI for placeholder team deletion, member role management, invite delivery, or retry settings until working backend contracts exist.
32-
- Label notifications as simulated while `Notification#sendEmail` only logs output. Do not fabricate delivery receipts, queue metrics, node progress, or recovery events.
33-
34-
## Backend changes
9+
## Project goal
3510

36-
Keep backend edits narrow and tied to the UI plan. Add or update focused tests when changing response contracts, authorization, execution state reads, or node ordering. The proposed execution state endpoint must verify the selected team and return a DTO, ordered by node position. Check authorization on flow node reads, creation, and reorder before exposing these controls.
11+
Build AlertOps into a production-ready alerting and escalation product for real users. Treat the backend, UI, security, reliability, and operations as parts of the same product. Use [the product launch checklist](docs/product-launch-readiness.md) for current release priorities.
3712

38-
## Verification before finishing
13+
## Changes
3914

40-
- Run the relevant backend tests for any Java changes.
41-
- Run UI type checking and production build; verify `ui/dist` is produced.
42-
- With the API running, exercise registration → login → team selection → task → flow with nodes → escalation create/start → execution detail. Confirm data survives reload and team switching never shows another team's data.
43-
- Verify the deployed UI can reach the independently deployed API, browser preflight succeeds, and direct UI route reloads reach `index.html`.
44-
- Report what was changed, which checks ran, and any unmet backend gate. Do not describe an untested path as verified.
15+
- Use clear, descriptive names for variables, methods, and classes.
16+
- Choose the simplest solution that meets the request. Keep the change focused and reuse existing code where practical.
17+
- Add files, abstractions, or dependencies only when they are needed and can be justified.
18+
- Review the final diff for unrelated changes and explain any important limitation.

‎Dockerfile‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@ COPY pom.xml ./
66
COPY src ./src
77
RUN --mount=type=cache,target=/root/.m2 mvn -DskipTests clean package
88

9-
FROM eclipse-temurin:17.0.19_10-jre-jammy@sha256:475d8e96b4b2bfe08999e5e854755c773af1581acdf959a4545d88f0696a2339
9+
FROM eclipse-temurin:17.0.20.1_1-jre-jammy@sha256:0776d74b60f5a0bf34d1dc8ee339bbb21d7243c9d50b134c8de69a8e822e9ede
1010
WORKDIR /app
1111

12+
# The pinned base can lag behind Ubuntu security updates.
13+
RUN apt-get update \
14+
&& apt-get install --yes --no-install-recommends \
15+
libssl3=3.0.2-0ubuntu1.30 \
16+
openssl=3.0.2-0ubuntu1.30 \
17+
&& rm -rf /var/lib/apt/lists/*
18+
1219
RUN addgroup --system spring && adduser --system --ingroup spring spring
1320
COPY --from=build /app/target/*.jar /app/app.jar
1421

‎README.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AlertOps
22

3-
**AlertOps** is a multi-tenant backend alerting and escalation platform designed to model real-world incident flows—where alerts are delayed, retried, escalated, and recovered reliably under failure.
3+
**AlertOps** is a multi-tenant alerting and escalation product being built for production use. It coordinates incident response across teams, with delayed steps, retries, and recovery after failures.
44

55
The system treats **time, retries, and ownership** as first-class concerns, using queue-driven execution instead of cron-based scheduling or in-memory timers.
66

@@ -100,9 +100,9 @@ AlertOps optimizes for **reliability and determinism**, not minimal code.
100100
## What This Project Is (and Is Not)
101101

102102
### It Is
103-
- A backend-focused system design project
104-
- A practical exploration of alerting and escalation workflows
105-
- A demonstration of queue-based, time-aware execution
103+
- A product for teams to configure and run incident escalation workflows
104+
- An open-source service being prepared for real users
105+
- A backend and UI that together support the full response workflow
106106

107107
### It Is Not
108108
- A UI-centric application
@@ -164,6 +164,8 @@ SMTP authentication and STARTTLS default to enabled. For an implicit TLS server
164164

165165
New registrations receive a one-time email verification link before they can use team features. The link points to `ALERTOPS_UI_BASE_URL` and expires after 30 minutes by default; change that window with `ALERTOPS_EMAIL_VERIFICATION_TTL`.
166166

167+
Escalation emails include an acknowledgement button scoped to the assigned recipient and run. Opening the email link only previews the run; the recipient must confirm on the page to stop later steps. These links expire after 72 hours by default; configure `ALERTOPS_ACKNOWLEDGEMENT_TTL` to change the lifetime. Keep `ALERTOPS_UI_BASE_URL` set to the deployed UI origin so email links open the right application.
168+
167169
Team invitation links point to the UI origin configured with `ALERTOPS_UI_BASE_URL` (defaults to `http://localhost:5173` for local development). Set this to the deployed UI origin when sending invitations outside local development.
168170

169171
If SMTP is not configured or the SMTP server rejects a send, the notification is recorded as failed and follows the node's configured retry/fallback path. A node marked `SENT` means the SMTP server accepted the message; AlertOps does not claim that it reached the recipient's inbox.

‎docs/product-launch-readiness.md‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,19 @@ The scheduling integration tests cover publication after commit, no publication
3232

3333
### 4. Align task description limits with the database
3434

35-
- [ ] The task form accepts 1,000 characters, but the initial database migration gives `tasks.description` and `flow_execution_state.task_details` only 255 characters. Add a new Flyway migration and matching API limits so a valid form or webhook payload cannot fail later when a run starts.
35+
- [x] The task form accepts up to 1,000 characters, and migration V3 changes both `tasks.description` and `flow_execution_state.task_details` to PostgreSQL `TEXT`. The saved task context can therefore survive task creation, run start, and email creation without the original 255-character database limit.
3636
- **Done when:** the allowed maximum length survives task creation, run start, saved state reads, and email creation.
37-
- **Evidence:** [task form](../ui/src/features/tasks/TasksPage.tsx), [initial schema](../src/main/resources/db/migration/V1__create_initial_schema.sql).
37+
- **Evidence:** [task form](../ui/src/features/tasks/TasksPage.tsx), [task entity](../src/main/java/com/alertops/task/model/Task.java), [context migration](../src/main/resources/db/migration/V3__use_text_for_task_context.sql).
3838

3939
## Then build these two product features
4040

4141
### Feature 1: Acknowledge an escalation from email
4242

43-
- [ ] Put an **Acknowledge** button in every escalation email. It opens a confirmation page for that specific run and recipient. The confirmation action records acknowledgement and stops later steps. The link needs a scoped, expiring token and must be safe to use more than once.
44-
- [ ] Make the confirmation a deliberate `POST`; opening the link with `GET` must not acknowledge anything, since mail scanners can open links automatically. The worker must recheck the saved run status before a later send. Show the acknowledged status, person, and time in the run detail UI.
45-
- **Done when:** acknowledgement before the next step prevents that step from sending; an expired or invalid link cannot acknowledge; repeated clicks return the same result; a completed run cannot be changed incorrectly. Cover the race between acknowledgement and an in-flight send.
46-
- **Evidence of current gap:** [run API](../src/main/java/com/alertops/flow_execution_engine/controller/EscalationController.java), [email template](../src/main/java/com/alertops/messaging/Notification.java).
43+
- [x] Put an **Acknowledge** button in every escalation email. It opens a confirmation page for that specific run and recipient. The confirmation action records acknowledgement and stops later steps. The link uses a recipient-scoped, expiring token; only its hash is stored.
44+
- [x] Make confirmation a deliberate `POST`; the email link opens a page and a read-only preview first. The worker and acknowledgement action lock the run row while making their state changes. If a send is already in progress, it finishes before acknowledgement is accepted; ready later steps then see the completed run and are ignored. The run detail shows who acknowledged and when.
45+
- [x] Focused tests cover valid, expired, repeated, invalid-state, and wrong-recipient acknowledgement cases; anonymous API access is limited to the token endpoints and `GET` cannot confirm. A PostgreSQL integration case exercises acknowledgement while a send is in flight.
46+
- **Done when:** acknowledgement before the next step prevents that step from sending; expired or invalid links cannot acknowledge; repeated clicks return the saved result; completed runs cannot be changed incorrectly; acknowledgement and in-flight sends are serialized.
47+
- **Evidence:** [acknowledgement API](../src/main/java/com/alertops/flow_execution_engine/controller/EscalationAcknowledgementController.java), [acknowledgement service](../src/main/java/com/alertops/flow_execution_engine/service/EscalationAcknowledgementService.java), [email template](../src/main/java/com/alertops/messaging/Notification.java), [confirmation page](../ui/src/features/escalations/AcknowledgeEscalationPage.tsx), [database migration](../src/main/resources/db/migration/V4__add_escalation_acknowledgement.sql).
4748

4849
### Feature 2: Create and start an escalation through a webhook
4950

‎docs/ui-implementation-plan.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Goal
44

5-
Build a small, independently deployed web app that lets a reviewer run the real AlertOps workflow: sign in, choose a team, create a task, configure an ordered escalation flow, start an escalation, and inspect its progress. Keep the interface useful for operating the demo and for explaining the engineering behind it. Every displayed status must come from the API; explanatory diagrams may describe the architecture but must be labeled as explanations.
5+
Build an independently deployed web app that lets users run the AlertOps workflow: sign in, choose a team, create a task, configure an ordered escalation flow, start an escalation, and inspect its progress. This document records the initial UI implementation scope; the project goal is a production-ready product, with current release priorities in [the product launch checklist](product-launch-readiness.md). Every displayed status must come from the API; explanatory diagrams may describe the architecture but must be labeled as explanations.
66

77
## Stack and deployment boundary
88

@@ -45,15 +45,15 @@ Configure Tailwind as a Vite plugin and import it in the root CSS. Set `VITE_API
4545
| List / read escalations | `GET /api/v1/escalation/all?page=0&size=20&sortBy=createdAt&sortDir=desc`, `GET /api/v1/escalation?escalationId=...` | Escalation includes `id,name,taskId,flowId,status,resolutionType,createdAt`. |
4646
| Create / start escalation | `POST /api/v1/escalation/create` with `{escalationName,taskId,flowId}`; `POST /api/v1/escalation/start` with `{escalationId}` | Creation currently returns HTTP 400 despite persisting. Fix before enabling create. Start requires at least one flow node. |
4747

48-
The initial auth token has no team ID. Team selection issues a new JWT with team ID; this is a required step before task, flow, and escalation calls. Store only the active token in `sessionStorage` for this demo, hydrate it on reload, and clear it on logout and unauthorized responses. Keep the active team ID in the route. On team switch, request a new team token and clear TanStack Query's cache so data from the previous team cannot appear. Do not decode a token as the source of permissions; the API decides access.
48+
The initial auth token has no team ID. Team selection issues a new JWT with team ID; this is a required step before task, flow, and escalation calls. Store only the active token in `sessionStorage`, hydrate it on reload, and clear it on logout and unauthorized responses. Keep the active team ID in the route. On team switch, request a new team token and clear TanStack Query's cache so data from the previous team cannot appear. Do not decode a token as the source of permissions; the API decides access.
4949

5050
## Backend gates to finish before calling the UI complete
5151

5252
1. **Correct the create response:** `EscalationController#createEsclation` saves and then returns HTTP 400. Return 201/200 with the saved escalation; add a controller test. The UI must treat non-2xx as failure and must not parse the 400 body as a success.
5353
2. **Make registration safe:** `UserController#registerUser` returns the `User` entity, including its password hash. Return a DTO with only public fields. Never show the current response body in the UI.
5454
3. **Enable browser access:** there is no CORS configuration in `SecurityConfig`. Add an allowlist for the separately deployed UI origin and allow `Authorization`, `Content-Type`, and the used methods, including `OPTIONS`. Keep the origin configurable. The local Vite proxy only solves local development.
5555
4. **Expose execution evidence:** add `GET /api/v1/escalation/{id}/execution-states`, scoped by the selected team, ordered by node position. Return a small DTO: `nodeId`, `userEmail`, `executionState`, `notificationState`, `sendAttemptCount`, `createdAt`, `updatedAt`. This lets the detail page show the actual durable queue workflow. Return 404 when the escalation is outside the team. Do not expose internal entities directly.
56-
5. **Secure flow node reads and writes:** `getNodesByFlowId`, node creation, and reorder currently look up a flow/node by ID without consistently checking the selected team's ownership. Enforce team membership on all three before exposing the controls in a public demo.
56+
5. **Secure flow node reads and writes:** `getNodesByFlowId`, node creation, and reorder currently look up a flow/node by ID without consistently checking the selected team's ownership. Enforce team membership on all three before exposing the controls to users.
5757
6. **Make reorder reliable before enabling it:** verify version increments, moving to first position, and reindexing. The current `newPosition.compareTo(BigInteger.valueOf(50)) < 50` condition is always true for a `compareTo` result. Add focused service tests for order and stale version handling.
5858
7. **Email delivery:** `Notification#sendEmail` sends a styled HTML message rendered from Markdown, with a Markdown plain-text fallback, through Spring Mail when SMTP is configured. A missing SMTP configuration or send error returns failure so the saved node state follows the existing retry/failure path. The UI should explain that `SENT` means accepted by SMTP, not confirmed delivery. Configurable retry rules are future backend work; node creation has no retry settings today.
5959

@@ -69,7 +69,7 @@ The follow-on backend and UI tickets are in [Team management UI task list](team-
6969
| `/register`, `/login` | Authentication | Simple forms with visible API errors; unverified users are sent to the verification screen after login. |
7070
| `/verify-email?token=...` | Email verification | Confirm the one-time link with an explicit `POST`, request another link for the entered email, and continue to teams or an invitation. |
7171
| `/teams` | Team picker | List teams, create team, select team. After create, select the new team automatically. |
72-
| `/app/:teamId` | Team overview | Guided four-step demo: task → flow → nodes → escalation. Show counts only if API results are loaded. |
72+
| `/app/:teamId` | Team overview | Guided setup: task → flow → nodes → escalation. Show counts only if API results are loaded. |
7373
| `/app/:teamId/members` | Members | Team-scoped member directory and owner/admin invitation form. The server enforces invitation permission and SMTP delivery. |
7474
| `/join?token=...` | Join team | Preview the invitation, sign in or register with the invited address, accept it, then select the team. |
7575
| `/app/:teamId/tasks` | Tasks | Table and create form; task detail text used in notifications. |
@@ -124,7 +124,7 @@ Each ticket is a reviewable commit or small PR. Finish its acceptance checks bef
124124

125125
## Definition of done
126126

127-
- A new reviewer can complete the full demo from a fresh account without using curl or the database.
127+
- A new user can complete the initial workflow from a fresh account without using curl or the database.
128128
- The UI is served from its own static deployment and calls the API through a documented public origin or edge route.
129129
- All live statuses and node progress are sourced from the API; email status wording reflects SMTP acceptance rather than inbox delivery.
130130
- Tenant switching does not mix data, and the backend checks team ownership for every exposed flow operation.

0 commit comments

Comments
 (0)