@checkstack/automation-frontend 0.6.1 → 0.7.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,178 @@
1
1
  # @checkstack/automation-frontend
2
2
 
3
+ ## 0.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [b1a5f3c]
8
+ - @checkstack/frontend-api@0.11.0
9
+ - @checkstack/auth-frontend@0.8.1
10
+ - @checkstack/catalog-common@2.4.1
11
+ - @checkstack/gitops-frontend@0.5.9
12
+ - @checkstack/script-packages-frontend@0.3.13
13
+ - @checkstack/secrets-frontend@0.2.8
14
+ - @checkstack/ui@1.16.2
15
+
16
+ ## 0.7.0
17
+
18
+ ### Minor Changes
19
+
20
+ - d2077bd: Platform-wide team-scoped access control on a unified relation-tuple store.
21
+
22
+ Admins can scope any resource to teams, and the **platform** (not each plugin)
23
+ enforces it. A plugin opts in declaratively by adding `instanceAccess` to a
24
+ procedure's contract; the auth middleware does the rest, so enforcement is
25
+ consistent across catalog, health checks, incidents, maintenances, SLOs,
26
+ automations, and the dependency map, and any third-party plugin gets it for free.
27
+
28
+ Core model:
29
+
30
+ - **Teams are optional.** A resource with no team grants behaves exactly as
31
+ before.
32
+ - **Team grants are additive and restrict who can CHANGE a resource, not who can
33
+ SEE it.** Granting a team `Manage` lets its members view and change the
34
+ resource; `Read-only` lets them view it. Either level grants access to team
35
+ members **even when they lack the global permission**, and granting never
36
+ removes read from anyone who already had it (e.g. a public status page stays
37
+ readable). Privacy is a separate, explicit opt-in via the **Private** toggle,
38
+ which removes the global read path so only the resource's teams can see it.
39
+ - **Ownership at creation.** Create forms expose an **Owning team** picker. A
40
+ non-admin can create a resource for a team they belong to that holds a
41
+ create-capability grant for that type; the new resource is auto-granted to that
42
+ team. Incidents and maintenances are **parent-gated**: anyone who can manage a
43
+ system may open incidents/maintenances for it, no separate grant needed.
44
+ - **Meaningful authorization errors.** A caller with neither the global rule nor
45
+ any team grant for a resource type gets a `403` with a structured body instead
46
+ of a silently-empty `200`. Anonymous callers on public endpoints are never
47
+ `403`'d, so status pages keep rendering.
48
+
49
+ Unified relation-tuple store:
50
+
51
+ - The previously separate access primitives (`resource_team_access.canRead` /
52
+ `.canManage`, ownership, `resource_access_settings.teamOnly`, and
53
+ `resource_create_grant`) are collapsed onto ONE
54
+ `relation_tuple(object, relation, subject)` store: "a team has
55
+ `viewer`/`editor`/`owner` on an object, or `creator` on a type". Privacy is an
56
+ explicit **`private` marker** tuple — its **presence** closes the global read
57
+ path (team grants only), its **absence** is the readable-by-default state, so a
58
+ private resource with zero grants is correctly inaccessible to everyone rather
59
+ than silently globalized. The access decision is a pure, unit-tested function.
60
+ - The auth API is generic: `writeRelation` / `removeRelation` / `setObjectPublic`
61
+ / `listObjectRelations` / `listSubjectRelations` / `setCreateGrant` /
62
+ `listTeamCreateGrants` (user-facing) and `check` / `listAccessibleObjectIds` /
63
+ `hasAnyTypeGrant` / `authorizeCreate` / `setOwner` / `deleteObjectRelations`
64
+ (service-to-service). Migration `0008` backfills tuples from the legacy tables
65
+ and drops them.
66
+
67
+ Explicit per-procedure scoping:
68
+
69
+ - Access rules (`access()` / `accessPair()`) define only the rule (id, level,
70
+ defaults); every procedure declares its own `instanceAccess`. This removes a
71
+ "loaded gun" default that silently applied a shared `idParam` to any procedure
72
+ which forgot its own override.
73
+ - Modes: `idParam` (single-resource pre-check, fails **closed** if the id does
74
+ not resolve), `listKey` / `recordKey` (post-filter a list/record to the
75
+ accessible subset), `create` (authorize creation + write the owning-team
76
+ grant), `parentScope` (scope by read/manage access to a PARENT type,
77
+ cross-plugin single-hop: "you may see incidents/maintenances/SLOs/health for
78
+ system S iff you may see S"), and `global: true` (the honest "intentionally not
79
+ team-scoped" opt-out). A boot-time validator **rejects** any procedure gated on
80
+ a team-scopable resource type that declares no `instanceAccess`, turning the
81
+ previous fail-open into a boot error.
82
+
83
+ Teams administration:
84
+
85
+ - **Team managers** manage their own team's members and managers without the
86
+ global `auth.teams.manage` rule; creating, deleting, and granting a team access
87
+ remain admin-only.
88
+ - A **standalone Teams page** (gated on `auth.teams.read`) lets managers reach
89
+ team administration without the admin Auth Settings page; members are added via
90
+ a debounced directory picker.
91
+ - A **cross-plugin `ResourceResolverRegistry`** lets owning plugins register a
92
+ name/search resolver for their resource types, so the Teams page lists a team's
93
+ grants **by name** (grouped by type) and offers a resource picker — an admin can
94
+ change a grant's level, revoke it, or add one, without auth depending on every
95
+ plugin. Resolvers shipped for catalog systems, health-check configurations,
96
+ incidents, maintenances, SLO objectives, and automations.
97
+
98
+ Frontend:
99
+
100
+ - The resource-side editor is **"Who can change this"** (one Manage checkbox per
101
+ team; unticked = read-only), with an always-visible **Private** toggle
102
+ (disabled until a team that can Manage exists, so a resource can't be stranded).
103
+ - `TeamOwnershipPicker` explains _why_ there's nothing to pick (not a member of
104
+ any team, or none of your teams manage the selected parent) instead of a bare
105
+ "global resource" line.
106
+ - Read-only **"who can change this"** indicators on resource detail pages expand
107
+ to the actual people by name; bulk + per-row **Scope to team** actions in the
108
+ catalog systems list; and the team-access copy spells out that grants are
109
+ additive and that Read-only grants view (not change) even without the global
110
+ permission.
111
+
112
+ Security hardening:
113
+
114
+ - Child deletes in catalog (`removeSystemContact` / `removeSystemLink`) are scoped
115
+ to both the child id and its parent `systemId`, closing a cross-system IDOR for
116
+ team-scoped managers.
117
+ - `searchUsers` is restricted to team administrators, closing a directory/email
118
+ enumeration path opened by the default `auth.teams.read` rule.
119
+ - Grant setters reject unregistered resource types.
120
+
121
+ BREAKING CHANGES (beta; shipped as minor bumps):
122
+
123
+ - `access()` and `accessPair()` no longer accept `idParam` / `listKey` /
124
+ `recordKey`; move instance config to the procedure's `instanceAccess`.
125
+ - Boot fails if a procedure gated on a team-scopable resource type omits
126
+ `instanceAccess`. Declare a scoping mode or `instanceAccess: { global: true }`.
127
+ - The `AuthService` interface is reshaped: `check`, `listAccessibleObjectIds`,
128
+ `hasAnyTypeGrant`, `authorizeCreate` (returns `isPrivate`), `setOwner`
129
+ (`isPrivate`), and `deleteObjectRelations`. Custom `AuthService` implementations
130
+ and mocks must update.
131
+ - The auth RPC contract's per-concept resource-access endpoints are replaced by
132
+ the generic tuple API above; external callers of the old
133
+ `getResourceTeamAccess` / `setResourceTeamAccess` / `setResourceAccessSettings`
134
+ / `grantResourceCreate` / etc. must move to the new procedures.
135
+ - Several contract inputs changed from a bare `string` to an object so the
136
+ middleware can resolve the resource id: catalog `deleteSystem` (`{ id }`),
137
+ `removeSystemContact` / `removeSystemLink` (`{ id, systemId }`); health-check
138
+ `deleteConfiguration` / `pauseConfiguration` / `resumeConfiguration` (`{ id }`).
139
+ All in-tree callers are updated.
140
+ - List/record endpoints that relied on returning an empty `200` to signal "no
141
+ access" now return a `403` for categorically-unauthorized principals.
142
+ - The mis-keyed bulk endpoints `getBulkIncidentsForSystems`,
143
+ `getBulkMaintenancesForSystems`, and `getBulkObjectivesForSystems` no longer
144
+ post-filter their (systemId-keyed) result; access is already gated by
145
+ `catalog.system` upstream.
146
+ - Team membership/manager mutations (`addUserToTeam`, `removeUserFromTeam`,
147
+ `addTeamManager`, `removeTeamManager`) now require `auth.teams.read` instead of
148
+ `auth.teams.manage` at the contract level (broadened to per-team managers).
149
+ - The `resource_team_access`, `resource_access_settings`, and
150
+ `resource_create_grant` tables are dropped (data backfilled into
151
+ `relation_tuple` by migration `0008`). A previously inconsistent "team-only with
152
+ zero grants" resource is now correctly inaccessible to global-access holders.
153
+
154
+ ### Patch Changes
155
+
156
+ - Updated dependencies [551eaa9]
157
+ - Updated dependencies [d2077bd]
158
+ - Updated dependencies [551eaa9]
159
+ - Updated dependencies [9ab73c5]
160
+ - Updated dependencies [5c6393f]
161
+ - @checkstack/ai-common@0.5.0
162
+ - @checkstack/auth-common@0.10.0
163
+ - @checkstack/auth-frontend@0.8.0
164
+ - @checkstack/common@0.16.0
165
+ - @checkstack/automation-common@0.7.0
166
+ - @checkstack/catalog-common@2.4.0
167
+ - @checkstack/ui@1.16.1
168
+ - @checkstack/frontend-api@0.10.0
169
+ - @checkstack/gitops-frontend@0.5.8
170
+ - @checkstack/integration-common@0.9.1
171
+ - @checkstack/script-packages-frontend@0.3.12
172
+ - @checkstack/secrets-frontend@0.2.7
173
+ - @checkstack/template-engine@0.4.5
174
+ - @checkstack/signal-frontend@0.2.5
175
+
3
176
  ## 0.6.1
4
177
 
5
178
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/automation-frontend",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "main": "src/index.tsx",
@@ -13,19 +13,20 @@
13
13
  "lint:code": "eslint . --max-warnings 0"
14
14
  },
