@checkstack/healthcheck-backend 0.1.0 → 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 CHANGED
@@ -1,5 +1,106 @@
1
1
  # @checkstack/healthcheck-backend
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8e43507: # Teams and Resource-Level Access Control
8
+
9
+ This release introduces a comprehensive Teams system for organizing users and controlling access to resources at a granular level.
10
+
11
+ ## Features
12
+
13
+ ### Team Management
14
+
15
+ - Create, update, and delete teams with name and description
16
+ - Add/remove users from teams
17
+ - Designate team managers with elevated privileges
18
+ - View team membership and manager status
19
+
20
+ ### Resource-Level Access Control
21
+
22
+ - Grant teams access to specific resources (systems, health checks, incidents, maintenances)
23
+ - Configure read-only or manage permissions per team
24
+ - Resource-level "Team Only" mode that restricts access exclusively to team members
25
+ - Separate `resourceAccessSettings` table for resource-level settings (not per-grant)
26
+ - Automatic cleanup of grants when teams are deleted (database cascade)
27
+
28
+ ### Middleware Integration
29
+
30
+ - Extended `autoAuthMiddleware` to support resource access checks
31
+ - Single-resource pre-handler validation for detail endpoints
32
+ - Automatic list filtering for collection endpoints
33
+ - S2S endpoints for access verification
34
+
35
+ ### Frontend Components
36
+
37
+ - `TeamsTab` component for managing teams in Auth Settings
38
+ - `TeamAccessEditor` component for assigning team access to resources
39
+ - Resource-level "Team Only" toggle in `TeamAccessEditor`
40
+ - Integration into System, Health Check, Incident, and Maintenance editors
41
+
42
+ ## Breaking Changes
43
+
44
+ ### API Response Format Changes
45
+
46
+ List endpoints now return objects with named keys instead of arrays directly:
47
+
48
+ ```typescript
49
+ // Before
50
+ const systems = await catalogApi.getSystems();
51
+
52
+ // After
53
+ const { systems } = await catalogApi.getSystems();
54
+ ```
55
+
56
+ Affected endpoints:
57
+
58
+ - `catalog.getSystems` → `{ systems: [...] }`
59
+ - `healthcheck.getConfigurations` → `{ configurations: [...] }`
60
+ - `incident.listIncidents` → `{ incidents: [...] }`
61
+ - `maintenance.listMaintenances` → `{ maintenances: [...] }`
62
+
63
+ ### User Identity Enrichment
64
+
65
+ `RealUser` and `ApplicationUser` types now include `teamIds: string[]` field with team memberships.
66
+
67
+ ## Documentation
68
+
69
+ See `docs/backend/teams.md` for complete API reference and integration guide.
70
+
71
+ - 97c5a6b: Add UUID-based collector identification for better multiple collector support
72
+
73
+ **Breaking Change**: Existing health check configurations with collectors need to be recreated.
74
+
75
+ - Each collector instance now has a unique UUID assigned on creation
76
+ - Collector results are stored under the UUID key with `_collectorId` and `_assertionFailed` metadata
77
+ - Auto-charts correctly display separate charts for each collector instance
78
+ - Charts are now grouped by collector instance with clear headings
79
+ - Assertion status card shows pass/fail for each collector
80
+ - Renamed "Success" to "HTTP Success" to clarify it's about HTTP request success
81
+ - Fixed deletion of collectors not persisting to database
82
+ - Fixed duplicate React key warnings in auto-chart grid
83
+
84
+ ### Patch Changes
85
+
86
+ - 97c5a6b: Fix collector lookup when health check is assigned to a system
87
+
88
+ Collectors are now stored in the registry with their fully-qualified ID format (ownerPluginId.collectorId) to match how they are referenced in health check configurations. Added `qualifiedId` field to `RegisteredCollector` interface to avoid re-constructing the ID at query time. This fixes the "Collector not found" warning that occurred when executing health checks with assigned systems.
89
+
90
+ - Updated dependencies [97c5a6b]
91
+ - Updated dependencies [8e43507]
92
+ - Updated dependencies [8e43507]
93
+ - Updated dependencies [97c5a6b]
94
+ - @checkstack/backend-api@0.2.0
95
+ - @checkstack/catalog-common@1.0.0
96
+ - @checkstack/catalog-backend@0.1.0
97
+ - @checkstack/common@0.1.0
98
+ - @checkstack/healthcheck-common@0.2.0
99
+ - @checkstack/command-backend@0.0.4
100
+ - @checkstack/integration-backend@0.0.4
101
+ - @checkstack/queue-api@0.0.4
102
+ - @checkstack/signal-common@0.0.4
103
+
3
104
  ## 0.1.0
4
105
 
