@checkstack/healthcheck-backend 0.16.1 → 0.16.3

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,37 @@
1
1
  # @checkstack/healthcheck-backend
2
2
 
3
+ ## 0.16.3
4
+
5
+ ### Patch Changes
6
+
7
+ - b53a40e: Fix GitOps entity update failures due to pending error records
8
+
9
+ - Ensured the `existingEntityId` parameter in the Reconciler engine is set to `undefined` instead of a `"pending-UUID"` when handling entities that failed to sync initially.
10
+ - Hardened the `Healthcheck` GitOps kind logic to explicitly ignore `"pending-"` IDs, preventing SQL update errors on synthetic provenance IDs.
11
+ - Fixed a bug where resolving YAML syntax errors would cause the subsequent sync to fail with `failed query: update [...]` because it attempted to update the nonexistent `"pending-"` entity instead of creating a new one.
12
+
13
+ - Updated dependencies [b53a40e]
14
+ - @checkstack/gitops-backend@0.2.2
15
+ - @checkstack/catalog-backend@0.5.3
16
+ - @checkstack/satellite-backend@0.2.11
17
+
18
+ ## 0.16.2
19
+
20
+ ### Patch Changes
21
+
22
+ - 57d54de: Fix GitOps Healthcheck reconciliation engine and Kind Registry UI
23
+
24
+ - Mandated fully qualified IDs for all healthcheck strategies and collector definitions.
25
+ - Refactored the Kind Registry UI to display schema documentation in beautifully formatted, interactive YAML examples.
26
+ - Entity Envelope Fields and Base Spec Schema are now displayed in collapsed accordions.
27
+ - Fixed condition logic that broke the collector documentation display.
28
+ - Enhanced UX by dynamically injecting fully-qualified strategy variants directly into the YAML examples.
29
+
30
+ - Updated dependencies [57d54de]
31
+ - @checkstack/gitops-backend@0.2.1
32
+ - @checkstack/catalog-backend@0.5.2
33
+ - @checkstack/satellite-backend@0.2.10
34
+
3
35
  ## 0.16.1
4
36
 