15
15
  "dependencies": {
16
- "@checkstack/auth-common": "0.9.1",
17
- "@checkstack/automation-common": "0.6.0",
18
- "@checkstack/ai-common": "0.4.0",
19
- "@checkstack/catalog-common": "2.3.6",
20
- "@checkstack/common": "0.15.0",
21
- "@checkstack/frontend-api": "0.9.0",
22
- "@checkstack/gitops-frontend": "0.5.7",
23
- "@checkstack/integration-common": "0.9.0",
24
- "@checkstack/script-packages-frontend": "0.3.11",
25
- "@checkstack/secrets-frontend": "0.2.6",
26
- "@checkstack/signal-frontend": "0.2.4",
27
- "@checkstack/template-engine": "0.4.4",
28
- "@checkstack/ui": "1.16.0",
16
+ "@checkstack/auth-common": "0.10.0",
17
+ "@checkstack/auth-frontend": "0.8.1",
18
+ "@checkstack/automation-common": "0.7.0",
19
+ "@checkstack/ai-common": "0.5.0",
20
+ "@checkstack/catalog-common": "2.4.1",
21
+ "@checkstack/common": "0.16.0",
22
+ "@checkstack/frontend-api": "0.11.0",
23
+ "@checkstack/gitops-frontend": "0.5.9",
24
+ "@checkstack/integration-common": "0.9.1",
25
+ "@checkstack/script-packages-frontend": "0.3.13",
26
+ "@checkstack/secrets-frontend": "0.2.8",
27
+ "@checkstack/signal-frontend": "0.2.5",
28
+ "@checkstack/template-engine": "0.4.5",
29
+ "@checkstack/ui": "1.16.2",
29
30
  "@dnd-kit/core": "^6.3.1",
30
31
  "@dnd-kit/sortable": "^8.0.0",
31
32
  "@dnd-kit/utilities": "^3.2.2",
@@ -39,6 +40,6 @@
39
40
  "typescript": "^5.0.0",
40
41
  "@types/react": "^19.0.0",
41
42
  "@checkstack/tsconfig": "0.0.7",
42
- "@checkstack/scripts": "0.6.1"
43
+ "@checkstack/scripts": "0.6.2"
43
44
  }
