1{2 "$comment": "TEMPLATE / EXAMPLE for migrations/<project>/source-schema.json when platform=csv. This is a shape to follow, NOT a strict schema to validate against. Both the producer (rp-discovery) and the consumer (rp-mapper) are LLMs, so structure is a convention that prevents drift, not a hard contract. Keep the agnostic core stable across platforms; push CSV quirks into the open `sourceMeta` blob. Synthesize it from data/csv-discovery/fileset.json.",34 "platform": "csv",5 "capturedAt": "2026-07-28T00:00:00.000Z",6 "rawDiscovery": "data/csv-discovery/",78 "sourceMeta": {9 "$comment": "CSV specifics live here so the agnostic core stays identical to every other platform.",
26 "$comment": "Shape C: same role + same normalized column set = one logical stream. partOf keeps the provenance of each row set visible."
27 },
28 {
29 "file": "categories.csv",
30 "role": "category",
31 "vendor": "custom",
32 "layout": { "pattern": "flat" }
33 }
34 ],
35
36 "vendor": {
37 "$comment": "Per file when the set mixes vendors. Confidence and evidence are recorded so a low-confidence match is visible rather than silently chosen.",
51 "$comment": "emptyPolicy is DETECTED, not assumed: `present-if-quoted` only when the file contains both a quoted and an unquoted empty. Generated readers apply it through coerceEmpty so a required Wix field is never fed an empty string the source did not have."
52 },
53
54 "drift": {
55 "$comment": "The header read at discovery time is authoritative; the vendor profile is advisory. These two lists make profile drift visible to the mapper and the user instead of silent.",
56 "unmappedColumns": ["Metafield: custom.fit"],
57 "missingExpectedColumns": []
58 },
59
60 "mappingHints": [
61 {
62 "$comment": "ADVISORY pre-fill from the vendor overlay's columnMap. rp-mapper reviews and corrects these; it never accepts them unchecked, and every column not covered here still flows through the mapper and the user unchanged.",
63 "column": "Variant SKU",
64 "wixTarget": "variant.sku",
65 "matchedAlias": "Variant SKU"
66 }
67 ],
68
69 "quirks": [
70 "A row with a blank Handle continues the previous product.",
71 "The product CSV does NOT carry collection membership — Shopify collections need the Admin API or an app."
72 ],
73
74 "joins": [
75 {
76 "$comment": "v1.1. The schema is designed now; automatic detection lands with a real normalized bundle. v1 emits an empty array plus joinsDeferred=true.",
92 "$comment": "origin.kind is the CSV addition to the agnostic core: an entity comes from one row, one row group, or the distinct values of a column. The reader needs it to know how to materialize the entity.",
144 "$comment": "A third collection inside the same row group. Only the vendor overlay's columnGroups can separate it from the variant columns — both vary within a product."
145 },
146 "recordCount": 1904,
147 "inUse": true
148 },
149
150 "category": {
151 "rawFile": "product--category.md",
152 "origin": {
153 "kind": "column-values",
154 "file": "products_export.csv",
155 "column": "Product Category",
156 "hierarchySeparator": ">",
157 "multiValueSeparator": null,
158 "linkPolicy": "leaf"
159 },
160 "$comment": "hierarchical=true MUST be set when the derived paths are nested. A hierarchical source taxonomy mapped to a flat Wix target triggers the mapper's mandatory faithfulness-ledger entry; without the flag the flatten happens silently. The reader emits one record per distinct path INCLUDING ancestors, depth-ascending, so the import creates a parent before its child.",
186 "$comment": "Wix Stores product tags are a native target that must be created before products and referenced by id — same create-then-link shape as categories."
187 }
188 },
189
190 "assumptions": [
191 "Record counts are exact when the file was scanned in full; when data/csv-discovery/raw-capture.json reports scan.truncated, they reflect the scanned prefix."
192 ],
193 "unknowns": [
194 "Metafield: custom.fit is present in the file but not covered by the shopify profile's columnMap."