# Proposal System Internal proposal management platform for Sea Haven Industries. Dispatchers submit proposal requests, AI generates draft line items from historical data via Bedrock RAG, admins review and approve in a pricing workspace, and the system produces branded PDFs for delivery. ## Architecture Overview Monorepo with five primary services: - **.NET 8 API** -- Clean Architecture REST API hosted on Lambda behind API Gateway (JWT-authorized) with Function URL for internal access - **React 19 Web** -- MUI v7 admin/dispatcher workspace served via CloudFront + S3 - **React Native Mobile** -- iOS-first field app for dispatchers (offline-capable) - **Python Lambdas** -- PDF extraction, PDF generation, library ingestion, AI suggestions, AOSS index provisioning - **CDK Infrastructure** -- Three TypeScript stacks managing all AWS resources ## Repository Structure ``` proposal-system/ ├── api/ .NET 8 Web API (Lambda-hosted, EF Core + PostgreSQL) ├── web/ React 19 + MUI v7 + Vite frontend ├── mobile/ React Native 0.85 iOS app ├── lambdas/ Python 3.12 processing functions (arm64) ├── infra/ CDK TypeScript (3 stacks) ├── shared/ TypeScript API contracts (shared between web + mobile) ├── scripts/ Post-deploy and utility scripts ├── .github/ CI/CD workflows └── docker-compose.yml ``` ## Tech Stack | Component | Technologies | |---|---| | API | .NET 8, ASP.NET Core, EF Core + Npgsql, FluentValidation, Cognito JWT, Amazon.Lambda.AspNetCoreServer | | Web | React 19, TypeScript, MUI v7, Vite, Redux Toolkit, TanStack Query, axios | | Mobile | React Native CLI 0.85, React 19, React Native Paper, React Navigation, react-native-app-auth (PKCE), amazon-cognito-identity-js (SRP), Keychain, offline draft queue | | Lambdas | Python 3.12, arm64, pdfplumber, reportlab, httpx, boto3 | | Infrastructure | CDK TypeScript (aws-cdk-lib 2.253.1) | | AI/RAG | Bedrock Knowledge Base (Titan Embeddings v2), OpenSearch Serverless, Claude via Bedrock Runtime | | Auth | Cognito User Pool + Google OAuth IdP (groups: dispatchers, admins, sysadmins) | ## AWS Resources All resources are in **us-east-1** (account 328440206208). | CDK Stack | Key Resources | |---|---| | `proposal-system-foundation` | RDS PostgreSQL 15 (t4g.small), S3 buckets, SQS queue + DLQ, Cognito user pool, Secrets Manager | | `proposal-system-compute` | API Gateway HTTP API (JWT authorizer), .NET 8 API Lambda + Function URL, Python Lambdas (pdf-extract, pdf-generate, library-ingest, suggestions, oss-index-creator), OpenSearch Serverless collection, Bedrock KB | | `proposal-system-frontend` | CloudFront distribution (S3 OAC) | | Resource Type | Names | |---|---| | S3 Buckets | `proposal-system-uploads`, `proposal-system-generated`, `proposal-system-library`, `seahaven-ios-certificates` | | SQS | `proposal-system-jobs` (720s visibility, reportBatchItemFailures) + `proposal-system-jobs-dlq` (message body filtering by jobType) | | Secrets | `proposal-system/db-credentials`, `proposal-system/internal-api-key` | ## Local Development ### Prerequisites - .NET 8 SDK - Node.js 24+ - Python 3.12 - PostgreSQL 16 (via docker-compose or native) ### Database ```bash docker compose up -d # starts PostgreSQL on port 5432 # database: proposalsystem, password: localdev ``` ### API ```bash cd api dotnet restore dotnet run --project src/ProposalSystem.Api # runs on http://localhost:5000 ``` In development mode (`DevMode=true` in appsettings.Development.json): - JWT auth uses a local symmetric HMAC key (no Cognito required) - S3 service returns fake presigned URLs - SQS publisher logs messages without sending ### Web Frontend ```bash cd web npm install npm run dev # runs on http://localhost:5173, proxies /api to localhost:5000 ``` When `VITE_COGNITO_CLIENT_ID` is not set, the login screen shows role-selector buttons for local development. ### Infrastructure ```bash cd infra npm install npx cdk synth ``` ## CI/CD ### CI (on pull request to main) Five parallel jobs calling org reusable workflows: | Job | Workflow | What it checks | |---|---|---| | .NET Build & Test | `ci-dotnet.yaml` | Restore, build, test the API solution | | Web Frontend Check | `ci-typescript-cdk.yaml` | TypeScript typecheck for web | | Mobile Typecheck | `ci-typescript-cdk.yaml` | TypeScript typecheck for mobile | | Python Lint | `ci-python-sam.yaml` | ruff check + format on lambdas/ | | CDK Synth | `ci-typescript-cdk.yaml` | Synthesize CDK stacks (includes .NET publish) | ### Deploy (on push to main) Calls `cd-cdk.yaml` reusable workflow: 1. Publishes .NET 8 API and Python Lambdas 2. Runs `cdk deploy --all` 3. Executes `scripts/post-deploy.sh` (builds web, syncs to S3, invalidates CloudFront) Deploy uses OIDC role `githubdeploy-proposal-system`. Concurrency group prevents parallel deploys. ### Mobile Deploy Workflow: `deploy-mobile.yaml` -- builds and uploads to TestFlight via `cd-mobile-ios.yaml` reusable workflow on `macos-26`. Triggers: - **Automatic**: push to `main` with changes in `mobile/**` - **Manual**: `workflow_dispatch` for on-demand builds ## Mobile iOS The iOS app uses **Fastlane** with **match** for code signing. Certificates and profiles are stored in the `seahaven-ios-certificates` S3 bucket (versioning enabled, public access blocked). Build and upload to TestFlight is handled by the `cd-mobile-ios.yaml` reusable workflow. Required secrets: | Secret | Purpose | |---|---| | `AWS_DEPLOY_ROLE_ARN` | OIDC role for match S3 access | | `MATCH_PASSWORD` | Decryption passphrase for signing assets | | `ASC_KEY_ID` | App Store Connect API key ID | | `ASC_ISSUER_ID` | App Store Connect issuer | | `ASC_KEY_CONTENT` | App Store Connect API key (base64) | ## Authentication & Authorization Two-layer auth architecture with defense-in-depth: | Path | Authorizer | Authentication | |---|---|---| | External clients → API Gateway `/{proxy+}` | Cognito JWT authorizer (web + mobile client IDs) | .NET JWT middleware (ValidateAudience=true) | | `/api/health` | None (public) | None | | `/api/auth/callback`, `/api/auth/dev-login` | None (unauthenticated) | None (pre-auth endpoints) | | Internal Lambdas → Function URL | None (NONE auth type) | Internal API key (`X-Internal-Api-Key` header, value from Secrets Manager) | **Role-based access:** Cognito groups (`dispatchers`, `admins`, `sysadmins`) map to API roles via `cognito:groups` claim. Dispatchers can only see their own proposals (ownership enforced in service layer). VendorProposals and GeneratedPdfs endpoints restricted to admins/sysadmins. **Internal API key:** Python Lambdas call the .NET API via a Lambda Function URL (bypasses API Gateway JWT check). The `InternalApiKeyMiddleware` validates the key and assigns the `admins` role to the synthetic identity. ## Data Flow 1. Dispatcher submits proposal request (web or mobile) 2. API creates proposal record (with advisory-locked number generation), publishes SQS message 3. If vendor PDF attached: `pdf-extract` Lambda parses and structures data 4. Suggestions Lambda queries Bedrock KB for similar proposals, generates line items via Claude 5. Admin reviews/edits line items in pricing workspace 6. On approval: `pdf-generate` Lambda creates branded PDF 7. On send: `library-ingest` Lambda adds approved proposal to KB for future matching Failed SQS messages are reported via `batchItemFailures` and retried up to 3 times before moving to the DLQ.