@checkstack/automation-frontend 0.9.2 → 0.10.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 CHANGED
@@ -1,5 +1,133 @@
1
1
  # @checkstack/automation-frontend
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0d912a3: Make the frontend fully RLAC-aware so team-scoped users see and can use exactly
8
+ what the backend already authorises - no more, no less. Previously every nav
9
+ entry, route, management page, create button, per-row action, and resource
10
+ picker gated purely on a user's GLOBAL access rule, so a user whose team manages
11
+ a system saw none of the surfaces the backend would happily let them use, and
12
+ (where a page did render) could select systems they don't manage and only fail
13
+ after submit.
14
+
15
+ Platform primitives (on `AccessApi`, from `@checkstack/frontend-api`, implemented
16
+ in `@checkstack/auth-frontend`). Each ORs the global RBAC rule with team-derived
17
+ (ReBAC) grants, so a global-rule holder always sees everything:
18
+
19
+ - `useCanCreate({ accessRule, objectType, parentType? })` - may the user create
20
+ this type (global rule, a team `creator` grant, or managing a parent resource).
21
+ - `useCanAccessType({ accessRule, objectType, parentType? })` - may the user
22
+ reach a management SURFACE for this type at all (create capability OR managing
23
+ any existing object of the type / its parent). Powers route guards, sidebar
24
+ entries, and a management page's top-level `allowed`.
25
+ - `useResourceAccess({ accessRule, objectType, resourceIds })` - a `canAccess(id)`
26
+ predicate for per-row controls and for filtering resource pickers.
27
+
28
+ Backed by three authenticated `auth` RPC procedures - `canCreate`,
29
+ `myManageableTypes`, and `listMyAccessibleResources` - the frontend-facing
30
+ mirrors of the existing S2S authorization endpoints, resolved against the
31
+ caller's own team grants.
32
+
33
+ Route/nav gating is now capability-aware: a route may declare
34
+ `manageCapability: { objectType, parentType? }`; the route guard and sidebar then
35
+ show/allow it for team-scoped users via `myManageableTypes`. Applied to the
36
+ catalog, incident, maintenance, SLO, healthcheck, automation, and status-page
37
+ management routes. The route guard resolves this through a single
38
+ `useRouteAccess` hook with a constant hook count, since the guard is reconciled
39
+ in place as the URL changes (a conditional hook there would trip the rules of
40
+ hooks).
41
+
42
+ Resource types are now typed, plugin-qualified constants. A new
43
+ `resourceType(pluginMetadata, localType)` factory in `@checkstack/common` mints a
44
+ nominal `ResourceType`, and each `*-common` package exports its constants (e.g.
45
+ `catalogResourceTypes.system`, `incidentResourceTypes.incident`). The capability
46
+ APIs accept `ResourceType`, so a mistyped `"catalog.system"` string now fails
47
+ typecheck instead of silently breaking a gate.
48
+
49
+ Resource pickers now offer only what the backend will accept:
50
+
51
+ - Incident and maintenance "Affected Systems" pickers show only systems the user
52
+ manages (or all with the global rule), matching the backend's requirement of
53
+ MANAGE on every referenced system.
54
+ - SLO creation is now system-scoped end to end: `createObjective` gains a
55
+ `catalog.system` parent gate (managing the target system authorises creating an
56
+ SLO for it, like incident/maintenance), and the SLO editor's system picker is
57
+ filtered to manageable systems.
58
+ - Catalog group and environment membership (add-to-group / add-to-environment,
59
+ per-row and bulk) is gated on managing the system being (re)assigned.
60
+ - The health-check assignment surface (Assignment IDE + the system-detail
61
+ "Health Checks" action) requires MANAGE on the target system.
62
+
63
+ Catalog membership chips only render a removable "x" for systems the user
64
+ manages (removing a group/environment membership requires managing the system),
65
+ and the Dependency Map only lets a user originate an edge from a system they
66
+ manage (the source is access-checked; the target is not).
67
+
68
+ Owning-team correctness: a parent-gated creator (team member, no global rule)
69
+ who left the owning team unset previously created an object with no team grant -
70
+ which they then could not edit. The `authorizeCreate` parent-gate path now
71
+ resolves an owning team instead of silently orphaning the object (auto-assigns
72
+ when the caller belongs to exactly one team, requires an explicit choice when
73
+ several), and the `TeamOwnershipPicker` marks the field required and
74
+ auto-selects the sole eligible team.
75
+
76
+ Dependency writes are fixed to authorize on the SOURCE system. `createDependency`
77
+ / `updateDependency` / `deleteDependency` previously used `instanceAccess:
78
+ { idParam: "systemId" }`, which made the middleware look for a `dependency` grant
79
+ keyed by the system id - a grant that never exists - so every team-scoped source
80
+ manager was denied ("Access denied to resource dependency:<systemId>"). They now
81
+ `parentScope` on `catalog.system` manage, so managing the source system
82
+ authorises editing its dependencies (the target is not access-checked), matching
83
+ health-check assignment.
84
+
85
+ The backend authorization changes are limited to: the new read-only capability
86
+ procedures (`canCreate` / `myManageableTypes` / `listMyAccessibleResources`), the
87
+ SLO create parent gate, the `authorizeCreate` owning-team resolution, and the
88
+ dependency source-scope fix. Everything else only aligns the UI with
89
+ authorization the backend already enforced.
90
+
91
+ ### Patch Changes
92
+
93
+ - Updated dependencies [0d912a3]
94
+ - Updated dependencies [0d912a3]
95
+ - Updated dependencies [d9f4654]
96
+ - Updated dependencies [0d912a3]
97
+ - Updated dependencies [a07b375]
98
+ - Updated dependencies [d9f4654]
99
+ - Updated dependencies [d9f4654]
100
+ - Updated dependencies [e430fbe]
101
+ - Updated dependencies [eab80e3]
102
+ - Updated dependencies [259b93c]
103
+ - Updated dependencies [0d912a3]
104
+ - Updated dependencies [692fa18]
105
+ - @checkstack/auth-frontend@0.11.0
106
+ - @checkstack/ui@1.22.0
107
+ - @checkstack/frontend-api@0.13.0
108
+ - @checkstack/common@0.19.0
109
+ - @checkstack/auth-common@0.12.0
110
+ - @checkstack/catalog-common@2.6.0
111
+ - @checkstack/automation-common@0.9.0
112
+ - @checkstack/gitops-frontend@0.6.5
113
+ - @checkstack/script-packages-frontend@0.4.5
114
+ - @checkstack/secrets-frontend@0.3.5
115
+ - @checkstack/ai-common@0.6.3
116
+ - @checkstack/integration-common@0.9.5
117
+ - @checkstack/signal-frontend@0.3.2
118
+ - @checkstack/template-engine@0.4.8
119
+
120
+ ## 0.9.3
121
+
122
+ ### Patch Changes
123
+
124
+ - Updated dependencies [baf9b6e]
125
+ - @checkstack/ui@1.21.0
126
+ - @checkstack/auth-frontend@0.10.2
127
+ - @checkstack/gitops-frontend@0.6.4
128
+ - @checkstack/script-packages-frontend@0.4.4
129
+ - @checkstack/secrets-frontend@0.3.4
130
+
3
131
  ## 0.9.2
4
132
 
5
133
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/automation-frontend",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
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.1",
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.3",
27
- "@checkstack/integration-common": "0.9.4",
28
- "@checkstack/script-packages-frontend": "0.4.3",
29
- "@checkstack/secrets-frontend": "0.3.3",
30
- "@checkstack/signal-frontend": "0.3.1",
31
- "@checkstack/template-engine": "0.4.7",
32
- "@checkstack/ui": "1.20.0",
19
+ "@checkstack/auth-common": "0.12.0",
20
+ "@checkstack/auth-frontend": "0.11.0",
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.5",
27
+ "@checkstack/integration-common": "0.9.5",
28
+ "@checkstack/script-packages-frontend": "0.4.5",
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
  }