@checkstack/backend-api 0.17.1 → 0.18.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 +98 -0
- package/package.json +4 -4
- package/src/actor.test.ts +29 -0
- package/src/actor.ts +27 -0
- package/src/collector-strategy.ts +12 -0
- package/src/event-bus-types.ts +13 -4
- package/src/hooks.ts +14 -1
- package/src/index.ts +1 -0
- package/src/plugin-system.ts +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,103 @@
|
|
|
1
1
|
# @checkstack/backend-api
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6d52276: feat(automation): expose `trigger.actor` so automations can filter on who/what caused an event
|
|
8
|
+
|
|
9
|
+
Every platform event now carries an **actor** - the user, application (API
|
|
10
|
+
client), service (backend-to-backend), or `system` (background /
|
|
11
|
+
unauthenticated) that caused it - and the automation engine surfaces it to
|
|
12
|
+
automations as `trigger.actor`. This lets a trigger filter gate on the
|
|
13
|
+
origin of the event it reacts to:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
{{ trigger.actor.type == "system" }} # auto-created by the platform
|
|
17
|
+
{{ trigger.actor.type == "user" }} # a human
|
|
18
|
+
{{ trigger.actor.id == "app-deploybot" }} # a specific application
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`trigger.actor` is available on **every** trigger - it is injected by the
|
|
22
|
+
platform, not declared per trigger - and editor autocomplete + Run Script
|
|
23
|
+
context types include `trigger.actor.{type,id,name}`.
|
|
24
|
+
|
|
25
|
+
How it works:
|
|
26
|
+
|
|
27
|
+
- **`@checkstack/common`** adds the canonical `Actor` type / `ActorSchema`
|
|
28
|
+
and `SYSTEM_ACTOR`.
|
|
29
|
+
- **`@checkstack/backend-api`** adds `resolveActor(user)` and a
|
|
30
|
+
`HookEventMeta` envelope. The hook listener / `onHook` signature gains an
|
|
31
|
+
optional second `meta` argument (additive, backward compatible).
|
|
32
|
+
- **`@checkstack/backend`** wraps emitted hooks in an envelope so the actor
|
|
33
|
+
travels with the payload through the distributed queue, unwrapping it
|
|
34
|
+
before delivery. The RPC emit path captures the authenticated caller;
|
|
35
|
+
background emits default to the system actor. Raw/legacy queue data is
|
|
36
|
+
treated as a system-actor payload, so delivery stays backward compatible.
|
|
37
|
+
- **`@checkstack/automation-backend`** threads the actor into the dispatch
|
|
38
|
+
scope (`trigger.actor`), available to trigger filters, top-level
|
|
39
|
+
conditions, and all run templates, and persisted in the run's scope
|
|
40
|
+
snapshot. Manual runs are attributed to the invoking user.
|
|
41
|
+
- **`@checkstack/automation-common`** / **`@checkstack/automation-frontend`**
|
|
42
|
+
expose `trigger.actor` in the editor variable scope and the generated
|
|
43
|
+
Run Script `context.trigger.actor` types.
|
|
44
|
+
|
|
45
|
+
No database migration and no per-trigger schema changes: the actor rides as
|
|
46
|
+
event-envelope metadata and in the run scope snapshot.
|
|
47
|
+
|
|
48
|
+
- 35bc682: feat(healthcheck): expose check + system run-context to script collectors
|
|
49
|
+
|
|
50
|
+
Script health checks can now read which check and system a run is for.
|
|
51
|
+
Previously shell scripts got only a curated env whitelist and inline
|
|
52
|
+
scripts only `context.config`, so a script had no built-in way to know
|
|
53
|
+
its own check name or the system it was checking.
|
|
54
|
+
|
|
55
|
+
- `@checkstack/backend-api`: new `CollectorRunContext` type
|
|
56
|
+
(`{ check: { id, name, intervalSeconds }, system: { id, name } }`) and
|
|
57
|
+
an optional `runContext` param on `CollectorStrategy.execute`. Optional,
|
|
58
|
+
so existing collector implementations are unaffected.
|
|
59
|
+
- Shell-script collector: injects reserved `CHECKSTACK_CHECK_ID`,
|
|
60
|
+
`CHECKSTACK_CHECK_NAME`, `CHECKSTACK_CHECK_INTERVAL_SECONDS`,
|
|
61
|
+
`CHECKSTACK_SYSTEM_ID`, `CHECKSTACK_SYSTEM_NAME` env vars (user-supplied
|
|
62
|
+
`env` still wins on collision).
|
|
63
|
+
- Inline-script collector: exposes `context.check` and `context.system`
|
|
64
|
+
alongside `context.config`; the inline-script editor now types them for
|
|
65
|
+
autocomplete.
|
|
66
|
+
- Shell editors (health-check collectors and automation shell actions) now
|
|
67
|
+
also suggest the user's own `env` (JSON) keys as `$NAME` completions, via
|
|
68
|
+
the new exported `customShellEnvVars` helper. Keys that aren't valid shell
|
|
69
|
+
identifiers are omitted.
|
|
70
|
+
- Fix: the Typefox `CodeEditor` captured a stale `onChange` at editor start,
|
|
71
|
+
so editing one `DynamicForm` field reverted sibling fields changed since
|
|
72
|
+
mount (e.g. typing in a shell `script` field wiped an unsaved `env` value,
|
|
73
|
+
or deleted a sibling automation action added after mount). The change
|
|
74
|
+
handler now routes through a ref to the current `onChange`.
|
|
75
|
+
- Fix: focusing a JSON editor threw "LanguageStatusService.addStatus is not
|
|
76
|
+
supported" because the standalone service set omitted `ILanguageStatusService`.
|
|
77
|
+
That one service is now registered via `serviceOverrides`.
|
|
78
|
+
- Fix: the automation trigger card nested a `<Badge>` (a `<div>`) inside a
|
|
79
|
+
`<p>`, producing a `validateDOMNesting` warning. Switched the wrapper to a
|
|
80
|
+
`<div>`.
|
|
81
|
+
- Local runs (`queue-executor`) and satellite runs both populate the
|
|
82
|
+
context. `SatelliteAssignment` (and the `getAssignmentsForSatellite`
|
|
83
|
+
RPC output) gained optional `configName` / `systemName` so the metadata
|
|
84
|
+
reaches satellite-side execution; `HealthCheckService` resolves the
|
|
85
|
+
system name via the catalog client.
|
|
86
|
+
|
|
87
|
+
BREAKING CHANGE: `createHealthCheckRouter` now requires a `catalogClient`
|
|
88
|
+
option (used to resolve system names for satellite assignments). Update
|
|
89
|
+
call sites to pass the catalog RPC client.
|
|
90
|
+
|
|
91
|
+
### Patch Changes
|
|
92
|
+
|
|
93
|
+
- Updated dependencies [6d52276]
|
|
94
|
+
- Updated dependencies [35bc682]
|
|
95
|
+
- @checkstack/common@0.12.0
|
|
96
|
+
- @checkstack/healthcheck-common@1.3.0
|
|
97
|
+
- @checkstack/signal-common@0.2.5
|
|
98
|
+
- @checkstack/cache-api@0.3.6
|
|
99
|
+
- @checkstack/queue-api@0.3.6
|
|
100
|
+
|
|
3
101
|
## 0.17.1
|
|
4
102
|
|
|
5
103
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/backend-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@checkstack/common": "0.11.0",
|
|
14
|
-
"@checkstack/healthcheck-common": "1.
|
|
15
|
-
"@checkstack/cache-api": "0.3.
|
|
16
|
-
"@checkstack/queue-api": "0.3.
|
|
14
|
+
"@checkstack/healthcheck-common": "1.2.0",
|
|
15
|
+
"@checkstack/cache-api": "0.3.5",
|
|
16
|
+
"@checkstack/queue-api": "0.3.5",
|
|
17
17
|
"@checkstack/signal-common": "0.2.4",
|
|
18
18
|
"@orpc/client": "^1.13.14",
|
|
19
19
|
"@orpc/contract": "^1.13.14",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { SYSTEM_ACTOR } from "@checkstack/common";
|
|
3
|
+
import { resolveActor } from "./actor";
|
|
4
|
+
|
|
5
|
+
describe("resolveActor", () => {
|
|
6
|
+
it("falls back to the system actor when there is no caller", () => {
|
|
7
|
+
expect(resolveActor(undefined)).toEqual(SYSTEM_ACTOR);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("maps a real (human) user", () => {
|
|
11
|
+
expect(
|
|
12
|
+
resolveActor({ type: "user", id: "user-1", name: "Nico" }),
|
|
13
|
+
).toEqual({ type: "user", id: "user-1", name: "Nico" });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("maps an application (API client)", () => {
|
|
17
|
+
expect(
|
|
18
|
+
resolveActor({ type: "application", id: "app-deploybot", name: "Deploy Bot" }),
|
|
19
|
+
).toEqual({ type: "application", id: "app-deploybot", name: "Deploy Bot" });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("maps a service to its originating plugin id", () => {
|
|
23
|
+
expect(resolveActor({ type: "service", pluginId: "healthcheck" })).toEqual({
|
|
24
|
+
type: "service",
|
|
25
|
+
id: "healthcheck",
|
|
26
|
+
name: "healthcheck",
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
package/src/actor.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SYSTEM_ACTOR, type Actor } from "@checkstack/common";
|
|
2
|
+
import type { AuthUser } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the canonical platform {@link Actor} for an event from the
|
|
6
|
+
* authenticated caller. Background / unauthenticated emits (no `user`)
|
|
7
|
+
* resolve to the system actor, so every emitted event carries an actor.
|
|
8
|
+
*
|
|
9
|
+
* - {@link RealUser} -> `{ type: "user" }`
|
|
10
|
+
* - {@link ApplicationUser} -> `{ type: "application" }`
|
|
11
|
+
* - {@link ServiceUser} -> `{ type: "service", id: pluginId }`
|
|
12
|
+
* - `undefined` -> {@link SYSTEM_ACTOR}
|
|
13
|
+
*/
|
|
14
|
+
export function resolveActor(user?: AuthUser): Actor {
|
|
15
|
+
if (!user) return SYSTEM_ACTOR;
|
|
16
|
+
switch (user.type) {
|
|
17
|
+
case "user": {
|
|
18
|
+
return { type: "user", id: user.id, name: user.name };
|
|
19
|
+
}
|
|
20
|
+
case "application": {
|
|
21
|
+
return { type: "application", id: user.id, name: user.name };
|
|
22
|
+
}
|
|
23
|
+
case "service": {
|
|
24
|
+
return { type: "service", id: user.pluginId, name: user.pluginId };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -17,6 +17,15 @@ export interface CollectorResult<TResult> {
|
|
|
17
17
|
error?: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Curated, read-only metadata about the health check + system a collector
|
|
22
|
+
* run is for. Metadata only - never secrets/config.
|
|
23
|
+
*/
|
|
24
|
+
export interface CollectorRunContext {
|
|
25
|
+
check: { id: string; name: string; intervalSeconds: number };
|
|
26
|
+
system: { id: string; name: string };
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
/**
|
|
21
30
|
* Generic collector strategy interface.
|
|
22
31
|
*
|
|
@@ -71,12 +80,15 @@ export interface CollectorStrategy<
|
|
|
71
80
|
* @param params.config - Validated collector configuration
|
|
72
81
|
* @param params.client - Connected transport client
|
|
73
82
|
* @param params.pluginId - ID of the transport strategy invoking this collector
|
|
83
|
+
* @param params.runContext - Curated, read-only metadata about the health
|
|
84
|
+
* check + system this run is for (metadata only, never secrets/config)
|
|
74
85
|
* @returns Collector result with typed metadata
|
|
75
86
|
*/
|
|
76
87
|
execute(params: {
|
|
77
88
|
config: TConfig;
|
|
78
89
|
client: TClient;
|
|
79
90
|
pluginId: string;
|
|
91
|
+
runContext?: CollectorRunContext;
|
|
80
92
|
}): Promise<CollectorResult<TResult>>;
|
|
81
93
|
|
|
82
94
|
/**
|
package/src/event-bus-types.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Hook,
|
|
3
|
+
HookEventMeta,
|
|
4
|
+
HookSubscribeOptions,
|
|
5
|
+
HookUnsubscribe,
|
|
6
|
+
} from "./hooks";
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* EventBus interface for dependency injection
|
|
@@ -7,22 +12,26 @@ export interface EventBus {
|
|
|
7
12
|
subscribe<T>(
|
|
8
13
|
pluginId: string,
|
|
9
14
|
hook: Hook<T>,
|
|
10
|
-
listener: (payload: T) => Promise<void>,
|
|
15
|
+
listener: (payload: T, meta?: HookEventMeta) => Promise<void>,
|
|
11
16
|
options?: HookSubscribeOptions
|
|
12
17
|
): Promise<HookUnsubscribe>;
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* Emit a hook through the distributed queue system.
|
|
16
21
|
* All instances receive broadcast hooks; one instance handles work-queue hooks.
|
|
22
|
+
*
|
|
23
|
+
* `meta` carries event-envelope metadata (the acting `actor`). When omitted,
|
|
24
|
+
* the bus defaults to the system actor, so every emitted hook carries an
|
|
25
|
+
* actor even when emitted from a background/unauthenticated context.
|
|
17
26
|
*/
|
|
18
|
-
emit<T>(hook: Hook<T>, payload: T): Promise<void>;
|
|
27
|
+
emit<T>(hook: Hook<T>, payload: T, meta?: HookEventMeta): Promise<void>;
|
|
19
28
|
|
|
20
29
|
/**
|
|
21
30
|
* Emit a hook locally only (not distributed).
|
|
22
31
|
* Use for instance-local hooks that should only run on THIS instance.
|
|
23
32
|
* Uses Promise.allSettled to ensure one listener error doesn't block others.
|
|
24
33
|
*/
|
|
25
|
-
emitLocal<T>(hook: Hook<T>, payload: T): Promise<void>;
|
|
34
|
+
emitLocal<T>(hook: Hook<T>, payload: T, meta?: HookEventMeta): Promise<void>;
|
|
26
35
|
|
|
27
36
|
shutdown(): Promise<void>;
|
|
28
37
|
}
|
package/src/hooks.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccessRule } from "@checkstack/common";
|
|
1
|
+
import type { AccessRule, Actor } from "@checkstack/common";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Hook definition for type-safe event emission and subscription
|
|
@@ -8,6 +8,19 @@ export interface Hook<T = unknown> {
|
|
|
8
8
|
_type?: T; // Phantom type for TypeScript inference
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Envelope metadata that travels alongside every emitted hook payload,
|
|
13
|
+
* independent of the hook's typed payload. Injected centrally at emit time
|
|
14
|
+
* (from the request context, defaulting to the system actor) and delivered to
|
|
15
|
+
* subscribers as the optional second listener argument.
|
|
16
|
+
*
|
|
17
|
+
* The automation engine reads `actor` and exposes it to automations as
|
|
18
|
+
* `trigger.actor`, so a trigger filter can gate on who/what caused the event.
|
|
19
|
+
*/
|
|
20
|
+
export interface HookEventMeta {
|
|
21
|
+
actor: Actor;
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
/**
|
|
12
25
|
* Create a typed hook
|
|
13
26
|
*/
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from "./rpc";
|
|
|
17
17
|
export * from "./test-utils";
|
|
18
18
|
export * from "./hooks";
|
|
19
19
|
export * from "./event-bus-types";
|
|
20
|
+
export * from "./actor";
|
|
20
21
|
export * from "./plugin-source";
|
|
21
22
|
export * from "./plugin-artifact-store";
|
|
22
23
|
export * from "./notification-strategy";
|
package/src/plugin-system.ts
CHANGED
|
@@ -2,7 +2,12 @@ import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
|
2
2
|
import { ServiceRef } from "./service-ref";
|
|
3
3
|
import { ExtensionPoint } from "./extension-point";
|
|
4
4
|
import type { AccessRule, PluginMetadata } from "@checkstack/common";
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
Hook,
|
|
7
|
+
HookEventMeta,
|
|
8
|
+
HookSubscribeOptions,
|
|
9
|
+
HookUnsubscribe,
|
|
10
|
+
} from "./hooks";
|
|
6
11
|
import { Router } from "@orpc/server";
|
|
7
12
|
import { RpcContext } from "./rpc";
|
|
8
13
|
import { AnyContractRouter } from "@orpc/contract";
|
|
@@ -48,7 +53,7 @@ export type AfterPluginsReadyContext = {
|
|
|
48
53
|
*/
|
|
49
54
|
onHook: <T>(
|
|
50
55
|
hook: Hook<T>,
|
|
51
|
-
listener: (payload: T) => Promise<void>,
|
|
56
|
+
listener: (payload: T, meta?: HookEventMeta) => Promise<void>,
|
|
52
57
|
options?: HookSubscribeOptions,
|
|
53
58
|
) => HookUnsubscribe;
|
|
54
59
|
/**
|