44
45
  }
@@ -50,6 +50,7 @@ import {
50
50
  GitOpsLockBanner,
51
51
  useProvenanceLock,
52
52
  } from "@checkstack/gitops-frontend";
53
+ import { TeamOwnershipPicker, teamCreateErrorMessage } from "@checkstack/auth-frontend";
53
54
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
54
55
  import { AutomationDefinitionEditor } from "../editor/AutomationDefinitionEditor";
55
56
  import { assignDefaultIds } from "../editor/action-helpers";
@@ -112,6 +113,9 @@ const AutomationEditContent: React.FC = () => {
112
113
  const { allowed: hasManageAccess } = accessApi.useAccess(
113
114
  automationAccess.manage,
114
115
  );
116
+ // allowGlobal: reuse the manage access check — global manage permission
117
+ // means the caller may create a resource not scoped to any team.
118
+ const allowGlobal = hasManageAccess;
115
119
 
116
120
  // GitOps provenance lock: when this automation is declaratively managed,
117
121
  // disable manual edits + show a banner. `entityId` is the automation id
@@ -139,6 +143,10 @@ const AutomationEditContent: React.FC = () => {
139
143
  // "not chosen yet" — save is blocked until one is selected.
140
144
  const [runAsApplicationId, setRunAsApplicationId] = React.useState("");
141
145
  const [statusEnabled, setStatusEnabled] = React.useState(true);
146
+ // Owning team — create mode only. null means global (no team owner).
147
+ const [ownerTeamId, setOwnerTeamId] = React.useState<string | null>(null);
148
+ // Inline error for team-create failures (e.g. OWNER_TEAM_REQUIRED).
149
+ const [ownerTeamError, setOwnerTeamError] = React.useState<string | null>(null);
142
150
 
143
151
  // Existing group values for the picker's "pick existing" suggestions.
144
152
  const groupsQuery = client.listAutomationGroups.useQuery();
@@ -299,7 +307,14 @@ const AutomationEditContent: React.FC = () => {
299
307
  resolveRoute(automationRoutes.routes.edit, { automationId: data.id }),
300
308
  );
301
309
  },
302
- onError: (error) => toast.error(extractErrorMessage(error)),
310
+ onError: (error) => {
311
+ const inline = teamCreateErrorMessage(error);
312
+ if (inline) {
313
+ setOwnerTeamError(inline);
314
+ return;
315
+ }
316
+ toast.error(extractErrorMessage(error));
317
+ },
303
318
  });
