- Hexagonal identity module: domain ports, use cases, argon2id hasher, pg repos - Migration 002_identity: identity_users + identity_sessions (token hash only) - Opaque 512-bit session tokens; DB stores SHA-256 hash; 7-day TTL in SQL - Cookie HttpOnly + Secure (COOKIE_SECURE, default true) + SameSite=Lax - LoginRateLimiter: 10 failures -> 429 + Retry-After, 15-min cooldown - Anti-enumeration: identical generic 401 + dummy-hash timing equalization - buildApp gains optional pool/cookieSecure; foundation-only app preserved - 47 unit + 14 integration tests; live smoke covers all acceptance criteria
1144 lines
45 KiB
JSON
1144 lines
45 KiB
JSON
{
|
|
"project": "mercadodevida-vnext",
|
|
"description": "Incremental SDD roadmap for MercadoDeVida vNext (modular monolith). Derived from project/design_prompt.md. One feature at a time, spec before code, gates before done.",
|
|
"rules": {
|
|
"one_feature_at_a_time": true,
|
|
"require_review_gate": true,
|
|
"require_security_gate": true,
|
|
"require_qa_gate": true,
|
|
"valid_status": [
|
|
"pending",
|
|
"in_progress",
|
|
"blocked",
|
|
"done"
|
|
],
|
|
"valid_types": [
|
|
"feature",
|
|
"fix",
|
|
"bug",
|
|
"chore"
|
|
]
|
|
},
|
|
"features": [
|
|
{
|
|
"id": "F-001",
|
|
"type": "chore",
|
|
"title": "Scaffold modular monolith skeleton",
|
|
"problem": "No codebase exists. Platform needs a boring, typed, modular home.",
|
|
"goal": "TypeScript modular monolith skeleton with strict module boundaries and green toolchain.",
|
|
"scope_in": [
|
|
"project/ layout: src/modules, src/shared, src/infrastructure, src/app",
|
|
"Node + TypeScript strict + Fastify app shell",
|
|
"ESLint + Prettier + typecheck + unit test runner",
|
|
"GET /health endpoint",
|
|
"Boundary lint rule: modules import only own folder or other modules public index"
|
|
],
|
|
"scope_out": [
|
|
"No business logic",
|
|
"No database",
|
|
"No frontend"
|
|
],
|
|
"priority": "high",
|
|
"risk": "low",
|
|
"depends_on": [],
|
|
"description": "Problem: No codebase exists. Platform needs a boring, typed, modular home. Goal: TypeScript modular monolith skeleton with strict module boundaries and green toolchain. Scope IN: project/ layout, Fastify + TS strict, toolchain, health endpoint, boundary rule. Scope OUT: no business logic, no DB. Type: chore. Priority: high. Risk: low.",
|
|
"acceptance": [
|
|
"install, build, lint, typecheck and test commands all green",
|
|
"GET /health returns HTTP 200 with status ok",
|
|
"src/modules, src/shared, src/infrastructure, src/app exist",
|
|
"A module importing another module internal file fails lint",
|
|
"verify.sh green"
|
|
],
|
|
"status": "done",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": true,
|
|
"security": true,
|
|
"qa": true
|
|
}
|
|
},
|
|
{
|
|
"id": "F-002",
|
|
"type": "chore",
|
|
"title": "Database foundation with module-owned schemas",
|
|
"problem": "Modules need PostgreSQL with clear ownership and safe migrations.",
|
|
"goal": "Migrations tooling, table naming convention per module, local dev database.",
|
|
"scope_in": [
|
|
"SQL migration tool (up/down, ordered, idempotent)",
|
|
"Naming convention: <module>_<table> (catalog_products, inventory_stock, ...)",
|
|
"docker-compose dev services: PostgreSQL + Redis",
|
|
"Migration test: fresh up then down then up",
|
|
"Rule: no schema change without migration"
|
|
],
|
|
"scope_out": [
|
|
"No business tables yet",
|
|
"No Redis usage beyond availability"
|
|
],
|
|
"priority": "high",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-001"
|
|
],
|
|
"description": "Problem: Modules need PostgreSQL with clear ownership and safe migrations. Goal: Migrations tooling, naming convention, local dev database. Scope IN: migration tool, naming convention, docker-compose, migration test. Scope OUT: no business tables. Type: chore. Priority: high. Risk: low.",
|
|
"acceptance": [
|
|
"Given a fresh database When migrations run Then schema is created and repeat run is a no-op",
|
|
"Given applied migrations When down runs Then schema rolls back cleanly",
|
|
"Table naming convention documented and enforced",
|
|
"Dev PostgreSQL and Redis start with one command",
|
|
"verify.sh green"
|
|
],
|
|
"status": "done",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": true,
|
|
"security": true,
|
|
"qa": true
|
|
}
|
|
},
|
|
{
|
|
"id": "F-003",
|
|
"type": "chore",
|
|
"title": "HTTP foundation and request context",
|
|
"problem": "Every request needs identity, structured logs and predictable errors.",
|
|
"goal": "request_id everywhere, JSON logs, one error envelope, no magic.",
|
|
"scope_in": [
|
|
"request_id generated or propagated on every request",
|
|
"Structured JSON logging with request_id",
|
|
"Single error response envelope",
|
|
"Input validation hook on API layer"
|
|
],
|
|
"scope_out": [
|
|
"No tracing backend yet",
|
|
"No metrics yet"
|
|
],
|
|
"priority": "high",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-001"
|
|
],
|
|
"description": "Problem: Every request needs identity, structured logs and predictable errors. Goal: request_id, JSON logs, error envelope. Scope IN: request context, logging, error envelope, validation hook. Scope OUT: no tracing or metrics backend. Type: chore. Priority: high. Risk: low.",
|
|
"acceptance": [
|
|
"Every response carries request_id",
|
|
"Every log line for a request carries the same request_id",
|
|
"Given an invalid request When handled Then error envelope shape is stable",
|
|
"Internal stack traces never leak to API responses",
|
|
"verify.sh green"
|
|
],
|
|
"status": "done",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": true,
|
|
"security": true,
|
|
"qa": true
|
|
}
|
|
},
|
|
{
|
|
"id": "F-004",
|
|
"type": "chore",
|
|
"title": "Typed config and feature flags",
|
|
"problem": "Risky features need activation separate from deployment; env access must be typed.",
|
|
"goal": "Fail-fast typed config plus simple feature flag module behind an interface.",
|
|
"scope_in": [
|
|
"Typed env config loader, fail fast on missing required vars",
|
|
"FeatureFlag interface with simple store implementation",
|
|
"Flags: deployment and activation are separate operations"
|
|
],
|
|
"scope_out": [
|
|
"No external flag service",
|
|
"No per-user segmentation yet"
|
|
],
|
|
"priority": "med",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-001"
|
|
],
|
|
"description": "Problem: Risky features need activation separate from deployment; env access must be typed. Goal: typed config + feature flag module. Scope IN: config loader, FeatureFlag interface, simple store. Scope OUT: no external flag service. Type: chore. Priority: med. Risk: low.",
|
|
"acceptance": [
|
|
"Given a missing required env var When app starts Then startup fails with clear message",
|
|
"Given flag off When code path guarded by flag runs Then path is skipped",
|
|
"Flag state change does not require redeploy",
|
|
"verify.sh green"
|
|
],
|
|
"status": "done",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": true,
|
|
"security": true,
|
|
"qa": true
|
|
}
|
|
},
|
|
{
|
|
"id": "F-005",
|
|
"type": "feature",
|
|
"title": "Identity: register, login, sessions",
|
|
"problem": "Customers need accounts; nothing trusts who calls the API.",
|
|
"goal": "Registration, login, logout with hashed passwords and secure sessions.",
|
|
"scope_in": [
|
|
"identity module: domain, application, infrastructure, api",
|
|
"Register, login, logout use cases",
|
|
"Argon2 password hashing",
|
|
"Secure session cookie: HttpOnly, Secure, SameSite",
|
|
"Login rate limiting"
|
|
],
|
|
"scope_out": [
|
|
"No MFA yet",
|
|
"No OAuth providers",
|
|
"No profile editing (users module)"
|
|
],
|
|
"priority": "high",
|
|
"risk": "med",
|
|
"depends_on": [
|
|
"F-002",
|
|
"F-003"
|
|
],
|
|
"description": "Problem: Customers need accounts; nothing trusts who calls the API. Goal: register/login/logout with hashed passwords and secure sessions. Scope IN: identity module, argon2, secure cookies, rate limit. Scope OUT: no MFA, no OAuth. Type: feature. Priority: high. Risk: med.",
|
|
"acceptance": [
|
|
"Given valid credentials When login Then HTTP 200 and secure session cookie set",
|
|
"Given wrong password When login Then HTTP 401 and no user enumeration hint",
|
|
"Passwords stored with argon2, never plaintext or reversible",
|
|
"Given 10 failed logins in a row When next login attempted Then HTTP 429",
|
|
"Session cookie is HttpOnly, Secure and SameSite",
|
|
"verify.sh green"
|
|
],
|
|
"status": "done",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": true,
|
|
"security": true,
|
|
"qa": true
|
|
}
|
|
},
|
|
{
|
|
"id": "F-006",
|
|
"type": "feature",
|
|
"title": "Users: profile, addresses, RBAC",
|
|
"problem": "Authenticated users need profile data, addresses and clear roles.",
|
|
"goal": "Users module with profile + address CRUD and customer/admin roles.",
|
|
"scope_in": [
|
|
"users module owning identity_users profile data and addresses",
|
|
"Profile and address CRUD behind use cases",
|
|
"Roles: customer, admin; RBAC guard on API layer",
|
|
"User can read/update only own data"
|
|
],
|
|
"scope_out": [
|
|
"No fine-grained permissions yet",
|
|
"No admin UI"
|
|
],
|
|
"priority": "med",
|
|
"risk": "med",
|
|
"depends_on": [
|
|
"F-005"
|
|
],
|
|
"description": "Problem: Authenticated users need profile data, addresses and clear roles. Goal: users module with profile, addresses, RBAC. Scope IN: users module, CRUD, roles, ownership guard. Scope OUT: no admin UI. Type: feature. Priority: med. Risk: med.",
|
|
"acceptance": [
|
|
"Given user A When A requests user B profile Then HTTP 403",
|
|
"Given customer role When admin-only endpoint called Then HTTP 403",
|
|
"Given admin role When admin-only endpoint called Then HTTP 200",
|
|
"Address CRUD works end to end for own addresses",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-007",
|
|
"type": "feature",
|
|
"title": "Categories module",
|
|
"problem": "Products need a navigable taxonomy with stable SEO URLs.",
|
|
"goal": "Category tree with unique slugs and SEO metadata.",
|
|
"scope_in": [
|
|
"categories module with tree structure",
|
|
"Unique slug per category, stable URL /categoria/<slug>",
|
|
"SEO metadata fields: title, description",
|
|
"Category CRUD via use cases"
|
|
],
|
|
"scope_out": [
|
|
"No product assignment yet (catalog core ticket)",
|
|
"No storefront rendering yet"
|
|
],
|
|
"priority": "high",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-002"
|
|
],
|
|
"description": "Problem: Products need a navigable taxonomy with stable SEO URLs. Goal: category tree with slugs and SEO metadata. Scope IN: categories module, tree, slugs, metadata. Scope OUT: no product assignment, no storefront. Type: feature. Priority: high. Risk: low.",
|
|
"acceptance": [
|
|
"Given duplicate slug When category created Then HTTP 409",
|
|
"Category tree supports parent/child and blocks cycles",
|
|
"Public URL is /categoria/<slug>, never internal id",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-008",
|
|
"type": "feature",
|
|
"title": "Catalog core: products domain",
|
|
"problem": "There is no product model; everything else depends on it.",
|
|
"goal": "Product aggregate with use cases, slugs, states and SEO metadata.",
|
|
"scope_in": [
|
|
"catalog module: domain, application, infrastructure, api",
|
|
"CreateProduct, UpdateProduct, SearchProducts use cases",
|
|
"Product states: draft, active, archived",
|
|
"Unique slug, stable URL /productos/<slug>",
|
|
"SEO metadata per product",
|
|
"Product-category assignment"
|
|
],
|
|
"scope_out": [
|
|
"No variants yet",
|
|
"No stock, no prices"
|
|
],
|
|
"priority": "high",
|
|
"risk": "med",
|
|
"depends_on": [
|
|
"F-002",
|
|
"F-007"
|
|
],
|
|
"description": "Problem: There is no product model; everything else depends on it. Goal: product aggregate with use cases, slugs, states, SEO metadata. Scope IN: catalog module, CRUD use cases, states, slugs, category assignment. Scope OUT: no variants, stock, prices. Type: feature. Priority: high. Risk: med.",
|
|
"acceptance": [
|
|
"Domain layer has zero database or HTTP imports",
|
|
"Given duplicate slug When product created Then HTTP 409",
|
|
"Only active products appear in public listings",
|
|
"Public URL is /productos/<slug>, never internal id",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-009",
|
|
"type": "feature",
|
|
"title": "Brands module",
|
|
"problem": "Products need brands for navigation, filtering and SEO pages.",
|
|
"goal": "Brand entity with slug and SEO page data.",
|
|
"scope_in": [
|
|
"brands module or catalog-owned brands with clear ownership",
|
|
"Unique slug, stable URL /marca/<slug>",
|
|
"Product-brand assignment"
|
|
],
|
|
"scope_out": [
|
|
"No brand storefront page yet"
|
|
],
|
|
"priority": "med",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-002",
|
|
"F-008"
|
|
],
|
|
"description": "Problem: Products need brands for navigation, filtering and SEO pages. Goal: brand entity with slug and SEO data. Scope IN: brands, slugs, product assignment. Scope OUT: no storefront page yet. Type: feature. Priority: med. Risk: low.",
|
|
"acceptance": [
|
|
"Given duplicate brand slug When created Then HTTP 409",
|
|
"Products list filterable by brand",
|
|
"Public URL is /marca/<slug>",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-010",
|
|
"type": "feature",
|
|
"title": "Variants, SKU/EAN and product rich data",
|
|
"problem": "One product has many sellable variants; nutrition and allergens must be trusted data.",
|
|
"goal": "Variants with SKU/EAN plus attributes, ingredients, allergens, nutrition with provenance.",
|
|
"scope_in": [
|
|
"Product variants with unique SKU and EAN",
|
|
"Attributes per variant",
|
|
"Ingredients, allergens, nutrition information",
|
|
"Organic/ecological certification fields",
|
|
"Provenance tracking: manual, manufacturer, openfoodfacts",
|
|
"External source never overwrites trusted internal data without validation"
|
|
],
|
|
"scope_out": [
|
|
"No OpenFoodFacts sync job yet",
|
|
"No images yet"
|
|
],
|
|
"priority": "high",
|
|
"risk": "med",
|
|
"depends_on": [
|
|
"F-008"
|
|
],
|
|
"description": "Problem: One product has many sellable variants; nutrition and allergens must be trusted data. Goal: variants with SKU/EAN, attributes, ingredients, allergens, nutrition with provenance. Scope IN: variants, rich data, provenance rules. Scope OUT: no OpenFoodFacts sync. Type: feature. Priority: high. Risk: med.",
|
|
"acceptance": [
|
|
"Given duplicate SKU or EAN When variant created Then HTTP 409",
|
|
"Given field with nutrition_source=manual When external source pushes same field Then internal value kept",
|
|
"Every nutrition payload stores its provenance",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-011",
|
|
"type": "feature",
|
|
"title": "Product images",
|
|
"problem": "Products need images for storefront and SEO.",
|
|
"goal": "Image attachment per product/variant behind a storage interface.",
|
|
"scope_in": [
|
|
"Image entity: url, alt text, ordering, role (main/gallery)",
|
|
"Storage adapter behind interface (local first)",
|
|
"Attach/detach/reorder via use cases"
|
|
],
|
|
"scope_out": [
|
|
"No CDN or image processing pipeline yet"
|
|
],
|
|
"priority": "med",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-010"
|
|
],
|
|
"description": "Problem: Products need images for storefront and SEO. Goal: image attachment per product/variant behind a storage interface. Scope IN: image entity, storage adapter, ordering. Scope OUT: no CDN pipeline. Type: feature. Priority: med. Risk: low.",
|
|
"acceptance": [
|
|
"Product exposes ordered image list with alt text",
|
|
"Swapping storage adapter touches only infrastructure layer",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-012",
|
|
"type": "feature",
|
|
"title": "Search: interface plus PostgreSQL FTS",
|
|
"problem": "Customers must find products; search backend must stay replaceable.",
|
|
"goal": "ProductSearch interface with PostgreSQL full text search implementation.",
|
|
"scope_in": [
|
|
"ProductSearch interface",
|
|
"PostgreSQL FTS implementation over name, brand, category",
|
|
"Basic relevance ordering and pagination",
|
|
"Popular searches logged for later cache use"
|
|
],
|
|
"scope_out": [
|
|
"No Elasticsearch/Meilisearch/Algolia yet",
|
|
"No typo tolerance beyond FTS defaults"
|
|
],
|
|
"priority": "high",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-008",
|
|
"F-009"
|
|
],
|
|
"description": "Problem: Customers must find products; search backend must stay replaceable. Goal: ProductSearch interface with PostgreSQL FTS implementation. Scope IN: interface, FTS adapter, pagination. Scope OUT: no external search engine. Type: feature. Priority: high. Risk: low.",
|
|
"acceptance": [
|
|
"Given product matching query When search Then product returned with stable ordering",
|
|
"Swapping implementation requires no API contract change",
|
|
"Search latency measured and logged",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-013",
|
|
"type": "feature",
|
|
"title": "Storefront shell (Next.js)",
|
|
"problem": "There is no customer-facing site.",
|
|
"goal": "Next.js + TypeScript + Tailwind shell consuming backend public API.",
|
|
"scope_in": [
|
|
"Next.js app with React, TypeScript, Tailwind",
|
|
"Layout, navigation, home page",
|
|
"Server Components by default; client code only where interaction needs it",
|
|
"Typed API client for backend public endpoints"
|
|
],
|
|
"scope_out": [
|
|
"No catalog pages yet",
|
|
"No cart UI yet"
|
|
],
|
|
"priority": "high",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-008"
|
|
],
|
|
"description": "Problem: There is no customer-facing site. Goal: Next.js + TS + Tailwind shell consuming backend API. Scope IN: app shell, navigation, typed API client. Scope OUT: no catalog pages. Type: feature. Priority: high. Risk: low.",
|
|
"acceptance": [
|
|
"Home renders server-side with navigation",
|
|
"Frontend never imports backend internals, only typed API client",
|
|
"Build, lint and typecheck green",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-014",
|
|
"type": "feature",
|
|
"title": "Storefront catalog pages (SSG/ISR)",
|
|
"problem": "Products, categories and brands need fast SEO-friendly public pages.",
|
|
"goal": "SSG/ISR pages for product, category and brand with stable URLs and metadata.",
|
|
"scope_in": [
|
|
"Product detail page at /productos/<slug>",
|
|
"Category listing at /categoria/<slug>",
|
|
"Brand page at /marca/<slug>",
|
|
"SSG/ISR rendering with on-demand revalidation",
|
|
"Per-page metadata and OpenGraph",
|
|
"Search results page"
|
|
],
|
|
"scope_out": [
|
|
"No cart, no checkout UI"
|
|
],
|
|
"priority": "high",
|
|
"risk": "med",
|
|
"depends_on": [
|
|
"F-010",
|
|
"F-012",
|
|
"F-013"
|
|
],
|
|
"description": "Problem: Products, categories and brands need fast SEO-friendly public pages. Goal: SSG/ISR catalog pages with stable URLs and metadata. Scope IN: product/category/brand/search pages, ISR, metadata, OG. Scope OUT: no commerce UI. Type: feature. Priority: high. Risk: med.",
|
|
"acceptance": [
|
|
"Product page served at /productos/<slug> with no internal id in URL",
|
|
"Given catalog update When revalidation triggered Then page reflects new data",
|
|
"Each page carries title, description and OpenGraph metadata",
|
|
"Lighthouse SEO score >= 90 on product and category pages",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-015",
|
|
"type": "feature",
|
|
"title": "SEO core: structured data, sitemap, redirects",
|
|
"problem": "Organic traffic is core business; structured data and crawlability are missing.",
|
|
"goal": "JSON-LD schemas, sitemap.xml, robots.txt and redirect management.",
|
|
"scope_in": [
|
|
"seo module",
|
|
"Product, Breadcrumb and Organization JSON-LD",
|
|
"sitemap.xml and robots.txt generation",
|
|
"Redirect store behind interface with 301 support",
|
|
"Canonical URLs on all public pages"
|
|
],
|
|
"scope_out": [
|
|
"No hreflang",
|
|
"No external SEO tooling integration"
|
|
],
|
|
"priority": "high",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-014"
|
|
],
|
|
"description": "Problem: Organic traffic is core business; structured data and crawlability are missing. Goal: JSON-LD, sitemap, robots, redirects. Scope IN: seo module, schemas, sitemap, robots, redirect store. Scope OUT: no hreflang. Type: feature. Priority: high. Risk: low.",
|
|
"acceptance": [
|
|
"Product page embeds valid Product JSON-LD (validator clean)",
|
|
"sitemap.xml lists active public URLs and excludes draft content",
|
|
"Given stored redirect When old URL requested Then HTTP 301 to new URL",
|
|
"Every public page has canonical URL",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-016",
|
|
"type": "feature",
|
|
"title": "Inventory module",
|
|
"problem": "Catalog says what a product is; nothing says if it can be sold.",
|
|
"goal": "Isolated inventory module with atomic stock operations and availability API.",
|
|
"scope_in": [
|
|
"inventory module, fully isolated from catalog",
|
|
"Stock states: available, reserved, sold, incoming",
|
|
"InventoryService.checkAvailability() public interface",
|
|
"Atomic reserve, release, confirm operations",
|
|
"Negative stock impossible at database and domain level"
|
|
],
|
|
"scope_out": [
|
|
"No warehouse/supplier integration",
|
|
"No incoming purchase orders UI"
|
|
],
|
|
"priority": "high",
|
|
"risk": "med",
|
|
"depends_on": [
|
|
"F-002"
|
|
],
|
|
"description": "Problem: Catalog says what a product is; nothing says if it can be sold. Goal: isolated inventory with atomic stock ops and availability API. Scope IN: inventory module, stock states, atomic reserve/release/confirm, no-negative rule. Scope OUT: no warehouse integration. Type: feature. Priority: high. Risk: med.",
|
|
"acceptance": [
|
|
"Given 10 concurrent reservations for the last unit When all settle Then exactly 1 succeeds and 9 get unavailable",
|
|
"Given zero stock When reservation requested Then rejected and stock never negative",
|
|
"Catalog module contains zero references to inventory tables",
|
|
"Checkout checks stock only through InventoryService",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-017",
|
|
"type": "feature",
|
|
"title": "Pricing module",
|
|
"problem": "Nothing calculates real prices and taxes; frontend must never supply them.",
|
|
"goal": "Server-side pricing with VAT behind PricingService.",
|
|
"scope_in": [
|
|
"pricing module owning price and tax rules",
|
|
"Price per variant with VAT (Spain general/reduced rates)",
|
|
"PricingService.calculate() public interface",
|
|
"Price history table for audit"
|
|
],
|
|
"scope_out": [
|
|
"No promotions yet",
|
|
"No multi-currency"
|
|
],
|
|
"priority": "high",
|
|
"risk": "med",
|
|
"depends_on": [
|
|
"F-002",
|
|
"F-010"
|
|
],
|
|
"description": "Problem: Nothing calculates real prices and taxes; frontend must never supply them. Goal: server-side pricing with VAT behind PricingService. Scope IN: pricing module, VAT, calculate interface, price history. Scope OUT: no promotions, no multi-currency. Type: feature. Priority: high. Risk: med.",
|
|
"acceptance": [
|
|
"Given variant and quantity When PricingService.calculate Then total with VAT returned",
|
|
"Given client-supplied price in any request Then price ignored and recalculated",
|
|
"Price change writes history row, never silent update",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-018",
|
|
"type": "feature",
|
|
"title": "Cart module",
|
|
"problem": "Customers need a cart that never lies about prices or stock.",
|
|
"goal": "Cart with items and server-side recalculation of totals.",
|
|
"scope_in": [
|
|
"cart module storing product_id, variant_id, quantity only",
|
|
"Add, remove, change quantity use cases",
|
|
"Totals always recalculated via PricingService and InventoryService",
|
|
"Stored prices never trusted"
|
|
],
|
|
"scope_out": [
|
|
"No guest cart persistence across devices yet",
|
|
"No promotions applied yet"
|
|
],
|
|
"priority": "high",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-016",
|
|
"F-017"
|
|
],
|
|
"description": "Problem: Customers need a cart that never lies about prices or stock. Goal: cart with items and server-side recalculation. Scope IN: cart module, item ops, recalculation via Pricing/Inventory. Scope OUT: no promotions yet. Type: feature. Priority: high. Risk: low.",
|
|
"acceptance": [
|
|
"Given product price changed after add to cart When cart read Then new price shown",
|
|
"Given variant out of stock When cart read Then item flagged unavailable",
|
|
"Cart payload from client containing price fields is ignored",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-019",
|
|
"type": "feature",
|
|
"title": "Promotions v1",
|
|
"problem": "Store needs discounts; discount logic must be validated server-side.",
|
|
"goal": "Simple promotions engine behind an interface, validated at recalculation.",
|
|
"scope_in": [
|
|
"promotions module with percent and fixed-amount rules",
|
|
"Promo codes with validity window and usage limits",
|
|
"Discount validation inside recalculation, never from frontend",
|
|
"No stacking unless rule explicitly allows"
|
|
],
|
|
"scope_out": [
|
|
"No loyalty points",
|
|
"No buy-X-get-Y rules yet"
|
|
],
|
|
"priority": "med",
|
|
"risk": "med",
|
|
"depends_on": [
|
|
"F-017",
|
|
"F-018"
|
|
],
|
|
"description": "Problem: Store needs discounts; discount logic must be validated server-side. Goal: simple promotions engine behind interface, validated at recalculation. Scope IN: percent/fixed rules, promo codes, validity, no frontend trust. Scope OUT: no loyalty. Type: feature. Priority: med. Risk: med.",
|
|
"acceptance": [
|
|
"Given valid promo code When applied Then discount recalculated server-side",
|
|
"Given expired or exhausted code When applied Then HTTP 422 with reason",
|
|
"Given client-supplied discount amount Then discount ignored",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-020",
|
|
"type": "feature",
|
|
"title": "Shipping module",
|
|
"problem": "Checkout cannot quote delivery cost.",
|
|
"goal": "Shipping zones, methods and cost calculation behind ShippingService.",
|
|
"scope_in": [
|
|
"shipping module with zones and methods",
|
|
"ShippingService.calculate(cart, address) interface",
|
|
"Free shipping threshold rule",
|
|
"Carrier adapter interface (implementation later)"
|
|
],
|
|
"scope_out": [
|
|
"No real carrier API integration yet",
|
|
"No tracking"
|
|
],
|
|
"priority": "med",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-002",
|
|
"F-018"
|
|
],
|
|
"description": "Problem: Checkout cannot quote delivery cost. Goal: zones, methods and cost calculation behind ShippingService. Scope IN: shipping module, zones, methods, threshold, adapter interface. Scope OUT: no carrier API. Type: feature. Priority: med. Risk: low.",
|
|
"acceptance": [
|
|
"Given address in known zone When calculate Then shipping cost returned",
|
|
"Given address outside all zones When checkout Then HTTP 422 with clear reason",
|
|
"Given cart above free shipping threshold Then shipping cost is zero",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-021",
|
|
"type": "feature",
|
|
"title": "Orders module with snapshots and state machine",
|
|
"problem": "Nothing records purchase truth; history must never depend on live catalog.",
|
|
"goal": "Order aggregate with item snapshots and explicit state transitions.",
|
|
"scope_in": [
|
|
"orders module",
|
|
"Order items snapshot: name, SKU, EAN, unit price, discount, tax, quantity",
|
|
"States: PENDING, AWAITING_PAYMENT, PAID, PROCESSING, SHIPPED, DELIVERED, CANCELLED, REFUNDED, PARTIALLY_REFUNDED",
|
|
"Explicit state machine, illegal transitions rejected",
|
|
"Domain events: OrderCreated, OrderPaid, OrderCancelled"
|
|
],
|
|
"scope_out": [
|
|
"No refunds execution yet (payments ticket)",
|
|
"No admin order UI"
|
|
],
|
|
"priority": "high",
|
|
"risk": "med",
|
|
"depends_on": [
|
|
"F-002",
|
|
"F-017"
|
|
],
|
|
"description": "Problem: Nothing records purchase truth; history must never depend on live catalog. Goal: order aggregate with item snapshots and explicit state machine. Scope IN: orders module, snapshots, state machine, domain events. Scope OUT: no refunds execution, no admin UI. Type: feature. Priority: high. Risk: med.",
|
|
"acceptance": [
|
|
"Given an order When product is later renamed or repriced Then order keeps original snapshot values",
|
|
"Given order in SHIPPED When transition to PENDING requested Then transition rejected",
|
|
"Every legal state transition is covered by unit test",
|
|
"OrderCreated event published on creation",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-022",
|
|
"type": "feature",
|
|
"title": "Checkout orchestrator with idempotency",
|
|
"problem": "No flow turns a cart into a payable order safely.",
|
|
"goal": "Checkout coordinates validation, pricing, stock, discounts, shipping, taxes, order creation and reservation.",
|
|
"scope_in": [
|
|
"checkout module as orchestrator only, owning no business data",
|
|
"Flow: validate products, prices, stock, discounts, shipping, taxes, create pending order, reserve inventory, create payment intent",
|
|
"idempotency_key required on checkout request",
|
|
"HTTP 409 on unavailable stock without order or payment",
|
|
"checkout_success_total and checkout_failure_total metrics"
|
|
],
|
|
"scope_out": [
|
|
"No payment provider implementation yet (interface stub)",
|
|
"No checkout UI yet"
|
|
],
|
|
"priority": "high",
|
|
"risk": "high",
|
|
"depends_on": [
|
|
"F-018",
|
|
"F-019",
|
|
"F-020",
|
|
"F-021"
|
|
],
|
|
"description": "Problem: No flow turns a cart into a payable order safely. Goal: checkout orchestrates validation, pricing, stock, discounts, shipping, taxes, pending order, reservation, payment intent. Scope IN: orchestrator flow, idempotency_key, 409 handling, metrics. Scope OUT: no provider implementation, no UI. Type: feature. Priority: high. Risk: high.",
|
|
"acceptance": [
|
|
"Given a cart containing an unavailable item When checkout requested Then HTTP 409, no order created, no payment initiated",
|
|
"Given same idempotency_key sent twice When second request arrives Then same order returned, no duplicate order, no duplicate reservation",
|
|
"Given successful checkout Then order exists in AWAITING_PAYMENT and stock is reserved",
|
|
"Given checkout failure after reservation When flow aborts Then reservation released",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-023",
|
|
"type": "feature",
|
|
"title": "Payments: provider interface + Stripe + webhooks",
|
|
"problem": "Checkout needs real money movement without coupling domain to Stripe.",
|
|
"goal": "PaymentProvider interface with Stripe adapter and idempotent webhook processing.",
|
|
"scope_in": [
|
|
"payments module with PaymentProvider interface",
|
|
"StripePaymentProvider adapter; domain never imports Stripe SDK",
|
|
"Webhook: validate signature, deduplicate event, process, publish domain event",
|
|
"Events: PaymentSucceeded, PaymentFailed, PaymentRefunded, ChargebackCreated",
|
|
"payments_transactions table as source of truth"
|
|
],
|
|
"scope_out": [
|
|
"No Redsys/PayPal yet",
|
|
"No payout reconciliation"
|
|
],
|
|
"priority": "high",
|
|
"risk": "high",
|
|
"depends_on": [
|
|
"F-022"
|
|
],
|
|
"description": "Problem: Checkout needs real money movement without coupling domain to Stripe. Goal: PaymentProvider interface, Stripe adapter, idempotent webhooks. Scope IN: interface, adapter, webhook validation/dedup, domain events. Scope OUT: no Redsys/PayPal. Type: feature. Priority: high. Risk: high.",
|
|
"acceptance": [
|
|
"Domain code contains zero direct Stripe SDK imports",
|
|
"Given webhook with bad signature When received Then rejected with non-2xx and not processed",
|
|
"Given same webhook delivered twice When second delivery arrives Then processed exactly once",
|
|
"Given PaymentSucceeded When processed Then order moves to PAID and event published",
|
|
"Payment status from frontend is never trusted",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-024",
|
|
"type": "feature",
|
|
"title": "Notifications: transactional email",
|
|
"problem": "Customers get no feedback after purchase or payment failure.",
|
|
"goal": "Transactional emails driven by domain events behind a provider interface.",
|
|
"scope_in": [
|
|
"notifications module",
|
|
"Email provider interface with one implementation",
|
|
"Templates: order confirmation, payment failed, order shipped",
|
|
"Send triggered by domain events, idempotent per event id"
|
|
],
|
|
"scope_out": [
|
|
"No marketing email",
|
|
"No SMS/push"
|
|
],
|
|
"priority": "med",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-023",
|
|
"F-005"
|
|
],
|
|
"description": "Problem: Customers get no feedback after purchase or payment failure. Goal: transactional emails driven by domain events behind provider interface. Scope IN: notifications module, email adapter, templates, idempotent sends. Scope OUT: no marketing, no SMS. Type: feature. Priority: med. Risk: low.",
|
|
"acceptance": [
|
|
"Given PaymentSucceeded event When processed Then order confirmation email queued",
|
|
"Given same event redelivered When processed Then no duplicate email",
|
|
"Swapping email provider touches only infrastructure adapter",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-025",
|
|
"type": "feature",
|
|
"title": "Reviews module",
|
|
"problem": "Social proof is missing; reviews must be tied to real purchases.",
|
|
"goal": "Verified-purchase reviews with moderation and rating aggregates.",
|
|
"scope_in": [
|
|
"reviews module",
|
|
"One review per order item, verified purchase only",
|
|
"Moderation states: pending, published, rejected",
|
|
"Rating aggregate per product, cached"
|
|
],
|
|
"scope_out": [
|
|
"No photos in reviews",
|
|
"No vendor responses"
|
|
],
|
|
"priority": "low",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-021",
|
|
"F-006"
|
|
],
|
|
"description": "Problem: Social proof is missing; reviews must be tied to real purchases. Goal: verified-purchase reviews with moderation and aggregates. Scope IN: reviews module, verified purchase rule, moderation, aggregates. Scope OUT: no photos. Type: feature. Priority: low. Risk: low.",
|
|
"acceptance": [
|
|
"Given user without delivered order item When review submitted Then HTTP 403",
|
|
"Given second review for same order item When submitted Then HTTP 409",
|
|
"Only published reviews appear on product page",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-026",
|
|
"type": "feature",
|
|
"title": "CMS module",
|
|
"problem": "Marketing needs landing and content pages without deploys.",
|
|
"goal": "Simple CMS with pages, content blocks, slugs and draft/publish states.",
|
|
"scope_in": [
|
|
"cms module with page and block model",
|
|
"Slug-based public routes",
|
|
"Draft/publish states",
|
|
"Storefront renders published pages"
|
|
],
|
|
"scope_out": [
|
|
"No WYSIWYG builder",
|
|
"No versioning"
|
|
],
|
|
"priority": "low",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-013"
|
|
],
|
|
"description": "Problem: Marketing needs landing and content pages without deploys. Goal: simple CMS with pages, blocks, slugs, draft/publish. Scope IN: cms module, slug routes, states, rendering. Scope OUT: no builder. Type: feature. Priority: low. Risk: low.",
|
|
"acceptance": [
|
|
"Given published page When slug requested Then page renders",
|
|
"Given draft page When slug requested Then HTTP 404",
|
|
"Given duplicate slug When page created Then HTTP 409",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-027",
|
|
"type": "feature",
|
|
"title": "Caching layer with explicit contracts",
|
|
"problem": "Hot catalog pages hit the database on every request.",
|
|
"goal": "Redis cache for product detail, category listing and navigation with key, TTL and invalidation.",
|
|
"scope_in": [
|
|
"Cache wrapper with key, TTL, invalidation strategy and source of truth documented per entry",
|
|
"Targets: product detail, category listing, navigation, popular searches",
|
|
"Invalidation on catalog update events",
|
|
"cache_hit_ratio metric"
|
|
],
|
|
"scope_out": [
|
|
"Redis never source of truth",
|
|
"No CDN config yet"
|
|
],
|
|
"priority": "med",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-014"
|
|
],
|
|
"description": "Problem: Hot catalog pages hit the database on every request. Goal: Redis cache for hot reads with explicit key/TTL/invalidation contracts. Scope IN: cache wrapper, hot targets, event invalidation, hit ratio metric. Scope OUT: Redis never truth, no CDN. Type: feature. Priority: med. Risk: low.",
|
|
"acceptance": [
|
|
"Every cache entry documents key pattern, TTL, invalidation and source of truth",
|
|
"Given catalog update When event published Then related cache entries invalidated",
|
|
"Given Redis down When read path runs Then requests still succeed from PostgreSQL",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-028",
|
|
"type": "feature",
|
|
"title": "Security hardening",
|
|
"problem": "Baseline security controls are scattered or missing.",
|
|
"goal": "Rate limiting, CSRF, audit log, admin MFA and dependency scanning in one pass.",
|
|
"scope_in": [
|
|
"Global and per-route rate limiting",
|
|
"CSRF protection where applicable",
|
|
"Audit log for admin mutations",
|
|
"MFA for admin accounts",
|
|
"Dependency scanning in CI"
|
|
],
|
|
"scope_out": [
|
|
"No WAF/network-level work",
|
|
"No pentest"
|
|
],
|
|
"priority": "med",
|
|
"risk": "med",
|
|
"depends_on": [
|
|
"F-006"
|
|
],
|
|
"description": "Problem: Baseline security controls are scattered or missing. Goal: rate limiting, CSRF, audit log, admin MFA, dependency scanning. Scope IN: all listed controls. Scope OUT: no WAF, no pentest. Type: feature. Priority: med. Risk: med.",
|
|
"acceptance": [
|
|
"Given admin mutation When executed Then audit log row with actor, action and target",
|
|
"Given admin login without MFA When attempted Then blocked until MFA enrolled",
|
|
"Given request above rate limit Then HTTP 429",
|
|
"CI fails on known vulnerable dependency",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-029",
|
|
"type": "feature",
|
|
"title": "Observability: traces and business metrics",
|
|
"problem": "Operations cannot see checkout health or latency.",
|
|
"goal": "OpenTelemetry traces plus Prometheus business metrics.",
|
|
"scope_in": [
|
|
"OpenTelemetry tracing across modules",
|
|
"Metrics: checkout_success_total, checkout_failure_total, payment_failure_total, order_created_total, inventory_conflict_total, api_latency, database_latency, cache_hit_ratio",
|
|
"request_id and trace_id on every request",
|
|
"Grafana dashboard baseline"
|
|
],
|
|
"scope_out": [
|
|
"No alerting rules beyond basics",
|
|
"No log aggregation infra"
|
|
],
|
|
"priority": "med",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-023"
|
|
],
|
|
"description": "Problem: Operations cannot see checkout health or latency. Goal: OTel traces plus Prometheus business metrics. Scope IN: tracing, listed metrics, ids, dashboard. Scope OUT: no full alerting stack. Type: feature. Priority: med. Risk: low.",
|
|
"acceptance": [
|
|
"Every request carries request_id and trace_id in logs",
|
|
"Each listed metric is exposed on /metrics and changes on the corresponding event",
|
|
"A checkout flow produces a connected trace across modules",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
},
|
|
{
|
|
"id": "F-030",
|
|
"type": "feature",
|
|
"title": "E2E suite for critical flows",
|
|
"problem": "Critical user journeys have no end-to-end protection.",
|
|
"goal": "Few but real E2E tests covering the money path.",
|
|
"scope_in": [
|
|
"E2E runner setup",
|
|
"Flows: register, login, search, view product, add to cart, checkout, payment, order confirmation, refund",
|
|
"Runs against disposable environment"
|
|
],
|
|
"scope_out": [
|
|
"No visual regression",
|
|
"No load testing"
|
|
],
|
|
"priority": "high",
|
|
"risk": "low",
|
|
"depends_on": [
|
|
"F-022",
|
|
"F-023"
|
|
],
|
|
"description": "Problem: Critical user journeys have no end-to-end protection. Goal: E2E tests covering the money path. Scope IN: runner, critical flows, disposable env. Scope OUT: no visual regression, no load testing. Type: feature. Priority: high. Risk: low.",
|
|
"acceptance": [
|
|
"Each critical flow has one green E2E test",
|
|
"E2E suite runs unattended in CI",
|
|
"Given payment webhook simulated in E2E Then order reaches PAID",
|
|
"verify.sh green"
|
|
],
|
|
"status": "pending",
|
|
"created_at": "2026-08-14",
|
|
"gates": {
|
|
"review": false,
|
|
"security": false,
|
|
"qa": false
|
|
}
|
|
}
|
|
]
|
|
}
|