Skip to content

Commit a68d714

Browse files
authored
Merge pull request #1243 from trycompai/mariano/fix-policies
[dev] [Marfuen] mariano/fix-policies
2 parents 1b351db + d6a70a0 commit a68d714

32 files changed

+32154
-12971
lines changed

apps/api/package.json

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,8 @@
11
{
22
"name": "@comp/api",
3-
"version": "0.0.1",
43
"description": "",
4+
"version": "0.0.1",
55
"author": "",
6-
"private": true,
7-
"license": "UNLICENSED",
8-
"scripts": {
9-
"build": "nest build",
10-
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
11-
"start": "nest start",
12-
"dev": "nest start --watch",
13-
"start:dev": "nest start --watch",
14-
"start:debug": "nest start --debug --watch",
15-
"start:prod": "node dist/main",
16-
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
17-
"test": "jest",
18-
"test:watch": "jest --watch",
19-
"test:cov": "jest --coverage",
20-
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
21-
"test:e2e": "jest --config ./test/jest-e2e.json",
22-
"typecheck": "tsc --noEmit",
23-
"db:generate": "bun run db:getschema && prisma generate",
24-
"db:getschema": "cp ../../node_modules/@trycompai/db/dist/schema.prisma prisma/schema.prisma",
25-
"prebuild": "bun run db:generate"
26-
},
276
"dependencies": {
287
"@aws-sdk/client-s3": "^3.859.0",
298
"@aws-sdk/s3-request-presigner": "^3.859.0",
@@ -83,5 +62,26 @@
8362
],
8463
"coverageDirectory": "../coverage",
8564
"testEnvironment": "node"
65+
},
66+
"license": "UNLICENSED",
67+
"private": true,
68+
"scripts": {
69+
"build": "nest build",
70+
"db:generate": "bun run db:getschema && prisma generate",
71+
"db:getschema": "cp ../../node_modules/@trycompai/db/dist/schema.prisma prisma/schema.prisma",
72+
"dev": "nest start --watch",
73+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
74+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
75+
"prebuild": "bun run db:generate",
76+
"start": "nest start",
77+
"start:debug": "nest start --debug --watch",
78+
"start:dev": "nest start --watch",
79+
"start:prod": "node dist/main",
80+
"test": "jest",
81+
"test:cov": "jest --coverage",
82+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
83+
"test:e2e": "jest --config ./test/jest-e2e.json",
84+
"test:watch": "jest --watch",
85+
"typecheck": "tsc --noEmit"
8686
}
8787
}

apps/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,4 @@
156156
"trigger:dev": "npx trigger.dev@4.0.0 dev",
157157
"typecheck": "tsc --noEmit"
158158
}
159-
}
159+
}

apps/app/src/app/(app)/onboarding/[orgId]/page.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,33 @@ export default async function OnboardingPage({ params }: OnboardingPageProps) {
6464
}
6565
});
6666

67+
// Local-only: prefill onboarding fields to speed up development
68+
const hdrs = await headers();
69+
const host = hdrs.get('host') || '';
70+
const isLocal =
71+
process.env.NODE_ENV !== 'production' ||
72+
host.includes('localhost') ||
73+
host.startsWith('127.0.0.1') ||
74+
host.startsWith('::1');
75+
76+
if (isLocal) {
77+
Object.assign(initialData, {
78+
describe:
79+
initialData.describe ||
80+
'comp ai is a grc platform saas that gets companies compliant with soc2 iso and hipaa in days',
81+
industry: initialData.industry || 'SaaS',
82+
teamSize: initialData.teamSize || '1-10',
83+
devices: initialData.devices || 'Personal laptops',
84+
authentication: initialData.authentication || 'Google Workspace',
85+
software:
86+
initialData.software || 'Rippling, HubSpot, Slack, Notion, Linear, GitHub, Figma, Stripe',
87+
workLocation: initialData.workLocation || 'Fully remote',
88+
infrastructure: initialData.infrastructure || 'AWS, Vercel',
89+
dataTypes: initialData.dataTypes || 'Employee data',
90+
geo: initialData.geo || 'North America,Europe (EU)',
91+
});
92+
}
93+
6794
// We'll use a modified version that starts at step 3
6895
return <PostPaymentOnboarding organization={organization} initialData={initialData} />;
6996
}

