ask-handoff

Sync quotation restriction to Control BEV + staging tree

slug ticket-108481-quotation-bev-writeback project asksuite-sophia-backend+asksuite-generic-bot source cursor updated 10/29/2026, 8:29:43 PM

Quotation restriction → Control BEV + staging tree — Implementation Plan

Goal: Staging form uses Sophia draft on version__staging__ S3 (no Control write); publish write-backs Control BEV like the old Booking Engines screen so prod form S3 expands. Task: TICKET-108481 Handoff: 108481 Spec: none — clarified in-session Architecture: On Sophia temp-tree build (initTempTree isSophia), overlay company.bookingEngineValidations with rules mapped from Sophia staging (version__staging__{companyId}) before lambda setJsonFormValidations — Control untouched. On PublishDeploymentUseCase, after promoting quotation restriction to prod, map rules→BEV and PUT company on Control (GET+merge+PUT) so Companies.update refresh recompiles prod S3. Discard/restore stays existing Sophia flow; prod Control only changes on publish. Tech: NestJS sophia-backend (Jest + fakes), generic-bot (Jest colocated *.test.ts), Control PUT /companies/:id, tree-manager setJsonFormValidations (no change if company BEV pre-patched). Test layout:

  • sophia-backend: npm test -- <path>.spec.ts (Jest, *.spec.ts next to use-case/provider; FakeControlProvider under src/tests/fakes/providers/)
  • generic-bot: npx jest --forceExit <path>.test.ts (colocated *.test.ts; avoid full npm test coverage gate per task) Global Constraints:
  • No Control write on staging save/patch/discard — only overlay staging tree BEV
  • Write-back Control only on publish (after prod quotation restriction promote)
  • Revert = normal discard (staging) or edit+publish again (prod)
  • TDD on every task (red → green)
  • Execute via execute (inline only)
  • Final review via review (designated reviewer model, mandatory)
  • No auto-commit; no push

Rejected simpler options: (1) tree-manager always reads Sophia on compile without Control write-back — leaves Livechat/other BEV readers stale; human chose write-back. (2) write-back Control on every staging patch — advances prod form before publish.


Task 1: Rules → BEV mapper (sophia-backend)

Files:

  • Create: src/modules/quotation-restriction/mappers/quotation-restriction-to-booking-engine-validations.mapper.ts
  • Test: src/modules/quotation-restriction/mappers/quotation-restriction-to-booking-engine-validations.mapper.spec.ts
  • Modify (optional reuse): keep inverse of CreateQuotationRestrictionUseCaseService.mapBookingEngineValidationsToRules — do not duplicate Control→rules

Interfaces:

  • Consumes: Partial<QuotationRestrictionRules>
  • Produces: BookingEngineValidations (same shape Control/BookingEnginesValidationService expect: guestsValidation.rules[ALL], periodValidation.rules[ALL])

Acceptance:

  • maxAdults/maxGuests/period fields round-trip with create syncFromControl mapping
  • empty/undefined rules → null or empty validations consistent with old screen null-clear behavior (document choice in test)

TDD:

  • Red: failing mapper specs for maxAdults + period fields
  • Green: pure mapper
  • Verify: npm test -- src/modules/quotation-restriction/mappers/quotation-restriction-to-booking-engine-validations.mapper.spec.ts
  • Expected: pass

Risk: mech


Task 2: ControlProvider update bookingEngineValidations

Files:

  • Modify: src/providers/control/control.provider.contract.ts
  • Modify: src/providers/control/control.provider.ts
  • Modify: src/tests/fakes/providers/fake-control.provider.ts
  • Modify: src/services/control/control.service.ts
  • Test: src/providers/control/control.provider.spec.ts
  • Test: src/services/control/control.service.spec.ts

Interfaces:

  • Consumes: companyId, BookingEngineValidations | null
  • Produces: void via GET /companies/:id → set bookingEngineValidations → PUT /companies/:id (same auth as existing Control calls)
  • ControlService.updateBookingEngineValidations(companyId, validations) wraps provider

