Adds a managed pricing library so admins can seed/curate reference priced items
directly, instead of the corpus being populated only by ingesting Sent proposals.
v1 PR5.
API:
- PricingLibraryItem entity + migration; /api/pricing-library CRUD (admin), with
GET {id} reachable by internal Lambda callers (admins role via internal key).
- Create/update publish an ADDITIVE library-ingest SQS job {pricingLibraryItemId},
wrapped so a publish failure never rolls back the save.
Lambda (library-ingest):
- Additive event-shape branch: pricingLibraryItemId -> fetch item, format markdown,
upload to pricing-library/{category}/{id}.md, trigger KB sync. The existing
proposalId path is byte-for-byte unchanged. Explicit error when neither id present;
warns when both present.
Web:
- Pricing Library management page (/admin/pricing-library): list / create / edit / delete.
GPT-4.1 cross-review on the event-shape change: no BLOCK (neither/both-id handling
applied). Verified: api 159 tests; web tsc + 26 tests; lambdas ruff + 37 pytest.
* feat: proposal delivery — email customers the PDF on "Mark as Sent"
Makes the system's namesake feature real: marking a proposal Sent now emails the
customer an expiring link to the branded PDF, and customers are managed (with
contact emails) instead of hardcoded. v1 PR4.
API:
- Customer.ContactEmail + migration; Customer list/update endpoints. Search stays
additive at GET /api/customers?query= (frozen-mobile + web compat); new paginated
list at GET /api/customers/list (admin).
- IEmailService (SesEmailService v2 / DevEmailService, dev-gated). MarkSentAsync
resolves the customer's email, presigns the latest PDF (7d), and sends via SES.
Email/presign failures are caught + audited and NEVER roll back the Sent transition.
- Startup EF migration guarded by a Postgres advisory lock (concurrency-safe).
Infra:
- SES email identity (proposals@seahavenind.com); least-privilege ses:SendEmail/
SendRawEmail scoped to the identity ARN + ses:FromAddress condition; SES_FROM_ADDRESS
env. SES starts in sandbox — production access needed for unverified recipients.
Web:
- Customer management page (/admin/customers): list / create / edit incl. contact email.
- New-proposal form searches real customers (free-solo) instead of a hardcoded value.
- Mark-as-Sent dialog notes the PDF will be emailed to the customer.
GPT-4.1 cross-review (SES IAM): no BLOCK (ses:FromAddress condition applied).
Verified: api build + 121 tests; web tsc + 26 tests; infra tsc; ruff clean.
* Potential fix for pull request finding 'CodeQL / Exposure of private information'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* Potential fix for pull request finding 'CodeQL / Exposure of private information'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* Potential fix for pull request finding 'CodeQL / Exposure of private information'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Re-theme the MUI app from teal (#0B5A73) to the Sea Haven Ops neutral-navy
structure (#111827) with action-blue (#2563EB) as the sole brand accent, driven
through the theme tokens so all screens update consistently. Cards become
border-driven (no shadow); table headers gray-50; status/priority chips aligned
to design-system token values.
Layout fixes:
- Topbar: square bottom corners (was inheriting MuiPaper radius)
- Sidebar: remove duplicate user tag (already shown in topbar)
- Main: drop redundant ml that double-counted the persistent drawer width
- New Proposal form: center the constrained container (mx: auto)
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.
- Replace $0.00 with "Not priced" via formatBidAmount helper
- Consistent login buttons with role descriptions
- Context-aware empty states (filter mismatch vs no data)
- Clear Filters button on proposal list and admin dashboard
- Rename "Regenerate" to "Regenerate Suggested Line Items"
- Add tooltips explaining disabled Save/Approve buttons
- Replace "RAG engine" jargon with plain language
- Improve User Management placeholder with Cognito guidance
- Add Puppeteer screenshot script for all roles/pages
RoleGuard: New component wrapping admin routes — dispatchers navigating
to /admin/* by URL now redirect to / instead of seeing error states.
Dashboard: Add mine=true filter so dispatchers only see their own
proposals and stats, not all users' data.
AdminWorkspace: Auto-save dirty changes before approving so edits to
refined scope and line items aren't silently discarded.
ProposalFormPage: Add onError toast and 300ms debounce on customer
search (was firing an API call per keystroke).
admin.ts: Stop swallowing errors in getPdf — let them propagate to the
mutation's onError handler. Fix AuditEntry.details type to string|null.
LoginPage: Fix pre-existing TS error with noUncheckedIndexedAccess.
* Fix NuGet versions and add InitialCreate EF Core migration
- Update AWSSDK.SQS and AWSSDK.SecretsManager to 3.7.500.0 (actual available versions)
- Update AWSSDK.Extensions.NETCore.Setup to 3.7.400
- Generate InitialCreate migration for PostgreSQL (all 8 entities)
- Build verified: 0 errors, 0 warnings
* Implement Dispatcher Frontend (Phase 2)
React 19 + MUI v7 + TypeScript + Vite SPA matching SHOC patterns:
Redux Toolkit (auth/ui slices), TanStack React Query, axios interceptors,
react-toastify, Cognito OAuth PKCE login flow, paginated proposal list,
new proposal form with customer autocomplete and vendor PDF upload,
read-only proposal detail with status stepper timeline.
* Add AuthController for Cognito code exchange and .env.example
Backend endpoint POST /api/auth/callback exchanges the OAuth
authorization code with Cognito's token endpoint, auto-provisions
the user in the DB, and returns the access token to the frontend.
* Implement Admin Frontend Experience (Phase 3)
Three-panel admin workspace: left reference panel (submission details,
vendor data), center editor (refined scope, inline line item table with
reorder/add/remove/pricing), right similar proposals panel (KB results
with pull-to-editor). Admin dashboard with stats cards and proposal
queue table. Approval flow with confirmation dialog, mark-as-sent,
and create-revision actions. Role-based sidebar navigation.
* Implement backend dev mode, internal API auth, and service layer enhancements
- Add dev-login endpoint with local JWT signing for local development
- Add InternalApiKeyMiddleware with timing-safe comparison for Lambda-to-API auth
- Add DevS3Service and NoOpJobPublisher for running without AWS services
- Implement CurrentUserService cascading user resolution (ID → sub → email → create)
- Add async ResolveAsync() to avoid synchronous DB calls in request pipeline
- Add /proposals/stats endpoint for efficient server-side status counts
- Guard status transitions: only allow Draft → InReview via update endpoint
- Add vendor proposals, generated PDFs, and similar proposals controllers
- Add ISimilarProposalService and SimilarProposalService
- Add [Authorize] to AddSimilarReference endpoint
* Implement Lambda functions for PDF processing, suggestions, and library ingest
- pdf-extract: Parse vendor PDFs with pdfplumber, fallback to Claude multimodal
- pdf-generate: Generate branded proposal PDFs with reportlab Platypus
- library-ingest: Format approved proposals as markdown and sync to Bedrock KB
- suggestions: Query KB for similar proposals, generate line items via Claude
- All Lambdas use internal API key auth and cold-start secret caching
- Fix pdf_path unbound variable in pdf-extract error handling
* Add Bedrock Knowledge Base, OpenSearch Serverless, and SQS message filtering
- Provision OpenSearch Serverless collection for vector search
- Create Bedrock Knowledge Base with Titan embedding model
- Configure S3 data source with fixed-size chunking (512 tokens, 20% overlap)
- Add suggestions Lambda with SQS event source filtering
- Scope bedrock:InvokeModel IAM to specific model ARN patterns
- Add internal API key secret in Secrets Manager
- Add log retention (2 months) to all Lambda functions
- Add docker-compose.yml for local PostgreSQL
* Apply SHOC design system styling across frontend
- Rewrite theme with SHOC palette (#0c4f6f primary, Nunito font, 4px radius)
- Add global CSS with Google Fonts import for Nunito
- Redesign Topbar with avatar initials, role subtitle, gradient header
- Redesign Sidebar with 220px width, section headers, active state border
- Restyle LoginPage with SHOC branded card and dev-mode role selector
- Update AdminDashboard KPI cards to centered SHOC style
- Add devLogin API method for local development auth flow
* Fix frontend navigation bugs, differentiate Dashboard from Proposals list
- Fix double nav selection by adding isNavActive() with ALL_NAV_PATHS set
- Fix /admin/users routing to placeholder instead of redirect to /
- Fix ProposalDetailPage Back button navigating to / instead of /proposals
- Differentiate Dashboard (KPI cards + recent 5) from ProposalListPage (full paginated table)
- Dashboard now uses dedicated /proposals/stats endpoint for accurate counts
- Fix adminApi.getPdf dead code (axios rejects before status check)
- Wire up PDF generation button in AdminWorkspace
- Adjust layout: 220px drawer, 10px content padding, 64px toolbar height
* Add appsettings.Development.json to gitignore
Prevent dev-only signing keys and connection strings from being committed.
* Fix CI failures: unused Python imports and CDK synth asset path
CDK synth job needs the .NET API published first so the Lambda asset
path exists. Python lint had 3 unused imports in pdf-generate.
* Apply ruff formatting to all Lambda Python files