Repository navigation
AL-2104 — [P28-F14] Follow the same reported gift through recording and handling #2104
Description
AL-2104 — [P28-F14] Follow the same reported gift through recording and handling
Approved implementation ticket. Plan key: P28-F14. Work only after its real blockers and exact qualification gates are satisfied. Publication does not establish implementation, qualification or activation.
Parent
Phase 28 implementation specification #2064. Canonical specification commit e93e2e9958f32380e28ec0e005d7ef3ee24f7e7a; documentation PR #2065. The full published spec and comments were verified against the canonical source. Preserve the parent’s body, title, labels, state and comments; new native child/dependency relationships are metadata only.
What to build
Maya and Finance see the same intake progress into exact recorded money while unresolved physical handling and duplicate/already-recorded resolution remain truthful and independently owned.
Source of truth and acceptance ownership
- Finance Stewardship: part 1 · part 2
- Common safeguards and proof: part 1 · part 2
- Owner reconciliation and later-decision precedence: part 1
- Evidence and existing seam limits: part 1
Primary requirement references: REQ28-F31. Stories: US28-F31.
The checked criteria below are this ticket’s primary trace ownership, not a relaxation of the full normative contract. For a requirement split across tickets, this ticket delivers its named behavior and enforces applicable invariants immediately; the other ticket’s full scenario is not claimed complete early. All source-specific state/control, numeric-boundary, owner, monitoring and exclusion sections in the linked volume remain binding. Preserve settled D1–D50 and C01–C20; do not reopen them or import contradictory vendor conventions.
Acceptance criteria
- AC28-F31-01 — Recording the gift resolves exactly covered recording work while Handling still needed remains if applicable; completing transfer first does not post money.
- AC28-F31-02 — Exactly reconciling an already-posted check links the result and preserves both report origins without creating another contribution, commitment application or receipt.
- AC28-F31-03 — Return, loss, dispute, rejection, duplicate or mistake retains attributable history and precise remaining work; reversal or NSF does not resurrect physical possession.
Implementation decisions and slice boundaries
- Finance continuation must not re-enter/repost/reissue receipt; split/move/carryforward preserves lineage.
- Same amount/name/date/check number is insufficient; independent reports keep origins until source-qualified resolution.
- No universal one-report/one-header cardinality; financial recording does not certify transfer/deposit/clearance; reversal does not recreate possession.
Binding normative decisions
REQ28-F31 — Continue the same intake and keep handling separate
Story US28-F31: As finance staff, I want to continue the same reported item,
so that Maya does not re-enter it and recording money does not hide unfinished
physical handling.
Decision basis: D33 C12–C16; P27 F61–F67.
Finance SHALL continue or exactly reconcile the same Phase 15 intake through its
qualified batch/commit path. Lineage SHALL survive splits, moves, carry-forward,
corrections and linking an already-posted result without reposting or reissuing
receipts. Similar amount, name, date or check number is not exact reconciliation.
Independent reports retain separate origins until owner-qualified duplicate
resolution; neither universal one-report/one-header cardinality nor a general
matching engine is presumed. Financial recording, reported possession/transfer,
deposit, clearance, commitment and fulfillment SHALL remain separate. Each
accepted report SHALL have qualified next responsibility or an owned unresolved
exception using existing source work, not a guessed staff audience.
Testing, migration and operating obligations
Every reached layer belongs to this slice: actual source persistence and upgrade behavior, canonical command/query, permitted UI, and executed proof. Reuse native owners, shared Base UI/base-maia components and the established architecture. No parallel CRM, Tasks, consent, sender, permission or retention engine.
Before affected activation, prove current trusted tenant/actor/subject/role and fine-grained purpose/field authority, effective old-row/new-row RLS and grants, same-tenant relationships, applicable uniqueness/history/delete constraints, and every reached view/RPC/storage/service path. Caller identifiers and metadata are never authority. Source revision and privacy checks precede disclosure and are repeated after ordering waits where required.
Implement semantic idempotency, required source history/receipt and durable secondary intent, truthful partial/unknown/lost-response recovery, context-switch/revocation fencing, temporal boundaries and correction lineage. Preserve each adopted default and exact Off/new-work/accepted-work/reenable behavior. Never infer consent, money, completion or actual interaction from a presentation action.
Test through existing Playwright whole-user journeys as the primary boundary; use canonical API Vitest tests, real PostgreSQL roles and at least two concurrent sessions, qualified Auth and existing provider-contract seams where the browser cannot prove the guarantee. Reuse existing account-frame/session-guard, task-recovery, roster/redaction, operation-handler, dispatch/restriction, field-policy SQL and replay proof patterns while retaining their demo/mock limits. No new test framework and no skipped mandatory fixture may count as a pass.
Use persistent truthful outcomes, clear context and next steps, keyboard/screen-reader/touch/list equivalents, narrow-screen reflow, localization, safe international data and honest interrupted-connection recovery. Inventory and fence reached legacy writers/readers, preserve lawful history and unresolved obligations, and qualify mixed versions, restore, retention and containment before activation. Adopt existing source-specific signals/thresholds/owners/responses and measured cohort budgets; do not invent a numeric SLO or defer correctness to monitoring.
Shared requirements Q01–Q27 apply where relevant to every ticket. R43, F41–F44, M41–M42 and C49 apply to their affected lanes. W05/W06 aggregate composed evidence; they are not permission to defer any slice’s safeguards.
Exact consumed contracts
- Consume AL-2021: Continue the same reported gift through finance recording (P27-F22) #2021 single-intake batch/commit lineage, AL-2026: Resolve duplicate reports and existing recorded gifts by exact lineage (P27-F23) #2026 exact already-posted/duplicate resolution and AL-2027: Track physical handling and truthful exceptions independently of posting (P27-F24) #2027 handling/exception source outcomes. Add P28 current-authorized status/navigation integration only, not new engines.
- Record-before-transfer, transfer-before-record, already-posted link, two reports same item, split/carryforward and handling exception; only qualified covered work closes.
Related source owners (references are not automatically blockers): #2021, #2026, #2027. Current source contract takes precedence over superseded issue wording.
Blocked by
A blocker’s closure is not runtime qualification: verify the exact consumed revision and executed proof. Keep independently qualified capabilities useful while an affected effect is unavailable. Integrate the approved specification into the implementation base; the open documentation PR is not evidence that these features exist.
Related approved slices
Short ticket keys in implementation notes resolve here. Published requirement, acceptance, story, decision and completeness codes retain their original meanings.
- P28-F13 / #2103 — Offer optional missionary receipt reporting with reliable status
- P28-W05 / #2151 — Recover and contain the composed workspace after restore
- P28-W06 / #2152 — Qualify whole journeys and the exact Phase 28 release cohort
The full execution index and primary-acceptance ownership map are published with the Phase 28 release-qualification ticket. Every shared safeguard remains mandatory within its implementing slice.
Activity
[-][P28-F14] Follow the same reported gift through recording and handling[/-][+]AL-2104 — [P28-F14] Follow the same reported gift through recording and handling[/+]II-ricky-bobby-II commentedon Oct 25, 2026
Spec r1, Scope, Oct 24, 2026, pending Samson approval
Status: r1 for #2104 (P28 F14), base
develop6ac594d. The issue body is the source of intent; this spec is the build contract. It's within both caps, so no split. F14 adds only status and navigation for a reported gift. Maya and finance see the same report move through recording, already-recorded or duplicate resolution, and physical handling, as separate facts from their owners. F14 adds no engine. Natively blocked by #2103, #2026 and #2027, which matches the body's "Blocked by" list. The body also consumes #2021, which isn't natively linked here but blocks both #2026 and #2027 natively. By slice name it needs F13-a and F13-b. All three owners (#2021, #2026, #2027) are open and unbuilt, so the production outcome adapter isunavailable(OQ1). Context read: #2064 (finance stewardship, common safeguards, owner reconciliation and evidence comments), the #2152 index comments, the #2103 r1 spec, the #2009, #2021, #2026 and #2027 bodies, andfinance-stewardship.md(REQ28-F31 :800-828, F41–F44 :1081-1231).§0 What I checked
6ac594d9(Oct 24, 2:34 PM PT). AL-2104 — [P28-F14] Follow the same reported gift through recording and handling #2104's body and its 3 ACs. The issue has 0 comments, so this is r1.apps/admin/app/(app)/contributions/[contributionId]/page.tsxwithcontribution-detail-sheet.tsxanddetail-read-model.ts;batch-continuation.tsexists for staff batches. Nothing on develop links a contribution to a reported intake.offline_report_submissionswithintake_ref, and the owner's status read (AL-2103 — [P28-F13] Offer optional missionary receipt reporting with reliable status #2103 r1).§1 Intended behavior
Maya reported John's envelope on Oct 19. Finance records it on Oct 21 as a 50.00 USD contribution. Maya's report now shows Recorded by finance and, on a separate line, Handling still needed until finance marks the envelope deposited. Finance, on that contribution, sees "Reported by Maya Chen, Oct 19" and can open the report. If finance instead links the report to a check already posted last week, both show the link and nothing new is recorded.
Outcome port (OQ1).
ReportedGiftOutcomeSource.outcome(intake_ref)returns two independent parts, each with its owner's revision:awaiting_finance,recorded{contributionRefs[], lineage}(splits, moves and carry-forward keep the lineage),linked_existing{contributionRef, otherOrigins[]},duplicate_of{intakeRef},rejected{reason},corrected{reversal|nsf|mistake, contributionRefs[]};handling_needed,transferred,deposited,exception{returned|lost|disputed|rejected},not_applicable.It also has
reportsForContribution(contributionRef). Each owner implements its part; until then the only production adapter isunavailable, and F14 shows "Status not available yet".One composed status.
reported_gift_status(report_ids)is a guarded read function. It returns F13's submission plus the port's two parts, filtered by current authority: the recorder, a holder of the current AL-2069 — [P28-W01] Enter and switch the exact authorized workspace context #2069 context for that subject, orfinance:manage_contributions. Revoked context removes the read.Maya's view. F13-b's list gains a detail panel with a Recording line and a Handling line, each with its date and owner's wording. Exceptions show their reason and the remaining work ("Returned to donor on Oct 22; nothing left to do"). F14 never resolves or closes work itself; covered recording work closes in AL-2021: Continue the same reported gift through finance recording (P27-F22) #2021's owner.
Finance view. The admin contribution detail sheet gains a "Reported origins" block from
reportsForContribution: each origin's recorder and date, with a link to the report. Two independent reports linked to one contribution both appear.No matching. F14 never suggests a match, a duplicate or a link from a similar amount, name, date or check number.
Separate facts. Recording doesn't mark handling done, and transfer or deposit doesn't show Recorded. A reversal or NSF shows on the Recording line only; the Handling line never returns to "In hand".
Threat model
Non-goals: finance recording, linking, duplicate resolution or handling commands; a matching engine; changes to money, receipts or fulfillment; notices.
§2 In scope
unavailableproduction adapter and the test adapter.§3 Out of scope
donations, receipts, batches or fulfillment.docs/ci.mdand the byte-pinned Phase 28 PRD archive.§4 Criteria
Tags: [A] Acceptance-owned test, [B] Builder test, [S] static, [R] real PostgreSQL (#2250's step). The owners are F14's test adapter, driven through event sequences.
A1 [A][R] Record and handle separately (AC28-F31-01).
A2 [A][R] Already posted (AC28-F31-02).
A3 [A][R] Exceptions and corrections (AC28-F31-03).
B1 [B][R] Current authority. After Maya's AL-2069 — [P28-W01] Enter and switch the exact authorized workspace context #2069 context for the subject is revoked, she can still read her own reports as recorder, a context holder can't, and a finance user can.
S1 [S]
donations, receipts, batches or fulfillment.GUARDED_RPCS.C1 [A] Journey. On a real stack with the test adapter, at a phone viewport for Maya and desktop for finance, keyboard-only and with screen-reader names checked:
Axe clean.
Handback
tenant_guardschema and role ([Phase 4] #505 slice 1/4: withTenant tenant guard (non-owner, non-BYPASSRLS role, fail-closed) #2250 live), whether any AL-2021: Continue the same reported gift through finance recording (P27-F22) #2021, AL-2026: Resolve duplicate reports and existing recorded gifts by exact lineage (P27-F23) #2026 or AL-2027: Track physical handling and truthful exceptions independently of posting (P27-F24) #2027 object exists (OQ1), and that none of the new names exist. Any mismatch goes to Samson. No writes.bun run verify:shadscanpasses.§5 Mapping
§6 Declared edits
supabase/migrations/<VERSION>_reported_gift_status.sql+ rollbackpackages/api/src/offline-reporting/outcomes/**(new)unavailableadapter, test adapter, status servicepackages/api/src/missionary-portal/offline-reports/route.ts(F13-a)packages/api/src/admin/contribution-operations/detail-read-model.tsapps/missionary/app/donors/**(F13-b section)apps/admin/app/(app)/contributions/contribution-detail-sheet.tsxpackages/api/src/identity/tenant-guard.tsGUARDED_RPCSentry onlydocs/features/offline-reporting/reported-gift-status.md(new)tooling/eslint-config/suppressions.json,tooling/shadscan/findings.jsontests/unit/acceptance/issue-2104/**tests/e2e/reported-gift-status.spec.ts(new)§7 Pins this change touches
<VERSION>_offline_receipt_reporting.sql(#2103)packages/api/src/admin/contribution-operations/detail-read-model.tstests/unit/apps/missionary/missionary-navigation-search.test.tsx:67-75tooling/eslint-config/suppressions.json:1181-1188scripts/verify/shadscan.mjs(ci.yml "Enforce complete Shadscan app floors and reviewed findings")issue-2250/static.acceptance.test.tsissue-2155/…:257-280(bc8d57a2be564d61),issue-2201/…:166-178(1078832efaff84e6)docs/ci.mdbyte pinissue-2250prefix pin and #2304verify:phase28-spec(ci.yml :91), 911 files underdocs/features/missionary-workspace-depth/grill/**§8 CI
No new CI step and no
docs/ci.mdedit under the OQ3 default. Real-DB tests run in "Verify tenant isolation (real stack)" (bun x vitest run realstack.acceptance, envTENANT_GUARD_IT_ADMIN_DATABASE_URL). Everything else runs in existing ci.yml steps:bun run format:check, "Run affected lint on pull requests", "Run affected typecheck on pull requests", "Run unit tests", "Run acceptance tests", Shadscan. Playwright C1 runs locally and is recorded (H3).§9 Data, grants, person_id and donor spine
reported_gift_status(report_ids uuid[])is a SECURITY DEFINER guarded function over F13's submissions that applies current authority; outcome parts come from the port.anon/authenticated/service_roletable privileges change. No RLS policy on an existing table is dropped or recreated.profiles.person_idnormissionaries.person_id. No [Security] missionaries.person_id column-grant fix: anon cross-tenant read + select("*") routes (follow-up from #2256) #2268 block is needed.§10 Size
Within both caps; no split.
§11 Dependencies and migration
<VERSION>_reported_gift_status.sql, prefix 20261026, after F13-a's.20261024120000), the 43 remote branches after a full fetch (only20261024140000,20261024141000and20261024150000are added off develop), open PRs (feat(identity): normalize identity emails and donor keys (#2254) #2291:20261024140000,20261024141000; fix(donations): read donors through the real FK and stop swallowing donations errors (#2269) #2319 and Modernize Sentry, content, email and browser payment SDKs #2214 add none), the reserved list (20261024: 130000 … 235900; 20261025: 010000 … 235700 as listed in the AL-2095 — [P28-F05] Copy a reviewed approved Support Plan into a goal #2095–AL-2099 — [P28-F09] Enable follow-up for a confirmed specific one-time need #2099 r1 specs; 20261026: 010000, 020000, 021000, 030000, 031000, 032000, 040000, 050000), or the other requests in the AL-2100 — [P28-F10] Follow up on every source-certified positive excess #2100–AL-2104 — [P28-F14] Follow the same reported gift through recording and handling #2104 r1 specs.20261026100000. Version is reserved; Samson may renumber at handback to keep merge order.§12 Open Questions (proposed defaults in bold)
unavailable, so reports show "Status not available yet" beyond Awaiting finance and the finance block stays empty. Wiring each owner's part, plus A1–A3 rerun against it, is a handback gate on that owner. No status is derived fromdonations, batches or similarity. This is a departure that narrows. The alternative is holding F14 until all three merge.finance:manage_contributions. Flagged for Samson.test-e2estep "Reported gift status journeys" with run textnode scripts/run-with-ci-env.mjs -- node node_modules/@playwright/test/cli.js test -c playwright.missionary.config.ts tests/e2e/reported-gift-status.spec.ts --workers=1, which re-pins both fingerprints and byte-pinsdocs/ci.md(with docs/ci.md:35 and :366 still call test-e2e informational on develop; update with the #2180 byte pin in one change (follow-up to #2161) #2304).§13 Deploy
20260515025814–20261024120000(25 applied, newest20260513173938). The dev project is 55 behind, with four20261001*collisions and an extra20260519233845. Develop's catalog is not assumed live anywhere.unavailable. (5) Each owner's real part as it merges.§14 Flags
II-ricky-bobby-II commentedon Oct 25, 2026
Approval (Samson, factory lead, Oct 24, 2026, 9:54 PM PT), relayed by Scope:
20261026100000_reported_gift_status.sql, which sorts after F13-a's20261026090000(AL-2103 — [P28-F13] Offer optional missionary receipt reporting with reliable status #2103). Version is reserved; Samson may renumber at handback to keep merge order.unavailable, so outcomes are unavailable until AL-2021: Continue the same reported gift through finance recording (P27-F22) #2021, AL-2026: Resolve duplicate reports and existing recorded gifts by exact lineage (P27-F23) #2026 and AL-2027: Track physical handling and truthful exceptions independently of posting (P27-F24) #2027 exist: reports show "Status not available yet" beyond Awaiting finance and the finance block stays empty. Wiring each owner's part, plus A1–A3 rerun against it, is a handback gate Samson checks. No status is derived fromdonations, batches or similarity.finance:manage_contributionsin that tenant. Nobody else can read it: not a AL-2069 — [P28-W01] Enter and switch the exact authorized workspace context #2069 context holder for the subject, and not a missionary unless they hold that permission. This replaces r1's context-holder reader (§1 item 2) and B1.reported_gift_statusand the missionary status routes return a report's status to its submitter (also after the submitter's AL-2069 — [P28-W01] Enter and switch the exact authorized workspace context #2069 context for the subject is revoked) and to a profile holdingfinance:manage_contributionsin the report's tenant. They return nothing for that report to another missionary, to a current AL-2069 — [P28-W01] Enter and switch the exact authorized workspace context #2069 context holder for the subject who isn't the submitter, to any profile without that permission, and to a profile holding it only in another tenant.docs/ci.mdedit; the Playwright run is local and recorded (H3).