Goal: Sentry Configuration as Code
Arda’s Sentry alerting is configured by hand in the Sentry UI. Seven issue alert
rules currently exist across two projects, with overlapping scope, one outright
duplicate, and a Linear integration that is only correctly configured on a rule
that is disabled. This project builds a small, reusable configuration-as-code
layer for Sentry issue alert rules — snapshot, plan, apply, verify — and then
uses it to perform a specific, auditable migration: consolidate high-priority
routing into Linear, add an urgent tier, and retire the arda-frontend Slack
notifications.
The layer is deliberately narrow. It is not a general-purpose Sentry SDK; it models only the API surface required for issue alert rules, but is structured so that additional resource types (projects, DSNs, uptime monitors, dashboards) can be added by writing one new file, not by refactoring the core.
Linear Tickets
Section titled “Linear Tickets”- None yet. A
PDEVticket should be created to track this work.
Repositories
Section titled “Repositories”| Repository | Role | Planned Changes |
|---|---|---|
infrastructure | Primary artifact repo | New Sentry configuration-as-code library, CLI, desired-state config, migration and rollback scripts |
documentation | Project artifacts | This goal, design and decision-log documents, schema notes, completion report |
Success Criteria
Section titled “Success Criteria”- A
snapshotcommand captures all issue alert rules forarda-frontendandplatform-beverbatim to disk, with a manifest recording org, projects, rule IDs, and a token fingerprint (hash — never the token). - Rule types are derived from observed live payloads, not from documentation or memory. Every empirically discovered shape is recorded in a schema-notes document with the date observed and its provenance.
- Server-managed fields are determined empirically (
GET→PUTunchanged →GET→ diff) and stripped by normalization, so an unchanged rule produces an empty diff. - A
plancommand prints a readable, path-addressed changeset and exits non-zero when changes are pending (CI-friendly). applyis dry-run by default, requires--confirm, and refuses to run without a snapshot from the current session.verifyre-reads live state and asserts conformance with the desired state.- The migration script is idempotent — running it twice produces no second set
of changes — and refuses to run while any entry in its
DECISIONSblock is stillTODO. - A rollback script restores from a named snapshot directory, with the new-rule-IDs limitation stated explicitly in code and documentation.
- Adding a new resource type requires one new file implementing the resource interface plus a registry entry — demonstrated by review, not by speculative implementation.
- The guidance compliance audit has been run against the finished branch and its result recorded in the PR, before the PR is opened for external review.
Context
Section titled “Context”Sentry was introduced to the platform by the Sentry Integration project, which explicitly placed “Sentry alert rule implementation” out of scope, leaving only recommendations. This project picks that thread up.
Note a divergence worth confirming: the Sentry Integration design specified
Sentry projects named arda-operations and arda-accounts, one per backend
component. The live org has arda-frontend and platform-be. Reality and the
design document disagree; the live org is authoritative for this project’s
purposes, but the discrepancy should be reconciled.
Live state at project start
Section titled “Live state at project start”Seven issue alert rules, verified in the org arda-systems
(region https://us.sentry.io):
| ID | Project(s) | Name | Status | Notes |
|---|---|---|---|---|
| 3153115 | both | Send a notification for high priority issues | enabled | Email to issue owners + Linear action (priority 1, no assignee/label/project) |
| 3426957 | platform-be | Send a notification for high priority issues | enabled | Email only, frequency 0 — the duplicate |
| 3190653 | both | Sentry Alerts - prod | enabled | Slack #sentry-fe-prod, first-seen, env production |
| 3190652 | arda-frontend | Sentry Alerts - stage | enabled | Slack, first-seen, env stage |
| 3190649 | arda-frontend | Sentry Alerts - dev | enabled | Slack, first-seen, env dev |
| 3462179 | platform-be | Operations fatal startup / uncaught exception | enabled | Slack, level=fatal + mechanism:UncaughtExceptionHandler |
| 3433641 | arda-frontend | Notify via Linear | disabled | Linear action, priority 2, has labelId and assigneeId, priority ≥ 75 filter |
Linear priority mapping, confirmed from live config: 1 = Urgent, 2 = High,
3 = Medium, 4 = Low. Rule 3433641 is the best-configured Linear action in
the org and serves as the template for the migration’s action blocks.
Architecture Placement
Section titled “Architecture Placement”This work is the first inhabitant of OperationsManagement — a fourth
platform element alongside Environments, Root, and the informal tools/scripts
collection. It holds resources that observe, alert on, and administer the
platform rather than run it; Grafana and comparable observability runtime are
future candidates.
Two framing points settled before design, recorded in
infrastructure/knowledge-base/platform-architecture.md:
cdkin this repo names the IaC machinery generally, not AWS CDK. The Construct / Stack / Application / Instance vocabulary is borrowed from AWS with a looser reading — a Construct need not extendconstructs.Construct, a Stack need not become a CloudFormation stack.platform/constructs/postmark/is the standing proof.- The element is spelled
OperationsManagement, neveroam, in paths.src/main/cdk/constructs/oam/already means something else — infrastructure-supporting constructs (IAM roles, secrets, OIDC providers, CloudWatch dashboards).
The capability layers as follows. Layers 1–2 are shared with any future
OperationsManagement provider; layer 3 is Sentry’s declared desired state:
| Layer | Location | Contents |
|---|---|---|
| Vendor metadata | platform/sentry-service.ts, platform/one-password.ts | Org slug, region URL, API base, SENTRY_API_SURFACE freshness block, Linear priority mapping, credential item reference |
| Transport + engine | tools/lib/sentry/ (client + resources), tools/lib/desired-state/, tools/lib/sentry/, tools/lib/drift/sentry-probe.ts | Authenticated transport with retry/backoff; vendor-neutral reconcile engine (resource seam, diff, plan, snapshot); Sentry resource implementations; read helpers |
| Construct | platform/constructs/sentry/ | LinearRoutedAlertTier — parametric on priority, environment, threshold, and Linear binding. Instantiated twice: High and Urgent. |
| Stack | stacks/operations-management/sentry-fault-alerting.ts | Deployed per Sentry project (arda-frontend, platform-be) |
| Application | apps/OperationsManagement/index.ts | Composes the Sentry stacks today; Grafana and others later |
| Instance | instances/OperationsManagement/arda-systems.ts | The values that configure it: org slug, Linear IDs, environment names, thresholds |
| Drivers | tools/, scripts/sentry/ | Config driver and drift driver; thin 1Password operator wrapper |
Three consequences worth stating explicitly, because each one changes the design:
- The functional decomposition lands at the Stack layer. Fault (issue alert
rules) and Performance (metric alerts, dashboards, thresholds) deploy and
change independently, and a Stack is the unit of deployment. A future
sentry-performance-monitoring.tsbecomes a sibling Stack, not a second toolchain — the client, engine, diff, plan, and CLI are untouched. - The
DECISIONSblock becomes the Instance file. That is the repo’s established home for “values that configure a capability for deployment”, it is reviewable in a PR, and it turns the refuse-if-TODOgate from a runtime check into a type error. - No
cdk.context.jsonhandoff. Postmark needs one because Route53 records depend on DKIM keys, forcing a pre-deploy step. Nothing in CloudFormation references a Sentry alert rule, soOperationsManagementis an independent deployment target. It reuses the vocabulary and theplatform/metadata layer, and nothing else.
In Scope
Section titled “In Scope”- Library — authenticated HTTP client with
Retry-Afterhandling and backoff, issue-alert rule types derived from live fixtures, a resource abstraction that is the extensibility seam, structural diff with empirical normalization, plan computation, and snapshot dump/restore. - CLI —
snapshot,import,plan,apply,verifysubcommands with a JSONL audit log behind an--auditflag. - Migration script — phased (
snapshot→discover→plan→apply→verify), individually confirmable, resumable, with a typedDECISIONSblock that gates execution. - Rollback script — restore from a named snapshot.
- Schema notes — every empirically discovered condition/filter/action shape, dated and attributed.
Out of Scope
Section titled “Out of Scope”- General-purpose coverage of the Sentry API. Only issue alert rules are modeled.
- Metric alert rules, dashboards, uptime monitors, projects, DSNs, and teams — the abstraction must accommodate them; this project does not implement them.
- Reconciling the Sentry project topology with the Sentry Integration design
(
arda-operations/arda-accountsvs.arda-frontend/platform-be). - Changes to the Linear workspace itself (teams, projects, labels, workflow states, automation rules).
- CI wiring of
planas a drift gate. The command is built to be CI-friendly; scheduling it is follow-on work.
Constraints
Section titled “Constraints”- Do not invent the API schema. Sentry’s issue-alert rule JSON — condition
IDs, filter IDs, action IDs, and their field names — is under-documented, and
the
sentry_appaction backing the Linear integration is especially opaque. Every payload is obtained read-modify-write from live state or by round-trip discovery. Guessing at a field name is a stop-and-ask condition, not a judgment call. - Never
applyunattended. The migration is built, dry-run, and its plan presented. Execution is the user’s call. SENTRY_AUTH_TOKENcomes from the environment only — never from a file, never hardcoded. Fail fast with a clear message when unset.- Fits the existing toolset. The code joins the repository’s established
TypeScript/
ts-node/Jest stack and reusestools/lib/helpers rather than standing up a parallel toolchain. This supersedes the source prompt’s zero-dependency,--experimental-strip-types, no-build-step constraint, which would have meant a second retry/backoff client besidepostmark-client.ts. - Correctness over coverage. A narrow, correct model beats a broad, speculative one.
- Commits land in logical increments — client, then types and resource, then diff and plan, then CLI, then migration — not one terminal commit.
Open Decisions
Section titled “Open Decisions”Superseded. The decisions originally listed here (D-01…D-08) were carried
into decision-log.md as DQ-001…DQ-020 and all but one
are settled. Recon against the live org reshaped several of them beyond
recognition:
| Original | What actually happened |
|---|---|
| D-01 code placement | Resolved — see Architecture Placement and DQ-001 |
D-02 urgentEnvironment | Alpha001-prod is real, but only on platform-be; the two projects share no environment name. Resolved by a logical-environment abstraction (DQ-012) |
D-03 escalationStrategy | Premise disproved by probe. A later rule firing for an already-ticketed Sentry issue creates no second ticket and does not upgrade priority — escalation was silently lost. Urgent now notifies via Slack instead (DQ-017) |
| D-04 Linear IDs | Resolved live: label = “Bug”, state = “Triage”, routing per discipline (DQ-010) |
D-05 frontendScope | Dissolved. Attachment is via detectorIds, so detach is a reversible array edit, not a delete-vs-keep dilemma (DQ-005) |
D-06 removalMode | Delete outright (DQ-018) |
| D-07 snapshots | Committed (DQ-006) |
| D-08 trigger narrowing | Drop existing_high_priority_issue (DQ-020) |
Two assumptions in this goal were also overturned outright: the API surface is Sentry’s workflow engine, not the legacy issue-alert-rule API (DQ-004); and the 30-minute Urgent window is not expressible — Sentry’s interval enum has no 30m (DQ-008).
Deliverables
Section titled “Deliverables”| # | Deliverable | Location |
|---|---|---|
| 1 | Sentry vendor metadata and credential reference | platform/sentry-service.ts, platform/one-password.ts |
| 2 | Sentry transport client | tools/lib/sentry/client.ts |
| 3 | Vendor-neutral desired-state engine | tools/lib/desired-state/ |
| 4 | Issue-alert resource implementation and read probe | tools/lib/sentry/, tools/lib/drift/sentry-probe.ts |
| 5 | Constructs, Stack, Application, Instance | platform/constructs/sentry/, stacks/operations-management/, apps/OperationsManagement/, instances/OperationsManagement/ |
| 6 | Config driver and drift driver + operator wrapper | tools/, scripts/sentry/ |
| 7 | One-time migration and rollback scripts | tools/ (transitional — apply subsumes them once desired state is declared) |
| 8 | Sentry API observations note (dated, per SENTRY_API_SURFACE) | current-system/oam/sentry-service/sentry-api-observations.md |
| 9 | Design document and decision log | This project directory |
| 10 | Plan output from a live dry run | This project directory |
Reference Documents
Section titled “Reference Documents”- Design — the design produced from this goal.
- Decision Log — settled decisions and rejected alternatives, including the ones that overturned this goal’s assumptions.
- Sentry Integration — the project that introduced Sentry and deferred alert rules.
- Operations Sentry — completed backend Sentry onboarding.
- Sentry Observability — how Arda uses Sentry today: capture topology, projects, environments.
- Platform elements and the generalized IaC vocabulary — the four platform elements, Construct/Stack/Application/Instance, and how non-AWS providers fit. Written by this project.
tools/vsscripts/split criteria — logic intools/, operator ergonomy inscripts/.typescript-codingskill (workspace/instructions/claude/skills/typescript-coding/) — strict mode, ESM packaging, zero-dependency patterns. Note: this skill has no counterpart page on the documentation site.
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved