Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(crm): terminal stages lock, and "no show" is a reversible off-ramp

merged#2592CedarCopilot

CedarCopilot wants to merge 1 commit into staging from fix/stage-inference-terminal-lock

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Aug 26, 2026, 1:46 PM
  2. Aug 26, 2026, 1:54 PM
  3. Merged
    Aug 26, 2026, 5:31 PM
  4. Live on prod
    Aug 26, 2026, 5:31 PM
  5. Observed 2 days
    Aug 26, 2026, 5:31 PM
  6. Pipelines steady after this deploy
    Aug 26, 2026, 5:31 PM
  7. Unobserved

    Live on prod, no production signal yet

    Aug 29, 2026, 3:23 AM

Behaviors Libra is checking

CRM integration behavior from crm/stage-inference.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 54 recent prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (Step updateOpportunityField failed: HTTP 400: [{\), but the same failure had 96 log lines in the comparable pre-deploy baseline. Libra is not blaming this PR.

prod, checked Aug 29, 2026, 3:23 AM

Libra has verdicts on 0 of 1 tracked behaviors on prod; 1 is still being checked. Libra checks hourly for 3 days after each deploy.

Two defects in stage inference, both surfaced against Warp's live Salesforce OpportunityHistory during the data audit. Both cause the inferred stage to misreport where a deal actually got to.

1. A terminal stage did not lock the arc

Once a deal is won / lost / dq it is over. But post-close traffic , onboarding calls, support threads, invoices , kept getting reclassified as an earlier selling stage, walking a closed deal backwards.

Terminal now only yields to another terminal, so a genuine reopen (won → lost) still lands while ordinary post-close noise does not.

The terminal check sits before the off-ramp branches on purpose: otherwise a non-terminal off-ramp becomes an unlock hatch back into the ladder (terminal → no-show → discovery).

2. "No Show" was not recognised as an off-ramp

A missed meeting is recoverable , reps rebook it , so it has to be a reversible side state. Treated as an ordinary linear stage it did two wrong things at once: it read as forward progress, and at a high enumOrder it permanently froze the arc.

In Warp's book there are 41 No Show → Demo Scheduled (S1) transitions, which would have frozen 35 in-scope deals, 8 of them won.

Tests

3 new cases , terminal lock, terminal→terminal reopen, no-show recovery. 17 pass (verified on a clean staging base, not just in my working tree).

Test Files  1 passed (1)
     Tests  17 passed (17)

Scope

Deliberately just the two fixes. The Warp audit tooling and deliverables are a separate PR so these two logic changes get read on their own.

🤖 Generated with Claude Code

Greptile Summary

This PR makes terminal CRM stages resist later nonterminal event noise while preserving terminal-to-terminal transitions, and treats no-show stages as reversible off-ramps.

  • Adds terminal-stage locking before reversible off-ramp handling.
  • Extends server-side off-ramp label inference to recognize common no-show spellings.
  • Adds regression coverage for terminal locking, terminal-to-terminal transitions, no-show recovery, and terminal-to-no-show behavior.

Confidence Score: 4/5

The PR should not merge until the mail-side off-ramp classifier is updated so Active Pipeline filtering agrees with the new server treatment of no-show stages.

The terminal reconciliation logic is covered and consistent with the stated behavior, but the server-only no-show classification change causes the mail Active Pipeline preset to retain deals that the server now classifies as parked off-ramps.

Files Needing Attention: apps/server/src/services/crm/stage-inference.ts and apps/mail/modules/crm/utils/stage-terminality.ts

Important Files Changed

FilenameOverview
apps/server/src/services/crm/stage-inference.tsAdds the intended terminal lock and no-show off-ramp behavior, but updating only the server classifier leaves the mail-side mirror inconsistent.
apps/server/src/services/crm/tests/stage-inference.test.tsAdds focused reconciliation and classifier coverage for the new server behavior, though it does not cover parity with the mail classifier.

Flowchart

rendering diagram…
Prompt To Fix All With AI
### Issue 1
apps/server/src/services/crm/stage-inference.ts:88-89
**No-show classifiers diverge**

When an organization uses a “No Show” stage with th
Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/server/src/services/crm/__tests__/stage-inference.test.tsno production surface mapped
  • apps/server/src/services/crm/stage-inference.tsno production surface mapped