@checkstack/healthcheck-common 0.1.0 → 0.3.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 +171 -0
- package/package.json +1 -1
- package/src/access.ts +43 -0
- package/src/index.ts +1 -1
- package/src/rpc-contract.ts +27 -25
- package/src/schemas.ts +5 -3
- package/src/zod-health-result.ts +113 -45
- package/src/permissions.ts +0 -39
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,176 @@
|
|
|
1
1
|
# @checkstack/healthcheck-common
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9faec1f: # Unified AccessRule Terminology Refactoring
|
|
8
|
+
|
|
9
|
+
This release completes a comprehensive terminology refactoring from "permission" to "accessRule" across the entire codebase, establishing a consistent and modern access control vocabulary.
|
|
10
|
+
|
|
11
|
+
## Changes
|
|
12
|
+
|
|
13
|
+
### Core Infrastructure (`@checkstack/common`)
|
|
14
|
+
|
|
15
|
+
- Introduced `AccessRule` interface as the primary access control type
|
|
16
|
+
- Added `accessPair()` helper for creating read/manage access rule pairs
|
|
17
|
+
- Added `access()` builder for individual access rules
|
|
18
|
+
- Replaced `Permission` type with `AccessRule` throughout
|
|
19
|
+
|
|
20
|
+
### API Changes
|
|
21
|
+
|
|
22
|
+
- `env.registerPermissions()` → `env.registerAccessRules()`
|
|
23
|
+
- `meta.permissions` → `meta.access` in RPC contracts
|
|
24
|
+
- `usePermission()` → `useAccess()` in frontend hooks
|
|
25
|
+
- Route `permission:` field → `accessRule:` field
|
|
26
|
+
|
|
27
|
+
### UI Changes
|
|
28
|
+
|
|
29
|
+
- "Roles & Permissions" tab → "Roles & Access Rules"
|
|
30
|
+
- "You don't have permission..." → "You don't have access..."
|
|
31
|
+
- All permission-related UI text updated
|
|
32
|
+
|
|
33
|
+
### Documentation & Templates
|
|
34
|
+
|
|
35
|
+
- Updated 18 documentation files with AccessRule terminology
|
|
36
|
+
- Updated 7 scaffolding templates with `accessPair()` pattern
|
|
37
|
+
- All code examples use new AccessRule API
|
|
38
|
+
|
|
39
|
+
## Migration Guide
|
|
40
|
+
|
|
41
|
+
### Backend Plugins
|
|
42
|
+
|
|
43
|
+
```diff
|
|
44
|
+
- import { permissionList } from "./permissions";
|
|
45
|
+
- env.registerPermissions(permissionList);
|
|
46
|
+
+ import { accessRules } from "./access";
|
|
47
|
+
+ env.registerAccessRules(accessRules);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### RPC Contracts
|
|
51
|
+
|
|
52
|
+
```diff
|
|
53
|
+
- .meta({ userType: "user", permissions: [permissions.read.id] })
|
|
54
|
+
+ .meta({ userType: "user", access: [access.read] })
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Frontend Hooks
|
|
58
|
+
|
|
59
|
+
```diff
|
|
60
|
+
- const canRead = accessApi.usePermission(permissions.read.id);
|
|
61
|
+
+ const canRead = accessApi.useAccess(access.read);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Routes
|
|
65
|
+
|
|
66
|
+
```diff
|
|
67
|
+
- permission: permissions.entityRead.id,
|
|
68
|
+
+ accessRule: access.read,
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- f533141: Enforce health result factory function usage via branded types
|
|
72
|
+
|
|
73
|
+
- Added `healthResultSchema()` builder that enforces the use of factory functions at compile-time
|
|
74
|
+
- Added `healthResultArray()` factory for array fields (e.g., DNS resolved values)
|
|
75
|
+
- Added branded `HealthResultField<T>` type to mark schemas created by factory functions
|
|
76
|
+
- Consolidated `ChartType` and `HealthResultMeta` into `@checkstack/common` as single source of truth
|
|
77
|
+
- Updated all 12 health check strategies and 11 collectors to use `healthResultSchema()`
|
|
78
|
+
- Using raw `z.number()` etc. inside `healthResultSchema()` now causes a TypeScript error
|
|
79
|
+
|
|
80
|
+
### Patch Changes
|
|
81
|
+
|
|
82
|
+
- Updated dependencies [9faec1f]
|
|
83
|
+
- Updated dependencies [f533141]
|
|
84
|
+
- @checkstack/common@0.2.0
|
|
85
|
+
- @checkstack/signal-common@0.1.0
|
|
86
|
+
|
|
87
|
+
## 0.2.0
|
|
88
|
+
|
|
89
|
+
### Minor Changes
|
|
90
|
+
|
|
91
|
+
- 8e43507: # Teams and Resource-Level Access Control
|
|
92
|
+
|
|
93
|
+
This release introduces a comprehensive Teams system for organizing users and controlling access to resources at a granular level.
|
|
94
|
+
|
|
95
|
+
## Features
|
|
96
|
+
|
|
97
|
+
### Team Management
|
|
98
|
+
|
|
99
|
+
- Create, update, and delete teams with name and description
|
|
100
|
+
- Add/remove users from teams
|
|
101
|
+
- Designate team managers with elevated privileges
|
|
102
|
+
- View team membership and manager status
|
|
103
|
+
|
|
104
|
+
### Resource-Level Access Control
|
|
105
|
+
|
|
106
|
+
- Grant teams access to specific resources (systems, health checks, incidents, maintenances)
|
|
107
|
+
- Configure read-only or manage permissions per team
|
|
108
|
+
- Resource-level "Team Only" mode that restricts access exclusively to team members
|
|
109
|
+
- Separate `resourceAccessSettings` table for resource-level settings (not per-grant)
|
|
110
|
+
- Automatic cleanup of grants when teams are deleted (database cascade)
|
|
111
|
+
|
|
112
|
+
### Middleware Integration
|
|
113
|
+
|
|
114
|
+
- Extended `autoAuthMiddleware` to support resource access checks
|
|
115
|
+
- Single-resource pre-handler validation for detail endpoints
|
|
116
|
+
- Automatic list filtering for collection endpoints
|
|
117
|
+
- S2S endpoints for access verification
|
|
118
|
+
|
|
119
|
+
### Frontend Components
|
|
120
|
+
|
|
121
|
+
- `TeamsTab` component for managing teams in Auth Settings
|
|
122
|
+
- `TeamAccessEditor` component for assigning team access to resources
|
|
123
|
+
- Resource-level "Team Only" toggle in `TeamAccessEditor`
|
|
124
|
+
- Integration into System, Health Check, Incident, and Maintenance editors
|
|
125
|
+
|
|
126
|
+
## Breaking Changes
|
|
127
|
+
|
|
128
|
+
### API Response Format Changes
|
|
129
|
+
|
|
130
|
+
List endpoints now return objects with named keys instead of arrays directly:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Before
|
|
134
|
+
const systems = await catalogApi.getSystems();
|
|
135
|
+
|
|
136
|
+
// After
|
|
137
|
+
const { systems } = await catalogApi.getSystems();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Affected endpoints:
|
|
141
|
+
|
|
142
|
+
- `catalog.getSystems` → `{ systems: [...] }`
|
|
143
|
+
- `healthcheck.getConfigurations` → `{ configurations: [...] }`
|
|
144
|
+
- `incident.listIncidents` → `{ incidents: [...] }`
|
|
145
|
+
- `maintenance.listMaintenances` → `{ maintenances: [...] }`
|
|
146
|
+
|
|
147
|
+
### User Identity Enrichment
|
|
148
|
+
|
|
149
|
+
`RealUser` and `ApplicationUser` types now include `teamIds: string[]` field with team memberships.
|
|
150
|
+
|
|
151
|
+
## Documentation
|
|
152
|
+
|
|
153
|
+
See `docs/backend/teams.md` for complete API reference and integration guide.
|
|
154
|
+
|
|
155
|
+
- 97c5a6b: Add UUID-based collector identification for better multiple collector support
|
|
156
|
+
|
|
157
|
+
**Breaking Change**: Existing health check configurations with collectors need to be recreated.
|
|
158
|
+
|
|
159
|
+
- Each collector instance now has a unique UUID assigned on creation
|
|
160
|
+
- Collector results are stored under the UUID key with `_collectorId` and `_assertionFailed` metadata
|
|
161
|
+
- Auto-charts correctly display separate charts for each collector instance
|
|
162
|
+
- Charts are now grouped by collector instance with clear headings
|
|
163
|
+
- Assertion status card shows pass/fail for each collector
|
|
164
|
+
- Renamed "Success" to "HTTP Success" to clarify it's about HTTP request success
|
|
165
|
+
- Fixed deletion of collectors not persisting to database
|
|
166
|
+
- Fixed duplicate React key warnings in auto-chart grid
|
|
167
|
+
|
|
168
|
+
### Patch Changes
|
|
169
|
+
|
|
170
|
+
- Updated dependencies [8e43507]
|
|
171
|
+
- @checkstack/common@0.1.0
|
|
172
|
+
- @checkstack/signal-common@0.0.4
|
|
173
|
+
|
|
3
174
|
## 0.1.0
|
|
4
175
|
|
|
5
176
|
### Minor Changes
|
package/package.json
CHANGED
package/src/access.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { access, accessPair } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Access rules for the Health Check plugin.
|
|
5
|
+
*/
|
|
6
|
+
export const healthCheckAccess = {
|
|
7
|
+
/**
|
|
8
|
+
* Status-only access for viewing health check status.
|
|
9
|
+
* Enabled by default for anonymous and authenticated users.
|
|
10
|
+
*/
|
|
11
|
+
status: access("healthcheck.status", "read", "View Health Check Status", {
|
|
12
|
+
isDefault: true,
|
|
13
|
+
isPublic: true,
|
|
14
|
+
}),
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Configuration access for viewing and managing health check configurations.
|
|
18
|
+
*/
|
|
19
|
+
configuration: accessPair("healthcheck", {
|
|
20
|
+
read: "Read Health Check Configurations",
|
|
21
|
+
manage: "Full management of Health Check Configurations",
|
|
22
|
+
}),
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Access for viewing detailed health check run data including metadata.
|
|
26
|
+
* Allows access to extended visualizations without full management access.
|
|
27
|
+
*/
|
|
28
|
+
details: access(
|
|
29
|
+
"healthcheck.details",
|
|
30
|
+
"read",
|
|
31
|
+
"View Detailed Health Check Run Data (Warning: This may expose sensitive data, depending on the health check strategy)"
|
|
32
|
+
),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* All access rules for registration with the plugin system.
|
|
37
|
+
*/
|
|
38
|
+
export const healthCheckAccessRules = [
|
|
39
|
+
healthCheckAccess.status,
|
|
40
|
+
healthCheckAccess.configuration.read,
|
|
41
|
+
healthCheckAccess.configuration.manage,
|
|
42
|
+
healthCheckAccess.details,
|
|
43
|
+
];
|
package/src/index.ts
CHANGED
package/src/rpc-contract.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "@checkstack/common";
|
|
6
6
|
import { pluginMetadata } from "./plugin-metadata";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import {
|
|
8
|
+
import { healthCheckAccess } from "./access";
|
|
9
9
|
import {
|
|
10
10
|
HealthCheckStrategyDtoSchema,
|
|
11
11
|
CollectorDtoSchema,
|
|
@@ -44,13 +44,13 @@ const SystemHealthStatusResponseSchema = z.object({
|
|
|
44
44
|
// Health Check RPC Contract using oRPC's contract-first pattern
|
|
45
45
|
export const healthCheckContract = {
|
|
46
46
|
// ==========================================================================
|
|
47
|
-
// STRATEGY MANAGEMENT (userType: "authenticated" with read
|
|
47
|
+
// STRATEGY MANAGEMENT (userType: "authenticated" with read access)
|
|
48
48
|
// ==========================================================================
|
|
49
49
|
|
|
50
50
|
getStrategies: _base
|
|
51
51
|
.meta({
|
|
52
52
|
userType: "authenticated",
|
|
53
|
-
|
|
53
|
+
access: [healthCheckAccess.configuration.read],
|
|
54
54
|
})
|
|
55
55
|
.output(z.array(HealthCheckStrategyDtoSchema)),
|
|
56
56
|
|
|
@@ -61,7 +61,7 @@ export const healthCheckContract = {
|
|
|
61
61
|
getCollectors: _base
|
|
62
62
|
.meta({
|
|
63
63
|
userType: "authenticated",
|
|
64
|
-
|
|
64
|
+
access: [healthCheckAccess.configuration.read],
|
|
65
65
|
})
|
|
66
66
|
.input(z.object({ strategyId: z.string() }))
|
|
67
67
|
.output(z.array(CollectorDtoSchema)),
|
|
@@ -73,14 +73,16 @@ export const healthCheckContract = {
|
|
|
73
73
|
getConfigurations: _base
|
|
74
74
|
.meta({
|
|
75
75
|
userType: "authenticated",
|
|
76
|
-
|
|
76
|
+
access: [healthCheckAccess.configuration.read],
|
|
77
77
|
})
|
|
78
|
-
.output(
|
|
78
|
+
.output(
|
|
79
|
+
z.object({ configurations: z.array(HealthCheckConfigurationSchema) })
|
|
80
|
+
),
|
|
79
81
|
|
|
80
82
|
createConfiguration: _base
|
|
81
83
|
.meta({
|
|
82
84
|
userType: "authenticated",
|
|
83
|
-
|
|
85
|
+
access: [healthCheckAccess.configuration.manage],
|
|
84
86
|
})
|
|
85
87
|
.input(CreateHealthCheckConfigurationSchema)
|
|
86
88
|
.output(HealthCheckConfigurationSchema),
|
|
@@ -88,7 +90,7 @@ export const healthCheckContract = {
|
|
|
88
90
|
updateConfiguration: _base
|
|
89
91
|
.meta({
|
|
90
92
|
userType: "authenticated",
|
|
91
|
-
|
|
93
|
+
access: [healthCheckAccess.configuration.manage],
|
|
92
94
|
})
|
|
93
95
|
.input(
|
|
94
96
|
z.object({
|
|
@@ -101,7 +103,7 @@ export const healthCheckContract = {
|
|
|
101
103
|
deleteConfiguration: _base
|
|
102
104
|
.meta({
|
|
103
105
|
userType: "authenticated",
|
|
104
|
-
|
|
106
|
+
access: [healthCheckAccess.configuration.manage],
|
|
105
107
|
})
|
|
106
108
|
.input(z.string())
|
|
107
109
|
.output(z.void()),
|
|
@@ -113,7 +115,7 @@ export const healthCheckContract = {
|
|
|
113
115
|
getSystemConfigurations: _base
|
|
114
116
|
.meta({
|
|
115
117
|
userType: "authenticated",
|
|
116
|
-
|
|
118
|
+
access: [healthCheckAccess.configuration.read],
|
|
117
119
|
})
|
|
118
120
|
.input(z.string())
|
|
119
121
|
.output(z.array(HealthCheckConfigurationSchema)),
|
|
@@ -125,7 +127,7 @@ export const healthCheckContract = {
|
|
|
125
127
|
getSystemAssociations: _base
|
|
126
128
|
.meta({
|
|
127
129
|
userType: "authenticated",
|
|
128
|
-
|
|
130
|
+
access: [healthCheckAccess.configuration.read],
|
|
129
131
|
})
|
|
130
132
|
.input(z.object({ systemId: z.string() }))
|
|
131
133
|
.output(
|
|
@@ -142,7 +144,7 @@ export const healthCheckContract = {
|
|
|
142
144
|
associateSystem: _base
|
|
143
145
|
.meta({
|
|
144
146
|
userType: "authenticated",
|
|
145
|
-
|
|
147
|
+
access: [healthCheckAccess.configuration.manage],
|
|
146
148
|
})
|
|
147
149
|
.input(
|
|
148
150
|
z.object({
|
|
@@ -155,7 +157,7 @@ export const healthCheckContract = {
|
|
|
155
157
|
disassociateSystem: _base
|
|
156
158
|
.meta({
|
|
157
159
|
userType: "authenticated",
|
|
158
|
-
|
|
160
|
+
access: [healthCheckAccess.configuration.manage],
|
|
159
161
|
})
|
|
160
162
|
.input(
|
|
161
163
|
z.object({
|
|
@@ -166,13 +168,13 @@ export const healthCheckContract = {
|
|
|
166
168
|
.output(z.void()),
|
|
167
169
|
|
|
168
170
|
// ==========================================================================
|
|
169
|
-
// RETENTION CONFIGURATION (userType: "authenticated" with manage
|
|
171
|
+
// RETENTION CONFIGURATION (userType: "authenticated" with manage access)
|
|
170
172
|
// ==========================================================================
|
|
171
173
|
|
|
172
174
|
getRetentionConfig: _base
|
|
173
175
|
.meta({
|
|
174
176
|
userType: "authenticated",
|
|
175
|
-
|
|
177
|
+
access: [healthCheckAccess.configuration.read],
|
|
176
178
|
})
|
|
177
179
|
.input(
|
|
178
180
|
z.object({
|
|
@@ -189,7 +191,7 @@ export const healthCheckContract = {
|
|
|
189
191
|
updateRetentionConfig: _base
|
|
190
192
|
.meta({
|
|
191
193
|
userType: "authenticated",
|
|
192
|
-
|
|
194
|
+
access: [healthCheckAccess.configuration.manage],
|
|
193
195
|
})
|
|
194
196
|
.input(
|
|
195
197
|
z.object({
|
|
@@ -201,13 +203,13 @@ export const healthCheckContract = {
|
|
|
201
203
|
.output(z.void()),
|
|
202
204
|
|
|
203
205
|
// ==========================================================================
|
|
204
|
-
// HISTORY & STATUS (userType: "user" with read
|
|
206
|
+
// HISTORY & STATUS (userType: "user" with read access)
|
|
205
207
|
// ==========================================================================
|
|
206
208
|
|
|
207
209
|
getHistory: _base
|
|
208
210
|
.meta({
|
|
209
211
|
userType: "public",
|
|
210
|
-
|
|
212
|
+
access: [healthCheckAccess.status],
|
|
211
213
|
})
|
|
212
214
|
.input(
|
|
213
215
|
z.object({
|
|
@@ -228,12 +230,12 @@ export const healthCheckContract = {
|
|
|
228
230
|
|
|
229
231
|
/**
|
|
230
232
|
* Get detailed health check run history with full result data.
|
|
231
|
-
* Requires
|
|
233
|
+
* Requires access to view detailed run data including metadata.
|
|
232
234
|
*/
|
|
233
235
|
getDetailedHistory: _base
|
|
234
236
|
.meta({
|
|
235
237
|
userType: "authenticated",
|
|
236
|
-
|
|
238
|
+
access: [healthCheckAccess.details],
|
|
237
239
|
})
|
|
238
240
|
.input(
|
|
239
241
|
z.object({
|
|
@@ -260,7 +262,7 @@ export const healthCheckContract = {
|
|
|
260
262
|
getAggregatedHistory: _base
|
|
261
263
|
.meta({
|
|
262
264
|
userType: "public",
|
|
263
|
-
|
|
265
|
+
access: [healthCheckAccess.status],
|
|
264
266
|
})
|
|
265
267
|
.input(
|
|
266
268
|
z.object({
|
|
@@ -280,12 +282,12 @@ export const healthCheckContract = {
|
|
|
280
282
|
/**
|
|
281
283
|
* Get detailed aggregated health check history including strategy-specific data.
|
|
282
284
|
* Returns buckets with core metrics AND aggregatedResult from strategy.
|
|
283
|
-
* Requires healthCheckDetailsRead
|
|
285
|
+
* Requires healthCheckDetailsRead access rule.
|
|
284
286
|
*/
|
|
285
287
|
getDetailedAggregatedHistory: _base
|
|
286
288
|
.meta({
|
|
287
289
|
userType: "public",
|
|
288
|
-
|
|
290
|
+
access: [healthCheckAccess.details],
|
|
289
291
|
})
|
|
290
292
|
.input(
|
|
291
293
|
z.object({
|
|
@@ -309,7 +311,7 @@ export const healthCheckContract = {
|
|
|
309
311
|
getSystemHealthStatus: _base
|
|
310
312
|
.meta({
|
|
311
313
|
userType: "public",
|
|
312
|
-
|
|
314
|
+
access: [healthCheckAccess.status],
|
|
313
315
|
})
|
|
314
316
|
.input(z.object({ systemId: z.string() }))
|
|
315
317
|
.output(SystemHealthStatusResponseSchema),
|
|
@@ -321,7 +323,7 @@ export const healthCheckContract = {
|
|
|
321
323
|
getSystemHealthOverview: _base
|
|
322
324
|
.meta({
|
|
323
325
|
userType: "public",
|
|
324
|
-
|
|
326
|
+
access: [healthCheckAccess.status],
|
|
325
327
|
})
|
|
326
328
|
.input(z.object({ systemId: z.string() }))
|
|
327
329
|
.output(
|
package/src/schemas.ts
CHANGED
|
@@ -56,9 +56,11 @@ export type CollectorAssertion = z.infer<typeof CollectorAssertionSchema>;
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* A collector configuration entry within a health check.
|
|
59
|
-
* Each entry includes the collector ID, its config, and per-collector assertions.
|
|
59
|
+
* Each entry includes a unique ID, the collector type ID, its config, and per-collector assertions.
|
|
60
60
|
*/
|
|
61
61
|
export const CollectorConfigEntrySchema = z.object({
|
|
62
|
+
/** Unique ID for this collector instance (UUID) */
|
|
63
|
+
id: z.string(),
|
|
62
64
|
/** Fully-qualified collector ID (e.g., collector-hardware.cpu) */
|
|
63
65
|
collectorId: z.string(),
|
|
64
66
|
/** Collector-specific configuration */
|
|
@@ -270,7 +272,7 @@ export const DEFAULT_RETENTION_CONFIG: RetentionConfig = {
|
|
|
270
272
|
/**
|
|
271
273
|
* Base schema for aggregated health check data buckets.
|
|
272
274
|
* Contains core metrics only (no strategy-specific data).
|
|
273
|
-
* Used by getAggregatedHistory endpoint (healthCheckStatusRead
|
|
275
|
+
* Used by getAggregatedHistory endpoint (healthCheckStatusRead access).
|
|
274
276
|
*/
|
|
275
277
|
export const AggregatedBucketBaseSchema = z.object({
|
|
276
278
|
bucketStart: z.date(),
|
|
@@ -290,7 +292,7 @@ export type AggregatedBucketBase = z.infer<typeof AggregatedBucketBaseSchema>;
|
|
|
290
292
|
|
|
291
293
|
/**
|
|
292
294
|
* Extended schema with strategy-specific aggregated result.
|
|
293
|
-
* Used by getDetailedAggregatedHistory endpoint (healthCheckDetailsRead
|
|
295
|
+
* Used by getDetailedAggregatedHistory endpoint (healthCheckDetailsRead access).
|
|
294
296
|
*/
|
|
295
297
|
export const AggregatedBucketSchema = AggregatedBucketBaseSchema.extend({
|
|
296
298
|
aggregatedResult: z.record(z.string(), z.unknown()).optional(),
|
package/src/zod-health-result.ts
CHANGED
|
@@ -1,54 +1,95 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { HealthResultMeta } from "@checkstack/common";
|
|
2
3
|
|
|
3
4
|
// ============================================================================
|
|
4
5
|
// HEALTH RESULT REGISTRY - Typed metadata for chart annotations
|
|
5
6
|
// ============================================================================
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Numeric types:
|
|
11
|
-
* - line: Time series line chart for numeric metrics over time
|
|
12
|
-
* - bar: Bar chart for distributions (record of string to number)
|
|
13
|
-
* - pie: Pie chart for category distributions (record of string to number)
|
|
14
|
-
* - counter: Simple count display with trend indicator
|
|
15
|
-
* - gauge: Percentage gauge for rates/percentages (0-100)
|
|
16
|
-
*
|
|
17
|
-
* Non-numeric types:
|
|
18
|
-
* - boolean: Boolean indicator (success/failure, connected/disconnected)
|
|
19
|
-
* - text: Text display for string values
|
|
20
|
-
* - status: Status badge for error/warning states
|
|
9
|
+
* Registry for health result schema metadata.
|
|
10
|
+
* Used by auto-chart components for visualization inference.
|
|
21
11
|
*/
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
| "gauge"
|
|
28
|
-
| "boolean"
|
|
29
|
-
| "text"
|
|
30
|
-
| "status";
|
|
12
|
+
export const healthResultRegistry = z.registry<HealthResultMeta>();
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// BRANDED TYPES FOR COMPILE-TIME ENFORCEMENT
|
|
16
|
+
// ============================================================================
|
|
31
17
|
|
|
32
18
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
19
|
+
* Unique symbol for branding health result fields.
|
|
20
|
+
* This enables compile-time enforcement that developers use factory functions.
|
|
35
21
|
*/
|
|
36
|
-
|
|
37
|
-
/** The type of chart to render for this field */
|
|
38
|
-
"x-chart-type"?: ChartType;
|
|
39
|
-
/** Human-readable label for the chart (defaults to field name) */
|
|
40
|
-
"x-chart-label"?: string;
|
|
41
|
-
/** Unit suffix for values (e.g., 'ms', '%', 'req/s') */
|
|
42
|
-
"x-chart-unit"?: string;
|
|
43
|
-
/** Whether this field supports JSONPath assertions */
|
|
44
|
-
"x-jsonpath"?: boolean;
|
|
45
|
-
}
|
|
22
|
+
declare const HealthResultBrand: unique symbol;
|
|
46
23
|
|
|
47
24
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
25
|
+
* A branded Zod schema type that marks it as a health result field.
|
|
26
|
+
* Only schemas created via factory functions (healthResultNumber, healthResultString, etc.)
|
|
27
|
+
* carry this brand.
|
|
50
28
|
*/
|
|
51
|
-
export
|
|
29
|
+
export type HealthResultField<T extends z.ZodTypeAny> = T & {
|
|
30
|
+
[HealthResultBrand]: true;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Allowed field types in a health result schema.
|
|
35
|
+
* Supports direct fields, optional fields, and fields with defaults.
|
|
36
|
+
*/
|
|
37
|
+
export type HealthResultFieldType =
|
|
38
|
+
| HealthResultField<z.ZodTypeAny>
|
|
39
|
+
| z.ZodOptional<HealthResultField<z.ZodTypeAny>>
|
|
40
|
+
| z.ZodDefault<HealthResultField<z.ZodTypeAny>>
|
|
41
|
+
| z.ZodNullable<HealthResultField<z.ZodTypeAny>>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Constraint for health result schema shapes.
|
|
45
|
+
* All fields must use factory functions (healthResultNumber, healthResultString, etc.)
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // ✅ Valid - uses factory functions
|
|
50
|
+
* const validSchema = z.object({
|
|
51
|
+
* value: healthResultNumber({ "x-chart-type": "line" }),
|
|
52
|
+
* error: healthResultString({ "x-chart-type": "status" }).optional(),
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // ❌ Invalid - uses raw z.number()
|
|
56
|
+
* const invalidSchema = z.object({
|
|
57
|
+
* value: z.number(), // Type error!
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export type HealthResultShape = Record<string, HealthResultFieldType>;
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// HEALTH RESULT SCHEMA BUILDER
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create a health result schema that enforces the use of factory functions.
|
|
69
|
+
*
|
|
70
|
+
* This builder ensures all fields use healthResultNumber(), healthResultString(),
|
|
71
|
+
* healthResultBoolean(), or healthResultJSONPath() - raw Zod schemas like z.number()
|
|
72
|
+
* will cause a compile-time error.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* // ✅ Valid - all fields use factory functions
|
|
77
|
+
* const schema = healthResultSchema({
|
|
78
|
+
* responseTime: healthResultNumber({ "x-chart-type": "line", "x-chart-label": "Response Time" }),
|
|
79
|
+
* error: healthResultString({ "x-chart-type": "status" }).optional(),
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* // ❌ Invalid - raw z.number() causes type error
|
|
83
|
+
* const schema = healthResultSchema({
|
|
84
|
+
* value: z.number(), // Type error: not assignable to HealthResultFieldType
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function healthResultSchema<T extends HealthResultShape>(
|
|
89
|
+
shape: T
|
|
90
|
+
): z.ZodObject<T> {
|
|
91
|
+
return z.object(shape);
|
|
92
|
+
}
|
|
52
93
|
|
|
53
94
|
// ============================================================================
|
|
54
95
|
// TYPED HEALTH RESULT FACTORIES
|
|
@@ -69,28 +110,53 @@ type ChartMeta = Omit<HealthResultMeta, "x-jsonpath">;
|
|
|
69
110
|
* });
|
|
70
111
|
* ```
|
|
71
112
|
*/
|
|
72
|
-
export function healthResultString(
|
|
113
|
+
export function healthResultString(
|
|
114
|
+
meta: ChartMeta
|
|
115
|
+
): HealthResultField<z.ZodString> {
|
|
73
116
|
const schema = z.string();
|
|
74
117
|
schema.register(healthResultRegistry, meta);
|
|
75
|
-
return schema
|
|
118
|
+
return schema as HealthResultField<z.ZodString>;
|
|
76
119
|
}
|
|
77
120
|
|
|
78
121
|
/**
|
|
79
122
|
* Create a health result number field with typed chart metadata.
|
|
80
123
|
*/
|
|
81
|
-
export function healthResultNumber(
|
|
124
|
+
export function healthResultNumber(
|
|
125
|
+
meta: ChartMeta
|
|
126
|
+
): HealthResultField<z.ZodNumber> {
|
|
82
127
|
const schema = z.number();
|
|
83
128
|
schema.register(healthResultRegistry, meta);
|
|
84
|
-
return schema
|
|
129
|
+
return schema as HealthResultField<z.ZodNumber>;
|
|
85
130
|
}
|
|
86
131
|
|
|
87
132
|
/**
|
|
88
133
|
* Create a health result boolean field with typed chart metadata.
|
|
89
134
|
*/
|
|
90
|
-
export function healthResultBoolean(
|
|
135
|
+
export function healthResultBoolean(
|
|
136
|
+
meta: ChartMeta
|
|
137
|
+
): HealthResultField<z.ZodBoolean> {
|
|
91
138
|
const schema = z.boolean();
|
|
92
139
|
schema.register(healthResultRegistry, meta);
|
|
93
|
-
return schema
|
|
140
|
+
return schema as HealthResultField<z.ZodBoolean>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create a health result array field with typed chart metadata.
|
|
145
|
+
* For arrays of strings (e.g., DNS resolved values, list of hosts).
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* const resultSchema = healthResultSchema({
|
|
150
|
+
* resolvedValues: healthResultArray({ "x-chart-type": "text", "x-chart-label": "Values" }),
|
|
151
|
+
* });
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export function healthResultArray(
|
|
155
|
+
meta: ChartMeta
|
|
156
|
+
): HealthResultField<z.ZodArray<z.ZodString>> {
|
|
157
|
+
const schema = z.array(z.string());
|
|
158
|
+
schema.register(healthResultRegistry, meta);
|
|
159
|
+
return schema as HealthResultField<z.ZodArray<z.ZodString>>;
|
|
94
160
|
}
|
|
95
161
|
|
|
96
162
|
/**
|
|
@@ -106,10 +172,12 @@ export function healthResultBoolean(meta: ChartMeta) {
|
|
|
106
172
|
* });
|
|
107
173
|
* ```
|
|
108
174
|
*/
|
|
109
|
-
export function healthResultJSONPath(
|
|
175
|
+
export function healthResultJSONPath(
|
|
176
|
+
meta: ChartMeta
|
|
177
|
+
): HealthResultField<z.ZodString> {
|
|
110
178
|
const schema = z.string();
|
|
111
179
|
schema.register(healthResultRegistry, { ...meta, "x-jsonpath": true });
|
|
112
|
-
return schema
|
|
180
|
+
return schema as HealthResultField<z.ZodString>;
|
|
113
181
|
}
|
|
114
182
|
|
|
115
183
|
// ============================================================================
|
package/src/permissions.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { createPermission } from "@checkstack/common";
|
|
2
|
-
|
|
3
|
-
export const permissions = {
|
|
4
|
-
/**
|
|
5
|
-
* Status-only permission for viewing health check status.
|
|
6
|
-
* Enabled by default for anonymous and authenticated users.
|
|
7
|
-
*/
|
|
8
|
-
healthCheckStatusRead: createPermission(
|
|
9
|
-
"healthcheck.status",
|
|
10
|
-
"read",
|
|
11
|
-
"View Health Check Status",
|
|
12
|
-
{ isAuthenticatedDefault: true, isPublicDefault: true }
|
|
13
|
-
),
|
|
14
|
-
/**
|
|
15
|
-
* Configuration read permission for viewing health check configurations.
|
|
16
|
-
* Restricted to users with explicit grant.
|
|
17
|
-
*/
|
|
18
|
-
healthCheckRead: createPermission(
|
|
19
|
-
"healthcheck",
|
|
20
|
-
"read",
|
|
21
|
-
"Read Health Check Configurations"
|
|
22
|
-
),
|
|
23
|
-
/**
|
|
24
|
-
* Permission for viewing detailed health check run data including metadata.
|
|
25
|
-
* Allows access to extended visualizations without full management access.
|
|
26
|
-
*/
|
|
27
|
-
healthCheckDetailsRead: createPermission(
|
|
28
|
-
"healthcheck.details",
|
|
29
|
-
"read",
|
|
30
|
-
"View Detailed Health Check Run Data (Warning: This may expose sensitive data, depending on the health check strategy)"
|
|
31
|
-
),
|
|
32
|
-
healthCheckManage: createPermission(
|
|
33
|
-
"healthcheck",
|
|
34
|
-
"manage",
|
|
35
|
-
"Full management of Health Check Configurations"
|
|
36
|
-
),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const permissionList = Object.values(permissions);
|