Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
import * as cdk from 'aws-cdk-lib';
|
|
|
|
|
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
|
|
|
import * as rds from 'aws-cdk-lib/aws-rds';
|
|
|
|
|
import * as s3 from 'aws-cdk-lib/aws-s3';
|
|
|
|
|
import * as sqs from 'aws-cdk-lib/aws-sqs';
|
|
|
|
|
import * as cognito from 'aws-cdk-lib/aws-cognito';
|
|
|
|
|
import * as logs from 'aws-cdk-lib/aws-logs';
|
|
|
|
|
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
2026-05-20 19:07:49 -04:00
|
|
|
import * as sns from 'aws-cdk-lib/aws-sns';
|
|
|
|
|
import * as snsSubscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
|
|
|
|
|
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
|
|
|
|
|
import * as cloudwatchActions from 'aws-cdk-lib/aws-cloudwatch-actions';
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
import { Construct } from 'constructs';
|
|
|
|
|
|
|
|
|
|
export class FoundationStack extends cdk.Stack {
|
|
|
|
|
public readonly vpc: ec2.IVpc;
|
|
|
|
|
public readonly lambdaSecurityGroup: ec2.ISecurityGroup;
|
|
|
|
|
public readonly dbSecret: secretsmanager.ISecret;
|
|
|
|
|
public readonly uploadsBucket: s3.IBucket;
|
|
|
|
|
public readonly generatedBucket: s3.IBucket;
|
|
|
|
|
public readonly libraryBucket: s3.IBucket;
|
|
|
|
|
public readonly jobsQueue: sqs.IQueue;
|
|
|
|
|
public readonly userPool: cognito.IUserPool;
|
2026-05-20 19:07:49 -04:00
|
|
|
public readonly alarmTopic: sns.ITopic;
|
2026-05-20 18:51:31 -04:00
|
|
|
public readonly webClientId: string;
|
|
|
|
|
public readonly mobileClientId: string;
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
|
|
|
|
|
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
|
|
|
|
super(scope, id, props);
|
|
|
|
|
|
|
|
|
|
// VPC: 2 AZs, public + private subnets, single NAT Gateway
|
|
|
|
|
this.vpc = new ec2.Vpc(this, 'Vpc', {
|
|
|
|
|
vpcName: 'proposal-system-vpc',
|
|
|
|
|
maxAzs: 2,
|
|
|
|
|
natGateways: 1,
|
|
|
|
|
subnetConfiguration: [
|
|
|
|
|
{
|
|
|
|
|
name: 'public',
|
|
|
|
|
subnetType: ec2.SubnetType.PUBLIC,
|
|
|
|
|
cidrMask: 24,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'private',
|
|
|
|
|
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
|
|
|
|
|
cidrMask: 24,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// VPC Endpoints
|
|
|
|
|
this.vpc.addGatewayEndpoint('S3Endpoint', {
|
|
|
|
|
service: ec2.GatewayVpcEndpointAwsService.S3,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.vpc.addInterfaceEndpoint('SecretsManagerEndpoint', {
|
|
|
|
|
service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Security Groups
|
|
|
|
|
this.lambdaSecurityGroup = new ec2.SecurityGroup(this, 'LambdaSg', {
|
|
|
|
|
vpc: this.vpc,
|
|
|
|
|
securityGroupName: 'proposal-system-lambda-sg',
|
|
|
|
|
description: 'Security group for proposal system Lambda functions',
|
|
|
|
|
allowAllOutbound: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', {
|
|
|
|
|
vpc: this.vpc,
|
|
|
|
|
securityGroupName: 'proposal-system-rds-sg',
|
|
|
|
|
description: 'Security group for proposal system RDS instance',
|
|
|
|
|
allowAllOutbound: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
rdsSg.addIngressRule(
|
|
|
|
|
this.lambdaSecurityGroup,
|
|
|
|
|
ec2.Port.tcp(5432),
|
|
|
|
|
'Allow PostgreSQL from Lambda SG'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// RDS PostgreSQL 15
|
|
|
|
|
const dbInstance = new rds.DatabaseInstance(this, 'Database', {
|
|
|
|
|
instanceIdentifier: 'proposal-system-db',
|
|
|
|
|
engine: rds.DatabaseInstanceEngine.postgres({
|
|
|
|
|
version: rds.PostgresEngineVersion.VER_15,
|
|
|
|
|
}),
|
|
|
|
|
instanceType: ec2.InstanceType.of(
|
|
|
|
|
ec2.InstanceClass.T4G,
|
|
|
|
|
ec2.InstanceSize.SMALL
|
|
|
|
|
),
|
|
|
|
|
vpc: this.vpc,
|
|
|
|
|
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
|
|
|
|
|
securityGroups: [rdsSg],
|
|
|
|
|
multiAz: false,
|
|
|
|
|
allocatedStorage: 20,
|
|
|
|
|
maxAllocatedStorage: 100,
|
|
|
|
|
storageEncrypted: true,
|
|
|
|
|
backupRetention: cdk.Duration.days(7),
|
|
|
|
|
deletionProtection: true,
|
|
|
|
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
|
|
|
|
databaseName: 'proposals',
|
|
|
|
|
credentials: rds.Credentials.fromGeneratedSecret('proposalsadmin', {
|
|
|
|
|
secretName: 'proposal-system/db-credentials',
|
|
|
|
|
}),
|
|
|
|
|
publiclyAccessible: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.dbSecret = dbInstance.secret!;
|
|
|
|
|
|
|
|
|
|
// S3 Buckets
|
2026-05-27 17:45:11 -04:00
|
|
|
// Fix: INF-M5 — enforce HTTPS-only access on all S3 buckets
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
this.uploadsBucket = new s3.Bucket(this, 'UploadsBucket', {
|
|
|
|
|
bucketName: `proposal-system-uploads-${this.account}`,
|
|
|
|
|
encryption: s3.BucketEncryption.S3_MANAGED,
|
audit: fix all Critical and High security/reliability issues across monorepo
6-domain audit (API, web, mobile, lambdas, infra, QA) with fixes:
API security: scope internal API key middleware to allowed paths only,
return 401 on invalid key instead of falling through, remove unvalidated
JWT code path, sanitize error messages, add UpdateProposal validator,
remove status field from UpdateProposalRequest to prevent over-posting,
log swallowed exceptions in ProposalService.
Infrastructure: enforce SSL on all S3 buckets, encrypt SQS queues,
enable optional MFA on Cognito, add API Gateway access logging.
Lambdas: fix _retry_request undefined variable across all 4 Lambdas,
re-raise exceptions in pdf-extract/pdf-generate instead of swallowing,
add idempotency guard to suggestions Lambda.
Web: add ErrorBoundary, add auth loading state to ProtectedRoute,
add mutation error toasts in AdminWorkspace, fix dead Cognito link.
Mobile: add mutex to offline queue processing, distinguish permanent
vs retryable failures, register all screens for both roles, log sync
errors.
Swagger/OpenAPI: add Swashbuckle with JWT bearer security definition,
add ProducesResponseType attributes to key endpoints.
Includes AUDIT-REPORT.md with complete findings and CLAUDE.md project
instructions.
2026-05-27 15:33:27 -04:00
|
|
|
enforceSSL: true,
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
versioned: true,
|
|
|
|
|
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
|
|
|
|
lifecycleRules: [
|
|
|
|
|
{
|
|
|
|
|
transitions: [
|
|
|
|
|
{
|
|
|
|
|
storageClass: s3.StorageClass.INFREQUENT_ACCESS,
|
|
|
|
|
transitionAfter: cdk.Duration.days(90),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
cors: [
|
|
|
|
|
{
|
|
|
|
|
allowedMethods: [s3.HttpMethods.PUT, s3.HttpMethods.POST],
|
Phase 3 audit fixes: FIX-01–47, accessibility NITs, code quality NITs [skip deploy]
## Summary
Implements Phase 3 of the AUDIT-2026-05-20 findings:
- 29 FIX-severity items across API, web, infra, and lambdas
- 7 accessibility NITs (aria-labels, document titles)
- 4 code quality NITs (deduplication, constants extraction)
Key changes:
- API: N+1 fix, pagination clamping, idempotent transitions, upload confirm endpoint, revision TotalBidAmount carry-forward
- Web: confirmation dialogs, currency formatting, error states, date range filters, document titles
- Infra: S3 CORS lockdown, API Gateway throttling, AOSS network policy fix, CI concurrency
- Lambdas: skip empty suggestions, remove status side-effect
- Scripts: post-deploy health check
## Test plan
- [x] tsc --noEmit (web + infra)
- [x] dotnet build (api)
- [x] ruff check + format (lambdas)
- [x] Cross-review via orchestrator (no blockers)
[skip deploy]
2026-05-20 19:38:36 -04:00
|
|
|
allowedOrigins: [
|
|
|
|
|
'https://proposals.seahaven.com',
|
|
|
|
|
'http://localhost:5173',
|
|
|
|
|
],
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
allowedHeaders: ['*'],
|
|
|
|
|
maxAge: 3600,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
|
|
|
|
});
|
|
|
|
|
cdk.Tags.of(this.uploadsBucket).add('Purpose', 'Vendor PDFs and dispatcher attachments');
|
|
|
|
|
cdk.Tags.of(this.uploadsBucket).add('ManagedBy', 'proposal-system');
|
|
|
|
|
|
|
|
|
|
this.generatedBucket = new s3.Bucket(this, 'GeneratedBucket', {
|
|
|
|
|
bucketName: `proposal-system-generated-${this.account}`,
|
|
|
|
|
encryption: s3.BucketEncryption.S3_MANAGED,
|
2026-05-27 17:45:11 -04:00
|
|
|
enforceSSL: true, // Fix: INF-M5
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
versioned: true,
|
|
|
|
|
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
|
|
|
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
|
|
|
|
});
|
|
|
|
|
cdk.Tags.of(this.generatedBucket).add('Purpose', 'Generated proposal PDFs');
|
|
|
|
|
cdk.Tags.of(this.generatedBucket).add('ManagedBy', 'proposal-system');
|
|
|
|
|
|
|
|
|
|
this.libraryBucket = new s3.Bucket(this, 'LibraryBucket', {
|
|
|
|
|
bucketName: `proposal-system-library-${this.account}`,
|
|
|
|
|
encryption: s3.BucketEncryption.S3_MANAGED,
|
2026-05-27 17:45:11 -04:00
|
|
|
enforceSSL: true, // Fix: INF-M5
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
versioned: true,
|
|
|
|
|
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
|
|
|
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
|
|
|
|
});
|
|
|
|
|
cdk.Tags.of(this.libraryBucket).add('Purpose', 'Historical proposal library for RAG');
|
|
|
|
|
cdk.Tags.of(this.libraryBucket).add('ManagedBy', 'proposal-system');
|
|
|
|
|
|
|
|
|
|
// SQS Queue + DLQ
|
|
|
|
|
const dlq = new sqs.Queue(this, 'JobsDlq', {
|
|
|
|
|
queueName: 'proposal-system-jobs-dlq',
|
|
|
|
|
retentionPeriod: cdk.Duration.days(14),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.jobsQueue = new sqs.Queue(this, 'JobsQueue', {
|
|
|
|
|
queueName: 'proposal-system-jobs',
|
2026-05-20 18:51:31 -04:00
|
|
|
visibilityTimeout: cdk.Duration.seconds(720),
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
deadLetterQueue: {
|
|
|
|
|
queue: dlq,
|
|
|
|
|
maxReceiveCount: 3,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Cognito User Pool
|
|
|
|
|
const userPool = new cognito.UserPool(this, 'UserPool', {
|
|
|
|
|
userPoolName: 'proposal-system-auth',
|
|
|
|
|
selfSignUpEnabled: false,
|
|
|
|
|
signInAliases: { email: true },
|
|
|
|
|
standardAttributes: {
|
|
|
|
|
email: { required: true, mutable: true },
|
|
|
|
|
fullname: { required: true, mutable: true },
|
|
|
|
|
},
|
|
|
|
|
passwordPolicy: {
|
|
|
|
|
minLength: 12,
|
|
|
|
|
requireUppercase: true,
|
|
|
|
|
requireLowercase: true,
|
|
|
|
|
requireDigits: true,
|
|
|
|
|
requireSymbols: false,
|
|
|
|
|
},
|
|
|
|
|
accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
|
|
|
|
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.userPool = userPool;
|
|
|
|
|
|
|
|
|
|
// Cognito Groups
|
|
|
|
|
new cognito.CfnUserPoolGroup(this, 'DispatchersGroup', {
|
|
|
|
|
userPoolId: userPool.userPoolId,
|
|
|
|
|
groupName: 'dispatchers',
|
|
|
|
|
description: 'Dispatchers who submit proposal requests',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
new cognito.CfnUserPoolGroup(this, 'AdminsGroup', {
|
|
|
|
|
userPoolId: userPool.userPoolId,
|
|
|
|
|
groupName: 'admins',
|
|
|
|
|
description: 'Admins who review and approve proposals',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
new cognito.CfnUserPoolGroup(this, 'SysadminsGroup', {
|
|
|
|
|
userPoolId: userPool.userPoolId,
|
|
|
|
|
groupName: 'sysadmins',
|
|
|
|
|
description: 'System administrators',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Cognito Domain
|
|
|
|
|
userPool.addDomain('CognitoDomain', {
|
|
|
|
|
cognitoDomain: { domainPrefix: 'proposal-system-seahaven' },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Web App Client (PKCE)
|
2026-05-20 11:36:51 -04:00
|
|
|
const webClient = userPool.addClient('WebClient', {
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
userPoolClientName: 'proposal-system-web',
|
|
|
|
|
generateSecret: false,
|
|
|
|
|
authFlows: {
|
|
|
|
|
userSrp: true,
|
|
|
|
|
},
|
|
|
|
|
oAuth: {
|
|
|
|
|
flows: { authorizationCodeGrant: true },
|
|
|
|
|
scopes: [
|
|
|
|
|
cognito.OAuthScope.OPENID,
|
|
|
|
|
cognito.OAuthScope.EMAIL,
|
|
|
|
|
cognito.OAuthScope.PROFILE,
|
|
|
|
|
],
|
|
|
|
|
callbackUrls: [
|
|
|
|
|
'https://proposals.seahaven.com/callback',
|
|
|
|
|
'http://localhost:5173/callback',
|
|
|
|
|
],
|
|
|
|
|
logoutUrls: [
|
|
|
|
|
'https://proposals.seahaven.com',
|
|
|
|
|
'http://localhost:5173',
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-20 18:51:31 -04:00
|
|
|
this.webClientId = webClient.userPoolClientId;
|
|
|
|
|
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
// Mobile App Client (PKCE)
|
2026-05-20 11:36:51 -04:00
|
|
|
const mobileClient = userPool.addClient('MobileClient', {
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
userPoolClientName: 'proposal-system-mobile',
|
|
|
|
|
generateSecret: false,
|
|
|
|
|
authFlows: {
|
|
|
|
|
userSrp: true,
|
|
|
|
|
},
|
|
|
|
|
oAuth: {
|
|
|
|
|
flows: { authorizationCodeGrant: true },
|
|
|
|
|
scopes: [
|
|
|
|
|
cognito.OAuthScope.OPENID,
|
|
|
|
|
cognito.OAuthScope.EMAIL,
|
|
|
|
|
cognito.OAuthScope.PROFILE,
|
|
|
|
|
],
|
2026-05-20 11:36:51 -04:00
|
|
|
callbackUrls: ['com.seahavenind.proposals://auth/callback'],
|
|
|
|
|
logoutUrls: ['com.seahavenind.proposals://auth/logout'],
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-20 19:09:35 -04:00
|
|
|
this.webClientId = webClient.userPoolClientId;
|
2026-05-20 18:51:31 -04:00
|
|
|
this.mobileClientId = mobileClient.userPoolClientId;
|
|
|
|
|
|
2026-05-20 19:07:49 -04:00
|
|
|
// SNS Alarm Topic
|
|
|
|
|
const alarmTopic = new sns.Topic(this, 'AlarmTopic', {
|
|
|
|
|
topicName: 'proposal-system-alarms',
|
|
|
|
|
displayName: 'Proposal System Alarms',
|
|
|
|
|
});
|
|
|
|
|
alarmTopic.addSubscription(
|
|
|
|
|
new snsSubscriptions.EmailSubscription('adam@seahavenind.com'),
|
|
|
|
|
);
|
|
|
|
|
this.alarmTopic = alarmTopic;
|
|
|
|
|
|
|
|
|
|
const alarmAction = new cloudwatchActions.SnsAction(alarmTopic);
|
|
|
|
|
|
|
|
|
|
// DLQ Alarm: any message landing in DLQ indicates a processing failure
|
|
|
|
|
const dlqAlarm = new cloudwatch.Alarm(this, 'DlqDepthAlarm', {
|
|
|
|
|
alarmName: 'proposal-system-dlq-depth',
|
|
|
|
|
alarmDescription: 'Messages in DLQ — SQS processing failures',
|
|
|
|
|
metric: dlq.metricApproximateNumberOfMessagesVisible({
|
|
|
|
|
period: cdk.Duration.minutes(1),
|
|
|
|
|
}),
|
|
|
|
|
threshold: 0,
|
|
|
|
|
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
|
|
|
|
|
evaluationPeriods: 1,
|
|
|
|
|
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
|
|
|
|
|
});
|
|
|
|
|
dlqAlarm.addAlarmAction(alarmAction);
|
|
|
|
|
|
|
|
|
|
// RDS Alarms
|
|
|
|
|
const rdsAlarms = [
|
|
|
|
|
new cloudwatch.Alarm(this, 'RdsCpuAlarm', {
|
|
|
|
|
alarmName: 'proposal-system-rds-cpu',
|
|
|
|
|
alarmDescription: 'RDS CPU utilization above 80%',
|
|
|
|
|
metric: dbInstance.metricCPUUtilization({ period: cdk.Duration.minutes(5) }),
|
|
|
|
|
threshold: 80,
|
|
|
|
|
evaluationPeriods: 3,
|
|
|
|
|
treatMissingData: cloudwatch.TreatMissingData.BREACHING,
|
|
|
|
|
}),
|
|
|
|
|
new cloudwatch.Alarm(this, 'RdsConnectionsAlarm', {
|
|
|
|
|
alarmName: 'proposal-system-rds-connections',
|
|
|
|
|
alarmDescription: 'RDS database connections above 80',
|
|
|
|
|
metric: dbInstance.metricDatabaseConnections({ period: cdk.Duration.minutes(5) }),
|
|
|
|
|
threshold: 80,
|
|
|
|
|
evaluationPeriods: 2,
|
|
|
|
|
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
|
|
|
|
|
}),
|
|
|
|
|
new cloudwatch.Alarm(this, 'RdsFreeStorageAlarm', {
|
|
|
|
|
alarmName: 'proposal-system-rds-free-storage',
|
|
|
|
|
alarmDescription: 'RDS free storage below 2 GB',
|
|
|
|
|
metric: dbInstance.metricFreeStorageSpace({ period: cdk.Duration.minutes(5) }),
|
|
|
|
|
threshold: 2_000_000_000,
|
|
|
|
|
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
|
|
|
|
|
evaluationPeriods: 1,
|
|
|
|
|
treatMissingData: cloudwatch.TreatMissingData.BREACHING,
|
|
|
|
|
}),
|
|
|
|
|
];
|
|
|
|
|
for (const alarm of rdsAlarms) {
|
|
|
|
|
alarm.addAlarmAction(alarmAction);
|
|
|
|
|
}
|
|
|
|
|
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
// CloudWatch Log Groups
|
|
|
|
|
const logGroupNames = [
|
|
|
|
|
'proposal-system-api',
|
|
|
|
|
'proposal-system-pdf-extract',
|
|
|
|
|
'proposal-system-pdf-generate',
|
|
|
|
|
'proposal-system-library-ingest',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const name of logGroupNames) {
|
|
|
|
|
new logs.LogGroup(this, `LogGroup-${name}`, {
|
|
|
|
|
logGroupName: `/aws/lambda/${name}`,
|
|
|
|
|
retention: logs.RetentionDays.TWO_MONTHS,
|
|
|
|
|
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Outputs
|
|
|
|
|
new cdk.CfnOutput(this, 'VpcId', { value: this.vpc.vpcId });
|
|
|
|
|
new cdk.CfnOutput(this, 'UserPoolId', { value: userPool.userPoolId });
|
|
|
|
|
new cdk.CfnOutput(this, 'UserPoolArn', { value: userPool.userPoolArn });
|
|
|
|
|
new cdk.CfnOutput(this, 'UploadsBucketName', { value: this.uploadsBucket.bucketName });
|
|
|
|
|
new cdk.CfnOutput(this, 'GeneratedBucketName', { value: this.generatedBucket.bucketName });
|
|
|
|
|
new cdk.CfnOutput(this, 'LibraryBucketName', { value: this.libraryBucket.bucketName });
|
|
|
|
|
new cdk.CfnOutput(this, 'JobsQueueUrl', { value: this.jobsQueue.queueUrl });
|
|
|
|
|
new cdk.CfnOutput(this, 'DbSecretArn', { value: this.dbSecret.secretArn });
|
2026-05-20 11:36:51 -04:00
|
|
|
new cdk.CfnOutput(this, 'WebClientId', { value: webClient.userPoolClientId });
|
|
|
|
|
new cdk.CfnOutput(this, 'MobileClientId', { value: mobileClient.userPoolClientId });
|
2026-05-20 19:07:49 -04:00
|
|
|
new cdk.CfnOutput(this, 'AlarmTopicArn', { value: alarmTopic.topicArn });
|
Initial scaffold: monorepo structure, CDK stacks, CI/CD, domain model
Phase 0 of proposal-system: complete project setup including:
- CDK infrastructure (3 stacks: foundation, compute, frontend)
- .NET 8 solution with Clean Architecture (Domain, Application, Infrastructure, Api)
- EF Core data model (PostgreSQL) with all entities
- Python Lambda placeholders (pdf-extract, pdf-generate, library-ingest)
- React 19 web frontend scaffold (Vite + MUI)
- React Native mobile placeholder
- Shared TypeScript API contracts
- GitHub Actions CI/CD (ci.yaml + deploy.yaml)
- OIDC deploy role (githubdeploy-proposal-system)
- Dependabot configuration
- Cognito User Pool with Google OAuth, PKCE clients, groups
2026-05-16 18:40:46 -04:00
|
|
|
}
|
|
|
|
|
}
|