Skip to content

Commit 328095a

Browse files
authored
Merge pull request #2519 from trycompai/main
[comp] Production Deploy
2 parents bc79b7f + fec7bbb commit 328095a

File tree

407 files changed

+40121
-5274
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

407 files changed

+40121
-5274
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,5 @@ scripts/sync-release-branch.sh
9595

9696
.claude/audit-findings.md
9797

98-
.superpowers/*
98+
.superpowers/*
99+
.claude/worktrees/

apps/api/package.json

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,57 @@
88
"@ai-sdk/groq": "^2.0.32",
99
"@ai-sdk/openai": "^2.0.65",
1010
"@aws-sdk/client-ec2": "^3.911.0",
11-
"@aws-sdk/client-s3": "^3.859.0",
11+
"@aws-sdk/client-s3": "3.1013.0",
12+
"@aws-sdk/client-acm": "^3.948.0",
13+
"@aws-sdk/client-backup": "^3.948.0",
14+
"@aws-sdk/client-cloudtrail": "^3.948.0",
15+
"@aws-sdk/client-cloudwatch": "^3.948.0",
16+
"@aws-sdk/client-cost-explorer": "^3.948.0",
17+
"@aws-sdk/client-cloudwatch-logs": "^3.948.0",
18+
"@aws-sdk/client-config-service": "^3.948.0",
19+
"@aws-sdk/client-dynamodb": "^3.948.0",
20+
"@aws-sdk/client-ecr": "^3.948.0",
21+
"@aws-sdk/client-ecs": "^3.948.0",
22+
"@aws-sdk/client-efs": "^3.948.0",
23+
"@aws-sdk/client-eks": "^3.948.0",
24+
"@aws-sdk/client-elastic-load-balancing-v2": "^3.948.0",
25+
"@aws-sdk/client-guardduty": "^3.948.0",
26+
"@aws-sdk/client-iam": "^3.948.0",
27+
"@aws-sdk/client-inspector2": "^3.948.0",
28+
"@aws-sdk/client-kms": "^3.948.0",
29+
"@aws-sdk/client-lambda": "^3.948.0",
30+
"@aws-sdk/client-macie2": "^3.948.0",
31+
"@aws-sdk/client-opensearch": "^3.948.0",
32+
"@aws-sdk/client-rds": "^3.948.0",
33+
"@aws-sdk/client-redshift": "^3.948.0",
34+
"@aws-sdk/client-route-53": "^3.948.0",
35+
"@aws-sdk/client-secrets-manager": "^3.948.0",
1236
"@aws-sdk/client-securityhub": "^3.948.0",
37+
"@aws-sdk/client-sns": "^3.948.0",
38+
"@aws-sdk/client-sqs": "^3.948.0",
39+
"@aws-sdk/client-wafv2": "^3.948.0",
40+
"@aws-sdk/client-api-gateway": "^3.948.0",
41+
"@aws-sdk/client-apigatewayv2": "^3.948.0",
42+
"@aws-sdk/client-appflow": "^3.948.0",
43+
"@aws-sdk/client-athena": "^3.948.0",
44+
"@aws-sdk/client-cloudfront": "^3.948.0",
45+
"@aws-sdk/client-codebuild": "^3.948.0",
46+
"@aws-sdk/client-cognito-identity-provider": "^3.948.0",
47+
"@aws-sdk/client-elastic-beanstalk": "^3.948.0",
48+
"@aws-sdk/client-elasticache": "^3.948.0",
49+
"@aws-sdk/client-emr": "^3.948.0",
50+
"@aws-sdk/client-eventbridge": "^3.948.0",
51+
"@aws-sdk/client-glue": "^3.948.0",
52+
"@aws-sdk/client-kafka": "^3.948.0",
53+
"@aws-sdk/client-kinesis": "^3.948.0",
54+
"@aws-sdk/client-network-firewall": "^3.948.0",
55+
"@aws-sdk/client-sagemaker": "^3.948.0",
56+
"@aws-sdk/client-sfn": "^3.948.0",
57+
"@aws-sdk/client-shield": "^3.948.0",
58+
"@aws-sdk/client-ssm": "^3.948.0",
1359
"@aws-sdk/client-sts": "^3.948.0",
14-
"@aws-sdk/s3-request-presigner": "^3.859.0",
60+
"@aws-sdk/client-transfer": "^3.948.0",
61+
"@aws-sdk/s3-request-presigner": "3.1013.0",
1562
"@browserbasehq/sdk": "2.6.0",
1663
"@browserbasehq/stagehand": "^3.0.5",
1764
"@mendable/firecrawl-js": "^4.9.3",

apps/api/src/admin-organizations/admin-audit-log.interceptor.spec.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,36 @@ jest.mock('@db', () => ({
1818
},
1919
Prisma: {},
2020
db: {
21-
auditLog: { get create() { return mockCreate; } },
22-
policy: { get findFirst() { return mockPolicyFind; } },
23-
taskItem: { get findFirst() { return mockTaskFind; } },
24-
vendor: { get findFirst() { return mockVendorFind; } },
25-
finding: { get findFirst() { return mockFindingFind; } },
26-
context: { get findFirst() { return mockContextFind; } },
21+
auditLog: {
22+
get create() {
23+
return mockCreate;
24+
},
25+
},
26+
policy: {
27+
get findFirst() {
28+
return mockPolicyFind;
29+
},
30+
},
31+
taskItem: {
32+
get findFirst() {
33+
return mockTaskFind;
34+
},
35+
},
36+
vendor: {
37+
get findFirst() {
38+
return mockVendorFind;
39+
},
40+
},
41+
finding: {
42+
get findFirst() {
43+
return mockFindingFind;
44+
},
45+
},
46+
context: {
47+
get findFirst() {
48+
return mockContextFind;
49+
},
50+
},
2751
},
2852
}));
2953

apps/api/src/admin-organizations/admin-context.controller.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ export class AdminContextController {
4343
}
4444

4545
@Post(':orgId/context')
46-
@ApiOperation({ summary: 'Create a context entry for an organization (admin)' })
46+
@ApiOperation({
47+
summary: 'Create a context entry for an organization (admin)',
48+
})
4749
@UsePipes(
4850
new ValidationPipe({
4951
whitelist: true,
@@ -59,7 +61,9 @@ export class AdminContextController {
5961
}
6062

6163
@Patch(':orgId/context/:contextId')
62-
@ApiOperation({ summary: 'Update a context entry for an organization (admin)' })
64+
@ApiOperation({
65+
summary: 'Update a context entry for an organization (admin)',
66+
})
6367
@UsePipes(
6468
new ValidationPipe({
6569
whitelist: true,

apps/api/src/admin-organizations/admin-evidence.controller.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ describe('AdminEvidenceController', () => {
2828
beforeEach(async () => {
2929
const module: TestingModule = await Test.createTestingModule({
3030
controllers: [AdminEvidenceController],
31-
providers: [
32-
{ provide: EvidenceFormsService, useValue: mockService },
33-
],
31+
providers: [{ provide: EvidenceFormsService, useValue: mockService }],
3432
}).compile();
3533

3634
controller = module.get<AdminEvidenceController>(AdminEvidenceController);

apps/api/src/admin-organizations/admin-evidence.controller.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ import {
2424
@UseInterceptors(AdminAuditLogInterceptor)
2525
@Throttle({ default: { ttl: 60000, limit: 30 } })
2626
export class AdminEvidenceController {
27-
constructor(
28-
private readonly evidenceFormsService: EvidenceFormsService,
29-
) {}
27+
constructor(private readonly evidenceFormsService: EvidenceFormsService) {}
3028

3129
@Get(':orgId/evidence-forms')
32-
@ApiOperation({ summary: 'List evidence form statuses for an organization (admin)' })
30+
@ApiOperation({
31+
summary: 'List evidence form statuses for an organization (admin)',
32+
})
3333
async listFormStatuses(@Param('orgId') orgId: string) {
3434
return this.evidenceFormsService.getFormStatuses(orgId);
3535
}
@@ -53,8 +53,12 @@ export class AdminEvidenceController {
5353
authContext: buildPlatformAdminAuthContext(req.userId, orgId),
5454
formType,
5555
search,
56-
limit: limit ? String(Math.min(200, Math.max(1, parseInt(limit, 10) || 1))) : undefined,
57-
offset: offset ? String(Math.max(0, parseInt(offset, 10) || 0)) : undefined,
56+
limit: limit
57+
? String(Math.min(200, Math.max(1, parseInt(limit, 10) || 1)))
58+
: undefined,
59+
offset: offset
60+
? String(Math.max(0, parseInt(offset, 10) || 0))
61+
: undefined,
5862
});
5963
}
6064
}

apps/api/src/admin-organizations/admin-findings.controller.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ export class AdminFindingsController {
3333

3434
@Get(':orgId/findings')
3535
@ApiOperation({ summary: 'List all findings for an organization (admin)' })
36-
async list(
37-
@Param('orgId') orgId: string,
38-
@Query('status') status?: string,
39-
) {
36+
async list(@Param('orgId') orgId: string, @Query('status') status?: string) {
4037
let validatedStatus: FindingStatus | undefined;
4138
if (status) {
4239
if (!Object.values(FindingStatus).includes(status as FindingStatus)) {

apps/api/src/admin-organizations/admin-guard-integration.spec.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,7 @@ describe('PlatformAdminGuard — runtime rejection scenarios', () => {
9999
});
100100
const ctx = buildContext({ cookie: 'session=valid' });
101101

102-
await expect(guard.canActivate(ctx)).rejects.toThrow(
103-
ForbiddenException,
104-
);
102+
await expect(guard.canActivate(ctx)).rejects.toThrow(ForbiddenException);
105103
await expect(guard.canActivate(ctx)).rejects.toThrow(
106104
'Access denied: Platform admin privileges required',
107105
);
@@ -116,9 +114,7 @@ describe('PlatformAdminGuard — runtime rejection scenarios', () => {
116114
});
117115
const ctx = buildContext({ cookie: 'session=valid' });
118116

119-
await expect(guard.canActivate(ctx)).rejects.toThrow(
120-
ForbiddenException,
121-
);
117+
await expect(guard.canActivate(ctx)).rejects.toThrow(ForbiddenException);
122118
});
123119

124120
it('rejects a user with role "owner" (org role, not platform admin)', async () => {
@@ -130,9 +126,7 @@ describe('PlatformAdminGuard — runtime rejection scenarios', () => {
130126
});
131127
const ctx = buildContext({ cookie: 'session=valid' });
132128

133-
await expect(guard.canActivate(ctx)).rejects.toThrow(
134-
ForbiddenException,
135-
);
129+
await expect(guard.canActivate(ctx)).rejects.toThrow(ForbiddenException);
136130
});
137131

138132
it('rejects when session claims admin but DB says user', async () => {
@@ -146,9 +140,7 @@ describe('PlatformAdminGuard — runtime rejection scenarios', () => {
146140
});
147141
const ctx = buildContext({ authorization: 'Bearer valid' });
148142

149-
await expect(guard.canActivate(ctx)).rejects.toThrow(
150-
ForbiddenException,
151-
);
143+
await expect(guard.canActivate(ctx)).rejects.toThrow(ForbiddenException);
152144
expect(mockFindUnique).toHaveBeenCalledWith({
153145
where: { id: 'usr_sneaky' },
154146
select: { id: true, email: true, role: true },

apps/api/src/admin-organizations/admin-organizations.controller.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,25 @@ export class AdminOrganizationsController {
4343
}
4444

4545
@Get('activity')
46-
@ApiOperation({ summary: 'Organization activity report - shows last session per org (platform admin)' })
47-
@ApiQuery({ name: 'inactiveDays', required: false, description: 'Filter orgs with no session in N days (default: 90)' })
48-
@ApiQuery({ name: 'hasAccess', required: false, description: 'Filter by hasAccess (true/false)' })
49-
@ApiQuery({ name: 'onboarded', required: false, description: 'Filter by onboardingCompleted (true/false)' })
46+
@ApiOperation({
47+
summary:
48+
'Organization activity report - shows last session per org (platform admin)',
49+
})
50+
@ApiQuery({
51+
name: 'inactiveDays',
52+
required: false,
53+
description: 'Filter orgs with no session in N days (default: 90)',
54+
})
55+
@ApiQuery({
56+
name: 'hasAccess',
57+
required: false,
58+
description: 'Filter by hasAccess (true/false)',
59+
})
60+
@ApiQuery({
61+
name: 'onboarded',
62+
required: false,
63+
description: 'Filter by onboardingCompleted (true/false)',
64+
})
5065
@ApiQuery({ name: 'page', required: false })
5166
@ApiQuery({ name: 'limit', required: false })
5267
async activity(
@@ -57,9 +72,16 @@ export class AdminOrganizationsController {
5772
@Query('limit') limit?: string,
5873
) {
5974
return this.service.getOrgActivity({
60-
inactiveDays: Math.max(0, Number.isFinite(parseInt(inactiveDays ?? '90', 10)) ? parseInt(inactiveDays ?? '90', 10) : 90),
61-
hasAccess: hasAccess === 'true' ? true : hasAccess === 'false' ? false : undefined,
62-
onboarded: onboarded === 'true' ? true : onboarded === 'false' ? false : undefined,
75+
inactiveDays: Math.max(
76+
0,
77+
Number.isFinite(parseInt(inactiveDays ?? '90', 10))
78+
? parseInt(inactiveDays ?? '90', 10)
79+
: 90,
80+
),
81+
hasAccess:
82+
hasAccess === 'true' ? true : hasAccess === 'false' ? false : undefined,
83+
onboarded:
84+
onboarded === 'true' ? true : onboarded === 'false' ? false : undefined,
6385
page: Math.max(1, parseInt(page || '1', 10) || 1),
6486
limit: Math.min(100, Math.max(1, parseInt(limit || '50', 10) || 50)),
6587
});
@@ -109,9 +131,19 @@ export class AdminOrganizationsController {
109131
}
110132

111133
@Get(':id/audit-logs')
112-
@ApiOperation({ summary: 'Get audit logs for an organization (platform admin)' })
113-
@ApiQuery({ name: 'entityType', required: false, description: 'Filter by entity type (e.g. policy, task)' })
114-
@ApiQuery({ name: 'take', required: false, description: 'Number of logs to return (max 100, default 100)' })
134+
@ApiOperation({
135+
summary: 'Get audit logs for an organization (platform admin)',
136+
})
137+
@ApiQuery({
138+
name: 'entityType',
139+
required: false,
140+
description: 'Filter by entity type (e.g. policy, task)',
141+
})
142+
@ApiQuery({
143+
name: 'take',
144+
required: false,
145+
description: 'Number of logs to return (max 100, default 100)',
146+
})
115147
async getAuditLogs(
116148
@Param('id') id: string,
117149
@Query('entityType') entityType?: string,

apps/api/src/admin-organizations/admin-organizations.service.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,14 @@ export class AdminOrganizationsService {
125125
createdAt: true,
126126
hasAccess: true,
127127
onboardingCompleted: true,
128-
_count: { select: { members: true, tasks: true, policy: true, auditLog: true } },
128+
_count: {
129+
select: {
130+
members: true,
131+
tasks: true,
132+
policy: true,
133+
auditLog: true,
134+
},
135+
},
129136
members: {
130137
where: { deactivated: false },
131138
select: {
@@ -168,14 +175,20 @@ export class AdminOrganizationsService {
168175
lastSession = sess;
169176
}
170177
if (member.role?.includes('owner') && !owner) {
171-
owner = { id: member.user.id, name: member.user.name, email: member.user.email };
178+
owner = {
179+
id: member.user.id,
180+
name: member.user.name,
181+
email: member.user.email,
182+
};
172183
}
173184
}
174185

175186
const lastAuditLog = org.auditLog?.[0]?.timestamp ?? null;
176187
const lastActivity = [lastSession, lastAuditLog]
177188
.filter(Boolean)
178-
.sort((a, b) => (b as Date).getTime() - (a as Date).getTime())[0] as Date | undefined;
189+
.sort((a, b) => (b as Date).getTime() - (a as Date).getTime())[0] as
190+
| Date
191+
| undefined;
179192

180193
const isActive = lastActivity ? lastActivity >= cutoff : false;
181194

@@ -191,7 +204,7 @@ export class AdminOrganizationsService {
191204
auditLogCount: org._count.auditLog,
192205
owner,
193206
lastSession: lastSession?.toISOString() ?? null,
194-
lastAuditLog: lastAuditLog ? (lastAuditLog as Date).toISOString() : null,
207+
lastAuditLog: lastAuditLog ? lastAuditLog.toISOString() : null,
195208
lastActivity: lastActivity?.toISOString() ?? null,
196209
isActive,
197210
};

0 commit comments

Comments
 (0)