openspec: tag-registry-product-link change — real registry seed + products/orders schema
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-08-07
|
||||
3
openspec/changes/tag-registry-product-link/README.md
Normal file
3
openspec/changes/tag-registry-product-link/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# tag-registry-product-link
|
||||
|
||||
Seed real preset tag registry (100 manufacturer IDs) + products/orders schema for stronger tag->product->order validation (mirrors 2014 product_id_hashes model)
|
||||
41
openspec/changes/tag-registry-product-link/design.md
Normal file
41
openspec/changes/tag-registry-product-link/design.md
Normal file
@@ -0,0 +1,41 @@
|
||||
## Context
|
||||
|
||||
Phase 1/2 built the app with a DEV-only tag registry (TEST codes). The real product — 100 preset physical tags — was manufactured with URLs carrying opaque base64 `productid` values (the old system's double-encoded IDs). Those IDs are confirmed real (the user generated the URLs and sent them to the manufacturer) and are now in `db/preset_tag_ids.txt`. This change seeds them as the real registry and adds the products/orders linkage that the 2014 system used for stronger validation and billing.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- The 100 real preset IDs are claimable (seeded registry).
|
||||
- `products`, `orders` schema + `tags.product_id`/`order_id` exist for the admin (Phase 4) and billing (Phase 5).
|
||||
- Idempotent migrations and seeds; no regressions to existing flows.
|
||||
|
||||
**Non-Goals:**
|
||||
- Laravel admin UI to assign products/orders (Phase 4).
|
||||
- Billing enforcement (Phase 5).
|
||||
- Changing the claim flow (still registry-by-code; the product link does not gate binding yet).
|
||||
- Decoding the opaque product IDs (they are intentionally opaque; tag_code IS the identifier).
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **Registry file = single source of truth.** `db/preset_tag_ids.txt` (committed) holds the 100 IDs; the seed reads it. Manufacturing new tags = append to this file + re-seed. *Alternative:* hardcode in Go — rejected, file is data not code.
|
||||
2. **`seed-registry` is a separate command from `seed`.** `make seed` keeps TEST codes for dev; `make seed-registry` adds the real IDs. Both idempotent (`InsertTag` + unique-violation skip, same pattern as the existing seed). *Alternative:* replace TEST entirely — rejected, dev needs stable TEST codes until production seeding is the only path (Phase 7).
|
||||
3. **Schema mirrors the 2014 model with modern naming.** `products` ≈ old `products`/`id_products`; `orders` ≈ old `online_orders`; `tags.product_id`+`order_id` ≈ old `online_order_product_details` + `product_id_hashes` mapping. The old `product_id_hashes` table is unnecessary here because `tag_code` IS the hash in our system — registry presence is the validation.
|
||||
4. **No new sqlc queries yet.** Schema + models only (sqlc regenerates `Product`/`Order` models and extends `Tag`); queries come with the Phase 4 admin. This keeps the change small and behaviour-stable.
|
||||
5. **Idempotent ALTERs** for existing DBs: `ALTER TABLE tags ADD COLUMN IF NOT EXISTS product_id ...` etc. — same pattern as `sms_enabled` (Phase 2).
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [TEST codes + real IDs coexist in dev] → accepted; production seeding documented as registry-only (Phase 7 gate).
|
||||
- [Opaque IDs can't be human-decoded] → by design (anti-enumeration); the file maps ID → URL format for reference.
|
||||
- [products/orders unused until Phase 4] → schema-only now, no dead code paths; avoids a breaking migration later.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. `make db-up` (idempotent ALTERs + new tables on the existing dev DB).
|
||||
2. `make seed-registry` (100 real IDs) — verify idempotent on re-run.
|
||||
3. sqlc `make generate` (new models). No data migration needed (new nullable columns).
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Whether the 100 IDs map 1:1 to physical tags currently in circulation (assumed yes — user created the URLs for the manufacturer).
|
||||
- Product template contents (sku/name) — seeded later with the Phase 4 admin.
|
||||
28
openspec/changes/tag-registry-product-link/proposal.md
Normal file
28
openspec/changes/tag-registry-product-link/proposal.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## Why
|
||||
|
||||
Anti-scam requires that only **preset, manufactured tag IDs** can be claimed — no user-created codes. The 100 real preset product IDs were recovered from the manufacturer list (`Where_woof_0_100.xls` → `db/preset_tag_ids.txt`) and confirmed as the actual tags (URLs sent to the manufacturer). This change (1) seeds that real registry so those IDs are claimable, and (2) adds the **products/orders schema** so each tag can be linked to a sold product and order — the stronger validation model the 2014 system had (`products` / `online_order_product_details` / `product_id_hashes`).
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Registry seeding**: a `seed-registry` command (Makefile target) reads `db/preset_tag_ids.txt` and inserts the 100 real product IDs into `tags` (status `unset`), idempotently. DEV `TEST000001..25` codes remain for local testing; production seeding uses the real registry.
|
||||
- **Products table**: `products` (sku unique, name, item_type, timestamps) — the product template (old `products` / `id_products`).
|
||||
- **Orders table**: `orders` (account reference, status lifecycle pending/paid/lapsed/cancelled, timestamps) — sales record (old `online_orders`).
|
||||
- **Tag linkage**: `tags` gains nullable `product_id` and `order_id` FKs — linking each tag to what it is and who bought it (old `online_order_product_details` + `product_id_hashes` mapping). No change to the claim flow yet (binding remains registry-by-code); the linkage is populated by the Laravel admin (Phase 4) and powers billing (Phase 5).
|
||||
- No app behaviour change to auth/tags/scan in this change.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `tag-registry`: real preset IDs are seedable into the registry; binding remains registry-only; unknown/self-made codes stay rejected.
|
||||
- `product-orders`: products + orders tables and the tag→product→order linkage exist for admin/billing (Phase 4/5).
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `database`: ADDED — `products`, `orders` tables; `tags.product_id`, `tags.order_id` columns.
|
||||
|
||||
## Impact
|
||||
|
||||
- **New**: `frontend/cmd/seed-registry/main.go` (reads `db/preset_tag_ids.txt`), Makefile `seed-registry` target, `db/preset_tag_ids.txt` (already committed, `833c292`).
|
||||
- **Schema**: `db/schema.sql` gains `products`, `orders` + two `ALTER TABLE tags ADD COLUMN IF NOT EXISTS` lines (idempotent); sqlc regenerated (new models; no new queries needed yet).
|
||||
- **No change** to handlers, templates, or the scan flow.
|
||||
@@ -0,0 +1,22 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: products table
|
||||
The database SHALL include a `products` table (unique sku, name, item_type, created_at/updated_at).
|
||||
|
||||
#### Scenario: Fresh database
|
||||
- **WHEN** `db/schema.sql` is applied to an empty database
|
||||
- **THEN** the `products` table exists with its columns
|
||||
|
||||
### Requirement: orders table
|
||||
The database SHALL include an `orders` table (account reference, status with allowed values, timestamps).
|
||||
|
||||
#### Scenario: Fresh database
|
||||
- **WHEN** `db/schema.sql` is applied to an empty database
|
||||
- **THEN** the `orders` table exists with its columns
|
||||
|
||||
### Requirement: tag product/order columns
|
||||
The `tags` table SHALL gain nullable `product_id` and `order_id` columns referencing `products(id)` and `orders(id)`, added idempotently for existing databases.
|
||||
|
||||
#### Scenario: Existing database migration
|
||||
- **WHEN** `db/schema.sql` is re-applied to a pre-existing database
|
||||
- **THEN** the two columns exist, nullable, without error
|
||||
@@ -0,0 +1,26 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Products table
|
||||
The `products` table SHALL store product templates with a unique sku, a name, an optional default item type, and timestamps.
|
||||
|
||||
#### Scenario: Unique sku
|
||||
- **WHEN** a second product row is inserted with an existing sku
|
||||
- **THEN** the insert is rejected
|
||||
|
||||
### Requirement: Orders table
|
||||
The `orders` table SHALL record sales with an account reference and a status lifecycle (`pending`, `paid`, `lapsed`, `cancelled`).
|
||||
|
||||
#### Scenario: Order lifecycle
|
||||
- **WHEN** an order is created as `pending` and later marked `paid`
|
||||
- **THEN** the status reflects each state
|
||||
|
||||
### Requirement: Tag to product and order linkage
|
||||
Each `tags` row SHALL optionally reference a `product_id` and an `order_id`, linking a tag to what it is and which sale it belongs to.
|
||||
|
||||
#### Scenario: Linked tag
|
||||
- **WHEN** an admin links a tag to a product and an order
|
||||
- **THEN** the tag row carries both foreign keys
|
||||
|
||||
#### Scenario: Unlinked tag
|
||||
- **WHEN** a tag has no product/order assigned yet
|
||||
- **THEN** both columns are NULL and the tag still functions (registry-only binding unchanged)
|
||||
@@ -0,0 +1,30 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Seed real preset tag registry
|
||||
The system SHALL provide a `seed-registry` command that reads `db/preset_tag_ids.txt` and inserts each ID as a `tags` row (status `unset`), skipping IDs that already exist. Re-running SHALL be safe (idempotent).
|
||||
|
||||
#### Scenario: First run
|
||||
- **WHEN** `make seed-registry` runs against an empty tags table
|
||||
- **THEN** all IDs from `db/preset_tag_ids.txt` exist as `unset` tags
|
||||
|
||||
#### Scenario: Re-run
|
||||
- **WHEN** `make seed-registry` runs again
|
||||
- **THEN** no duplicate `tag_code` errors occur and the registry is unchanged
|
||||
|
||||
### Requirement: Registry-only claimable codes
|
||||
Only codes present in the `tags` table (the registry) SHALL be claimable. A code not in the registry SHALL be rejected (existing bind behaviour).
|
||||
|
||||
#### Scenario: Real ID claimed
|
||||
- **WHEN** an owner enters one of the 100 real preset IDs
|
||||
- **THEN** the tag binds to their account (if unset)
|
||||
|
||||
#### Scenario: Unknown code
|
||||
- **WHEN** an owner enters a made-up code
|
||||
- **THEN** binding is rejected with "Tag not found, or already claimed"
|
||||
|
||||
### Requirement: DEV test codes coexist
|
||||
The existing `TEST000001..25` seed SHALL remain available for local development, separate from the real registry seed.
|
||||
|
||||
#### Scenario: Both seeds applied
|
||||
- **WHEN** `make seed` and `make seed-registry` have both run
|
||||
- **THEN** both TEST codes and real IDs exist and are claimable in dev
|
||||
16
openspec/changes/tag-registry-product-link/tasks.md
Normal file
16
openspec/changes/tag-registry-product-link/tasks.md
Normal file
@@ -0,0 +1,16 @@
|
||||
## 1. Schema
|
||||
|
||||
- [ ] 1.1 `db/schema.sql`: add `products` and `orders` tables; add idempotent `ALTER TABLE tags ADD COLUMN IF NOT EXISTS product_id BIGINT REFERENCES products(id)` and `order_id BIGINT REFERENCES orders(id)`; run `make db-up`
|
||||
- [ ] 1.2 `make generate` (sqlc adds `Product`/`Order` models, extends `Tag`); verify build
|
||||
|
||||
## 2. Registry Seed
|
||||
|
||||
- [ ] 2.1 `frontend/cmd/seed-registry/main.go`: read `db/preset_tag_ids.txt` (skip comment lines), `InsertTag` each ID, skip unique-violations, report counts
|
||||
- [ ] 2.2 Makefile `seed-registry` target (cwd frontend, schema path aware); README note
|
||||
|
||||
## 3. Verification
|
||||
|
||||
- [ ] 3.1 `make seed-registry` on fresh DB → 100 tags; re-run → no errors (idempotent)
|
||||
- [ ] 3.2 Bind a real ID (register owner, add tag with one of the 100 IDs) → bound; made-up code → rejected
|
||||
- [ ] 3.3 Existing suites still pass (`/tmp/verify.sh` Phase 1 + `/tmp/verify2.sh` Phase 2) with TEST codes
|
||||
- [ ] 3.4 `openspec validate tag-registry-product-link`; commit
|
||||
Reference in New Issue
Block a user