@oneuptime/common 11.5.7 → 11.5.8
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/Models/DatabaseModels/AIRun.ts +31 -0
- package/Server/API/CodeFixRunAPI.ts +25 -74
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Middleware/MasterAdminAuthorization.ts +1 -1
- package/Server/Middleware/ProjectAuthorization.ts +1 -1
- package/Server/Services/AIRunService.ts +10 -11
- package/Server/Utils/AI/AIRunPrivacyFilter.ts +106 -0
- package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +295 -0
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/UI/Components/EditionLabel/EditionLabel.tsx +821 -331
- package/build/dist/Models/DatabaseModels/AIRun.js +31 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Server/API/CodeFixRunAPI.js +25 -53
- package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +1 -1
- package/build/dist/Server/Services/AIRunService.js +10 -3
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +82 -0
- package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +424 -139
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/package.json +1 -1
|
@@ -66,6 +66,7 @@ import CodeFixTaskContext from "../../Types/AI/CodeFixTaskContext";
|
|
|
66
66
|
Permission.ProjectOwner,
|
|
67
67
|
Permission.ProjectAdmin,
|
|
68
68
|
Permission.ProjectMember,
|
|
69
|
+
Permission.Viewer,
|
|
69
70
|
],
|
|
70
71
|
delete: [],
|
|
71
72
|
update: [],
|
|
@@ -77,6 +78,7 @@ export default class AIRun extends BaseModel {
|
|
|
77
78
|
Permission.ProjectOwner,
|
|
78
79
|
Permission.ProjectAdmin,
|
|
79
80
|
Permission.ProjectMember,
|
|
81
|
+
Permission.Viewer,
|
|
80
82
|
],
|
|
81
83
|
update: [],
|
|
82
84
|
})
|
|
@@ -109,6 +111,7 @@ export default class AIRun extends BaseModel {
|
|
|
109
111
|
Permission.ProjectOwner,
|
|
110
112
|
Permission.ProjectAdmin,
|
|
111
113
|
Permission.ProjectMember,
|
|
114
|
+
Permission.Viewer,
|
|
112
115
|
],
|
|
113
116
|
update: [],
|
|
114
117
|
})
|
|
@@ -133,6 +136,7 @@ export default class AIRun extends BaseModel {
|
|
|
133
136
|
Permission.ProjectOwner,
|
|
134
137
|
Permission.ProjectAdmin,
|
|
135
138
|
Permission.ProjectMember,
|
|
139
|
+
Permission.Viewer,
|
|
136
140
|
],
|
|
137
141
|
update: [],
|
|
138
142
|
})
|
|
@@ -157,6 +161,7 @@ export default class AIRun extends BaseModel {
|
|
|
157
161
|
Permission.ProjectOwner,
|
|
158
162
|
Permission.ProjectAdmin,
|
|
159
163
|
Permission.ProjectMember,
|
|
164
|
+
Permission.Viewer,
|
|
160
165
|
],
|
|
161
166
|
update: [],
|
|
162
167
|
})
|
|
@@ -181,6 +186,7 @@ export default class AIRun extends BaseModel {
|
|
|
181
186
|
Permission.ProjectOwner,
|
|
182
187
|
Permission.ProjectAdmin,
|
|
183
188
|
Permission.ProjectMember,
|
|
189
|
+
Permission.Viewer,
|
|
184
190
|
],
|
|
185
191
|
update: [],
|
|
186
192
|
})
|
|
@@ -206,6 +212,7 @@ export default class AIRun extends BaseModel {
|
|
|
206
212
|
Permission.ProjectOwner,
|
|
207
213
|
Permission.ProjectAdmin,
|
|
208
214
|
Permission.ProjectMember,
|
|
215
|
+
Permission.Viewer,
|
|
209
216
|
],
|
|
210
217
|
update: [],
|
|
211
218
|
})
|
|
@@ -238,6 +245,7 @@ export default class AIRun extends BaseModel {
|
|
|
238
245
|
Permission.ProjectOwner,
|
|
239
246
|
Permission.ProjectAdmin,
|
|
240
247
|
Permission.ProjectMember,
|
|
248
|
+
Permission.Viewer,
|
|
241
249
|
],
|
|
242
250
|
update: [],
|
|
243
251
|
})
|
|
@@ -262,6 +270,7 @@ export default class AIRun extends BaseModel {
|
|
|
262
270
|
Permission.ProjectOwner,
|
|
263
271
|
Permission.ProjectAdmin,
|
|
264
272
|
Permission.ProjectMember,
|
|
273
|
+
Permission.Viewer,
|
|
265
274
|
],
|
|
266
275
|
update: [],
|
|
267
276
|
})
|
|
@@ -294,6 +303,7 @@ export default class AIRun extends BaseModel {
|
|
|
294
303
|
Permission.ProjectOwner,
|
|
295
304
|
Permission.ProjectAdmin,
|
|
296
305
|
Permission.ProjectMember,
|
|
306
|
+
Permission.Viewer,
|
|
297
307
|
],
|
|
298
308
|
update: [],
|
|
299
309
|
})
|
|
@@ -318,6 +328,7 @@ export default class AIRun extends BaseModel {
|
|
|
318
328
|
Permission.ProjectOwner,
|
|
319
329
|
Permission.ProjectAdmin,
|
|
320
330
|
Permission.ProjectMember,
|
|
331
|
+
Permission.Viewer,
|
|
321
332
|
],
|
|
322
333
|
update: [],
|
|
323
334
|
})
|
|
@@ -343,6 +354,7 @@ export default class AIRun extends BaseModel {
|
|
|
343
354
|
Permission.ProjectOwner,
|
|
344
355
|
Permission.ProjectAdmin,
|
|
345
356
|
Permission.ProjectMember,
|
|
357
|
+
Permission.Viewer,
|
|
346
358
|
],
|
|
347
359
|
update: [],
|
|
348
360
|
})
|
|
@@ -368,6 +380,7 @@ export default class AIRun extends BaseModel {
|
|
|
368
380
|
Permission.ProjectOwner,
|
|
369
381
|
Permission.ProjectAdmin,
|
|
370
382
|
Permission.ProjectMember,
|
|
383
|
+
Permission.Viewer,
|
|
371
384
|
],
|
|
372
385
|
update: [],
|
|
373
386
|
})
|
|
@@ -397,6 +410,7 @@ export default class AIRun extends BaseModel {
|
|
|
397
410
|
Permission.ProjectOwner,
|
|
398
411
|
Permission.ProjectAdmin,
|
|
399
412
|
Permission.ProjectMember,
|
|
413
|
+
Permission.Viewer,
|
|
400
414
|
],
|
|
401
415
|
update: [],
|
|
402
416
|
})
|
|
@@ -422,6 +436,7 @@ export default class AIRun extends BaseModel {
|
|
|
422
436
|
Permission.ProjectOwner,
|
|
423
437
|
Permission.ProjectAdmin,
|
|
424
438
|
Permission.ProjectMember,
|
|
439
|
+
Permission.Viewer,
|
|
425
440
|
],
|
|
426
441
|
update: [],
|
|
427
442
|
})
|
|
@@ -447,6 +462,7 @@ export default class AIRun extends BaseModel {
|
|
|
447
462
|
Permission.ProjectOwner,
|
|
448
463
|
Permission.ProjectAdmin,
|
|
449
464
|
Permission.ProjectMember,
|
|
465
|
+
Permission.Viewer,
|
|
450
466
|
],
|
|
451
467
|
update: [],
|
|
452
468
|
})
|
|
@@ -472,6 +488,7 @@ export default class AIRun extends BaseModel {
|
|
|
472
488
|
Permission.ProjectOwner,
|
|
473
489
|
Permission.ProjectAdmin,
|
|
474
490
|
Permission.ProjectMember,
|
|
491
|
+
Permission.Viewer,
|
|
475
492
|
],
|
|
476
493
|
update: [],
|
|
477
494
|
})
|
|
@@ -497,6 +514,7 @@ export default class AIRun extends BaseModel {
|
|
|
497
514
|
Permission.ProjectOwner,
|
|
498
515
|
Permission.ProjectAdmin,
|
|
499
516
|
Permission.ProjectMember,
|
|
517
|
+
Permission.Viewer,
|
|
500
518
|
],
|
|
501
519
|
update: [],
|
|
502
520
|
})
|
|
@@ -518,6 +536,7 @@ export default class AIRun extends BaseModel {
|
|
|
518
536
|
Permission.ProjectOwner,
|
|
519
537
|
Permission.ProjectAdmin,
|
|
520
538
|
Permission.ProjectMember,
|
|
539
|
+
Permission.Viewer,
|
|
521
540
|
],
|
|
522
541
|
update: [],
|
|
523
542
|
})
|
|
@@ -539,6 +558,7 @@ export default class AIRun extends BaseModel {
|
|
|
539
558
|
Permission.ProjectOwner,
|
|
540
559
|
Permission.ProjectAdmin,
|
|
541
560
|
Permission.ProjectMember,
|
|
561
|
+
Permission.Viewer,
|
|
542
562
|
],
|
|
543
563
|
update: [],
|
|
544
564
|
})
|
|
@@ -561,6 +581,7 @@ export default class AIRun extends BaseModel {
|
|
|
561
581
|
Permission.ProjectOwner,
|
|
562
582
|
Permission.ProjectAdmin,
|
|
563
583
|
Permission.ProjectMember,
|
|
584
|
+
Permission.Viewer,
|
|
564
585
|
],
|
|
565
586
|
update: [],
|
|
566
587
|
})
|
|
@@ -583,6 +604,7 @@ export default class AIRun extends BaseModel {
|
|
|
583
604
|
Permission.ProjectOwner,
|
|
584
605
|
Permission.ProjectAdmin,
|
|
585
606
|
Permission.ProjectMember,
|
|
607
|
+
Permission.Viewer,
|
|
586
608
|
],
|
|
587
609
|
update: [],
|
|
588
610
|
})
|
|
@@ -605,6 +627,7 @@ export default class AIRun extends BaseModel {
|
|
|
605
627
|
Permission.ProjectOwner,
|
|
606
628
|
Permission.ProjectAdmin,
|
|
607
629
|
Permission.ProjectMember,
|
|
630
|
+
Permission.Viewer,
|
|
608
631
|
],
|
|
609
632
|
update: [],
|
|
610
633
|
})
|
|
@@ -627,6 +650,7 @@ export default class AIRun extends BaseModel {
|
|
|
627
650
|
Permission.ProjectOwner,
|
|
628
651
|
Permission.ProjectAdmin,
|
|
629
652
|
Permission.ProjectMember,
|
|
653
|
+
Permission.Viewer,
|
|
630
654
|
],
|
|
631
655
|
update: [],
|
|
632
656
|
})
|
|
@@ -649,6 +673,7 @@ export default class AIRun extends BaseModel {
|
|
|
649
673
|
Permission.ProjectOwner,
|
|
650
674
|
Permission.ProjectAdmin,
|
|
651
675
|
Permission.ProjectMember,
|
|
676
|
+
Permission.Viewer,
|
|
652
677
|
],
|
|
653
678
|
update: [],
|
|
654
679
|
})
|
|
@@ -724,6 +749,7 @@ export default class AIRun extends BaseModel {
|
|
|
724
749
|
Permission.ProjectOwner,
|
|
725
750
|
Permission.ProjectAdmin,
|
|
726
751
|
Permission.ProjectMember,
|
|
752
|
+
Permission.Viewer,
|
|
727
753
|
],
|
|
728
754
|
update: [],
|
|
729
755
|
})
|
|
@@ -753,6 +779,7 @@ export default class AIRun extends BaseModel {
|
|
|
753
779
|
Permission.ProjectOwner,
|
|
754
780
|
Permission.ProjectAdmin,
|
|
755
781
|
Permission.ProjectMember,
|
|
782
|
+
Permission.Viewer,
|
|
756
783
|
],
|
|
757
784
|
update: [],
|
|
758
785
|
})
|
|
@@ -777,6 +804,7 @@ export default class AIRun extends BaseModel {
|
|
|
777
804
|
Permission.ProjectOwner,
|
|
778
805
|
Permission.ProjectAdmin,
|
|
779
806
|
Permission.ProjectMember,
|
|
807
|
+
Permission.Viewer,
|
|
780
808
|
],
|
|
781
809
|
update: [],
|
|
782
810
|
})
|
|
@@ -798,6 +826,7 @@ export default class AIRun extends BaseModel {
|
|
|
798
826
|
Permission.ProjectOwner,
|
|
799
827
|
Permission.ProjectAdmin,
|
|
800
828
|
Permission.ProjectMember,
|
|
829
|
+
Permission.Viewer,
|
|
801
830
|
],
|
|
802
831
|
update: [],
|
|
803
832
|
})
|
|
@@ -828,6 +857,7 @@ export default class AIRun extends BaseModel {
|
|
|
828
857
|
Permission.ProjectOwner,
|
|
829
858
|
Permission.ProjectAdmin,
|
|
830
859
|
Permission.ProjectMember,
|
|
860
|
+
Permission.Viewer,
|
|
831
861
|
],
|
|
832
862
|
update: [],
|
|
833
863
|
})
|
|
@@ -852,6 +882,7 @@ export default class AIRun extends BaseModel {
|
|
|
852
882
|
Permission.ProjectOwner,
|
|
853
883
|
Permission.ProjectAdmin,
|
|
854
884
|
Permission.ProjectMember,
|
|
885
|
+
Permission.Viewer,
|
|
855
886
|
],
|
|
856
887
|
update: [],
|
|
857
888
|
})
|
|
@@ -9,11 +9,9 @@ import Express, {
|
|
|
9
9
|
import Response from "../Utils/Response";
|
|
10
10
|
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
11
11
|
import ObjectID from "../../Types/ObjectID";
|
|
12
|
-
import PositiveNumber from "../../Types/PositiveNumber";
|
|
13
12
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
14
13
|
import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
|
|
15
14
|
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
16
|
-
import Query from "../../Types/BaseDatabase/Query";
|
|
17
15
|
import Select from "../Types/Database/Select";
|
|
18
16
|
import { JSONObject } from "../../Types/JSON";
|
|
19
17
|
import AIRunType from "../../Types/AI/AIRunType";
|
|
@@ -26,19 +24,26 @@ import ProjectService from "../Services/ProjectService";
|
|
|
26
24
|
import AIRunService from "../Services/AIRunService";
|
|
27
25
|
import AIRunEventService from "../Services/AIRunEventService";
|
|
28
26
|
|
|
29
|
-
const DEFAULT_LIMIT: number = 25;
|
|
30
|
-
const MAX_LIMIT: number = 100;
|
|
31
27
|
const MAX_EVENTS: number = 500;
|
|
32
28
|
|
|
33
29
|
/*
|
|
34
|
-
*
|
|
30
|
+
* The AI fix task detail page's event trail.
|
|
35
31
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
32
|
+
* The LIST page does NOT come through here — it reads AIRuns through the
|
|
33
|
+
* standard /ai-run CRUD, which AIRunService opens up for code-fix runs via a
|
|
34
|
+
* forced `runType = CodeFix OR userId = <caller>` clause (see
|
|
35
|
+
* Utils/AI/AIRunPrivacyFilter).
|
|
36
|
+
*
|
|
37
|
+
* The events cannot follow. AIRunEvent has NO runType column to key that
|
|
38
|
+
* clause off, and "userId is null" does not mean "system-authored" — the
|
|
39
|
+
* investigation engine writes null-userId events too — so project-scoping
|
|
40
|
+
* AIRunEvent wholesale would expose other members' CHAT toolArguments, which
|
|
41
|
+
* carry user-typed prompt content. Events are therefore still reached only
|
|
42
|
+
* through a run this endpoint has already authorized as runType = CodeFix.
|
|
43
|
+
*
|
|
44
|
+
* Access is gated explicitly, the same pattern as AIInvestigationAPI: confirm
|
|
45
|
+
* the caller can read the parent object (the project) under THEIR permissions,
|
|
46
|
+
* then read the run and its events as root.
|
|
42
47
|
*/
|
|
43
48
|
|
|
44
49
|
const RUN_SELECT: Select<AIRun> = {
|
|
@@ -51,6 +56,15 @@ const RUN_SELECT: Select<AIRun> = {
|
|
|
51
56
|
triggeredByTelemetryExceptionId: true,
|
|
52
57
|
totalTokens: true,
|
|
53
58
|
codeFixTaskType: true,
|
|
59
|
+
attemptCount: true,
|
|
60
|
+
llmCallCount: true,
|
|
61
|
+
toolCallCount: true,
|
|
62
|
+
/*
|
|
63
|
+
* Deliberately NOT selected: totalCostInUSDCents. This endpoint's gate is
|
|
64
|
+
* Project read, which admits roles beyond AIRun's own table ACL, so it must
|
|
65
|
+
* not return more than the detail page renders — and nothing there renders
|
|
66
|
+
* per-run spend.
|
|
67
|
+
*/
|
|
54
68
|
};
|
|
55
69
|
|
|
56
70
|
/*
|
|
@@ -81,69 +95,6 @@ export default class CodeFixRunAPI {
|
|
|
81
95
|
}
|
|
82
96
|
|
|
83
97
|
private initRoutes(): void {
|
|
84
|
-
// List the project's code-fix runs, newest first.
|
|
85
|
-
this.router.post(
|
|
86
|
-
"/code-fix-run/list",
|
|
87
|
-
UserMiddleware.getUserMiddleware,
|
|
88
|
-
async (
|
|
89
|
-
req: ExpressRequest,
|
|
90
|
-
res: ExpressResponse,
|
|
91
|
-
next: NextFunction,
|
|
92
|
-
): Promise<void> => {
|
|
93
|
-
try {
|
|
94
|
-
const props: DatabaseCommonInteractionProps =
|
|
95
|
-
await this.getAccessCheckedProps(req);
|
|
96
|
-
|
|
97
|
-
const body: JSONObject = (req.body as JSONObject) || {};
|
|
98
|
-
|
|
99
|
-
let limit: number = DEFAULT_LIMIT;
|
|
100
|
-
|
|
101
|
-
if (typeof body["limit"] === "number") {
|
|
102
|
-
limit = body["limit"];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
limit = Math.min(Math.max(Math.floor(limit), 1), MAX_LIMIT);
|
|
106
|
-
|
|
107
|
-
let skip: number = 0;
|
|
108
|
-
|
|
109
|
-
if (typeof body["skip"] === "number") {
|
|
110
|
-
skip = Math.max(Math.floor(body["skip"]), 0);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const query: Query<AIRun> = {
|
|
114
|
-
projectId: props.tenantId!,
|
|
115
|
-
runType: AIRunType.CodeFix,
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// Root read after the tenant access check above.
|
|
119
|
-
const runs: Array<AIRun> = await AIRunService.findBy({
|
|
120
|
-
query: query,
|
|
121
|
-
select: RUN_SELECT,
|
|
122
|
-
sort: { createdAt: SortOrder.Descending },
|
|
123
|
-
limit: limit,
|
|
124
|
-
skip: skip,
|
|
125
|
-
props: { isRoot: true },
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const count: PositiveNumber = await AIRunService.countBy({
|
|
129
|
-
query: query,
|
|
130
|
-
props: { isRoot: true },
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
Response.sendJsonObjectResponse(req, res, {
|
|
134
|
-
runs: runs.map((run: AIRun): JSONObject => {
|
|
135
|
-
return runToJson(run);
|
|
136
|
-
}),
|
|
137
|
-
count: count.toNumber(),
|
|
138
|
-
});
|
|
139
|
-
return;
|
|
140
|
-
} catch (err) {
|
|
141
|
-
next(err);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
);
|
|
146
|
-
|
|
147
98
|
// One run + its ordered glass-box event trail.
|
|
148
99
|
this.router.post(
|
|
149
100
|
"/code-fix-run/get/:runId",
|
package/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class BackfillCodeFixTaskType1784105912819
|
|
4
|
+
implements MigrationInterface
|
|
5
|
+
{
|
|
6
|
+
public name = "BackfillCodeFixTaskType1784105912819";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* A null AIRun.codeFixTaskType on a CodeFix run has always MEANT
|
|
10
|
+
* FixException: every code-fix run created before task recipes existed was
|
|
11
|
+
* an exception fix, and both the server's read path
|
|
12
|
+
* (CodeFixTaskTypeHelper.fromDatabaseValue) and the dashboard normalize the
|
|
13
|
+
* null to FixException so nobody ever sees it.
|
|
14
|
+
*
|
|
15
|
+
* That normalization only covers reads. Now that the AI Tasks list filters
|
|
16
|
+
* through the standard CRUD, a "Fix Exception" filter compiles to
|
|
17
|
+
* `codeFixTaskType IN ('FixException')`, which NULL never matches — the
|
|
18
|
+
* oldest runs would silently vanish from the filtered list. The bespoke
|
|
19
|
+
* endpoint papered over this with an equalToOrNull query it built itself;
|
|
20
|
+
* that endpoint is gone, so the meaning gets written into the data instead.
|
|
21
|
+
*
|
|
22
|
+
* Scoped to runType = 'CodeFix': codeFixTaskType is meaningless on chat and
|
|
23
|
+
* investigation runs and must stay null there.
|
|
24
|
+
*/
|
|
25
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
26
|
+
await queryRunner.query(
|
|
27
|
+
`UPDATE "AIRun" SET "codeFixTaskType" = 'FixException' WHERE "runType" = 'CodeFix' AND "codeFixTaskType" IS NULL`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Deliberately a no-op. The rows this set are indistinguishable from rows
|
|
33
|
+
* that already said FixException, so nulling them back would corrupt data
|
|
34
|
+
* this migration never touched. Reverting is also unnecessary: the read path
|
|
35
|
+
* still normalizes null to FixException, so an older build reads these rows
|
|
36
|
+
* exactly as it did before.
|
|
37
|
+
*/
|
|
38
|
+
public async down(_queryRunner: QueryRunner): Promise<void> {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -450,6 +450,7 @@ import { AddSentinelInsightFlags1784010274994 } from "./1784010274994-AddSentine
|
|
|
450
450
|
import { RenameSentinelToAI1784030612266 } from "./1784030612266-RenameSentinelToAI";
|
|
451
451
|
import { MigrationName1784033837629 } from "./1784033837629-MigrationName";
|
|
452
452
|
import { AddCreatedByUserToUser1784048917994 } from "./1784048917994-AddCreatedByUserToUser";
|
|
453
|
+
import { BackfillCodeFixTaskType1784105912819 } from "./1784105912819-BackfillCodeFixTaskType";
|
|
453
454
|
|
|
454
455
|
export default [
|
|
455
456
|
InitialMigration,
|
|
@@ -904,4 +905,5 @@ export default [
|
|
|
904
905
|
RenameSentinelToAI1784030612266,
|
|
905
906
|
MigrationName1784033837629,
|
|
906
907
|
AddCreatedByUserToUser1784048917994,
|
|
908
|
+
BackfillCodeFixTaskType1784105912819,
|
|
907
909
|
];
|
|
@@ -59,7 +59,7 @@ export default class MasterAdminAuthorization {
|
|
|
59
59
|
* Same as isAuthorizedMasterAdminMiddleware, but ALSO accepts the instance-wide
|
|
60
60
|
* master API key (Admin Dashboard → Settings → API Key) supplied in the
|
|
61
61
|
* `apikey` header. The master key has root/master-admin access, so this lets
|
|
62
|
-
* automated callers reach the master-admin
|
|
62
|
+
* automated callers reach the master-admin OneUptime Health endpoints with the
|
|
63
63
|
* key instead of a logged-in master-admin session.
|
|
64
64
|
*
|
|
65
65
|
* Deliberately scoped to the read-only health / diagnostics routes only. It is
|
|
@@ -65,7 +65,7 @@ export default class ProjectMiddleware {
|
|
|
65
65
|
* (Admin Dashboard → Settings → API Key) and that key is currently enabled.
|
|
66
66
|
* The master key has root/master-admin access, so this is shared with
|
|
67
67
|
* MasterAdminAuthorization to let the key reach master-admin-only endpoints
|
|
68
|
-
* (e.g.
|
|
68
|
+
* (e.g. OneUptime Health) as well.
|
|
69
69
|
*/
|
|
70
70
|
@CaptureSpan()
|
|
71
71
|
public static async isMasterApiKey(apiKey: ObjectID): Promise<boolean> {
|
|
@@ -16,7 +16,7 @@ import FindBy from "../Types/Database/FindBy";
|
|
|
16
16
|
import { OnFind } from "../Types/Database/Hooks";
|
|
17
17
|
import DatabaseService from "./DatabaseService";
|
|
18
18
|
import Model from "../../Models/DatabaseModels/AIRun";
|
|
19
|
-
import {
|
|
19
|
+
import { applyAIRunPrivacyFilter } from "../Utils/AI/AIRunPrivacyFilter";
|
|
20
20
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
21
21
|
import { UpdateQueryBuilder, UpdateResult } from "typeorm";
|
|
22
22
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
|
@@ -331,23 +331,22 @@ export class Service extends DatabaseService<Model> {
|
|
|
331
331
|
protected override async onBeforeFind(
|
|
332
332
|
findBy: FindBy<Model>,
|
|
333
333
|
): Promise<OnFind<Model>> {
|
|
334
|
-
findBy.query =
|
|
335
|
-
findBy.query,
|
|
336
|
-
findBy.props,
|
|
337
|
-
"userId",
|
|
338
|
-
);
|
|
334
|
+
findBy.query = applyAIRunPrivacyFilter(findBy.query, findBy.props);
|
|
339
335
|
return { findBy, carryForward: null };
|
|
340
336
|
}
|
|
341
337
|
|
|
338
|
+
/*
|
|
339
|
+
* countBy MUST repeat the filter. DatabaseService has no onBeforeCount hook
|
|
340
|
+
* (only onCountSuccess / onCountError), and BaseAPI.getList issues findBy
|
|
341
|
+
* and countBy with the SAME client query, so filtering in onBeforeFind alone
|
|
342
|
+
* would return a correctly-scoped list beside an unscoped project-wide
|
|
343
|
+
* count on every page. IncidentService repeats it for the same reason.
|
|
344
|
+
*/
|
|
342
345
|
@CaptureSpan()
|
|
343
346
|
public override async countBy(
|
|
344
347
|
countBy: CountBy<Model>,
|
|
345
348
|
): Promise<PositiveNumber> {
|
|
346
|
-
countBy.query =
|
|
347
|
-
countBy.query,
|
|
348
|
-
countBy.props,
|
|
349
|
-
"userId",
|
|
350
|
-
);
|
|
349
|
+
countBy.query = applyAIRunPrivacyFilter(countBy.query, countBy.props);
|
|
351
350
|
return super.countBy(countBy);
|
|
352
351
|
}
|
|
353
352
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
2
|
+
import { FindWhereProperty } from "../../../Types/BaseDatabase/Query";
|
|
3
|
+
import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
|
|
4
|
+
import AIRunType from "../../../Types/AI/AIRunType";
|
|
5
|
+
import Text from "../../../Types/Text";
|
|
6
|
+
import { combineWithPrivacyClause } from "../PrivacyFilterUtil";
|
|
7
|
+
import { Raw } from "typeorm";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* AIRun mixes two kinds of row under one table:
|
|
11
|
+
*
|
|
12
|
+
* - Chat and investigation runs are PERSONAL. They hang off a conversation
|
|
13
|
+
* and carry the user's own prompt trail, so only their author may read
|
|
14
|
+
* them. This is what AIChatPrivacyFilter's flat pin protects, and that pin
|
|
15
|
+
* stays exactly as it is for AIConversation, AIConversationMessage and
|
|
16
|
+
* AIRunEvent.
|
|
17
|
+
* - CodeFix runs are a SHARED TEAM ARTIFACT: they are triggered off an
|
|
18
|
+
* exception or an incident, they produce a pull request, and their child
|
|
19
|
+
* AIAgentTaskPullRequest rows are already project-readable through the
|
|
20
|
+
* standard CRUD. `userId` on a CodeFix run is attribution only (who
|
|
21
|
+
* clicked "Fix with AI Agent") and is null for automatic triggers — it
|
|
22
|
+
* carries no conversational content.
|
|
23
|
+
*
|
|
24
|
+
* So AIRun cannot use that flat pin. It needs `runType = CodeFix OR userId =
|
|
25
|
+
* <caller>`, a CROSS-COLUMN disjunction, which a query object cannot express
|
|
26
|
+
* (query objects are conjunctions).
|
|
27
|
+
*
|
|
28
|
+
* The clause below is built ENTIRELY from server state and AND-ed onto the
|
|
29
|
+
* caller's query. It never READS query.runType to decide anything — that
|
|
30
|
+
* would be unsound, because onBeforeFind runs BEFORE QueryUtil.serializeQuery,
|
|
31
|
+
* so query.runType here is still a raw, client-controlled operator object
|
|
32
|
+
* whose .value/.toString() are identical across the safe and hostile cases:
|
|
33
|
+
* LessThan("CodeFix") stringifies to exactly "CodeFix" but compiles to
|
|
34
|
+
* `runType < 'CodeFix'`, which matches "Chat". Because the clause is forced, a
|
|
35
|
+
* caller-supplied runType filter can only ever NARROW it, and
|
|
36
|
+
* combineWithPrivacyClause fails closed on operator shapes it does not
|
|
37
|
+
* recognize.
|
|
38
|
+
*
|
|
39
|
+
* Modelled on Utils/Incident/IncidentPrivacyFilter, which solves the same
|
|
40
|
+
* "unrestricted rows for everyone, restricted rows only for their owner"
|
|
41
|
+
* shape.
|
|
42
|
+
*/
|
|
43
|
+
export function getAIRunPrivacyRaw(
|
|
44
|
+
props: DatabaseCommonInteractionProps,
|
|
45
|
+
): FindWhereProperty<any> | undefined {
|
|
46
|
+
if (props.isRoot || props.isMasterAdmin) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/*
|
|
51
|
+
* The same hard gate the flat pin has, and it must stay. A caller with no
|
|
52
|
+
* user — a project API key, which gets userTenantAccessPermission but never
|
|
53
|
+
* userAuthorization (see ProjectAuthorization), so props.userId is
|
|
54
|
+
* undefined — has no personal scope. Today such a caller cannot read AIRun
|
|
55
|
+
* at all. Letting CodeFix rows through here would hand every API key holding
|
|
56
|
+
* ProjectMember the project's whole fix history: access that neither
|
|
57
|
+
* /ai-run nor /code-fix-run grants today. That is a separate product
|
|
58
|
+
* decision and must not ride in as a side effect of this filter.
|
|
59
|
+
*/
|
|
60
|
+
if (!props.userId) {
|
|
61
|
+
throw new NotAuthorizedException(
|
|
62
|
+
"AI runs are personal and can only be accessed by the user who created them.",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const userIdRid: string = "aiRunUid_" + Text.generateRandomText(10);
|
|
67
|
+
const runTypeRid: string = "aiRunType_" + Text.generateRandomText(10);
|
|
68
|
+
|
|
69
|
+
return Raw(
|
|
70
|
+
(alias: string): string => {
|
|
71
|
+
return `(${alias} = :${runTypeRid} OR "AIRun"."userId" = :${userIdRid})`;
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
[runTypeRid]: AIRunType.CodeFix,
|
|
75
|
+
[userIdRid]: props.userId.toString(),
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* Applies the clause above on AIRun.runType. Project isolation is unaffected:
|
|
82
|
+
* @TenantColumn("projectId") independently forces projectId = <tenant>, so
|
|
83
|
+
* "CodeFix runs are readable" always means "in YOUR project".
|
|
84
|
+
*/
|
|
85
|
+
export function applyAIRunPrivacyFilter<TQuery>(
|
|
86
|
+
query: TQuery,
|
|
87
|
+
props: DatabaseCommonInteractionProps,
|
|
88
|
+
): TQuery {
|
|
89
|
+
const rawClause: FindWhereProperty<any> | undefined =
|
|
90
|
+
getAIRunPrivacyRaw(props);
|
|
91
|
+
|
|
92
|
+
if (!rawClause) {
|
|
93
|
+
return query;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!query) {
|
|
97
|
+
return { runType: rawClause } as unknown as TQuery;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
(query as any).runType = combineWithPrivacyClause(
|
|
101
|
+
(query as any).runType,
|
|
102
|
+
rawClause,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return query;
|
|
106
|
+
}
|