Adds Monitoring Setup docs and deploy-scp troubleshooting/debug fixes
Continuous Integration / config (pull_request) Successful in 9s
Continuous Integration / backend-build (pull_request) Successful in 4m27s
Continuous Integration / vulnerability-scan (pull_request) Successful in 4m10s
Continuous Integration / backend-test (pull_request) Canceled after 0s
Continuous Integration / frontend-build (pull_request) Canceled after 0s
Continuous Integration / frontend-test (pull_request) Canceled after 0s
Continuous Integration / frontend-lint (pull_request) Canceled after 0s
Continuous Integration / publish-test (pull_request) Canceled after 0s
Continuous Integration / publish-production (pull_request) Canceled after 0s
Continuous Integration / deploy-test (pull_request) Canceled after 0s
Continuous Integration / deploy-production (pull_request) Canceled after 0s
Continuous Integration / frontend-prepare (pull_request) Canceled after 50s

Monitoring Setup: operations/plans/monitoring-setup-plan.md and
operations/monitoring/monitoring-instructions.md, covering Sentry alert
rules on the security_event tag, UptimeRobot's 6 liveness monitors, and
the two new Umami website entries for the admin SPA.

deployment-instructions.md gains a missing Observability__Environment
host var (without it, both environments would tag Sentry events as
"Production"), the nginx client_max_body_size fix for the 413 seen on
publish-test/production artifact uploads, and two troubleshooting notes
on Gitea Actions re-run behaviour: re-running deploy-test/production
alone loses the run's uploaded artifact, and re-running all jobs on an
existing (rather than a brand new) run can replay stale secrets.