5
106
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-backend",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
@@ -61,7 +61,7 @@ const createMockCatalogClient = () => ({
61
61
  notifySystemSubscribers: mock(async () => ({ notifiedCount: 0 })),
62
62
  // Other methods not used in queue-executor
63
63
  getEntities: mock(async () => ({ systems: [], groups: [] })),
64
- getSystems: mock(async () => []),
64
+ getSystems: mock(async () => ({ systems: [] })),
65
65
  getGroups: mock(async () => []),
66
66
  createSystem: mock(async () => ({})),
67
67
  updateSystem: mock(async () => ({})),
@@ -316,6 +316,9 @@ async function executeHealthCheckJob(props: {
316
316
  continue;
317
317
  }
318
318
 
319
+ // Use the collector's UUID as the storage key
320
+ const storageKey = collectorEntry.id;
321
+
319
322
  try {
320
323
  const collectorResult = await registered.collector.execute({
321
324
  config: collectorEntry.config,
@@ -323,9 +326,6 @@ async function executeHealthCheckJob(props: {
323
326
  pluginId: configRow.strategyId,
324
327
  });
325
328
 
326
- // Store result under collector ID
327
- collectorResults[collectorEntry.collectorId] = collectorResult.result;
328
-
329
329
  // Check for collector-level error
330
330
  if (collectorResult.error) {
331
331
  hasCollectorError = true;
@@ -333,6 +333,7 @@ async function executeHealthCheckJob(props: {
333
333
  }
334
334
 
335
335
  // Evaluate per-collector assertions
336
+ let assertionFailed: string | undefined;
336
337
  if (
337
338
  collectorEntry.assertions &&
338
339
  collectorEntry.assertions.length > 0 &&
@@ -345,23 +346,31 @@ async function executeHealthCheckJob(props: {
345
346
  );
346
347
  if (failedAssertion) {
347
348
  hasCollectorError = true;
348
- errorMessage = `Assertion failed: ${failedAssertion.field} ${
349
+ assertionFailed = `${failedAssertion.field} ${
349
350
  failedAssertion.operator
350
351
  } ${failedAssertion.value ?? ""}`;
352
+ errorMessage = `Assertion failed: ${assertionFailed}`;
351
353
  logger.debug(
352
- `Collector ${collectorEntry.collectorId} assertion failed: ${errorMessage}`
354
+ `Collector ${storageKey} assertion failed: ${errorMessage}`
353
355
  );
354
356
  }
355
357
  }
358
+
359
+ // Store result under the collector's UUID, with collector type and assertion metadata
360
+ collectorResults[storageKey] = {
361
+ _collectorId: collectorEntry.collectorId, // Store the type for frontend schema linking
362
+ _assertionFailed: assertionFailed, // null if no assertion failed
363
+ ...collectorResult.result,
364
+ };
356
365
  } catch (error) {
357
366
  hasCollectorError = true;
358
367
  errorMessage = error instanceof Error ? error.message : String(error);
359
- collectorResults[collectorEntry.collectorId] = {
368
+ collectorResults[storageKey] = {
369
+ _collectorId: collectorEntry.collectorId,
370
+ _assertionFailed: undefined,
360
371
  error: errorMessage,
361
372
  };
362
- logger.debug(
363
- `Collector ${collectorEntry.collectorId} failed: ${errorMessage}`
364
- );
373
+ logger.debug(`Collector ${storageKey} failed: ${errorMessage}`);
365
374
  }
366
375
  }
367
376
  } finally {
@@ -82,11 +82,13 @@ describe("HealthCheck Router", () => {
82
82
  });
83
83
 
84
84
  const result = await call(router.getConfigurations, undefined, { context });
85
- expect(Array.isArray(result)).toBe(true);
85
+ expect(result).toHaveProperty("configurations");
86
+ expect(Array.isArray(result.configurations)).toBe(true);
86
87
  });
87
88
 
88
89
  it("getCollectors returns collectors for strategy", async () => {
89
90
  const mockCollector = {
91
+ qualifiedId: "collector-hardware.cpu",
90
92
  collector: {
91
93
  id: "cpu",
92
94
  displayName: "CPU Metrics",
package/src/router.ts CHANGED
@@ -68,9 +68,8 @@ export const createHealthCheckRouter = (
68
68
  pluginId,
69
69
  });
70
70
 
71
- return registeredCollectors.map(({ collector, ownerPlugin }) => ({
72
- // Fully-qualified ID: ownerPluginId.collectorId
73
- id: `${ownerPlugin.pluginId}.${collector.id}`,
71
+ return registeredCollectors.map(({ qualifiedId, collector }) => ({
72
+ id: qualifiedId,
74
73
  displayName: collector.displayName,
75
74
  description: collector.description,
76
75
  configSchema: toJsonSchema(collector.config.schema),
@@ -80,7 +79,7 @@ export const createHealthCheckRouter = (
80
79
  }),
81
80
 
82
81
  getConfigurations: os.getConfigurations.handler(async () => {
83
- return service.getConfigurations();
82
+ return { configurations: await service.getConfigurations() };
84
83
  }),
85
84
 
86
85
  createConfiguration: os.createConfiguration.handler(async ({ input }) => {