@checkstack/automation-frontend 0.9.3 → 0.10.1

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,132 @@
1
1
  # @checkstack/automation-frontend
2
2
 
3
+ ## 0.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [0cac684]
8
+ - Updated dependencies [0cac684]
9
+ - @checkstack/auth-frontend@0.11.1
10
+ - @checkstack/gitops-frontend@0.6.6
11
+ - @checkstack/script-packages-frontend@0.4.6
12
+
13
+ ## 0.10.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 0d912a3: Make the frontend fully RLAC-aware so team-scoped users see and can use exactly
18
+ what the backend already authorises - no more, no less. Previously every nav
19
+ entry, route, management page, create button, per-row action, and resource
20
+ picker gated purely on a user's GLOBAL access rule, so a user whose team manages
21
+ a system saw none of the surfaces the backend would happily let them use, and
22
+ (where a page did render) could select systems they don't manage and only fail
23
+ after submit.
24
+
25
+ Platform primitives (on `AccessApi`, from `@checkstack/frontend-api`, implemented
26
+ in `@checkstack/auth-frontend`). Each ORs the global RBAC rule with team-derived
27
+ (ReBAC) grants, so a global-rule holder always sees everything:
28
+
29
+ - `useCanCreate({ accessRule, objectType, parentType? })` - may the user create
30
+ this type (global rule, a team `creator` grant, or managing a parent resource).
31
+ - `useCanAccessType({ accessRule, objectType, parentType? })` - may the user
32
+ reach a management SURFACE for this type at all (create capability OR managing
33
+ any existing object of the type / its parent). Powers route guards, sidebar
34
+ entries, and a management page's top-level `allowed`.
35
+ - `useResourceAccess({ accessRule, objectType, resourceIds })` - a `canAccess(id)`
36
+ predicate for per-row controls and for filtering resource pickers.
37
+
38
+ Backed by three authenticated `auth` RPC procedures - `canCreate`,
39
+ `myManageableTypes`, and `listMyAccessibleResources` - the frontend-facing
40
+ mirrors of the existing S2S authorization endpoints, resolved against the
41
+ caller's own team grants.
42
+
43
+ Route/nav gating is now capability-aware: a route may declare
44
+ `manageCapability: { objectType, parentType? }`; the route guard and sidebar then
45
+ show/allow it for team-scoped users via `myManageableTypes`. Applied to the
46
+ catalog, incident, maintenance, SLO, healthcheck, automation, and status-page
47
+ management routes. The route guard resolves this through a single
48
+ `useRouteAccess` hook with a constant hook count, since the guard is reconciled
49
+ in place as the URL changes (a conditional hook there would trip the rules of
50
+ hooks).
51
+
52
+ Resource types are now typed, plugin-qualified constants. A new
53
+ `resourceType(pluginMetadata, localType)` factory in `@checkstack/common` mints a
54
+ nominal `ResourceType`, and each `*-common` package exports its constants (e.g.
55
+ `catalogResourceTypes.system`, `incidentResourceTypes.incident`). The capability
56
+ APIs accept `ResourceType`, so a mistyped `"catalog.system"` string now fails
57
+ typecheck instead of silently breaking a gate.
58
+
59
+ Resource pickers now offer only what the backend will accept:
60
+
61
+ - Incident and maintenance "Affected Systems" pickers show only systems the user
62
+ manages (or all with the global rule), matching the backend's requirement of
63
+ MANAGE on every referenced system.
64
+ - SLO creation is now system-scoped end to end: `createObjective` gains a
65
+ `catalog.system` parent gate (managing the target system authorises creating an
66
+ SLO for it, like incident/maintenance), and the SLO editor's system picker is
67
+ filtered to manageable systems.
68
+ - Catalog group and environment membership (add-to-group / add-to-environment,
69
+ per-row and bulk) is gated on managing the system being (re)assigned.
70
+ - The health-check assignment surface (Assignment IDE + the system-detail
71
+ "Health Checks" action) requires MANAGE on the target system.
72
+
73
+ Catalog membership chips only render a removable "x" for systems the user
74
+ manages (removing a group/environment membership requires managing the system),
75
+ and the Dependency Map only lets a user originate an edge from a system they
76
+ manage (the source is access-checked; the target is not).
77
+
78
+ Owning-team correctness: a parent-gated creator (team member, no global rule)
79
+ who left the owning team unset previously created an object with no team grant -
80
+ which they then could not edit. The `authorizeCreate` parent-gate path now
81
+ resolves an owning team instead of silently orphaning the object (auto-assigns
82
+ when the caller belongs to exactly one team, requires an explicit choice when
83
+ several), and the `TeamOwnershipPicker` marks the field required and
84
+ auto-selects the sole eligible team.
85
+
86
+ Dependency writes are fixed to authorize on the SOURCE system. `createDependency`
87
+ / `updateDependency` / `deleteDependency` previously used `instanceAccess:
88
+ { idParam: "systemId" }`, which made the middleware look for a `dependency` grant
89
+ keyed by the system id - a grant that never exists - so every team-scoped source
90
+ manager was denied ("Access denied to resource dependency:<systemId>"). They now
91
+ `parentScope` on `catalog.system` manage, so managing the source system
92
+ authorises editing its dependencies (the target is not access-checked), matching
93
+ health-check assignment.
94
+
95
+ The backend authorization changes are limited to: the new read-only capability
96
+ procedures (`canCreate` / `myManageableTypes` / `listMyAccessibleResources`), the
97
+ SLO create parent gate, the `authorizeCreate` owning-team resolution, and the
98
+ dependency source-scope fix. Everything else only aligns the UI with
99
+ authorization the backend already enforced.
100
+
101
+ ### Patch Changes
102
+
103
+ - Updated dependencies [0d912a3]
104
+ - Updated dependencies [0d912a3]
105
+ - Updated dependencies [d9f4654]
106
+ - Updated dependencies [0d912a3]
107
+ - Updated dependencies [a07b375]
108
+ - Updated dependencies [d9f4654]
109
+ - Updated dependencies [d9f4654]
110
+ - Updated dependencies [e430fbe]
111
+ - Updated dependencies [eab80e3]
112
+ - Updated dependencies [259b93c]
113
+ - Updated dependencies [0d912a3]
114
+ - Updated dependencies [692fa18]
115
+ - @checkstack/auth-frontend@0.11.0
116
+ - @checkstack/ui@1.22.0
117
+ - @checkstack/frontend-api@0.13.0
118
+ - @checkstack/common@0.19.0
119
+ - @checkstack/auth-common@0.12.0
120
+ - @checkstack/catalog-common@2.6.0
121
+ - @checkstack/automation-common@0.9.0
122
+ - @checkstack/gitops-frontend@0.6.5
123
+ - @checkstack/script-packages-frontend@0.4.5
124
+ - @checkstack/secrets-frontend@0.3.5
125
+ - @checkstack/ai-common@0.6.3
126
+ - @checkstack/integration-common@0.9.5
127
+ - @checkstack/signal-frontend@0.3.2
128
+ - @checkstack/template-engine@0.4.8
129
+
3
130
  ## 0.9.3