304
319
 
305
320
  const updateMutation = client.updateAutomation.useMutation({
@@ -343,6 +358,7 @@ const AutomationEditContent: React.FC = () => {
343
358
  };
344
359
 
345
360
  const handleSave = async () => {
361
+ setOwnerTeamError(null);
346
362
  const committed = commitActiveTab();
347
363
  if (!committed) return;
348
364
 
@@ -375,6 +391,7 @@ const AutomationEditContent: React.FC = () => {
375
391
  runAs: runAsApplicationId,
376
392
  status: statusEnabled ? "enabled" : "disabled",
377
393
  definition: merged,
394
+ teamId: ownerTeamId ?? undefined,
378
395
  });
379
396
  } else if (automationId) {
380
397
  updateMutation.mutate({
@@ -555,6 +572,19 @@ const AutomationEditContent: React.FC = () => {
555
572
  disabled={!canManage}
556
573
  showError={!!runAsError}
557
574
  />
575
+ {/* Owning team — shown only when creating a new automation */}
576
+ {isNew && (
577
+ <TeamOwnershipPicker
578
+ value={ownerTeamId}
579
+ onChange={(id) => {
580
+ setOwnerTeamId(id);
581
+ setOwnerTeamError(null);
582
+ }}
583
+ allowGlobal={allowGlobal}
584
+ disabled={!canManage}
585
+ error={ownerTeamError}
586
+ />
587
+ )}
558
588
  <div className="flex items-center justify-between">
559
589
  <Label htmlFor="enabled">Enabled</Label>
560
590
  <Toggle
package/tsconfig.json CHANGED
@@ -10,6 +10,9 @@
10
10
  {
11
11
  "path": "../auth-common"
12
12
  },
13
+ {
14
+ "path": "../auth-frontend"
15
+ },
13
16
  {
14
17
  "path": "../automation-common"
15
18
  },