apps/app/src/app/(app)/onboarding/actions/complete-onboarding.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const onboardingCompletionSchema = z.object({
2222
workLocation: z.string().min(1),
2323
infrastructure: z.string().min(1),
2424
dataTypes: z.string().min(1),
25+
geo: z.string().min(1),
2526
});
2627

2728
export const completeOnboarding = authActionClient

apps/app/src/app/(app)/onboarding/components/PostPaymentOnboarding.tsx

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@comp/ui/c
77
import { Form, FormControl, FormField, FormItem, FormMessage } from '@comp/ui/form';
88
import type { Organization } from '@db';
99
import { ArrowLeft, ArrowRight } from 'lucide-react';
10-
import { useEffect } from 'react';
10+
import { useEffect, useMemo } from 'react';
1111
import { usePostPaymentOnboarding } from '../hooks/usePostPaymentOnboarding';
1212

1313
interface PostPaymentOnboardingProps {
@@ -33,12 +33,24 @@ export function PostPaymentOnboarding({
3333
isLastStep,
3434
currentStepNumber,
3535
totalSteps,
36+
completeNow,
3637
} = usePostPaymentOnboarding({
3738
organizationId: organization.id,
3839
organizationName: organization.name,
3940
initialData,
4041
});
4142

43+
const isLocal = useMemo(() => {
44+
if (typeof window === 'undefined') return false;
45+
const host = window.location.host || '';
46+
return (
47+
process.env.NODE_ENV !== 'production' ||
48+
host.includes('localhost') ||
49+
host.startsWith('127.0.0.1') ||
50+
host.startsWith('::1')
51+
);
52+
}, []);
53+
4254
// Dispatch custom event for background animation when step changes
4355
useEffect(() => {
4456
if (typeof window !== 'undefined') {
@@ -124,24 +136,37 @@ export function PostPaymentOnboarding({
124136
Back
125137
</Button>
126138

127-
<Button
128-
type="submit"
129-
form="onboarding-form"
130-
disabled={isOnboarding || isFinalizing || isLoading}
131-
className="group transition-all hover:pl-3"
132-
data-testid="onboarding-next-button"
133-
>
134-
{isFinalizing ? (
135-
'Setting up...'
136-
) : isLastStep ? (
137-
'Complete Setup'
138-
) : (
139-
<>
140-
Next
141-
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-0.5" />
142-
</>
139+
<div className="flex items-center gap-2">
140+
{isLocal && (
141+
<Button
142+
type="button"
143+
variant="secondary"
144+
onClick={completeNow}
145+
disabled={isOnboarding || isFinalizing || isLoading}
146+
className="group transition-all"
147+
>
148+
Complete now
149+
</Button>
143150
)}
144-
</Button>
151+
<Button
152+
type="submit"
153+
form="onboarding-form"
154+
disabled={isOnboarding || isFinalizing || isLoading}
155+
className="group transition-all hover:pl-3"
156+
data-testid="onboarding-next-button"
157+
>
158+
{isFinalizing ? (
159+
'Setting up...'
160+
) : isLastStep ? (
161+
'Complete Setup'
162+
) : (
163+
<>
164+
Next
165+
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-0.5" />
166+
</>
167+
)}
168+
</Button>
169+
</div>
145170
</div>
146171
<div className="w-full border-t border-border/30 pt-3">
147172
<p className="text-center text-xs text-muted-foreground/70">

apps/app/src/app/(app)/onboarding/hooks/usePostPaymentOnboarding.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,21 @@ export function usePostPaymentOnboarding({
138138
workLocation: allAnswers.workLocation || '',
139139
infrastructure: allAnswers.infrastructure || '',
140140
dataTypes: allAnswers.dataTypes || '',
141+
geo: allAnswers.geo || '',
141142
});
142143
};
143144

145+
const completeNow = () => {
146+
const currentValues = form.getValues();
147+
const allAnswers: Partial<CompanyDetails> = {
148+
...savedAnswers,
149+
...currentValues,
150+
organizationName,
151+
} as Partial<CompanyDetails>;
152+
153+
handleCompleteOnboarding(allAnswers);
154+
};
155+
144156
const onSubmit = (data: OnboardingFormFields) => {
145157
const newAnswers: OnboardingFormFields = { ...savedAnswers, ...data };
146158

@@ -215,5 +227,6 @@ export function usePostPaymentOnboarding({
215227
isLastStep,
216228
currentStepNumber: stepIndex + 1, // Display as steps 1-9
217229
totalSteps: postPaymentSteps.length, // Total 9 steps for post-payment
230+
completeNow,
218231
};
219232
}

apps/app/src/app/(app)/setup/actions/create-organization-minimal.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export const createOrganizationMinimal = authActionClientWithoutOrg
4747
name: parsedInput.organizationName,
4848
website: parsedInput.website,
4949
onboardingCompleted: false, // Explicitly set to false
50+
// Local-only: default access for faster local development
51+
...(process.env.NODE_ENV !== 'production' && { hasAccess: true }),
5052
members: {
5153
create: {
5254
userId: session.user.id,

apps/app/src/app/(app)/setup/lib/constants.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const companyDetailsSchema = z.object({
1919
devices: z.string().min(1, 'Please select device types'),
2020
authentication: z.string().min(1, 'Please select authentication methods'),
2121
workLocation: z.string().min(1, 'Please select work arrangement'),
22+
geo: z.string().min(1, 'Please select where your data is located'),
2223
});
2324

2425
export const steps: Step[] = [
@@ -121,6 +122,21 @@ export const steps: Step[] = [
121122
'Other',
122123
],
123124
},
125+
{
126+
key: 'geo',
127+
question: 'Where is your data located?',
128+
placeholder: 'e.g., North America',
129+
options: [
130+
'North America',
131+
'Europe (EU)',
132+
'United Kingdom',
133+
'Asia-Pacific',
134+
'South America',
135+
'Africa',
136+
'Middle East',
137+
'Australia/New Zealand',
138+
],
139+
},
124140
];
125141

126142
export const welcomeText = [

apps/app/src/app/(app)/setup/lib/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type CompanyDetails = {
1111
infrastructure: string;
1212
dataTypes: string;
1313
software: string;
14+
geo: string;
1415
};
1516

1617
export type ChatBubble = {

apps/app/src/jobs/lib/prompts.ts

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Policy } from '@db';
1+
import { FrameworkEditorFramework, Policy } from '@db';
22
import type { JSONContent } from '@tiptap/react';
33
import { logger } from '@trigger.dev/sdk';
44

@@ -8,53 +8,79 @@ export const generatePrompt = ({
88
contextHub,
99
companyName,
1010
companyWebsite,
11+
frameworks,
1112
}: {
1213
contextHub: string;
1314
companyName: string;
1415
companyWebsite: string;
1516
policy: Policy;
1617
existingPolicyContent: JSONContent | JSONContent[];
18+
frameworks: FrameworkEditorFramework[];
1719
}) => {
1820
logger.info(`Generating prompt for policy ${policy.name}`);
1921
logger.info(`Company Name: ${companyName}`);
2022
logger.info(`Company Website: ${companyWebsite}`);
2123
logger.info(`Context: ${contextHub}`);
2224
logger.info(`Existing Policy Content: ${JSON.stringify(existingPolicyContent)}`);
25+
logger.info(
26+
`Frameworks: ${JSON.stringify(
27+
frameworks.map((f) => ({ id: f.id, name: f.name, version: f.version })),
28+
)}`,
29+
);
2330

24-
return `
25-
Company details:
26-
27-
Company Name: ${companyName}
28-
Company Website: ${companyWebsite}
31+
const frameworkList =
32+
frameworks.length > 0
33+
? frameworks.map((f) => `${f.name} v${f.version}`).join(', ')
34+
: 'None explicitly selected';
35+
const hasHIPAA = frameworks.some((f) => f.name.toLowerCase().includes('hipaa'));
36+
const hasSOC2 = frameworks.some(
37+
(f) => /soc\s*2/i.test(f.name) || f.name.toLowerCase().includes('soc'),
38+
);
2939

30-
Knowledge Base for ${companyName}:
40+
return `
41+
Company: ${companyName} (${companyWebsite})
42+
Frameworks selected: ${frameworkList}
3143
44+
Knowledge base:
3245
${contextHub}
3346
34-
Tailoring rules:
35-
Create or update a policy based on strict alignment with SOC 2 standards and controls.
47+
Task: Edit the provided TipTap JSON template to produce the final policy TipTap JSON. Apply ONLY the rules below.
3648
37-
Contextualise every section with company Secure-specific systems, regions, and roles.
38-
Replace office-centric language with cloud and home-office equivalents.
39-
Build control statements that directly mitigate the listed risks; remove irrelevant clauses.
40-
Use mandatory language such as “must” or “shall”; specify measurable review cycles (quarterly, annually).
41-
End with a bullet list of auditor evidence artefacts (logs, tickets, approvals, screenshots).
42-
Limit to three-sentence executive summary and maximum 600-word main body.
43-
Wrap any unresolved detail in <<TO REVIEW>>.
49+
Required rules (keep this simple):
4450
45-
1.Remove Document Version Control section altogether(if present) and also adjust numbering accordingly
46-
2. Make a table of contents (in tiptap format)
47-
3. Give me executive summary on top of the document
48-
4. Wrap any unresolved detail in <<TO REVIEW>>
49-
5. Number 1 in Table of Contents will be Document Content Page
50-
6. I want to document to be strictly aligned with SOC 2 standards and controls
51+
1) Company details
52+
- If the template contains placeholders like {{...}}, replace ANY placeholder with information you actually have (from the knowledge base, company name, company website, frameworks context).
53+
- If a specific placeholder cannot be resolved, set it to "N/A" (do not invent values).
54+
- Only fill placeholders where the template asks; do not add new fields beyond the placeholders.
55+
- Placeholder legend (map values from the knowledge base Q&A where available):
56+
- {{COMPANY}} ⇐ Company Name
57+
- {{COMPANYINFO}} ⇐ Describe your company in a few sentences
58+
- {{INDUSTRY}} ⇐ What Industry is your company in?
59+
- {{EMPLOYEES}} ⇐ How many employees do you have
60+
- {{DEVICES}} ⇐ What Devices do your team members use
61+
- {{SOFTWARE}} ⇐ What software do you use
62+
- {{LOCATION}} ⇐ How does your team work
63+
- {{CRITICAL}} ⇐ Where do you host your application and data
64+
- {{DATA}} ⇐ What type of data do you handle
65+
- {{GEO}} ⇐ Where is your data located
66+
- If multiple answers exist, choose the most specific/concise form. If no answer is found for a placeholder, set it to "N/A".
5167
52-
Policy Title: ${policy.name}
53-
Policy: ${policy.description}
68+
2) Structure & style
69+
- Keep the same section order and general layout as the template (headings or bold titles as-is).
70+
- Do NOT copy instruction cue lines (e.g., "Add a HIPAA checklist...", "State that...", "Clarify that..."). Convert such cues into real policy language, and then remove the cue line entirely. If a cue precedes bullet points, keep the bullets but delete the cue line.
5471
72+
3) Handlebars-style conditionals
73+
- The template may contain conditional blocks using {{#if var}}...{{/if}} syntax (e.g., {{#if soc2}}, {{#if hipaa}}).
74+
- Evaluate these using the selected frameworks:
75+
- soc2 is ${hasSOC2 ? 'true' : 'false'}
76+
- hipaa is ${hasHIPAA ? 'true' : 'false'}
77+
- If the condition is true: keep only the inner content and remove the {{#if}}/{{/if}} markers.
78+
- If the condition is false: remove the entire block including its content.
79+
- For any other unknown {{#if X}} variables: assume false and remove the block.
5580
56-
Here is the initial template policy to edit:
81+
Output: Return ONLY the final TipTap JSON document.
5782
83+
Template (TipTap JSON) to edit:
5884
${JSON.stringify(existingPolicyContent)}
5985
`;
6086
};

0 commit comments

Comments
 (0)