Migrated 2026-09-28 from the codenforce repo's
docs/subsystems/cear/cear-logic.md(dating
to April 2026), per the doc-organization convention that a subsystem's deployed "how something
works" architecture lives here in the public-facing docs repo, while in-flight
features/design-questions/debugging notes stay in the JSF (codenforce) repo under
docs/subsystems/cear/. See the CEAR dev overview for the
current in-flight feature index.
Reflects CEARInternalSubmitBB, CEActionRequestsBB, CaseCoordinator,
CEARProcessingRouteEnum, and CommunicationCoordinator as of April 2026, except section 3
(Routing), section 4 (Status Lifecycle), and section 6 (Email Notification System), which are
current as of 2026-09-30 (the CEAR-A through CEAR-J batch — see the codenforce dev index at
docs/subsystems/cear/cear-index.md for the full per-item history) — check the codenforce dev
index for anything that has changed since.
A Code Enforcement Action Request (CEAR) is a complaint or municipal concern submitted to the CE office. It exists in one of three modes:
| Mode | Flags | Description |
|---|---|---|
| Property-specific | muniGeneral=false, notAtKnownAddress=false |
Tied to a specific parcel via parcelKey |
| Not at known address | muniGeneral=false, notAtKnownAddress=true |
Cannot be mapped to any parcel |
| Muni-general | muniGeneral=true, notAtKnownAddress=true |
Infrastructure/patrol/non-parcel; no property attached |
cearInternalSubmit.xhtml / CEARInternalSubmitBB)This page is for officers creating a CEAR on behalf of a caller or for themselves.
@PostConstruct)CaseCoordinator.cear_getInititalizedCEActionRequest() → returns blank CEActionRequest with dateOfRecord=now, requestorPersonType=Public, empty blob list.muni and dateOfRecord on workingCEAR.muniGeneralRequest = false, submitAsSelf = true.issueTypeList) and muni-general filtered (muniGeneralIssueTypeList).onMuniGeneralToggle() to prime activeIssueTypeList to the full list.onMuniGeneralToggle)Bound to the p:selectOneRadio AJAX listener.
workingCEAR.issue.workingCEAR.isMuniGeneral() (just set by the radio submit).muniGeneralRequest — used by all rendered= conditions in the XHTML to avoid repeated getter chaining.notAtKnownAddress=false, activeIssueTypeList = issueTypeList.notAtKnownAddress=true, activeIssueTypeList = muniGeneralIssueTypeList.The AJAX update targets both the property wrapper (outside the main form) and the details panel (inside it) using absolute client IDs to avoid cross-form resolution failures.
propertyQuickSearchCC to set sessionBean.sessProperty.muniGeneralRequest controls whether the property panel renders at all.sessProperty is set.validatePreSubmit)sessProperty == null → error, abort.onSubmitWithoutPhotos)validatePreSubmit()applyParcelAndSubmitterFields():
sessProperty.parcelKey onto workingCEAR.internalUserSubmitter=true; if submitting as self clears name/phone/email, sets requestorPersonType=User.CaseCoordinator.cear_insertCEARFirstStage(workingCEAR, ua):
actionRequestInitialStatusCode.createdBy, lastUpdatedBy.CEActionRequestIntegrator.insertCEActionRequest() → returns new requestID.resetWorkingState() to blank the form.onSubmitWithPhotos)Same as above through step 3, but instead of resetting:
workingCEAR.requestID.cearPreSaved = true.BlobUtilitiesBB.requestID != 0 just re-opens the dialog (idempotent).onResetForAnother → resetWorkingState().Handled by CEActionRequestSubmitBB. Also calls cear_insertCEARFirstStage for first-stage insert, then cear_updatePublicFields for contact info in the second stage.
cearCentral.xhtml / CEActionRequestsBB)After submission, unprocessed CEARs land on the CEAR dashboard (cearCentral.xhtml). Officers route them through a 3-step modal flow (cear-process-flow-var dialog).
evaulateSelectedRequestRoutingStatusAndUpdateRouteList)Called at every routing-state change. Builds processingRouteList based on request type:
| Request type condition | Routes offered |
|---|---|
isMuniGeneral() |
MUNI_GENERAL_ACTION_UNDERWAY, MUNI_GENERAL_ACTION_COMPLETED, INVALID_REQUEST, REFERRED_TO_OTHER_DEPARTMENT |
isNotAtKnownAddress() |
INVALID_REQUEST, REFERRED_TO_OTHER_DEPARTMENT |
| Standard (property-linked) — property has existing CE cases | ATTACH_TO_EXISTING_CASE, ATTACH_TO_NEW_CASE, ATTACH_TO_OCC_PERIOD, INVALID_REQUEST, REFERRED_TO_OTHER_DEPARTMENT |
| Standard (property-linked) — property has no CE cases yet | ATTACH_TO_NEW_CASE, ATTACH_TO_OCC_PERIOD, INVALID_REQUEST, REFERRED_TO_OTHER_DEPARTMENT |
ATTACH_TO_EXISTING_CASE is omitted when selectedRequestPropertyDH.getCeCaseList() is null or empty, preventing the officer from reaching step 3 only to find an empty table.
Retired from new routing 2026-09-30 (CEAR-F):
NO_VIOLATION_FOUND. The enum constant,
its DB status row, and theUNPROCESSEDre-route escape hatch for CEARs already sitting in
this status are all untouched — this is a soft deactivation at the route-offering layer
only (CEARProcessingRouteEnumis a Java enum, not a DB-backed lookup table, so there is no
row to flagdeactivatedtson). The constant carries@Deprecatedplus a Javadoc pointer to
this decision. Rationale: a genuine "no violation found" determination is a code-enforcement
finding and now requires a real CE case with a logged inspection
(ATTACH_TO_NEW_CASE/ATTACH_TO_EXISTING_CASE) rather than a one-click dashboard closeout.
Officers are routed into a case for anything short of literal junk (INVALID_REQUEST) or a
referral (REFERRED_TO_OTHER_DEPARTMENT).Added 2026-09-30 (CEAR-C, tier 1):
REFERRED_TO_OTHER_DEPARTMENT. A plain terminal
"get this off my plate" route — free-text note, no case/property requirement, offered in
every branch above. It reuses the existingCEAR_ROUTEDblast event (fires on every
terminal-route commit once a muni enables that event type) rather than needing its own event
type. A tier 2 enhancement (officer picks a specific staff person with a valid UMAP —
"MuniStaff rank or above" is being revisited to also allow read-only accounts — to receive
the full CEAR confirmation email as a new-task notification, via
CEARUpdateBlastContext.overrideToAddress, alongside — not instead of — the normal update
blast) is spec'd but stillPLANNING; see the codenforce dev index's CEAR-C doc and
cearbacklog.md.
After the type-based list is built, the method resolves the current route (resolveCurrentRoute()) by scanning all enum values for the one whose DB status key matches the current requestStatus.statusID. It then checks isTerminal() on that route:
UNPROCESSED, MUNI_GENERAL_ACTION_UNDERWAY): selectedRequestAwaitingInitialRouting = true — the "Process this request" button remains visible.UNPROCESSED as an escape-hatch re-route option, sets selectedRequestAwaitingInitialRouting = false — the button hides.Changed 2026-09-30 (CEAR-H-3): muni-general requests no longer skip this step. The
earlier behavior (onCEARProcessingInitspecial-casingselectedRequest.isMuniGeneral()to
jump straight tocearFlowActiveStep = 2) left an officer with no way to search for and
attach a real property to a muni-general concern that turned out to have one (e.g. a
"streetlights are out downtown" report that's actually traceable to one parcel). Every
request type — including muni-general — now always starts at step 1. A muni-general request
with genuinely no property gets a dedicated "This is a general request (no property),
proceed to routing" confirm button (rendered only whenempty requestProperty and muniGeneral) alongside the normal property-search box, wired to the same
onCEARFlowPropertyConfirmlistener. If an officer instead searches for and attaches a real
property to a muni-general CEAR here,CaseCoordinator.cear_updateCEARProperty()now also
flipsmuniGeneralback tofalse(mirroring its existingnotAtKnownAddresstoggle-off),
so the request routes as standard/property-based going forward instead of being stuck
offering only the muni-general routes.
For all other request types, step 1 displays the currently linked property address (or "No property linked").
requestProperty is not empty):
onCEARFlowPropertyConfirm → calls evaulateSelectedRequestRoutingStatusAndUpdateRouteList() then advances cearFlowActiveStep = 2.propertyQuickSearchCC):
onCEARFlowPropertyReassign(prop) → cear_updateCEARProperty(...) → refreshSelectedRequest() → evaulateSelectedRequestRoutingStatusAndUpdateRouteList() → advances to step 2.cear_updateCEARProperty)SystemCoordinator.appendNoteBlock.parcelKey on CEAR.notAtKnownAddress=false (request is now at a known address).CEActionRequestIntegrator.updateActionRequestInternalFields(cear).Officer picks a route from p:selectOneListbox (no placeholder; list is populated from processingRouteList).
onCEARFlowRouteSelectCommit:
selectedRoute == null → error.requestProperty into sessProperty.cearFlowActiveStep = 3.rendered="#{not ...muniGeneral}") → onCEARFlowBackToStep1 → sets cearFlowActiveStep = 1.Muni-general requests: the "Back to property" button in step 2 is hidden (
rendered="#{not cEActionRequestsBB.selectedRequest.muniGeneral}"), since there is no property step to return to.
Each route renders its own f:subview:
cear-flow-step3-excase-sv)selectedRequestPropertyDH.path2UseSelectedCaseForAttachment(cse):
CaseCoordinator.cear_connectCEARToCECase(cse, cear, ATTACH_TO_EXISTING_CASE, ua):
caseID, caseAttachmentTimestamp, caseAttachmentUser, lastUpdatedBy.actionRequestExistingCaseStatusCode.oncomplete.cear-flow-step3-newcase-sv)path1CreateNewCaseAtProperty:
parcelKey != 0: loads PropertyDataHeavy into sessProperty so ceCaseAddBB can read it.sessionBean.sessCEAR = selectedRequest so CECaseAddBB.onAddNewCaseCommitButtonChange can link the CEAR to the new case.publicExternalNotes.cear_updateCEAR(selectedRequest, ua, null) — persists note without status change (the new case creation action, handled by CECaseAddBB, will determine final CEAR status via cecase_insertNewCECase).cecase-add-dialog (cecase-add-dialog-var).CECaseAddBB.onAddNewCaseCommitButtonChange:
sessCEAR; if parcel keys match, passes it to CaseCoordinator.cecase_insertNewCECase, which links the CEAR to the new case automatically.cear-flow-step3-occperiod-sv)sessOccPeriod, then returns and confirms.path2AttachToOccPeriodDirect(op).path2AttachToOccPeriod(ev) reads sessOccPeriod.CaseCoordinator.cear_connectCEARToOccPeriod(op, cear, ua):
occPeriodID, caseAttachmentTimestamp, caseAttachmentUser.actionRequestOccPeriodStatusCode.Changed 2026-09-30 (CEAR-E): requires a real description + two-step confirm. Officers
were misusing this route for genuine code-enforcement determinations instead of literal form
junk (spam, gibberish, abusive text).path3AttachInvalidMessagenow rejects a blank or
trivially shortinvalidMessage(minimum ~20 characters) both client-side (inline validation)
and, per this codebase's "UI check is never the real gate" rule, inside
CaseCoordinator.cear_updateCEARitself as a route-specific audit guard (throws
BObStatusExceptionif bypassed). Ap:confirmDialog-based two-step confirm (BB Rule 6 —
neveronclick="return confirm(...)") asks the officer to affirm the request is genuinely
invalid junk, not a real determination, before committing. Applies identically whether reached
from a standard, not-at-known-address, or muni-general CEAR — no special-casing.
path3AttachInvalidMessage(ev):
publicExternalNotes using message bundle header/explanation.cear_updateCEAR(..., INVALID_REQUEST) → updates status, persists, flushes.path4AttachNoViolationFoundMessage(ev) still exists and still works for re-routes, but theprocessingRouteList for any new routing decision (§3.1).path3AttachInvalidMessage — free-text note, no confirmation steppath6ReferToOtherDepartment(ev) method, mirroring path3AttachInvalidMessage: acear_updateCEAR(..., REFERRED_TO_OTHER_DEPARTMENT),fireRoutingBlastIfEnabled() / CEAR_ROUTED blast path — no special recipientRendered by cear-flow-step3-munigeneral-sv when isCearFlowRouteMuniGeneral() is true.
path5MuniGeneralAction(ev):
appendRoutingNotesToRequest().selectedRoute == MUNI_GENERAL_ACTION_UNDERWAY: calls cc.cear_setMuniGeneralActionUnderway(selectedRequest, ua) — status is set to actionRequestMuniGeneralUnderwayStatusCode.selectedRoute == MUNI_GENERAL_ACTION_COMPLETED: calls cc.cear_setMuniGeneralActionCompleted(selectedRequest, null, ua) — status is set to actionRequestMuniGeneralCompletedStatusCode.onCEARFlowBackToStep2() (sets cearFlowActiveStep = 2), not onCEARFlowBackToStep1, because muni-general has no step 1.A muni-general CEAR can also be routed to
INVALID_REQUEST, in which case it falls through to the standardcear-flow-step3-closeout-svsubview (rendered whenisCearFlowRouteNoteAndClose()is true). The officer enters an optional message, then clicks "Confirm: Invalid request" →path3AttachInvalidMessage. The "Back to route selection" button in that subview also callsonCEARFlowBackToStep2()so the user returns to step 2, not the property step they never visited.
cear_updateCEAR(..., UNPROCESSED) → resets to initial status code.All status transitions go through cear_updateRequestStatus(cear, route) (now promoted onto
CaseCoordinator per CEAR-D, called by cear_updateCEAR), which reads the DB status ID from
the resource bundle key stored in CEARProcessingRouteEnum.
[inserted] → UNPROCESSED (non-terminal)
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Property-specific / not-at-known-address / muni-general │
│ ATTACH_TO_EXISTING_CASE → status=existingCase (terminal) │
│ ATTACH_TO_NEW_CASE → status=newCase (terminal) │
│ ATTACH_TO_OCC_PERIOD → status=occPeriod (terminal) │
│ INVALID_REQUEST → status=invalid (terminal)│
│ REFERRED_TO_OTHER_DEPARTMENT → status=referredOtherDept(terminal)│
│ NO_VIOLATION_FOUND (deprecated, legacy data / re-route only) │
│ → status=noViolation (terminal)│
├─────────────────────────────────────────────────────────────────────┤
│ Muni-general only │
│ MUNI_GENERAL_ACTION_UNDERWAY → status=muniGenUnderway (non-term.) │
│ MUNI_GENERAL_ACTION_COMPLETED → status=muniGenCompleted (terminal) │
└─────────────────────────────────────────────────────────────────────┘
↑
Any terminal status can be reset back to UNPROCESSED
(re-routing: officer selects UNPROCESSED from route list)
Non-terminal statuses (UNPROCESSED, MUNI_GENERAL_ACTION_UNDERWAY)
keep the "Process this request" button visible without a reset option.
Ground-truthed 2026-09-28, fixed 2026-09-30 (CEAR-D).
cear_updateRequestStatusused to
be a bare status-code write with no automatic internal note and no single "when was this
CEAR last routed" timestamp —caseAttachmentTimeStamponly ever covered the two case/occ-
period attachment routes, neverINVALID_REQUEST,NO_VIOLATION_FOUND,
MUNI_GENERAL_ACTION_COMPLETED, orREFERRED_TO_OTHER_DEPARTMENT. Fixed by:
- A new
routingcompletedts timestamp with time zonecolumn, stamped centrally inside
cear_updateRequestStatus()wheneverroute.isTerminal()— covering every terminal route
(including the two case/occ-period attachment routes, which keep setting
caseAttachmentTimeStampseparately as before; the two columns answer different
questions and are both kept). Never stamped onUNPROCESSED(non-terminal — that's the
un-resolution, not a resolution).- An automatic re-route note: if the CEAR's current route (resolved the same way
resolveCurrentRoute()does, now promoted ontoCaseCoordinator) was already terminal
when a new terminal route commits,cear_updateRequestStatus()appends an internal note
("Re-routed from{oldRoute}to{newRoute}by{officer}.") automatically, instead of
depending on each of the ~7 routing call sites to remember to do it themselves.routingcompletedtsis exposed in the CEAR dashboard UI (the routed-date line on the CEAR
card and the search table — see §7 of the codenforce dev index's CEAR-H doc) and is
searchable viaSearchParamsCEActionRequestsDateFieldsEnum.ROUTINGCOMPLETED_TS.
CaseCoordinator holds ceCaseCacheManager (Caffeine). CEARs are flushed from cache after:
refreshSelectedRequest() in CEActionRequestsBB always fetches a fresh copy from the coordinator + integrator after any mutation.
CEAR email notifications are handled entirely by CommunicationCoordinator. As of 2026-09-28
there are two structurally separate systems in production side by side, built five months
apart, each with its own recipient model, gating rules, and content builder. They are not layers
of one pipeline — a single routing action can fire both, independently, in the same request.
| §6.1 Legacy routing blast (April 2026 MVP) | §6.2 Update-blast system (Phase 12, Sept 2026) | |
|---|---|---|
| Entry point | CEActionRequestsBB.fireRoutingBlastIfEnabled() → CommunicationCoordinator.sendCEARRoutingUpdateBlast() |
CaseCoordinator.cear_dispatchRouteBlast() / cecase_dispatchBlastIfConfigured() → CommunicationCoordinator.comm_sendCEARUpdateBlast() |
| Event scope | CEAR routing only | CEAR routing plus case-lifecycle events for the CEAR's whole life: NOV sent, violation attached/compliant/nullified/stipcomp-extended, citation status recorded, case closed, case event logged |
| Gating | One BB checkbox (notifySubscribersOnRouting), on by default |
Per-muni, per-event-type JSON settings (CEARMuniUpdateBlastSettings), off by default for every event type; plus independent officer opt-out and submitter opt-out gates |
| Recipients per call | Broadcasts to the muni's whole staff-subscriber list (UserEmailSettings.cearSubscribeEnabled) + the requestor |
One targeted email per eligible submitter — the external requestor, or (separately) the specific staff user who created that CEAR, if they opted in |
| Content control | Fixed content, split only internal vs. external | 3 detail levels (MINIMAL/STANDARD/FULL — FULL currently clamped to STANDARD at runtime) |
| Unsubscribe | None | Per-recipient opaque opt-out token + link |
| Content builder | cear_buildRoutingUpdateEmailHTML |
comm_buildCEARUpdateBlastHTML |
| Context object | Flat params (routeName, publicNote, actingOfficer, internalRecipient) |
CEARUpdateBlastContext (event type, host case, violation/NOV/citation refs, detail-relevant fields) |
| Maintenance status | Frozen — preserved as-is; bugfixes only (§6.4) | Active — new CEAR/case-event-blast work lands here |
| Aspect | Detail |
|---|---|
| Storage | ceactionrequest.requestoremail — plain VARCHAR, no identity backing |
| Object | CEActionRequest.requestorEmail (String) |
| Opt-in gate | ceactionrequest.getemailupdates → CEActionRequest.isGetEmailUpdates() |
| Set at | Submission time (public form or internal submit when not submitting as self) |
| Changeability | Frozen to the row — cannot be updated without an UPDATE to the CEAR itself |
| Identity graph | None. No Human, no Person, no ContactEmail object. Just a string captured from the form |
The opt-in confirmation flag (getemailconfirmation) is a parallel boolean persisted on the same row; it records whether the submitter checked "send me a confirmation" at submission time. The routing updates flag (getemailupdates) is set either by the public submitter, or by the internal officer via the "Subscribe requestor to routing status update emails" checkbox on cearInternalSubmit.xhtml.
| Aspect | Detail |
|---|---|
| Storage | useremailsettings.cearsubscribed (boolean), useremailsettings.cearcontactemail_emailid (FK → contactemail) |
| Object graph | UserEmailSettings → ContactEmail → Human (full identity graph) |
| Opt-in gate | UserEmailSettings.cearSubscribeEnabled (mirrors cearsubscribed column) |
| Resolved via | CommunicationCoordinator.comm_getSubscribedUsersForMuni(muni) → SystemIntegrator.getUserEmailSettingsForMuniSubscribers(municode) |
| Address hydration | PersonCoordinator.getContactEmail(id) — live DB lookup at blast time |
| Changeability | Officer updates their ContactEmail record independently; no CEAR change needed |
| Identity graph | Full Human parent, managed in admin UI |
The key architectural distinction: Pathway 1 is adequate for anonymous public requestors who may have no system account. Pathway 2 gives staff full address-book management independent of any specific CEAR — it is muni-wide, not tied to who created any given CEAR. Contrast with §6.2.2's internal-submitter model, which is the opposite: tied to one specific CEAR's creator, not a subscriber list.
sendCEARSubscriptionBlast)When: Immediately after CaseCoordinator.cear_insertCEARFirstStage successfully inserts the CEAR.
Who receives it: Pathway 2 only — all staff with cearsubscribed=true for the muni.
Email category: EmailCategoryEnum.CEAR_STAFF_SUBSCRIBE
Content: Built by cear_buildConfirmationEmailHTML — includes location, concern type, description, requestor contact (suppressed when anonymityRequested=true), muni notes (internal submissions only), submission date.
Non-throwing: Individual recipient failures are logged to stderr; the insert is never rolled back.
The requestor confirmation email (sent to the raw email address) is a separate concern and is not currently implemented as a blast — it would be triggered at insert time if desired.
sendCEARRoutingUpdateBlast)When: After an officer completes a routing action in cearCentral.xhtml. Fired by CEActionRequestsBB.fireRoutingBlastIfEnabled() when the notifySubscribersOnRouting checkbox is checked — unconditionally for every routing commit, regardless of any per-muni setting (contrast §6.2.2).
Who receives it: Both pathways:
cear.isGetEmailUpdates() == true and requestorEmail is non-blank (Pathway 1).Email category: EmailCategoryEnum.CEAR_ROUTING_UPDATE
Content: Built by cear_buildRoutingUpdateEmailHTML — includes reference #, submission date, concern type, description, property address, the route name chosen by the officer, any public note entered at routing time, and (internal copy only) the acting officer's identity.
Non-throwing: Same per-recipient fault isolation as the subscription blast.
Because trigger B fires on every routing commit with no muni-level off switch, it is the
blast most munis actually see today for CEAR routing — the Phase 12 system'sCEAR_ROUTED
event (§6.2) only starts firing once a muni explicitly turns it on.
After every routing blast, CommunicationCoordinator.writeBlastTraceNote() appends an internal muni note to the CEAR (scope NoteScopeEnum.INTERNAL — staff-only, never shown on public view) recording the exact outcome:
Routing update blast: sent to alice@town.gov [staff]; bob@town.gov [staff]; req@gmail.com [requestor].
Routing update blast: sent to alice@town.gov [staff]. FAILED for: req@gmail.com [requestor].
Routing update blast: no eligible recipients.
Addresses are tagged [staff] or [requestor] to distinguish the two pathways. Any delivery failure is also recorded. This note is non-throwing — a failure to persist it is logged to stderr but does not affect the blast itself or any routing operation. The writer method itself (writeBlastTraceNote) is shared with the Phase 12 system (§6.3) — only the label text differs per call site.
| Who | Opt-in mechanism | Where set | Gate |
|---|---|---|---|
| Public submitter | "Send me status updates" checkbox on submission form | CEActionRequestSubmitBB |
cear.isGetEmailUpdates() |
| Internal staff submitter | "Subscribe requestor to routing status update emails" checkbox | CEARInternalSubmitBB |
cear.isGetEmailUpdates() |
| Staff officer | UserEmailSettings.cearSubscribeEnabled in admin UI |
Admin settings page | getUserEmailSettingsForMuniSubscribers() filter |
A per-municipality-configurable blast system covering every notable event in a CEAR's — and its
host case's — lifecycle, not just routing. Settings, event catalog, detail levels, and opt-out
mechanics are specified in the codenforce dev index at
docs/subsystems/i_municipality/municearupdatesettings-jul2026.md; this section summarizes how
it wires into CEAR processing specifically.
CEARUpdateBlastEventTypeEnum enumerates the blastable events, each independently switchable
per municipality (all off by default) via the JSON-typed updateblastsettings column,
managed in the muniManage admin page's "CEAR Subscriber Update Blasts" panel
(MunicipalityManageBB):
| Event | Fired from |
|---|---|
CEAR_ROUTED |
CaseCoordinator.cear_dispatchRouteBlast() — same routing commits as legacy Trigger B, but only for terminal routes, and only once the muni has this event enabled |
NOV_MARKED_SENT |
NOV marked sent (NoticeOfViolationBB) |
VIOLATION_ATTACHED |
Violation attached to a case |
VIOLATION_MARKED_COMPLIANT / VIOLATION_NULLIFIED / VIOLATION_STIPCOMP_EXTENDED |
Violation batch operations (CECaseBB) |
CITATION_STATUS_NCM |
Citation status recorded (CitationBB) |
CASE_CLOSED |
Case closed (CaseloadActionsBB, CECaseBB) |
CASE_EVENT_NCM |
Officer-selected event category logged (EventBB / EventCoordinator) |
Each enabled event type carries its own CEARUpdateBlastDetailLevelEnum (§6.2.3).
Every dispatch resolves recipients from the CEAR(s) attached to the case, then sends one
targeted email per unique eligible address (de-duplicated, normalized) — never a broadcast list:
requestorEmail, when non-blank and not opted outisCEARSubmitterOptedOut).CEActionRequest.createdBy), resolved via comm_resolveEmailForCEARInternalSubmitter(),cear.isNotifyInternalSubmitter()==true (an opt-in captured at submissioncearInternalSubmit.xhtml) and not opted out (isCEARInternalSubmitterOptedOut).This "internal" concept is unrelated to legacy Pathway 2 (§6.1.1). Pathway 2 is a muni-wide
subscriber address book: any staff member can opt in, regardless of which CEARs they touch.
This system's internal recipient is exactly one person per CEAR — whoever created it — and only
if that CEAR itself was flagged for it. There is no muni-wide staff broadcast list in this
system at all.
Every send additionally passes through CommunicationCoordinator.comm_sendCEARUpdateBlast()'s
four gates, in order: (1) muni has the event type enabled, (2) the acting officer hasn't opted
this specific blast out, (3) the submitter (external or internal, per above) hasn't opted out,
(4) a delivery address is actually present. Any gate failing returns null silently — no email,
no error.
Each muni/event-type pair configures a CEARUpdateBlastDetailLevelEnum:
| Level | Adds |
|---|---|
MINIMAL |
Reference #, submission date, the event itself, property address |
STANDARD |
+ concern type, any public note |
FULL |
+ affected-violation count — currently always clamped down to STANDARD at runtime (comm_clampDetailLevel) for non-internal submitters; reserved for future use |
Two private CaseCoordinator dispatchers build a CEARUpdateBlastContext and hand it to
CommunicationCoordinator.comm_sendCEARUpdateBlast():
cear_dispatchRouteBlast(cear, hostCase, route, publicNote, ua) — the CEAR-routing path,cecase_dispatchBlastIfConfigured(eventType, cse, cv, nov, citation, eventDescriptor, publicNote, violationCount, officerOptedOut, ua) — every other case-lifecycle event; iteratesBoth are best-effort/non-throwing: a blast failure never affects the primary operation
(routing, closing a case, marking a violation compliant, etc.).
Each CEAR carries independent opaque tokens for its external requestor
(updateBlastOptOutToken) and, when applicable, its internal submitter
(internalSubmitterBlastOptOutToken). CaseCoordinator.cear_processOptOutToken(token) is the
unauthenticated, idempotent public endpoint behind the unsubscribe link embedded in every blast
footer — the token itself is the credential. Legacy blasts (§6.1) have no equivalent; they cannot
be unsubscribed from short of disabling the muni-wide subscriber flag or the routing-blast
checkbox.
No blast under this system may ever fire without the acting officer first seeing a
one-click-skippable notice. This is a hard product invariant, not a per-event-type setting —
it applies identically whether the muni has that event type's blast enabled or disabled, and
there is no admin override, permission, or role that bypasses it. Ratified after an internal
review found the original Phase 12 design would have let a blast go out silently from a couple
of case-lifecycle action buttons with no confirmation step at all.
Mechanics:
cearBlastNoticeCC (<tt:cearBlastNoticeCC .../> — verify theofficerOptedOut parameter oncecase_dispatchBlastIfConfigured (§6.2.4) — opting out skips only this one dispatch, notCEARUpdateBlastEventTypeEnum values, even when the muni currently has that specific eventupdateblastsettings — so an officer is never surprised the otherCEAR_ROUTEDNOV_MARKED_SENT,VIOLATION_ATTACHED, VIOLATION_MARKED_COMPLIANT, VIOLATION_NULLIFIED,VIOLATION_STIPCOMP_EXTENDED, CITATION_STATUS_NCM, CASE_CLOSED, CASE_EVENT_NCM — seenotifySubscribersOnRouting) serving a similar "officer is aware a blast may fire" purpose.As of the 2026-09-28 refactor (CEAR-I, codenforce dev index), the two systems share their HTML
rendering layer — but nothing about recipients or gating:
CEAR_STYLE_* inline-style constants on CommunicationCoordinator (onebuildCearHeaderBanner (title + muni name + the "Internal CNFbuildCearReferenceRow, buildCearSubmittedRow,buildCearPropertyRow, buildCearOfficerRow ("Action taken by"), buildCearFooterRow.resolveInternalUserDisplayName, buildInquiryContactRowForEmail,resolvePropertyAddressForEmail, and the outbound sendEmail/EmailLog machinery.writeBlastTraceNote (§6.1.3) — both systems append the same style of internal audit note,This merge was deliberately scoped to presentation only. sendCEARRoutingUpdateBlast,
comm_sendCEARUpdateBlast, CearEmailContent, and CEARUpdateBlastContext were left untouched
— the two systems' recipient/gating engines remain intentionally separate (§6.4).
The legacy routing blast (§6.1) is preserved as-is going forward and is not a target for new
features. It stays wired up exactly as built in the April 2026 MVP — same unconditional
broadcast-to-subscribers model, same lack of per-muni settings or unsubscribe link — and will
only be touched again for genuine bugfixes (e.g. a rendering defect shared via §6.3, or a crash).
Any new CEAR- or case-event-blast capability (new event types, new detail-level content, new
opt-out UX, etc.) belongs in the Phase 12 update-blast system (§6.2). A full sunset of the legacy
system (removing it once every muni has migrated its routing-notification needs onto the Phase 12
CEAR_ROUTED event) remains an open, undated follow-up — see the codenforce dev index's CEAR-I
doc for the tracking note.
| Class | Scope | Responsibility |
|---|---|---|
CEARInternalSubmitBB |
@ViewScoped |
Staff CEAR submission form; owns workingCEAR for the duration of the form session |
CEActionRequestsBB |
@ViewScoped |
CEAR dashboard + processing flow; owns selectedRequest and routing step state |
CECaseAddBB |
(checked via EL) | Case creation dialog; reads sessCEAR from session to auto-link new case to CEAR |
CaseCoordinator |
@ApplicationScoped |
All CEAR business logic: insert, update, status change, attachment, muni-general operations; also owns both blast dispatchers (cear_dispatchRouteBlast, cecase_dispatchBlastIfConfigured) |
CEActionRequestIntegrator |
@ApplicationScoped |
All SQL for CEAR CRUD operations, plus opt-out token lookups (isCEARSubmitterOptedOut, isCEARInternalSubmitterOptedOut) |
CEARProcessingRouteEnum |
Enum | Maps each routing outcome to: DB status key, UI title, dialog widgetVar, update targets, and isTerminal() flag. Non-terminal: UNPROCESSED, MUNI_GENERAL_ACTION_UNDERWAY. Terminal: all others. |
CommunicationCoordinator |
@ApplicationScoped |
Builds + sends all CEAR emails for both blast systems (§6); owns the shared rendering helpers (§6.3) |
CEARUpdateBlastContext |
Entity (transient) | Per-send context for the Phase 12 update-blast system: event type, host case, driving violation/NOV/citation, detail-relevant fields, opt-out state |
CEARMuniUpdateBlastSettings |
Entity (JSON-backed) | Per-muni, per-event-type enablement + detail level for the Phase 12 system, persisted as updateblastsettings JSON |
CEARUpdateBlastEventTypeEnum |
Enum | Catalog of blastable events for the Phase 12 system (§6.2.1) — CEAR routing is only one of nine |