4
131
 
5
132
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/automation-frontend",
3
- "version": "0.9.3",
3
+ "version": "0.10.1",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -16,20 +16,20 @@
16
16
  "lint:code": "eslint . --max-warnings 0"
17
17
  },
18
18
  "dependencies": {
19
- "@checkstack/auth-common": "0.11.2",
20
- "@checkstack/auth-frontend": "0.10.2",
21
- "@checkstack/automation-common": "0.8.2",
22
- "@checkstack/ai-common": "0.6.2",
23
- "@checkstack/catalog-common": "2.5.0",
24
- "@checkstack/common": "0.18.0",
25
- "@checkstack/frontend-api": "0.12.1",
26
- "@checkstack/gitops-frontend": "0.6.4",
27
- "@checkstack/integration-common": "0.9.4",
28
- "@checkstack/script-packages-frontend": "0.4.4",
29
- "@checkstack/secrets-frontend": "0.3.4",
30
- "@checkstack/signal-frontend": "0.3.1",
31
- "@checkstack/template-engine": "0.4.7",
32
- "@checkstack/ui": "1.21.0",
19
+ "@checkstack/auth-common": "0.12.0",
20
+ "@checkstack/auth-frontend": "0.11.1",
21
+ "@checkstack/automation-common": "0.9.0",
22
+ "@checkstack/ai-common": "0.6.3",
23
+ "@checkstack/catalog-common": "2.6.0",
24
+ "@checkstack/common": "0.19.0",
25
+ "@checkstack/frontend-api": "0.13.0",
26
+ "@checkstack/gitops-frontend": "0.6.6",
27
+ "@checkstack/integration-common": "0.9.5",
28
+ "@checkstack/script-packages-frontend": "0.4.6",
29
+ "@checkstack/secrets-frontend": "0.3.5",
30
+ "@checkstack/signal-frontend": "0.3.2",
31
+ "@checkstack/template-engine": "0.4.8",
32
+ "@checkstack/ui": "1.22.0",
33
33
  "@dnd-kit/core": "^6.3.1",
34
34
  "@dnd-kit/sortable": "^8.0.0",
35
35
  "@dnd-kit/utilities": "^3.2.2",
@@ -43,6 +43,6 @@
43
43
  "typescript": "^5.0.0",
44
44
  "@types/react": "^19.0.0",
45
45
  "@checkstack/tsconfig": "0.0.7",
46
- "@checkstack/scripts": "0.6.5"
46
+ "@checkstack/scripts": "0.7.0"
47
47
  }
