@checkstack/incident-common 1.6.3 → 1.7.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 +149 -0
- package/package.json +7 -7
- package/src/access.ts +10 -1
- package/src/index.ts +13 -1
- package/src/rpc-contract.test.ts +38 -0
- package/src/rpc-contract.ts +38 -0
- package/src/schemas.ts +43 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,154 @@
|
|
|
1
1
|
# @checkstack/incident-common
|
|
2
2
|
|
|
3
|
+
## 1.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e430fbe: Add "Mass delete" and "Mass resolve" to the Incidents and Maintenances lists,
|
|
8
|
+
authorized per item (RLAC).
|
|
9
|
+
|
|
10
|
+
The incidents and maintenances list pages now support multi-select with a bulk
|
|
11
|
+
action bar. A user may only select and act on entries they are allowed to
|
|
12
|
+
MANAGE: a row's checkbox appears only when the caller can manage it (the same
|
|
13
|
+
`canAccess(id)` gate as the per-row actions), so a team-scoped member sees
|
|
14
|
+
checkboxes only for their team's entries. Mass delete confirms before running;
|
|
15
|
+
mass resolve (incidents) and mass complete (maintenances, the "resolve"
|
|
16
|
+
equivalent = close, status -> completed) skip entries that are already
|
|
17
|
+
resolved/completed. Each action reports a per-id partial-success summary
|
|
18
|
+
(e.g. "3 deleted, 1 skipped").
|
|
19
|
+
|
|
20
|
+
New backend procedures: `incident.bulkDeleteIncidents`,
|
|
21
|
+
`incident.bulkResolveIncidents`, `maintenance.bulkDeleteMaintenances`, and
|
|
22
|
+
`maintenance.bulkCloseMaintenances`. Each authorizes EACH id against the
|
|
23
|
+
caller's manage grant and never fails open: unauthorized ids are filtered out
|
|
24
|
+
before the handler runs and returned as `forbidden`; missing ids as `notFound`;
|
|
25
|
+
a per-id failure is isolated as `error` without aborting the batch. Per-id cache
|
|
26
|
+
invalidation, realtime signals, and subscriber notifications run for every
|
|
27
|
+
success so dashboards and status pages stay consistent.
|
|
28
|
+
|
|
29
|
+
Platform: a new `instanceAccess` mode `bulkManage: { idsParam }` is the
|
|
30
|
+
enforcement point for bulk writes. Before the handler runs, `autoAuthMiddleware`
|
|
31
|
+
partitions the input id array into the caller's manageable subset and the denied
|
|
32
|
+
remainder and exposes both on `context.bulkAccess` (fail-closed on an S2S
|
|
33
|
+
error). The boot-time contract validator (`validateContractInstanceAccess`)
|
|
34
|
+
accepts `bulkManage` as one of the mutually-exclusive scoping modes, marks its
|
|
35
|
+
type team-scopable, and cross-checks `idsParam` against the input schema.
|
|
36
|
+
|
|
37
|
+
State and scale: authorization is derived per request from the shared team-grant
|
|
38
|
+
store via the existing auth S2S path (no process-local state); the read returns
|
|
39
|
+
the same answer on every pod. No database migration.
|
|
40
|
+
|
|
41
|
+
- 0d912a3: Make the frontend fully RLAC-aware so team-scoped users see and can use exactly
|
|
42
|
+
what the backend already authorises - no more, no less. Previously every nav
|
|
43
|
+
entry, route, management page, create button, per-row action, and resource
|
|
44
|
+
picker gated purely on a user's GLOBAL access rule, so a user whose team manages
|
|
45
|
+
a system saw none of the surfaces the backend would happily let them use, and
|
|
46
|
+
(where a page did render) could select systems they don't manage and only fail
|
|
47
|
+
after submit.
|
|
48
|
+
|
|
49
|
+
Platform primitives (on `AccessApi`, from `@checkstack/frontend-api`, implemented
|
|
50
|
+
in `@checkstack/auth-frontend`). Each ORs the global RBAC rule with team-derived
|
|
51
|
+
(ReBAC) grants, so a global-rule holder always sees everything:
|
|
52
|
+
|
|
53
|
+
- `useCanCreate({ accessRule, objectType, parentType? })` - may the user create
|
|
54
|
+
this type (global rule, a team `creator` grant, or managing a parent resource).
|
|
55
|
+
- `useCanAccessType({ accessRule, objectType, parentType? })` - may the user
|
|
56
|
+
reach a management SURFACE for this type at all (create capability OR managing
|
|
57
|
+
any existing object of the type / its parent). Powers route guards, sidebar
|
|
58
|
+
entries, and a management page's top-level `allowed`.
|
|
59
|
+
- `useResourceAccess({ accessRule, objectType, resourceIds })` - a `canAccess(id)`
|
|
60
|
+
predicate for per-row controls and for filtering resource pickers.
|
|
61
|
+
|
|
62
|
+
Backed by three authenticated `auth` RPC procedures - `canCreate`,
|
|
63
|
+
`myManageableTypes`, and `listMyAccessibleResources` - the frontend-facing
|
|
64
|
+
mirrors of the existing S2S authorization endpoints, resolved against the
|
|
65
|
+
caller's own team grants.
|
|
66
|
+
|
|
67
|
+
Route/nav gating is now capability-aware: a route may declare
|
|
68
|
+
`manageCapability: { objectType, parentType? }`; the route guard and sidebar then
|
|
69
|
+
show/allow it for team-scoped users via `myManageableTypes`. Applied to the
|
|
70
|
+
catalog, incident, maintenance, SLO, healthcheck, automation, and status-page
|
|
71
|
+
management routes. The route guard resolves this through a single
|
|
72
|
+
`useRouteAccess` hook with a constant hook count, since the guard is reconciled
|
|
73
|
+
in place as the URL changes (a conditional hook there would trip the rules of
|
|
74
|
+
hooks).
|
|
75
|
+
|
|
76
|
+
Resource types are now typed, plugin-qualified constants. A new
|
|
77
|
+
`resourceType(pluginMetadata, localType)` factory in `@checkstack/common` mints a
|
|
78
|
+
nominal `ResourceType`, and each `*-common` package exports its constants (e.g.
|
|
79
|
+
`catalogResourceTypes.system`, `incidentResourceTypes.incident`). The capability
|
|
80
|
+
APIs accept `ResourceType`, so a mistyped `"catalog.system"` string now fails
|
|
81
|
+
typecheck instead of silently breaking a gate.
|
|
82
|
+
|
|
83
|
+
Resource pickers now offer only what the backend will accept:
|
|
84
|
+
|
|
85
|
+
- Incident and maintenance "Affected Systems" pickers show only systems the user
|
|
86
|
+
manages (or all with the global rule), matching the backend's requirement of
|
|
87
|
+
MANAGE on every referenced system.
|
|
88
|
+
- SLO creation is now system-scoped end to end: `createObjective` gains a
|
|
89
|
+
`catalog.system` parent gate (managing the target system authorises creating an
|
|
90
|
+
SLO for it, like incident/maintenance), and the SLO editor's system picker is
|
|
91
|
+
filtered to manageable systems.
|
|
92
|
+
- Catalog group and environment membership (add-to-group / add-to-environment,
|
|
93
|
+
per-row and bulk) is gated on managing the system being (re)assigned.
|
|
94
|
+
- The health-check assignment surface (Assignment IDE + the system-detail
|
|
95
|
+
"Health Checks" action) requires MANAGE on the target system.
|
|
96
|
+
|
|
97
|
+
Catalog membership chips only render a removable "x" for systems the user
|
|
98
|
+
manages (removing a group/environment membership requires managing the system),
|
|
99
|
+
and the Dependency Map only lets a user originate an edge from a system they
|
|
100
|
+
manage (the source is access-checked; the target is not).
|
|
101
|
+
|
|
102
|
+
Owning-team correctness: a parent-gated creator (team member, no global rule)
|
|
103
|
+
who left the owning team unset previously created an object with no team grant -
|
|
104
|
+
which they then could not edit. The `authorizeCreate` parent-gate path now
|
|
105
|
+
resolves an owning team instead of silently orphaning the object (auto-assigns
|
|
106
|
+
when the caller belongs to exactly one team, requires an explicit choice when
|
|
107
|
+
several), and the `TeamOwnershipPicker` marks the field required and
|
|
108
|
+
auto-selects the sole eligible team.
|
|
109
|
+
|
|
110
|
+
Dependency writes are fixed to authorize on the SOURCE system. `createDependency`
|
|
111
|
+
/ `updateDependency` / `deleteDependency` previously used `instanceAccess:
|
|
112
|
+
{ idParam: "systemId" }`, which made the middleware look for a `dependency` grant
|
|
113
|
+
keyed by the system id - a grant that never exists - so every team-scoped source
|
|
114
|
+
manager was denied ("Access denied to resource dependency:<systemId>"). They now
|
|
115
|
+
`parentScope` on `catalog.system` manage, so managing the source system
|
|
116
|
+
authorises editing its dependencies (the target is not access-checked), matching
|
|
117
|
+
health-check assignment.
|
|
118
|
+
|
|
119
|
+
The backend authorization changes are limited to: the new read-only capability
|
|
120
|
+
procedures (`canCreate` / `myManageableTypes` / `listMyAccessibleResources`), the
|
|
121
|
+
SLO create parent gate, the `authorizeCreate` owning-team resolution, and the
|
|
122
|
+
dependency source-scope fix. Everything else only aligns the UI with
|
|
123
|
+
authorization the backend already enforced.
|
|
124
|
+
|
|
125
|
+
### Patch Changes
|
|
126
|
+
|
|
127
|
+
- Updated dependencies [d1b71b6]
|
|
128
|
+
- Updated dependencies [d9f4654]
|
|
129
|
+
- Updated dependencies [e430fbe]
|
|
130
|
+
- Updated dependencies [eab80e3]
|
|
131
|
+
- Updated dependencies [259b93c]
|
|
132
|
+
- Updated dependencies [53666a7]
|
|
133
|
+
- Updated dependencies [0d912a3]
|
|
134
|
+
- @checkstack/notification-common@1.5.0
|
|
135
|
+
- @checkstack/frontend-api@0.13.0
|
|
136
|
+
- @checkstack/common@0.19.0
|
|
137
|
+
- @checkstack/catalog-common@2.6.0
|
|
138
|
+
- @checkstack/signal-common@0.2.14
|
|
139
|
+
|
|
140
|
+
## 1.6.4
|
|
141
|
+
|
|
142
|
+
### Patch Changes
|
|
143
|
+
|
|
144
|
+
- Updated dependencies [defb97b]
|
|
145
|
+
- Updated dependencies [defb97b]
|
|
146
|
+
- @checkstack/catalog-common@2.5.0
|
|
147
|
+
- @checkstack/common@0.18.0
|
|
148
|
+
- @checkstack/frontend-api@0.12.1
|
|
149
|
+
- @checkstack/notification-common@1.4.2
|
|
150
|
+
- @checkstack/signal-common@0.2.13
|
|
151
|
+
|
|
3
152
|
## 1.6.3
|
|
4
153
|
|
|
5
154
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/incident-common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@checkstack/common": "0.
|
|
16
|
-
"@checkstack/catalog-common": "2.
|
|
17
|
-
"@checkstack/frontend-api": "0.
|
|
18
|
-
"@checkstack/notification-common": "1.
|
|
19
|
-
"@checkstack/signal-common": "0.2.
|
|
15
|
+
"@checkstack/common": "0.19.0",
|
|
16
|
+
"@checkstack/catalog-common": "2.6.0",
|
|
17
|
+
"@checkstack/frontend-api": "0.13.0",
|
|
18
|
+
"@checkstack/notification-common": "1.5.0",
|
|
19
|
+
"@checkstack/signal-common": "0.2.14",
|
|
20
20
|
"@orpc/contract": "^1.14.4",
|
|
21
21
|
"zod": "^4.2.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"typescript": "^5.7.2",
|
|
25
25
|
"@checkstack/tsconfig": "0.0.7",
|
|
26
|
-
"@checkstack/scripts": "0.
|
|
26
|
+
"@checkstack/scripts": "0.7.0"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"typecheck": "tsgo -b",
|
package/src/access.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { accessPair } from "@checkstack/common";
|
|
1
|
+
import { accessPair, resourceType } from "@checkstack/common";
|
|
2
2
|
import { pluginMetadata } from "./plugin-metadata";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Plugin-qualified resource type ids for the incident plugin's resources.
|
|
6
|
+
* Reference these (never a raw `"incident.incident"` string) at capability call
|
|
7
|
+
* sites so a typo fails typecheck.
|
|
8
|
+
*/
|
|
9
|
+
export const incidentResourceTypes = {
|
|
10
|
+
incident: resourceType(pluginMetadata, "incident"),
|
|
11
|
+
};
|
|
12
|
+
|
|
4
13
|
/**
|
|
5
14
|
* Access rules for the Incident plugin.
|
|
6
15
|
*/
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
incidentAccess,
|
|
3
|
+
incidentAccessRules,
|
|
4
|
+
incidentResourceTypes,
|
|
5
|
+
} from "./access";
|
|
2
6
|
export {
|
|
3
7
|
incidentContract,
|
|
4
8
|
IncidentApi,
|
|
@@ -16,6 +20,10 @@ export {
|
|
|
16
20
|
CreateIncidentInputSchema,
|
|
17
21
|
UpdateIncidentInputSchema,
|
|
18
22
|
AddIncidentUpdateInputSchema,
|
|
23
|
+
BulkIncidentActionStatusEnum,
|
|
24
|
+
BulkIncidentActionResultSchema,
|
|
25
|
+
BulkIncidentIdsInputSchema,
|
|
26
|
+
BulkResolveIncidentsInputSchema,
|
|
19
27
|
type IncidentStatus,
|
|
20
28
|
type IncidentSeverity,
|
|
21
29
|
type Incident,
|
|
@@ -27,6 +35,10 @@ export {
|
|
|
27
35
|
type CreateIncidentInput,
|
|
28
36
|
type UpdateIncidentInput,
|
|
29
37
|
type AddIncidentUpdateInput,
|
|
38
|
+
type BulkIncidentActionStatus,
|
|
39
|
+
type BulkIncidentActionResult,
|
|
40
|
+
type BulkIncidentIdsInput,
|
|
41
|
+
type BulkResolveIncidentsInput,
|
|
30
42
|
} from "./schemas";
|
|
31
43
|
export { IncidentDetailsSlot, IncidentStatusSlot } from "./slots";
|
|
32
44
|
export {
|
package/src/rpc-contract.test.ts
CHANGED
|
@@ -39,6 +39,7 @@ interface InstanceAccess {
|
|
|
39
39
|
recordKey?: string;
|
|
40
40
|
create?: InstanceAccessCreate;
|
|
41
41
|
parentScope?: InstanceAccessParentScope;
|
|
42
|
+
bulkManage?: { idsParam?: string };
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
function instanceAccessFor(
|
|
@@ -146,3 +147,40 @@ describe("incident contract createIncident carries create-mode team ownership",
|
|
|
146
147
|
expect(access?.create).toBeUndefined();
|
|
147
148
|
});
|
|
148
149
|
});
|
|
150
|
+
|
|
151
|
+
describe("incident contract bulk actions gate per-id via bulkManage", () => {
|
|
152
|
+
// Both bulk mutations authorize EACH id against the caller's manage grant
|
|
153
|
+
// through the platform `bulkManage` instance-access mode, keyed on the `ids`
|
|
154
|
+
// input array (RLAC: never fail open on a bulk WRITE).
|
|
155
|
+
const bulkProcs: Array<keyof typeof incidentContract> = [
|
|
156
|
+
"bulkDeleteIncidents",
|
|
157
|
+
"bulkResolveIncidents",
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
for (const proc of bulkProcs) {
|
|
161
|
+
test(`${proc} declares instanceAccess.bulkManage.idsParam = "ids"`, () => {
|
|
162
|
+
const access = instanceAccessFor(proc);
|
|
163
|
+
expect(access).toBeDefined();
|
|
164
|
+
expect(access?.bulkManage?.idsParam).toBe("ids");
|
|
165
|
+
// Exactly one mode: no object/list/record/create scoping alongside it.
|
|
166
|
+
expect(access?.idParam).toBeUndefined();
|
|
167
|
+
expect(access?.listKey).toBeUndefined();
|
|
168
|
+
expect(access?.recordKey).toBeUndefined();
|
|
169
|
+
expect(access?.create).toBeUndefined();
|
|
170
|
+
expect(access?.global).toBeUndefined();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test(`${proc} is gated on the incident.manage access rule`, () => {
|
|
174
|
+
const procDef = incidentContract[proc] as unknown as Record<
|
|
175
|
+
string,
|
|
176
|
+
unknown
|
|
177
|
+
>;
|
|
178
|
+
const orpc = procDef["~orpc"] as {
|
|
179
|
+
meta?: { access?: Array<{ id: string; resource: string; level: string }> };
|
|
180
|
+
};
|
|
181
|
+
const rule = orpc.meta?.access?.[0];
|
|
182
|
+
expect(rule?.resource).toBe("incident");
|
|
183
|
+
expect(rule?.level).toBe("manage");
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
package/src/rpc-contract.ts
CHANGED
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
UpdateIncidentInputSchema,
|
|
13
13
|
AddIncidentUpdateInputSchema,
|
|
14
14
|
IncidentStatusEnum,
|
|
15
|
+
BulkIncidentActionResultSchema,
|
|
16
|
+
BulkIncidentIdsInputSchema,
|
|
17
|
+
BulkResolveIncidentsInputSchema,
|
|
15
18
|
} from "./schemas";
|
|
16
19
|
|
|
17
20
|
export const incidentContract = {
|
|
@@ -193,6 +196,41 @@ export const incidentContract = {
|
|
|
193
196
|
.input(z.object({ id: z.string() }))
|
|
194
197
|
.output(z.object({ success: z.boolean() })),
|
|
195
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Mass-delete incidents, authorizing EACH id against the caller's manage
|
|
201
|
+
* grant. The `bulkManage` instance-access mode pre-partitions `input.ids`
|
|
202
|
+
* into the caller's manageable subset (global rule OR per-incident team
|
|
203
|
+
* grant) and the denied remainder BEFORE the handler runs, so an
|
|
204
|
+
* unauthorized id is never deleted. Returns a per-id result so the frontend
|
|
205
|
+
* can report partial success; a denied id is reported as `forbidden`, never a
|
|
206
|
+
* hard failure of the whole batch.
|
|
207
|
+
*/
|
|
208
|
+
bulkDeleteIncidents: proc({
|
|
209
|
+
operationType: "mutation",
|
|
210
|
+
userType: "authenticated",
|
|
211
|
+
access: [incidentAccess.incident.manage],
|
|
212
|
+
instanceAccess: { bulkManage: { idsParam: "ids" } },
|
|
213
|
+
})
|
|
214
|
+
.route({ method: "POST" })
|
|
215
|
+
.input(BulkIncidentIdsInputSchema)
|
|
216
|
+
.output(z.object({ results: z.array(BulkIncidentActionResultSchema) })),
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Mass-resolve incidents (mirrors the single-item `resolveIncident`: status →
|
|
220
|
+
* resolved). Authorizes EACH id against the caller's manage grant via the
|
|
221
|
+
* `bulkManage` mode; only authorized ids are resolved and a per-id result is
|
|
222
|
+
* returned. Already-resolved or missing ids are reported, never fatal.
|
|
223
|
+
*/
|
|
224
|
+
bulkResolveIncidents: proc({
|
|
225
|
+
operationType: "mutation",
|
|
226
|
+
userType: "authenticated",
|
|
227
|
+
access: [incidentAccess.incident.manage],
|
|
228
|
+
instanceAccess: { bulkManage: { idsParam: "ids" } },
|
|
229
|
+
})
|
|
230
|
+
.route({ method: "POST" })
|
|
231
|
+
.input(BulkResolveIncidentsInputSchema)
|
|
232
|
+
.output(z.object({ results: z.array(BulkIncidentActionResultSchema) })),
|
|
233
|
+
|
|
196
234
|
/**
|
|
197
235
|
* Check if a system has an active incident with notification suppression enabled.
|
|
198
236
|
* Used by the health check system to suppress notifications during acknowledged incidents.
|
package/src/schemas.ts
CHANGED
|
@@ -126,3 +126,46 @@ export const AddIncidentUpdateInputSchema = z.object({
|
|
|
126
126
|
export type AddIncidentUpdateInput = z.infer<
|
|
127
127
|
typeof AddIncidentUpdateInputSchema
|
|
128
128
|
>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Per-id outcome of a bulk incident action (mass delete / mass resolve).
|
|
132
|
+
*
|
|
133
|
+
* - `deleted` / `resolved`: the action succeeded for this incident.
|
|
134
|
+
* - `notFound`: no incident with this id exists (or it vanished mid-batch).
|
|
135
|
+
* - `forbidden`: the caller cannot MANAGE this incident (filtered out by the
|
|
136
|
+
* `bulkManage` instance-access mode before the handler ran).
|
|
137
|
+
* - `error`: the per-id operation threw; the batch continued past it.
|
|
138
|
+
*/
|
|
139
|
+
export const BulkIncidentActionStatusEnum = z.enum([
|
|
140
|
+
"deleted",
|
|
141
|
+
"resolved",
|
|
142
|
+
"notFound",
|
|
143
|
+
"forbidden",
|
|
144
|
+
"error",
|
|
145
|
+
]);
|
|
146
|
+
export type BulkIncidentActionStatus = z.infer<
|
|
147
|
+
typeof BulkIncidentActionStatusEnum
|
|
148
|
+
>;
|
|
149
|
+
|
|
150
|
+
export const BulkIncidentActionResultSchema = z.object({
|
|
151
|
+
id: z.string(),
|
|
152
|
+
status: BulkIncidentActionStatusEnum,
|
|
153
|
+
});
|
|
154
|
+
export type BulkIncidentActionResult = z.infer<
|
|
155
|
+
typeof BulkIncidentActionResultSchema
|
|
156
|
+
>;
|
|
157
|
+
|
|
158
|
+
/** Input for a bulk incident action: a non-empty set of incident ids. */
|
|
159
|
+
export const BulkIncidentIdsInputSchema = z.object({
|
|
160
|
+
ids: z.array(z.string()).min(1, "At least one incident is required"),
|
|
161
|
+
});
|
|
162
|
+
export type BulkIncidentIdsInput = z.infer<typeof BulkIncidentIdsInputSchema>;
|
|
163
|
+
|
|
164
|
+
/** Input for bulk resolve: incident ids plus an optional shared message. */
|
|
165
|
+
export const BulkResolveIncidentsInputSchema = z.object({
|
|
166
|
+
ids: z.array(z.string()).min(1, "At least one incident is required"),
|
|
167
|
+
message: z.string().optional(),
|
|
168
|
+
});
|
|
169
|
+
export type BulkResolveIncidentsInput = z.infer<
|
|
170
|
+
typeof BulkResolveIncidentsInputSchema
|
|
171
|
+
>;
|