Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The template is [`adr/ADR-TEMPLATE.md`](adr/ADR-TEMPLATE.md) (ADR-0006). Propose
| [0017](adr/0017-domain-events-and-transactional-outbox.md) | Transactional outbox and idempotent consumers | Proposed |
| [0018](adr/0018-backend-frontend-contracts.md) | PHP data objects → generated TS types | Proposed |
| [0019](adr/0019-search-behind-a-port.md) | Search port, MySQL first | Proposed (D-8) |
| [0020](adr/0020-platform-administration-in-filament.md) | Filament for staff; Vue for vendors and customers | Proposed |
| [0020](adr/0020-platform-administration-in-filament.md) | Filament for staff; Vue for vendors and customers | Accepted (owner, 2026-11-04) |
| [0021](adr/0021-environments-demo-data-and-production-isolation.md) | Isolated environments; reproducible demo | Proposed |

Engineering standards that do **not** shape a domain, data-isolation, money, runtime-dependency or deployment boundary live in [`../engineering/`](../engineering/), not in an ADR.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ Rejected. A closed browser loses the order, and the return URL is client-control

## Decision

1. **Checkout requires an authenticated customer.** Guest checkout is out of scope for v2 (PRD §6); the cart is merged on login. This removes the guest-cart ownership class of bug.
1. **Checkout requires an authenticated customer with a verified email (P-1, approved 2026-11-04).**
- Guests may browse and keep a cart. They start checkout by signing in or registering inline. Either way the guest cart is merged into the customer's cart (FR-CART-02).
- Step 1 (place) and Step 2 (attempt) both reject a guest or an unverified account **before any write**. No OrderGroup, reservation, redemption, points hold, payment attempt or provider call is made. The check is in the use cases as well as the routes (security-model §5), so it holds even when the UI is bypassed.
- Verification happens before placement, and reservations are created only at placement. So the reservation TTL never runs while a customer verifies.
- Guest checkout is out of scope for v2 (PRD §6). This removes the guest-cart ownership class of bug.

2. **Step 1, place.** `POST /checkout` carries an `Idempotency-Key`: a UUID generated when the checkout page renders, unique per customer.
1. Re-price the cart server-side: current prices, promotions, points, shipping quotes. If the total differs from the total the customer confirmed, return **409** with the new summary. Never charge an amount the customer did not see.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
# ADR-0020: Build platform administration with Filament; build the vendor dashboard in Vue

- **Number:** 0020
- **Status:** Proposed
- **Status:** Accepted, by owner decision (2026-11-04)
- **Date:** 2026-11-01
- **Decision owner:** Project owner

---

## Owner decision (2026-11-04)

The owner accepted this ADR before Phase 2:

- Filament is used for platform administration at `/admin`, for authorised platform staff only.
- The vendor dashboard stays Vue 3 + Inertia.js (ADR-0008).
- Filament uses the Phase 2 design tokens.
- The admin interface is English-only in v2.
- Filament resources and actions delegate business operations to Application use cases. They never mutate domain status or financial data directly.
- Laravel 13 compatibility is verified in Phase 3. The fallback in Decision §5 is retained.

**Rationale** (the decision drivers below):

- Filament delivers dense back-office tooling for a staff-only surface at low cost.
- Vendors keep a product-grade, localised Vue experience.
- Keeping vendors out of Filament avoids the panel-tenancy pattern that Phase 0 found unsafe (S1).

**Dependency on a Proposed ADR.** This ADR relies on mechanisms defined by ADR-0011, which is still **Proposed**:

- `platform_role`;
- the permission enum;
- `VendorContext`;
- the rule that staff cannot hold a vendor membership.

Decision §1 and §6 name these mechanisms as the intended design. They are not accepted until ADR-0011 is. If ADR-0011 changes, §1 and §6 follow it, through a new ADR (ADR-0006).

## Context

- The brief (§6.3) asks to *evaluate and improve* Filament rather than assume replacement.
Expand Down Expand Up @@ -44,6 +70,20 @@ Which technology serves the platform-administration and vendor surfaces?
3. Admin dashboards (GMV, orders, refunds, payables, outbox and reconciliation health) read from Finance and Platform read models.
4. Filament is themed with the Phase 2 design tokens (colours, typography) so it looks like part of the same product.
5. **Compatibility check:** Filament's support for Laravel 13 / PHP 8.5 is to be verified in Phase 3 before installation. If it is unsupported, this ADR is revisited. The fallback is Vue admin pages built from the same component kit.
6. **Surface separation.** The two back-office surfaces do not overlap:

| | Platform administration | Vendor dashboard |
|---|---|---|
| Route | `/admin` | `/vendor` |
| Technology | Filament (Livewire) | Vue 3 + Inertia.js |
| Who | Users with `platform_role` (staff) | Vendor members, resolved through `VendorContext` (ADR-0011) |
| Languages | English only | `en`, `ar` (RTL), `ms` |

