- Add data-source hierarchy to opportunity manager (cache-only, cache-then-cw, cw-first)
- fetchPages/search/fetchByCompany use cache-only: Redis → DB (no CW calls)
- fetchItem uses cache-then-cw by default, cw-first when fresh=true
- Add idleTimeout: 255 to Bun.serve to prevent request timeouts
- Map CW status 57 (04. Confirmed Quote) to Active equivalency
- Add computeCacheTTL algorithm and opportunityCache module
- Add POST /v1/sales/opportunities/:identifier/products with field-level permission gating
- Add CWForecastItemCreate type for forecast item creation
- Store product display order locally (productSequence Int[] on Opportunity)
- Rewrite resequenceProducts to be local-only (no CW PUT, stable IDs)
- Remove reorderProducts CW util (PUT regenerated IDs & broke procurement)
- Update fetchProducts to apply local ordering with CW sequenceNumber fallback
- Add productSequence to OpportunityController.toJson()
- Update API_ROUTES.md, PERMISSIONS.md, PermissionNodes.ts
- Wrap startup syncs in safeStartup() to prevent crash on external service failure
- Add migrate-entrypoint.sh for auto-generating migrations from schema diff
- Update Dockerfile migration stage to use entrypoint script
- Add test job to build-and-publish workflow (runs before build)
- Add tests.yaml workflow to run tests on every push
- Fix test setup to use real RSA key pair instead of plain strings
- Add test script to package.json