@checkstack/dependency-backend 1.2.0 → 1.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 +92 -0
- package/package.json +17 -17
- package/src/automations.test.ts +63 -8
- package/src/automations.ts +63 -23
- package/src/dependency-entity.test.ts +270 -0
- package/src/dependency-entity.ts +157 -0
- package/src/hooks.ts +7 -32
- package/src/index.ts +110 -27
- package/src/router.ts +72 -29
- package/src/services/dependency-service.ts +67 -3
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import type {
|
|
3
|
+
EntityChanged,
|
|
4
|
+
EntityHandle,
|
|
5
|
+
} from "@checkstack/automation-backend";
|
|
6
|
+
import { SYSTEM_ACTOR } from "@checkstack/common";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
DEPENDENCY_EDGE_ENTITY_KIND,
|
|
10
|
+
DEPENDENCY_TRIGGER_EVENTS,
|
|
11
|
+
DependencyEdgeStateSchema,
|
|
12
|
+
createDependencyEntityRead,
|
|
13
|
+
dependencyChangeToPayload,
|
|
14
|
+
deriveDependencyTriggerEvents,
|
|
15
|
+
removeDependencyEdge,
|
|
16
|
+
toDependencyEdgeState,
|
|
17
|
+
writeDependencyEdge,
|
|
18
|
+
type DependencyEdgeState,
|
|
19
|
+
} from "./dependency-entity";
|
|
20
|
+
import {
|
|
21
|
+
dependencyCreatedTrigger,
|
|
22
|
+
dependencyDeletedTrigger,
|
|
23
|
+
dependencyUpdatedTrigger,
|
|
24
|
+
} from "./automations";
|
|
25
|
+
import type { DependencyService } from "./services/dependency-service";
|
|
26
|
+
|
|
27
|
+
function change(overrides: Partial<EntityChanged> = {}): EntityChanged {
|
|
28
|
+
return {
|
|
29
|
+
kind: DEPENDENCY_EDGE_ENTITY_KIND,
|
|
30
|
+
id: "dep-1",
|
|
31
|
+
prev: {
|
|
32
|
+
sourceSystemId: "a",
|
|
33
|
+
targetSystemId: "b",
|
|
34
|
+
impactType: "degraded",
|
|
35
|
+
transitive: false,
|
|
36
|
+
},
|
|
37
|
+
next: {
|
|
38
|
+
sourceSystemId: "a",
|
|
39
|
+
targetSystemId: "b",
|
|
40
|
+
impactType: "critical",
|
|
41
|
+
transitive: false,
|
|
42
|
+
},
|
|
43
|
+
delta: { impactType: "critical" },
|
|
44
|
+
changedFields: ["impactType"],
|
|
45
|
+
actor: SYSTEM_ACTOR,
|
|
46
|
+
occurredAt: new Date().toISOString(),
|
|
47
|
+
...overrides,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("dependencyChangeToPayload — payloadSchema parity", () => {
|
|
52
|
+
it("a create payload validates against the created trigger's payloadSchema", () => {
|
|
53
|
+
const payload = dependencyChangeToPayload(
|
|
54
|
+
change({
|
|
55
|
+
prev: null,
|
|
56
|
+
next: {
|
|
57
|
+
sourceSystemId: "a",
|
|
58
|
+
targetSystemId: "b",
|
|
59
|
+
impactType: "degraded",
|
|
60
|
+
transitive: false,
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
const parsed = dependencyCreatedTrigger.payloadSchema.parse(payload);
|
|
65
|
+
expect(parsed.dependencyId).toBe("dep-1");
|
|
66
|
+
expect(parsed.sourceSystemId).toBe("a");
|
|
67
|
+
expect(parsed.targetSystemId).toBe("b");
|
|
68
|
+
expect(parsed.impactType).toBe("degraded");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("an update payload validates against the updated trigger's payloadSchema", () => {
|
|
72
|
+
const parsed = dependencyUpdatedTrigger.payloadSchema.parse(
|
|
73
|
+
dependencyChangeToPayload(change()),
|
|
74
|
+
);
|
|
75
|
+
expect(parsed.dependencyId).toBe("dep-1");
|
|
76
|
+
expect(parsed.impactType).toBe("critical");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("a delete payload validates against the deleted trigger's payloadSchema (endpoints from prev)", () => {
|
|
80
|
+
const parsed = dependencyDeletedTrigger.payloadSchema.parse(
|
|
81
|
+
dependencyChangeToPayload(change({ next: null })),
|
|
82
|
+
);
|
|
83
|
+
expect(parsed.dependencyId).toBe("dep-1");
|
|
84
|
+
// The deleted edge's endpoints come from `prev` (next is the tombstone).
|
|
85
|
+
expect(parsed.sourceSystemId).toBe("a");
|
|
86
|
+
expect(parsed.targetSystemId).toBe("b");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("deriveDependencyTriggerEvents", () => {
|
|
91
|
+
it("create → dependency.created", () => {
|
|
92
|
+
expect(
|
|
93
|
+
deriveDependencyTriggerEvents(
|
|
94
|
+
change({
|
|
95
|
+
prev: null,
|
|
96
|
+
next: {
|
|
97
|
+
sourceSystemId: "a",
|
|
98
|
+
targetSystemId: "b",
|
|
99
|
+
impactType: "degraded",
|
|
100
|
+
transitive: false,
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
104
|
+
).toEqual([DEPENDENCY_TRIGGER_EVENTS.created]);
|
|
105
|
+
});
|
|
106
|
+
it("tombstone → dependency.deleted", () => {
|
|
107
|
+
expect(deriveDependencyTriggerEvents(change({ next: null }))).toEqual([
|
|
108
|
+
DEPENDENCY_TRIGGER_EVENTS.deleted,
|
|
109
|
+
]);
|
|
110
|
+
});
|
|
111
|
+
it("update → dependency.updated", () => {
|
|
112
|
+
expect(deriveDependencyTriggerEvents(change())).toEqual([
|
|
113
|
+
DEPENDENCY_TRIGGER_EVENTS.updated,
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("DependencyEdgeStateSchema", () => {
|
|
119
|
+
it("parses the reactive subset", () => {
|
|
120
|
+
const parsed = DependencyEdgeStateSchema.parse({
|
|
121
|
+
sourceSystemId: "a",
|
|
122
|
+
targetSystemId: "b",
|
|
123
|
+
impactType: "degraded",
|
|
124
|
+
transitive: true,
|
|
125
|
+
});
|
|
126
|
+
expect(parsed.transitive).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("toDependencyEdgeState", () => {
|
|
131
|
+
it("projects the reactive subset off a full dependency", () => {
|
|
132
|
+
expect(
|
|
133
|
+
toDependencyEdgeState({
|
|
134
|
+
sourceSystemId: "a",
|
|
135
|
+
targetSystemId: "b",
|
|
136
|
+
impactType: "critical",
|
|
137
|
+
transitive: true,
|
|
138
|
+
}),
|
|
139
|
+
).toEqual({
|
|
140
|
+
sourceSystemId: "a",
|
|
141
|
+
targetSystemId: "b",
|
|
142
|
+
impactType: "critical",
|
|
143
|
+
transitive: true,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("createDependencyEntityRead", () => {
|
|
149
|
+
it("routes the batched read straight to the service (plugin-backed)", async () => {
|
|
150
|
+
const seen: ReadonlyArray<string>[] = [];
|
|
151
|
+
const service = {
|
|
152
|
+
async getManyEntityStates(ids: ReadonlyArray<string>) {
|
|
153
|
+
seen.push(ids);
|
|
154
|
+
return {
|
|
155
|
+
"dep-1": {
|
|
156
|
+
sourceSystemId: "a",
|
|
157
|
+
targetSystemId: "b",
|
|
158
|
+
impactType: "degraded" as const,
|
|
159
|
+
transitive: false,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
} as unknown as DependencyService;
|
|
164
|
+
const read = createDependencyEntityRead(service);
|
|
165
|
+
const out = await read(["dep-1", "dep-2"]);
|
|
166
|
+
expect(seen).toEqual([["dep-1", "dep-2"]]);
|
|
167
|
+
expect(out["dep-1"]).toEqual({
|
|
168
|
+
sourceSystemId: "a",
|
|
169
|
+
targetSystemId: "b",
|
|
170
|
+
impactType: "degraded",
|
|
171
|
+
transitive: false,
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("writeDependencyEdge", () => {
|
|
177
|
+
it("drives the write through handle.mutate keyed by dependency id", async () => {
|
|
178
|
+
const calls: Array<{ id: string; next: DependencyEdgeState }> = [];
|
|
179
|
+
const handle = {
|
|
180
|
+
kind: DEPENDENCY_EDGE_ENTITY_KIND,
|
|
181
|
+
async mutate(input: {
|
|
182
|
+
id: string;
|
|
183
|
+
apply: () => Promise<DependencyEdgeState>;
|
|
184
|
+
}) {
|
|
185
|
+
const next = await input.apply();
|
|
186
|
+
calls.push({ id: input.id, next });
|
|
187
|
+
return next;
|
|
188
|
+
},
|
|
189
|
+
} as unknown as EntityHandle<DependencyEdgeState>;
|
|
190
|
+
let applied = false;
|
|
191
|
+
await writeDependencyEdge({
|
|
192
|
+
handle,
|
|
193
|
+
dependencyId: "dep-9",
|
|
194
|
+
apply: async () => {
|
|
195
|
+
applied = true;
|
|
196
|
+
return {
|
|
197
|
+
sourceSystemId: "a",
|
|
198
|
+
targetSystemId: "b",
|
|
199
|
+
impactType: "critical",
|
|
200
|
+
transitive: true,
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
expect(applied).toBe(true);
|
|
205
|
+
expect(calls).toEqual([
|
|
206
|
+
{
|
|
207
|
+
id: "dep-9",
|
|
208
|
+
next: {
|
|
209
|
+
sourceSystemId: "a",
|
|
210
|
+
targetSystemId: "b",
|
|
211
|
+
impactType: "critical",
|
|
212
|
+
transitive: true,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
]);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("still runs the plugin write when no handle is wired", async () => {
|
|
219
|
+
let applied = false;
|
|
220
|
+
await writeDependencyEdge({
|
|
221
|
+
handle: undefined,
|
|
222
|
+
dependencyId: "x",
|
|
223
|
+
apply: async () => {
|
|
224
|
+
applied = true;
|
|
225
|
+
return {
|
|
226
|
+
sourceSystemId: "a",
|
|
227
|
+
targetSystemId: "b",
|
|
228
|
+
impactType: "degraded",
|
|
229
|
+
transitive: false,
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
expect(applied).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("removeDependencyEdge", () => {
|
|
238
|
+
it("tombstones via handle.remove({ apply })", async () => {
|
|
239
|
+
const removed: string[] = [];
|
|
240
|
+
let deleted = false;
|
|
241
|
+
const handle = {
|
|
242
|
+
kind: DEPENDENCY_EDGE_ENTITY_KIND,
|
|
243
|
+
async remove(input: { id: string; apply: () => Promise<void> }) {
|
|
244
|
+
await input.apply();
|
|
245
|
+
removed.push(input.id);
|
|
246
|
+
},
|
|
247
|
+
} as unknown as EntityHandle<DependencyEdgeState>;
|
|
248
|
+
await removeDependencyEdge({
|
|
249
|
+
handle,
|
|
250
|
+
dependencyId: "dep-9",
|
|
251
|
+
apply: async () => {
|
|
252
|
+
deleted = true;
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
expect(deleted).toBe(true);
|
|
256
|
+
expect(removed).toEqual(["dep-9"]);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("still runs the delete when no handle is wired", async () => {
|
|
260
|
+
let deleted = false;
|
|
261
|
+
await removeDependencyEdge({
|
|
262
|
+
handle: undefined,
|
|
263
|
+
dependencyId: "x",
|
|
264
|
+
apply: async () => {
|
|
265
|
+
deleted = true;
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
expect(deleted).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reactive `dependency-edge` entity (reactive automation engine §10.5).
|
|
3
|
+
*
|
|
4
|
+
* Model B PLUGIN-BACKED entity: the `dependencies` table is authoritative AND
|
|
5
|
+
* IS the entity's current-state storage — there is NO framework `entity_state`
|
|
6
|
+
* row for a dependency edge. `defineEntity({ read })` makes that plugin state
|
|
7
|
+
* reactive: every reactive-state write goes through `handle.mutate`, whose
|
|
8
|
+
* `apply()` performs the REAL `dependencies` write via the `DependencyService`
|
|
9
|
+
* (the plugin's own db/tx) and returns the resulting reactive subset
|
|
10
|
+
* `{ sourceSystemId, targetSystemId, impactType, transitive }`. The framework
|
|
11
|
+
* snapshots `prev` via `read`, appends the transition log (its own db), and
|
|
12
|
+
* emits `ENTITY_CHANGED`. The change → trigger-event deriver reproduces
|
|
13
|
+
* `dependency.created/.updated/.deleted` so automations keep firing.
|
|
14
|
+
*
|
|
15
|
+
* `dependency_derived_states` (the propagation cursor) and the
|
|
16
|
+
* `dependency.impact_propagated` notification stay NON-reactive (§5):
|
|
17
|
+
* derived per-system state is reachable via the `health` entity, and
|
|
18
|
+
* impact propagation is a fan-out signal, not a single mutable field.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { ImpactTypeSchema } from "@checkstack/dependency-common";
|
|
22
|
+
import type {
|
|
23
|
+
EntityChangeDeriver,
|
|
24
|
+
EntityChangePayloadMapper,
|
|
25
|
+
EntityHandle,
|
|
26
|
+
EntityRead,
|
|
27
|
+
} from "@checkstack/automation-backend";
|
|
28
|
+
import {
|
|
29
|
+
withEntityRemove,
|
|
30
|
+
withEntityWrite,
|
|
31
|
+
} from "@checkstack/automation-backend";
|
|
32
|
+
|
|
33
|
+
import type { DependencyService } from "./services/dependency-service";
|
|
34
|
+
|
|
35
|
+
export const DEPENDENCY_EDGE_ENTITY_KIND = "dependency-edge";
|
|
36
|
+
|
|
37
|
+
export const DependencyEdgeStateSchema = z.object({
|
|
38
|
+
sourceSystemId: z.string(),
|
|
39
|
+
targetSystemId: z.string(),
|
|
40
|
+
impactType: ImpactTypeSchema,
|
|
41
|
+
transitive: z.boolean(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export type DependencyEdgeState = z.infer<typeof DependencyEdgeStateSchema>;
|
|
45
|
+
|
|
46
|
+
export const DEPENDENCY_TRIGGER_EVENTS = {
|
|
47
|
+
created: "dependency.created",
|
|
48
|
+
updated: "dependency.updated",
|
|
49
|
+
deleted: "dependency.deleted",
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* `dependency-edge` change → trigger events. Create (`prev === null`),
|
|
54
|
+
* tombstone (`next === null`), or a field update map to the matching
|
|
55
|
+
* lifecycle event (the handle suppresses no-op diffs, so an update always
|
|
56
|
+
* carries a real change).
|
|
57
|
+
*/
|
|
58
|
+
export const deriveDependencyTriggerEvents: EntityChangeDeriver = (changed) => {
|
|
59
|
+
if (changed.prev === null && changed.next !== null) {
|
|
60
|
+
return [DEPENDENCY_TRIGGER_EVENTS.created];
|
|
61
|
+
}
|
|
62
|
+
if (changed.next === null) {
|
|
63
|
+
return [DEPENDENCY_TRIGGER_EVENTS.deleted];
|
|
64
|
+
}
|
|
65
|
+
return [DEPENDENCY_TRIGGER_EVENTS.updated];
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Map a `dependency-edge` change to the domain-named `trigger.payload` the
|
|
70
|
+
* dependency triggers declare via `payloadSchema` (`dependencyId`,
|
|
71
|
+
* `sourceSystemId`, `targetSystemId`, `impactType`). Restores the keys
|
|
72
|
+
* operators read (`trigger.payload.dependencyId`, `.sourceSystemId`, …) that
|
|
73
|
+
* the generic change shape omits.
|
|
74
|
+
*
|
|
75
|
+
* `dependencyId` is the entity id. The edge fields are read from `next`, or
|
|
76
|
+
* from `prev` on a tombstone (`deleted`), so a delete trigger still carries
|
|
77
|
+
* the removed edge's endpoints. `impactType` is omitted on a delete (the
|
|
78
|
+
* `deleted` schema does not declare it).
|
|
79
|
+
*/
|
|
80
|
+
export const dependencyChangeToPayload: EntityChangePayloadMapper = (
|
|
81
|
+
changed,
|
|
82
|
+
) => {
|
|
83
|
+
const source = changed.next ?? changed.prev;
|
|
84
|
+
const impactType = source?.["impactType"];
|
|
85
|
+
return {
|
|
86
|
+
dependencyId: changed.id,
|
|
87
|
+
sourceSystemId: source?.["sourceSystemId"],
|
|
88
|
+
targetSystemId: source?.["targetSystemId"],
|
|
89
|
+
...(impactType === undefined ? {} : { impactType }),
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build the PLUGIN-BACKED `read` accessor for the `dependency-edge` entity.
|
|
95
|
+
* Routes straight to the service's batched authoritative read over the
|
|
96
|
+
* `dependencies` table — no framework storage.
|
|
97
|
+
*/
|
|
98
|
+
export function createDependencyEntityRead(
|
|
99
|
+
service: DependencyService,
|
|
100
|
+
): EntityRead<DependencyEdgeState> {
|
|
101
|
+
return (ids) => service.getManyEntityStates(ids);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Project a dependency row onto the reactive `{ sourceSystemId,
|
|
106
|
+
* targetSystemId, impactType, transitive }` subset. The router/action service
|
|
107
|
+
* writes return the full dependency; this is the `apply()` return for
|
|
108
|
+
* `handle.mutate`.
|
|
109
|
+
*/
|
|
110
|
+
export function toDependencyEdgeState(dependency: {
|
|
111
|
+
sourceSystemId: string;
|
|
112
|
+
targetSystemId: string;
|
|
113
|
+
impactType: DependencyEdgeState["impactType"];
|
|
114
|
+
transitive: boolean;
|
|
115
|
+
}): DependencyEdgeState {
|
|
116
|
+
return {
|
|
117
|
+
sourceSystemId: dependency.sourceSystemId,
|
|
118
|
+
targetSystemId: dependency.targetSystemId,
|
|
119
|
+
impactType: dependency.impactType,
|
|
120
|
+
transitive: dependency.transitive,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Drive a reactive-state `dependency-edge` write through `handle.mutate`
|
|
126
|
+
* (§10.5). `apply` performs the REAL `dependencies` write via the service
|
|
127
|
+
* (the plugin's own db/tx) and returns the new reactive state. The framework
|
|
128
|
+
* snapshots `prev`, appends the transition log, and emits `ENTITY_CHANGED`
|
|
129
|
+
* (the deriver turns that into `dependency.created/.updated`).
|
|
130
|
+
*
|
|
131
|
+
* When no handle is available (tests construct the router without one), the
|
|
132
|
+
* write still runs — the entity reactivity is layered on top, never required
|
|
133
|
+
* for the underlying write to succeed.
|
|
134
|
+
*/
|
|
135
|
+
export async function writeDependencyEdge(args: {
|
|
136
|
+
handle: EntityHandle<DependencyEdgeState> | undefined;
|
|
137
|
+
dependencyId: string;
|
|
138
|
+
apply: () => Promise<DependencyEdgeState>;
|
|
139
|
+
}): Promise<void> {
|
|
140
|
+
const { handle, dependencyId, apply } = args;
|
|
141
|
+
await withEntityWrite({ handle, id: dependencyId, apply });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Drive a dependency-edge tombstone through `handle.remove` (§10.5). `apply`
|
|
146
|
+
* performs the REAL delete via the service; the framework records the
|
|
147
|
+
* tombstone transition and emits a tombstone change (the deriver fires
|
|
148
|
+
* `dependency.deleted`). Without a handle, the delete still runs.
|
|
149
|
+
*/
|
|
150
|
+
export async function removeDependencyEdge(args: {
|
|
151
|
+
handle: EntityHandle<DependencyEdgeState> | undefined;
|
|
152
|
+
dependencyId: string;
|
|
153
|
+
apply: () => Promise<void>;
|
|
154
|
+
}): Promise<void> {
|
|
155
|
+
const { handle, dependencyId, apply } = args;
|
|
156
|
+
await withEntityRemove({ handle, id: dependencyId, apply });
|
|
157
|
+
}
|
package/src/hooks.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { createHook } from "@checkstack/backend-api";
|
|
2
|
-
import type {
|
|
3
|
-
DerivedState,
|
|
4
|
-
ImpactType,
|
|
5
|
-
} from "@checkstack/dependency-common";
|
|
2
|
+
import type { DerivedState } from "@checkstack/dependency-common";
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
5
|
* Dependency hooks for cross-plugin communication.
|
|
@@ -14,34 +11,12 @@ import type {
|
|
|
14
11
|
* in the editor.
|
|
15
12
|
*/
|
|
16
13
|
export const dependencyHooks = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
targetSystemId: string;
|
|
24
|
-
impactType: ImpactType;
|
|
25
|
-
}>("dependency.created"),
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Emitted when a dependency is updated.
|
|
29
|
-
*/
|
|
30
|
-
dependencyUpdated: createHook<{
|
|
31
|
-
dependencyId: string;
|
|
32
|
-
sourceSystemId: string;
|
|
33
|
-
targetSystemId: string;
|
|
34
|
-
impactType: ImpactType;
|
|
35
|
-
}>("dependency.updated"),
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Emitted when a dependency is deleted.
|
|
39
|
-
*/
|
|
40
|
-
dependencyDeleted: createHook<{
|
|
41
|
-
dependencyId: string;
|
|
42
|
-
sourceSystemId: string;
|
|
43
|
-
targetSystemId: string;
|
|
44
|
-
}>("dependency.deleted"),
|
|
14
|
+
// The `dependency.created` / `.updated` / `.deleted` hooks were removed in
|
|
15
|
+
// Phase 4 (§10.5): dependency edges are now the reactive `dependency-edge`
|
|
16
|
+
// entity, whose change deriver fires the matching `dependency.created` /
|
|
17
|
+
// `.updated` / `.deleted` trigger events through Stage-1 routing. The
|
|
18
|
+
// `impact_propagated` hook below is KEPT — it is a derived fan-out signal
|
|
19
|
+
// (per-downstream deltas), not a single mutable entity field.
|
|
45
20
|
|
|
46
21
|
/**
|
|
47
22
|
* Emitted when an upstream system's state change has propagated
|