@opengeni/contracts 0.31.1 → 0.35.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.
@@ -16,12 +16,100 @@ export const OPENGENI_SLACK_BOT_REQUIRED_SCOPES = [
16
16
  "users:read",
17
17
  ] as const;
18
18
 
19
+ /** Read-only scope needed only for the optional emoji-reaction summon surface. */
20
+ export const OPENGENI_SLACK_REACTION_REQUIRED_SCOPE = "reactions:read" as const;
21
+
22
+ /**
23
+ * Scopes requested by the managed and generated self-hosted manifests.
24
+ *
25
+ * `reactions:read` is deliberately not part of the base eligibility contract:
26
+ * legacy installations may continue using existing Slack interactions and
27
+ * tools while the reaction setting stays disabled and the UI asks an admin to
28
+ * reinstall.
29
+ */
30
+ export const OPENGENI_SLACK_BOT_REQUESTED_SCOPES = [
31
+ ...OPENGENI_SLACK_BOT_REQUIRED_SCOPES,
32
+ OPENGENI_SLACK_REACTION_REQUIRED_SCOPE,
33
+ ] as const;
34
+
35
+ export const OPENGENI_SLACK_BOT_EVENTS = [
36
+ "app_mention",
37
+ "message.channels",
38
+ "message.groups",
39
+ "message.im",
40
+ "message.mpim",
41
+ "reaction_added",
42
+ ] as const;
43
+
44
+ export const OPENGENI_MANAGED_PUBLIC_BASE_URL = "https://app.opengeni.ai" as const;
45
+
46
+ function normalizedSlackManifestBaseUrl(publicBaseUrl: string): string {
47
+ const parsed = new URL(publicBaseUrl);
48
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password) {
49
+ throw new Error("Slack bot manifest public base URL must be credential-free HTTPS");
50
+ }
51
+ parsed.hash = "";
52
+ parsed.search = "";
53
+ parsed.pathname = parsed.pathname.replace(/\/+$/, "");
54
+ return parsed.toString().replace(/\/$/, "");
55
+ }
56
+
57
+ /** Canonical managed/self-hosted Slack app manifest. Slack accepts JSON manifests. */
58
+ export function buildOpenGeniSlackBotManifest(publicBaseUrl: string) {
59
+ const baseUrl = normalizedSlackManifestBaseUrl(publicBaseUrl);
60
+ return {
61
+ display_information: { name: "OpenGeni" },
62
+ features: {
63
+ bot_user: { display_name: "OpenGeni", always_online: false },
64
+ slash_commands: [
65
+ {
66
+ command: "/opengeni",
67
+ description: "Start an OpenGeni task in this channel",
68
+ should_escape: false,
69
+ url: `${baseUrl}/v1/integrations/slack/commands`,
70
+ },
71
+ ],
72
+ shortcuts: [
73
+ {
74
+ callback_id: "opengeni_message",
75
+ description: "Start an OpenGeni task from this Slack message",
76
+ name: "Open in OpenGeni",
77
+ type: "message",
78
+ },
79
+ ],
80
+ },
81
+ oauth_config: {
82
+ redirect_urls: [
83
+ `${baseUrl}/v1/integrations/oauth/callback`,
84
+ `${baseUrl}/v1/integrations/slack/callback`,
85
+ ],
86
+ scopes: { bot: [...OPENGENI_SLACK_BOT_REQUESTED_SCOPES] },
87
+ },
88
+ settings: {
89
+ event_subscriptions: {
90
+ bot_events: [...OPENGENI_SLACK_BOT_EVENTS],
91
+ request_url: `${baseUrl}/v1/integrations/slack/events`,
92
+ },
93
+ interactivity: {
94
+ is_enabled: true,
95
+ request_url: `${baseUrl}/v1/integrations/slack/interactions`,
96
+ },
97
+ org_deploy_enabled: false,
98
+ socket_mode_enabled: false,
99
+ token_rotation_enabled: false,
100
+ },
101
+ } as const;
102
+ }
103
+
19
104
  /**
20
105
  * Optional bot grants that remain inside the shipped bot's read/identity
21
106
  * boundary. Every other unrequired scope fails closed, including unknown future
22
107
  * Slack scopes, so verification, core routing, and UI eligibility cannot drift.
23
108
  */
