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.
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.
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).