Stateful journeys/Capture & Feedback
hybridfeedback report aggregate

iOS feedback report and review

Open one feedback UI generation, normalize a bounded report candidate, commit an application submission, reconcile provider acknowledgement separately, review evidence, and close only with an attributable resolution.

Every state means something operational

A state is not a UI label. It determines which actor may act, what evidence exists, what may be retried, and how recovery proceeds.

01
draft

Draft

The host owns purpose, category intent, reporter consent, location context, and retention policy before opening provider UI.

02
capturing

Capturing

One presented feedback generation owns UI, focus, permission prompts, callbacks, cancellation, and cleanup.

03
candidate

Candidate

The adapter copied allow-listed category, bounded description, normalized location identity, and attachment references into application state.

04
submitted

Submitted

The contributor deliberately committed one immutable application report with idempotency, expected version, audit, and outbox evidence.

05
provider_pending

Provider pending

A separately selected adapter has an acknowledged or unknown provider submission outcome that requires reconciliation.

06
review_pending

Review pending

Submission evidence and any provider acknowledgement await an attributable quality, privacy, duplication, or policy decision.

07
resolvedterminal

Resolved

An authorized reviewer recorded the disposition and exact supporting evidence, with provider resolution only when independently proven.

08
rejectedterminal

Rejected

Review found the report invalid, duplicate, out of scope, or unsupported and preserved the reason plus evidence lineage.

09
cancelledterminal

Cancelled

The contributor ended the active attempt before application submission and every UI resource was disposed.

Transitions are attributable and retry-safe

Commands express intent. The aggregate validates current state and invariants, commits one new version, and emits a fact in the same transaction.

Command & actorAllowed fromResultIdempotency strategy
create_reportHost application
new aggregate
draftfeedback_report.created

One external case and purpose maps to one draft aggregate across retries.

open_feedback_uiContributor
draft
capturingfeedback_report.ui_opened

One command creates one active presentation generation and ownership record.

receive_candidateMappls Feedback Kit
capturing
candidatefeedback_report.candidate_received

Accept one terminal allow-listed result for the active generation and normalized content hash.

commit_submissionContributor
candidate
submittedfeedback_report.submitted

Commit report, processed command, audit event, and outbox atomically under expected version.

request_provider_submissionProvider adapter
submitted
provider_pendingfeedback_report.provider_requested

Persist application command and provider request identities before attempting the separately approved contract.

queue_reviewHost application
submittedprovider_pending
review_pendingfeedback_report.review_queued

One immutable submission revision creates at most one active review case.

resolve_reportOperations reviewer
review_pending
resolvedfeedback_report.resolved

Bind actor, disposition, exact evidence revision, audit, and notification outbox in one commit.

reject_reportOperations reviewer
review_pending
rejectedfeedback_report.rejected

One reviewer decision applies once to one immutable report revision.

cancel_reportContributor
draftcapturingcandidate
cancelledfeedback_report.cancelled

Repeated cancellation returns the terminal result while cleanup remains exactly-once-in-effect.

Persist enough to recover and explain

Keep provider responses, business identity, state, events, and side-effect delivery distinct so each can be reconciled safely.

Feedback report

Host-owned intent, lifecycle state, current immutable submission, and optimistic version.

reportIdexternalIdpurposestatesubmissionRevisionversion

Submission revision

Allow-listed category, description, normalized location, consent, attachment references, and content identity.

submissionIdreportIdcontentHashlocationIdentityconsentVersionsubmittedAt

Provider attempt

Separately selected contract request, acknowledgement, unknown outcome, and reconciliation evidence.

attemptIdsubmissionIdproviderRequestIdstatusreceiptReferencelastCheckedAt

Review decision

Attributable disposition against one exact submission and provider-evidence revision.

reviewIdsubmissionIdreviewerdecisionreasondecidedAt

Protect truth at the aggregate boundary

  1. 1

    A UI callback creates only a candidate; it cannot prove application submission, provider receipt, publication, or resolution.

  2. 2

    One presentation generation produces at most one accepted terminal result and all late callbacks are ignored.

  3. 3

    Every submission revision is immutable and content-hashed; rework creates a new linked attempt.

  4. 4

    Provider acknowledgement and reviewer disposition are separate attributable records.

  5. 5

    Credentials, view controllers, delegates, attachment bodies, and opaque provider payloads never enter durable state.

  6. 6

    Idempotency, optimistic concurrency, audit, outbox, privacy purpose, retention, and redaction apply to every committed transition.

Know what to do before it breaks

Recovery changes durable truth only through the same rules as normal operation. A timeout is an unknown outcome, not evidence that nothing happened.

UI is denied, blocked, dismissed, or times out

Detect

The active generation ends without a valid allow-listed candidate.

Recover

Dispose, restore focus, retain the draft, record a safe reason, and offer a purpose-appropriate manual fallback.

A callback arrives after replacement or disposal

Detect

Its presentation generation differs from the report's active generation.

Recover

Ignore it, release its resources, and keep current report state unchanged.

Provider submission response is lost

Detect

The application has a durable request identity but no authoritative acknowledgement.

Recover

Remain provider pending and reconcile under the same identity before any retry.

Review rejects or requests corrected evidence

Detect

Policy, privacy, duplication, location, or content validation fails for the immutable revision.

Recover

Preserve the decision and original revision; create a new linked attempt rather than editing history.

Observe the journey, not merely HTTP status

Measure state age, event health, retries, reconciliation, and sensitive-data access alongside latency and error rate.

  • UI open, activation, cancellation, candidate, and terminal outcome by released component version
  • Schema, size, stale-generation, duplicate, and late-callback rejection
  • Draft-to-candidate and candidate-to-submit conversion
  • Provider pending age, acknowledgement, unknown outcome, and reconciliation
  • Review queue age, disposition, rework, and duplicate rate
  • Idempotency replay, version conflict, audit, and outbox health
  • Privacy retention and redaction completion without attachment-body logging

Contracts, repositories, workshops, tutorials, and runnable evidence

Only explicitly indexed evidence is linked. Empty sections are not backfilled with invented endpoints or package names.