Skip to content

Changelog

Phase 15 — B2B Workflow, Inventory Lifecycle, Refund Approvals (2026-05, Current)

A 236-commit wave covering the B2B four-phase approval workflow, packaging + MMOQ, the inventory state machine + external-fulfillment handoff, refund approvals with durable Temporal execution, product labels with predicates, AI parse-to-cart, and the extension self-registration completeness story.

B2B Cart / Order Approval (Rounds 1–3 + C-series, Decided #200, C1–C16)

  • Multi-phase workflow (cart + payment approval phases) with rejections written to CartRejectionEvent and faults emitted on vectis.workflow.fault.v1 (Decided #200)
  • Cart-level split-tender composer with PaymentTender.source provenance column (cascade vs buyer_prepended, Decided C12)
  • Stale-approval pricing-drift gateCart.cart_approved_grand_total is compared against the live grand total at place-order; mismatch rejects placement (Decided C8)
  • Inventory revalidation gate at placeOrder — re-checks inventory under a serializable transaction; on drift the cart transitions to cart_approved_blocked_inventory status (Decided C9)
  • recurateCascade(orderId, newCascade: JSON!, reason?) lets an approver re-curate the tender cascade per-order after a fault (Decided C4)
  • Multi-approver archival quorum preservation so removing one approver doesn't disable an in-flight approval (Decided C1)
  • Refund cap invariant + recurring auto-pause (Decided C6, C14)
  • Card-auth expiry void daily sweepVoidExpiringCardAuthsWorkflow voids authorized card legs that aged past their gateway expiry, including ACH-paired tenders that hadn't settled in time (Decided C10)

Inventory, Packaging, MMOQ (Decided #25, #160, #161, #172, #234, #235, #236)

  • Packaging UoM ladderpackages + product_packages tables with per-product slug/multiplier; cart and order snapshots store the chosen package; reorder respects the original slug and flags unit_changed when the ladder shifts (#235)
  • MMOQ caps on ProductVariantmax_per_customer_per_order, max_per_customer_30d, max_backorder_qty, mmoq_display_unit_id; cart-aggregate enforcement; channel-timezone 30-day window; serializable re-validation at finalize; MmoqViolationDetail returned on cartBulkLookup rows (#236)
  • Reservation state machine — HELD → CONFIRMED → RELEASED / EXPIRED / FULFILLED with TTL expiry via ExpireStaleReservationsWorkflow (#160)
  • External stock pushpushExternalStock API-key-scoped mutation; tracked products update reservable inventory, untracked products only set Variant.external_stock_snapshot for audit (#161, #234)
  • Inventory state versionProduct.inventory_state_version bumps on policy change and gates place-order races (#234)
  • External fulfillment handoffacknowledgeOrderExternalHandoff(orderId, externalReference?) releases internal holds so the ERP becomes the system of record (#172)
  • Order.inventory_risk_flag auto-fires on oversold; orders(inventoryRiskOnly:) filter for admin triage
  • clearProductInventoryState(productId): ClearProductInventoryStateResult! releases reservations; clearProductBackorders(productId) unblocks order resumption
  • OrderLineItem.tracking_enabled_at_checkout captures the tracking flag at place-order time so fulfillment isn't surprised by a live toggle

Refund Approval (OQ #26 / #27)

  • Refund Approvals Inbox in admin (/refund-approvals) — staff submits via submitRefundForApproval(orderId, input), approver decides via decideRefundApproval(refundRequestId, approved, notes?)
  • Self-approval blocked at the API; per-tender progress tracking in the inbox
  • Refund execution is a durable Temporal RefundExecutionWorkflow (backend/vectis/modules/refund_approval/workflows.py); faults emit on vectis.workflow.fault.v1; retryRefundExecution(refundRequestId) is idempotent against empty-transaction states

Product Labels (Decided #228)

  • Predicate-driven labels rendered on PDP, related products, recently-viewed, search results, and catalog cards
  • Batch evaluation (evaluate_many + supports_batch flag) so hundreds of labels evaluate against thousands of products in one pass
  • Stale reference GC via clearProductLabelStaleReferences(extensionName: String!): Int! mutation — scoped per extension; predicate namespacing keeps cross-extension predicates from colliding

Cart Bulk Ops + AI Parse-to-Cart

  • Bulk select + delete + save-for-later with bulkRemoveCartLines(lineIds), bulkSaveCartLinesForLater(lineIds), restoreSavedCartLine (Decided #230 — unit_package_id persists across save→restore)
  • Quick Order CSV/paste with cartBulkLookup preview (MMOQ violations, packaging hints, unitChanged) and partial-success bulkAddToCart (Decided #231)
  • AI parse-to-cart from text or photocartAiParseToLookupItems(input: CartAIParseInput!); result feeds cartBulkLookupbulkAddToCart; cart_ai_provider_priority Setting controls provider order (Decided #232)

Extensions Plug-In Completeness (Decided #222–#231 + #233)

  • Self-registration for goshippo, omnisend, shipstation; new slots for CarrierSeed, SavedPayment, AI providers, notification templates, Cmd-K Quick Actions, federated search, page tabs, nav, models, GraphQL queries / mutations
  • ExtensionRegistry._activate consults InstallStateService (Decided #148) — install-state persistence, version detection, on_upgrade hook
  • Zero-extension-imports invariant enforced via AST guard (Decided #233) — core never imports ext_*
  • Worker readiness gate — refuses to start if an enabled payment method has no registered strategy (strict mode default)
  • ext_excise_engine swap — decommissioned ext_exciseiq (Decided #225) with a one-click config migration
  • TaxJar real integration — calculation, filing workflow, order sync (replaces stub)
  • J'AI → ext_jai_chatsupport_chat reduced to strategy ABCs only; the extension contributes models, GraphQL, admin tab, and a session-cleanup schedule
  • Cmd-K Quick Actions + federated search (Decided #226, #227) — extensions contribute palette entries and async search indexes; merged into the global picker

Observability + DR (Decided #153, #154, #157, #158)

  • /metrics (Prometheus via prometheus-fastapi-instrumentator) and /ready (parallel DB/Redis/Redpanda probes); /health is the cheap liveness check
  • Token-bucket rate limiter — burst is the ceiling, per-endpoint policies in rate_limit_policies, attributes by X-Forwarded-For (Decided #157, #165)
  • JSON Schema event registry under backend/vectis/events/schemas/ (Decided #153); see vectis/docs/REDPANDA_TOPICS.md
  • DR runbook + reference backend/scripts/backup_postgres.sh + verify_backup_restore.py weekly verify (Decided #158)

Other Platform Polish

  • Fernet-encrypted secrets with SECRETS_MASTER_KEY + SECRETS_MASTER_KEY_ROTATING_FROM rotation (Decided #155)
  • Per-gateway settlement webhook dispatcher with multi-secret rotation window (Decided #198, OQ #28)
  • Payment gateway capability introspectionpaymentMethodCapabilities query with orphaned + disabled filters (Decided #233)
  • Money precision validationmoney.decimal_places save-time + cache invalidation (Decided #104)
  • Automated promotion override invalidation (Decided #168) — stale rules flag automatically
  • Phase-based promotion enginestack_group, $0 floor, order-discount distribution (Decided #162, #170, #171); enforcement-vs-observability usage writes (#152)
  • Stage-sorted tax engine with order-subtotal redistribution (Decided #151)
  • CMS block templates + ProductTranslation overlay with cache contract (Decided #145, #146, #156)
  • Split CSP for CMS embeds (Decided #177)
  • Multi-factor affiliate fraud guard + suspicious review (Decided #179)
  • Location archive + 3-way employee reassignment (Decided #166); storefront active-location switcher (Decided #167); channel context via X-Channel-Slug header forwarded by the BFF (Decided #164 — header-based; admin/storefront routes stay flat)
  • BFF X-Forwarded-For + Uvicorn --proxy-headers for correct audit/rate-limit attribution (Decided #165)
  • Order reissuereissueOrder and reissueShipment create new linked orders so ERP pollers pick them up; original order untouched (Order columns: is_reissue, reissued_from_order_id, reissue_reason)
  • Background shipping rate precompute with explicit cache-hash composition, cubing dedup, untracked-item filter, asyncio yield (Round 6 shipping)
  • Allow store-credit overdraft (Decided #173) — GcExpiredOverdraftDraftsWorkflow reaps unredeemed drafts
  • Cookie consent rewrite — close 7 audit gaps + always-show granular banner
  • A11y warning sweep — 6 critical errors fixed, 50 warnings cleared
  • Tests at 1,670 across backend + storefront/admin Playwright; make check is the local gate (Phase D regression guard + schema-drift guard)
  • CI currently disabled in favor of local make check (2026-05-18); push directly to main when the local gate passes

Platform — Houdini Frontend Migration & CI Pipeline

  • Migrated both admin and storefront apps to Houdini (houdini 1.5 + houdini-svelte 2.1) as the sole GraphQL client; every page's ops live in src/lib/houdini/<PageName>.ts and consume generated *Store classes from $houdini (Decided #119)
  • Deleted the hand-rolled $lib/api.ts (admin) and server-side gql() helper in $lib/server/api.ts (storefront); the storefront's $lib/server/api.ts now exposes only an apiHeaders() builder for the BFF forwarder
  • Page loaders standardized on +page.ts + load_<QueryName> (no more +page.server.ts for data loading); mutations use new <Op>Store().mutate(...); refetches use <QueryStore>.fetch() instead of invalidateAll()
  • BFF /api/graphql is a CSRF-guarded forwarder that attaches the JWT from the session cookie server-side — JWT never reaches the browser (Decided #88)
  • Added GitHub Actions CI (.github/workflows/check.yml) with five jobs — backend-lint (ruff), backend-resolver-check (AST guard against duplicate Strawberry resolvers), backend-tests (pytest against real PG 16 + Redis), admin-check (houdini generate + svelte-check, 0 errors), storefront-check (same)
  • Backend cleanup landed during the ruff sweep: 10 F821 fixes, 3 B904 raise ... from, 2 B905 zip(strict=True), a real B023 bug in schedules.py (closure captured last iteration), and 44 E741 renames

Phase 14 — Payment Lifecycle Rework

  • Reworked payment processing to support configurable capture modes (authorize-only vs auth+capture)
  • Added full payment lifecycle: authorize → capture → void → refund with per-transaction status and type tracking
  • Automatic void/refund on order cancellation and refund transitions via state machine side effects
  • Authorize.net FDS fraud filter detection: orders flagged as HeldForReview, admin fraud hold release
  • Added modifyOrder mutation for editing authorized orders with automatic reauthorization
  • Added adminCreateOrder mutation and admin UI for staff to create orders with saved card charging
  • Saved payment methods now support B2B location scoping (location_id)
  • Admin payments page now includes capture mode configuration (Authorize Only / Authorize + Capture)
  • Added PaymentTransaction.type field (authorization, capture, charge, void, refund)
  • Added Order.fraud_status and Order.fraud_details fields
  • Updated order detail page with fraud hold banner, transaction actions (capture, void), and release button
  • Added admin Create Order page with account/channel selection, line items, saved cards, and capture mode

Phase 13 — Core Geocoding Service

  • Added geographic data model (GeoUnion, Country, Region, County, City) with ISO standards
  • Implemented IP geolocation, address geocoding, and address validation strategies
  • Enriched RequestContext with IP and geo data
  • Added admin UI for managing geographic data and geocoding settings

Phase 12 — Tax Architecture Rework

  • Rebuilt tax calculation engine using geographic data models
  • Added support for granular sales tax, excise tax, and compound tax
  • Tax jurisdiction lookup by ISO region, county, and city
  • Added admin Tax management page

Phase 11 — Documentation System

  • Added MkDocs Material documentation site with developer, usage, and operations guides
  • Added in-admin contextual help system (HelpDrawer + HelpTooltip components)
  • Added structured, translatable help JSON files for all admin routes
  • Added Cursor rule to enforce documentation updates alongside code changes
  • Added GraphQL schema introspection script for auto-generated API reference

Phase 10 — Multi-Currency & Checkout Pipeline

  • Implemented multi-currency support: channel-level supported currencies, exchange rates, currency-aware price resolution
  • Added dual-amount orders (transacted + base currency)
  • Wired end-to-end checkout pipeline: promotions → shipping → tax → payment → order creation
  • Integrated promotion engine with cart-level coupon application and per-line discount allocation
  • Added cart pessimistic locking via SELECT FOR UPDATE during checkout
  • Added Redpanda event publishing for order.created, order.status_changed, inventory.adjusted
  • Added dedicated event consumer service in Docker Compose

Phase 9 — Internationalization & Channel Commerce Mode

  • Implemented channel-based commerce modes: B2B, B2C, hybrid
  • Added multi-language support with JSONB translations on products, categories, brands, and CMS pages
  • Added locale resolution middleware (X-Locale header, cookie, Accept-Language)
  • Added LanguageSwitcher and CurrencySwitcher storefront components
  • Implemented channel-scoped extension activation via ChannelExtension model

Phase 8 — Payment & Shipping Extensions

  • Built Authorize.Net CIM extension (PaymentProcessStrategy)
  • Built gift card extension with virtual/physical cards, partial redemption, bulk generation
  • Added UPS shipping rate extension
  • Replaced inline SVG/emoji icons with Lucide icons across storefront and admin
  • Added shipping carrier icon components (UPS, USPS, FedEx, OnTrac, freight)
  • Added payment card brand icons (Visa, Mastercard, Amex, Discover)

Phase 7 — Testing & Production Prep

  • Set up pytest with real PostgreSQL test database
  • Added test fixtures: event_bus, strategy_resolver, request contexts
  • Wrote test suites for strategy resolver, pricing, cart/order flow, GraphQL API, RBAC, events, extensions
  • Created production-ready multi-stage Dockerfiles
  • Added CMS policy pages: contact, shipping, return, privacy, terms
  • Fixed accessibility warnings in storefront and admin layouts

Phase 6 — Admin Panel & Dashboard

  • Built 20+ admin routes: orders, products, customers, pricing, promotions, inventory, shipping, tax, CMS, settings
  • Implemented command palette (Cmd+K) with fuzzy search
  • Added Customer 360 view
  • Built collapsible sidebar navigation with Lucide icons

Phase 5 — Core Commerce Modules

  • Implemented all 14 strategy ABCs with default implementations
  • Built promotion engine with condition/action pattern and CartIndex
  • Built fulfillment module with by_order and by_box modes
  • Built RMA module with line-level approval
  • Built store credit, gift card, and net terms modules
  • Built reporting and audit modules

Phase 4 — Pricing & Inventory

  • Implemented five-level pricing hierarchy
  • Built price list management with scheduling and volume tiers
  • Built inventory module with warehouses, stock levels, and reservations
  • Implemented backorder support

Phase 3 — Orders & Cart

  • Built configurable order state machine
  • Implemented cart with per-location support and session-based guest carts
  • Built checkout mutation with initial pipeline
  • Fixed SQLAlchemy identity map caching issues

Phase 2 — Auth & Accounts

  • Implemented BFF authentication pattern with httpOnly session cookies
  • Built B2B account hierarchy: Account → Location → Employee → Address
  • Implemented RBAC with role-based permissions
  • Built customer group system

Phase 1 — Products & Catalog

  • Built product module with variants, categories, and brands
  • Implemented Meilisearch integration for full-text search
  • Built catalog visibility rules engine

Phase 0 — Foundation

  • Scaffolded monorepo: backend, admin, storefront, migrator
  • Set up Docker Compose with PostgreSQL, Redis, Redpanda, Temporal, Meilisearch, MinIO
  • Created FastAPI application factory with Strawberry GraphQL
  • Created SvelteKit admin and storefront shells
  • Set up Alembic migrations with initial schema
  • Established Forge methodology artifacts: constitution, conventions, reconciliation