Home / Baseline

Seraya Psikologi — Implementation Guide

Status

Implementation Baseline — boleh dipakai sebagai pegangan implementasi.

Source baseline:

Dokumen ini sengaja tidak menunggu semua jawaban pada dua PRD form. Keputusan yang sudah confirmed menjadi aturan implementasi. Hal yang belum diputuskan ditulis sebagai TBC dan harus diisolasi di configuration/adapter/policy seam, bukan ditebak sebagai keputusan bisnis.

1. Cara membaca dokumen ini

1.1 Authority order

Jika terjadi perbedaan:

  1. Accepted ADR terbaru;
  2. bagian canonical pada Technical PRD revision 168;
  3. dokumen ini;
  4. catatan/open question lama.

Jika konflik belum terselesaikan, jangan memilih diam-diam. Buat TBC atau blocker ticket dan minta keputusan owner.

1.2 Label

1.3 Prinsip utama

2. Launch scope

2.1 In scope — CONFIRMED

2.2 Out of scope — DEFERRED

3. Actors and authorization

3.1 Active roles — CONFIRMED

Role Scope
Visitor Public pages, catalog, program content, published availability
Client/Guest Own verified Booking/PackagePurchase melalui scoped ClientAccess
Psychologist Own profile/availability input, assigned operational appointments, initial completed/no_show
Admin Operational owner: catalog, availability override, booking, payment/refund, cancellation, privacy, staff, audit
Editor DEFERRED; jangan aktifkan di launch

3.2 Staff authentication — CONFIRMED

3.3 ClientAccess — CONFIRMED baseline

TBC-ACCESS-01: rate limit detail, recovery flow, re-authentication, session revocation, dan abuse response. Isolasi di ClientAccessPolicy/SessionPolicy; jangan campur dengan domain Booking.

4. Domain model minimum

Implementasi awal harus memiliki boundary/module untuk entity berikut. Nama tabel boleh berbeda, tetapi vocabulary domain harus dipertahankan.

4.1 Catalog and capacity

4.2 Transaction and service delivery

4.3 Financial and policy records

4.4 Access, content, and notifications

5. Module seams

Implementasi boleh berupa satu Worker/modular monolith. Jangan membocorkan detail provider atau SQL ke semua caller. Gunakan module interface yang dalam dan kecil.

5.1 Required modules

5.2 Minimum command interface

Transport route dan exact payload shape adalah TBC-API-01. Command semantics berikut sudah confirmed:

Setiap command harus menerima atau menghasilkan:

6. State machines and invariants

6.1 Booking and Appointment — CONFIRMED

Create Booking
  → pending_payment + SlotHold(active)
  → confirmed hanya setelah verified PaymentEvent(success)

Payment/hold failure
  → expired | failed

Hold expiry + later verified success
  → Payment=paid_late/reconciliation-required
  → reacquire original slot atomically jika masih free
  → jika tidak free: no automatic alternate Appointment

Invariants:

6.1.1 Appointment state machine — CONFIRMED (ADR 0092)

confirmed
  → (auto, T+15m, no client_arrived) → no_show        [entitlement consumed, checkpoint locked]
  → (client_arrived ≤ T+15m) → in_progress              [auto-checkpoint cancelled]
  → (client_arrived > T+15m) → in_progress              [auto-checkpoint already locked; outcome_final = no_show_late]
  → (CancellationDecision approve) → cancelled          [entitlement restored jika valid]

in_progress
  → (session_ended ≥ 60m attended) → completed          [entitlement consumed]
  → (session_ended 1–59m attended) → completed_partial  [entitlement consumed; compensation token eligible]
  → (CancellationDecision approve) → cancelled          [entitlement restored jika valid]

Final outcome enum (lima nilai):

Outcome Trigger Entitlement Notification
completed psikolog/Admin, end-of-session, attended ≥60m consumed email outcome_finalized
completed_partial psikolog/Admin, end-of-session, attended 1–59m consumed email outcome_finalized
no_show system auto T+15m, atau psikolog/Admin override consumed email no_show_recorded + admin alert
no_show_late psikolog/Admin, client_arrived > T+15m dan sesi berjalan ≥1m consumed email outcome_finalized
cancelled CancellationDecision approve restored jika valid sesuai cancellation flow

6.1.2 Late-arrival handling — CONFIRMED (ADR 0092)

6.1.3 Outcome correction — CONFIRMED (ADR 0092, ADR 0054)

6.1.4 Notification templates untuk outcome events

6.2 Package and entitlement — CONFIRMED (ADR 0092, ADR 0095)

TBC-PACKAGE-01: exact package validity/expiry calendar semantics, reminder offset, dan package availability resolution SLA. Simpan policy sebagai snapshot pada PackagePurchase; jangan membaca ulang catalog saat entitlement lama diproses.

6.3 Cancellation — CONFIRMED

Canonical matrix lives in ADR 0095-package-cancellation-matrix.md. Ringkasan operasional:

