Proposal Management System - AI-powered proposal generation and management
Find a file
Adam Moussa 9632c1048c
fix: resolve Phase 6c gate findings (2 high, 5 low) across api, web, lambda, mobile
Gemini scanner sweep (token-bypass), GPT-4.1 cross-review, and the
6-detector /sh-security-review fan-out ran against 6acfdab..HEAD; every
confirmed finding fixed:

HIGH (deployment blockers, logic detector):
- CONC-L1: suggestions lambda's bulk line-item PUT sent no
  proposalVersion — every AI suggestion job would 422 and be silently
  swallowed. Now fetches the proposal's rowVersion, echoes it, and
  retries once with a fresh token on 409. Pytest updated (38 green).
- CONC-L2: mobile admin surface (update/approve/send/revise, bulk line
  items) sent no tokens — the entire mobile admin workflow would 422.
  Tokens threaded through mobile api layer + workspace/line-item
  screens with 409 refetch handling. tsc clean.

MEDIUM-adjacent (scanner):
- VendorProposalsController: the VendorTotalCost write on Proposal now
  bumps Version (was a silent lost-update path bypassing the guard).
- FilesController: GeneratePDF audit staged into the same SaveChanges.

LOW (detectors):
- 409 envelope is schema-validated client-side
  (proposalConcurrencyConflictSchema.safeParse) and id-checked before
  seeding the react-query cache; malformed state degrades to
  invalidation (INJ-409-01/WEB-CONC-L1).
- ProposalConcurrencyException.CurrentState typed ProposalResponse?
  so an EF entity can never serialize into the 409 body (SC-1).
- Guard caller contract documented + GuardedEndpointAuthorizationTests
  reflection tripwire: guard-reaching endpoints must stay admin-gated
  (AUTHZ-CG-01).
- Pre-check currentState now loads display navigations so both 409
  paths return the same shape (CONC-L3).
- Save chain's trailing getById failure no longer misreports a
  committed save; falls back to invalidation (CONC-L4).

Also caught during fix verification: the handler's manual currentState
serialization lacked JsonStringEnumConverter — enums would serialize
as numbers, client schema validation would reject every guarded 409,
and the state would always be discarded. Now matches the MVC pipeline
and is pinned by a wire test.