Acceptance:

  • Fake records last PUT body with updated BEV
  • Provider PUT failure throws typed Control exception (mirror lead-config pattern)
  • Does not call Control on unrelated fields beyond merge of returned company + BEV

TDD:

  • Red: provider/service specs expect update method
  • Green: GET+merge+PUT
  • Verify: npm test -- src/providers/control/control.provider.spec.ts src/services/control/control.service.spec.ts
  • Expected: pass

Risk: integ


Task 3: Publish write-back Control BEV

Files:

  • Modify: src/modules/deployment/use-cases/publish-deployment/publish-deployment.usecase.ts
  • Test: src/modules/deployment/use-cases/publish-deployment/publish-deployment.usecase.spec.ts
  • Modify (thin helper if needed): src/modules/quotation-restriction/quotation-restriction.service.ts only if publish needs a named syncBookingEngineValidationsToControl to keep use-case thin

Interfaces:

  • Consumes: prod quotation restriction rules after createOrUpdateProdQuotationRestriction
  • Produces: ControlService.updateBookingEngineValidations(companyId, mappedBEV)
  • Order: promote Sophia prod → write-back Control → existing cache invalidate

Acceptance:

  • When staging has quotation restriction, publish maps published rules and updates Control
  • When no quotation restriction, Control update not called
  • Control update failure fails publish (same class as other hard promotions — form would stay wrong if soft-fail)
  • Discard/restore paths unchanged (no Control write)

TDD:

  • Red: publish specs assert Control update called with mapped maxAdults; assert not called when no restriction; assert publish throws when Control update rejects
  • Green: hook after promote
  • Verify: npm test -- src/modules/deployment/use-cases/publish-deployment/publish-deployment.usecase.spec.ts
  • Expected: pass

Risk: integ


Task 4: Staging temp tree overlays Sophia staging BEV

Files:

  • Modify: src/connectors/test-chat-web/index.ts (or small named helper under src/services/quotation-restriction/)
  • Modify: src/services/quotation-restriction/QuotationRestrictionService.ts — add named method to resolve validations for version__staging__{companyId} (API path is versionCompanyId)
  • Test: src/services/quotation-restriction/QuotationRestrictionService.test.ts
  • Test: new or existing connector/helper test colocated if extracted

Interfaces:

  • Consumes: prod company + isSophia===true before treeCompiler.initTempTree
  • Produces: company.bookingEngineValidations set from Sophia staging rules (mapper already in generic-bot); on fetch miss/error keep company BEV (fail-soft so temp tree still builds)
  • Lambda init-temp-tree unchanged — already applies company.bookingEngineValidations via setJsonFormValidations

Acceptance:

  • isSophia create-test fetches version__staging__{sanitize(companyId)} (or equivalent versionCompanyId), maps rules, sets BEV before lambda
  • Non-Sophia temp tree unchanged
  • No Control HTTP from this path
  • After discard/restoreToProd on staging restriction, next rebuild uses restored staging rules

TDD:

  • Red: QuotationRestrictionService (or helper) test for staging versionCompanyId fetch + overlay; assert Control not involved
  • Green: minimal overlay in executeCreateTest when isSophia
  • Verify: npx jest --forceExit src/services/quotation-restriction/QuotationRestrictionService.test.ts
  • Expected: pass

Risk: integ


Task 5: Publish regression — discard does not touch Control

Files:

  • Test: src/modules/deployment/use-cases/discard-changes/discard-changes.usecase.spec.ts
  • Modify: none unless a regression spy is missing

Interfaces:

  • Consumes: existing discard flow (restoreToProdQuotationRestriction)
  • Produces: proof ControlService.updateBookingEngineValidations is never called on discard

Acceptance:

  • Discard restores Sophia staging only
  • Control BEV remains last published value (no update call)

TDD:

  • Red: discard spec with Control update spy → must not be called
  • Green: no production code if already true; else remove accidental wiring
  • Verify: npm test -- src/modules/deployment/use-cases/discard-changes/discard-changes.usecase.spec.ts
  • Expected: pass

Risk: mech