feat(ADM-018): completed feature

This commit is contained in:
chattie
2026-08-17 22:23:10 +02:00
parent cf1c69fc8b
commit d595b4871f
871 changed files with 47411 additions and 281 deletions

View File

@@ -0,0 +1,53 @@
# Architect — F-010 Variants, SKU/EAN and product rich data
done -> work/artifacts/F-010/architect.md
## Deliverables
- Extend `src/modules/catalog/` with product variants and rich product data.
- PostgreSQL migration for `catalog_product_variants` and `catalog_product_rich_data`.
- Unit and integration tests for duplicate SKU/EAN, nutrition provenance persistence, and manual nutrition protection.
## Key decisions
1. **Catalog owns variants and rich data**: variants are part of the product aggregate in this slice. Do not create a new module yet; inventory/pricing later reference variant IDs through catalog public contracts.
2. **Variants are product-scoped sellable identities**: `catalog_product_variants` has `product_id`, `sku`, `ean`, `attributes jsonb`, timestamps. `sku` and `ean` are globally unique when present. `ean` can be nullable if a product lacks barcode, but duplicate non-null EAN must conflict.
3. **Rich data is product-level in v1**: ingredients, allergens, nutrition, organic/ecological certification live in `catalog_product_rich_data` keyed by `product_id`. Variant-specific rich data can come later if needed.
4. **Provenance is explicit and stored with nutrition**: allowed nutrition sources are `manual`, `manufacturer`, `openfoodfacts`. Store `nutrition_source` plus `nutrition jsonb` together. Every nutrition payload must have a source.
5. **Trusted internal data wins**: when current `nutrition_source = 'manual'`, updates from `manufacturer` or `openfoodfacts` must not overwrite `nutrition` or `nutrition_source`. Return the existing row unchanged for those fields. Manual can overwrite any source; manufacturer/openfoodfacts can overwrite each other unless current source is manual.
6. **No OpenFoodFacts sync job**: this slice exposes an explicit update use case/API that can receive external-source payloads, but no scheduled import.
7. **No images, no prices, no stock**: those remain out of scope. Do not add sellable pricing/inventory semantics.
8. **No new dependencies**: use existing Zod/pg/Vitest.
## Suggested API contract
- `POST /products/:id/variants` → admin-only create variant; duplicate SKU/EAN returns `409 PRODUCT_VARIANT_CODE_EXISTS`.
- `PATCH /products/:id/variants/:variantId` → admin-only update variant attributes/SKU/EAN.
- `GET /productos/:slug` should include ordered `variants` and rich data summary if available.
- `PATCH /products/:id/rich-data` → admin-only upsert rich data. Payload includes optional `ingredients`, `allergens`, certification fields, and `nutrition` + required `nutritionSource` when nutrition is present.
## Domain model additions
- `ProductVariant`: `id`, `productId`, `sku`, `ean`, `attributes`, `createdAt`, `updatedAt`.
- `NewProductVariant`: `sku`, optional nullable `ean`, optional `attributes` record.
- `ProductRichData`: `productId`, `ingredients`, `allergens`, `nutrition`, `nutritionSource`, `isOrganic`, `organicCertification`, `createdAt`, `updatedAt`.
- `NutritionSource = 'manual' | 'manufacturer' | 'openfoodfacts'`.
## Error mapping
- Duplicate SKU or EAN → `409 PRODUCT_VARIANT_CODE_EXISTS`.
- Missing product/variant → `404 NOT_FOUND`.
- Nutrition payload without source → `400 VALIDATION_ERROR`.
## Test plan
- Unit: manual nutrition source blocks external overwrite and preserves existing manual nutrition.
- Unit: external source can update when current source is not manual.
- Integration/API: duplicate SKU returns 409.
- Integration/API: duplicate EAN returns 409.
- Integration/API: nutrition rich data stores payload and provenance.
- Integration/API: external update after manual source leaves manual nutrition unchanged.
## Security posture
- All mutations are admin-only via injected shared auth and `requireRole`.
- Public product reads expose only catalog data intended for storefront display.
- Variant attributes and nutrition JSON are validated as bounded records/objects; no arbitrary huge payloads.
- SQL remains parameterized; dynamic update columns must be whitelisted.
## Risks
- JSONB payloads can become a dumping ground. Keep fields explicit and bounded at API validation.
- Manual-source protection must happen in application/use-case logic, not only in route code, so future import jobs cannot bypass it.

View File

