Commit graph

3 commits

Author SHA1 Message Date
senke
26cb523334 fix(distribution,audit): propagate ErrSubscriptionNoPayment to handler + P0.12 closure date + E2E regression TODO
Self-review of the v1.0.6.2 hotfix surfaced that
distribution.checkEligibility silently swallowed
subscription.ErrSubscriptionNoPayment as "ineligible, no extra info",
so a user with a fantôme subscription trying to submit a distribution
got "Distribution requires Creator or Premium plan" — misleading, the
user has a plan but no payment. checkEligibility now propagates the
error so the handler can surface "Your subscription is not linked to
a payment. Complete payment to enable distribution."

Security is unchanged — the gate still refuses. This is a UX clarity
fix for honest-path users who landed in the fantôme state via a
broken payment flow.

Also:
- Closure timestamp added to axis-1 P0.12 ("closed 2026-04-17 in
  v1.0.6.2 (commit 9a8d2a4e7)") so future readers know the finding's
  lifecycle without re-grepping the CHANGELOG.
- Item G in v107-plan.md gains an explicit E2E Playwright @critical
  acceptance — the shell probe + Go unit tests validate the fix
  today but don't run on every commit, so a refactor of Subscribe or
  checkEligibility could silently re-open the bypass. The E2E test
  makes regression coverage automatic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 12:43:21 +02:00
senke
68a0d390e2 docs(audit): P1.7 → P0.12 post-probe; add v1.0.7 item G + Idempotency-Key TTL note
2026-04-17 Q2 probe confirmed the subscription money-movement finding
wasn't a "needs confirmation from ops" P1 — it was a live P0 bypass.
An authenticated user could POST /api/v1/subscriptions/subscribe,
receive 201 active without payment, and satisfy the distribution
eligibility gate. v1.0.6.2 (commit 9a8d2a4e7) closed the bypass at
the consumption site via GetUserSubscription filter + migration 980
cleanup.

axis-1-correctness.md:
  * P1.7 renamed to P0.12 with the bypass chain, probe evidence, and
    v1.0.6.2 closure cross-reference.
  * Residual subscription-refund / webhook completeness work split out
    as P1.7' (original scope, still v1.0.8).

v107-plan.md:
  * Item G added (M effort) — replaces the v1.0.6.2 filter with a
    mandatory pending_payment state + webhook-driven activation,
    closing the creation path rather than compensating at the gate.
  * Dependency graph gains a third track (independent of A/B/C/D/E/F).
  * Effort total revised from 9-10d to 12-13d single-dev, 5d to 7d
    two-dev parallel.
  * Item D acceptance gains a TTL caveat section — Hyperswitch
    Idempotency-Key has a 24h-7d server-side TTL; app-level
    idempotency (order.id / partial UNIQUE) remains the load-bearing
    guard beyond that window.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 12:31:07 +02:00
senke
6b345ede9f docs(audit): 2026-04 correctness/accounting findings (axis 1)
Axis 1 of the 5-axis VEZA audit, scoped to money-movement correctness
and ledger↔PSP reconciliation. Layout: one file per axis under
docs/audit-2026-04/, README index, v107-plan.md derived.

P0 findings (block v1.0.7 "ready-to-show" gate):
  * P0.1 — SellerTransfer.StripeTransferID declared but never populated.
    stripe_connect_service.CreateTransfer discards the *stripe.Transfer
    return value (`_, err := transfer.New(params)`), so the column in
    models.go:237 is dead. Structural blocker for the CHANGELOG-parked
    v1.0.7 "Stripe Connect reversal" item.
  * P0.2 — No Stripe Connect reversal on refund.succeeded. Every refund
    today creates a permanent VEZA↔Stripe ledger gap. Action reworked
    to decouple via a new `seller_transfers.status = 'reversal_pending'`
    state + async worker, so Stripe flaps never block buyer-facing
    refund UX.
  * P0.3 — No reconciliation sweep for stuck orders / refunds / refund
    rows with empty hyperswitch_refund_id. Hourly worker recommended,
    same pattern as v1.0.5 Fix 6 orphan-tracks cleaner.
  * P0.4 — No Idempotency-Key on outbound Hyperswitch POST /payments and
    POST /refunds. Action includes an explicit scope note: the header
    covers HTTP-transport retry only, NOT application-level replay (for
    which the fix is a state-machine precondition).

P1 findings:
  * P1.5 — Webhook raw payloads not persisted (blocks dispute forensics)
  * P1.6 — Disputes / chargebacks silently dropped (new, surfaced during
    review; dispute.* webhooks fall through the default case)
  * P1.7 — Subscription money-movement not covered by v1.0.6 hardening
  * P1.8 — No ledger-health Prometheus metrics

P2 findings:
  * P2.9 — No admin API for manual override
  * P2.10 — Partial refund latent compromise (amount *int64 always nil)

wontfix:
  * wontfix.11 — Per-seller retry interval (re-evaluate at 10× load)

Derived deliverable: v107-plan.md sequences the 6 de-duplicated items
(4 P0 + 2 P1) with a dependency graph, two parallel tracks, per-commit
effort estimates (D→A→B; E→C→F), release gating and open questions
(volume magnitude, Connect backfill %).

Info needed from ops (tracked in axis-1 doc, not determinable from
code): last manual reconciliation date, whether subscriptions are
currently sold, current order/refund volume.

Axes 2-5 deferred: README.md marks axis 2 (state machines) as gated
on v1.0.7 landing first, otherwise the transition matrix captures a
v1.0.6.1 snapshot that's immediately stale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 03:21:33 +02:00