24
- export const OPENGENI_SLACK_BOT_SAFE_OPTIONAL_SCOPES = ["team:read"] as const;
109
+ export const OPENGENI_SLACK_BOT_SAFE_OPTIONAL_SCOPES = [
110
+ "team:read",
111
+ OPENGENI_SLACK_REACTION_REQUIRED_SCOPE,
112
+ ] as const;
25
113
 
26
114
  /** @deprecated Use evaluateOpenGeniSlackBotScopes; an allowlist is the policy. */
27
115
  export const OPENGENI_SLACK_BOT_FORBIDDEN_SCOPES = ["channels:join", "chat:write.public"] as const;
@@ -46,6 +134,10 @@ export function areOpenGeniSlackBotScopesAccepted(grantedScopes: readonly string
46
134
  );
47
135
  }
48
136
 
137
+ export function hasOpenGeniSlackReactionScope(grantedScopes: readonly string[]): boolean {
138
+ return grantedScopes.includes(OPENGENI_SLACK_REACTION_REQUIRED_SCOPE);
139
+ }
140
+
49
141
  export function evaluateOpenGeniSlackBotScopes(
50
142
  grantedScopes: readonly string[],
51
143
  ): OpenGeniSlackBotScopePolicy {
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  export const WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS = 262_144;
4
+ export const WORKSPACE_INSTRUCTION_POLICY_PROMPT_MAX_UTF8_BYTES = 131_072;
4
5
  export const WORKSPACE_INSTRUCTION_POLICY_REASON_MAX_CHARS = 4_096;
5
6
  export const WORKSPACE_INSTRUCTION_POLICY_ROLE_KEY_MAX_CHARS = 64;
6
7
  export const WORKSPACE_INSTRUCTION_POLICY_SOURCE_ID_MAX_CHARS = 512;
@@ -50,10 +51,23 @@ export function normalizeWorkspaceInstructionPolicyRoleKey(value: string): strin
50
51
  return value.normalize("NFKC").trim().toLowerCase().replace(/\s+/gu, "-").replace(/-+/g, "-");
51
52
  }
52
53
 
53
- const RequestRoleKey = z
54
+ export const WorkspaceInstructionPolicyRoleKeyInput = z
54
55
  .string()
55
56
  .transform(normalizeWorkspaceInstructionPolicyRoleKey)
56
57
  .pipe(WorkspaceInstructionPolicyRoleKey);
58
+ export type WorkspaceInstructionPolicyRoleKeyInput = z.infer<
59
+ typeof WorkspaceInstructionPolicyRoleKeyInput
60
+ >;
61
+
62
+ export const WorkspaceInstructionPolicyRoleSource = z.enum([
63
+ "session_binding",
64
+ "metadata_fallback",
65
+ "none",
66
+ "invalid_metadata_fallback",
67
+ ]);
68
+ export type WorkspaceInstructionPolicyRoleSource = z.infer<
69
+ typeof WorkspaceInstructionPolicyRoleSource
70
+ >;
57
71
 