- No Filament panel, resource or Filament tenancy is used for vendors.
- A vendor member without `platform_role` is denied at `/admin`.
- Under ADR-0011 (Proposed), staff cannot hold a vendor membership, so they never use `/vendor`.
- Both surfaces call the same Application use cases and policies. Business rules are not duplicated in either UI.
7. **Language.** The admin interface is English-only in v2. Arabic, Malay and right-to-left requirements apply to the storefront and the vendor dashboard.

## Positive consequences

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ A `pending` vendor can manage its profile, shipping and draft products, but cann

| Layer | Mechanism |
|---|---|
| Routing | Route groups: `storefront` (web), `account` (auth + verified), `vendor` (auth + verified + `vendor.context`), `admin` (Filament panel + 2FA), `webhooks` (signature middleware, CSRF-exempt) |
| Routing | Route groups: `storefront` (web), `checkout` (entry step public, offering sign-in or inline registration; the place-order and payment-attempt routes need auth + verified, and the use cases re-check this, P-1 / ADR-0015 §1), `account` (auth + verified), `vendor` (auth + verified + `vendor.context`), `admin` (Filament panel + 2FA), `webhooks` (signature middleware, CSRF-exempt) |
| Binding | Vendor routes use scoped bindings: `{vendorOrder}` resolves `where vendor_id = context`. Customer routes resolve `where customer_id = auth id`. |
| Policies | One policy per aggregate; `before()` never grants a blanket allow, not even for Admin (Admin is granted explicitly per permission) |
| Use cases | Accept `Actor` (user, role, vendor context, impersonator) and re-check the permission. Use cases may be called from Filament, jobs or controllers, so the check lives in the use case as well as in the controller. |
Expand Down
43 changes: 43 additions & 0 deletions docs/discovery/PHASE-1-REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,46 @@ These are the significant ones, each with its reason. The trade-offs and alterna
- [ ] Authorise (or not) committing this branch; decide D-15.

**Phase 1 stops here. Phase 2 (Brand Identity and Design System) has not been started.**

---

## Addendum A: owner decisions for Phase 2 readiness (2026-11-04)

The Phase 1 decision briefing (2026-11-03) labelled its spec inconsistencies C1–C13. **Those labels are unrelated to Phase 0's code findings C1–C6** (§7) (for example Phase 0 C1, "payments never marked paid"). Here they are written as "briefing C*n*".

### A.1 Decisions recorded

| Decision | Outcome | Where it is recorded |
|---|---|---|
| ADR-0020: Filament for platform administration | **Accepted.** `/admin` for authorised platform staff only; vendor dashboard stays Vue 3 + Inertia; Filament uses Phase 2 design tokens; the admin is English-only; Filament delegates to use cases and never mutates status or financial data directly; Laravel 13 compatibility is verified in Phase 3, with the fallback retained | ADR-0020 (status line, "Owner decision", Decision §6–§7); `architecture/README.md` ADR index |
| P-1: account-required checkout | **Approved.** Guest browsing and guest cart are allowed; placing an order requires a signed-in account; inline registration at checkout; verified email required before order submission and payment initiation; checkout resumes after verification | PRD §6.3 P-1, §6.1; FR-ID-01, FR-CART-02, FR-CHK-01; journey CJ-1; ADR-0015 §1; security-model §5 |

### A.2 Briefing C7 (P-1 inline sign-up vs FR-ID-01 email verification): resolved

- **The contradiction:**
- P-1 promised inline sign-up on the checkout page.
- FR-ID-01 said unverified users are "redirected to verification" before checkout.
- The flow for a new customer was undefined.
- **The resolution:** verification is a step inside checkout, not a detour out of it.
- Inline registration signs the customer in and merges the guest cart.
- Checkout then shows a verification prompt with a resend action.
- The existing signed, 60-minute link (FR-ID-01) marks the email verified and returns the customer to checkout with the same cart.
- Placement and payment-attempt requests from unverified accounts are rejected before any write, in the routes and in the use cases.
- Stock is reserved only at placement, so verifying never uses up a reservation.
- **What was not changed:** the verification mechanism itself (signed link, 60-minute expiry). A one-time-code alternative was considered and not adopted.

### A.3 Consistency edits that follow from ADR-0020 (briefing C13)

ADR-0020 makes the admin English-only. Three statements implied translation of every page, so they are now scoped to the storefront and vendor dashboard:

- PRD §6.1 (i18n row);
- PRD §7 gate 7;
- NFR-I18N-01.

NFR-A11Y-01 was already scoped that way.

### A.4 Still open

- Every other §6 item: ADR-0005, ADR-0009–0019, ADR-0021, D-3, D-8–D-11, P-2–P-8, D-12 follow-up, D-13 step 3, D-15, tooling, and the React reference branch.
- ADR-0020 relies on `platform_role` and the permission enum from ADR-0011, which is still Proposed.
- The Phase 2 readiness items not decided here: provisional order-state vocabulary (ADR-0012), the late-payment transitions (briefing C1), and the Arabic numeral policy (ADR-0018 §3).
6 changes: 3 additions & 3 deletions docs/product/functional-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

