promotion_notifications_apply
This implementation is a dependency of the documentation branch.
Compose the coupon event's editable web/PWA and app push switches in one save. Omitted switches retain their existing values.
Access and input
Requires the reader's permission and separate notification service configuration, a current observed revision and an active stored coupon event. Source hosted tool and promotion-notifications recovery scopes are selected-channel only.
| Input | Contract |
|---|---|
| channelCode | Required selected channel code, with the reader's validation. |
| requestId | Required stable identifier matching [A-Za-z0-9_-]{8,80}. |
| expectedRevision | Required current reader digest, exactly 64 lowercase hexadecimal characters. |
| settings | Required strict object containing at least one supported boolean. |
| settings.isSendPwaPush | Optional desired web/PWA push state. Omission preserves it. |
| settings.isSendAppPush | Optional desired app push state. Omission preserves it. |
| dryRun | Optional boolean, default false. |
All objects are strict. eventKey, activation, email, third-party flags, receivers, message bodies and financial fields are rejected. Replace the illustrative digest with the returned revision.
{"channelCode":"selected-channel-code","requestId":"coupon-push-001","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","settings":{"isSendPwaPush":true,"isSendAppPush":false},"dryRun":true}
{"channelCode":"selected-channel-code","requestId":"coupon-push-001","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","settings":{"isSendPwaPush":true,"isSendAppPush":false},"dryRun":false}
Planning and save
The tool first reads stable complete event/app state and verifies expectedRevision. A stale revision returns PROMOTION_NOTIFICATION_CONFLICT. An inactive stored event returns action_required/PROMOTION_NOTIFICATION_DISABLED with writesPerformed:false and an exact console destination; the tool cannot enable the visually locked event. No new journal or mutating business request is created in that path; the checks above still read the current configuration.
A new dry run returns PROMOTION_NOTIFICATIONS_PLAN, changesRequired, sourceBody and writesPerformed:false without a journal or PUT. sourceBody preserves isActivated and isSendEmail, combines the requested switches with omitted values, and sets source constants isSendRealtime:true, receiverType:MEMBER_SINGLE and uniReceiverTypes:[SOURCE_OWNER]. These constants can differ from previously stored values; they are part of the source save contract. A dry run against an existing matching journal reports existingPhase rather than producing a fresh plan.
If requested switch values already match, the actual apply records a completed no-op and sends no PUT. It does not normalize source constants merely to force a write. Otherwise it journals prepared state, repeats complete record/selected app checks, persists save-sent before dispatch, then sends one PUT to the fixed coupon-event path. The notification service must return HTTP 200 data with sendingConfiguration. Its separate acknowledgement contract requires the known positive record id, fixed eventKey, activation/receiver/realtime/app/PWA/email fields; channelId may be omitted. Optional custom key, timestamps and other fields may also be absent. Empty acknowledgement is insufficient. Every returned field except top-level updatedAt must exist in the intended baseline-plus-body and match it; explicit foreign identity, contradictory fields or newly introduced unknown fields are rejected. Missing receipt fields are never fabricated from the baseline.
Completion independently requires a full stable selected GET, including channelId, and the unchanged app indicator. Activation, email, custom message/null and every other unedited JSON business field must survive. Only top-level updatedAt is excluded from comparison across a changed save; when present before, it must still be present after. insertedAt and all other fields remain protected. Full updatedAt remains in snapshots, revision/pre-write checks, no-op records and stored evidence. This exception does not weaken stale-revision rejection. App-indicator drift or incomplete/mismatched content leaves the operation pending. No rollback, notification send or delivery test is performed.
Inputs
| Field | Required here | Contract |
|---|---|---|
settings |
Yes | object |
settings.isSendPwaPush |
No | boolean |
settings.isSendAppPush |
No | boolean |
confirmToken |
No | string; pattern ^ct[a-f\d]{32}$; The confirmToken from this tool's preview result. Omit it to preview; repeat the same input with it to execute. |
Results and recovery
Use task status or resume with taskType promotion-notifications and this requestId as taskId. Phases are prepared, save-sent and done. A prepared resume may send the first PUT after guards. Once save-sent is persisted, resume only reconciles by reading the known fixed event; it never repeats a sent PUT. Do not use a new requestId to bypass an uncertain operation.
Completed results have PROMOTION_NOTIFICATIONS_COMPLETE; incomplete results have PROMOTION_NOTIFICATIONS_PENDING. They include phase, historical, noop, writeAcknowledged, writesPerformed and, when saved, the two settings. lastResult explains a pending failure. writesPerformed is false for a no-op/prepared phase, true after the transport returns its accepted acknowledgement, otherwise null; null is uncertainty, not evidence no effect occurred. writeAcknowledged can be true even when subsequent receipt-field validation fails; it is not proof of a valid saved record. An error after save-sent reports outcome:unknown unless an explicit rejected outcome is known, including post-dispatch authentication failures. It must not be read as not_sent. A pending context/permission error may omit normal task content and report journalPersistence.saved instead. A persistence failure must not be treated as a durable recovery guarantee.
Fresh completed verification has confirmed:true and storedCompletion:true. Replaying a completed apply, status or completed resume returns historical:true and confirmed:false; it does not refresh or rewrite completed state. Use get for a new observation. notificationDeliveryVerified, causalityVerified and atomicSnapshot remain false even when matching current state completes recovery.
Reusing a requestId with different input returns REQUEST_ID_CONFLICT. Invalid/inconsistent stored journals return TASK_STATE_UNVERIFIED, and missing tasks return TASK_NOT_FOUND. Checked journal identity, request fingerprint, original revision and progress must agree; do not edit a journal to force success. Missing service configuration, authentication or foreign channel state must be resolved without guessing an origin or resending an uncertain save.
The exact accessible console destination is /channels/{channelCode}/promotion-campaign/notification-settings. Demo omits live destinations. There is no arbitrary event initialization, limited-time-start writer, app installation, consent change or financial action.
Saved plan replay
An existing matching task's dryRun returns a historical saved plan, with
historical:true, replayed:true, phase, taskId and false current verification.
It has no current console/action link and does not reread target business state,
repeat effects or change the journal. Follow nextStep for a current observation;
retain the original task ID for status/resume or uncertain-outcome inspection.
A new task keeps normal fresh-plan validation.
nextStep: promotion_notifications_get retains the original channel and fixed coupon event. The plan code remains PROMOTION_NOTIFICATIONS_PLAN; existingPhase is retained alongside phase and taskId. No current event/app-indicator read or message delivery occurs. There is no notification activation, installation or financial action.
Saved record identity
A missing record and a malformed existing record are different. If the stored
JSON is null, false, zero or an empty string, apply/dry-run/status/resume return
TASK_STATE_UNVERIFIED; they do not start new work, replace the record, or report
it as absent. Preserve it for operator inspection. Do not delete/edit the record
or generate a new request ID to bypass the error. Valid stored work and genuinely
missing task IDs keep the recovery behavior described above.