Admin WhatsApp (public channel)
  → CancellationRequest (at most one open per target; ADR 0095 §1.2)
  → state: open
  → race resolution per ADR 0095 §2:
      R1: outcome completed lands first  → request auto_resolved
      R2: outcome no_show lands first     → request auto_resolved
      R3: approval lands first            → outcome marking blocked afterwards
      R4: RescheduleAction lands          → request rebinds to replacement
  → Admin approve | deny (DecideCancellation)

approve atomically (per target; see ADR 0095 §3):
  target = appointment:
    Appointment → cancelled
    CapacityReservation → cancelled (release_reason = appointment_cancelled)
    linked SessionEntitlement → restored if valid, else closed
    eligible future slot → available

  target = package_purchase (single transaction):
    all future non-terminal Appointments → cancelled
    all CapacityReservations for those Appointments → cancelled
    all unused SessionEntitlements with valid_until >= now → closed_restored_by_cancellation
    all other unused SessionEntitlements → closed_cancelled_with_package
    PackagePurchase.state → closed_by_cancellation (terminal, no re-open)
    PackageValidity.valid_until unchanged
    consumed/expired entitlements untouched (refund is separate)

  target = booking (single-session single-Appointment):
    identical to target = appointment

den y:
  no mutation to Booking/Appointment/slot/entitlement/Payment

correction (repeat rule, ADR 0095 §4):
  new CancellationRequest with correction_of = previous_decision_id
  new Decision applies §3 effects afresh (idempotent on already-cancelled)
  original Decision marked superseded_by, remains immutable history

6.4 Refund — CONFIRMED

6.5 Availability — CONFIRMED

7. Payment implementation — WhatsApp manual flow (per ADR 0097)

Launch path tidak menggunakan payment gateway. Klien menerima invoice PDF + plain text via WhatsApp, membayar manual (bank transfer / VA / QRIS), mengirim bukti ke Admin WhatsApp, dan Admin menandai Booking sebagai paid via tombol "Mark as paid" di Admin workspace. Refund tetap off-platform (ADR 0077 vocabulary). Midtrans Snap deferred post-MVP (ADR 0068 superseded).

7.0 Launch payment model — CONFIRMED (per ADR 0097)

7.1 Invoice generation — CONFIRMED

Invoice adalah immutable output per Booking, generated on-demand dari OfferSnapshot + payment instructions di Admin CMS config. Dua format tersedia:

7.1.1 PDF invoice

Server-side rendered via template engine (puppeteer / pdfkit / equivalent). Fields:

Tidak termasuk: clinical information, contact phone (jika ada), BookingParticipant detail untuk couple — hanya identitas payer.

PDF di-cache per booking_id. Signed URL dengan retention mengikuti Booking (12 bulan setelah last active service, ADR 0083). Regen hanya jika OfferSnapshot berubah (tidak seharusnya — snapshot immutable).

7.1.2 Plain text invoice

Static template dengan field substitution. Format WhatsApp-friendly (markdown lite: *bold*, list):

*SERAYA PSIKOLOGI — INVOICE*

Invoice ID: INV-<booking_id>
Issued: <YYYY-MM-DD HH:mm WIB>
Due: <YYYY-MM-DD HH:mm WIB> (24 jam)

Client: <client_name>
Email: <client_email>
Booking ID: <booking_id>
Service: <service_offering_name>
Mode: <online|offline>
Sessions: <1 | 2 | 3 | couple-3>
Amount: Rp <amount formatted>

*Payment Instructions*
Method: <bank_transfer|va|qris_manual>
<bank_name>: <account_number>
a/n: <account_holder>
<VA number>  (jika applicable)
<QRIS image: <url>>  (jika applicable)

Setelah membayar, mohon kirim bukti transfer ke WhatsApp Admin: wa.me/<admin_number>

Pembayaran akan dikonfirmasi dalam 1 hari kerja.

7.1.3 Generation rules

7.1.4 Invoice buttons di client confirmation page

7.2 Mark-as-paid workflow (Admin workspace) — CONFIRMED

7.2.1 MarkAsPaid command