48
48
  }
package/src/index.tsx CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  automationRoutes,
4
4
  pluginMetadata,
5
5
  automationAccess,
6
+ automationResourceTypes,
6
7
  } from "@checkstack/automation-common";
7
8
  import { Workflow } from "lucide-react";
8
9
 
@@ -60,6 +61,8 @@ export default createFrontendPlugin({
60
61
  })),
61
62
  title: "New automation",
62
63
  accessRule: automationAccess.manage,
64
+ // Team-scoped: a team with an automation creator grant may reach create.
65
+ manageCapability: { objectType: automationResourceTypes.automation },
63
66
  },
64
67
  {
65
68
  route: automationRoutes.routes.createBlank,
@@ -69,6 +72,8 @@ export default createFrontendPlugin({
69
72
  })),
70
73
  title: "New automation",
71
74
  accessRule: automationAccess.manage,
75
+ // Team-scoped: a team with an automation creator grant may reach create.
76
+ manageCapability: { objectType: automationResourceTypes.automation },
72
77
  },
73
78
  {
74
79
  route: automationRoutes.routes.edit,
@@ -17,6 +17,7 @@ import {
17
17
  AutomationApi,
18
18
  automationAccess,
19
19
  automationRoutes,
20
+ automationResourceTypes,
20
21
  type AutomationDefinition,
21
22
  } from "@checkstack/automation-common";
22
23
  import {
@@ -118,12 +119,28 @@ const AutomationEditContent: React.FC = () => {
118
119
  const { allowed: canRead, loading: accessLoading } = accessApi.useAccess(
119
120
  automationAccess.read,
120
121
  );
121
- const { allowed: hasManageAccess } = accessApi.useAccess(
122
+ // allowGlobal: the GLOBAL manage rule means the caller may create a resource
123
+ // not scoped to any team. This feeds the TeamOwnershipPicker only.
124
+ const { allowed: hasGlobalManageAccess } = accessApi.useAccess(
122
125
  automationAccess.manage,
123
126
  );
124
- // allowGlobal: reuse the manage access check — global manage permission
125
- // means the caller may create a resource not scoped to any team.
126
- const allowGlobal = hasManageAccess;
127
+ const allowGlobal = hasGlobalManageAccess;
128
+
129
+ // Create/page gate (new automations): global manage rule OR team-derived
130
+ // create capability.
131
+ const { allowed: canCreate } = accessApi.useCanCreate({
132
+ accessRule: automationAccess.manage,
133
+ objectType: automationResourceTypes.automation,
134
+ });
135
+ // Per-resource manage gate (existing automations): grants to global-manage
136
+ // holders AND to team-scoped users with a per-object grant on THIS id.
137
+ const { canAccess } = accessApi.useResourceAccess({
138
+ accessRule: automationAccess.manage,
139
+ objectType: automationResourceTypes.automation,
140
+ resourceIds: !isNew && automationId ? [automationId] : [],
141
+ });
142
+ const canManageThisAutomation =
143
+ !isNew && automationId ? canAccess(automationId) : false;
127
144
 
128
145
  // GitOps provenance lock: when this automation is declaratively managed,
129
146
  // disable manual edits + show a banner. `entityId` is the automation id
@@ -134,8 +151,10 @@ const AutomationEditContent: React.FC = () => {
134
151
  entityId: isNew ? undefined : automationId,
135
152
  });
136
153
 
137
- // Effective edit permission: manage access AND not GitOps-locked.
138
- const canManage = hasManageAccess && !isLocked;
154
+ // Effective edit permission: RLAC manage (create capability for new, per-
155
+ // resource access for existing) AND not GitOps-locked.
156
+ const canManage =
157
+ (isNew ? canCreate : canManageThisAutomation) && !isLocked;
139
158
 
140
159
  const loadQuery = client.getAutomation.useQuery(
141
160
  { id: automationId ?? "" },
@@ -12,6 +12,7 @@ import {
12
12
  AutomationApi,
13
13
  automationAccess,
14
14
  automationRoutes,
15
+ automationResourceTypes,
15
16
  type Automation,
16
17
  } from "@checkstack/automation-common";
17
18
  import {
@@ -84,7 +85,12 @@ const AutomationListContent: React.FC = () => {
84
85
  const { allowed: canRead, loading: accessLoading } = accessApi.useAccess(
85
86
  automationAccess.read,
86
87
  );
87
- const { allowed: canManage } = accessApi.useAccess(automationAccess.manage);
88
+ // Create/page gate: whether the user may create a new automation at all
89
+ // (global manage rule OR team-derived create capability).
90
+ const { allowed: canCreate } = accessApi.useCanCreate({
91
+ accessRule: automationAccess.manage,
92
+ objectType: automationResourceTypes.automation,
93
+ });
88
94
 
89
95
  const [statusFilter, setStatusFilter] = React.useState<
90
96
  "all" | "enabled" | "disabled"
@@ -163,6 +169,15 @@ const AutomationListContent: React.FC = () => {
163
169
  const automations = React.useMemo(() => items ?? [], [items]);
164
170
  const isEmpty = !query.isLoading && automations.length === 0;
165
171
 
172
+ // Per-resource manage gate: grants ALL rows to global-manage holders, and
173
+ // per-object grants to team-scoped users. Gate each row's toggle/delete on
174
+ // `canAccess(automation.id)` instead of a single global bool.
175
+ const { canAccess } = accessApi.useResourceAccess({
176
+ accessRule: automationAccess.manage,
177
+ objectType: automationResourceTypes.automation,
178
+ resourceIds: automations.map((a) => a.id),
179
+ });
180
+
166
181
  // Collapsible sections, sorted alphabetically with "Ungrouped" last.
167
182
  const groups = React.useMemo(
168
183
  () => groupAutomations({ automations }),
@@ -195,7 +210,7 @@ const AutomationListContent: React.FC = () => {
195
210
  )}
196
211
  aria-hidden
197
212
  />
198
- {canManage ? (
213
+ {canAccess(automation.id) ? (
199
214
  <Toggle
200
215
  checked={automation.status === "enabled"}
201
216
  onCheckedChange={(enabled) =>
@@ -276,7 +291,7 @@ const AutomationListContent: React.FC = () => {
276
291
  Runs
277
292
  </Button>
278
293
  </Link>
279
- {canManage && (
294
+ {canAccess(automation.id) && (
280
295
  <Button
281
296
  variant="ghost"
282
297
  size="icon"
@@ -324,7 +339,7 @@ const AutomationListContent: React.FC = () => {
324
339
  )}
325
340
  </div>
326
341
  <div onClick={(e) => e.stopPropagation()}>
327
- {canManage ? (
342
+ {canAccess(automation.id) ? (
328
343
  <Toggle
329
344
  checked={automation.status === "enabled"}
330
345
  onCheckedChange={(enabled) =>
@@ -389,7 +404,7 @@ const AutomationListContent: React.FC = () => {
389
404
  Runs
390
405
  </Button>
391
406
  </Link>
392
- {canManage && (
407
+ {canAccess(automation.id) && (
393
408
  <Button
394
409
  variant="ghost"
395
410
  size="icon"
@@ -419,7 +434,7 @@ const AutomationListContent: React.FC = () => {
419
434
  Playground
420
435
  </Button>
421
436
  </Link>
422
- {canManage && (
437
+ {canCreate && (
423
438
  <Link to={resolveRoute(automationRoutes.routes.create)}>
424
439
  <Button size="sm">
425
440
  <Plus className="mr-1 h-4 w-4" />
@@ -464,7 +479,7 @@ const AutomationListContent: React.FC = () => {
464
479
  icon={<Workflow className="h-8 w-8 text-muted-foreground" />}
465
480
  title="No automations yet"
466
481
  description={
467
- canManage
482
+ canCreate
468
483
  ? 'Click "New automation" to wire your first trigger.'
469
484
  : "Once an admin creates an automation it will appear here."
470
485
  }