| ID | P | Requirement | Acceptance criteria |
|---|---|---|---|
| FR-ID-01 | M | Visitors can register with name, email and password, and must verify their email before checkout | Unverified users are redirected to verification when they reach checkout; the verification link is signed and expires in 60 minutes |
| FR-ID-01 | M | Visitors can register with name, email and password, on the registration page or inline during checkout (P-1). The email must be verified before an order can be placed. A signed-in, unverified customer can request a new verification email. | **Registration:** both entry points apply the same validation and password rules (security-model §6). Success creates a customer account, signs the customer in, merges the guest cart (FR-CART-02) and sends one verification email. Inline registration returns the customer to checkout, not to a separate page.<br>**Verification:** the link is signed and expires in 60 minutes. Following a valid link marks the email verified. Following an expired or invalid link changes nothing; the page explains why and offers a resend.<br>**Resend:** available to a signed-in, unverified customer from the checkout verification prompt and from the account area. It is rate-limited (NFR-SEC-04). An already-verified customer is not sent another email.<br>**Enforcement:** an unverified customer cannot submit an order or initiate a payment (FR-CHK-01). |
| FR-ID-02 | M | Login, logout, password reset, with rate limiting | Login is throttled to 5 attempts per minute per email+IP; reset tokens are single-use and expire |
| FR-ID-03 | M | Customers manage profile and address book (Malaysian address format, zone derived from state) | Address state determines the shipping zone; at most one default address |
| FR-ID-04 | M | Platform staff accounts are created by an Admin, require 2FA, and cannot register publicly | A staff login without 2FA confirmation cannot reach `/admin` |
Expand Down Expand Up @@ -78,7 +78,7 @@
| ID | P | Requirement | Acceptance criteria |
|---|---|---|---|
| FR-CART-01 | M | Visitors and customers add variants from any number of vendors to one cart, grouped by vendor in the UI | |
| FR-CART-02 | M | A guest cart merges into the customer cart at login (quantities summed, capped at available) | Merge is idempotent |
| FR-CART-02 | M | A guest cart merges into the customer cart at login, including the automatic sign-in after registration (quantities summed, capped at available) | Merge is idempotent; after inline registration at checkout, the merged cart is the one shown in checkout |
| FR-CART-03 | M | The cart revalidates on view: price changes, unavailable items and quantity above available are flagged, and checkout is blocked until acknowledged | |
| FR-CART-04 | M | Quantity per line is limited to 1–20 (configurable) and to available stock | |
| FR-CART-05 | M | A cart is owned by exactly one session or customer; no endpoint accepts a cart id from the client | Test: another user's cart cannot be read or checked out |
Expand All @@ -87,7 +87,7 @@

| ID | P | Requirement | Acceptance criteria |
|---|---|---|---|
| FR-CHK-01 | M | Checkout requires a verified customer account (P-1) | |
| FR-CHK-01 | M | Placing an order requires a signed-in customer whose email is verified (P-1). Guests can start checkout only by signing in or registering inline. Unverified customers are prompted to verify, and resume checkout once verified. | **Guest:** starting checkout offers sign-in or inline registration. The guest cart is kept and merged on sign-in (FR-CART-02).<br>**Unverified customer:** checkout shows that verification is required, with a resend action (FR-ID-01), and the place-order action is unavailable.<br>**Server enforcement:** a place-order request (ADR-0015 step 1) or a payment-attempt request (step 2) from a guest or an unverified account is rejected before any write. No OrderGroup, reservation, promotion redemption, points hold or payment attempt is created, and nothing is charged. This holds even if the UI is bypassed (for example a stale tab or a direct request).<br>**Resume:** after following a valid verification link, the customer returns to checkout with the same cart. If the link is opened on another device, the email is still verified. The original device, still signed in, can then place the order with the same cart. Any device the customer signs in on shows the same cart, because the cart belongs to the account.<br>**Reservation:** no stock is reserved before placement, so time spent registering or verifying never uses up a reservation (FR-INV-03). |
| FR-CHK-02 | M | The customer selects a shipping address; the system quotes shipping per vendor and shows a breakdown per vendor and in total | |
| FR-CHK-03 | M | The customer may apply one coupon and redeem points; the system shows the allocation as a total discount | Invalid coupons give a reason code |
| FR-CHK-04 | M | Placing the order re-prices server-side; if the total differs from the confirmed total, nothing is charged and the customer reviews again | 409 test |
Expand Down
2 changes: 1 addition & 1 deletion docs/product/non-functional-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ These requirements are measurable. Where a target is an assumption rather than a

| ID | Requirement |
|---|---|
| NFR-I18N-01 | en, ar and ms have 100 % key coverage for the UI; CI fails on missing keys |
| NFR-I18N-01 | en, ar and ms have 100 % key coverage for the storefront and vendor dashboard UI; CI fails on missing keys. The platform admin is English-only in v2 (ADR-0020). |
| NFR-I18N-02 | RTL uses CSS logical properties and direction-aware components (icons, carousels, steppers, progress); no physical `left/right` utilities in components (lint rule) |
| NFR-I18N-03 | Currency, number and date formatting is locale-aware through `Intl`; dates display in MYT |
| NFR-I18N-04 | Locale in the storefront URL (`/ms/...`, `/ar/...`; `en` unprefixed) for shareable links; `hreflang` alternates |
Expand Down
Loading
Loading