5
37
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-backend",
3
- "version": "0.16.1",
3
+ "version": "0.16.3",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "checkstack": {
@@ -14,8 +14,8 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@checkstack/backend-api": "0.12.0",
17
- "@checkstack/catalog-backend": "0.5.0",
18
- "@checkstack/catalog-common": "1.4.0",
17
+ "@checkstack/catalog-backend": "0.5.1",
18
+ "@checkstack/catalog-common": "1.4.1",
19
19
  "@checkstack/command-backend": "0.1.19",
20
20
  "@checkstack/common": "0.6.5",
21
21
  "@checkstack/gitops-backend": "0.2.0",
@@ -25,7 +25,7 @@
25
25
  "@checkstack/integration-backend": "0.1.19",
26
26
  "@checkstack/maintenance-common": "0.4.9",
27
27
  "@checkstack/queue-api": "0.2.13",
28
- "@checkstack/satellite-backend": "0.2.8",
28
+ "@checkstack/satellite-backend": "0.2.9",
29
29
  "@checkstack/signal-common": "0.1.9",
30
30
  "@hono/zod-validator": "^0.7.6",
31
31
  "drizzle-orm": "^0.45.0",
@@ -135,23 +135,31 @@ const cpuCollectorConfigSchema = z.object({
135
135
  function createMockHealthCheckRegistry() {
136
136
  const strategies = new Map<
137
137
  string,
138
- { id: string; config: Versioned<unknown> }
138
+ { strategy: any; ownerPluginId: string; qualifiedId: string }
139
139
  >();
140
140
 
141
- // Register a test strategy
142
- strategies.set("postgres", {
141
+ const strategy = {
143
142
  id: "postgres",
143
+ displayName: "PostgreSQL",
144
+ description: "test",
144
145
  config: new Versioned({
145
146
  version: 1,
146
147
  schema: postgresConfigSchema,
147
148
  }),
149
+ };
150
+
151
+ // Register a test strategy
152
+ strategies.set("postgres", {
153
+ strategy,
154
+ ownerPluginId: "mock",
155
+ qualifiedId: "postgres",
148
156
  });
149
157
 
150
158
  return {
151
- getStrategy: (id: string) => strategies.get(id) as ReturnType<import("@checkstack/backend-api").HealthCheckRegistry["getStrategy"]>,
159
+ getStrategy: (id: string) => strategies.get(id)?.strategy as ReturnType<import("@checkstack/backend-api").HealthCheckRegistry["getStrategy"]>,
152
160
  getStrategies: () =>
153
- [...strategies.values()] as ReturnType<import("@checkstack/backend-api").HealthCheckRegistry["getStrategies"]>,
154
- getStrategiesWithMeta: () => [],
161
+ [...strategies.values()].map(s => s.strategy) as ReturnType<import("@checkstack/backend-api").HealthCheckRegistry["getStrategies"]>,
162
+ getStrategiesWithMeta: () => [...strategies.values()] as any,
155
163
  register: () => {},
156
164
  };
157
165
  }
@@ -251,6 +259,36 @@ describe("Healthcheck GitOps Kind: Healthcheck", () => {
251
259
  expect(mockService.configs[0].strategyId).toBe("postgres");
252
260
  });
253
261
 
262
+ it("creates a new configuration when existingEntityId is a pending error record", async () => {
263
+ const kind = buildKind();
264
+
265
+ const result = await kind.reconcile({
266
+ entity: {
267
+ apiVersion: CHECKSTACK_API_VERSION,
268
+ kind: "Healthcheck",
269
+ metadata: { name: "payment-db-check" },
270
+ spec: {
271
+ strategy: "postgres",
272
+ intervalSeconds: 30,
273
+ config: {
274
+ host: "db.internal",
275
+ port: 5432,
276
+ database: "payments",
277
+ user: "monitor",
278
+ password: "secret",
279
+ },
280
+ },
281
+ },
282
+ existingEntityId: "pending-12345",
283
+ context: mockContext,
284
+ });
285
+
286
+ expect(result.entityId).toBe("hc-1");
287
+ expect(mockService.createConfiguration).toHaveBeenCalledTimes(1);
288
+ expect(mockService.updateConfiguration).not.toHaveBeenCalled();
289
+ expect(mockService.configs).toHaveLength(1);
290
+ });
291
+
254
292
  it("updates an existing configuration using existingEntityId", async () => {
255
293
  const kind = buildKind();
256
294
 
@@ -117,18 +117,21 @@ export function buildHealthcheckKind(
117
117
  const service = deps.createService();
118
118
  const spec = entity.spec;
119
119
 
120
- // Look up strategy first we need its typed schema for secret resolution
120
+ // Look up strategy using strictly the fully qualified ID
121
121
  const healthCheckRegistry = deps.getHealthCheckRegistry();
122
- const strategy = healthCheckRegistry.getStrategy(spec.strategy);
123
- if (!strategy) {
122
+ const allStrategies = healthCheckRegistry.getStrategiesWithMeta();
123
+ const matchStrategy = allStrategies.find((s) => s.qualifiedId === spec.strategy);
124
+
125
+ if (!matchStrategy) {
124
126
  throw new Error(
125
127
  `Unknown health check strategy "${spec.strategy}". ` +
126
- `Available: ${healthCheckRegistry
127
- .getStrategies()
128
- .map((s) => s.id)
128
+ `Available: ${allStrategies
129
+ .map((s) => s.qualifiedId)
129
130
  .join(", ")}`,
130
131
  );
131
132
  }
133
+
134
+ const strategy = matchStrategy.strategy;
132
135
 
133
136
  // Resolve secrets using the strategy's typed schema.
134
137
  // Only fields marked with configString({ "x-secret": true }) get resolved.
@@ -151,17 +154,22 @@ export function buildHealthcheckKind(
151
154
  const resolvedCollectors = spec.collectors
152
155
  ? await Promise.all(
153
156
  spec.collectors.map(async (c) => {
157
+ // Look up collector using strictly the fully qualified ID
154
158
  const collectorReg = deps.getCollectorRegistry();
155
- const registered = collectorReg.getCollector(c.collectorId);
156
- if (!registered) {
159
+ const allCollectors = collectorReg.getCollectors();
160
+ const matchCollector = allCollectors.find(
161
+ (col) => col.qualifiedId === c.collectorId
162
+ );
163
+
164
+ if (!matchCollector) {
157
165
  throw new Error(
158
166
  `Unknown collector "${c.collectorId}". ` +
159
- `Available: ${collectorReg
160
- .getCollectors()
167
+ `Available: ${allCollectors
161
168
  .map((col) => col.qualifiedId)
162
169
  .join(", ")}`,
163
170
  );
164
171
  }
172
+ const registered = matchCollector;
165
173
 
166
174
  // Resolve secrets using the collector's typed schema
167
175
  const { resolved: resolvedCollectorConfig } =
@@ -188,7 +196,7 @@ export function buildHealthcheckKind(
188
196
  // Create or update configuration
189
197
  const displayName = entity.metadata.title ?? entity.metadata.name;
190
198
 
191
- if (existingEntityId) {
199
+ if (existingEntityId && !existingEntityId.startsWith("pending-")) {
192
200
  await service.updateConfiguration(existingEntityId, {
193
201
  name: displayName,
194
202
  strategyId: spec.strategy,
@@ -370,15 +378,24 @@ export function registerHealthcheckGitOpsDocumentation({
370
378
  apiVersion: CHECKSTACK_API_VERSION,
371
379
  kind: "Healthcheck",
372
380
  fieldPath: "config",
373
- variantId: registered.ownerPluginId,
381
+ variantId: registered.qualifiedId,
374
382
  label: registered.strategy.displayName,
375
- description: `ID: ${registered.ownerPluginId}\n\n${registered.strategy.description}`,
383
+ description: `ID: ${registered.qualifiedId}\n\n${registered.strategy.description}`,
376
384
  schema: registered.strategy.config.schema,
377
385
  });
378
386
  }
379
387
 
380
388
  // 2. Register documentation for collector configs (fieldPath: "collectors[].config")
381
389
  for (const registered of collectorRegistry.getCollectors()) {
390
+ const supportedStrategyIds = healthCheckRegistry
391
+ .getStrategiesWithMeta()
392
+ .filter((s) =>
393
+ registered.collector.supportedPlugins.some(
394
+ (p) => p.pluginId === s.ownerPluginId,
395
+ ),
396
+ )
397
+ .map((s) => s.qualifiedId);
398
+
382
399
  kindRegistry.registerSpecSchemaDocumentation({
383
400
  apiVersion: CHECKSTACK_API_VERSION,
384
401
  kind: "Healthcheck",
@@ -390,9 +407,7 @@ export function registerHealthcheckGitOpsDocumentation({
390
407
  conditions: [
391
408
  {
392
409
  fieldPath: "config",
393
- variantIds: registered.collector.supportedPlugins.map(
394
- (p) => p.pluginId,
395
- ),
410
+ variantIds: supportedStrategyIds,
396
411
  },
397
412
  ],
398
413
  });