deploy-scp.yaml: step names no longer show literal unresolved
${{ inputs.* }} text (Gitea doesn't interpolate that context in step
names), and a temporary debug step logs PI_MAIN_USERNAME/PASSWORD
length plus a username equality check to diagnose a persistent
Permission denied during the SSH steps, without ever logging the
secret values themselves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FffvxxJp5wG34Ru48GBig
This commit is contained in:
2026-07-29 19:39:44 +02:00
co-authored by Claude Sonnet 5
parent d81168b7a9
commit 56f4f6fe0f
5 changed files with 382 additions and 7 deletions
@@ -0,0 +1,127 @@
# Monitoring Setup Plan
## Context already established (not re-asked)
The observability *stack* and *what it must cover* were already decided in Requirements Analysis and
built during Construction (U4) — this stage is about configuring the real external services and
writing the operational documentation, not re-deciding the approach:
- **Stack**: Sentry (backend `Sentry.AspNetCore` + frontend `@sentry/react`), self-hosted Umami
analytics, UptimeRobot uptime monitoring (D-18, D-24, requirements.md § "Monitoring and observability")
- **Sentry projects**: **one** project for the whole CMS, `environment` tag distinguishes
test/production (D-19) — so one DSN, reused by both environments and both frontend/backend
- **Sentry is optional by design**: an absent DSN disables it cleanly, console logging stays active
(BR-U4-08) — nothing breaks if a value is left blank temporarily
- **Alertable events already exist in code**: six `security_event` tag values are emitted today —
`failed_login`, `authorization_denied`, `master_api_key_rejected`, `admin_bypass_rejected`,
`rate_limit_triggered`, `migration_failure` (`SecurityEvents.cs`). Alert rules must filter on the
`security_event` tag, not message text (`nfr-design-patterns.md` Pattern 6) — this is explicitly
named as **Operations — Monitoring Setup** work in `logical-components.md` (FR-19)
- **UptimeRobot scope**: `/health`, `/` (public website) and `/admin`, per environment — 6 monitors
total (D-23)
- **Umami scope**: reuse the existing self-hosted instance at `analytics.slpsoftware.nl`, add new
website entries for this CMS rather than standing up a new instance (D-24/ASM-05). Both the admin
SPA (via `VITE_UMAMI_WEBSITE_ID_<ENV>`, built by this pipeline) and the public website (via the
separate website-workspace, FR-09/FR-16, out of this pipeline's scope) are measured (D-25) — this
stage only configures the **admin SPA's** two website entries; the public website's Umami wiring
belongs to whichever website workspace serves it
- **Health endpoint**: liveness only, no DB check, already built and already on the
`AvailabilityMiddleware` bypass list (D-21) — nothing left to build, only to point UptimeRobot at
- **Log retention deviation**: accepted knowingly (DEV-01) — Sentry's ~30-day retention against the
90-day SECURITY-14 minimum. No action here beyond documenting it.
## Question 1: Include Monitoring Setup?
Given U4 built all the alertable-event plumbing specifically so that Operations could wire up real
alert rules and monitors on top of it, declining this stage would leave that code inert — events
would be emitted into a Sentry project that doesn't exist yet, with no alert rule reading the tag,
and no UptimeRobot monitor watching `/health`. Recommended: A.
A) Yes — configure the real services and document it (Recommended)
B) No — monitoring is handled elsewhere or not needed
C) Not sure — suggest an approach and I'll decide
X) Other (please describe after [Answer]: tag below)
[Answer]:A
## Question 2: Monitoring Method
Confirms the stack already decided in Requirements Analysis — asked per the mandatory format, not
because it's genuinely open.
A) Sentry + self-hosted Umami + UptimeRobot, exactly as scoped above (Recommended)
B) Something else entirely — describe below
X) Other (please describe after [Answer]: tag below)
[Answer]:A
---
The remaining questions are the genuinely open items — real account/service facts that only you
know, which nothing in Construction could decide for you.
## Question 3: Sentry Project
Does a Sentry project for this CMS already exist (e.g. created ad hoc while testing U4 locally), or
does this stage need to walk through creating one from scratch?
A) Already exists — I'll provide the DSN after [Answer]: below
B) Doesn't exist yet — walk me through creating one (org, project platform picks for .NET + React,
where to find the DSN afterward)
C) Not yet, and I'd rather set it up myself later — document it as a TODO with exactly which Gitea
secrets/variables need the DSN once it exists
X) Other (please describe after [Answer]: tag below)
[Answer]:A — DSN to be added directly to the Gitea secret/variable once shared (kept out of this doc)
## Question 4: Sentry Alert Rule Thresholds
FR-19 requires alert rules for repeated auth failures and authorization violations, filtering on the
`security_event` tag (values: `failed_login`, `authorization_denied`, `master_api_key_rejected`,
`admin_bypass_rejected`, `rate_limit_triggered`; `migration_failure` is `Critical`-level and worth
its own always-fire rule regardless of count). What threshold should the repeated-failure rules use?
A) A single reasonable default for all of them (e.g. 20 occurrences in 5 minutes) — document it as a
starting point, tune later from real traffic (Recommended)
B) I'll specify exact thresholds per event type — describe below
C) No count threshold — alert on every occurrence of each of the six (noisier, but nothing is ever
missed)
X) Other (please describe after [Answer]: tag below)
[Answer]:A
## Question 5: UptimeRobot Account and Alert Contacts
6 monitors are needed (`/health`, `/`, `/admin` × test/production). Do you have an UptimeRobot
account already (e.g. from the reference `SlpSoftware` project), and where should alerts go?
A) Existing account, reuse it — alert contact(s): describe after [Answer]: below (email, and/or
any other channel already configured there)
B) Need a new account/monitor group set up from scratch — walk me through it
C) Not sure yet — document the 6 monitors' exact URLs and recommended check interval as a checklist,
I'll create them myself
X) Other (please describe after [Answer]: tag below)
[Answer]:A — existing account, alert contact to be confirmed
## Question 6: Umami Website Entries (Admin SPA)
Two new website entries are needed on the existing `analytics.slpsoftware.nl` instance — one per
environment — to obtain the `VITE_UMAMI_WEBSITE_ID_TEST`/`_PRODUCTION` values the CI workflow already
expects as Gitea variables (`continuous_integration.yaml` reads `vars.VITE_UMAMI_WEBSITE_ID_TEST`/
`_PRODUCTION`).
A) I have access to the Umami instance — I'll create the two entries and provide the website IDs
after [Answer]: below
B) Walk me through creating them (Umami's own UI/API steps)
C) Not yet — document it as a TODO with the exact variable names the pipeline expects, I'll fill
them in before the first real deploy
X) Other (please describe after [Answer]: tag below)
[Answer]:A — website IDs to be added directly to Gitea variables once created (kept out of this doc)