* fix(infra): give the Aurora cluster a distinct construct ID
The RDS->Aurora swap (PR3 #125) kept construct ID 'Database', so CloudFormation
saw the same logical ID change from AWS::RDS::DBInstance to AWS::RDS::DBCluster and
rejected the changeset ('Update of resource type is not permitted'). Renaming the
construct to 'AuroraCluster' gives the cluster a new logical ID, so CFN does a clean
replace (remove old DBInstance, add new DBCluster) instead of an in-place type change.
* chore(deps): group Dependabot minor/patch updates per ecosystem
Add a group to each update entry so weekly minor/patch bumps land as a
single PR per ecosystem/directory instead of one PR per package. Major
bumps remain individual PRs so breaking changes get isolated review.
Grouping takes effect when open-pull-requests-limit is raised above 0
(version updates are still paused during development, #109).
* chore(deps): unpause Dependabot version updates
Raise open-pull-requests-limit from 0 to 10 across all ecosystems,
re-enabling weekly version updates (paused during development, #109).
With grouping now in place, minor/patch bumps land as one grouped PR
per ecosystem; the limit caps outstanding major-bump PRs.
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>
Adds an env config layer resolved from CDK context (`-c env=staging`, default prod)
and threads it through all three stacks so a fully isolated staging environment can
be deployed in the same AWS account.
prod is byte-identical: the prod config reproduces the deployed values exactly and
stackSuffix='' keeps every construct ID, stack name, and physical resource name
unchanged. Verified via synth — prod foundation keeps proposal-system-db /
-uploads / db-credentials / -auth / seahaven; staging suffixes all of them.
- config.ts: EnvConfig (prod + staging, same account) + resolveConfig
- app.ts: env-aware stack naming + config passthrough
- foundation/compute/frontend: ~40 physical names suffixed with config.stackSuffix;
CORS, Cognito domain/callbacks, alarms email from config; RETAIN / deletionProtection
gated on config.retainData so staging can be torn down
- cdk.json: register `env` context (default prod)
- post-deploy.sh: STACK_SUFFIX for dynamic stack-name lookup (default prod)
Note: automated staging CI deploy needs a one-line `cdk-context` input added to the
org reusable cd-cdk.yaml (companion change). prod deploy is unaffected (default prod).
The .NET API Lambda Function URL uses authType=AWS_IAM, but the four workload
Lambdas (suggestions, pdf-extract, pdf-generate, library-ingest) sent unsigned
requests with only X-Internal-Api-Key -> every internal call 403s. They also
used bare fromAsset() with no pip bundling -> ImportError at cold start. Both
made the SQS->Lambda->API pipeline non-functional when deployed (v1 pre-flight).
- Add _sign_request_headers (botocore SigV4Auth, service "lambda"); serialize the
JSON body once and send via httpx content= so the signed payload hash matches
the bytes sent; preserve X-Internal-Api-Key for the app-layer check. Sign per
retry attempt to avoid SigV4 timestamp expiry on slow retries.
- Add CDK pip bundling (--platform manylinux2014_aarch64 --only-binary=:all:) to
all four Lambdas so ARM64 wheels (reportlab, Pillow, pdfplumber) ship.
- Converge _retry_request across all four (fixes possibly-undefined return in
pdf-extract/pdf-generate).
- Add SigV4 signing regression tests.
Verified: ruff clean, infra tsc clean, aarch64 wheels resolve for all four,
23 pytest pass. GPT-4.1 cross-family review: no BLOCK (FIX + NIT applied).
Build artifacts (api/publish/, *.tsbuildinfo) were untracked-but-committable;
.NET publish output can include appsettings.*.json. Flagged by sh-build-review.
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)
Restores the deliberate dev-pause from #86. The 2026-06-05 audit
remediation raised these limits to 5 without knowing the pause was
intentional; the resulting 14 version-update PRs were closed unmerged.
Security updates are unaffected by this setting. Re-raise at V1.
proposal-system's seven CI jobs have distinct names, so the org ruleset's
required 'ci / ci' status check never reported here. The aggregator needs
all real CI jobs and fails if any failed or was cancelled. NOTE: this
check will be red until the pre-existing Python Lint/Tests failures
(INFRA-55) are fixed — it reports CI state honestly.
Per handbook Pinning Principle: exact pins are kept current by Dependabot version updates gated by CI + dependency review. Blanket ignores let pins rot (see today's fast-uri incident).
Bump all actions/checkout references to @v6 (org target). v4 runs on a
node runtime version that is being deprecated; v6 is the verified org
standard alongside configure-aws-credentials@v6.
Ref: engineering-handbook cicd.md (workflow standardization).
Set open-pull-requests-limit to 0 on all 11 ecosystem entries so Dependabot
stops opening version-update PRs (which were being closed unactioned during
active development). Security updates are unaffected — they ignore this limit.
Revert the limits (or raise them) once the repo stabilizes.
- Add CloudFront origin to API Gateway CORS preflight and .NET CORS policy
- Replace HttpMethod.ANY with explicit methods so OPTIONS preflight doesn't
hit the JWT authorizer (was causing 403 on all API calls)
- Return Cognito ID token instead of access token from auth callback
(access tokens lack the aud claim required by API Gateway JWT authorizer)
- Add CloudFront callback URI to allowed redirect list
- Remove identity_provider=Google from login URL to show Cognito hosted UI
- Replace useBlocker (requires data router) with state-based navigation guard
to fix crash on AdminWorkspace with BrowserRouter
- Add auto-migration on Lambda cold start
- Enable Swagger in production
- AUDIT-REPORT.md: mark all Phase 6 findings fixed (API-M2/M5/M7/M9/M10/M12/M13,
WEB-M3/M4/M8/M9/M11, LAM-M2/M3/M6/M9, INF-M1/M2/M9), update test count to 149
- README.md: Function URL NONE→AWS_IAM, add Testing and Security sections,
expand CI table with test jobs, note SQS encryption/OpenSearch VPC/access logging
- Remove stale session docs (AUDIT-2026-05-20, HANDOFF, RETROSPECTIVE, CHATGPT prompt)
- Add .claude/agents/ to .gitignore
- Remove empty-state placeholder from SimilarProposalsPanel
- ProposalNumberGenerator tests (8 tests): format validation (SHI-YYYY-NNNN),
sequence incrementing, revision skipping, year boundary isolation, uniqueness,
zero-padding, high sequence rollover. Uses SQLite in-memory with Postgres
function stubs to support ExecuteSqlRawAsync.
- LineItemService state guard tests (18 tests): verifies line items cannot be
created/bulk-updated/deleted on Approved or Sent proposals (QA-C2), confirms
operations succeed on InReview and Revised statuses, validates
KeyNotFoundException on missing proposals, verifies audit logging.
- API client interceptor tests (14 tests): request interceptor attaches Bearer
token from sessionStorage (WEB-C1), handles missing/malformed token data,
response interceptor dispatches Redux logout on 401 (WEB-M2), returns friendly
messages for 403/404, extracts server error details, handles network errors.
- DbContextFactory updated to suppress InMemoryEventId.TransactionIgnoredWarning
so BulkUpdateAsync tests work with in-memory provider.
- Added SqliteDbContextFactory for tests requiring relational features.
- Added Microsoft.EntityFrameworkCore.Sqlite to test project dependencies.
Total: 104 .NET tests (was 77), 26 web tests (was 12). CI already wired.
- API-M2: Add comment for fail-loud auth config guard (already implemented)
- API-M5: Add FluentValidation validators for VendorProposal, GeneratedPdf,
and SimilarReference DTOs; move request records to Application DTOs
- API-M7: Add AsNoTracking() to all read-only queries in ProposalService,
LineItemService, AdminController, UsersController, FilesController
- API-M9: Log stderr from dev PDF generation instead of returning to client
- API-M10: Return generic "Authentication service unavailable" in auth
callbacks instead of leaking Cognito/DevMode configuration state
- API-M12: Enrich audit logging with before/after values for status changes,
proposal edits, and line item operations using structured JSON
- API-M13: Log previous role alongside new role on user role changes in
both UsersController and Cognito-synced role updates in AuthController
- WEB-M3: Add minimum length validation (10 chars) on scope of work field
with inline MUI error message
- WEB-M4: Add 'Other' to shared ServiceCategory contract to align with
API enum (already present in frontend and backend)
- WEB-M8: Show error alert with retry button instead of misleading zeros
when dashboard stats fetch fails (both dispatcher and admin dashboards)
- WEB-M9: Add MUI Skeleton loading state for line items in admin workspace
- WEB-M11: Wire 'Return to Review' button on approved proposals — backend
supports Approved->InReview transition, API client already had the method
LAM-M2: Add sanitize_user_text() to suggestions Lambda that strips common
prompt injection patterns (blocklist + delimiter neutralisation) before
including user-supplied text in Bedrock prompts.
LAM-M3: Add file size check in pdf-extract before downloading — rejects
PDFs over 50 MB with a logged warning and ValueError.
LAM-M6: Add validate_line_item_numerics() to suggestions Lambda that
rejects Bedrock-generated line items with negative values, NaN/Inf, or
amounts exceeding $10M ceiling.
LAM-M9: Replace indefinite API key cache with 5-minute TTL in all four
Lambdas (suggestions, pdf-extract, pdf-generate, library-ingest) so
rotated Secrets Manager values take effect promptly.
INF-M1: Replace wildcard anthropic.claude-* foundation-model ARN with the
specific cross-region inference profile ARN and its backing foundation model.
Both suggestions and pdf-extract Lambdas use us.anthropic.claude-sonnet-4-5-20250929-v1:0.
INF-M2: Replace aoss:* data access policy permissions with scoped actions.
KB role gets DescribeCollectionItems/CreateCollectionItems/UpdateCollectionItems
on collection and DescribeIndex/ReadDocument/WriteDocument on indexes.
Index creator gets CreateIndex/DescribeIndex/WriteDocument plus collection describe/create.
INF-M9: Change --require-approval never to --require-approval broadening in
infra/package.json deploy script so IAM/security changes require manual
confirmation during local development.
LAM-M1: Add event/record validation at handler entry for all 4 SQS-triggered
Lambdas. Validates Records key exists and is a non-empty list, checks each
record has a body key, and catches malformed JSON separately to add to
batchItemFailures.
LAM-M5: Change logger.error() to logger.exception() inside all except blocks
across pdf-extract, pdf-generate, suggestions, and library-ingest handlers
so stack traces are included in CloudWatch logs for debugging.
LAM-M8: Add _validate_s3_key() to pdf-extract, pdf-generate, and
library-ingest that strips path traversal sequences (../, ..\), collapses
double slashes, and rejects keys with disallowed characters via regex.