@@ -0,0 +1,19 @@
# Documenter — F-010 Variants, SKU/EAN and product rich data
done -> work/artifacts/F-010/documenter.md
## Documentation updated
- `project/README.md`
## Covered
- Product variant error code `PRODUCT_VARIANT_CODE_EXISTS`.
- Variant endpoints under `/products/:id/variants`.
- Rich data endpoint `/products/:id/rich-data`.
- Variant SKU/EAN uniqueness.
- Nutrition provenance sources: `manual`, `manufacturer`, `openfoodfacts`.
- Manual nutrition protection from external-source overwrites.
- Explicit out-of-scope note: no stock, prices, images, or external sync job.
## Verification
- `cd project && npm run lint` — PASS after README Prettier normalization.
- `./scripts/verify.sh` — PASS from repository root before documentation close.

View File

@@ -0,0 +1,51 @@
# Implementer — F-010 Variants, SKU/EAN and product rich data
done -> work/artifacts/F-010/implementer.md
## Summary
Implemented catalog product variants and rich product data with SKU/EAN uniqueness, nutrition provenance, and manual nutrition overwrite protection.
## Changed files
- `project/src/modules/catalog/domain/variant.ts`
- `project/src/modules/catalog/domain/errors.ts`
- `project/src/modules/catalog/domain/ports.ts`
- `project/src/modules/catalog/application/variant-use-cases.ts`
- `project/src/modules/catalog/infrastructure/pg-variant-repository.ts`
- `project/src/modules/catalog/infrastructure/pg-rich-data-repository.ts`
- `project/src/modules/catalog/api/catalog.routes.ts`
- `project/migrations/008_catalog_variants_rich_data.js`
- `project/src/modules/catalog/tests/rich-data-use-cases.test.ts`
- `project/src/app/tests/catalog.itest.ts`
- `work/artifacts/F-010/architect.md`
- `backlog/features.json`
- `work/current.md`
## Acceptance traceability
1. Duplicate SKU or EAN returns HTTP 409
- Implemented via unique constraints on `catalog_product_variants.sku` and `catalog_product_variants.ean`.
- Mapped to `PRODUCT_VARIANT_CODE_EXISTS`.
- Covered by `project/src/app/tests/catalog.itest.ts`.
2. Manual nutrition source blocks external overwrite
- Implemented in `protectManualNutrition` inside application use-case logic.
- Covered by `project/src/modules/catalog/tests/rich-data-use-cases.test.ts` and integration test scenario.
3. Every nutrition payload stores provenance
- API validation requires `nutritionSource` when `nutrition` is provided.
- DB check enforces `nutrition IS NULL OR nutrition_source IS NOT NULL`.
- Covered by integration test scenario.
4. `verify.sh` green
- Verified after implementation.
## Commands run
- `cd project && npm run typecheck` — PASS
- `cd project && npm run lint` — PASS after Prettier normalization
- `cd project && npm test` — PASS: 61 passed, 33 skipped (DB integration skipped without `TEST_DATABASE_URL`)
- `cd project && npm run build` — PASS
- `cd project && npm run lint:boundaries` — PASS: 84 files checked
- `./scripts/verify.sh` — PASS
## Notes
- No new npm dependencies.
- Variant and rich data mutations are admin-only via existing catalog route auth.
- No OpenFoodFacts sync job, images, stock, or pricing added.
- JSON payloads are bounded through Zod validation.
- Documentation stage should update README/API notes because new variant and rich data endpoints/error codes were added.

View File

@@ -0,0 +1,36 @@
{
"feature_id": "F-010",
"agent": "leader",
"stage": "close",
"verdict": "APPROVED",
"title": "Variants, SKU/EAN and product rich data",
"gates": {
"review": "APPROVED",
"security": "APPROVED",
"qa": "APPROVED"
},
"verification": {
"lint": "clean",
"boundaries": "84 files OK",
"typecheck": "clean",
"build": "clean",
"unit_tests": "61 passed, 33 skipped",
"integration_tests": "present; skipped without TEST_DATABASE_URL",
"verify_sh": "green"
},
"deliverables": [
"migrations/008_catalog_variants_rich_data.js",
"src/modules/catalog/domain/variant.ts",
"src/modules/catalog/application/variant-use-cases.ts",
"src/modules/catalog/infrastructure/pg-variant-repository.ts",
"src/modules/catalog/infrastructure/pg-rich-data-repository.ts",
"catalog API routes for variants and rich data",
"tests: rich data provenance unit, catalog integration acceptance",
"README variants/rich-data documentation"
],
"known_followups": [
"No OpenFoodFacts sync job until a dedicated integration ticket",
"No images until F-011",
"No stock/pricing semantics until inventory/pricing tickets"
]
}

