-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add watchtower route with alert digest #228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
iamasx
wants to merge
3
commits into
main
Choose a base branch
from
feat/206
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| export const watchtowerOverview = { | ||
| eyebrow: "Watchtower route / Alert digest", | ||
| title: "Scan alert drift, service health, and operator notes before the next rotation.", | ||
| description: | ||
| "The watchtower route compresses high-signal alerts, short health summaries, and operator context into a single handoff view for the next duty lead.", | ||
| digestWindow: "Digest window · 18:00 to 22:00 UTC", | ||
| focusNote: | ||
| "Two critical digests still need explicit operator approval before automation can widen traffic again.", | ||
| reviewWindow: "22:20 UTC", | ||
| shiftLead: "Shift lead · N. Alvarez", | ||
| }; | ||
|
|
||
| export const watchtowerAlerts = [ | ||
| { | ||
| severity: "critical" as const, | ||
| service: "Orbital relay mesh", | ||
| title: "Fallback relays are saturating the north relay spine.", | ||
| scope: "21 relays / 3 regions", | ||
| owner: "M. Chen", | ||
| digest: | ||
| "Queue pressure crossed the auto-shed threshold twice after the west mesh reroute, so traffic is holding on manual trims until the next clean sample.", | ||
| nextStep: "Approve a 12-minute route trim before 22:20 UTC.", | ||
| affectedSystems: ["mesh-east-2", "north-spine cache", "fallback batch 4"], | ||
| }, | ||
| { | ||
| severity: "elevated" as const, | ||
| service: "Payload scanner lane", | ||
| title: "Scanner calibration drift is slowing parcel verification.", | ||
| scope: "4 scanner cells", | ||
| owner: "R. Singh", | ||
| digest: | ||
| "Verification retries climbed to 8.3% after the last firmware pull, which is still within failover range but now exceeding the shift target.", | ||
| nextStep: "Hold inbound surges until calibration pass C finishes.", | ||
| affectedSystems: ["scan-west-4", "handoff queue", "firmware mirror"], | ||
| }, | ||
| { | ||
| severity: "critical" as const, | ||
| service: "Manifest handoff", | ||
| title: "Manifest receipts are arriving behind the operator review window.", | ||
| scope: "2 late cutover lanes", | ||
| owner: "L. Ortega", | ||
| digest: | ||
| "Late receipts are not dropping traffic, but they are now compressing the audit review window enough to threaten the next automated closeout.", | ||
| nextStep: "Keep closeout manual for the next two digest cycles.", | ||
| affectedSystems: ["receipt stream", "lane-c7", "audit closeout bot"], | ||
| }, | ||
| ]; | ||
|
|
||
| export const watchtowerHealthSummaries = [ | ||
| { | ||
| tone: "risk" as const, | ||
| title: "Relay mesh health", | ||
| signal: "Capacity margin 68%", | ||
| owner: "Transit network", | ||
| summary: | ||
| "Manual trims are keeping the mesh stable, but the reserve margin is now thin enough that another burst would force wider shedding.", | ||
| recommendation: "Keep bulk traffic capped until relay drift clears.", | ||
| }, | ||
| { | ||
| tone: "watch" as const, | ||
| title: "Verification lane health", | ||
| signal: "Retry rate 8.3%", | ||
| owner: "Dock systems", | ||
| summary: | ||
| "Scanner throughput is still acceptable for current volume, although the current retry profile will create queue drag if inbound demand spikes.", | ||
| recommendation: "Finish calibration pass C before lifting intake caps.", | ||
| }, | ||
| { | ||
| tone: "stable" as const, | ||
| title: "Operator coverage health", | ||
| signal: "Coverage confidence 94%", | ||
| owner: "Shift command", | ||
| summary: | ||
| "Crew overlap and specialist coverage remain strong enough to absorb manual interventions without extending the next handoff.", | ||
| recommendation: "Use the reserve reviewer to shadow critical receipts only.", | ||
| }, | ||
| ]; | ||
|
|
||
| export const watchtowerOperatorNotes = [ | ||
| { | ||
| status: "tracking" as const, | ||
| channel: "Ops chat", | ||
| author: "N. Alvarez", | ||
| time: "21:42 UTC", | ||
| note: "Keep the north relay trim manual until queue depth stays below 68% for two consecutive samples.", | ||
| followUp: "Review after the 22:10 UTC digest snapshot.", | ||
| }, | ||
| { | ||
| status: "queued" as const, | ||
| channel: "Dock review", | ||
| author: "R. Singh", | ||
| time: "21:37 UTC", | ||
| note: "Bundle scanner drift updates into one operator post so the incoming shift does not chase duplicate recalibration notes.", | ||
| followUp: "Publish a single rollup before handoff.", | ||
| }, | ||
| { | ||
| status: "closed" as const, | ||
| channel: "Audit lane", | ||
| author: "L. Ortega", | ||
| time: "21:31 UTC", | ||
| note: "Receipt lag is manageable as long as the closeout bot stays paused and lane C7 remains on a human review path.", | ||
| followUp: "Confirm pause state in the 22:20 UTC review.", | ||
| }, | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| import { render, screen, within } from "@testing-library/react"; | ||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import { | ||
| watchtowerAlerts, | ||
| watchtowerHealthSummaries, | ||
| watchtowerOperatorNotes, | ||
| watchtowerOverview, | ||
| } from "./_data/watchtower-data"; | ||
| import WatchtowerPage from "./page"; | ||
|
|
||
| describe("WatchtowerPage", () => { | ||
| it("renders the route shell headings, pulse copy, and primary actions", () => { | ||
| render(<WatchtowerPage />); | ||
|
|
||
| expect(screen.getByRole("heading", { name: watchtowerOverview.title })).toBeInTheDocument(); | ||
| expect(screen.getByText(watchtowerOverview.digestWindow)).toBeInTheDocument(); | ||
| expect(screen.getByText(watchtowerOverview.shiftLead)).toBeInTheDocument(); | ||
| expect( | ||
| screen.getByRole("heading", { | ||
| name: /alert digest cards built for the next operator handoff/i, | ||
| }), | ||
| ).toBeInTheDocument(); | ||
| expect( | ||
| screen.getByRole("heading", { | ||
| name: /short health summaries for the systems that shape the handoff/i, | ||
| }), | ||
| ).toBeInTheDocument(); | ||
| expect( | ||
| screen.getByRole("link", { name: /jump to alert digest/i }), | ||
| ).toHaveAttribute("href", "#watchtower-digest"); | ||
| expect( | ||
| screen.getByRole("link", { name: /review health summaries/i }), | ||
| ).toHaveAttribute("href", "#watchtower-health"); | ||
| expect( | ||
| screen.getByRole("link", { name: /back to route index/i }), | ||
| ).toHaveAttribute("href", "/"); | ||
| }); | ||
|
|
||
| it("renders every alert digest card with scope, ownership, systems, and next steps", () => { | ||
| render(<WatchtowerPage />); | ||
|
|
||
| const alertList = screen.getByRole("list", { name: /watchtower alert digest cards/i }); | ||
|
|
||
| expect(alertList.querySelectorAll(':scope > [role="listitem"]')).toHaveLength( | ||
| watchtowerAlerts.length, | ||
| ); | ||
|
|
||
| for (const alert of watchtowerAlerts) { | ||
| const card = within(alertList) | ||
| .getByRole("heading", { name: alert.title }) | ||
| .closest('[role="listitem"]'); | ||
|
|
||
| expect(card).toBeTruthy(); | ||
| expect(card?.textContent).toContain(alert.service); | ||
| expect(card?.textContent).toContain(alert.scope); | ||
| expect(card?.textContent).toContain(alert.owner); | ||
| expect(card?.textContent).toContain(alert.nextStep); | ||
| expect(card?.textContent).toContain(alert.affectedSystems[0]); | ||
| } | ||
| }); | ||
|
|
||
| it("renders health summaries with signals, owners, and recommendations", () => { | ||
| render(<WatchtowerPage />); | ||
|
|
||
| const healthSection = screen.getByLabelText(/watchtower health summaries/i); | ||
| const healthList = within(healthSection).getByRole("list", { name: /watchtower health summary cards/i }); | ||
|
|
||
| expect(within(healthList).getAllByRole("listitem")).toHaveLength( | ||
| watchtowerHealthSummaries.length, | ||
| ); | ||
|
|
||
| for (const summary of watchtowerHealthSummaries) { | ||
| expect(within(healthSection).getByText(summary.title)).toBeInTheDocument(); | ||
| expect(within(healthSection).getByText(summary.signal)).toBeInTheDocument(); | ||
| expect(within(healthSection).getByText(summary.owner)).toBeInTheDocument(); | ||
| expect(within(healthSection).getByText(summary.recommendation)).toBeInTheDocument(); | ||
| } | ||
|
|
||
| expect(within(healthSection).getByText("Stable")).toBeInTheDocument(); | ||
| expect(within(healthSection).getByText("Watch")).toBeInTheDocument(); | ||
| expect(within(healthSection).getByText("Risk")).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("renders the operator notes rail with the handoff anchor and every note entry", () => { | ||
| render(<WatchtowerPage />); | ||
|
|
||
| const notesRail = screen.getByLabelText(/operator notes rail/i); | ||
| const noteList = within(notesRail).getByRole("list", { name: /watchtower operator note entries/i }); | ||
|
|
||
| expect(within(notesRail).getByText(watchtowerOverview.focusNote)).toBeInTheDocument(); | ||
| expect( | ||
| within(notesRail).getByText(`Review window · ${watchtowerOverview.reviewWindow}`), | ||
| ).toBeInTheDocument(); | ||
| expect(noteList.querySelectorAll(':scope > [role="listitem"]')).toHaveLength( | ||
| watchtowerOperatorNotes.length, | ||
| ); | ||
|
|
||
| for (const note of watchtowerOperatorNotes) { | ||
| const card = within(noteList) | ||
| .getByText(note.author) | ||
| .closest('[role="listitem"]'); | ||
|
|
||
| expect(card).toBeTruthy(); | ||
| expect(card?.textContent).toContain(note.channel); | ||
| expect(card?.textContent).toContain(note.time); | ||
| expect(card?.textContent).toContain(note.note); | ||
| expect(card?.textContent).toContain(note.followUp); | ||
| } | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test log: add a console statement