193 xUnit / 70 vitest / 38 pytest green; mobile + shared tsc clean;
Playwright smoke 2/2.
2026-07-13 21:20:12 -04:00
.github ci(web): Phase 5 — prettier check + Playwright smoke via org frontend workflow 2026-07-13 20:14:41 -04:00
.security-review chore(security): add repo-local suppressions for adjudicated FPs (#219) 2026-07-13 14:30:51 -04:00
api fix: resolve Phase 6c gate findings (2 high, 5 low) across api, web, lambda, mobile 2026-07-13 21:20:12 -04:00
docs/adr feat(infra): migrate Bedrock KB vector store to Aurora pgvector (#125) 2026-06-12 18:44:42 -04:00
infra build(deps-dev): bump aws-cdk in /infra in the infra group 2026-07-11 04:32:54 +00:00
lambdas fix: resolve Phase 6c gate findings (2 high, 5 low) across api, web, lambda, mobile 2026-07-13 21:20:12 -04:00
mobile fix: resolve Phase 6c gate findings (2 high, 5 low) across api, web, lambda, mobile 2026-07-13 21:20:12 -04:00
scripts feat(infra): parameterize stacks for multi-env (prod/staging) (#123) 2026-06-12 18:04:53 -04:00
shared/api-contracts feat(contracts+web): thread concurrency tokens and 409 conflict handling through the domain layer 2026-07-13 21:01:46 -04:00
web fix: resolve Phase 6c gate findings (2 high, 5 low) across api, web, lambda, mobile 2026-07-13 21:20:12 -04:00
.gitignore ci(web): Phase 5 — prettier check + Playwright smoke via org frontend workflow 2026-07-13 20:14:41 -04:00
AUDIT-REPORT.md docs: Phase 6 cleanup — update AUDIT-REPORT, README, remove stale docs 2026-05-27 18:18:44 -04:00
CLAUDE.md docs: update CLAUDE.md audit status for Phase 5 2026-05-27 18:18:44 -04:00
docker-compose.yml Fix Phase 2 audit findings: reliability, UX, and operational monitoring 2026-05-20 19:07:49 -04:00
README.md ci(web): Phase 5 — prettier check + Playwright smoke via org frontend workflow 2026-07-13 20:14:41 -04:00

Proposal System

.NET TypeScript Python AWS CDK CI

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 (AWS_IAM) 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 v9, Vite, TanStack Query, react-hook-form + zod, 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 (VPC-only), Claude Sonnet via Bedrock cross-region inference
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 + access logging), .NET 8 API Lambda + Function URL (AWS_IAM), Python Lambdas (pdf-extract, pdf-generate, library-ingest, suggestions, oss-index-creator), OpenSearch Serverless collection (VPC endpoint), 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, SQS-managed encryption, reportBatchItemFailures) + proposal-system-jobs-dlq (SQS-managed encryption, 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

docker compose up -d    # starts PostgreSQL on port 5432
# database: proposalsystem, password: localdev

API

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

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

cd infra
npm install
npx cdk synth

CI/CD

CI (on pull request to main)

Six parallel jobs calling org reusable workflows:

Job Workflow What it checks
.NET Build & Test ci-dotnet.yaml Restore, build, test the API solution (xUnit)
Web Frontend Check ci-typescript-frontend.yaml Prettier format:check, build (includes tsc -b), vitest suite, Playwright chromium smoke (dev-login → proposal list, API mocked)
Mobile Typecheck ci-typescript-cdk.yaml TypeScript typecheck for mobile
Python Lint ci-python-sam.yaml ruff check + format on lambdas/
Python Tests ci-python-sam.yaml pytest suite (19 tests — pdf-generate, suggestions handlers)
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 AWS_IAM (grantInvokeUrl) 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 with AWS_IAM auth (bypasses API Gateway JWT check). The InternalApiKeyMiddleware validates the X-Internal-Api-Key header and assigns the admins role to the synthetic identity. Lambdas cache the API key from Secrets Manager with a 5-minute TTL.

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.

Testing

149 tests across three stacks, all run in CI on every PR:

Suite Framework Count Coverage
.NET API xUnit 104 State machine transitions, authorization attributes, middleware, validators, ProposalNumberGenerator, LineItemService state guards
Web vitest 26 ProtectedRoute, RoleGuard, API client interceptor (401 logout, token attachment)
Python Lambdas pytest 19 pdf-generate and suggestions handler contracts
cd api && dotnet test          # .NET tests
cd web && npm test             # vitest
cd lambdas && python -m pytest # pytest

Security

Hardening applied across all layers (see AUDIT-REPORT.md for full details):

  • Auth: Cognito JWT validation with audience check, startup fails if auth not configured, DevMode gated to IsDevelopment()
  • API: FluentValidation on all DTOs, generic error responses (no stack traces or config leaks), structured audit logging with before/after diffs
  • Function URL: AWS_IAM auth + internal API key (two-layer defense)
  • Infrastructure: S3 enforceSSL + BLOCK_ALL, SQS managed encryption, OpenSearch VPC-only, Cognito optional TOTP MFA, API Gateway access logging
  • Lambdas: Prompt injection sanitization, PDF size limits, numeric validation on AI suggestions, S3 key sanitization, idempotent SQS processing
  • CI/CD: OIDC (no long-lived credentials), SHA-pinned workflow refs, --require-approval broadening on local deploys
  • Web: sessionStorage for tokens (not localStorage), error boundaries, role guards on all admin routes, 401 interceptor clears auth state