veza/docs/audit-2026-04
senke 2583c92d4f
Some checks failed
Veza CI / Backend (Go) (push) Failing after 0s
Veza CI / Frontend (Web) (push) Failing after 0s
Veza CI / Rust (Stream Server) (push) Failing after 0s
Security Scan / Secret Scanning (gitleaks) (push) Failing after 0s
Veza CI / Notify on failure (push) Failing after 0s
refactor(connect): persist stripe_transfer_id on create + retry — v1.0.7 item A
TransferService.CreateTransfer signature changes from (...) error to
(...) (string, error) — the caller now captures the Stripe transfer
identifier and persists it on the SellerTransfer row. Pre-v1.0.7 the
stripe_transfer_id column was declared on the model and table but
never written to, which blocked the reversal worker (v1.0.7 item B)
from identifying which transfer to reverse on refund.

Changes:
  * `TransferService` interface and `StripeConnectService.CreateTransfer`
    both return the Stripe transfer id alongside the error.
  * `processSellerTransfers` (marketplace service) persists the id on
    success before `tx.Create(&st)` so a crash between Stripe ACK and
    DB commit leaves no inconsistency.
  * `TransferRetryWorker.retryOne` persists on retry success — a row
    that failed on first attempt and succeeded via the worker is
    reversal-ready all the same.
  * `admin_transfer_handler.RetryTransfer` (manual retry) persists too.
  * `SellerPayout.ExternalPayoutID` is populated by the Connect payout
    flow (`payout.go`) — the field existed but was never written.
  * Four test mocks updated; two tests assert the id is persisted on
    the happy path, one on the failure path confirms we don't write a
    fake id when the provider errors.

Migration `981_seller_transfers_stripe_reversal_id.sql`:
  * Adds nullable `stripe_reversal_id` column for item B.
  * Partial UNIQUE indexes on both stripe_transfer_id and
    stripe_reversal_id (WHERE IS NOT NULL AND <> ''), mirroring the
    v1.0.6.1 pattern for refunds.hyperswitch_refund_id.
  * Logs a count of historical completed transfers that lack an id —
    these are candidates for the backfill CLI follow-up task.

Backfill for historical rows is a separate follow-up (cmd/tools/
backfill_stripe_transfer_ids, calling Stripe's transfers.List with
Destination + Metadata[order_id]). Pre-v1.0.7 transfers without a
backfilled id cannot be auto-reversed on refund — document in P2.9
admin-recovery when it lands. Acceptable scope per v107-plan.

Migration number bumped 980 → 981 because v1.0.6.2 used 980 for the
unpaid-subscription cleanup; v107-plan updated with the note.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 13:08:39 +02:00
..
axis-1-correctness.md fix(distribution,audit): propagate ErrSubscriptionNoPayment to handler + P0.12 closure date + E2E regression TODO 2026-04-17 12:43:21 +02:00
README.md docs(audit): 2026-04 correctness/accounting findings (axis 1) 2026-04-17 03:21:33 +02:00
v107-plan.md refactor(connect): persist stripe_transfer_id on create + retry — v1.0.7 item A 2026-04-17 13:08:39 +02:00

VEZA Audit — 2026-04

Scope — VEZA backend (Go) + web (TypeScript). TALAS software (firmware, PCB reverse-engineering pipeline) is out of scope and will be audited separately when its phase stabilises.

Source state — commits up to a57bb6f78 (v1.0.6.1, 2026-04-17).

Auditor — Claude Opus 4.7 (1M context).

Axes

# File Status
1 axis-1-correctness.md — correctness / accounting delivered
2 axis-2-state-machines.md — transition matrix + illegal-transition tests 🔲 pending v1.0.7
3 axis-3-security.md — attack surface (signatures, rate limits, authz, secrets) 🔲 pending
4 axis-4-tests.md — coverage vs reality, failure-injection gap 🔲 pending
5 axis-5-debt.md — documented debt vs hidden debt (TODO/FIXME inventory) 🔲 pending

Axis 2 is gated on v1.0.7 landing first — otherwise the transition matrix captures a v1.0.6.1 snapshot that's immediately stale. See v107-plan.md for the sequencing.

Reading conventions

Every finding cites file:line evidence. Structure:

### P{0|1|2}.N — short title
**Evidence** — concrete cites
**Consequence** — what breaks today / tomorrow
**Action** — what to do, with enough detail that an implementer can start
**Criticity** — P0 / P1 / P2 / wontfix (with justification)

P0 = fix within v1.0.7 or earlier (ledger diverges today, or a v1.0.7 commitment is structurally blocked). P1 = v1.0.7 target. Operational visibility / correctness hardening. P2 = v1.0.8+. Nice-to-have. wontfix = justified non-action.

Info needed from ops (not determinable from code)

Tracked in axis-1-correctness.md. Absence of answers becomes a finding in its own right.

Derived deliverables

  • v107-plan.md — sequencing, dependencies and relative effort for the axis-1 P0 findings + the CHANGELOG-parked v1.0.7 items. Read this before picking up v1.0.7 work.