Aspect Spec
Input booking_id, payment_method (bank_transfer | va | qris_manual), amount_idr, evidence_url (nullable, signed URL ke uploaded screenshot di R2), evidence_note (nullable, max 500 char), actor_membership_id
Precondition booking.status IN ('pending_manual_payment', 'awaiting_confirmation'); payment.status = 'pending_manual_payment'; payment.provider = 'manual_whatsapp'; tidak ada payment_proof existing dengan status = 'verified' untuk booking_id
Authorization Admin only (StaffMembership.role = 'admin')
Idempotency idempotency_key = booking_id; re-submit dengan key sama = no-op + return existing payment_proof.id
Atomic effects (single transaction) (1) INSERT INTO payment_proof ...; (2) UPDATE payment SET status = 'paid', settled_at = now(); (3) UPDATE booking SET status = 'confirmed'; (4) untuk package: slot reacquire attempt per ADR 0091 + PackagePurchase + ordered SessionEntitlement (SessionEntitlement #1.state = 'scheduled' jika reacquire sukses, 'pending_schedule' jika gagal); (5) INSERT INTO audit_record (action mark_as_paid, reason manual_whatsapp_verified); (6) outbox: booking_confirmed + payment_received email
Output payment_proof_id, booking.status = 'confirmed', payment.status = 'paid', payment.settled_at

7.2.2 RejectPaymentProof command

Aspect Spec
Input payment_proof_id, reason_note (required, max 500 char), actor_membership_id
Precondition payment_proof.status = 'verified'; booking.status = 'confirmed'
Effect payment_proof.status = 'rejected' (audit-logged sebagai payment_proof_rejected). Tidak mengubah booking.status atau payment.status (financial tetap settled).
Follow-up Admin minta bukti baru via WhatsApp; jika diterima, Admin create new payment_proof row dengan correction_of = <original_row_id>. Original immutable history.

7.2.3 MarkAwaitingConfirmation command

Aspect Spec
Input booking_id, actor_membership_id
Precondition booking.status = 'pending_manual_payment'
Effect booking.status = 'awaiting_confirmation'. SlotHold released (slot kembali ke available pool). Tidak menggerakkan payment.
Use case Admin melihat bukti masuk via WhatsApp, toggle state sebelum verifikasi final.

7.2.4 Booking state transitions di Admin workspace

From Event To Side effects
pending_manual_payment Client klik "Saya sudah bayar" / Admin MarkAwaitingConfirmation awaiting_confirmation SlotHold released; email reminder ke Admin admin_invoice_sent.
pending_manual_payment / awaiting_confirmation Admin MarkAsPaid confirmed payment_proof insert; Payment.status = 'paid' + settled_at; slot reacquire attempt (single-session → Appointment confirmed atau pending reconcile; package → SessionEntitlement #1 scheduled atau pending_schedule); outbox events.
pending_manual_payment / awaiting_confirmation CancellationRequest approve, atau Admin manual cancel (no proof dalam 24h window) cancelled Slot released per ADR 0095. RefundAction terpisah jika applicable (tidak ada paid amount → no refund action).

7.3 payment_proof audit — CONFIRMED

7.3.1 Schema (D1/SQLite + Postgres equivalent)

CREATE TABLE payment_proof (
  id                          TEXT PRIMARY KEY,
  booking_id                  TEXT NOT NULL REFERENCES booking(id),
  payment_method              TEXT NOT NULL CHECK (payment_method IN ('bank_transfer','va','qris_manual')),
  amount_idr                  INTEGER NOT NULL CHECK (amount_idr > 0),
  evidence_url                TEXT,
  evidence_note               TEXT,
  verified_by_membership_id   TEXT NOT NULL REFERENCES staff_membership(id),
  verified_at                 TEXT NOT NULL,
  status                      TEXT NOT NULL CHECK (status IN ('verified','rejected')),
  correction_of               TEXT REFERENCES payment_proof(id),
  created_at                  TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);

CREATE INDEX idx_payment_proof_booking_id ON payment_proof(booking_id);
CREATE INDEX idx_payment_proof_verified_at ON payment_proof(verified_at);
CREATE UNIQUE INDEX payment_proof_one_verified_per_booking
  ON payment_proof(booking_id) WHERE status = 'verified';

7.3.2 Append-only invariant

7.3.3 Idempotency

7.3.4 Audit trail integration

Setiap payment_proof row di-link ke audit_record:

payment_proof action audit_record action actor
Insert dengan status = 'verified' mark_as_paid (juga mengaudit Booking transition) admin
Update status = 'verified' → 'rejected' payment_proof_rejected admin
Insert dengan correction_of != null payment_proof_re_verified admin

7.3.5 Retention

payment_proof rows tunduk pada Payment/Refund retention policy (audit/legal). Default aman: 7 tahun (Indonesian PSAK + UU PDP untuk financial record), tapi exact duration adalah TBC-PAY-MANUAL-01 operational concern (admin/finance owner). evidence_url (uploaded screenshot) mengikuti retention yang sama; auto-delete dari R2 setelah expiry.

7.3.6 Acceptance test untuk payment_proof

  1. Single verify path: Admin Mark as Paid → payment_proof row dengan status = 'verified' → Booking confirmed → email payment_received terkirim. Atomic.
  2. Re-verify path: Admin reject existing payment_proof (status rejected) → Admin create new row dengan correction_of = <original_id> → new row verified, original immutable. Booking tetap confirmed.
  3. Idempotency: Admin double-click "Mark as Paid" → hanya satu payment_proof row, response return existing ID.
  4. Settlement uniqueness: Dua MarkAsPaid concurrent (race) → unique partial index reject salah satu + audit log mark_as_paid_duplicate_attempt. Hanya satu Payment.status = 'paid'.
  5. Slot reacquire failure (package): Admin Mark as Paid untuk package dengan slot yang sudah ter-overlap → SessionEntitlement #1.state = 'pending_schedule' + PackagePurchase.requires_first_session_scheduling = true. Admin resolution via existing reconciliation flow (ADR 0067).

7.4 Settlement uniqueness invariants — CONFIRMED (cross-reference ADR 0093)

Diperlukan oleh ADR 0093-payment-settlement-uniqueness.md. Berlaku untuk semua payment path termasuk WhatsApp manual (per ADR 0097):

7.5 ManualWhatsappAdapter contract — CONFIRMED (deferred post-MVP future gateway seam)

PaymentGatewayAdapter interface tetap dipertahankan sebagai seam untuk reaktivasi payment gateway post-MVP. Untuk launch WhatsApp manual, adapter ini di-stub dengan ManualWhatsappAdapter yang tidak memanggil provider eksternal:

interface PaymentGatewayAdapter {
  createCheckout(input: CreateCheckoutInput): Promise<CheckoutCreated>;
  verifyNotification(input: unknown): Promise<VerifiedPaymentEvent>;
  requestFullRefund(input: FullRefundInput): Promise<RefundProviderResult>;
}

interface CreateCheckoutInput {
  bookingId: string;            // = order_id, matches Booking.id
  amountCents: number;          // snapshot from OfferSnapshot.amount_cents
  currency: 'IDR';              // launch only IDR
  clientName: string;
  clientEmail: string;
  enabledMethods: ('qris' | 'va')[];  // launch subset; no e-wallet/card/OTC/BNPL
  expiresAtSeconds: number;     // matches SlotHold TTL (default 600 = 10 min)
  metadata: Record<string, string>;
}

interface CheckoutCreated {
  provider: 'manual_whatsapp';  // launch: no provider; field untuk future gateway reactivation
  providerIntentId: string;     // = Booking.id (no external transaction)
  snapToken: null;              // not applicable
  snapRedirectUrl: null;        // not applicable
  expiresAt: null;              // not applicable; manual flow has no provider-side expiry
}

interface VerifiedPaymentEvent {
  providerEventId: string;       // = payment_proof.id; idempotency key
  paymentIntentId: string;      // = providerIntentId; FK to Payment
  bookingId: string;             // = order_id; must match Booking.id
  eventType:
    | 'capture' | 'settlement'   // → MarkAsPaid verified
    | 'deny' | 'cancel' | 'expire' | 'failure'  // → Admin manual cancel
    | 'refund' | 'partial_refund' | 'chargeback'
    | 'challenge';
  grossAmountCents: number;     // must match OfferSnapshot.amount_cents
  currency: 'IDR';
  merchantId: string;           // launch: 'manual_whatsapp' (no real merchant)
  verifiedAt: string;           // ISO timestamp
  payloadHash: string;          // sha256 hex of canonical JSON payload
  rawPayloadRedacted: Record<string, unknown>;
}

interface FullRefundInput {
  paymentProviderId: string;    // launch: = booking.id (manual off-platform)
  amountCents: number;          // = captured amount for full_refund
  reasonCode: string;           // policy/version reference
  idempotencyKey: string;
}

interface RefundProviderResult {
  provider: 'manual_whatsapp';
  providerRefundId: string;
  status: 'completed' | 'pending' | 'failed';
  rawResult: Record<string, unknown>;
}

Adapter wajib:

Adapter tidak wajib:

7.6 paid_late package effects — CONFIRMED

Mengikuti ADR 0093 §5 Option A. Untuk manual flow, "late" tidak berlaku (tidak ada provider late event), tetapi slot reacquire failure pada MarkAsPaid menghasilkan efek yang sama dengan paid_late_first_session_pending:

Invariant: PackagePurchase dan SessionEntitlement rows dibuat tepat pada saat MarkAsPaid verified, bukan ditunda. Financial truth terjaga. Admin resolution tidak pernah otomatis — selalu eksplisit Admin decision (konsisten dengan ADR 0076 no auto-cutoff).

Status names paid_late_slot_reacquired / paid_late_first_session_pending dicadangkan untuk future gateway integration (saat Midtrans diaktifkan kembali, ADR baru menjelaskan mapping manual → gateway).

7.7 Duplicate Mark-as-Paid integration test (acceptance criteria #1)

Didefinisikan di ADR 0097 §7.2. Skenario test wajib:

  1. Buat Booking dengan Booking.id = X. Payment row status = 'pending_manual_payment', provider = 'manual_whatsapp'.
  2. Admin submit MarkAsPaid dengan idempotency_key = X, payment_method = 'bank_transfer', amount_idr = 235000, evidence_url = <signed_url>, evidence_note = 'BCA transfer 235000'.
  3. Admin submit MarkAsPaid ulang dengan idempotency_key = X (double-click atau network retry).
  4. Assertion: - Hanya satu payment_proof row dengan status = 'verified' (verified by unique partial index). - Submission pertama apply state transition: Booking.status = 'confirmed', Payment.status = 'paid', settled_at = now(). - Submission kedua adalah idempotency no-op (return existing payment_proof.id, no duplicate row, no duplicate email).
  5. State akhir: Booking confirmed, Payment.settled_at di-set sekali (tidak di-update kedua kali).

7.8 Slot reacquire failure integration test (acceptance criteria #2)

Didefinisikan di ADR 0097 §7.2.6.5 dan ADR 0093 §5. Skenario test wajib:

  1. Buat Booking package (couple atau individual) dengan Booking.id = Y, SlotHold aktif untuk slot S1.
  2. Klien transfer manual dan kirim bukti.
  3. Place CapacityReservation baru untuk slot lain (hold oleh Booking berbeda) untuk mensimulasikan overlap → reacquire attempt akan gagal.
  4. Admin submit MarkAsPaid untuk Y dengan bukti valid.
  5. Assertion: - Payment.status = 'paid', settled_at di-set. - Booking.status = 'confirmed'. - PackagePurchase.status = 'paid_late' (atau flag requires_first_session_scheduling = true). - SessionEntitlement #1.state = 'pending_schedule'. - SessionEntitlement #2..N.state = 'available'. - PackageValidity.validity_start = now() (bukan original booking time).
  6. Admin resolution action: ScheduleNextEntitlement untuk slot S2 (different slot).
  7. Assertion post-resolution: - SessionEntitlement #1.state = 'scheduled', linked ke Appointment di S2. - PackagePurchase.requires_first_session_scheduling = false. - Booking tetap confirmed (Payment settled, slot reacquire failure adalah orthogonal concern).

7.9 Refund off-platform — CONFIRMED

Mengikuti ADR 0077 vocabulary (full_refund / no_refund):

7.10 PRODUCTION GATE / TBC

TBC-PAY-01 (Midtrans-specific evidence gap) closed by ADR 0097 — Midtrans no longer in launch path; specific evidence not required for launch. Midtrans-specific items (merchant onboarding, sandbox test, retry/dead-letter) deferred post-MVP.

TBC-PAY-SETTLEMENT-01 tetap closed by ADR 0093; settlement uniqueness invariant berlaku untuk manual flow juga (unique partial index + application precheck).

TBC-PAY-EXPIRY-01 closed by ADR 0097 — provider expiry no longer relevant (no provider). Reopen jika Midtrans diaktifkan kembali post-MVP.

TBC-PAY-SETTLEMENT-01 closed by ADR 0093-payment-settlement-uniqueness.md; manual flow coverage di §7.4 di atas; patch section §6.3 dan §6.4 cross-reference.

8. Persistence and data rules

8.1 General

8.2 Concurrency

Minimal concurrency protections:

9. Privacy and retention

9.1 Data boundary — CONFIRMED

Allowed:

Forbidden in MVP:

9.2 Retention — CONFIRMED baseline + TBC policy values

TBC-PRIVACY-01 / PRODUCTION GATE: exact policy-controlled duration, definition/event trigger untuk last active service, exception precedence, policy owner evidence, dan execution cadence.

Saat Client/contact eligible:

  1. redact direct identifier/contact fields;
  2. pertahankan non-reversible pseudonymous reference hanya jika minimum transactional/audit integrity membutuhkan;
  3. catat policy/version/action/result di audit;
  4. jangan cascade-delete Payment/Refund/Audit integrity.

10. Notifications and manual support

10.1 Automated email — CONFIRMED

Email dipakai untuk:

Delivery failure tidak me-roll back domain truth.

TBC-NOTIFY-01: provider email, sender/domain verification, copy/template, localization, bounce handling, dan package reminder offset.

10.2 WhatsApp — CONFIRMED boundary

11. Admin workspace and audit

Admin workspace minimal harus bisa:

TBC-ADMIN-01: exact form fields, table columns, filter, bulk action, visibility matrix detail, notification copy, dan exception UX. Default: least privilege; jika field tidak diperlukan untuk command, jangan tampilkan.

Audit record minimal:

12. Suggested implementation sequence

Slice 0 — Foundation

Slice 1 — Catalog and public availability

Slice 2 — Guest booking and SlotHold

Slice 3 — Payment adapter

Slice 4 — Package entitlement

Slice 5 — Staff and ClientAccess

Slice 6 — Cancellation/refund

Slice 7 — Notifications and support

Slice 8 — Privacy/retention

Slice 9 — CMS/admin hardening and UAT

13. Test requirements

13.1 Domain/module tests

13.2 Integration tests

13.3 Browser/UAT tests

14. TBC register

ID Topic Current implementation instruction Owner / gate
TBC-ACCESS-01 ClientAccess rate limit/recovery/revocation Isolate in AccessPolicy; use conservative configurable defaults Technical; before production
TBC-API-01 Exact API routes/payloads Define transport after command interfaces and UAT cases Technical
TBC-PACKAGE-01 Exact validity calendar semantics/reminders Store snapshot policy; do not read current catalog for old purchase Business + technical
TBC-PAY-MANUAL-01 Admin verification SLA, payment-proof retention, dispute handling, real account/QRIS configuration Keep manual flow behind WhatsAppManualPaymentModule; demo configuration cannot pass production gate Operations/finance; production gate
TBC-PRIVACY-01 Policy duration/trigger/exception/evidence Implement RetentionPolicy, no destructive production action yet Clinical/ethics + technical/data; production gate
TBC-NOTIFY-01 Email provider/copy/bounce/package offsets Domain events first; provider behind NotificationAdapter Business + technical
TBC-ADMIN-01 Admin forms/visibility/copy Least privilege and command-driven UI Technical + operations
TBC-SCHEDULE-01 Fuja recurring schedule/offline venue Placeholder only; no live slot publication Operations; production gate
TBC-POLICY-01 Package unavailability SLA/credit/transfer workflow Same-offering resolution first; explicit client consent for transfer Business + clinical/ethics
TBC-REC-01 Retry/dead-letter/reconciliation cadence Persist reconciliation-required; never silently resolve Operations/finance; production gate
TBC-CONSENT-01 Final consent wording/publication evidence Version ConsentRecord and block publish where required Clinical/ethics; production gate
TBC-LIVE-PRD-01 Reconcile live form readback with canonical closure baseline Do not treat missing summary fields as new business decisions; reconcile against ADR/domain model before using live form as authority Technical; before implementation handoff
TBC-PACKAGE-CANCEL-01 Package-wide cancellation matrix and pending-versus-outcome race Closed by ADR 0095-package-cancellation-matrix.md: targets, open-request invariant, R1–R4 race resolution, atomic package-wide effects, partial-package 1-of-N, repeat/correction, couple override, RescheduleAction table Owner: clinical/ethics + operations + technical; closed 2026-08-31
TBC-NO-SHOW-01 No-show early checkpoint vs terminal outcome/late-arrival correction Closed by ADR 0092-appointment-outcome-timing.md: no_show early checkpoint T+15m, completed_partial 1–59m, no_show_late late arrival with session held, OutcomeCorrection window 7×24 jam Owner: clinical/ethics + operations; closed 2026-08-31
TBC-RESCHEDULE-01 Allowed RescheduleAction lifecycle states/cutoff/repeat handling Closed by ADR 0095-package-cancellation-matrix.md §5: forbidden transitions enumerated, replacement capacity overlap enforced, couple-package rules via BookingParticipant, cancellation-request rebound (R4) Owner: operations + technical; closed 2026-08-31
TBC-PAY-SETTLEMENT-01 At-most-one successful settlement and package paid_late effects Verify amount/currency uniqueness per intent; document paid_late PackagePurchase/Entitlement creation rules Business/ops + technical; before Slice 3 production
TBC-EXTENSION-01 Explicit Admin extension state for entitlement/correction past windows Add extension_request / extension_grant audited commands; matrix update to ADR 0092 §6.1.3 and ADR 0095 §3 Operations; before production

15. Definition of implementation-ready

A slice is implementation-ready when:

The MVP is implementation-ready for core slices when:

16. Production launch gate

PRD/design handoff can proceed now using this document and explicit placeholders (see §16.1). Production launch requires passing every gate in §16.2 with named-role owner sign-off and concrete acceptance evidence attached (no narrative-only "TBD"). No cell in §16.2 may be left as TBD or TBC; every blocked gate must close the related TBC first per §16.3.

Canonical checklist: docs/adr/0096-launch-gate-checklist.md (this section is the executable mirror). Gate numbering G-1..G-14 and source ADRs match ADR 0096 §3.

16.1 PRD/design handoff checklist (allowed now)

These items are not gates for PRD/design handoff. Handoff may proceed now using this guide, DOMAIN-MODEL.md, ADR 0001–0095, and explicit placeholders per ADR 0088.

# Item Handoff assumption Placeholder / explicit marker
H-1 Domain model vocabulary DOMAIN-MODEL.md + ADR 0089–0095 none
H-2 Aggregate ownership ADR 0095 §3 (Booking/Appointment/PackagePurchase/SessionEntitlement) none
H-3 Catalog/pricing (individual only) IMPLEMENTATION-GUIDE.md §2.1 + ADR 0074 couple: see §16.2 G-6
H-4 Catalog/pricing (couple) shown in catalog with badge coming soon per PRD-GUIDELINE-REVIEW.md Round 2 R2-07 recommendation explicit not_purchasable until G-6 evidence
H-5 Identity/auth model ADR 0080 Google SSO + ADR 0081 two-Admin bootstrap staff-session TBC carries forward
H-6 State machines Booking/Payment/Appointment/Package/Entitlement/Cancellation/Refund per ADR 0095 + ADR 0093 + ADR 0092 none
H-7 Module seams IMPLEMENTATION-GUIDE.md §5 TBC-API-01 routes/payload
H-8 Test seams IMPLEMENTATION-GUIDE.md §13 none

16.2 Production-launch checklist (Matrix §7, executable)

Blocking stage legend:

Gate Title Owner (named role) Blocking stage Acceptance evidence (artifact) Source ADR / TBC
G-1 Profile asset, service presentation, placeholder venue/schedule clearly marked fixture-only, NOT paid at launch business owner + technical before UAT (a) PsychologistProfile.publish_status for non-Fuja slots = not_published (D1 query returns empty); (b) catalog screenshot showing coming soon badge on couple; (c) Fuja profile fields complete per ADR 0075; (d) business owner sign-off note confirming "no live paid booking except SERAYA PULANG individual counseling". ADR 0075, Ticket 06 06.6, PRD-GUIDELINE-REVIEW.md Round 2 R2-08
G-2 Real availability + online joining instructions + offline venue operations + psychologist (Fuja) + technical before UAT (a) AvailabilityRule rows populated for Fuja's recurring schedule; (b) AvailabilityException for blackouts; (c) no row with location_label = 'anytime' in published slots (D1 query returns empty); (d) online join instructions at docs/operations/online-join-instructions.md; (e) offline venue at docs/operations/offline-venue.md; (f) psychologist sign-off confirming schedule is current. ADR 0075, ADR 0088:17, TBC-SCHEDULE-01
G-3 Approved consent, privacy notice, cancellation/refund policy, crisis/referral information clinical/ethics + business owner production only (a) Consent copy final versioned at docs/consent/consent-v1.md (8 sections matching JSON consents); (b) clinical/ethics sign-off note in frontmatter; (c) privacy notice published page URL + 12-month retention per ADR 0083; (d) cancellation/refund public copy "Cancellation and refund are handled by Admin via WhatsApp; review is case-by-case." published on /booking + in confirmation email; (e) crisis boundary text in every public page footer per ADR 0082; (f) referrals populated with ≥3 named services; TBC-CONSENT-01 + TBC-CANCELLATION-PUBLIC-01 must be closed. ADR 00820086, ADR 0076/0077, Round 3
G-4 Verified payment integration and reconciliation behavior technical + finance + operations production only (a) Midtrans sandbox test pass log (CI run-id) — capture/settlement/expire/refund all green; (b) production merchant activation evidence (dashboard screenshot or signed letter) — QRIS + VA enabled; (c) ≥1 successful sandbox test refund with disbursement; (d) reconciliation runbook at docs/operations/payment-reconciliation.md (daily cadence, retry policy, dead-letter escalation); (e) paid_late integration test pass (run-id, §7.7); (f) duplicate webhook integration test pass (run-id, §7.6); (g) finance sign-off on QRIS/VA account mapping; (h) TBC-PAY-01 + TBC-PAY-EXPIRY-01 must be closed. ADR 0088:13-14, ADR 0093, §7.3–§7.7
G-5 Booking confirmation/reminder flow and operational owner operations + technical before UAT (a) Email templates final: docs/templates/email/{booking-confirmation,payment-confirmation,reminder-24h,reminder-2h,outcome-finalized,no-show-recorded}.md; (b) UAT §13.3 reminder scenarios pass with delivery log; (c) Admin WhatsApp number in /contact footer + confirmation email; (d) on-call runbook docs/operations/admin-on-call.md with primary + backup Admin + response SLA; (e) TBC-NOTIFY-01 must be closed. ADR 0052, ADR 0088:19, Round 3
G-6 Couple-participant/consent decisions if couple counselling is bookable business owner + clinical/ethics + technical production only If couple launch-deferred (recommended per PRD-GUIDELINE-REVIEW.md Round 2 R2-07): business owner sign-off note + catalog badge coming soon verified in §13.3 UAT. If couple launch-ready: (a) BookingParticipant/AppointmentParticipant migration applied (migration-id); (b) couple_consent + participant_consent_a + participant_consent_b + joint_session_consent finalized and signed by clinical/ethics; (c) notification routing test pass; (d) visibility matrix test pass; (e) couple-package cancellation test per ADR 0095 §6. ADR 0090, ADR 0095 §6, PRD-GUIDELINE-REVIEW.md Round 2 R2-07/R2-11
G-7 Architecture / persistence stack ratified technical before slice 0 ADR 0089-architecture-worker-d1.md accepted; migrations/0001_init.sql applied to D1 binding; backup/restore runbook at docs/operations/d1-backup-restore.md; D1 binding in wrangler.toml (committed). ADR 0089, PRD-GUIDELINE-REVIEW.md Round 1 P0-01
G-8 Booking intake, minor (16–17) guardian route, eligibility boundary, cutoff business owner + clinical/ethics + technical before slice 2 ADR 0094-intake-eligibility-cutoff.md accepted; intake schema applied (D1 migration-id); JSON booking_intake updated to match ADR 0094 field list; 1 hour before scheduled_start cutoff enforced in CreateBooking precondition (integration test run-id). ADR 0094, PRD-GUIDELINE-REVIEW.md Round 1 P0-04, Ticket 09
G-9 No-show timing, late-arrival correction window clinical/ethics + operations + technical before UAT ADR 0092-appointment-outcome-timing.md accepted; T+15m auto-checkpoint cron handler deployed to Worker; OutcomeCorrection 7×24h window enforced (integration test run-id covering in-window vs out-of-window). ADR 0092, PRD-GUIDELINE-REVIEW.md Round 1 P1-12
G-10 Package-wide cancellation matrix + outcome race operations + technical before UAT ADR 0095-package-cancellation-matrix.md accepted; D1/Postgres triggers applied (migration-id); 15 acceptance criteria tests pass (run-id); couple-package target resolution per ADR 0095 §6 test pass. ADR 0095, PRD-GUIDELINE-REVIEW.md Round 1 P1-13, Ticket 10
G-11 Payment settlement uniqueness + paid_late package technical + finance production only (paired with G-4) Unique partial index payment(booking_id) WHERE status = 'paid' AND settled_at IS NOT NULL applied (migration-id); paid_late test pass (§7.7 run-id); duplicate webhook test pass (§7.6 run-id); finance sign-off confirming reconciliation report uses Payment.settled_at as canonical truth. ADR 0093, PRD-GUIDELINE-REVIEW.md Round 1 P1-10
G-12 Two-Admin bootstrap + last-active-Admin guard business owner + operations + technical before UAT (a) StaffMembership rows for both bootstrap Admins with RoleAssignment.role = 'admin' and is_active = true (D1 query result); (b) last-active-Admin guard implementation (cannot revoke the only remaining active Admin without another Admin present — integration test run-id); (c) Google SSO configured per ADR 0080 (OAuth client_id/secret in secret store, redirect URIs registered). ADR 0080, ADR 0081, PRD-GUIDELINE-REVIEW.md Round 1 P1-11
G-13 UAT pass for recorded critical scenarios operations + technical + business owner production only (a) UAT §13.3 scenarios recorded as pass with screenshots + run-ids (four program pillars; counseling catalog + pricing; guest booking + hold expiry; payment success/failure/late webhook; package next-session scheduling; Admin cancellation approve/deny + separate refund; staff role visibility; mobile keyboard/focus/contrast/error; privacy/no-clinical-data); (b) accessibility/perf/SEO checks (Lighthouse scores committed); (c) business owner walkthrough sign-off. §13.3, ADR 0088:12, PRD-GUIDELINE-REVIEW.md Round 1 P1-06
G-14 Operational sign-off (consolidated) business owner + operations + clinical/ethics + finance + technical production only Consolidated sign-off at docs/launch/release-sign-off-v1.md listing G-1..G-13 status (pass / blocked / waived) with each owner's signature (typed name + role + date) and attached evidence links. No TBD cells. Waivers require explicit business owner acknowledgement. this section + ADR 0096 §3

16.3 Gate-to-TBC dependency map

Every TBC that still blocks one or more gates is listed below. Closing each TBC is the prerequisite for collecting the related evidence; the TBC is not closed until the evidence artifact exists.

TBC Blocks gate Closure action
TBC-CONSENT-01 G-3 Clinical/ethics sign-off on consent copy final; commit ConsentRecord.version.
TBC-PRIVACY-01 G-3 Retention policy values decided; execution cadence documented; test fixtures redacted.
TBC-PAY-01 G-4 Method codes, fees, limits, refund capability documented; adapter config committed.
TBC-PAY-EXPIRY-01 G-4 Provider expiry vs SlotHold TTL invariant decided; integration test pass.
TBC-NOTIFY-01 G-5 Email provider + sender/domain verification + bounce handling + reminder offsets committed.
TBC-REC-01 G-4, G-11 Reconciliation cadence and dead-letter policy committed.
TBC-ADMIN-01 G-12, G-5 Admin workspace fields/visibility matrix committed; last-active-Admin guard tested.
TBC-ACCESS-01 G-12 ClientAccess rate limit/recovery/revocation decided; conservative defaults applied.
TBC-STAFF-SESSION-01 G-12 OAuth state/nonce/session/cookie/CSRF/re-auth/revocation/recovery behavior committed.
TBC-COUPLE-LAUNCH-01 G-6 Business owner decides couple launch-deferred (default) vs launch-ready.
TBC-SCHEDULE-01 G-2 Fuja recurring schedule + offline venue confirmed; AvailabilityRule populated.
TBC-API-01 G-13 Transport routes/payloads defined; UAT scenarios reference real endpoints.
TBC-LIVE-PRD-01 G-13, G-14 Live form reconciled with closure baseline; missing canonical keys restored or removed.
TBC-EXTENSION-01 G-9, G-10 extension_request / extension_grant audited commands added to ADR 0092 §6.1.3 and ADR 0095 §3.

16.4 Release sign-off template

Live launch can only proceed after G-14 consolidated sign-off is signed and every G-1..G-13 gate is pass or explicitly waived by the business owner.

Required signatures on docs/launch/release-sign-off-v1.md:

Until G-14 is signed, development may use fixtures and sandbox behavior, but production must not infer or silently invent the missing business decisions.