@app-vitals/agent-admin 0.4.0 → 0.6.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/openapi.json +104 -1
- package/openapi.platform.json +104 -1
- package/package.json +2 -2
- package/prisma/client/commonInputTypes.ts +75 -0
- package/prisma/client/internal/class.ts +4 -4
- package/prisma/client/internal/prismaNamespace.ts +9 -0
- package/prisma/client/internal/prismaNamespaceBrowser.ts +9 -0
- package/prisma/client/models/AgentGitHubInstallationsSnapshot.ts +27 -1
- package/prisma/migrations/20261028000000_add_repo_access_to_github_installations_snapshot/migration.sql +3 -0
- package/prisma/schema/schema.prisma +1 -0
- package/src/admin-boot.ts +7 -0
- package/src/agent-account-assignment.ts +42 -10
- package/src/agent-github-installations.integration.test.ts +32 -0
- package/src/agent-github-installations.ts +11 -1
- package/src/agent-provisioner.ts +69 -4
- package/src/agent-provisioner.unit.test.ts +136 -0
- package/src/agents-api.integration.test.ts +147 -0
- package/src/agents-api.smoke.test.ts +288 -1
- package/src/agents-api.ts +89 -4
- package/src/agents.ts +7 -0
- package/src/api.smoke.test.ts +15 -0
- package/src/api.ts +6 -2
- package/src/kubernetes-client.unit.test.ts +22 -0
- package/src/openapi-schemas.ts +51 -0
- package/src/openapi-schemas.unit.test.ts +31 -0
- package/src/repo-access-config.ts +22 -0
- package/src/repo-access-config.unit.test.ts +34 -0
|
@@ -2749,6 +2749,7 @@ export const AgentGitHubInstallationsSnapshotScalarFieldEnum = {
|
|
|
2749
2749
|
agentId: 'agentId',
|
|
2750
2750
|
reportedAt: 'reportedAt',
|
|
2751
2751
|
installations: 'installations',
|
|
2752
|
+
repoAccess: 'repoAccess',
|
|
2752
2753
|
createdAt: 'createdAt'
|
|
2753
2754
|
} as const
|
|
2754
2755
|
|
|
@@ -2808,6 +2809,14 @@ export const JsonNullValueInput = {
|
|
|
2808
2809
|
export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput]
|
|
2809
2810
|
|
|
2810
2811
|
|
|
2812
|
+
export const NullableJsonNullValueInput = {
|
|
2813
|
+
DbNull: DbNull,
|
|
2814
|
+
JsonNull: JsonNull
|
|
2815
|
+
} as const
|
|
2816
|
+
|
|
2817
|
+
export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput]
|
|
2818
|
+
|
|
2819
|
+
|
|
2811
2820
|
export const QueryMode = {
|
|
2812
2821
|
default: 'default',
|
|
2813
2822
|
insensitive: 'insensitive'
|
|
@@ -439,6 +439,7 @@ export const AgentGitHubInstallationsSnapshotScalarFieldEnum = {
|
|
|
439
439
|
agentId: 'agentId',
|
|
440
440
|
reportedAt: 'reportedAt',
|
|
441
441
|
installations: 'installations',
|
|
442
|
+
repoAccess: 'repoAccess',
|
|
442
443
|
createdAt: 'createdAt'
|
|
443
444
|
} as const
|
|
444
445
|
|
|
@@ -498,6 +499,14 @@ export const JsonNullValueInput = {
|
|
|
498
499
|
export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput]
|
|
499
500
|
|
|
500
501
|
|
|
502
|
+
export const NullableJsonNullValueInput = {
|
|
503
|
+
DbNull: DbNull,
|
|
504
|
+
JsonNull: JsonNull
|
|
505
|
+
} as const
|
|
506
|
+
|
|
507
|
+
export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput]
|
|
508
|
+
|
|
509
|
+
|
|
501
510
|
export const QueryMode = {
|
|
502
511
|
default: 'default',
|
|
503
512
|
insensitive: 'insensitive'
|
|
@@ -43,6 +43,7 @@ export type AgentGitHubInstallationsSnapshotCountAggregateOutputType = {
|
|
|
43
43
|
agentId: number
|
|
44
44
|
reportedAt: number
|
|
45
45
|
installations: number
|
|
46
|
+
repoAccess: number
|
|
46
47
|
createdAt: number
|
|
47
48
|
_all: number
|
|
48
49
|
}
|
|
@@ -67,6 +68,7 @@ export type AgentGitHubInstallationsSnapshotCountAggregateInputType = {
|
|
|
67
68
|
agentId?: true
|
|
68
69
|
reportedAt?: true
|
|
69
70
|
installations?: true
|
|
71
|
+
repoAccess?: true
|
|
70
72
|
createdAt?: true
|
|
71
73
|
_all?: true
|
|
72
74
|
}
|
|
@@ -148,6 +150,7 @@ export type AgentGitHubInstallationsSnapshotGroupByOutputType = {
|
|
|
148
150
|
agentId: string
|
|
149
151
|
reportedAt: Date
|
|
150
152
|
installations: runtime.JsonValue
|
|
153
|
+
repoAccess: runtime.JsonValue | null
|
|
151
154
|
createdAt: Date
|
|
152
155
|
_count: AgentGitHubInstallationsSnapshotCountAggregateOutputType | null
|
|
153
156
|
_min: AgentGitHubInstallationsSnapshotMinAggregateOutputType | null
|
|
@@ -177,6 +180,7 @@ export type AgentGitHubInstallationsSnapshotWhereInput = {
|
|
|
177
180
|
agentId?: Prisma.StringFilter<"AgentGitHubInstallationsSnapshot"> | string
|
|
178
181
|
reportedAt?: Prisma.DateTimeFilter<"AgentGitHubInstallationsSnapshot"> | Date | string
|
|
179
182
|
installations?: Prisma.JsonFilter<"AgentGitHubInstallationsSnapshot">
|
|
183
|
+
repoAccess?: Prisma.JsonNullableFilter<"AgentGitHubInstallationsSnapshot">
|
|
180
184
|
createdAt?: Prisma.DateTimeFilter<"AgentGitHubInstallationsSnapshot"> | Date | string
|
|
181
185
|
agent?: Prisma.XOR<Prisma.AgentScalarRelationFilter, Prisma.AgentWhereInput>
|
|
182
186
|
}
|
|
@@ -186,6 +190,7 @@ export type AgentGitHubInstallationsSnapshotOrderByWithRelationInput = {
|
|
|
186
190
|
agentId?: Prisma.SortOrder
|
|
187
191
|
reportedAt?: Prisma.SortOrder
|
|
188
192
|
installations?: Prisma.SortOrder
|
|
193
|
+
repoAccess?: Prisma.SortOrderInput | Prisma.SortOrder
|
|
189
194
|
createdAt?: Prisma.SortOrder
|
|
190
195
|
agent?: Prisma.AgentOrderByWithRelationInput
|
|
191
196
|
}
|
|
@@ -198,6 +203,7 @@ export type AgentGitHubInstallationsSnapshotWhereUniqueInput = Prisma.AtLeast<{
|
|
|
198
203
|
NOT?: Prisma.AgentGitHubInstallationsSnapshotWhereInput | Prisma.AgentGitHubInstallationsSnapshotWhereInput[]
|
|
199
204
|
reportedAt?: Prisma.DateTimeFilter<"AgentGitHubInstallationsSnapshot"> | Date | string
|
|
200
205
|
installations?: Prisma.JsonFilter<"AgentGitHubInstallationsSnapshot">
|
|
206
|
+
repoAccess?: Prisma.JsonNullableFilter<"AgentGitHubInstallationsSnapshot">
|
|
201
207
|
createdAt?: Prisma.DateTimeFilter<"AgentGitHubInstallationsSnapshot"> | Date | string
|
|
202
208
|
agent?: Prisma.XOR<Prisma.AgentScalarRelationFilter, Prisma.AgentWhereInput>
|
|
203
209
|
}, "id" | "agentId">
|
|
@@ -207,6 +213,7 @@ export type AgentGitHubInstallationsSnapshotOrderByWithAggregationInput = {
|
|
|
207
213
|
agentId?: Prisma.SortOrder
|
|
208
214
|
reportedAt?: Prisma.SortOrder
|
|
209
215
|
installations?: Prisma.SortOrder
|
|
216
|
+
repoAccess?: Prisma.SortOrderInput | Prisma.SortOrder
|
|
210
217
|
createdAt?: Prisma.SortOrder
|
|
211
218
|
_count?: Prisma.AgentGitHubInstallationsSnapshotCountOrderByAggregateInput
|
|
212
219
|
_max?: Prisma.AgentGitHubInstallationsSnapshotMaxOrderByAggregateInput
|
|
@@ -221,6 +228,7 @@ export type AgentGitHubInstallationsSnapshotScalarWhereWithAggregatesInput = {
|
|
|
221
228
|
agentId?: Prisma.StringWithAggregatesFilter<"AgentGitHubInstallationsSnapshot"> | string
|
|
222
229
|
reportedAt?: Prisma.DateTimeWithAggregatesFilter<"AgentGitHubInstallationsSnapshot"> | Date | string
|
|
223
230
|
installations?: Prisma.JsonWithAggregatesFilter<"AgentGitHubInstallationsSnapshot">
|
|
231
|
+
repoAccess?: Prisma.JsonNullableWithAggregatesFilter<"AgentGitHubInstallationsSnapshot">
|
|
224
232
|
createdAt?: Prisma.DateTimeWithAggregatesFilter<"AgentGitHubInstallationsSnapshot"> | Date | string
|
|
225
233
|
}
|
|
226
234
|
|
|
@@ -228,6 +236,7 @@ export type AgentGitHubInstallationsSnapshotCreateInput = {
|
|
|
228
236
|
id?: string
|
|
229
237
|
reportedAt: Date | string
|
|
230
238
|
installations: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
239
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
231
240
|
createdAt?: Date | string
|
|
232
241
|
agent: Prisma.AgentCreateNestedOneWithoutGithubInstallationsSnapshotInput
|
|
233
242
|
}
|
|
@@ -237,6 +246,7 @@ export type AgentGitHubInstallationsSnapshotUncheckedCreateInput = {
|
|
|
237
246
|
agentId: string
|
|
238
247
|
reportedAt: Date | string
|
|
239
248
|
installations: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
249
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
240
250
|
createdAt?: Date | string
|
|
241
251
|
}
|
|
242
252
|
|
|
@@ -244,6 +254,7 @@ export type AgentGitHubInstallationsSnapshotUpdateInput = {
|
|
|
244
254
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
|
245
255
|
reportedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
246
256
|
installations?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
257
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
247
258
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
248
259
|
agent?: Prisma.AgentUpdateOneRequiredWithoutGithubInstallationsSnapshotNestedInput
|
|
249
260
|
}
|
|
@@ -253,6 +264,7 @@ export type AgentGitHubInstallationsSnapshotUncheckedUpdateInput = {
|
|
|
253
264
|
agentId?: Prisma.StringFieldUpdateOperationsInput | string
|
|
254
265
|
reportedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
255
266
|
installations?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
267
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
256
268
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
257
269
|
}
|
|
258
270
|
|
|
@@ -261,6 +273,7 @@ export type AgentGitHubInstallationsSnapshotCreateManyInput = {
|
|
|
261
273
|
agentId: string
|
|
262
274
|
reportedAt: Date | string
|
|
263
275
|
installations: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
276
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
264
277
|
createdAt?: Date | string
|
|
265
278
|
}
|
|
266
279
|
|
|
@@ -268,6 +281,7 @@ export type AgentGitHubInstallationsSnapshotUpdateManyMutationInput = {
|
|
|
268
281
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
|
269
282
|
reportedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
270
283
|
installations?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
284
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
271
285
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
272
286
|
}
|
|
273
287
|
|
|
@@ -276,6 +290,7 @@ export type AgentGitHubInstallationsSnapshotUncheckedUpdateManyInput = {
|
|
|
276
290
|
agentId?: Prisma.StringFieldUpdateOperationsInput | string
|
|
277
291
|
reportedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
278
292
|
installations?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
293
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
279
294
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
280
295
|
}
|
|
281
296
|
|
|
@@ -289,6 +304,7 @@ export type AgentGitHubInstallationsSnapshotCountOrderByAggregateInput = {
|
|
|
289
304
|
agentId?: Prisma.SortOrder
|
|
290
305
|
reportedAt?: Prisma.SortOrder
|
|
291
306
|
installations?: Prisma.SortOrder
|
|
307
|
+
repoAccess?: Prisma.SortOrder
|
|
292
308
|
createdAt?: Prisma.SortOrder
|
|
293
309
|
}
|
|
294
310
|
|
|
@@ -342,6 +358,7 @@ export type AgentGitHubInstallationsSnapshotCreateWithoutAgentInput = {
|
|
|
342
358
|
id?: string
|
|
343
359
|
reportedAt: Date | string
|
|
344
360
|
installations: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
361
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
345
362
|
createdAt?: Date | string
|
|
346
363
|
}
|
|
347
364
|
|
|
@@ -349,6 +366,7 @@ export type AgentGitHubInstallationsSnapshotUncheckedCreateWithoutAgentInput = {
|
|
|
349
366
|
id?: string
|
|
350
367
|
reportedAt: Date | string
|
|
351
368
|
installations: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
369
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
352
370
|
createdAt?: Date | string
|
|
353
371
|
}
|
|
354
372
|
|
|
@@ -372,6 +390,7 @@ export type AgentGitHubInstallationsSnapshotUpdateWithoutAgentInput = {
|
|
|
372
390
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
|
373
391
|
reportedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
374
392
|
installations?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
393
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
375
394
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
376
395
|
}
|
|
377
396
|
|
|
@@ -379,6 +398,7 @@ export type AgentGitHubInstallationsSnapshotUncheckedUpdateWithoutAgentInput = {
|
|
|
379
398
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
|
380
399
|
reportedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
381
400
|
installations?: Prisma.JsonNullValueInput | runtime.InputJsonValue
|
|
401
|
+
repoAccess?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue
|
|
382
402
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
|
383
403
|
}
|
|
384
404
|
|
|
@@ -389,6 +409,7 @@ export type AgentGitHubInstallationsSnapshotSelect<ExtArgs extends runtime.Types
|
|
|
389
409
|
agentId?: boolean
|
|
390
410
|
reportedAt?: boolean
|
|
391
411
|
installations?: boolean
|
|
412
|
+
repoAccess?: boolean
|
|
392
413
|
createdAt?: boolean
|
|
393
414
|
agent?: boolean | Prisma.AgentDefaultArgs<ExtArgs>
|
|
394
415
|
}, ExtArgs["result"]["agentGitHubInstallationsSnapshot"]>
|
|
@@ -398,6 +419,7 @@ export type AgentGitHubInstallationsSnapshotSelectCreateManyAndReturn<ExtArgs ex
|
|
|
398
419
|
agentId?: boolean
|
|
399
420
|
reportedAt?: boolean
|
|
400
421
|
installations?: boolean
|
|
422
|
+
repoAccess?: boolean
|
|
401
423
|
createdAt?: boolean
|
|
402
424
|
agent?: boolean | Prisma.AgentDefaultArgs<ExtArgs>
|
|
403
425
|
}, ExtArgs["result"]["agentGitHubInstallationsSnapshot"]>
|
|
@@ -407,6 +429,7 @@ export type AgentGitHubInstallationsSnapshotSelectUpdateManyAndReturn<ExtArgs ex
|
|
|
407
429
|
agentId?: boolean
|
|
408
430
|
reportedAt?: boolean
|
|
409
431
|
installations?: boolean
|
|
432
|
+
repoAccess?: boolean
|
|
410
433
|
createdAt?: boolean
|
|
411
434
|
agent?: boolean | Prisma.AgentDefaultArgs<ExtArgs>
|
|
412
435
|
}, ExtArgs["result"]["agentGitHubInstallationsSnapshot"]>
|
|
@@ -416,10 +439,11 @@ export type AgentGitHubInstallationsSnapshotSelectScalar = {
|
|
|
416
439
|
agentId?: boolean
|
|
417
440
|
reportedAt?: boolean
|
|
418
441
|
installations?: boolean
|
|
442
|
+
repoAccess?: boolean
|
|
419
443
|
createdAt?: boolean
|
|
420
444
|
}
|
|
421
445
|
|
|
422
|
-
export type AgentGitHubInstallationsSnapshotOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "agentId" | "reportedAt" | "installations" | "createdAt", ExtArgs["result"]["agentGitHubInstallationsSnapshot"]>
|
|
446
|
+
export type AgentGitHubInstallationsSnapshotOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "agentId" | "reportedAt" | "installations" | "repoAccess" | "createdAt", ExtArgs["result"]["agentGitHubInstallationsSnapshot"]>
|
|
423
447
|
export type AgentGitHubInstallationsSnapshotInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
|
424
448
|
agent?: boolean | Prisma.AgentDefaultArgs<ExtArgs>
|
|
425
449
|
}
|
|
@@ -440,6 +464,7 @@ export type $AgentGitHubInstallationsSnapshotPayload<ExtArgs extends runtime.Typ
|
|
|
440
464
|
agentId: string
|
|
441
465
|
reportedAt: Date
|
|
442
466
|
installations: runtime.JsonValue
|
|
467
|
+
repoAccess: runtime.JsonValue | null
|
|
443
468
|
createdAt: Date
|
|
444
469
|
}, ExtArgs["result"]["agentGitHubInstallationsSnapshot"]>
|
|
445
470
|
composites: {}
|
|
@@ -869,6 +894,7 @@ export interface AgentGitHubInstallationsSnapshotFieldRefs {
|
|
|
869
894
|
readonly agentId: Prisma.FieldRef<"AgentGitHubInstallationsSnapshot", 'String'>
|
|
870
895
|
readonly reportedAt: Prisma.FieldRef<"AgentGitHubInstallationsSnapshot", 'DateTime'>
|
|
871
896
|
readonly installations: Prisma.FieldRef<"AgentGitHubInstallationsSnapshot", 'Json'>
|
|
897
|
+
readonly repoAccess: Prisma.FieldRef<"AgentGitHubInstallationsSnapshot", 'Json'>
|
|
872
898
|
readonly createdAt: Prisma.FieldRef<"AgentGitHubInstallationsSnapshot", 'DateTime'>
|
|
873
899
|
}
|
|
874
900
|
|
|
@@ -496,6 +496,7 @@ model AgentGitHubInstallationsSnapshot {
|
|
|
496
496
|
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
|
497
497
|
reportedAt DateTime
|
|
498
498
|
installations Json // GitHubInstallation[]
|
|
499
|
+
repoAccess Json? // RepoAccessEntry[] — per-repo read/write probe (COS-44.1); null until a flagged runtime reports it
|
|
499
500
|
createdAt DateTime @default(now())
|
|
500
501
|
}
|
|
501
502
|
|
package/src/admin-boot.ts
CHANGED
|
@@ -74,6 +74,7 @@ import { AgentAskService } from "./agent-ask.ts";
|
|
|
74
74
|
import { parseAgentAskConfig } from "./agent-ask-config.ts";
|
|
75
75
|
import { CronRunNowService } from "./cron-run-now.ts";
|
|
76
76
|
import { parseCronRunNowConfig } from "./cron-run-now-config.ts";
|
|
77
|
+
import { parsePodServiceAccountConfig } from "./pod-service-account-config.ts";
|
|
77
78
|
import { AgentChatTokenService } from "./agent-chat-tokens.ts";
|
|
78
79
|
import { AgentCronJobService } from "./agent-cron-jobs.ts";
|
|
79
80
|
import { AgentCronRunStatsService } from "./agent-cron-run-stats.ts";
|
|
@@ -104,6 +105,7 @@ import { mountPlatformDoc } from "./platform-doc-route.ts";
|
|
|
104
105
|
import { createAdminPrismaClient } from "./prisma-client.ts";
|
|
105
106
|
import { isPushEnabled } from "./push-sender.ts";
|
|
106
107
|
import { PushService } from "./push-service.ts";
|
|
108
|
+
import { parseRepoAccessConfig } from "./repo-access-config.ts";
|
|
107
109
|
import { parseSelfServeConfig } from "./self-serve-config.ts";
|
|
108
110
|
import { HttpSlackProvisioningClient } from "./slack-provisioning-client.ts";
|
|
109
111
|
import { SpecialtyRegistry } from "./specialty-manifest-loader.ts";
|
|
@@ -300,6 +302,9 @@ export async function startAdmin(
|
|
|
300
302
|
// Cron-run transcript API (COS-43.1): flag off by default.
|
|
301
303
|
const transcriptConfig = parseTranscriptConfig(process.env);
|
|
302
304
|
|
|
305
|
+
// Per-repo GitHub access check (COS-44.1): flag off by default.
|
|
306
|
+
const repoAccessConfig = parseRepoAccessConfig(process.env);
|
|
307
|
+
|
|
303
308
|
// Web Push (CFB-4.2). Enabled ONLY when public + private + subject are all
|
|
304
309
|
// set — a partial config must not half-enable the feature. When disabled,
|
|
305
310
|
// pushService is undefined, the push routes 503, and the toggle renders "".
|
|
@@ -425,6 +430,7 @@ export async function startAdmin(
|
|
|
425
430
|
agentTokenService,
|
|
426
431
|
anthropicFederationEnabled,
|
|
427
432
|
transcriptUploadEnabled: transcriptConfig.enabled,
|
|
433
|
+
repoAccessCheckEnabled: repoAccessConfig.enabled,
|
|
428
434
|
});
|
|
429
435
|
|
|
430
436
|
root.route("/agents", runtimeApp);
|
|
@@ -458,6 +464,7 @@ export async function startAdmin(
|
|
|
458
464
|
pool: adminPool,
|
|
459
465
|
selfServe,
|
|
460
466
|
anthropicFederationEnabled,
|
|
467
|
+
podServiceAccountEnabled: parsePodServiceAccountConfig(process.env).enabled,
|
|
461
468
|
chatCredentialsEnabled: parseChatCredentialConfig(process.env).enabled,
|
|
462
469
|
...(chatCredentialsClient
|
|
463
470
|
? { chatCredentials: chatCredentialsClient }
|
|
@@ -15,15 +15,44 @@ import type { AgentProvisioner } from "./agent-provisioner.ts";
|
|
|
15
15
|
* already committed, so a reconcile failure is returned as a warning string
|
|
16
16
|
* (the next full reconcile pass will converge it) instead of failing the call.
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
type ReconcilableAgent = Partial<AgentAnthropicIdentity> & {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
selfHosted: boolean;
|
|
22
|
+
accountId?: string | null;
|
|
23
|
+
podServiceAccountName?: string | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function reconcileAgentAfterAccountChange(
|
|
27
|
+
provisioner: Pick<AgentProvisioner, "reconcile">,
|
|
28
|
+
agent: ReconcilableAgent,
|
|
29
|
+
): Promise<string | undefined> {
|
|
30
|
+
return reconcileAgentWorkload(provisioner, agent, "account updated");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* COS-49.1: reconciles the workload after podServiceAccountName changed.
|
|
35
|
+
* `previous` is the column value before the update; the provisioner uses it to
|
|
36
|
+
* clear the live ServiceAccount only when it is still the one it applied.
|
|
37
|
+
*/
|
|
38
|
+
export function reconcileAgentAfterPodServiceAccountChange(
|
|
39
|
+
provisioner: Pick<AgentProvisioner, "reconcile">,
|
|
40
|
+
agent: ReconcilableAgent,
|
|
41
|
+
previous: string | null,
|
|
42
|
+
): Promise<string | undefined> {
|
|
43
|
+
return reconcileAgentWorkload(
|
|
44
|
+
provisioner,
|
|
45
|
+
agent,
|
|
46
|
+
"pod service account updated",
|
|
47
|
+
previous,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function reconcileAgentWorkload(
|
|
19
52
|
provisioner: Pick<AgentProvisioner, "reconcile">,
|
|
20
|
-
agent:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
selfHosted: boolean;
|
|
24
|
-
accountId?: string | null;
|
|
25
|
-
podServiceAccountName?: string | null;
|
|
26
|
-
},
|
|
53
|
+
agent: ReconcilableAgent,
|
|
54
|
+
what: string,
|
|
55
|
+
previousPodServiceAccountName?: string | null,
|
|
27
56
|
): Promise<string | undefined> {
|
|
28
57
|
if (agent.selfHosted) return undefined;
|
|
29
58
|
try {
|
|
@@ -38,14 +67,17 @@ export async function reconcileAgentAfterAccountChange(
|
|
|
38
67
|
anthropicFederationRuleId: agent.anthropicFederationRuleId,
|
|
39
68
|
k8sServiceAccountName: agent.k8sServiceAccountName,
|
|
40
69
|
podServiceAccountName: agent.podServiceAccountName,
|
|
70
|
+
...(previousPodServiceAccountName !== undefined
|
|
71
|
+
? { previousPodServiceAccountName }
|
|
72
|
+
: {}),
|
|
41
73
|
},
|
|
42
74
|
]);
|
|
43
75
|
const failure = result.failed.find((f) => f.agentId === agent.id);
|
|
44
76
|
return failure
|
|
45
|
-
?
|
|
77
|
+
? `${what}, but reconciling the agent workload failed: ${failure.error}`
|
|
46
78
|
: undefined;
|
|
47
79
|
} catch (err) {
|
|
48
80
|
const message = err instanceof Error ? err.message : String(err);
|
|
49
|
-
return
|
|
81
|
+
return `${what}, but reconciling the agent workload failed: ${message}`;
|
|
50
82
|
}
|
|
51
83
|
}
|
|
@@ -66,6 +66,38 @@ describeOrSkip("AgentGitHubInstallationsService (integration)", () => {
|
|
|
66
66
|
expect(read?.reportedAt).toEqual(reportedAt);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
it("round-trips repoAccess and keeps it when a later push omits it (COS-44.1)", async () => {
|
|
70
|
+
const agentId = await createAgent(prisma);
|
|
71
|
+
const repoAccess = [
|
|
72
|
+
{
|
|
73
|
+
repo: "agent",
|
|
74
|
+
owner: "app-vitals",
|
|
75
|
+
read: true,
|
|
76
|
+
write: true,
|
|
77
|
+
checkedAt: "2026-01-15T12:00:00.000Z",
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
const first = await service.push(agentId, {
|
|
81
|
+
reportedAt: new Date("2026-01-15T12:00:00.000Z"),
|
|
82
|
+
installations: [],
|
|
83
|
+
repoAccess,
|
|
84
|
+
});
|
|
85
|
+
expect(first.repoAccess).toEqual(repoAccess);
|
|
86
|
+
|
|
87
|
+
await service.push(agentId, {
|
|
88
|
+
reportedAt: new Date("2026-01-16T12:00:00.000Z"),
|
|
89
|
+
installations: [],
|
|
90
|
+
});
|
|
91
|
+
expect((await service.get(agentId))?.repoAccess).toEqual(repoAccess);
|
|
92
|
+
|
|
93
|
+
await service.push(agentId, {
|
|
94
|
+
reportedAt: new Date("2026-01-17T12:00:00.000Z"),
|
|
95
|
+
installations: [],
|
|
96
|
+
repoAccess: [],
|
|
97
|
+
});
|
|
98
|
+
expect((await service.get(agentId))?.repoAccess).toEqual([]);
|
|
99
|
+
});
|
|
100
|
+
|
|
69
101
|
it("get() returns null when nothing has been pushed", async () => {
|
|
70
102
|
const agentId = await createAgent(prisma);
|
|
71
103
|
expect(await service.get(agentId)).toBeNull();
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* One row per agent (AgentGitHubInstallationsSnapshot.agentId is @unique).
|
|
7
7
|
* push() upserts the single row, replacing the installations list wholesale —
|
|
8
|
-
* an installation absent from the latest push is gone.
|
|
8
|
+
* an installation absent from the latest push is gone. repoAccess (COS-44.1)
|
|
9
|
+
* is only written when the push carries it, so a push from a runtime with the
|
|
10
|
+
* check off never wipes a result stored earlier.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import type {
|
|
@@ -19,6 +21,8 @@ export type { AgentGitHubInstallationsSnapshot };
|
|
|
19
21
|
interface PushGitHubInstallationsInput {
|
|
20
22
|
reportedAt: Date;
|
|
21
23
|
installations: Prisma.InputJsonValue;
|
|
24
|
+
/** Omitted leaves the stored per-repo access result unchanged; [] clears it. */
|
|
25
|
+
repoAccess?: Prisma.InputJsonValue;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export class AgentGitHubInstallationsService {
|
|
@@ -35,10 +39,16 @@ export class AgentGitHubInstallationsService {
|
|
|
35
39
|
agentId,
|
|
36
40
|
reportedAt: input.reportedAt,
|
|
37
41
|
installations: input.installations,
|
|
42
|
+
...(input.repoAccess === undefined
|
|
43
|
+
? {}
|
|
44
|
+
: { repoAccess: input.repoAccess }),
|
|
38
45
|
},
|
|
39
46
|
update: {
|
|
40
47
|
reportedAt: input.reportedAt,
|
|
41
48
|
installations: input.installations,
|
|
49
|
+
...(input.repoAccess === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { repoAccess: input.repoAccess }),
|
|
42
52
|
},
|
|
43
53
|
});
|
|
44
54
|
}
|
package/src/agent-provisioner.ts
CHANGED
|
@@ -151,6 +151,15 @@ export interface ReconcileTarget extends Partial<AgentAnthropicIdentity> {
|
|
|
151
151
|
accountId?: string | null;
|
|
152
152
|
/** The Agent row's pod ServiceAccount (COS-48.1); rendered only when the flag is on. */
|
|
153
153
|
podServiceAccountName?: string | null;
|
|
154
|
+
/**
|
|
155
|
+
* The pod ServiceAccount this agent had before the change being reconciled
|
|
156
|
+
* (COS-49.1). Only used to clear: when `podServiceAccountName` is now unset,
|
|
157
|
+
* the live ServiceAccount is nulled only if it still equals this value, so a
|
|
158
|
+
* ServiceAccount set by another actor is left alone. The PATCH handler passes
|
|
159
|
+
* the pre-update column value; startup/full passes leave it absent (so they
|
|
160
|
+
* never clear anything).
|
|
161
|
+
*/
|
|
162
|
+
previousPodServiceAccountName?: string | null;
|
|
154
163
|
}
|
|
155
164
|
|
|
156
165
|
export interface AgentProvisioner {
|
|
@@ -701,6 +710,44 @@ export function federationPatch(
|
|
|
701
710
|
return Object.keys(patch).length > 0 ? patch : undefined;
|
|
702
711
|
}
|
|
703
712
|
|
|
713
|
+
/** The pod-identity patch: the ServiceAccount fields only. */
|
|
714
|
+
interface PodIdentityPatch {
|
|
715
|
+
serviceAccountName: string | null;
|
|
716
|
+
serviceAccount?: null;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Strategic-merge fragment that brings the live pod ServiceAccount to the
|
|
721
|
+
* Agent row's podServiceAccountName (COS-49.1), for Deployments without
|
|
722
|
+
* federation wiring (federationPatch owns the ServiceAccount when the desired
|
|
723
|
+
* Deployment carries federation).
|
|
724
|
+
*
|
|
725
|
+
* - Desired ServiceAccount set: patched when the live one differs.
|
|
726
|
+
* - Desired unset: cleared (serviceAccountName and the deprecated
|
|
727
|
+
* `serviceAccount` alias nulled) ONLY when the live value equals
|
|
728
|
+
* `previous`, the value this admin previously applied. A ServiceAccount set
|
|
729
|
+
* by another actor therefore survives, as does everything when `previous`
|
|
730
|
+
* is unknown (startup passes).
|
|
731
|
+
*/
|
|
732
|
+
export function podIdentityPatch(
|
|
733
|
+
live: KubernetesDeployment,
|
|
734
|
+
desired: KubernetesDeployment,
|
|
735
|
+
previous?: string | null,
|
|
736
|
+
): PodIdentityPatch | undefined {
|
|
737
|
+
const cur = federationParts(live);
|
|
738
|
+
const want = federationParts(desired);
|
|
739
|
+
if (want.volume || want.env.size > 0) return undefined;
|
|
740
|
+
if (want.serviceAccountName) {
|
|
741
|
+
return cur.serviceAccountName !== want.serviceAccountName
|
|
742
|
+
? { serviceAccountName: want.serviceAccountName }
|
|
743
|
+
: undefined;
|
|
744
|
+
}
|
|
745
|
+
if (previous && cur.serviceAccountName === previous) {
|
|
746
|
+
return { serviceAccountName: null, serviceAccount: null };
|
|
747
|
+
}
|
|
748
|
+
return undefined;
|
|
749
|
+
}
|
|
750
|
+
|
|
704
751
|
function isConflict(err: unknown): boolean {
|
|
705
752
|
return err instanceof ConflictError;
|
|
706
753
|
}
|
|
@@ -1420,6 +1467,16 @@ export class KubernetesAgentProvisioner implements AgentProvisioner {
|
|
|
1420
1467
|
const federation = manageFederation
|
|
1421
1468
|
? federationPatch(deployment, desiredDeployment)
|
|
1422
1469
|
: undefined;
|
|
1470
|
+
// Pod ServiceAccount (COS-49.1). Runs under policyOnly too, so an
|
|
1471
|
+
// admin restart never regresses a Deployment to the wrong identity.
|
|
1472
|
+
const podIdentity =
|
|
1473
|
+
federation?.serviceAccountName === undefined
|
|
1474
|
+
? podIdentityPatch(
|
|
1475
|
+
deployment,
|
|
1476
|
+
desiredDeployment,
|
|
1477
|
+
agent.previousPodServiceAccountName,
|
|
1478
|
+
)
|
|
1479
|
+
: undefined;
|
|
1423
1480
|
const containerDrift =
|
|
1424
1481
|
!policyOnly &&
|
|
1425
1482
|
current !== undefined &&
|
|
@@ -1427,7 +1484,11 @@ export class KubernetesAgentProvisioner implements AgentProvisioner {
|
|
|
1427
1484
|
containerDrifted(current, desired);
|
|
1428
1485
|
if (
|
|
1429
1486
|
desired &&
|
|
1430
|
-
(labelsDrifted ||
|
|
1487
|
+
(labelsDrifted ||
|
|
1488
|
+
containerDrift ||
|
|
1489
|
+
policy ||
|
|
1490
|
+
federation ||
|
|
1491
|
+
podIdentity)
|
|
1431
1492
|
) {
|
|
1432
1493
|
// Under policyOnly the container entry carries only the policy and
|
|
1433
1494
|
// federation mount/env changes (if any); image, the rest of env,
|
|
@@ -1466,11 +1527,15 @@ export class KubernetesAgentProvisioner implements AgentProvisioner {
|
|
|
1466
1527
|
...(policy?.volumes ?? []),
|
|
1467
1528
|
...(federation?.volumes ?? []),
|
|
1468
1529
|
];
|
|
1469
|
-
const
|
|
1530
|
+
const saPatch =
|
|
1470
1531
|
federation?.serviceAccountName !== undefined
|
|
1532
|
+
? federation
|
|
1533
|
+
: podIdentity;
|
|
1534
|
+
const serviceAccount =
|
|
1535
|
+
saPatch?.serviceAccountName !== undefined
|
|
1471
1536
|
? {
|
|
1472
|
-
serviceAccountName:
|
|
1473
|
-
...(
|
|
1537
|
+
serviceAccountName: saPatch.serviceAccountName,
|
|
1538
|
+
...(saPatch.serviceAccount === null
|
|
1474
1539
|
? { serviceAccount: null }
|
|
1475
1540
|
: {}),
|
|
1476
1541
|
}
|