@checkstack/slo-common 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/package.json +30 -0
- package/src/access.ts +27 -0
- package/src/index.ts +79 -0
- package/src/plugin-metadata.ts +9 -0
- package/src/routes.ts +30 -0
- package/src/rpc-contract.ts +194 -0
- package/src/schemas.ts +225 -0
- package/tsconfig.json +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @checkstack/slo-common
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3c34b07: Complete SLO Reliability Engine frontend and backend
|
|
8
|
+
|
|
9
|
+
**Frontend** — 7 new visualization components:
|
|
10
|
+
|
|
11
|
+
- `StreakCounter`: Fire-themed compliance streak counter with color-coded flame and best-streak trophy
|
|
12
|
+
- `AchievementBadge`: Emoji-labeled badges for 9 achievement types with hover tooltip
|
|
13
|
+
- `AttributionChart`: Horizontal stacked bar showing error budget split (self/upstream/remaining)
|
|
14
|
+
- `DowntimeTimeline`: Dot-and-line timeline with attribution badges and timestamps
|
|
15
|
+
- `SloTrendChart`: Pure SVG availability trend line chart from daily snapshots
|
|
16
|
+
- `MilestoneFeed`: Organization-wide milestone feed on the SLO overview sidebar
|
|
17
|
+
- `DependencyExclusionConfig`: Interactive upstream dependency picker for SLO editor
|
|
18
|
+
|
|
19
|
+
**Backend** — Weekly digest scheduled integration event:
|
|
20
|
+
|
|
21
|
+
- `weekly-digest.ts`: Cron job (Monday 09:00 UTC) emitting SLO performance summary
|
|
22
|
+
- Top/worst performers, breach counts, and streak data delivered via configured notification channels
|
|
23
|
+
- New `sloWeeklyDigest` hook registered as integration event
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [d1a2796]
|
|
28
|
+
- @checkstack/common@0.6.5
|
|
29
|
+
- @checkstack/frontend-api@0.3.9
|
|
30
|
+
- @checkstack/signal-common@0.1.9
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@checkstack/slo-common",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"import": "./src/index.ts"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@checkstack/common": "0.6.4",
|
|
12
|
+
"@checkstack/frontend-api": "0.3.8",
|
|
13
|
+
"@checkstack/signal-common": "0.1.8",
|
|
14
|
+
"@orpc/contract": "^1.13.14",
|
|
15
|
+
"zod": "^4.2.1"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.2",
|
|
19
|
+
"@checkstack/tsconfig": "0.0.5",
|
|
20
|
+
"@checkstack/scripts": "0.1.2"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"lint": "bun run lint:code",
|
|
25
|
+
"lint:code": "eslint . --max-warnings 0"
|
|
26
|
+
},
|
|
27
|
+
"checkstack": {
|
|
28
|
+
"type": "common"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/access.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { accessPair } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Access rules for the SLO plugin.
|
|
5
|
+
*/
|
|
6
|
+
export const sloAccess = {
|
|
7
|
+
/**
|
|
8
|
+
* SLO access with both read and manage levels.
|
|
9
|
+
* Read is public by default (anyone can view SLO status).
|
|
10
|
+
* Manage requires authentication (create, edit, delete SLOs).
|
|
11
|
+
*/
|
|
12
|
+
slo: accessPair("slo", {
|
|
13
|
+
read: {
|
|
14
|
+
description: "View SLOs and error budgets",
|
|
15
|
+
isDefault: true,
|
|
16
|
+
isPublic: true,
|
|
17
|
+
},
|
|
18
|
+
manage: {
|
|
19
|
+
description: "Create, edit, and delete SLOs",
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* All access rules for registration with the plugin system.
|
|
26
|
+
*/
|
|
27
|
+
export const sloAccessRules = [sloAccess.slo.read, sloAccess.slo.manage];
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export { sloAccess, sloAccessRules } from "./access";
|
|
2
|
+
export { sloContract, SloApi, type SloContract } from "./rpc-contract";
|
|
3
|
+
export {
|
|
4
|
+
DependencyExclusionModeSchema,
|
|
5
|
+
AttributionTypeSchema,
|
|
6
|
+
AchievementTypeSchema,
|
|
7
|
+
BurnRateThresholdsSchema,
|
|
8
|
+
SloObjectiveSchema,
|
|
9
|
+
SloDowntimeEventSchema,
|
|
10
|
+
SloDailySnapshotSchema,
|
|
11
|
+
SloStreakSchema,
|
|
12
|
+
SloAchievementSchema,
|
|
13
|
+
SloAttributionEntrySchema,
|
|
14
|
+
SloStatusSchema,
|
|
15
|
+
CreateSloObjectiveInputSchema,
|
|
16
|
+
UpdateSloObjectiveInputSchema,
|
|
17
|
+
type DependencyExclusionMode,
|
|
18
|
+
type AttributionType,
|
|
19
|
+
type AchievementType,
|
|
20
|
+
type BurnRateThresholds,
|
|
21
|
+
type SloObjective,
|
|
22
|
+
type SloDowntimeEvent,
|
|
23
|
+
type SloDailySnapshot,
|
|
24
|
+
type SloStreak,
|
|
25
|
+
type SloAchievement,
|
|
26
|
+
type SloAttributionEntry,
|
|
27
|
+
type SloStatus,
|
|
28
|
+
type CreateSloObjectiveInput,
|
|
29
|
+
type UpdateSloObjectiveInput,
|
|
30
|
+
} from "./schemas";
|
|
31
|
+
export * from "./plugin-metadata";
|
|
32
|
+
export { sloRoutes } from "./routes";
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// REALTIME SIGNALS
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
import { createSignal } from "@checkstack/signal-common";
|
|
39
|
+
import { z } from "zod";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Broadcast when an SLO's status changes (budget consumed, breach started/ended).
|
|
43
|
+
* Frontend components can refetch SLO data for the affected system.
|
|
44
|
+
*/
|
|
45
|
+
export const SLO_STATUS_CHANGED = createSignal(
|
|
46
|
+
"slo.status.changed",
|
|
47
|
+
z.object({
|
|
48
|
+
systemId: z.string(),
|
|
49
|
+
objectiveId: z.string(),
|
|
50
|
+
budgetRemainingPercent: z.number(),
|
|
51
|
+
isBreaching: z.boolean(),
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Broadcast when a streak is updated (incremented or broken).
|
|
57
|
+
* Used to update streak counter UI in real-time.
|
|
58
|
+
*/
|
|
59
|
+
export const SLO_STREAK_UPDATED = createSignal(
|
|
60
|
+
"slo.streak.updated",
|
|
61
|
+
z.object({
|
|
62
|
+
systemId: z.string(),
|
|
63
|
+
objectiveId: z.string(),
|
|
64
|
+
currentStreak: z.number(),
|
|
65
|
+
broken: z.boolean(),
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Broadcast when a system unlocks a new achievement.
|
|
71
|
+
* Used to trigger celebration UI and milestone feed updates.
|
|
72
|
+
*/
|
|
73
|
+
export const SLO_ACHIEVEMENT_UNLOCKED = createSignal(
|
|
74
|
+
"slo.achievement.unlocked",
|
|
75
|
+
z.object({
|
|
76
|
+
systemId: z.string(),
|
|
77
|
+
achievement: z.string(),
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { definePluginMetadata } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plugin metadata for the SLO plugin.
|
|
5
|
+
* Exported from the common package so both backend and frontend can reference it.
|
|
6
|
+
*/
|
|
7
|
+
export const pluginMetadata = definePluginMetadata({
|
|
8
|
+
pluginId: "slo",
|
|
9
|
+
});
|
package/src/routes.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createRoutes } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Route definitions for the SLO plugin.
|
|
5
|
+
*
|
|
6
|
+
* @example Frontend plugin usage
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { sloRoutes } from "@checkstack/slo-common";
|
|
9
|
+
*
|
|
10
|
+
* createFrontendPlugin({
|
|
11
|
+
* routes: [
|
|
12
|
+
* { route: sloRoutes.routes.overview, element: <SloOverviewPage /> },
|
|
13
|
+
* ],
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @example Link generation
|
|
18
|
+
* ```tsx
|
|
19
|
+
* import { sloRoutes } from "@checkstack/slo-common";
|
|
20
|
+
* import { resolveRoute } from "@checkstack/common";
|
|
21
|
+
*
|
|
22
|
+
* const detailPath = resolveRoute(sloRoutes.routes.detail, { sloId });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export const sloRoutes = createRoutes("slo", {
|
|
26
|
+
overview: "/",
|
|
27
|
+
config: "/config",
|
|
28
|
+
configDetail: "/config/:sloId",
|
|
29
|
+
detail: "/:sloId",
|
|
30
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createClientDefinition, proc } from "@checkstack/common";
|
|
3
|
+
import { sloAccess } from "./access";
|
|
4
|
+
import { pluginMetadata } from "./plugin-metadata";
|
|
5
|
+
import {
|
|
6
|
+
SloObjectiveSchema,
|
|
7
|
+
SloStatusSchema,
|
|
8
|
+
SloDowntimeEventSchema,
|
|
9
|
+
SloDailySnapshotSchema,
|
|
10
|
+
SloStreakSchema,
|
|
11
|
+
SloAchievementSchema,
|
|
12
|
+
CreateSloObjectiveInputSchema,
|
|
13
|
+
UpdateSloObjectiveInputSchema,
|
|
14
|
+
} from "./schemas";
|
|
15
|
+
|
|
16
|
+
export const sloContract = {
|
|
17
|
+
// ==========================================================================
|
|
18
|
+
// SLO OBJECTIVE MANAGEMENT
|
|
19
|
+
// ==========================================================================
|
|
20
|
+
|
|
21
|
+
/** List all SLO objectives with their computed status */
|
|
22
|
+
listObjectives: proc({
|
|
23
|
+
operationType: "query",
|
|
24
|
+
userType: "public",
|
|
25
|
+
access: [sloAccess.slo.read],
|
|
26
|
+
}).output(
|
|
27
|
+
z.object({
|
|
28
|
+
objectives: z.array(
|
|
29
|
+
z.object({
|
|
30
|
+
objective: SloObjectiveSchema,
|
|
31
|
+
status: SloStatusSchema,
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
|
|
37
|
+
/** Get a single SLO objective with full status and attribution */
|
|
38
|
+
getObjective: proc({
|
|
39
|
+
operationType: "query",
|
|
40
|
+
userType: "public",
|
|
41
|
+
access: [sloAccess.slo.read],
|
|
42
|
+
})
|
|
43
|
+
.input(z.object({ id: z.string() }))
|
|
44
|
+
.output(
|
|
45
|
+
z
|
|
46
|
+
.object({
|
|
47
|
+
objective: SloObjectiveSchema,
|
|
48
|
+
status: SloStatusSchema,
|
|
49
|
+
})
|
|
50
|
+
.nullable(),
|
|
51
|
+
),
|
|
52
|
+
|
|
53
|
+
/** Get SLOs for a specific system (for badge display) */
|
|
54
|
+
getObjectivesForSystem: proc({
|
|
55
|
+
operationType: "query",
|
|
56
|
+
userType: "public",
|
|
57
|
+
access: [sloAccess.slo.read],
|
|
58
|
+
})
|
|
59
|
+
.input(z.object({ systemId: z.string() }))
|
|
60
|
+
.output(
|
|
61
|
+
z.array(
|
|
62
|
+
z.object({
|
|
63
|
+
objective: SloObjectiveSchema,
|
|
64
|
+
status: SloStatusSchema,
|
|
65
|
+
}),
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
|
|
69
|
+
/** Bulk fetch SLO statuses for multiple systems (dashboard, avoid N+1) */
|
|
70
|
+
getBulkObjectivesForSystems: proc({
|
|
71
|
+
operationType: "query",
|
|
72
|
+
userType: "public",
|
|
73
|
+
access: [sloAccess.slo.read],
|
|
74
|
+
instanceAccess: { recordKey: "systems" },
|
|
75
|
+
})
|
|
76
|
+
.input(z.object({ systemIds: z.array(z.string()) }))
|
|
77
|
+
.output(
|
|
78
|
+
z.object({
|
|
79
|
+
systems: z.record(
|
|
80
|
+
z.string(),
|
|
81
|
+
z.array(
|
|
82
|
+
z.object({
|
|
83
|
+
objective: SloObjectiveSchema,
|
|
84
|
+
status: SloStatusSchema,
|
|
85
|
+
}),
|
|
86
|
+
),
|
|
87
|
+
),
|
|
88
|
+
}),
|
|
89
|
+
),
|
|
90
|
+
|
|
91
|
+
/** Create a new SLO objective */
|
|
92
|
+
createObjective: proc({
|
|
93
|
+
operationType: "mutation",
|
|
94
|
+
userType: "authenticated",
|
|
95
|
+
access: [sloAccess.slo.manage],
|
|
96
|
+
})
|
|
97
|
+
.input(CreateSloObjectiveInputSchema)
|
|
98
|
+
.output(SloObjectiveSchema),
|
|
99
|
+
|
|
100
|
+
/** Update an existing SLO objective */
|
|
101
|
+
updateObjective: proc({
|
|
102
|
+
operationType: "mutation",
|
|
103
|
+
userType: "authenticated",
|
|
104
|
+
access: [sloAccess.slo.manage],
|
|
105
|
+
})
|
|
106
|
+
.input(UpdateSloObjectiveInputSchema)
|
|
107
|
+
.output(SloObjectiveSchema),
|
|
108
|
+
|
|
109
|
+
/** Delete an SLO objective and all associated data */
|
|
110
|
+
deleteObjective: proc({
|
|
111
|
+
operationType: "mutation",
|
|
112
|
+
userType: "authenticated",
|
|
113
|
+
access: [sloAccess.slo.manage],
|
|
114
|
+
})
|
|
115
|
+
.input(z.object({ id: z.string() }))
|
|
116
|
+
.output(z.object({ success: z.boolean() })),
|
|
117
|
+
|
|
118
|
+
// ==========================================================================
|
|
119
|
+
// DOWNTIME EVENTS & DAILY SNAPSHOTS
|
|
120
|
+
// ==========================================================================
|
|
121
|
+
|
|
122
|
+
/** Get recent downtime events for an SLO objective */
|
|
123
|
+
getDowntimeEvents: proc({
|
|
124
|
+
operationType: "query",
|
|
125
|
+
userType: "public",
|
|
126
|
+
access: [sloAccess.slo.read],
|
|
127
|
+
})
|
|
128
|
+
.input(
|
|
129
|
+
z.object({
|
|
130
|
+
objectiveId: z.string(),
|
|
131
|
+
limit: z.number().optional().default(50),
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
134
|
+
.output(z.object({ events: z.array(SloDowntimeEventSchema) })),
|
|
135
|
+
|
|
136
|
+
/** Get daily SLO trend data for a time range */
|
|
137
|
+
getDailySnapshots: proc({
|
|
138
|
+
operationType: "query",
|
|
139
|
+
userType: "public",
|
|
140
|
+
access: [sloAccess.slo.read],
|
|
141
|
+
})
|
|
142
|
+
.input(
|
|
143
|
+
z.object({
|
|
144
|
+
objectiveId: z.string(),
|
|
145
|
+
startDate: z.date(),
|
|
146
|
+
endDate: z.date(),
|
|
147
|
+
}),
|
|
148
|
+
)
|
|
149
|
+
.output(z.object({ snapshots: z.array(SloDailySnapshotSchema) })),
|
|
150
|
+
|
|
151
|
+
// ==========================================================================
|
|
152
|
+
// STREAKS & ACHIEVEMENTS
|
|
153
|
+
// ==========================================================================
|
|
154
|
+
|
|
155
|
+
/** Get streaks for all SLO objectives */
|
|
156
|
+
getStreaks: proc({
|
|
157
|
+
operationType: "query",
|
|
158
|
+
userType: "public",
|
|
159
|
+
access: [sloAccess.slo.read],
|
|
160
|
+
}).output(z.object({ streaks: z.array(SloStreakSchema) })),
|
|
161
|
+
|
|
162
|
+
/** Get achievements for a specific system */
|
|
163
|
+
getAchievements: proc({
|
|
164
|
+
operationType: "query",
|
|
165
|
+
userType: "public",
|
|
166
|
+
access: [sloAccess.slo.read],
|
|
167
|
+
})
|
|
168
|
+
.input(z.object({ systemId: z.string() }))
|
|
169
|
+
.output(z.object({ achievements: z.array(SloAchievementSchema) })),
|
|
170
|
+
|
|
171
|
+
/** Get recent milestones across all systems (for milestone feed) */
|
|
172
|
+
getRecentMilestones: proc({
|
|
173
|
+
operationType: "query",
|
|
174
|
+
userType: "public",
|
|
175
|
+
access: [sloAccess.slo.read],
|
|
176
|
+
})
|
|
177
|
+
.input(z.object({ limit: z.number().optional().default(20) }))
|
|
178
|
+
.output(
|
|
179
|
+
z.object({
|
|
180
|
+
milestones: z.array(
|
|
181
|
+
z.object({
|
|
182
|
+
systemId: z.string(),
|
|
183
|
+
systemName: z.string().optional(),
|
|
184
|
+
achievement: z.string(),
|
|
185
|
+
unlockedAt: z.date(),
|
|
186
|
+
}),
|
|
187
|
+
),
|
|
188
|
+
}),
|
|
189
|
+
),
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export type SloContract = typeof sloContract;
|
|
193
|
+
|
|
194
|
+
export const SloApi = createClientDefinition(sloContract, pluginMetadata);
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// ENUMS
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Dependency exclusion mode for SLO error budget calculation.
|
|
9
|
+
* - strict: Count ALL downtime regardless of cause
|
|
10
|
+
* - self-only: Only count self-caused downtime (upstream-attributed downtime is excluded)
|
|
11
|
+
*/
|
|
12
|
+
export const DependencyExclusionModeSchema = z.enum([
|
|
13
|
+
"strict",
|
|
14
|
+
"self-only",
|
|
15
|
+
]);
|
|
16
|
+
export type DependencyExclusionMode = z.infer<
|
|
17
|
+
typeof DependencyExclusionModeSchema
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Attribution type for a downtime event.
|
|
22
|
+
* - self: Downtime is caused by the system itself
|
|
23
|
+
* - upstream: Downtime is attributed to an upstream dependency failure
|
|
24
|
+
*/
|
|
25
|
+
export const AttributionTypeSchema = z.enum(["self", "upstream"]);
|
|
26
|
+
export type AttributionType = z.infer<typeof AttributionTypeSchema>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Achievement types that can be unlocked by systems.
|
|
30
|
+
*/
|
|
31
|
+
export const AchievementTypeSchema = z.enum([
|
|
32
|
+
"first_steps",
|
|
33
|
+
"iron_uptime",
|
|
34
|
+
"diamond_uptime",
|
|
35
|
+
"budget_miser",
|
|
36
|
+
"clean_sheet",
|
|
37
|
+
"nines_club",
|
|
38
|
+
"cascade_breaker",
|
|
39
|
+
"full_coverage",
|
|
40
|
+
"rapid_recovery",
|
|
41
|
+
]);
|
|
42
|
+
export type AchievementType = z.infer<typeof AchievementTypeSchema>;
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// CONFIGURABLE THRESHOLDS
|
|
46
|
+
// =============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Burn rate alert thresholds, configurable per SLO objective.
|
|
50
|
+
*/
|
|
51
|
+
export const BurnRateThresholdsSchema = z.object({
|
|
52
|
+
warningPercent: z.number().min(0).max(100).default(50),
|
|
53
|
+
criticalPercent: z.number().min(0).max(100).default(80),
|
|
54
|
+
fastBurnMultiplier: z.number().min(1).default(5),
|
|
55
|
+
});
|
|
56
|
+
export type BurnRateThresholds = z.infer<typeof BurnRateThresholdsSchema>;
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// CORE ENTITIES
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* SLO objective definition.
|
|
64
|
+
* Represents a reliability target for a system (or a specific health check on a system).
|
|
65
|
+
* Multiple SLOs can be defined per system. When healthCheckConfigurationId is null,
|
|
66
|
+
* the SLO applies to the system's aggregate availability across all health checks.
|
|
67
|
+
*/
|
|
68
|
+
export const SloObjectiveSchema = z.object({
|
|
69
|
+
id: z.string(),
|
|
70
|
+
systemId: z.string(),
|
|
71
|
+
healthCheckConfigurationId: z.string().nullable(),
|
|
72
|
+
target: z.number().min(0).max(100),
|
|
73
|
+
windowDays: z.number().int().positive(),
|
|
74
|
+
dependencyExclusion: DependencyExclusionModeSchema,
|
|
75
|
+
excludedDependencyIds: z.array(z.string()).optional(),
|
|
76
|
+
burnRateThresholds: BurnRateThresholdsSchema,
|
|
77
|
+
createdAt: z.date(),
|
|
78
|
+
updatedAt: z.date(),
|
|
79
|
+
});
|
|
80
|
+
export type SloObjective = z.infer<typeof SloObjectiveSchema>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Event-sourced downtime record, written in real-time on SYSTEM_STATUS_CHANGED.
|
|
84
|
+
* Events are immutable after creation — when attribution changes mid-outage,
|
|
85
|
+
* the open event is closed and a new one starts (event splitting).
|
|
86
|
+
*/
|
|
87
|
+
export const SloDowntimeEventSchema = z.object({
|
|
88
|
+
id: z.string(),
|
|
89
|
+
objectiveId: z.string(),
|
|
90
|
+
systemId: z.string(),
|
|
91
|
+
startTime: z.date(),
|
|
92
|
+
endTime: z.date().nullable(),
|
|
93
|
+
durationSeconds: z.number().nullable(),
|
|
94
|
+
attributionType: AttributionTypeSchema,
|
|
95
|
+
upstreamSystemId: z.string().nullable(),
|
|
96
|
+
upstreamSystemName: z.string().nullable(),
|
|
97
|
+
});
|
|
98
|
+
export type SloDowntimeEvent = z.infer<typeof SloDowntimeEventSchema>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Daily SLO snapshot for trend charts.
|
|
102
|
+
* Persisted by a daily cron job at UTC midnight.
|
|
103
|
+
*/
|
|
104
|
+
export const SloDailySnapshotSchema = z.object({
|
|
105
|
+
id: z.string(),
|
|
106
|
+
objectiveId: z.string(),
|
|
107
|
+
date: z.date(),
|
|
108
|
+
availabilityPercent: z.number(),
|
|
109
|
+
budgetConsumedMinutes: z.number(),
|
|
110
|
+
budgetRemainingPercent: z.number(),
|
|
111
|
+
burnRate: z.number().nullable(),
|
|
112
|
+
streakDays: z.number(),
|
|
113
|
+
});
|
|
114
|
+
export type SloDailySnapshot = z.infer<typeof SloDailySnapshotSchema>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Streak tracking for an SLO objective.
|
|
118
|
+
* One streak record per objective (1:1).
|
|
119
|
+
*/
|
|
120
|
+
export const SloStreakSchema = z.object({
|
|
121
|
+
objectiveId: z.string(),
|
|
122
|
+
systemId: z.string(),
|
|
123
|
+
currentStreak: z.number(),
|
|
124
|
+
bestStreak: z.number(),
|
|
125
|
+
streakStart: z.date().nullable(),
|
|
126
|
+
bestStreakEnd: z.date().nullable(),
|
|
127
|
+
});
|
|
128
|
+
export type SloStreak = z.infer<typeof SloStreakSchema>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Achievement unlocked by a system.
|
|
132
|
+
* System-centric only — no user attribution.
|
|
133
|
+
*/
|
|
134
|
+
export const SloAchievementSchema = z.object({
|
|
135
|
+
id: z.string(),
|
|
136
|
+
systemId: z.string(),
|
|
137
|
+
achievement: AchievementTypeSchema,
|
|
138
|
+
unlockedAt: z.date(),
|
|
139
|
+
});
|
|
140
|
+
export type SloAchievement = z.infer<typeof SloAchievementSchema>;
|
|
141
|
+
|
|
142
|
+
// =============================================================================
|
|
143
|
+
// COMPUTED STATUS (returned by API, not persisted)
|
|
144
|
+
// =============================================================================
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Attribution breakdown entry for a single source (self or an upstream dependency).
|
|
148
|
+
*/
|
|
149
|
+
export const SloAttributionEntrySchema = z.object({
|
|
150
|
+
sourceType: AttributionTypeSchema,
|
|
151
|
+
systemId: z.string().optional(),
|
|
152
|
+
systemName: z.string().optional(),
|
|
153
|
+
minutes: z.number(),
|
|
154
|
+
});
|
|
155
|
+
export type SloAttributionEntry = z.infer<typeof SloAttributionEntrySchema>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Computed SLO status for display, aggregated from downtime events.
|
|
159
|
+
* Returned by the API, not persisted directly.
|
|
160
|
+
*/
|
|
161
|
+
export const SloStatusSchema = z.object({
|
|
162
|
+
objectiveId: z.string(),
|
|
163
|
+
systemId: z.string(),
|
|
164
|
+
target: z.number(),
|
|
165
|
+
windowDays: z.number(),
|
|
166
|
+
healthCheckConfigurationId: z.string().nullable(),
|
|
167
|
+
healthCheckConfigurationName: z.string().nullable(),
|
|
168
|
+
currentAvailability: z.number().nullable(),
|
|
169
|
+
strictAvailability: z.number().nullable(),
|
|
170
|
+
errorBudgetTotalMinutes: z.number(),
|
|
171
|
+
errorBudgetConsumedMinutes: z.number(),
|
|
172
|
+
errorBudgetConsumedStrictMinutes: z.number(),
|
|
173
|
+
errorBudgetRemainingMinutes: z.number(),
|
|
174
|
+
errorBudgetRemainingPercent: z.number(),
|
|
175
|
+
burnRate: z.number().nullable(),
|
|
176
|
+
dependencyExclusion: DependencyExclusionModeSchema,
|
|
177
|
+
isBreaching: z.boolean(),
|
|
178
|
+
hasOpenDowntime: z.boolean(),
|
|
179
|
+
attribution: z.array(SloAttributionEntrySchema),
|
|
180
|
+
});
|
|
181
|
+
export type SloStatus = z.infer<typeof SloStatusSchema>;
|
|
182
|
+
|
|
183
|
+
// =============================================================================
|
|
184
|
+
// INPUT SCHEMAS
|
|
185
|
+
// =============================================================================
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Input for creating a new SLO objective.
|
|
189
|
+
*/
|
|
190
|
+
export const CreateSloObjectiveInputSchema = z.object({
|
|
191
|
+
systemId: z.string().min(1, "System is required"),
|
|
192
|
+
healthCheckConfigurationId: z.string().optional(),
|
|
193
|
+
target: z
|
|
194
|
+
.number()
|
|
195
|
+
.min(0, "Target must be >= 0")
|
|
196
|
+
.max(100, "Target must be <= 100"),
|
|
197
|
+
windowDays: z.number().int().positive("Window must be at least 1 day"),
|
|
198
|
+
dependencyExclusion: DependencyExclusionModeSchema.optional().default(
|
|
199
|
+
"strict",
|
|
200
|
+
),
|
|
201
|
+
excludedDependencyIds: z.array(z.string()).optional().default([]),
|
|
202
|
+
burnRateThresholds: BurnRateThresholdsSchema.optional().default({
|
|
203
|
+
warningPercent: 50,
|
|
204
|
+
criticalPercent: 80,
|
|
205
|
+
fastBurnMultiplier: 5,
|
|
206
|
+
}),
|
|
207
|
+
});
|
|
208
|
+
export type CreateSloObjectiveInput = z.infer<
|
|
209
|
+
typeof CreateSloObjectiveInputSchema
|
|
210
|
+
>;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Input for updating an existing SLO objective.
|
|
214
|
+
*/
|
|
215
|
+
export const UpdateSloObjectiveInputSchema = z.object({
|
|
216
|
+
id: z.string(),
|
|
217
|
+
target: z.number().min(0).max(100).optional(),
|
|
218
|
+
windowDays: z.number().int().positive().optional(),
|
|
219
|
+
dependencyExclusion: DependencyExclusionModeSchema.optional(),
|
|
220
|
+
excludedDependencyIds: z.array(z.string()).optional(),
|
|
221
|
+
burnRateThresholds: BurnRateThresholdsSchema.optional(),
|
|
222
|
+
});
|
|
223
|
+
export type UpdateSloObjectiveInput = z.infer<
|
|
224
|
+
typeof UpdateSloObjectiveInputSchema
|
|
225
|
+
>;
|