View File

@@ -0,0 +1,41 @@
{
"feature_id": "F-010",
"stage": "qa_gate",
"agent": "qa",
"verdict": "APPROVED",
"acceptance": [
{
"criterion": "Given duplicate SKU or EAN When variant created Then HTTP 409",
"status": "PASS",
"evidence": "Covered by src/app/tests/catalog.itest.ts; integration test is skipped automatically when TEST_DATABASE_URL is absent. DB unique constraints and error mapper reviewed."
},
{
"criterion": "Given field with nutrition_source=manual When external source pushes same field Then internal value kept",
"status": "PASS",
"evidence": "src/modules/catalog/tests/rich-data-use-cases.test.ts PASS; integration scenario present."
},
{
"criterion": "Every nutrition payload stores its provenance",
"status": "PASS",
"evidence": "API validation requires nutritionSource with nutrition; DB check constraint enforces source when nutrition is non-null; integration scenario present."
},
{
"criterion": "verify.sh green",
"status": "PASS",
"evidence": "./scripts/verify.sh — PASS"
}
],
"commands": {
"targeted_tests": "cd project && npm test -- --run src/modules/catalog/tests/rich-data-use-cases.test.ts src/app/tests/catalog.itest.ts — PASS: 2 passed, 6 skipped without TEST_DATABASE_URL",
"full_tests": "cd project && npm test — PASS: 61 passed, 33 skipped without TEST_DATABASE_URL",
"typecheck": "cd project && npm run typecheck — PASS",
"lint": "cd project && npm run lint — PASS",
"build": "cd project && npm run build — PASS",
"boundaries": "cd project && npm run lint:boundaries — PASS",
"verify": "./scripts/verify.sh — PASS"
},
"findings": [],
"notes": [
"Real PostgreSQL acceptance tests are implemented but not executed in this environment because TEST_DATABASE_URL is not set. This matches existing project test behavior."
]
}

View File

@@ -0,0 +1,24 @@
{
"feature_id": "F-010",
"stage": "review_gate",
"agent": "reviewer",
"verdict": "APPROVED",
"checked": [
"Catalog variant domain/application/infrastructure/API additions",
"Migration constraints for SKU/EAN uniqueness and nutrition provenance",
"Manual nutrition protection implemented in use-case layer",
"Nutrition payload provenance validation in API and DB",
"No out-of-scope OpenFoodFacts sync/images/pricing/stock",
"Acceptance test coverage"
],
"findings": [],
"evidence": {
"targeted_tests": "cd project && npm test -- --run src/modules/catalog/tests/rich-data-use-cases.test.ts src/app/tests/catalog.itest.ts — PASS: 2 passed, 6 skipped without TEST_DATABASE_URL",
"typecheck": "cd project && npm run typecheck — PASS",
"lint": "cd project && npm run lint — PASS",
"test": "cd project && npm test — PASS: 61 passed, 33 skipped without TEST_DATABASE_URL",
"build": "cd project && npm run build — PASS",
"boundaries": "cd project && npm run lint:boundaries — PASS",
"verify": "./scripts/verify.sh — PASS"
}
}

View File

@@ -0,0 +1,27 @@
{
"feature_id": "F-010",
"stage": "security_gate",
"agent": "security",
"verdict": "APPROVED",
"checked": [
"No new runtime or dev dependencies",
"Variant and rich data mutation routes are admin-only",
"SKU/EAN uniqueness enforced at database level",
"Nutrition provenance enforced by API validation and database check constraint",
"Manual nutrition overwrite protection lives in application use case",
"JSON payloads are bounded by validation",
"SQL queries use parameterized pg placeholders; dynamic UPDATE columns are whitelisted",
"Secret scan over F-010 files"
],
"findings": [],
"evidence": {
"npm_audit_runtime": "cd project && npm audit --audit-level=high --omit=dev — PASS: found 0 vulnerabilities",
"secret_scan": "grep over F-010 files found only a non-secret test password fixture",
"verify": "./scripts/verify.sh — PASS",
"tests": "cd project && npm test — PASS"
},
"notes": [
"No external OpenFoodFacts job or remote call was added.",
"No price, stock, or image behavior was introduced."
]
}