AstroBaaS

Catalogue & product data

GPSR Product Safety Metadata

Free — GPL coresize Mplanned, not built

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 priceFree (core)
Rival anchorNo 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.