58
72
  const targetShape = {
59
73
  kind: WorkspaceInstructionPolicyKind,
@@ -116,6 +130,7 @@ export type WorkspaceInstructionPolicyRevisionIdentity = z.infer<
116
130
 
117
131
  export const WorkspaceInstructionPolicyRevision = z.object({
118
132
  ...revisionIdentityShape,
133
+ operationId: z.string().uuid(),
119
134
  accountId: z.string().uuid(),
120
135
  workspaceId: z.string().uuid(),
121
136
  ...targetShape,
@@ -138,8 +153,67 @@ export const WorkspaceInstructionPolicyHead = z.object({
138
153
  });
139
154
  export type WorkspaceInstructionPolicyHead = z.infer<typeof WorkspaceInstructionPolicyHead>;
140
155
 
156
+ export const WorkspaceInstructionPolicySnapshotProvenance = z.object({
157
+ source: WorkspaceInstructionPolicyProvenanceSource,
158
+ sourceIdHash: z
159
+ .string()
160
+ .regex(/^[0-9a-f]{64}$/)
161
+ .nullable(),
162
+ });
163
+ export type WorkspaceInstructionPolicySnapshotProvenance = z.infer<
164
+ typeof WorkspaceInstructionPolicySnapshotProvenance
165
+ >;
166
+
167
+ export const WorkspaceInstructionPolicySnapshotEntry = z.object({
168
+ kind: WorkspaceInstructionPolicyKind,
169
+ scope: WorkspaceInstructionPolicyScope,
170
+ roleKey: WorkspaceInstructionPolicyRoleKey.nullable(),
171
+ revisionId: z.string().uuid(),
172
+ revision: z.number().int().positive(),
173
+ contentHash: z.string().regex(/^[0-9a-f]{64}$/),
174
+ activationVersion: z.number().int().positive(),
175
+ activatedAt: z.string().datetime(),
176
+ provenance: WorkspaceInstructionPolicySnapshotProvenance,
177
+ });
178
+ export type WorkspaceInstructionPolicySnapshotEntry = z.infer<
179
+ typeof WorkspaceInstructionPolicySnapshotEntry
180
+ >;
181
+
182
+ export const WorkspaceInstructionPolicySnapshot = z.object({
183
+ id: z.string().uuid(),
184
+ workspaceId: z.string().uuid(),
185
+ sessionId: z.string().uuid(),
186
+ turnId: z.string().uuid(),
187
+ attemptId: z.string().uuid(),
188
+ executionGeneration: z.number().int().positive(),
189
+ policyRole: WorkspaceInstructionPolicyRoleKey.nullable(),
190
+ roleSource: WorkspaceInstructionPolicyRoleSource,
191
+ entryHash: z.string().regex(/^[0-9a-f]{64}$/),
192
+ entries: z.array(WorkspaceInstructionPolicySnapshotEntry).max(3),
193
+ createdAt: z.string().datetime(),
194
+ });
195
+ export type WorkspaceInstructionPolicySnapshot = z.infer<typeof WorkspaceInstructionPolicySnapshot>;
196
+
197
+ export const ResolvedWorkspaceInstructionPolicySnapshotEntry =
198
+ WorkspaceInstructionPolicySnapshotEntry.extend({
199
+ content: z.string().min(1).max(WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS),
200
+ });
201
+ export type ResolvedWorkspaceInstructionPolicySnapshotEntry = z.infer<
202
+ typeof ResolvedWorkspaceInstructionPolicySnapshotEntry
203
+ >;
204
+
205
+ export const ResolvedWorkspaceInstructionPolicySnapshot = WorkspaceInstructionPolicySnapshot.extend(
206
+ {
207
+ entries: z.array(ResolvedWorkspaceInstructionPolicySnapshotEntry).max(3),
208
+ },
209
+ );
210
+ export type ResolvedWorkspaceInstructionPolicySnapshot = z.infer<
211
+ typeof ResolvedWorkspaceInstructionPolicySnapshot
212
+ >;
213
+
141
214
  export const WorkspaceInstructionPolicyActivationEvent = z.object({
142
215
  id: z.string().uuid(),
216
+ operationId: z.string().uuid(),
143
217
  accountId: z.string().uuid(),
144
218
  workspaceId: z.string().uuid(),
145
219
  ...targetShape,
@@ -157,9 +231,10 @@ export type WorkspaceInstructionPolicyActivationEvent = z.infer<
157
231
 
158
232
  export const CreateWorkspaceInstructionPolicyDraftRequest = z
159
233
  .object({
234
+ operationId: z.string().uuid().optional(),
160
235
  kind: WorkspaceInstructionPolicyKind,
161
236
  scope: WorkspaceInstructionPolicyScope,
162
- roleKey: RequestRoleKey.nullable().default(null),
237
+ roleKey: WorkspaceInstructionPolicyRoleKeyInput.nullable().default(null),
163
238
  content: z
164
239
  .string()
165
240
  .min(1)
@@ -181,6 +256,7 @@ export type CreateWorkspaceInstructionPolicyDraftRequest = z.infer<
181
256
 
182
257
  export const ImportLegacyWorkspaceInstructionPolicyDraftRequest = z
183
258
  .object({
259
+ operationId: z.string().uuid().optional(),
184
260
  supersedesRevisionId: z.string().uuid().nullable().default(null),
185
261
  })
186
262
  .strict();
@@ -191,7 +267,7 @@ export type ImportLegacyWorkspaceInstructionPolicyDraftRequest = z.infer<
191
267
  export const WorkspaceInstructionPolicyListQuery = z.object({
192
268
  kind: WorkspaceInstructionPolicyKind.optional(),
193
269
  scope: WorkspaceInstructionPolicyScope.optional(),
194
- roleKey: RequestRoleKey.nullable().optional(),
270
+ roleKey: WorkspaceInstructionPolicyRoleKeyInput.nullable().optional(),
195
271
  afterRevision: z.coerce.number().int().positive().optional(),
196
272
  limit: z.coerce.number().int().min(1).max(100).default(50),
197
273
  });
@@ -233,7 +309,9 @@ export type WorkspaceInstructionPolicyDiffResponse = z.infer<
233
309
  >;
234
310
 
235
311
  export const ActivateWorkspaceInstructionPolicyRequest = z.object({
312
+ operationId: z.string().uuid().optional(),
236
313
  expectedCurrentRevisionId: z.string().uuid().nullable(),
314
+ expectedActivationVersion: z.number().int().nonnegative().optional(),
237
315
  reason: z.string().trim().min(1).max(WORKSPACE_INSTRUCTION_POLICY_REASON_MAX_CHARS),
238
316
  });
239
317
  export type ActivateWorkspaceInstructionPolicyRequest = z.infer<
@@ -241,8 +319,10 @@ export type ActivateWorkspaceInstructionPolicyRequest = z.infer<
241
319
  >;
242
320
 
243
321
  export const RollbackWorkspaceInstructionPolicyRequest = z.object({
322
+ operationId: z.string().uuid().optional(),
244
323
  targetRevisionId: z.string().uuid(),
245
324
  expectedCurrentRevisionId: z.string().uuid(),
325
+ expectedActivationVersion: z.number().int().positive().optional(),
246
326
  reason: z.string().trim().min(1).max(WORKSPACE_INSTRUCTION_POLICY_REASON_MAX_CHARS),
247
327
  });
248
328
  export type RollbackWorkspaceInstructionPolicyRequest = z.infer<
@@ -265,3 +345,11 @@ export const WorkspaceInstructionPolicyConflictResponse = z.object({
265
345
  export type WorkspaceInstructionPolicyConflictResponse = z.infer<
266
346
  typeof WorkspaceInstructionPolicyConflictResponse
267
347
  >;
348
+
349
+ export const WorkspaceInstructionPolicyOperationReuseResponse = z.object({
350
+ code: z.literal("WORKSPACE_INSTRUCTION_POLICY_OPERATION_REUSED"),
351
+ message: z.string(),
352
+ });
353
+ export type WorkspaceInstructionPolicyOperationReuseResponse = z.infer<
354
+ typeof WorkspaceInstructionPolicyOperationReuseResponse
355
+ >;
@@ -4,7 +4,9 @@ import {
4
4
  WorkspaceInstructionPolicyKind,
5
5
  WorkspaceInstructionPolicyProvenanceSource,
6
6
  WorkspaceInstructionPolicyRoleKey,
7
+ WorkspaceInstructionPolicyRoleSource,
7
8
  WorkspaceInstructionPolicyScope,
9
+ WorkspaceInstructionPolicySnapshotEntry,
8
10
  } from "./workspace-instruction-policies";
9
11
 
10
12
  export const WORKSPACE_STATE_MAX_ACTIVE_POLICY_HEADS = 32;
@@ -17,6 +19,9 @@ export const WORKSPACE_STATE_MEMORY_SAMPLE_LIMIT = 100;
17
19
 
18
20
  const Count = z.number().int().nonnegative();
19
21
 
22
+ export const WorkspaceStateQuery = z.object({ attemptId: z.string().uuid().optional() }).strict();
23
+ export type WorkspaceStateQuery = z.infer<typeof WorkspaceStateQuery>;
24
+
20
25
  export const WorkspaceStateDocumentStatusCounts = z
21
26
  .object({
22
27
  queued: Count,
@@ -196,6 +201,112 @@ export const WorkspaceStateKnowledge = z.discriminatedUnion("availability", [
196
201
  ]);
197
202
  export type WorkspaceStateKnowledge = z.infer<typeof WorkspaceStateKnowledge>;
198
203
 
204
+ export const WorkspaceStateGovernanceDriftStatus = z.enum([
205
+ "identical",
206
+ "changed",
207
+ "superseded",
208
+ "missing",
209
+ "unavailable",
210
+ "truncated",
211
+ ]);
212
+ export type WorkspaceStateGovernanceDriftStatus = z.infer<
213
+ typeof WorkspaceStateGovernanceDriftStatus
214
+ >;
215
+
216
+ const WorkspaceStatePolicySnapshotAvailable = z
217
+ .object({
218
+ status: z.literal("available"),
219
+ id: z.string().uuid(),
220
+ createdAt: z.string().datetime(),
221
+ entryHash: z.string().regex(/^[0-9a-f]{64}$/),
222
+ policyRole: WorkspaceInstructionPolicyRoleKey.nullable(),
223
+ roleSource: WorkspaceInstructionPolicyRoleSource,
224
+ entries: z.array(WorkspaceInstructionPolicySnapshotEntry).max(3),
225
+ })
226
+ .strict();
227
+
228
+ const WorkspaceStatePolicySnapshotMissing = z.object({ status: z.literal("missing") }).strict();
229
+
230
+ const WorkspaceStatePreferenceSnapshotAvailable = z
231
+ .object({
232
+ status: z.literal("available"),
233
+ id: z.string().uuid(),
234
+ createdAt: z.string().datetime(),
235
+ descriptorHash: z.string().regex(/^[0-9a-f]{64}$/),
236
+ descriptorCount: Count.max(64),
237
+ truncated: z.boolean(),
238
+ })
239
+ .strict();
240
+
241
+ const WorkspaceStatePreferenceSnapshotMissing = z.object({ status: z.literal("missing") }).strict();
242
+
243
+ const WorkspaceStateGovernanceDrift = z
244
+ .object({
245
+ overall: WorkspaceStateGovernanceDriftStatus,
246
+ policy: z
247
+ .object({
248
+ status: WorkspaceStateGovernanceDriftStatus,
249
+ snapshotHash: z
250
+ .string()
251
+ .regex(/^[0-9a-f]{64}$/)
252
+ .nullable(),
253
+ currentHash: z
254
+ .string()
255
+ .regex(/^[0-9a-f]{64}$/)
256
+ .nullable(),
257
+ snapshotTargetCount: Count,
258
+ currentTargetCount: Count,
259
+ })
260
+ .strict(),
261
+ preferences: z
262
+ .object({
263
+ status: WorkspaceStateGovernanceDriftStatus,
264
+ snapshotHash: z
265
+ .string()
266
+ .regex(/^[0-9a-f]{64}$/)
267
+ .nullable(),
268
+ currentHash: z
269
+ .string()
270
+ .regex(/^[0-9a-f]{64}$/)
271
+ .nullable(),
272
+ snapshotDescriptorCount: Count,
273
+ currentDescriptorCount: Count,
274
+ snapshotTruncated: z.boolean(),
275
+ currentTruncated: z.boolean(),
276
+ })
277
+ .strict(),
278
+ })
279
+ .strict();
280
+
281
+ export const WorkspaceStateAttemptGovernance = z.discriminatedUnion("status", [
282
+ z.object({ status: z.literal("not_requested") }).strict(),
283
+ z
284
+ .object({
285
+ status: z.literal("unavailable"),
286
+ reason: z.literal("attempt_not_found_or_not_authorized"),
287
+ driftStatus: z.literal("unavailable"),
288
+ })
289
+ .strict(),
290
+ z
291
+ .object({
292
+ status: z.literal("available"),
293
+ attemptId: z.string().uuid(),
294
+ executionGeneration: z.number().int().positive(),
295
+ acceptedAt: z.string().datetime(),
296
+ policySnapshot: z.discriminatedUnion("status", [
297
+ WorkspaceStatePolicySnapshotAvailable,
298
+ WorkspaceStatePolicySnapshotMissing,
299
+ ]),
300
+ preferenceSnapshot: z.discriminatedUnion("status", [
301
+ WorkspaceStatePreferenceSnapshotAvailable,
302
+ WorkspaceStatePreferenceSnapshotMissing,
303
+ ]),
304
+ drift: WorkspaceStateGovernanceDrift,
305
+ })
306
+ .strict(),
307
+ ]);
308
+ export type WorkspaceStateAttemptGovernance = z.infer<typeof WorkspaceStateAttemptGovernance>;
309
+
199
310
  export const WorkspaceStateResponse = z
200
311
  .object({
201
312
  workspaceId: z.string().uuid(),
@@ -208,12 +319,7 @@ export const WorkspaceStateResponse = z
208
319
  capturedAt: z.string().datetime(),
209
320
  })
210
321
  .strict(),
211
- policySnapshot: z
212
- .object({
213
- status: z.literal("not_captured"),
214
- reason: z.literal("workspace_instruction_policy_snapshot_not_implemented"),
215
- })
216
- .strict(),
322
+ attemptGovernance: WorkspaceStateAttemptGovernance,
217
323
  })
218
324
  .strict(),
219
325
  policy: WorkspaceStatePolicy,
@@ -1,45 +0,0 @@
1
- // src/slack-bot-scopes.ts
2
- var OPENGENI_SLACK_BOT_REQUIRED_SCOPES = [
3
- "app_mentions:read",
4
- "canvases:read",
5
- "channels:history",
6
- "channels:read",
7
- "chat:write",
8
- "commands",
9
- "files:read",
10
- "groups:history",
11
- "groups:read",
12
- "im:history",
13
- "im:read",
14
- "im:write",
15
- "mpim:history",
16
- "mpim:read",
17
- "users:read"
18
- ];
19
- var OPENGENI_SLACK_BOT_SAFE_OPTIONAL_SCOPES = ["team:read"];
20
- var OPENGENI_SLACK_BOT_FORBIDDEN_SCOPES = ["channels:join", "chat:write.public"];
21
- function isOpenGeniSlackBotScopeAllowed(scope) {
22
- return OPENGENI_SLACK_BOT_REQUIRED_SCOPES.includes(scope) || OPENGENI_SLACK_BOT_SAFE_OPTIONAL_SCOPES.includes(scope);
23
- }
24
- function areOpenGeniSlackBotScopesAccepted(grantedScopes) {
25
- return OPENGENI_SLACK_BOT_REQUIRED_SCOPES.every((scope) => grantedScopes.includes(scope)) && grantedScopes.every(isOpenGeniSlackBotScopeAllowed);
26
- }
27
- function evaluateOpenGeniSlackBotScopes(grantedScopes) {
28
- const granted = new Set(grantedScopes);
29
- const missingRequired = OPENGENI_SLACK_BOT_REQUIRED_SCOPES.filter((scope) => !granted.has(scope));
30
- const unsupported = [...granted].filter((scope) => !isOpenGeniSlackBotScopeAllowed(scope)).sort();
31
- return {
32
- accepted: areOpenGeniSlackBotScopesAccepted(grantedScopes),
33
- missingRequired,
34
- unsupported
35
- };
36
- }
37
-
38
- export {
39
- OPENGENI_SLACK_BOT_REQUIRED_SCOPES,
40
- OPENGENI_SLACK_BOT_SAFE_OPTIONAL_SCOPES,
41
- OPENGENI_SLACK_BOT_FORBIDDEN_SCOPES,
42
- areOpenGeniSlackBotScopesAccepted,
43
- evaluateOpenGeniSlackBotScopes
44
- };
45
- //# sourceMappingURL=chunk-KGL5BVGF.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/slack-bot-scopes.ts"],"sourcesContent":["export const OPENGENI_SLACK_BOT_REQUIRED_SCOPES = [\n \"app_mentions:read\",\n \"canvases:read\",\n \"channels:history\",\n \"channels:read\",\n \"chat:write\",\n \"commands\",\n \"files:read\",\n \"groups:history\",\n \"groups:read\",\n \"im:history\",\n \"im:read\",\n \"im:write\",\n \"mpim:history\",\n \"mpim:read\",\n \"users:read\",\n] as const;\n\n/**\n * Optional bot grants that remain inside the shipped bot's read/identity\n * boundary. Every other unrequired scope fails closed, including unknown future\n * Slack scopes, so verification, core routing, and UI eligibility cannot drift.\n */\nexport const OPENGENI_SLACK_BOT_SAFE_OPTIONAL_SCOPES = [\"team:read\"] as const;\n\n/** @deprecated Use evaluateOpenGeniSlackBotScopes; an allowlist is the policy. */\nexport const OPENGENI_SLACK_BOT_FORBIDDEN_SCOPES = [\"channels:join\", \"chat:write.public\"] as const;\n\nexport type OpenGeniSlackBotScopePolicy = {\n accepted: boolean;\n missingRequired: string[];\n unsupported: string[];\n};\n\nfunction isOpenGeniSlackBotScopeAllowed(scope: string): boolean {\n return (\n (OPENGENI_SLACK_BOT_REQUIRED_SCOPES as readonly string[]).includes(scope) ||\n (OPENGENI_SLACK_BOT_SAFE_OPTIONAL_SCOPES as readonly string[]).includes(scope)\n );\n}\n\nexport function areOpenGeniSlackBotScopesAccepted(grantedScopes: readonly string[]): boolean {\n return (\n OPENGENI_SLACK_BOT_REQUIRED_SCOPES.every((scope) => grantedScopes.includes(scope)) &&\n grantedScopes.every(isOpenGeniSlackBotScopeAllowed)\n );\n}\n\nexport function evaluateOpenGeniSlackBotScopes(\n grantedScopes: readonly string[],\n): OpenGeniSlackBotScopePolicy {\n const granted = new Set(grantedScopes);\n const missingRequired = OPENGENI_SLACK_BOT_REQUIRED_SCOPES.filter((scope) => !granted.has(scope));\n const unsupported = [...granted].filter((scope) => !isOpenGeniSlackBotScopeAllowed(scope)).sort();\n return {\n accepted: areOpenGeniSlackBotScopesAccepted(grantedScopes),\n missingRequired,\n unsupported,\n };\n}\n"],"mappings":";AAAO,IAAM,qCAAqC;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,IAAM,0CAA0C,CAAC,WAAW;AAG5D,IAAM,sCAAsC,CAAC,iBAAiB,mBAAmB;AAQxF,SAAS,+BAA+B,OAAwB;AAC9D,SACG,mCAAyD,SAAS,KAAK,KACvE,wCAA8D,SAAS,KAAK;AAEjF;AAEO,SAAS,kCAAkC,eAA2C;AAC3F,SACE,mCAAmC,MAAM,CAAC,UAAU,cAAc,SAAS,KAAK,CAAC,KACjF,cAAc,MAAM,8BAA8B;AAEtD;AAEO,SAAS,+BACd,eAC6B;AAC7B,QAAM,UAAU,IAAI,IAAI,aAAa;AACrC,QAAM,kBAAkB,mCAAmC,OAAO,CAAC,UAAU,CAAC,QAAQ,IAAI,KAAK,CAAC;AAChG,QAAM,cAAc,CAAC,GAAG,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,+BAA+B,KAAK,CAAC,EAAE,KAAK;AAChG,SAAO;AAAA,IACL,UAAU,kCAAkC,aAAa;AAAA,IACzD;AAAA,IACA;AAAA,EACF;AACF;","names":[]}