fix/svc-name
needs attention3577318 · fullpre-PRreviewed 2026-07-31 00:39 UTC2H · 3M · 3L · 1I- Purpose
- Unblock CFE pipeline for orgs whose stored utility_contracts.serviceName has gone stale and cannot be corrected by re-submitting the job with the correct name.
- Goal
- Let a caller-supplied serviceName win over the cached contract name, while keeping the stored name reachable as a fallback so a wrong caller-supplied name doesn't permanently break an RPU.
- Sub-goals
- Emit both candidates from resolveServiceNameCandidates (caller first)
- Thread serviceNameFallback through ASL to both collector and PS Lambdas
- Retry once with fallback in attemptRegistrationWithRescue on SERVICE_NAME_MISMATCH
- Maintain backward compat for in-flight SFN executions that omit the new field
- What
- Introduces resolveServiceNameCandidates() pure function in decisions.ts, adds serviceNameFallback to CacheCheckState/CacheCheckDecision, threads the field through the SFN ASL for both CfeStepFunctions and CfePaymentStatus stacks, and consumes it in both Lambda handlers.
- Why
- Prod incident 2026-07-30: 28 RPUs at org Tiendas Neto deadlocked. Pre-fix the stored name always won, so jobs submitted with the corrected name still re-registered with the old one and CFE kept answering SERVICE_NAME_MISMATCH.
- Areas
- domains/utility/src/cfe-job+168−5infra/cdk/src/stacks/services/utility/bills/cfe+9−1services/utility/bills/cfe/src/handlers+22−2.claude/rules+8−2
- Blast
- 7 files, +207/−10 across domain logic, infra ASL, and two Lambda handlers
Findings · 9
correctness1
Empty-string configServiceName treated as absent — callers cannot explicitly clear a stored name
domains/utility/src/cfe-job/cfe-job.decisions.ts:928
`|| null` coercion means supplying service_name: '' via the API falls back to the stored name rather than clearing it.
security3
Caller-controlled string becomes primary CFE identity with no visible server-side validation
services/utility/bills/cfe/src/handlers/collector.lambda.ts
The service_name from POST /v1/jobs now wins as the primary name sent to CFE portal. Verify upstream API handler enforces length cap and character-set constraint.
Only .trim() applied before external CFE submission
domains/utility/src/cfe-job/cfe-job.decisions.ts:928
No character-set restriction or length cap visible in this diff.
serviceNameFallback visible in SFN execution history
infra/cdk/src/stacks/services/utility/bills/cfe/stepfunctions.stack.ts
Titleholder legal names now appear in SFN ResultSelectors. Confirm history retention and IAM grants are scoped appropriately.
tests3
Lambda handler fallbackName expression is untested
services/utility/bills/cfe/src/handlers/collector.lambda.ts
The inline fallbackName ternary in both Lambda handlers has no unit tests. A bug here directly reproduces the prod incident. Add 3-case table tests: fallback absent, fallback equals primary (no-op), fallback differs from primary (used).
ASL serviceNameFallback threading is an untested seam
infra/cdk/src/stacks/services/utility/bills/cfe/stepfunctions.stack.ts
Nothing verifies the ASL threads serviceNameFallback from cacheCheck output into Lambda Parameters. If missing, handler receives undefined and silently degrades — same incident.
'both absent → both null, collector fails fast with RPU_INVALID' does not assert the RPU_INVALID path
domains/utility/src/cfe-job/__tests__/cfe-job.decisions.test.ts
Test name promises RPU_INVALID but only asserts the null tuple. Rename or add a follow-up test.
improvement2
Identical 3-line fallbackName expression duplicated in two sibling Lambda handlers
services/utility/bills/cfe/src/handlers/payment-status.lambda.ts
collector.lambda.ts and payment-status.lambda.ts live in the same package and contain verbatim duplicate fallbackName derivation. Extract a shared helper (resolveFallbackName) so a future fix propagates to both.
Redundant !== primaryName guard in Lambda handlers
services/utility/bills/cfe/src/handlers/collector.lambda.ts
resolveServiceNameCandidates already guarantees serviceNameFallback !== serviceName. The re-check is redundant and obscures the upstream invariant.