Catalogue & product data
GPSR Product Safety Metadata
Generated from docs/plan/core/gpsr-product-safety-metadata/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Core infrastructure for storing manufacturer/importer identity, batch numbers, safety instructions, and compliance documents directly on products. Merchants selling optics, automotive parts, or industrial goods can now comply with GPSR (product safety regulation, enforced 2023+) by attaching structured regulatory metadata to SKUs. This is foundational storage and schema; validation and category-specific checklists belong in a paid pack.
The problem
GPSR (enforced 2023+) mandates product listings carry manufacturer/importer info, batch numbers, safety instructions, and compliance documents. Merchants selling optics, automotive, or industrial goods cannot store this structured data, risking regulatory fines and delisting.
What it does
- Add product fields: manufacturer_name, manufacturer_address (street, city, postal_code, country_code), importer_name, importer_address, batch_code, safety_instructions (rich text), ce_marking (boolean), compliance_docs (array of downloadable HTTPS URLs with names)
- Store fields in all three drivers (lowdb JSON, libSQL, SqlStorage) with consistent normalization
- Normalize manufacturer/importer addresses: max 500 chars each, trim whitespace, no markup
- Normalize batch_code: alphanumeric + dash/underscore, max 64 chars, reject invalid formats
- Normalize compliance_docs URLs: HTTPS only, max 20 docs, max 2000 chars per URL, with fallback name ‘Document’
- Expose fields in GET /products/:id, POST /products, PUT /products/:id, and product list filters
- Add admin UI tabs for ‘Manufacturer’, ‘Importer’, ‘Safety & Compliance’ with structured form fields
- Include fields in product export (CSV/JSON) and import, with validation that rejects invalid batch codes
- Audit log captures changes to regulatory metadata (manufacturer, batch code, compliance docs added/removed)
- Document example: optics merchant stores ‘CE mark’, prescription-grinding docs, importer address for traceability
- No validation of actual GPSR compliance or category-specific requirements (paid tier)
- No category-gating or per-country rules (paid tier)
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Validation that the stored manufacturer name matches official registries — core stores what the merchant enters; accuracy is their responsibility
- Per-category checklists (optics vs automotive vs toys) — that’s per-country regulatory expertise, owned by the paid pack
- Automatic audit trail proof for regulators — core logs changes; merchant must export/archive for compliance proof
- Integration with manufacturer or importer APIs to verify registration — that’s credential-based, belongs in paid
- Multi-language safety instructions — i18n is handled elsewhere; safety_instructions follows base description pattern
Data model
New product fields: manufacturer_name (string, max 255), manufacturer_street (string, max 255), manufacturer_city (string, max 100), manufacturer_postal_code (string, max 20), manufacturer_country_code (string, 2-char ISO 3166), importer_name (string, max 255), importer_street (string, max 255), importer_city (string, max 100), importer_postal_code (string, max 20), importer_country_code (string, 2-char ISO 3166), batch_code (string, max 64, alphanumeric+dash+underscore), safety_instructions (text, max 5000 chars), ce_marking (boolean, default false), compliance_docs (array of {name: string, url: string}, max 20 items). No migration required if driver supports adding fields; existing products inherit null/empty values.
API
- GET /products/:id — include new fields in response
- POST /products — accept new fields in body, normalize and store
- PUT /products/:id — accept partial updates to new fields, derive and update audit log
- GET /products?manufacturer_country=DE — filter by manufacturer country code
- GET /products?has_ce_marking=true — filter by CE marking presence
- POST /products/:id/compliance_docs — add a compliance document (URL + name)
- DELETE /products/:id/compliance_docs/:doc_id — remove a compliance document
- GET /exports/products — include new fields in product export
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Product edit form gains three sections: ‘Manufacturer’ (structured address fields), ‘Importer’ (structured address fields), ‘Safety & Compliance’ (batch code, CE marking toggle, safety instructions textarea, compliance doc uploader/list with delete). Operators can bulk-import manufacturers and importers via CSV. A read-only ‘Compliance Audit’ report shows all products with manufacturer/importer info and when each was last updated.
The seam — why this is core
Core owns the product field schema, normalization, storage, and API surface — an honest hand-modelled product-update UI. Paid tier owns GPSR templates per category (optics, automotive, food, toy), validation that the stored data matches the category’s mandatory fields, and per-country rule enforcement (e.g., must have importer in EU if selling in EU). Core does not gatekeep merchants from storing the data they need.
core owns the interface + an honest hand-modelled implementation — structured fields (manufacturer/importer name + address, batch code, safety instructions, CE marking, compliance docs) and storage are foundational. Merchants are responsible for content accuracy.
Dependencies
- product-management (must exist first; this extends the product model)
- Assumes audit log system exists (stores all manufacturer/importer/batch changes)
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- A product with manufacturer_name=‘Zeiss’ and importer_country_code=‘DE’ stores and returns both fields via GET /products/:id
- Batch code ‘2024-BATCH_01’ is accepted; ‘BATCH<001>’ is rejected and normalized to undefined
- A compliance doc URL ‘http://example.com/cert.pdf’ is rejected; ‘https://example.com/cert.pdf’ is accepted
- PUT /products/:id with only {batch_code: ‘2024’} does not wipe manufacturer_name (partial update preserves existing fields)
- Export CSV contains all new columns; re-importing the CSV repopulates fields correctly
- Audit log entry is created when manufacturer_name is changed, showing old and new value
- Filtering by manufacturer_country=DE returns only products where importer_country_code=‘DE’
- A product with no manufacturer data has all fields null/empty in the response, not missing
Risks
Adding many new fields to the product model increases row size and memory footprint; a merchant with 100k SKUs and rich manufacturer data will notice slower list queries. Normalization functions must be consistent across all three storage drivers, or a migrated product will lose data. Compliance docs are HTTPS URLs; a merchant incorrectly storing file:// or javascript: links (data validation failure) could expose security issues on the storefront if the field is rendered unsafely.
Commercial context
| Suggested price | Free (core) |
| Rival anchor | No direct equivalent (Shopify/Magento have no GPSR framework); regulatory consultants charge €1k-5k per category audit |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.