@wix/agent-skills 1.35.1 → 1.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (820) hide show
  1. package/package.json +8 -3
  2. package/skills/wix-app/references/CODE_QUALITY.md +1 -1
  3. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +27 -29
  4. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +1 -1
  5. package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +26 -25
  6. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +12 -42
  7. package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +9 -20
  8. package/skills/wix-app/references/editor-react-component/MANIFEST-ERRORS.md +5 -4
  9. package/skills/wix-app/references/editor-react-component/PARTS.md +1 -1
  10. package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +1 -1
  11. package/skills/wix-app/references/editor-react-component/VECTOR-ART.md +95 -0
  12. package/skills/wix-app/references/editor-react-component/VISUAL-QUALITY.md +39 -0
  13. package/skills/wix-docs/references/CALLING.md +1 -1
  14. package/skills/wix-headless-kit/guides/api-run.md +1 -1
  15. package/skills/wix-headless-kit/install/pins.json +1 -1
  16. package/skills/wix-headless-templates/blog/seed/seed-blog.mjs +1 -1
  17. package/skills/wix-headless-templates/donations/app/wix/donations/campaigns.ts +0 -1
  18. package/skills/wix-headless-templates/donations/app/wix/donations/donate.ts +1 -1
  19. package/skills/wix-headless-templates/donations/project/src/wix/donations/campaigns.ts +0 -1
  20. package/skills/wix-headless-templates/donations/project/src/wix/donations/donate.ts +1 -1
  21. package/skills/wix-headless-templates/donations/rest/campaigns.ts +0 -1
  22. package/skills/wix-headless-templates/donations/rest/donate.ts +1 -1
  23. package/skills/wix-headless-templates/events/seed/seed-events.mjs +1 -1
  24. package/skills/wix-headless-templates/faq/INSTRUCTIONS.md +1 -1
  25. package/skills/wix-headless-templates/faq/app/wix/faq/faq.ts +2 -2
  26. package/skills/wix-headless-templates/faq/project/src/wix/faq/faq.ts +2 -2
  27. package/skills/wix-headless-templates/faq/rest/faq.ts +2 -2
  28. package/skills/wix-headless-templates/faq/seed/SEED.md +1 -1
  29. package/skills/wix-headless-templates/faq/seed/read-site.mjs +1 -1
  30. package/skills/wix-headless-templates/faq/seed/seed-faq.mjs +5 -5
  31. package/skills/wix-headless-templates/forms/app/wix/forms/forms.ts +2 -2
  32. package/skills/wix-headless-templates/forms/app/wix/forms/submissions.ts +2 -2
  33. package/skills/wix-headless-templates/forms/project/src/wix/forms/forms.ts +2 -2
  34. package/skills/wix-headless-templates/forms/project/src/wix/forms/submissions.ts +2 -2
  35. package/skills/wix-headless-templates/members/seed/read-site.mjs +1 -1
  36. package/skills/wix-headless-templates/pricing-plans/app/wix/pricing-plans/plan-access.ts +1 -1
  37. package/skills/wix-headless-templates/pricing-plans/project/src/wix/pricing-plans/plan-access.ts +1 -1
  38. package/skills/wix-headless-templates/pricing-plans/rest/plan-access.ts +1 -1
  39. package/skills/wix-headless-templates/restaurants/app/wix/restaurants/menu.ts +0 -1
  40. package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/menu.ts +0 -1
  41. package/skills/wix-headless-templates/restaurants/rest/menu.ts +0 -1
  42. package/skills/wix-headless-templates/shared/rest/client.ts +1 -1
  43. package/skills/wix-headless-templates/shared/seed/site.mjs +2 -1
  44. package/skills/wix-manage/SKILL.md +3 -3
  45. package/skills/wix-manage/references/cms/cms-data-items-crud.md +78 -1
  46. package/skills/wix-manage/references/google-ads/create-performance-max-campaign.md +11 -11
  47. package/skills/wix-manage/references/google-ads/get-campaign-suggestions.md +0 -1
  48. package/skills/wix-manage/references/google-ads/install-and-create-account.md +0 -1
  49. package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +20 -51
  50. package/skills/wix-manage/references/marketing/generate-and-publish-marketing-plan.md +4 -4
  51. package/skills/wix-manage/references/rich-content/ricos-converter-service.md +1 -1
  52. package/skills/wix-base44-connector/SKILL.md +0 -437
  53. package/skills/wix-base44-connector/references/CAPABILITY_MAP.md +0 -78
  54. package/skills/wix-base44-connector/scripts/utils.cjs +0 -466
  55. package/skills/wix-export/SKILL.md +0 -83
  56. package/skills/wix-harmony-editor/.publish-manifest.json +0 -8
  57. package/skills/wix-harmony-editor/COPY-SITE.md +0 -494
  58. package/skills/wix-harmony-editor/SKILL.md +0 -317
  59. package/skills/wix-harmony-editor/scripts/copy/build-description.mjs +0 -249
  60. package/skills/wix-harmony-editor/scripts/copy/check-description.mjs +0 -225
  61. package/skills/wix-harmony-editor/scripts/copy/check-mirror.mjs +0 -89
  62. package/skills/wix-harmony-editor/scripts/copy/compare-preview.mjs +0 -76
  63. package/skills/wix-harmony-editor/scripts/copy/deploy-description.mjs +0 -212
  64. package/skills/wix-harmony-editor/scripts/copy/desc-hash.mjs +0 -44
  65. package/skills/wix-harmony-editor/scripts/copy/editor-run.mjs +0 -45
  66. package/skills/wix-harmony-editor/scripts/copy/export-page.mjs +0 -53
  67. package/skills/wix-harmony-editor/scripts/copy/find-interactions.mjs +0 -76
  68. package/skills/wix-harmony-editor/scripts/copy/hide-scrollbars.mjs +0 -16
  69. package/skills/wix-harmony-editor/scripts/copy/import-media.mjs +0 -150
  70. package/skills/wix-harmony-editor/scripts/copy/link-url.mjs +0 -27
  71. package/skills/wix-harmony-editor/scripts/copy/lint-api.mjs +0 -56
  72. package/skills/wix-harmony-editor/scripts/copy/measure-source.mjs +0 -211
  73. package/skills/wix-harmony-editor/scripts/copy/merge-mirrors.mjs +0 -29
  74. package/skills/wix-harmony-editor/scripts/copy/mirror-site.mjs +0 -92
  75. package/skills/wix-harmony-editor/scripts/copy/mobile-stage.mjs +0 -56
  76. package/skills/wix-harmony-editor/scripts/copy/preview-widths.mjs +0 -73
  77. package/skills/wix-harmony-editor/scripts/copy/render-plate.mjs +0 -67
  78. package/skills/wix-harmony-editor/scripts/copy/serve-mirror.mjs +0 -102
  79. package/skills/wix-harmony-editor/scripts/copy/snapshot-capabilities.mjs +0 -40
  80. package/skills/wix-harmony-editor/scripts/copy/split-sections.mjs +0 -191
  81. package/skills/wix-harmony-editor/scripts/copy/upload-media-manager.mjs +0 -61
  82. package/skills/wix-harmony-editor/scripts/launch.sh +0 -359
  83. package/skills/wix-headless/SKILL.md +0 -151
  84. package/skills/wix-headless/references/AI_FEATURES.md +0 -178
  85. package/skills/wix-headless/references/CAPABILITIES.md +0 -117
  86. package/skills/wix-headless/references/CONTENT.md +0 -36
  87. package/skills/wix-headless/references/DESIGN.md +0 -55
  88. package/skills/wix-headless/references/DISCOVERY.md +0 -80
  89. package/skills/wix-headless/references/DOC_DISCOVERY.md +0 -43
  90. package/skills/wix-headless/references/FEEDBACK.md +0 -129
  91. package/skills/wix-headless/references/IMAGE_GENERATION.md +0 -80
  92. package/skills/wix-headless/references/SDK_HANDOFF.md +0 -108
  93. package/skills/wix-headless/references/SEED.md +0 -74
  94. package/skills/wix-headless/references/SETUP.md +0 -60
  95. package/skills/wix-headless/references/astro.md +0 -98
  96. package/skills/wix-headless/references/inline-recipes/experience-donations.md +0 -102
  97. package/skills/wix-headless/references/inline-recipes/experience-store.md +0 -49
  98. package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +0 -196
  99. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +0 -590
  100. package/skills/wix-headless/references/inline-recipes/how-to-code-bookings.md +0 -279
  101. package/skills/wix-headless/references/inline-recipes/how-to-code-cms.md +0 -163
  102. package/skills/wix-headless/references/inline-recipes/how-to-code-donations.md +0 -146
  103. package/skills/wix-headless/references/inline-recipes/how-to-code-events.md +0 -203
  104. package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +0 -696
  105. package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +0 -124
  106. package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +0 -160
  107. package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +0 -118
  108. package/skills/wix-headless/references/inline-recipes/how-to-code-portfolio.md +0 -132
  109. package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +0 -126
  110. package/skills/wix-headless/references/inline-recipes/how-to-code-rentals.md +0 -254
  111. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +0 -191
  112. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +0 -213
  113. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurants.md +0 -161
  114. package/skills/wix-headless/references/inline-recipes/setup-blog.md +0 -175
  115. package/skills/wix-headless/references/inline-recipes/setup-bookings.md +0 -232
  116. package/skills/wix-headless/references/inline-recipes/setup-cms.md +0 -200
  117. package/skills/wix-headless/references/inline-recipes/setup-donations.md +0 -15
  118. package/skills/wix-headless/references/inline-recipes/setup-events.md +0 -196
  119. package/skills/wix-headless/references/inline-recipes/setup-forms.md +0 -288
  120. package/skills/wix-headless/references/inline-recipes/setup-online-store.md +0 -214
  121. package/skills/wix-headless/references/inline-recipes/setup-portfolio.md +0 -155
  122. package/skills/wix-headless/references/inline-recipes/setup-pricing-plans.md +0 -229
  123. package/skills/wix-headless/references/inline-recipes/setup-rentals.md +0 -254
  124. package/skills/wix-headless/references/inline-recipes/setup-restaurant-experiences.md +0 -73
  125. package/skills/wix-headless/references/inline-recipes/setup-restaurant-orders.md +0 -197
  126. package/skills/wix-headless/references/inline-recipes/setup-restaurant-reservations.md +0 -136
  127. package/skills/wix-headless/references/inline-recipes/setup-restaurants.md +0 -152
  128. package/skills/wix-headless/references/managed/AUTHENTICATION.md +0 -71
  129. package/skills/wix-headless/references/managed/CONNECT.md +0 -74
  130. package/skills/wix-headless/references/managed/CREATE.md +0 -149
  131. package/skills/wix-headless/references/managed/DEPLOYMENT.md +0 -104
  132. package/skills/wix-headless/references/non-astro.md +0 -59
  133. package/skills/wix-headless/references/self-managed/AUTHENTICATION.md +0 -22
  134. package/skills/wix-headless/references/self-managed/DEPLOYMENT.md +0 -15
  135. package/skills/wix-headless/references/stripe/AUTHENTICATION.md +0 -55
  136. package/skills/wix-headless/references/stripe/DEPLOYMENT.md +0 -71
  137. package/skills/wix-headless/scripts/bootstrap.mjs +0 -156
  138. package/skills/wix-headless-replatform/.publish-manifest.json +0 -8
  139. package/skills/wix-headless-replatform/SKILL.md +0 -116
  140. package/skills/wix-headless-replatform/agents/openai.yaml +0 -6
  141. package/skills/wix-headless-replatform/references/control-and-visual-fidelity-contract.md +0 -43
  142. package/skills/wix-headless-replatform/references/extraction-schema.md +0 -187
  143. package/skills/wix-headless-replatform/references/interaction-runtime-contract.md +0 -137
  144. package/skills/wix-headless-replatform/references/layout-blueprint-contract.md +0 -41
  145. package/skills/wix-headless-replatform/references/output-contract.md +0 -93
  146. package/skills/wix-headless-replatform/references/post-build-gap-contract.md +0 -125
  147. package/skills/wix-headless-replatform/references/qa-checklist.md +0 -83
  148. package/skills/wix-headless-replatform/references/safety.md +0 -19
  149. package/skills/wix-headless-replatform/references/ui-normalization-contract.md +0 -70
  150. package/skills/wix-headless-replatform/references/workflow.md +0 -157
  151. package/skills/wix-headless-replatform/registry/README.md +0 -20
  152. package/skills/wix-headless-replatform/registry/approvals/button/shadcn-default-ac60ef5.json +0 -28
  153. package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/button.tsx +0 -54
  154. package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/capabilities.json +0 -60
  155. package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/contract.md +0 -48
  156. package/skills/wix-headless-replatform/registry/licenses/shadcn-ui-ac60ef5-MIT.txt +0 -21
  157. package/skills/wix-headless-replatform/registry/registry.json +0 -36
  158. package/skills/wix-headless-replatform/resources/rp-facelift/SKILL.md +0 -32
  159. package/skills/wix-headless-replatform/resources/rp-project-context/SKILL.md +0 -62
  160. package/skills/wix-headless-replatform/resources/rp-qa-gap-loop/SKILL.md +0 -64
  161. package/skills/wix-headless-replatform/resources/rp-source-evidence/SKILL.md +0 -42
  162. package/skills/wix-headless-replatform/resources/rp-ui-implementation/SKILL.md +0 -73
  163. package/skills/wix-headless-replatform/schemas/build-plan.schema.json +0 -10
  164. package/skills/wix-headless-replatform/schemas/candidate.schema.json +0 -19
  165. package/skills/wix-headless-replatform/schemas/common.schema.json +0 -33
  166. package/skills/wix-headless-replatform/schemas/decision-patch.schema.json +0 -10
  167. package/skills/wix-headless-replatform/schemas/decision-request.schema.json +0 -10
  168. package/skills/wix-headless-replatform/schemas/extraction-gap.schema.json +0 -28
  169. package/skills/wix-headless-replatform/schemas/extraction-manifest.schema.json +0 -34
  170. package/skills/wix-headless-replatform/schemas/final-report.schema.json +0 -10
  171. package/skills/wix-headless-replatform/schemas/foundation.schema.json +0 -11
  172. package/skills/wix-headless-replatform/schemas/metadata.schema.json +0 -11
  173. package/skills/wix-headless-replatform/schemas/observation-packet.schema.json +0 -17
  174. package/skills/wix-headless-replatform/schemas/page-capture.schema.json +0 -11
  175. package/skills/wix-headless-replatform/schemas/page-resolution.schema.json +0 -33
  176. package/skills/wix-headless-replatform/schemas/shared-chrome.schema.json +0 -11
  177. package/skills/wix-headless-replatform/schemas/unit.schema.json +0 -40
  178. package/skills/wix-headless-replatform/scripts/assemble-extraction.mjs +0 -844
  179. package/skills/wix-headless-replatform/scripts/benchmark-extraction.mjs +0 -163
  180. package/skills/wix-headless-replatform/scripts/browser-extraction-preflight.mjs +0 -42
  181. package/skills/wix-headless-replatform/scripts/build-extracted-homepage.mjs +0 -228
  182. package/skills/wix-headless-replatform/scripts/clear-wix-starter.mjs +0 -87
  183. package/skills/wix-headless-replatform/scripts/create-wix-project.mjs +0 -505
  184. package/skills/wix-headless-replatform/scripts/discover-urls.mjs +0 -142
  185. package/skills/wix-headless-replatform/scripts/extract-assets.mjs +0 -140
  186. package/skills/wix-headless-replatform/scripts/extract-design-system.mjs +0 -637
  187. package/skills/wix-headless-replatform/scripts/extract-interactions.mjs +0 -1903
  188. package/skills/wix-headless-replatform/scripts/extract-page.mjs +0 -2461
  189. package/skills/wix-headless-replatform/scripts/extract-seo.mjs +0 -25
  190. package/skills/wix-headless-replatform/scripts/extract-tokens.mjs +0 -63
  191. package/skills/wix-headless-replatform/scripts/finalize-extraction-report.mjs +0 -126
  192. package/skills/wix-headless-replatform/scripts/finalize-gap-review.mjs +0 -180
  193. package/skills/wix-headless-replatform/scripts/finalize-gap-review.test.mjs +0 -98
  194. package/skills/wix-headless-replatform/scripts/finalize-migration-website.mjs +0 -103
  195. package/skills/wix-headless-replatform/scripts/generate-control-state-contract.mjs +0 -176
  196. package/skills/wix-headless-replatform/scripts/generate-design-md.mjs +0 -261
  197. package/skills/wix-headless-replatform/scripts/generate-extraction-validators.mjs +0 -25
  198. package/skills/wix-headless-replatform/scripts/generate-invoice-download-backend.mjs +0 -342
  199. package/skills/wix-headless-replatform/scripts/generate-layout-blueprint.mjs +0 -140
  200. package/skills/wix-headless-replatform/scripts/generate-repeater-cms.mjs +0 -22
  201. package/skills/wix-headless-replatform/scripts/generate-route-plan.mjs +0 -143
  202. package/skills/wix-headless-replatform/scripts/generate-scene-contract.mjs +0 -288
  203. package/skills/wix-headless-replatform/scripts/generate-ui-normalization.mjs +0 -266
  204. package/skills/wix-headless-replatform/scripts/generate-visual-assets.mjs +0 -111
  205. package/skills/wix-headless-replatform/scripts/lib/browser-tooling.mjs +0 -570
  206. package/skills/wix-headless-replatform/scripts/lib/common.mjs +0 -329
  207. package/skills/wix-headless-replatform/scripts/lib/component-capabilities.mjs +0 -121
  208. package/skills/wix-headless-replatform/scripts/lib/component-registry.mjs +0 -282
  209. package/skills/wix-headless-replatform/scripts/lib/content-boundary.mjs +0 -61
  210. package/skills/wix-headless-replatform/scripts/lib/extraction-contract.mjs +0 -374
  211. package/skills/wix-headless-replatform/scripts/lib/extraction-detectors.mjs +0 -243
  212. package/skills/wix-headless-replatform/scripts/lib/extraction-validators.generated.mjs +0 -1139
  213. package/skills/wix-headless-replatform/scripts/lib/font-contract.mjs +0 -326
  214. package/skills/wix-headless-replatform/scripts/lib/frontend-automation-state.mjs +0 -184
  215. package/skills/wix-headless-replatform/scripts/lib/gap-analysis.mjs +0 -396
  216. package/skills/wix-headless-replatform/scripts/lib/html-extract.mjs +0 -168
  217. package/skills/wix-headless-replatform/scripts/lib/interaction-bootstrap.mjs +0 -10
  218. package/skills/wix-headless-replatform/scripts/lib/interaction-runtime.mjs +0 -194
  219. package/skills/wix-headless-replatform/scripts/lib/interaction-timeline.mjs +0 -348
  220. package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/classifiers.mjs +0 -141
  221. package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/geometry.mjs +0 -43
  222. package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/vocabulary.mjs +0 -18
  223. package/skills/wix-headless-replatform/scripts/lib/ui-normalization.css +0 -64
  224. package/skills/wix-headless-replatform/scripts/optimize-svg.mjs +0 -31
  225. package/skills/wix-headless-replatform/scripts/post-build-gap.mjs +0 -358
  226. package/skills/wix-headless-replatform/scripts/provision-repeater-cms.mjs +0 -25
  227. package/skills/wix-headless-replatform/scripts/record-gap-decision.mjs +0 -39
  228. package/skills/wix-headless-replatform/scripts/replay-extraction.mjs +0 -107
  229. package/skills/wix-headless-replatform/scripts/resolve-home-page.mjs +0 -96
  230. package/skills/wix-headless-replatform/scripts/site-clone.mjs +0 -658
  231. package/skills/wix-headless-replatform/scripts/validate-font-contract.mjs +0 -29
  232. package/skills/wix-headless-replatform/scripts/verify-interactions.mjs +0 -669
  233. package/skills/wix-headless-replatform/scripts/visual-qa.mjs +0 -370
  234. package/skills/wix-import/SKILL.md +0 -90
  235. package/skills/wix-replatform/.publish-manifest.json +0 -8
  236. package/skills/wix-replatform/CLI.md +0 -162
  237. package/skills/wix-replatform/README.md +0 -52
  238. package/skills/wix-replatform/SKILL.md +0 -238
  239. package/skills/wix-replatform/console/account.css +0 -1
  240. package/skills/wix-replatform/console/app.css +0 -1
  241. package/skills/wix-replatform/console/app.js +0 -117
  242. package/skills/wix-replatform/console/index.html +0 -4
  243. package/skills/wix-replatform/console/json.css +0 -11
  244. package/skills/wix-replatform/lib/agent-adapters/index.js +0 -71
  245. package/skills/wix-replatform/lib/app-page-routing.js +0 -75
  246. package/skills/wix-replatform/lib/artifact-freshness.js +0 -225
  247. package/skills/wix-replatform/lib/blocked-data-requests.js +0 -427
  248. package/skills/wix-replatform/lib/census-reconciliation.js +0 -426
  249. package/skills/wix-replatform/lib/completion-report.js +0 -1580
  250. package/skills/wix-replatform/lib/config-env.js +0 -133
  251. package/skills/wix-replatform/lib/confirmed-pictures.js +0 -212
  252. package/skills/wix-replatform/lib/dependency-readiness.js +0 -423
  253. package/skills/wix-replatform/lib/dependency-recovery.js +0 -21
  254. package/skills/wix-replatform/lib/destination-choice.js +0 -163
  255. package/skills/wix-replatform/lib/destination-handoff.js +0 -62
  256. package/skills/wix-replatform/lib/destination-http.js +0 -81
  257. package/skills/wix-replatform/lib/destination-route.js +0 -98
  258. package/skills/wix-replatform/lib/destination.js +0 -919
  259. package/skills/wix-replatform/lib/discovered-census.js +0 -231
  260. package/skills/wix-replatform/lib/discovered-set-status.js +0 -255
  261. package/skills/wix-replatform/lib/document-url-guard.js +0 -203
  262. package/skills/wix-replatform/lib/entity-qa-attestation.js +0 -339
  263. package/skills/wix-replatform/lib/entity-verification.js +0 -18
  264. package/skills/wix-replatform/lib/execution-capability.js +0 -120
  265. package/skills/wix-replatform/lib/execution-log.js +0 -170
  266. package/skills/wix-replatform/lib/execution-manifest.js +0 -203
  267. package/skills/wix-replatform/lib/execution-state.js +0 -195
  268. package/skills/wix-replatform/lib/expectations-ledger.js +0 -96
  269. package/skills/wix-replatform/lib/generated-plugin-handoff.js +0 -140
  270. package/skills/wix-replatform/lib/import-readiness-contract.js +0 -23
  271. package/skills/wix-replatform/lib/import-recovery.js +0 -1107
  272. package/skills/wix-replatform/lib/imported-redirect-targets.js +0 -608
  273. package/skills/wix-replatform/lib/internal-link-reconciliation.js +0 -166
  274. package/skills/wix-replatform/lib/link-audit.js +0 -173
  275. package/skills/wix-replatform/lib/link-census.js +0 -194
  276. package/skills/wix-replatform/lib/link-plan.js +0 -148
  277. package/skills/wix-replatform/lib/link-verification.js +0 -274
  278. package/skills/wix-replatform/lib/link-write-journal.js +0 -42
  279. package/skills/wix-replatform/lib/local-state.js +0 -1481
  280. package/skills/wix-replatform/lib/member-completion.js +0 -658
  281. package/skills/wix-replatform/lib/member-crosswalk.js +0 -69
  282. package/skills/wix-replatform/lib/member-outcome-registry.js +0 -265
  283. package/skills/wix-replatform/lib/migration-completion.js +0 -311
  284. package/skills/wix-replatform/lib/operator-identity.js +0 -47
  285. package/skills/wix-replatform/lib/orchestration-decisions.js +0 -248
  286. package/skills/wix-replatform/lib/orchestration-preflight.js +0 -525
  287. package/skills/wix-replatform/lib/orchestration-router.js +0 -698
  288. package/skills/wix-replatform/lib/orchestration-state.js +0 -631
  289. package/skills/wix-replatform/lib/order-reference-readiness.js +0 -30
  290. package/skills/wix-replatform/lib/payments-outcome-registry.js +0 -200
  291. package/skills/wix-replatform/lib/plugin-cms-fallback.js +0 -618
  292. package/skills/wix-replatform/lib/preservation-approval-stamp.js +0 -54
  293. package/skills/wix-replatform/lib/product-media-deferral.js +0 -96
  294. package/skills/wix-replatform/lib/progress-log.js +0 -240
  295. package/skills/wix-replatform/lib/progress-log.sh +0 -140
  296. package/skills/wix-replatform/lib/read-back-approval.js +0 -184
  297. package/skills/wix-replatform/lib/read-back-comparison.js +0 -136
  298. package/skills/wix-replatform/lib/read-back-contract.js +0 -243
  299. package/skills/wix-replatform/lib/read-back-extract.js +0 -118
  300. package/skills/wix-replatform/lib/read-back-gate.js +0 -734
  301. package/skills/wix-replatform/lib/read-back-reads.js +0 -94
  302. package/skills/wix-replatform/lib/read-back-state.js +0 -107
  303. package/skills/wix-replatform/lib/read-back-summary.js +0 -81
  304. package/skills/wix-replatform/lib/rerun-conflicts.js +0 -334
  305. package/skills/wix-replatform/lib/run-controller.js +0 -401
  306. package/skills/wix-replatform/lib/seo-outcome-registry.js +0 -45
  307. package/skills/wix-replatform/lib/setup-verification.js +0 -148
  308. package/skills/wix-replatform/lib/source-access.js +0 -231
  309. package/skills/wix-replatform/lib/source-coverage.js +0 -1130
  310. package/skills/wix-replatform/lib/source-secrets.js +0 -277
  311. package/skills/wix-replatform/lib/structured-link-patch.js +0 -59
  312. package/skills/wix-replatform/lib/task-state.js +0 -110
  313. package/skills/wix-replatform/lib/url-preservation-report.js +0 -319
  314. package/skills/wix-replatform/lib/url-preservation-state.js +0 -739
  315. package/skills/wix-replatform/lib/url-redirect-plan.js +0 -728
  316. package/skills/wix-replatform/lib/url-verification.js +0 -315
  317. package/skills/wix-replatform/lib/user-action-effects.js +0 -42
  318. package/skills/wix-replatform/lib/user-actions.js +0 -264
  319. package/skills/wix-replatform/lib/website-handoff.js +0 -1220
  320. package/skills/wix-replatform/lib/wix-client-callers.js +0 -54
  321. package/skills/wix-replatform/lib/wix-dashboard-url.js +0 -35
  322. package/skills/wix-replatform/lib/write-verification-state.js +0 -92
  323. package/skills/wix-replatform/lib/write-verification.js +0 -274
  324. package/skills/wix-replatform/resources/rp-destination/SKILL.md +0 -137
  325. package/skills/wix-replatform/resources/rp-discovery/SKILL.md +0 -357
  326. package/skills/wix-replatform/resources/rp-discovery/scripts/promote-source-coverage.js +0 -47
  327. package/skills/wix-replatform/resources/rp-execute-import/SKILL.md +0 -1369
  328. package/skills/wix-replatform/resources/rp-execute-import/scripts/shipping-estimate-audit.js +0 -70
  329. package/skills/wix-replatform/resources/rp-execute-setup/SKILL.md +0 -482
  330. package/skills/wix-replatform/resources/rp-execute-setup/scripts/execute-setup.js +0 -442
  331. package/skills/wix-replatform/resources/rp-execute-setup/scripts/mint-token.sh +0 -64
  332. package/skills/wix-replatform/resources/rp-execution-policy/SKILL.md +0 -112
  333. package/skills/wix-replatform/resources/rp-execution-policy/lib/coupon-currency-plan-report.js +0 -143
  334. package/skills/wix-replatform/resources/rp-execution-policy/lib/payments-report.js +0 -244
  335. package/skills/wix-replatform/resources/rp-import-codegen/SKILL.md +0 -3158
  336. package/skills/wix-replatform/resources/rp-mapper/SKILL.md +0 -947
  337. package/skills/wix-replatform/resources/rp-mapper/lib/mapping-resolve.js +0 -217
  338. package/skills/wix-replatform/resources/rp-mapper/lib/structure-bridge-planning.js +0 -303
  339. package/skills/wix-replatform/resources/rp-mapper/scripts/coverage-reconcile.js +0 -88
  340. package/skills/wix-replatform/resources/rp-mapper/scripts/resolve-mapping.js +0 -148
  341. package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +0 -378
  342. package/skills/wix-replatform/resources/rp-preflight/SKILL.md +0 -40
  343. package/skills/wix-replatform/resources/rp-project-intake/SKILL.md +0 -55
  344. package/skills/wix-replatform/resources/rp-quick-runtime/SKILL.md +0 -60
  345. package/skills/wix-replatform/resources/rp-quick-runtime/lib/import-progress.js +0 -89
  346. package/skills/wix-replatform/resources/rp-quick-runtime/lib/limit-recovery.js +0 -36
  347. package/skills/wix-replatform/resources/rp-quick-shopify/SKILL.md +0 -123
  348. package/skills/wix-replatform/resources/rp-quick-shopify/quick-mode.json +0 -124
  349. package/skills/wix-replatform/resources/rp-quick-shopify/scripts/quick-mode.js +0 -522
  350. package/skills/wix-replatform/resources/rp-quick-woocommerce/SKILL.md +0 -56
  351. package/skills/wix-replatform/resources/rp-quick-woocommerce/quick-mode.json +0 -91
  352. package/skills/wix-replatform/resources/rp-quick-woocommerce/scripts/quick-mode.js +0 -255
  353. package/skills/wix-replatform/resources/rp-setup-discovery/SKILL.md +0 -253
  354. package/skills/wix-replatform/resources/rp-setup-discovery/scripts/quick-setup-plan.js +0 -44
  355. package/skills/wix-replatform/resources/rp-source-csv/SKILL.md +0 -481
  356. package/skills/wix-replatform/resources/rp-source-csv/lib/canonical-record.js +0 -136
  357. package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fileset.js +0 -0
  358. package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fingerprint.js +0 -310
  359. package/skills/wix-replatform/resources/rp-source-csv/lib/csv-layout.js +0 -1081
  360. package/skills/wix-replatform/resources/rp-source-csv/lib/csv-parse.js +0 -618
  361. package/skills/wix-replatform/resources/rp-source-csv/scripts/csv-discovery.js +0 -969
  362. package/skills/wix-replatform/resources/rp-source-csv/source-schema.example.json +0 -196
  363. package/skills/wix-replatform/resources/rp-source-csv/vendors/bigcommerce.json +0 -66
  364. package/skills/wix-replatform/resources/rp-source-csv/vendors/magento.json +0 -60
  365. package/skills/wix-replatform/resources/rp-source-csv/vendors/shopify.json +0 -95
  366. package/skills/wix-replatform/resources/rp-source-csv/vendors/woocommerce.json +0 -82
  367. package/skills/wix-replatform/resources/rp-source-inputs/SKILL.md +0 -66
  368. package/skills/wix-replatform/resources/rp-source-sfcc/SKILL.md +0 -153
  369. package/skills/wix-replatform/resources/rp-source-shopify/SKILL.md +0 -394
  370. package/skills/wix-replatform/resources/rp-source-shopify/lib/discovery-reads.js +0 -200
  371. package/skills/wix-replatform/resources/rp-source-shopify/lib/import-findings.js +0 -34
  372. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-bulk-sqlite-worker.js +0 -44
  373. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-bulk.js +0 -1470
  374. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-cli.js +0 -446
  375. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-gift-cards.js +0 -474
  376. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-protected-fields.js +0 -879
  377. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-session.js +0 -426
  378. package/skills/wix-replatform/resources/rp-source-shopify/queries/articles.graphql +0 -24
  379. package/skills/wix-replatform/resources/rp-source-shopify/queries/blogs.graphql +0 -18
  380. package/skills/wix-replatform/resources/rp-source-shopify/queries/collections.graphql +0 -40
  381. package/skills/wix-replatform/resources/rp-source-shopify/queries/customers.graphql +0 -76
  382. package/skills/wix-replatform/resources/rp-source-shopify/queries/delivery-profiles.graphql +0 -51
  383. package/skills/wix-replatform/resources/rp-source-shopify/queries/discounts.graphql +0 -35
  384. package/skills/wix-replatform/resources/rp-source-shopify/queries/files.graphql +0 -20
  385. package/skills/wix-replatform/resources/rp-source-shopify/queries/gift-cards.graphql +0 -18
  386. package/skills/wix-replatform/resources/rp-source-shopify/queries/inventory.graphql +0 -31
  387. package/skills/wix-replatform/resources/rp-source-shopify/queries/locations.graphql +0 -21
  388. package/skills/wix-replatform/resources/rp-source-shopify/queries/menus.graphql +0 -21
  389. package/skills/wix-replatform/resources/rp-source-shopify/queries/metaobjects.graphql +0 -30
  390. package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-core.graphql +0 -150
  391. package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-fulfillments.graphql +0 -36
  392. package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-refunds.graphql +0 -48
  393. package/skills/wix-replatform/resources/rp-source-shopify/queries/pages.graphql +0 -20
  394. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-address.graphql +0 -8
  395. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-email.graphql +0 -6
  396. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-name.graphql +0 -8
  397. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-phone.graphql +0 -8
  398. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-address.graphql +0 -12
  399. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-email.graphql +0 -6
  400. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-name.graphql +0 -13
  401. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-phone.graphql +0 -13
  402. package/skills/wix-replatform/resources/rp-source-shopify/queries/products.graphql +0 -73
  403. package/skills/wix-replatform/resources/rp-source-shopify/queries/url-redirects.graphql +0 -13
  404. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-discovery.js +0 -693
  405. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-extract.js +0 -283
  406. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-gift-card-remedy.js +0 -191
  407. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-session-check.js +0 -115
  408. package/skills/wix-replatform/resources/rp-source-shopify/source-schema.example.json +0 -201
  409. package/skills/wix-replatform/resources/rp-source-wordpress/PLUGIN-READ-PLANS.md +0 -146
  410. package/skills/wix-replatform/resources/rp-source-wordpress/SKILL.md +0 -970
  411. package/skills/wix-replatform/resources/rp-source-wordpress/lib/blocked-data-handlers.js +0 -73
  412. package/skills/wix-replatform/resources/rp-source-wordpress/lib/checkout-field-csv-parser.js +0 -115
  413. package/skills/wix-replatform/resources/rp-source-wordpress/lib/checkout-field-csv-parser.test-fixture.js +0 -91
  414. package/skills/wix-replatform/resources/rp-source-wordpress/lib/coupon-discovery.js +0 -440
  415. package/skills/wix-replatform/resources/rp-source-wordpress/lib/currency-gate.js +0 -125
  416. package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-source-keys.js +0 -252
  417. package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-source-keys.json +0 -183
  418. package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-summary.mjs +0 -333
  419. package/skills/wix-replatform/resources/rp-source-wordpress/lib/invoice-discovery.js +0 -426
  420. package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-disposition.js +0 -400
  421. package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-entity-read-plan.js +0 -633
  422. package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-knowledge.js +0 -1127
  423. package/skills/wix-replatform/resources/rp-source-wordpress/lib/refund-corroboration.js +0 -194
  424. package/skills/wix-replatform/resources/rp-source-wordpress/lib/refund-corroboration.json +0 -100
  425. package/skills/wix-replatform/resources/rp-source-wordpress/lib/sampled-ids-batch.js +0 -157
  426. package/skills/wix-replatform/resources/rp-source-wordpress/lib/secret-value-guard.js +0 -234
  427. package/skills/wix-replatform/resources/rp-source-wordpress/lib/store-currency.js +0 -101
  428. package/skills/wix-replatform/resources/rp-source-wordpress/lib/structure-bridge-reader.js +0 -755
  429. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wix-wp-plugin-v2-client.js +0 -1074
  430. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wix-wp-plugin-v2-client.test-fixture.js +0 -149
  431. package/skills/wix-replatform/resources/rp-source-wordpress/lib/woo-inventory.js +0 -25
  432. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-http.js +0 -444
  433. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-image-url.js +0 -186
  434. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-plugin-detect.js +0 -1422
  435. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-redirect-sources.js +0 -343
  436. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-route-classifier.js +0 -680
  437. package/skills/wix-replatform/resources/rp-source-wordpress/lib/yoast-product-seo-evidence.js +0 -272
  438. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/README.md +0 -373
  439. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/advanced-custom-fields.json +0 -101
  440. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/back-in-stock-notifier-for-woocommerce.json +0 -81
  441. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/capabilities-pending-decision.json +0 -128
  442. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/cms-fallback-eligible.json +0 -18
  443. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/contact-form-7.json +0 -104
  444. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/facebook-for-woocommerce.json +0 -44
  445. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/fingerprint-aliases.json +0 -49
  446. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/free-gifts-for-woocommerce.json +0 -94
  447. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/index.json +0 -863
  448. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/mailpoet.json +0 -52
  449. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/membership-for-woocommerce.json +0 -161
  450. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/mwb-bookings-for-woocommerce.json +0 -180
  451. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/no-migration-needed.json +0 -286
  452. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/pricing-deals-for-woocommerce.json +0 -79
  453. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/product-extras-for-woocommerce.json +0 -120
  454. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/pw-woocommerce-gift-cards.json +0 -128
  455. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/requires-development.json +0 -88
  456. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/seo-by-rank-math.json +0 -80
  457. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/seriously-simple-podcasting.json +0 -46
  458. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/strong-testimonials.json +0 -55
  459. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/subscriptions-for-woocommerce.json +0 -99
  460. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/tablepress.json +0 -55
  461. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/the-events-calendar.json +0 -160
  462. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/ti-woocommerce-wishlist.json +0 -81
  463. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-checkout-field-editor-pro.json +0 -60
  464. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-discount-rules.json +0 -74
  465. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-product-bundle.json +0 -75
  466. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-gift-cards.json +0 -60
  467. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-other-payment-gateway.json +0 -44
  468. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-pdf-invoices-packing-slips.json +0 -57
  469. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-product-addon.json +0 -141
  470. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-progressive-discounts.json +0 -54
  471. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wordpress-seo.json +0 -158
  472. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wpc-composite-products.json +0 -97
  473. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wpforms-lite.json +0 -86
  474. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/capture-store-settings.js +0 -90
  475. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/plugin-knowledge-validate.js +0 -51
  476. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/plugin-knowledge.js +0 -103
  477. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-discovery.js +0 -2409
  478. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-plugin-inventory.js +0 -273
  479. package/skills/wix-replatform/resources/rp-source-wordpress/secret-value-patterns.v1.json +0 -11
  480. package/skills/wix-replatform/resources/rp-source-wordpress/source-schema.example.json +0 -71
  481. package/skills/wix-replatform/resources/rp-source-wordpress/sub-parts.json +0 -13
  482. package/skills/wix-replatform/resources/rp-target-wix/SKILL.md +0 -1046
  483. package/skills/wix-replatform/resources/rp-target-wix/app-page-routing-capabilities.json +0 -36
  484. package/skills/wix-replatform/resources/rp-target-wix/domains/README.md +0 -57
  485. package/skills/wix-replatform/resources/rp-target-wix/domains/SERVICE-PLUGINS.md +0 -322
  486. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/docs-survey.json +0 -70
  487. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/domain.json +0 -29
  488. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/category.json +0 -71
  489. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/comment.json +0 -83
  490. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/post.json +0 -95
  491. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/tag.json +0 -66
  492. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/docs-survey.json +0 -332
  493. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/domain.json +0 -75
  494. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/booking.json +0 -181
  495. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/service-add-on.json +0 -130
  496. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/service.json +0 -180
  497. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/docs-survey.json +0 -161
  498. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/domain.json +0 -25
  499. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/collection.json +0 -67
  500. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/data-item.json +0 -75
  501. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/reference.json +0 -41
  502. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/docs-survey.json +0 -204
  503. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/domain.json +0 -27
  504. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/contact.json +0 -275
  505. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/email-subscription.json +0 -105
  506. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/label.json +0 -77
  507. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/docs-survey.json +0 -306
  508. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/domain.json +0 -44
  509. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/delivery-profile.json +0 -171
  510. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/discount-rule.json +0 -182
  511. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-applied-discount.json +0 -129
  512. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-invoice-document.json +0 -192
  513. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item-options.json +0 -123
  514. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item.json +0 -85
  515. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-merchant-note.json +0 -112
  516. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-payment.json +0 -191
  517. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-refund.json +0 -164
  518. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order.json +0 -462
  519. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/pickup-location.json +0 -122
  520. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/shipping-option.json +0 -98
  521. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/wishlist-item.json +0 -86
  522. package/skills/wix-replatform/resources/rp-target-wix/domains/events/docs-survey.json +0 -128
  523. package/skills/wix-replatform/resources/rp-target-wix/domains/events/domain.json +0 -38
  524. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/category.json +0 -73
  525. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/event.json +0 -107
  526. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/registration-form.json +0 -89
  527. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/rsvp.json +0 -118
  528. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/ticket-definition.json +0 -93
  529. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/docs-survey.json +0 -86
  530. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/domain.json +0 -56
  531. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/entities/form-submission.json +0 -121
  532. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/entities/form.json +0 -134
  533. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/docs-survey.json +0 -41
  534. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/domain.json +0 -50
  535. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/entities/gift-card-product.json +0 -87
  536. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/entities/gift-card.json +0 -130
  537. package/skills/wix-replatform/resources/rp-target-wix/domains/index.json +0 -1418
  538. package/skills/wix-replatform/resources/rp-target-wix/domains/media/docs-survey.json +0 -41
  539. package/skills/wix-replatform/resources/rp-target-wix/domains/media/domain.json +0 -24
  540. package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-file.json +0 -75
  541. package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-folder.json +0 -71
  542. package/skills/wix-replatform/resources/rp-target-wix/domains/members/docs-survey.json +0 -119
  543. package/skills/wix-replatform/resources/rp-target-wix/domains/members/domain.json +0 -23
  544. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/badge.json +0 -71
  545. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/custom-field.json +0 -82
  546. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member-about.json +0 -77
  547. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member.json +0 -205
  548. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/docs-survey.json +0 -41
  549. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/domain.json +0 -46
  550. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/entities/plan-order.json +0 -137
  551. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/entities/plan.json +0 -133
  552. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/docs-survey.json +0 -7
  553. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/domain.json +0 -36
  554. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/amazon-shop-connect.json +0 -71
  555. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/ebay-shop-connect.json +0 -67
  556. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/google-youtube-connect.json +0 -72
  557. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/meta-catalog-connect.json +0 -74
  558. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/pinterest-catalog-connect.json +0 -69
  559. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/tiktok-catalog-connect.json +0 -61
  560. package/skills/wix-replatform/resources/rp-target-wix/domains/schema.json +0 -111
  561. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/docs-survey.json +0 -109
  562. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/domain.json +0 -73
  563. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/item-seo-tags.json +0 -148
  564. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/seo-pattern.json +0 -91
  565. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/site-seo-tags.json +0 -79
  566. package/skills/wix-replatform/resources/rp-target-wix/domains/site/docs-survey.json +0 -32
  567. package/skills/wix-replatform/resources/rp-target-wix/domains/site/domain.json +0 -49
  568. package/skills/wix-replatform/resources/rp-target-wix/domains/site/entities/business-contact.json +0 -103
  569. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/docs-survey.json +0 -203
  570. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/domain.json +0 -42
  571. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/back-in-stock-request.json +0 -119
  572. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/brand.json +0 -111
  573. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/category.json +0 -81
  574. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/coupon.json +0 -241
  575. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/inventory-item.json +0 -118
  576. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-customization.json +0 -111
  577. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-tag.json +0 -79
  578. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product.json +0 -221
  579. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/promotion.json +0 -84
  580. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/review.json +0 -178
  581. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/subscription-product.json +0 -87
  582. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/docs-survey.json +0 -106
  583. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/domain.json +0 -64
  584. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/manual-tax-mapping.json +0 -119
  585. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-group.json +0 -94
  586. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-region.json +0 -113
  587. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-settings.json +0 -65
  588. package/skills/wix-replatform/resources/rp-target-wix/lib/audit-summary.js +0 -64
  589. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-author-attribution.js +0 -165
  590. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-client.js +0 -60
  591. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-duplicates.js +0 -201
  592. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-ledger.js +0 -306
  593. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-report.js +0 -229
  594. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-stage.js +0 -55
  595. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build.js +0 -715
  596. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-media.js +0 -567
  597. package/skills/wix-replatform/resources/rp-target-wix/lib/bulk-results.js +0 -73
  598. package/skills/wix-replatform/resources/rp-target-wix/lib/contact-extended-fields.js +0 -194
  599. package/skills/wix-replatform/resources/rp-target-wix/lib/contact-import.js +0 -133
  600. package/skills/wix-replatform/resources/rp-target-wix/lib/contract-ledger.js +0 -150
  601. package/skills/wix-replatform/resources/rp-target-wix/lib/coupon-redemption-drift.js +0 -491
  602. package/skills/wix-replatform/resources/rp-target-wix/lib/customer-member.js +0 -104
  603. package/skills/wix-replatform/resources/rp-target-wix/lib/data-extension-schema.js +0 -673
  604. package/skills/wix-replatform/resources/rp-target-wix/lib/domain-knowledge.js +0 -835
  605. package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-balance-reconciliation.js +0 -119
  606. package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-build.js +0 -566
  607. package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-redemption-build.js +0 -249
  608. package/skills/wix-replatform/resources/rp-target-wix/lib/ndjson.js +0 -260
  609. package/skills/wix-replatform/resources/rp-target-wix/lib/order-applied-discount-build.js +0 -63
  610. package/skills/wix-replatform/resources/rp-target-wix/lib/order-buyer.js +0 -187
  611. package/skills/wix-replatform/resources/rp-target-wix/lib/order-channel-build.js +0 -64
  612. package/skills/wix-replatform/resources/rp-target-wix/lib/order-history.js +0 -1027
  613. package/skills/wix-replatform/resources/rp-target-wix/lib/order-invoice-contract.js +0 -90
  614. package/skills/wix-replatform/resources/rp-target-wix/lib/order-payment-mapping.js +0 -420
  615. package/skills/wix-replatform/resources/rp-target-wix/lib/pricing-plan-definition.js +0 -177
  616. package/skills/wix-replatform/resources/rp-target-wix/lib/product-import.js +0 -151
  617. package/skills/wix-replatform/resources/rp-target-wix/lib/product-seo.js +0 -723
  618. package/skills/wix-replatform/resources/rp-target-wix/lib/seo-redirects.js +0 -353
  619. package/skills/wix-replatform/resources/rp-target-wix/lib/shipping-build.js +0 -1437
  620. package/skills/wix-replatform/resources/rp-target-wix/lib/shipping-option-stage.js +0 -354
  621. package/skills/wix-replatform/resources/rp-target-wix/lib/store-config-build.js +0 -134
  622. package/skills/wix-replatform/resources/rp-target-wix/lib/stores-category-attach.js +0 -221
  623. package/skills/wix-replatform/resources/rp-target-wix/lib/stores-inventory.js +0 -248
  624. package/skills/wix-replatform/resources/rp-target-wix/lib/stores-verification.js +0 -321
  625. package/skills/wix-replatform/resources/rp-target-wix/lib/tax-build.js +0 -231
  626. package/skills/wix-replatform/resources/rp-target-wix/lib/url-mapper-transport.js +0 -28
  627. package/skills/wix-replatform/resources/rp-target-wix/lib/value-utils.js +0 -14
  628. package/skills/wix-replatform/resources/rp-target-wix/lib/variant-correlation.js +0 -34
  629. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-build.js +0 -388
  630. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-target-spec.js +0 -255
  631. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +0 -6041
  632. package/skills/wix-replatform/resources/rp-target-wix/lib/write-verification.js +0 -3
  633. package/skills/wix-replatform/resources/rp-target-wix/scripts/docs-survey-sync.js +0 -150
  634. package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge-validate.js +0 -69
  635. package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge.js +0 -100
  636. package/skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js +0 -112
  637. package/skills/wix-replatform/resources/rp-telemetry/SKILL.md +0 -484
  638. package/skills/wix-replatform/resources/rp-telemetry/lib/bi-sink.js +0 -318
  639. package/skills/wix-replatform/resources/rp-telemetry/lib/telemetry-recorder.js +0 -2831
  640. package/skills/wix-replatform/resources/rp-telemetry/lib/transcript-digest.js +0 -318
  641. package/skills/wix-replatform/resources/rp-telemetry/scripts/meter-from-audit.js +0 -83
  642. package/skills/wix-replatform/resources/rp-telemetry/scripts/rp-telemetry.js +0 -243
  643. package/skills/wix-replatform/resources/rp-website-continuation/SKILL.md +0 -94
  644. package/skills/wix-replatform/scripts/artifact-freshness.js +0 -76
  645. package/skills/wix-replatform/scripts/census-reconciliation.js +0 -25
  646. package/skills/wix-replatform/scripts/cms-fallback.js +0 -89
  647. package/skills/wix-replatform/scripts/discovered-census.js +0 -26
  648. package/skills/wix-replatform/scripts/discovered-set-status.js +0 -33
  649. package/skills/wix-replatform/scripts/entity-qa-attestation.js +0 -130
  650. package/skills/wix-replatform/scripts/execution-health-check.js +0 -415
  651. package/skills/wix-replatform/scripts/execution-manifest-validate.js +0 -43
  652. package/skills/wix-replatform/scripts/execution-state-prepare.js +0 -128
  653. package/skills/wix-replatform/scripts/expectations-ledger.js +0 -44
  654. package/skills/wix-replatform/scripts/import-contract-verify.js +0 -183
  655. package/skills/wix-replatform/scripts/internal-link-audit.js +0 -90
  656. package/skills/wix-replatform/scripts/item-seo-tags-verify.js +0 -205
  657. package/skills/wix-replatform/scripts/migration-completion.js +0 -76
  658. package/skills/wix-replatform/scripts/orchestration-preflight.js +0 -67
  659. package/skills/wix-replatform/scripts/orchestration-route.js +0 -77
  660. package/skills/wix-replatform/scripts/orchestration-state.js +0 -381
  661. package/skills/wix-replatform/scripts/read-back-gate.js +0 -175
  662. package/skills/wix-replatform/scripts/replatform-console.js +0 -146
  663. package/skills/wix-replatform/scripts/replatform.js +0 -436
  664. package/skills/wix-replatform/scripts/source-secrets.js +0 -77
  665. package/skills/wix-replatform/scripts/url-redirects.js +0 -286
  666. package/skills/wix-replatform/scripts/user-action.js +0 -58
  667. package/skills/wix-replatform/scripts/website-handoff-generate.js +0 -64
  668. package/skills/wix-replatform/scripts/wix-dashboard-url.js +0 -54
  669. package/skills/wix-replatform/scripts/wix-destination.js +0 -143
  670. package/skills/wix-replatform/scripts/wix-headless-scaffold.js +0 -762
  671. package/skills/wix-vibe-headless/SKILL.md +0 -183
  672. package/skills/wix-vibe-headless/install/deploy.cjs +0 -221
  673. package/skills/wix-vibe-headless/install/pin-agents-md.cjs +0 -35
  674. package/skills/wix-vibe-headless/platforms/README.md +0 -26
  675. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +0 -127
  676. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +0 -121
  677. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +0 -87
  678. package/skills/wix-vibe-headless/platforms/base44.md +0 -148
  679. package/skills/wix-vibe-headless/platforms/generic.md +0 -132
  680. package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +0 -30
  681. package/skills/wix-vibe-headless/references/_shared/routing.md +0 -86
  682. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +0 -270
  683. package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +0 -43
  684. package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +0 -25
  685. package/skills/wix-vibe-headless/references/blog/app/components/PostGrid.jsx +0 -15
  686. package/skills/wix-vibe-headless/references/blog/app/context/TaxonomyContext.jsx +0 -35
  687. package/skills/wix-vibe-headless/references/blog/app/hooks/usePostDetail.js +0 -38
  688. package/skills/wix-vibe-headless/references/blog/app/pages/Blog.jsx +0 -40
  689. package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +0 -54
  690. package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +0 -65
  691. package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +0 -50
  692. package/skills/wix-vibe-headless/references/blog/app/rest/wix-blog.js +0 -217
  693. package/skills/wix-vibe-headless/references/blog/seed/SEED.md +0 -99
  694. package/skills/wix-vibe-headless/references/blog/seed/seed-blog.cjs +0 -269
  695. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +0 -342
  696. package/skills/wix-vibe-headless/references/bookings/app/components/BookingForm.jsx +0 -65
  697. package/skills/wix-vibe-headless/references/bookings/app/components/RentalDurationPicker.jsx +0 -67
  698. package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +0 -47
  699. package/skills/wix-vibe-headless/references/bookings/app/components/ServiceGrid.jsx +0 -13
  700. package/skills/wix-vibe-headless/references/bookings/app/components/SlotPicker.jsx +0 -126
  701. package/skills/wix-vibe-headless/references/bookings/app/hooks/useRentalDuration.js +0 -128
  702. package/skills/wix-vibe-headless/references/bookings/app/hooks/useServiceDetail.js +0 -110
  703. package/skills/wix-vibe-headless/references/bookings/app/hooks/useServices.js +0 -58
  704. package/skills/wix-vibe-headless/references/bookings/app/lib/serviceFacts.js +0 -95
  705. package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +0 -94
  706. package/skills/wix-vibe-headless/references/bookings/app/pages/Services.jsx +0 -89
  707. package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-checkout.js +0 -148
  708. package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-rentals.js +0 -250
  709. package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-services.js +0 -300
  710. package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +0 -154
  711. package/skills/wix-vibe-headless/references/bookings/seed/seed-bookings.cjs +0 -333
  712. package/skills/wix-vibe-headless/references/bookings/seed/seed-rentals.cjs +0 -267
  713. package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +0 -178
  714. package/skills/wix-vibe-headless/references/cms/app/collection.config.js +0 -31
  715. package/skills/wix-vibe-headless/references/cms/app/lib/wixImage.js +0 -18
  716. package/skills/wix-vibe-headless/references/cms/app/rest/wix-cms.js +0 -231
  717. package/skills/wix-vibe-headless/references/cms/seed/SEED.md +0 -145
  718. package/skills/wix-vibe-headless/references/donations/INSTRUCTIONS.md +0 -88
  719. package/skills/wix-vibe-headless/references/donations/app/rest/wix-donations.js +0 -112
  720. package/skills/wix-vibe-headless/references/donations/seed/SEED.md +0 -11
  721. package/skills/wix-vibe-headless/references/donations/tests/donations.test.mjs +0 -258
  722. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +0 -267
  723. package/skills/wix-vibe-headless/references/events/app/components/CategoryFilter.jsx +0 -23
  724. package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +0 -43
  725. package/skills/wix-vibe-headless/references/events/app/components/EventGrid.jsx +0 -13
  726. package/skills/wix-vibe-headless/references/events/app/components/EventRegistration.jsx +0 -29
  727. package/skills/wix-vibe-headless/references/events/app/components/RsvpForm.jsx +0 -57
  728. package/skills/wix-vibe-headless/references/events/app/components/TicketPicker.jsx +0 -78
  729. package/skills/wix-vibe-headless/references/events/app/hooks/useEventDetail.js +0 -27
  730. package/skills/wix-vibe-headless/references/events/app/hooks/useEventsList.js +0 -54
  731. package/skills/wix-vibe-headless/references/events/app/hooks/useRsvpForm.js +0 -40
  732. package/skills/wix-vibe-headless/references/events/app/hooks/useTicketing.js +0 -81
  733. package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +0 -48
  734. package/skills/wix-vibe-headless/references/events/app/pages/Events.jsx +0 -36
  735. package/skills/wix-vibe-headless/references/events/app/rest/wix-events-browse.js +0 -136
  736. package/skills/wix-vibe-headless/references/events/app/rest/wix-events-registration.js +0 -187
  737. package/skills/wix-vibe-headless/references/events/seed/SEED.md +0 -103
  738. package/skills/wix-vibe-headless/references/events/seed/seed-events.cjs +0 -251
  739. package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +0 -487
  740. package/skills/wix-vibe-headless/references/forms/app/hooks/useWixForm.js +0 -425
  741. package/skills/wix-vibe-headless/references/forms/app/lib/wix-form-schema-utils.js +0 -187
  742. package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms-submissions.js +0 -238
  743. package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms.js +0 -108
  744. package/skills/wix-vibe-headless/references/forms/seed/SEED.md +0 -1836
  745. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +0 -328
  746. package/skills/wix-vibe-headless/references/members/app/components/LoginForm.jsx +0 -101
  747. package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +0 -33
  748. package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +0 -21
  749. package/skills/wix-vibe-headless/references/members/app/components/SocialButtons.jsx +0 -29
  750. package/skills/wix-vibe-headless/references/members/app/context/MemberContext.jsx +0 -54
  751. package/skills/wix-vibe-headless/references/members/app/hooks/useLoginForm.js +0 -77
  752. package/skills/wix-vibe-headless/references/members/app/pages/Account.jsx +0 -36
  753. package/skills/wix-vibe-headless/references/members/app/pages/Callback.jsx +0 -32
  754. package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +0 -35
  755. package/skills/wix-vibe-headless/references/members/app/rest/wix-members-auth.js +0 -390
  756. package/skills/wix-vibe-headless/references/members/install/Register.jsx +0 -22
  757. package/skills/wix-vibe-headless/references/members/seed/SEED.md +0 -21
  758. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +0 -270
  759. package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +0 -31
  760. package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionGrid.jsx +0 -15
  761. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +0 -39
  762. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectGrid.jsx +0 -15
  763. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectMedia.jsx +0 -29
  764. package/skills/wix-vibe-headless/references/portfolio/app/hooks/useCollectionProjects.js +0 -35
  765. package/skills/wix-vibe-headless/references/portfolio/app/hooks/usePortfolioGallery.js +0 -27
  766. package/skills/wix-vibe-headless/references/portfolio/app/hooks/useProjectDetail.js +0 -25
  767. package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +0 -39
  768. package/skills/wix-vibe-headless/references/portfolio/app/pages/Portfolio.jsx +0 -27
  769. package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +0 -60
  770. package/skills/wix-vibe-headless/references/portfolio/app/rest/wix-portfolio.js +0 -223
  771. package/skills/wix-vibe-headless/references/portfolio/seed/SEED.md +0 -90
  772. package/skills/wix-vibe-headless/references/portfolio/seed/seed-portfolio.cjs +0 -236
  773. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +0 -280
  774. package/skills/wix-vibe-headless/references/pricing-plans/app/components/OrderCard.jsx +0 -35
  775. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +0 -48
  776. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanGrid.jsx +0 -16
  777. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanPrice.jsx +0 -35
  778. package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/useMyPlans.js +0 -25
  779. package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/usePlanDetail.js +0 -28
  780. package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/usePlans.js +0 -30
  781. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/MyPlans.jsx +0 -30
  782. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +0 -53
  783. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/Plans.jsx +0 -28
  784. package/skills/wix-vibe-headless/references/pricing-plans/app/rest/wix-pricing-plans.js +0 -157
  785. package/skills/wix-vibe-headless/references/pricing-plans/seed/SEED.md +0 -82
  786. package/skills/wix-vibe-headless/references/pricing-plans/seed/seed-pricing-plans.cjs +0 -251
  787. package/skills/wix-vibe-headless/references/rentals/INSTRUCTIONS.md +0 -13
  788. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +0 -290
  789. package/skills/wix-vibe-headless/references/restaurants/app/components/ItemDialog.jsx +0 -81
  790. package/skills/wix-vibe-headless/references/restaurants/app/components/MenuItemCard.jsx +0 -71
  791. package/skills/wix-vibe-headless/references/restaurants/app/components/MenuList.jsx +0 -43
  792. package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartButton.jsx +0 -21
  793. package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartDrawer.jsx +0 -72
  794. package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +0 -66
  795. package/skills/wix-vibe-headless/references/restaurants/app/hooks/useItemOrder.js +0 -32
  796. package/skills/wix-vibe-headless/references/restaurants/app/hooks/useReservation.js +0 -73
  797. package/skills/wix-vibe-headless/references/restaurants/app/pages/Menu.jsx +0 -28
  798. package/skills/wix-vibe-headless/references/restaurants/app/pages/Reservations.jsx +0 -102
  799. package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-menu.js +0 -224
  800. package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +0 -218
  801. package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-reservations.js +0 -107
  802. package/skills/wix-vibe-headless/references/restaurants/seed/SEED.md +0 -176
  803. package/skills/wix-vibe-headless/references/restaurants/seed/seed-restaurants.cjs +0 -452
  804. package/skills/wix-vibe-headless/references/shared/app/components/WixManageBanner.jsx +0 -56
  805. package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +0 -9
  806. package/skills/wix-vibe-headless/references/shared/app/rest/wix-client.js +0 -185
  807. package/skills/wix-vibe-headless/references/shared/app/rest/wix-config.js +0 -8
  808. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -471
  809. package/skills/wix-vibe-headless/references/storefront/app/components/CartButton.jsx +0 -20
  810. package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +0 -167
  811. package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +0 -83
  812. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +0 -104
  813. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +0 -163
  814. package/skills/wix-vibe-headless/references/storefront/app/hooks/useShop.js +0 -83
  815. package/skills/wix-vibe-headless/references/storefront/app/hooks/useVariantOptions.js +0 -76
  816. package/skills/wix-vibe-headless/references/storefront/app/lib/storeImage.js +0 -64
  817. package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +0 -176
  818. package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +0 -218
  819. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +0 -174
  820. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +0 -546
@@ -1,3158 +0,0 @@
1
- ---
2
- name: rp-import-codegen
3
- description: >-
4
- Generates migration readers, transforms, setup plans, and thin Wix write specs from schema and mapping
5
- artifacts. Use when producing runnable extract/import code under the migration project.
6
- ---
7
-
8
- # rp-import-codegen
9
-
10
- Generate source readers, transforms, setup/import entrypoints, and thin Wix write specs from approved migration artifacts.
11
-
12
- ## Purpose
13
-
14
- This skill turns the schema, mapping, and setup decisions into implementation files under the active migration project.
15
-
16
- ## Required inputs
17
-
18
- - `migrations/<project>/source-schema.json`
19
- - `migrations/<project>/mapping/mapping-plan.json`
20
- - `migrations/<project>/setup/setup-plan.json`
21
- - `migrations/<project>/setup/setup-requirements.json` when setup affects write paths
22
-
23
- Prefer the machine-readable artifacts above. Markdown review files are secondary renderings
24
- for humans, not the primary codegen contract.
25
-
26
- ## Dependency readiness contract
27
-
28
- Copy `lib/dependency-readiness.js` and its shared dependencies into the generated runtime.
29
- Bind the execution manifest to the approved mapping plan with `mappingPlanDigest`.
30
- Each import task declares canonical `entityRefs`, the domain knowledge's `dependencyRefs`,
31
- and matching `dependencyEdges`. Every edge has exactly one producer: an import task,
32
- all bound setup step IDs, or an approved census disposition. Run `validateManifestForProject(projectDir)` after writing the candidate manifest; it loads the real census adapter and calls `bindDependencyProducers`. Validate the complete graph;
33
- missing, unreviewed, multiply bound or cyclic dependencies stop codegen.
34
-
35
- Declare `sourceBindings: [{ ref, sourceEntity, keyField, keyPrefix, manifestPath, extractPath }]`
36
- on tasks so the runtime can bind source keys to the complete extract and its manifest. Bind every contributing source separately, including multiple census/source populations targeting one ref; the pass records each manifest, extract and declared read plan without overwriting another source. Stamp attempts with the owning task ID and crosswalks with that binding’s source entity and raw source ID. Use the canonical target `domain/entity` on attempt/crosswalk rows; the gate also recognizes the existing singular, dotted and hyphenated forms for the same ref. A key outside that ref’s current source population, a key from another entity in a combined task, or mismatched evidence identity cannot resolve.
37
- Use `dependencySourceBindings` for excluded source-key associations. Preserve the reviewed
38
- mapping's census links rather than guessing associations from names. Setup-produced edges
39
- always use `task-complete`; use `per-record` only where target knowledge explicitly permits
40
- it for an import or excluded import dependency.
41
-
42
- Import the gate's `startImportRun`, `bindTask`, `evaluateTask`, `gateBatch` and
43
- `finishTaskPass`. For EAV extracts, source bindings additionally name the existing project-relative `readPlanPath`; the gate reuses the shipped EAV completeness and file-digest validator rather than treating a missing snapshot total as empty. Create one import run, bind each task, and create its Wix client from the
44
- returned capability. Immediately before every batch, including each selective/resume
45
- path, evaluate the task and pass the source-keyed records through `gateBatch`.
46
- Each `gateBatch` input is `{ sourceStableKey, transform: async ({ resolveReference, resolveTaskReference, resolveSetupReference }) => payload }` (or `{ sourceStableKey, payload }` when it needs no target references). Transforms receive `resolveReference(ref, sourceStableKey, path)` for per-record edges and `resolveTaskReference(ref, sourceStableKey, path)` for task-complete import edges only after a complete current producer pass (empty producers cannot resolve a key). This separate resolver preserves whole-task blocking on any partial producer. They also receive `resolveSetupReference(ref, sourceStableKey, path)` after setup passes. They do not read a crosswalk or choose target IDs themselves. Use concrete indexed paths for repeated order fields. Send only
47
- the returned `send` payloads, with each request's `sourceStableKey` and that batch's
48
- `readinessGrant`. Use `writers.bindRecordClient(taskClient, { readinessGrant: grant, sourceStableKey })` for existing writer composites, or `writers.withReadinessGrant(request, { readinessGrant: grant, sourceStableKey })` for individual builders. These retain trusted read/auxiliary marks. A valid grant never authorizes a raw or altered writer request; object-spreading a marked request loses its authority. Principal payloads still match every resolved reference. Closed product patch builders bind the current record’s product and compare references under each changed top-level field; omitted fields retain the existing product state. Only the closed media/payment/extended-field auxiliary builders omit those principal paths. For standard product stock, get the current product through the bound record client, call `registerImportInventoryScope(recordClient, canonicalProduct, targetProduct)`, and pass that record client to `storesInventory.reconcileInventory`. The same-record principal write or current site-bound crosswalk establishes product ownership; exact variant-choice matches, requested stock and actual location/item reads bound the composite. This preserves standard quantity/location mappings without expanding quick Shopify’s separate exception. Persist/report the returned deferrals without substituting empty IDs.
49
- The gate appends its own deferred attempt rows; do not append a duplicate deferred outcome. The runner appends started and terminal rows for attempted sends and persists confirmed crosswalks through the shared writers. Call `finishTaskPass` with the actual selection mode only after the pass finishes. Selective
50
- work never establishes a full/empty pass. The append-only `state/readiness/task-passes.ndjson`
51
- is evidence; `execution/dependency-readiness.json` is a diagnostic projection, not authority. Prerequisite deferrals remain current recovery work after admission, `started`, uncertain outcomes or failed retries. Only a current-site confirmed `imported`/`skipped_already_imported` outcome for the same task/key clears that lineage; admission is not completion. The runtime updates this task-scoped recovery index from journal deltas, and completion uses the same reducer.
52
-
53
- `--describe` must include `dependencyReadiness.tasks` with each task's ID and exact
54
- `{ ref, mode }` edges. Run `import-contract-verify` against the manifest. Generated importers
55
- must obtain capabilities only through the dependency gate: no setup/quick/probe issuer,
56
- caller-supplied phase, raw request escape, or direct execution-capability import. Site reconciliation uses `reconcileSiteEvidence({ taskBinding, readBack })`; reuse the existing unresolved-write reader/comparison against the recorded write. The callback returns `{ result, matchesRecordedWrite }` with the actual unchanged marked-read response from this site's registered client; a boolean or hand-built response never restamps evidence. Tasks with
57
- no dependencies still require their task capability and batch grant. For source orders call `orderShippingReadiness` from `lib/order-reference-readiness.js` with the bundled, verified `selectOrderReferenceMapping()` conclusion and the reviewed source delivery/pickup mapping as its `{ logistics }` option. Preserve source tax amounts/breakdown separately; omission of a target tax-group ID never permits losing source tax values. A missing or inconclusive conclusion returns a deferred reference-fidelity result; preserve it through `gateBatch` so the journal and existing mapping-gap ledger expose the affected records. No stored API response or accepted source ID proves dashboard fidelity. Always carry source currency explicitly: Import Order may store an empty currency rather than applying the documented site default. Keep the existing
58
- read-back gate below: readiness does not replace write verification or authorize resends.
59
-
60
- ## Generated read-back gate contract
61
-
62
- For `order`, `member`, `contact` and `product`, reuse the reviewed pure `buildWrite(record)`
63
- module and the entity decision's copied `readBackContract`. Generate no new omission policy or
64
- comparison rules. Each mapping row retains its exact `sourcePaths[]`, canonical `writePath` and
65
- `readPath` or `unverifiableReason`, with `fieldDispositions[].mappingRef` pointing to that row.
66
- Copy the bundled read-back libraries with the shared runtime; importing a CLI must not execute
67
- an extract or write. Other entities call `declaration(entity, { verificationKind, reason })`
68
- from `lib/read-back-gate.js` and retain that explicit coverage declaration in their manifest.
69
- Persist it with `recordDeclaration(projectDir, runId, entity, { verificationKind, reason })`
70
- from `lib/read-back-summary.js`; the existing gate artifact then supplies completion and CLI
71
- coverage reporting. A non-gated declaration never replaces gated-domain or earlier gate evidence.
72
-
73
- Before the first target write and every resume, load the existing
74
- `execution/read-back-inventory/<entity>.json` and call `ReadBackGate.prepare(inventory)`.
75
- The inventory is built by the streaming `scripts/read-back-gate.js inventory` command using the
76
- same builder; a missing extract means the check was not run. Validation requires the reviewed
77
- mapping, decision, resolution and inventory bindings. Missing, stale or incomplete field
78
- accounting holds the entity. Populated omissions require the existing identified mapping
79
- approval user action, including all four current references; 1-click cannot approve them.
80
-
81
- Wire `executeCanaries(getRecord)` before the entity's bulk import. The deterministic schedule
82
- covers every populated intended write path, with a default ceiling of ten records; raising it
83
- to at most thirty requires the recorded user decision. Canaries run serially, stop at the first
84
- failed or unknown verification, and are excluded from subsequent create calls. Keep normal bulk
85
- batching and concurrency after they pass. Call `verifySample({ record, sourceId, targetId,
86
- index, isLast })` after every 200th record and the final record, using one-based stable source
87
- order and a confirmed target ID. Synchronize at those sample boundaries; a failed sample stops
88
- new batches and requires `reconcileInterval(getRecord, targetIdFor)` before continuation.
89
- A partial final batch still performs its last-record check.
90
-
91
- An uncertain send is reconciled through the gate's existing identity lookup before any retry.
92
- Every lookup page and by-ID read must use the gate's budgeted read proxy (`reads.wix`), including
93
- product catalog sweeps and contact candidate reads; a failed or truncated lookup is unknown,
94
- never permission to send again. Preserve the gate and write-verification evidence across
95
- resumes. Never rewrite an imported order. Contact, product and member repairs use the gate's
96
- single-attempt-per-contract rule, unchanged revision marker and existing update path; a member
97
- repair writes its contact with that contact's current revision. Never overwrite a record edited
98
- since import.
99
-
100
- When `ensureMember` removes a phone through its approved phone-collision guard, compare against
101
- the request actually sent and preserve the writer's `guard-dropped` paths in the gate summary.
102
- Canaries record it automatically. For bulk writes, pass each returned `findings` array to
103
- `gate.recordGuard(sourceId, findings)` (it persists only the recognized collision finding), or
104
- as `verifySample({ ..., findings })` on a sampled record; samples and `reconcileInterval` then
105
- exempt only that record's guarded phone path. A missing target phone is never treated as a guard.
106
- Do not turn that guard into `notMigrated`, an observed Wix loss or a fabricated placeholder
107
- phone. Pending product-media processing is unknown until bounded settlement resolves; it cannot
108
- be classified as a permanent dropped field or used to approve an omission.
109
-
110
- Completion must retain `execution/read-back-gate.json`. Call the existing completion-report
111
- writer so its read-back producer supplies actual per-entity statuses, path counts, pending
112
- settlements, observed losses, approved omissions and merchant acknowledgements, plus recovery
113
- operator actions. Do not infer a pass from a successful HTTP write or emit a separate success
114
- summary. A held, not-run or unknown gate keeps the run incomplete, and missing acknowledgement
115
- for an approved omission remains a pre-launch task.
116
-
117
- ## Source read contract
118
-
119
- Generating a correct reader requires platform-specific knowledge — auth model,
120
- pagination, rate limits, and REST quirks. That knowledge lives in the matching **source
121
- adapter** skill, not here, so this skill never names a platform. Resolve the adapter from
122
- the `platform` field in `source-schema.json` via the naming convention `rp-source-<platform>`
123
- (e.g. `platform: "wordpress"` → `rp-source-wordpress`) and read its "Read contract"
124
- section. The operational facts should already be recorded in `source-profile.md`; use the
125
- adapter to fill any gaps rather than guessing. `rp-execute-import` runs the reader you
126
- generate and stays platform-agnostic — so the platform specifics must be baked into this
127
- generated code, not deferred to execution.
128
-
129
- Adding a new source platform therefore requires no change to this skill: a new
130
- `rp-source-<platform>` adapter is enough.
131
-
132
- ### Reading plugin-provided entities
133
-
134
- Entities that came from a source plugin carry extra `sourceMeta` the generated reader must
135
- respect. These are read mechanics, not mapping decisions:
136
-
137
- - **`origin: "embedded"`** — the records live inside a property of a parent record
138
- (`propertyPath`) on an already-fetched route (`embeddedIn`). Extract them from the parent
139
- fetch. Do **not** issue a second request per parent; some of these plugins have no route of
140
- their own at all.
141
- - **`requiresParent`** — a sub-collection route parameterized by the parent id
142
- (`.../{id}/notes`). Read the parent first and iterate; order this after the parent entity in
143
- the dependency graph.
144
- - **`channel: "plugin-rest-child"`** — a sub-resource on a CORE collection this plugin does
145
- not itself profile (e.g. WooCommerce orders), so it cannot use `requiresParent` (which
146
- points at another entity declared in the same profile). `route` carries a `{parentId}`
147
- template (`/wc/v3/orders/{parentId}/notes`); `parentRoute` names the collection route to
148
- iterate for real parent ids. Unlike discovery's `sampleChildEntities` (which checks only a
149
- representative few parents), the generated reader must iterate **every** parent record from
150
- `parentRoute` and fetch its child route — that full iteration is what actually migrates the
151
- data; the discovery-time sample only confirmed the shape is real.
152
- - **`context`** — when the adapter says view and edit contexts return different data, request
153
- the one the entity declares. Requesting only one context can silently halve the result set.
154
- - **Per-plugin credentials** — some plugin APIs use their own key system rather than the
155
- platform credential. Load them from project-local config like any other secret, and fail
156
- loudly with the missing key name rather than falling back to unauthenticated reads.
157
- - **`recognized: false`** (derived entity) — no plugin-specific read contract exists; read it as an ordinary
158
- `wp/v2`-style collection using the shared transport. Do not invent plugin-specific handling
159
- for it.
160
- - **`requestMethod`/`requestBody`** — the entity's real read path is not a plain GET
161
- collection; it is a declared non-GET method with a JSON body template. The
162
- generated reader must issue that exact method/body, not fall back to a GET.
163
- - A body value of the form `"$SAMPLED_IDS:<route>"` names another route whose record count is
164
- UNKNOWN and possibly unbounded (e.g. an entire product catalog) — never resolve it against
165
- one fixed id list fetched in a single request, at discovery time or at execution time,
166
- because `<route>` may hold far more ids than fit in one page or one request body. The
167
- generated reader must instead: (1) paginate `<route>` to exhaustion (every page, not a
168
- capped sample); (2) split the collected ids into bounded batches (mirror
169
- `lib/sampled-ids-batch.js`'s default of 50 ids per batch — do not send an unbounded id list
170
- in one request); (3) issue one request per batch against this entity's own route, with the
171
- placeholder resolved to just that batch's ids; (4) normalize each batch's RAW, unmodified
172
- response (envelope resolution, then `responseFragmentGroupSize` reassembly below — never
173
- pre-coerce a non-array raw response to an empty list before normalizing it, since an
174
- enveloped response is legitimately an object, not an array, until unwrapped) and merge the
175
- batches, deduplicating by `recordKeyField` (default `id`) — the same record can otherwise
176
- appear once per batch it happened to match; (5) stop and record the run as an explicit
177
- failure/deferred outcome on the first page or batch that cannot be read OR whose normalized
178
- result is not a valid array shape (a batch that doesn't match the declared envelope/fragment
179
- contract must defer the whole entity, not be counted as a legitimate empty batch — that
180
- would produce an exact-looking but false zero) — never emit a partial/undercounted or
181
- falsely-exact result as if it were complete. `rp-source-wordpress/lib/sampled-ids-batch.js`
182
- implements this exact algorithm (`collectAllIds`, `queryInBatches`) for `wp-discovery.js`
183
- itself; a generated reader for another platform must reproduce the same five steps, not
184
- invent a different policy.
185
- - **`responseFragmentGroupSize`** — the response arrives as N separate flat single-key array
186
- entries per logical record instead of one object (a plugin bug some source APIs have). The
187
- generated reader must chunk the raw array into groups of N and merge each group with
188
- `Object.assign` into one record before any mapping/transform runs or any count is emitted —
189
- per batch when combined with a `$SAMPLED_IDS` requestBody above, mirroring
190
- `wp-discovery.js`'s `inspectEntity`/`normalizeResponseRecords` handling of the same field.
191
-
192
- ### `structure-bridge-plugin` entities (`channel: "db-only"`)
193
-
194
- **New bound entities use the project-local plan contract in
195
- `rp-source-wordpress/PLUGIN-READ-PLANS.md`.** Carry `sourceMeta.extractionPlan` literally
196
- from canonical discovery through mapping. A profile is optional; absent/invalid plans
197
- remain typed deferrals. Never suffix physical table names or guess artifact filenames.
198
- Use `lib/generated-plugin-handoff.js`'s `emitPluginHandoff(projectDir)` to emit the small
199
- checked dispatch and importer/extractor entrypoints; preserve existing native readers,
200
- target handlers, selectors, ledger/idempotency and authorization/mute/setup preflight in
201
- the adapter described there. The emitter refuses existing non-template entrypoints before
202
- writing any destination. Deliberately adapt their implementation before replacing them;
203
- preserve heartbeat, self-check and dependency ordering as well as target writes. The
204
- adapter's pure `describeImport()` supplies its actual ledger/selector configuration and
205
- `entityOrder`; the wrapper checks that descriptor instead of asserting invented capabilities.
206
- Preflight runs once per import run before its first ready write.
207
- Run the committed `import-contract-verify.js` with the intended selection before any
208
- source/target I/O. The wrappers enforce preparation/full readiness and selected source
209
- prerequisites; discovery samples are preview only. Keep unrelated native reruns available.
210
- Include plan/evidence/assembly/extractor/helper inputs in existing freshness metadata.
211
-
212
- **Legacy caller compatibility follows below.** New discovery emits bindings for profiled
213
- entities too; it does not add a wrapping pass for old sidecars. Existing legacy readers
214
- retain their signature, pagination and complete-identity behavior. Do not use the legacy
215
- filename convention below as a fallback for a new selected entity with a missing binding.
216
-
217
- An entity whose `blocked[].fulfillment.kind` is `structure-bridge-plugin` has no REST
218
- route at all — its declared readiness (self-test passing, checked via
219
- `blocked-data-handlers.js` at planning time, per `rp-mapper`) means it is safe to treat
220
- as a normal candidate target, but its generated reader is genuinely different from every
221
- other reader this skill generates: it reads through `wix-wp-plugin-v2`'s `/query` or
222
- `/query/admin-key` route, not the platform adapter's own transport. This is **not**
223
- resolved through `blocked-data-requests.js`'s one-shot `attemptFulfillment()`/snapshot
224
- mechanism the way `csv-upload` is — that mechanism exists for a small, static,
225
- human-provided document; a `structure-bridge-plugin` table is a live, potentially large,
226
- paginated dataset, so it gets a normal incremental reader like any REST entity, just
227
- reading from a different transport.
228
-
229
- - **Vendor `rp-source-wordpress/lib/wix-wp-plugin-v2-client.js` into `src/lib/`**, the
230
- same way an ordinary WordPress reader vendors `wp-http.js`. Do not re-implement
231
- `discoverStructure()`/`queryStructure()` or their response validation.
232
- - **Vendor `rp-source-wordpress/lib/structure-bridge-reader.js` beside it and call
233
- `extractStructureBridgeEntity({ dataDir, entity, table, transport, expectEav })` once per
234
- entity. Do not hand-write the page loop, checkpoint, reconciliation or manifest.** It
235
- implements every rule below for both variants (single request and EAV read plan) and is
236
- covered by `tests/source-wordpress/structure-bridge-plugin-generated-reader-test.js`.
237
- `transport` is `{ httpClient, siteBaseUrl, auth, onSourceRead }` built from the credential
238
- rule below; set `expectEav: true` when the entity's discovery markdown reports an `eavPair`.
239
- It returns rather than throws: `status` is `promoted`, `reused` or `deferred` (with `code`
240
- and `reasons`), plus `bestEffort` and `redactions`. Continue with the other entities after
241
- a `deferred` one. Each deferral is also written to
242
- `data/source-extract/<entity>.deferred.json` (reason, affected rows, resumable files,
243
- prerequisites, resolution check and a **Copy prompt for Import Operator**); a later
244
- promotion marks it `resolved`. At completion, spread
245
- `deferredExtractionReportInput(await collectDeferredExtractions(dataDir))` into the
246
- `createCompletionReport()` input (merge its `operatorActions` and `statuses` with the
247
- importer's own), so the deferral reaches the handoff as a **Requires Import Operator**
248
- action. Pass `restart: true` only when the record says `restartRequired`. It never promotes an EAV read whose
249
- plan has `droppedIdentityColumns`, and never sets `acknowledgedDroppedColumns` (complete
250
- identity). The rest of this section is the contract that file implements.
251
- - **Generate the same bridge credential selection discovery used.** When
252
- `WMH2_MIGRATION_KEY` is configured, call `queryStructure()` with
253
- `auth: { type: 'migration-key', migrationKey, siteId, migrationId, timeoutMs }` so the
254
- vendored client uses signed `/query`; preferring the key when it is available avoids
255
- capability drift on the `manage_woocommerce`-gated route. Otherwise, when WooCommerce is
256
- present and a WordPress REST credential is configured, pass the generated `httpClient`
257
- and omit `auth` to use `/query/admin-key`. Keep one stable `migrationId` for the
258
- extraction process and let the client create a fresh request `jti` per page. If neither
259
- usable bridge credential exists, halt to needs-user before opening the output file.
260
- - **The structureRequest is fixed, not derived at codegen time.** Read it verbatim from
261
- the machine-readable artifact discovery already persisted:
262
- `data/wp-discovery/wix-wp-plugin-v2--<table>.structure-request.json`. **Never re-call
263
- `discoverStructure()` and rebuild a fresh default at codegen time**, even when the
264
- fulfillment declares no `sampleStructureRequest` of its own — the table's live schema
265
- could have changed between the discovery run and this codegen run, and re-deriving
266
- independently is exactly the drift this artifact exists to prevent; the sample and the
267
- full extraction must run the literal same request, not two requests that happen to
268
- usually agree. If the artifact does not exist (discovery never ran for this table, or
269
- discovery deliberately skipped building one — e.g. a >50-column table with no declared
270
- `sampleStructureRequest`, or a zero-column schema), this entity cannot be extracted
271
- yet: fail clearly, name the missing artifact path, and stop — do not invent a request.
272
- - **Page at the plugin's maximum, 200 — not discovery's small sample limit.** Loop: call
273
- `queryStructure({ httpClient, auth: bridgeAuth, structureRequest, cursor, limit: 200,
274
- onSourceRead })`, append every returned row, and continue only while
275
- `pagingMetadata.hasNext` is true. `bridgeAuth` is `undefined` for the admin-key path and
276
- the migration-key auth object above for the signed path.
277
- - **Pass `onSourceRead` on every `queryStructure` call, and file the totals once at the end**:
278
-
279
- ```js
280
- const totals = { discovery_queries: 0, scoped_queries: 0, tier_refusals: 0, redacted_values: 0 };
281
- const tables = { discovery_tables: new Set(), scoped_tables: new Set() };
282
- const onSourceRead = (counts) => {
283
- for (const k of Object.keys(totals)) totals[k] += counts[k] || 0;
284
- for (const t of counts.discovery_tables || []) tables.discovery_tables.add(t);
285
- for (const t of counts.scoped_tables || []) tables.scoped_tables.add(t);
286
- };
287
- // ...after the read loop, once:
288
- // node <rp-telemetry>/scripts/rp-telemetry.js source-read '<totals JSON>' --project <dir>
289
- ```
290
-
291
- The client derives the counts; the generated reader only accumulates and files them. Omitting
292
- the hook does not error — it just makes the run report that it read no key/value data, which
293
- is indistinguishable from a run that genuinely did not. Never abort an extraction because
294
- telemetry failed.
295
- - **Check `redactionMetadata.redactedValueCount` on every page.** Non-zero means a returned
296
- value matched a credential shape and arrived as `[REDACTED:secret-shaped]`. Write the row
297
- through as-is — the redaction is the plugin's decision, not something to work around — and
298
- surface the count and column names in the extraction's own report. It is a finding about the
299
- source site: something there keeps a secret in that column.
300
- - **A key/value table has TWO artifacts, and the second one is where the values come from.**
301
- `…--<table>.structure-request.json` selects key names only; running it verbatim extracts no
302
- values at all. The values come from `…--<table>.eav-read-plan.json`, which discovery writes
303
- beside it:
304
-
305
- ```jsonc
306
- {
307
- "table": "postmeta",
308
- "distinctKeyCount": 213, // across every pair
309
- "keysTruncated": false,
310
- "expectedBatches": 5, // total across all pairs
311
- "pageLimit": 200,
312
- "identityColumns": ["meta_id", "post_id"], // ride every batch, so values attribute to a record
313
- "orderColumn": "meta_id", "orderByIsUnique": true,
314
- "pairs": [ { "keyColumn": "meta_key", "valueColumn": "meta_value", "distinctKeyCount": 213,
315
- "batches": [ { "batchIndex": 0, "keys": ["…"],
316
- "structureRequest": { /* runnable as-is */ } } ] } ]
317
- }
318
- ```
319
-
320
- **Run every `pairs[].batches[].structureRequest` verbatim, page each at `pageLimit`, and
321
- concatenate into the same NDJSON the single-request path writes.** The batching is already
322
- done — each batch pins at most the plugin's 50-key cap, carries the table's identity columns
323
- so a value can be attributed to the record it belongs to, and selects only the value column
324
- belonging to the key it pins. Never merge batches, never add another pair's value column, and
325
- never author a scoped request yourself: an unpinned value column is refused with a 400 that
326
- looks exactly like naming a nonexistent column, so a hand-built request fails in the least
327
- diagnosable way available.
328
-
329
- **The EAV checkpoint replaces the single-request checkpoint below — do not write both.** The
330
- single-request shape (one cursor, one snapshot total, one request hash) cannot resume a
331
- multi-batch plan: on restart there is no way to tell which batches already finished. Every
332
- property that shape has is still needed, but *per active batch*:
333
-
334
- ```jsonc
335
- { "planFile": "…--postmeta.eav-read-plan.json",
336
- "planHash": "6cbfc47c83d83264", // the plan's own planHash, checked on resume
337
- "activeBatch": { // null between batches and when the plan is finished
338
- "batchIndex": 2,
339
- "cursor": "…", // opaque; null before the first page of this batch
340
- "hasNext": true, // REQUIRED — see below
341
- "rowsRead": 88,
342
- "snapshotTotal": 412 // this batch's first page total, then carried unchanged
343
- },
344
- "completedBatches": [ // one entry per FINISHED batch, no duplicates
345
- { "batchIndex": 0, "rowsRead": 412, "snapshotTotal": 412 },
346
- { "batchIndex": 1, "rowsRead": 130, "snapshotTotal": 130 }
347
- ],
348
- "rowsWritten": 542 }
349
- ```
350
-
351
- - `hasNext` is required for the same reason the single-request checkpoint requires it: the
352
- plugin returns `cursors.next: null` on the last page, so `cursor: null` alone cannot
353
- distinguish "this batch has not started" from "this batch just finished its final page".
354
- - `planHash` replaces `structureRequestHash`, and for the same reason: discovery can re-run
355
- and rewrite the plan — the site gained or lost keys — and resuming a half-finished
356
- extraction into a different batch layout silently mixes two reads. `reconcileEavReadPlan`
357
- rejects a checkpoint whose `planHash` does not match the plan on disk; on a mismatch,
358
- restart the extraction rather than resuming it.
359
- - `batchIndex` is unique across the whole plan, including across pairs, so resume means "skip
360
- every `completedBatches` index, then restart `activeBatch` from its cursor".
361
- - ⚠️ Never record a batch in `completedBatches` twice. A resume that re-runs a finished batch
362
- duplicates its rows in the output; reconciliation rejects the duplicate rather than letting
363
- a doubled file promote.
364
-
365
- **Which keys?** The plan contains every key the site actually has, which is the complete
366
- extraction and the right default. A mapping step may narrow `batches` to the keys that matter;
367
- because the plan is a persisted, diffable artifact, that narrowing is reviewable instead of
368
- buried in generated code. Regenerate the plan rather than editing requests inside it.
369
-
370
- **Reconcile before promoting the `.tmp` file**, with `reconcileEavReadPlan(plan, {
371
- planHash: checkpoint.planHash, batches: completedBatches, rowsWritten })` from
372
- `wix-wp-plugin-v2-client.js`. ⚠️ `planHash` is required and must come from the CHECKPOINT —
373
- passing the plan's own makes the check compare the plan to itself. It returns the
374
- problems; a non-empty result must fail the extraction loudly. "It did not throw" is not
375
- completeness — it catches four things a partial run gets wrong silently:
376
-
377
- - a batch the plan expects never ran, named by index rather than merely counted;
378
- - a batch that read fewer rows than its own first page promised, i.e. its page loop stopped
379
- early;
380
- - `rowsWritten` disagreeing with the sum of what the batches actually read;
381
- - `keysTruncated: true`, meaning key discovery hit its cap, so the key list itself is
382
- incomplete and the extraction cannot be called whole-table;
383
- - a non-empty `droppedIdentityColumns`, meaning the table is wider than the 50-column select
384
- cap so **every extracted row is missing columns**. Narrow the mapping and regenerate the
385
- plan with an explicit column choice, or set `acknowledgedDroppedColumns: true` on the plan
386
- to record that the omission was chosen. It does not pass by default, because a per-record
387
- incompleteness nobody decided on is the kind that reaches an import unnoticed.
388
-
389
- ⚠️ It also reports `orderByIsUnique: false` — a table with no primary or unique column is
390
- paged on a repeating sort key, so rows can shift between pages. That extraction is
391
- best-effort and must be reported as such, never as complete.
392
-
393
- **If the plan is absent for a table whose schema reports an `eavPair`, stop.** That means key
394
- discovery failed during the discovery run; the entity's markdown says so. Do not fall back to
395
- the key-names request and report the result as an extraction.
396
- - **Write through a temporary file, promoted atomically only after reconciliation
397
- passes — never write `data/source-extract/<entity>.ndjson` directly while the read is
398
- still in progress.** Append to `data/source-extract/<entity>.ndjson.tmp` and, after
399
- each durable append, checkpoint `data/source-extract/<entity>.ndjson.tmp.cursor.json`.
400
- ⚠️ **For a key/value table this is the EAV checkpoint shape above, not the single-request
401
- shape here** — the two are alternatives, never both. The single-request shape is
402
- (`{ "cursor": "...", "hasNext": true|false, "rowsWritten": N, "snapshotTotal": M,
403
- "structureRequestHash": "..." }`). `snapshotTotal` is the first page's
404
- `pagingMetadata.total`, captured once and carried through every subsequent checkpoint
405
- unchanged. `structureRequestHash` is a hash of the exact structureRequest this read
406
- started with (the JSON read from the sidecar at the start of THIS read, not re-read on
407
- every page) and is likewise carried unchanged through every checkpoint of the same
408
- read. wix-wp-plugin-v2's cursor is an opaque, HMAC-signed token encoding a frozen
409
- snapshot — unlike an ordinary REST reader's
410
- `page`/`offset`, it cannot be reconstructed from row count alone, so the checkpoint is
411
- the only way to resume correctly. **`hasNext` is required, not cosmetic: the plugin
412
- returns `cursors.next: null` on the last page precisely because there is nothing
413
- further to fetch, so a checkpoint's `cursor: null` is ambiguous between "never started"
414
- and "just finished the final page" unless `hasNext` is also recorded.**
415
- **`structureRequestHash` is required for the same reason a final file needs a
416
- manifest: discovery can re-run and rewrite the sidecar to a different approved query
417
- while a `.tmp` from an earlier read is still in progress, and the plugin's cursor
418
- encodes only an offset and a snapshot total — it has no way to detect that the query
419
- underneath it changed. Without this check, a resume would silently splice rows queried
420
- under the old request with a continuation queried under the new one, and the file would
421
- end up permanently mislabeled by whichever request happened to be current when it was
422
- promoted.** On resume, first hash the CURRENT sidecar and compare it against the
423
- checkpoint's `structureRequestHash`:
424
- - If they differ, the approved query changed since this `.tmp` was started. DELETE both
425
- the `.tmp` file and its checkpoint and restart the read from `cursor: null` into a
426
- freshly created `.tmp`, using the current sidecar's request and hash — do not attempt
427
- to salvage or continue rows written under the old request.
428
- - If they match, compare the `.tmp` file's own line count against the checkpoint's
429
- `rowsWritten`:
430
- - If they match and `hasNext` is `true`, continue paging from the saved `cursor`,
431
- using the same structureRequest this read started with.
432
- - If they match and `hasNext` is `false`, the read itself is already complete — skip
433
- straight to the completion reconciliation below (comparing `rowsWritten` against
434
- the checkpoint's own `snapshotTotal`, with no further plugin call) and promote.
435
- **Do not call `queryStructure` again with `cursor: null` in this case** — that
436
- re-issues page one and appends a full duplicate copy onto an already-complete temp
437
- file.
438
- - If they do not match (a prior interrupted write), DELETE both the `.tmp` file and
439
- its checkpoint and restart the read from `cursor: null` into a freshly created
440
- `.tmp` file — resuming a mismatched cursor against stale content, or restarting
441
- from `cursor: null` while appending onto the existing (possibly stale or partial)
442
- file, both duplicate rows.
443
-
444
- Reconciliation passing is not the last check. **Immediately before promoting — even on
445
- a single uninterrupted run that never needed to resume — re-hash the current sidecar
446
- one more time and compare it against the checkpoint's carried `structureRequestHash`.**
447
- All of the pages in this read could have completed correctly under request A while
448
- discovery replaced the sidecar with request B somewhere in the middle; nothing earlier
449
- in this contract catches that, because the resume-time hash check above only ever runs
450
- when a read is picked back up after being interrupted, not at the end of one that ran
451
- straight through. If the hashes differ at this final check, do **not** promote — leave
452
- the `.tmp` file and its checkpoint in place (recording `structureRequestHash: A`,
453
- exactly as it was throughout the read) and report the entity as a deferred extraction,
454
- the same as any other unresolved page error; the next run's resume logic will see the
455
- mismatch and correctly restart under the now-current request. Writing the manifest with
456
- request A's hash after promoting under request B would make the bookkeeping honest but
457
- would still let request A's data reach `data/source-extract/<entity>.ndjson` and
458
- everything downstream of it after B became the approved query — the check has to block
459
- the promotion itself, not just label what already happened.
460
-
461
- ⚠️ **For a key/value table, substitute `planHash` for `structureRequestHash` throughout the
462
- paragraph above.** The hazard is identical — discovery can replace the plan mid-read, and only
463
- a final re-hash catches a read that ran straight through under the old one — but the artifact
464
- is the read plan, so re-derive the hash of `…--<table>.eav-read-plan.json` and compare against
465
- the checkpoint's `planHash`. **Use `eavReadPlanHash(plan)` from
466
- `wix-wp-plugin-v2-client.js` — never `JSON.stringify` and hash it yourself.** It excludes the
467
- plan's own `planHash` field (a hash cannot cover itself) and sorts keys, so the value it
468
- returns is the one discovery stamped. Three call sites hashing "the plan" three slightly
469
- different ways is how this check ended up unable to detect anything. There is no single `structureRequest` to hash: an EAV read runs one
470
- request per batch, all of them derived from the plan, which is why the plan is the unit of
471
- provenance. `reconcileEavReadPlan()` **requires** `planHash` in the observation it is given —
472
- pass the checkpoint's, never the plan's own, or the check compares the plan to itself and
473
- proves nothing.
474
-
475
- Only once this final hash check and the completion reconciliation above both pass does
476
- the `.tmp` file get renamed (atomically) to `data/source-extract/<entity>.ndjson` and
477
- its checkpoint deleted, and the manifest below written using the checkpoint's own
478
- `structureRequestHash` (now confirmed current, not merely carried).
479
-
480
- ⚠️ **That order is load-bearing: rename first, write the manifest second.** The two writes
481
- cannot be atomic together, so one has to survive a crash between them. Writing the manifest
482
- first means a crash leaves a NEW manifest sitting beside an OLD final file — it verifies
483
- cleanly against data it does not describe, and the next run reuses stale rows silently.
484
- Renaming first means a crash leaves a fresh file with a missing or stale manifest, which the
485
- reuse check refuses, so the entity is re-extracted. Re-doing work is the acceptable failure;
486
- trusting the wrong rows is not. If
487
- `data/source-extract/<entity>.ndjson` already exists (no `.tmp` in progress), do not
488
- assume it is complete just because the file is present — see the manifest/provenance
489
- check below before trusting it.
490
- - **A final `.ndjson` file is only trustworthy if it was produced by the structureRequest
491
- currently on file.** On promotion, write
492
- `data/source-extract/<entity>.manifest.json` recording the read's own
493
- `structureRequestHash` (the same value carried through its checkpoints, not
494
- recomputed), plus `rowsWritten`, `snapshotTotal`, and the extraction timestamp. Before
495
- treating an existing `data/source-extract/<entity>.ndjson` as already fully extracted,
496
- hash the CURRENT sidecar and compare it against the manifest's stored hash. A match
497
- means skip re-extraction. A mismatch — the table's approved query changed since that
498
- file was written — or a missing manifest means the existing file is stale: do not reuse
499
- it; re-extract from `cursor: null` into a fresh `.tmp` the same as if no file existed.
500
-
501
- ⚠️ **A key/value table's manifest has a different shape, because the read did.** There is no
502
- single `snapshotTotal` — each batch has its own — and no single `structureRequestHash`:
503
-
504
- ```jsonc
505
- { "kind": "eav-read-plan",
506
- "planFile": "…--postmeta.eav-read-plan.json",
507
- "planHash": "6cbfc47c83d83264",
508
- "completedBatches": [ { "batchIndex": 0, "rowsRead": 412, "snapshotTotal": 412 } ],
509
- "rowsWritten": 542,
510
- "keysTruncated": false,
511
- "droppedIdentityColumns": [], // non-empty = rows are incomplete per record
512
- "acknowledgedDroppedColumns": false, // the APPROVAL that let an incomplete read promote
513
- "orderByIsUnique": true, // false = paging was best-effort
514
- "ndjsonDigest": "sha256…", // binds this manifest to the file it describes
515
- "extractedAt": "…" }
516
- ```
517
-
518
- Build it with `eavExtractionManifest(plan, { planFile, completedBatches, rowsWritten,
519
- extractedAt, ndjsonDigest })` rather than by hand, so a new qualifier cannot be forgotten at
520
- one call site. It refuses a missing `planFile` or `ndjsonDigest`, and
521
- `eavExtractionIsReusable()` requires every field it emits — the documented shape and the
522
- builder are the same contract, checked both ways.
523
-
524
- ⚠️ **Compute `ndjsonDigest` from the promoted file, with `eavExtractionFileDigest()`, AFTER
525
- the rename.** Every other field describes the read that was supposed to happen; this is the
526
- only one that says what actually landed on disk.
527
-
528
- Decide reuse with `eavExtractionIsReusable(plan, manifest, observed)`, which returns the
529
- reasons an existing file cannot be trusted; empty means skip re-extraction. `observed` is what
530
- is on disk **right now** and is required:
531
-
532
- ```jsonc
533
- { "planFile": "…--postmeta.eav-read-plan.json", // the path YOU resolved for this entity
534
- "ndjsonDigest": "sha256…", // eavExtractionFileDigest(<final file>)
535
- "ndjsonRowCount": 542 } // lines in the final file
536
- ```
537
-
538
- ⚠️ **Never open `manifest.planFile`.** A manifest is untrusted input — it can name
539
- `../../something-else.json` — so following its path would let the file choose what it is
540
- validated against. Pass the sidecar path you already resolved; the check compares the two and
541
- refuses a mismatch. The same reasoning is why the digest and row count come from the file you
542
- are about to reuse, not from the manifest: a manifest that is only checked against itself
543
- cannot detect a truncated, replaced or half-written NDJSON beside it. ⚠️ **The plan hash alone is
544
- not the whole decision.** `acknowledgedDroppedColumns` is deliberately excluded from the hash
545
- so that acknowledging an omission cannot invalidate an in-flight resume — which means
546
- *withdrawing* it would leave the hash unchanged, and an extraction that promoted only because
547
- someone approved its missing columns would still look reusable. The reuse check compares the
548
- approval directly, in both directions: withdrawn, and no-longer-needed.
549
-
550
- The staleness half is the same idea against the plan: `eavReadPlanHash()` the current plan
551
- file and compare with the manifest's `planHash`; a mismatch or a missing manifest means
552
- re-extract. ⚠️ `reconcileEavReadPlan()` additionally verifies the plan against ITS OWN embedded
553
- hash before anything else, so a plan file edited by hand after discovery wrote it is refused
554
- outright — without that, `planHash` only proved two strings matched. Carry
555
- `keysTruncated`, `droppedIdentityColumns` and `orderByIsUnique` into the manifest **because
556
- they qualify what the file contains** — a downstream consumer reading an extraction that
557
- silently omitted columns, or was paged on a tying sort key, must be able to learn that from
558
- the artifact rather than from whoever happened to run it.
559
- - **Fail clearly, never loop or silently truncate**, if `hasNext` is true but the
560
- returned `cursors.next` is missing or identical to the cursor just used — record the
561
- entity as a failed/deferred extraction with that reason (leaving the `.tmp` file and
562
- its checkpoint in place for the next resume attempt), the same as any other reader's
563
- unrecoverable page error.
564
- - **Reconcile before promoting, never after.** The first page's `pagingMetadata.total`
565
- is the frozen snapshot total for the whole read. Before renaming `.tmp` to the final
566
- `.ndjson`, compare it against the `.tmp` file's own line count; a mismatch is reported
567
- as a failed/deferred extraction, and the `.tmp` file is NOT promoted — a row inserted
568
- or deleted mid-read can still shift a page's contents (an accepted residual gap), but the counts themselves must always be checked before anything
569
- downstream is allowed to treat this entity as complete.
570
- - **Authorization and query validation are the plugin's job, not the reader's.**
571
- wix-wp-plugin-v2 re-validates every identifier against a live `DESCRIBE` on every
572
- request; the generated reader only needs to treat the plugin's 403/404 responses as a
573
- failed extraction for that entity, not re-derive its own authorization logic.
574
-
575
- ### CSV sources (`platform: "csv"`)
576
-
577
- When `source-schema.json.platform === "csv"`, generate a **file reader** instead of an HTTP
578
- reader and read `rp-source-csv` → "Read contract". The differences that matter:
579
-
580
- - **Vendor `lib/csv-parse.js` into `src/lib/`** and import it, exactly as a WordPress reader
581
- vendors `wp-http.js`. Do not re-emit parsing: the sampler and the reader must agree on what
582
- the file contains. There is no auth, no pagination, and no rate limiting to generate.
583
- - **Grouping is the reader's core job.** Replay `sourceMeta.sourceFiles[].layout`: with
584
- `continuation: "blank-key"` a blank key extends the current group; with a `sectioned`
585
- pattern the discriminator column routes parent vs child rows and `parentRefColumn` resolves
586
- a child to its parent (it may hold `id:123` or a SKU, and the child is not guaranteed to
587
- follow its parent).
588
- - **Iterate the file set** by role, treating `sourceFiles[].partOf` entries as continuations
589
- of the same logical stream and honoring each part's own column order.
590
- - **Materialize `column-values` entities**: collect the distinct values of the source column
591
- and their ancestors into their own entity file, emitted depth-ascending so a parent
592
- category exists before its child, plus the linking relation. Do not invent Wix ids at
593
- extract time — the `ImportCrosswalk` resolves them at import time.
594
- - **Apply `sourceMeta.dialect.emptyPolicy`** through the shared `coerceEmpty` helper so a
595
- required Wix field is never fed an empty string the source did not have.
596
- - CSV values are plain text; write them as-is with no entity-decoding step.
597
-
598
- ### Shopify sources (`platform: "shopify"`)
599
-
600
- When `source-schema.json.platform === "shopify"`, read `rp-source-shopify` → "Read contract"
601
- instead of generating a reader. The differences that matter:
602
-
603
- - **Every read goes through the adapter's CLI wrapper** (`rp-source-shopify/lib/shopify-cli.js`),
604
- never a direct HTTP call and never a stored token: the store session lives in the operator's
605
- Shopify CLI. Vendor the wrapper; do not re-emit it.
606
- - **The full-store extract is the adapter's bulk extractor**, not a generated per-record
607
- GraphQL pager: the extract step is `rp-source-shopify/scripts/shopify-extract.js` (resumable,
608
- one bulk dataset at a time, writing `data/source-extract/<entity>.ndjson` and
609
- `extract-manifest.json`; a source catch-up rerun passes `--catch-up`). Generate the importer
610
- to read those files only once the manifest says `complete`. If this bundle does not ship the
611
- extractor, stop codegen and report it; do not improvise one.
612
- - **A gap found during the extract** (a read short of Shopify's count, a withheld field) is
613
- recorded through `rp-source-shopify/lib/import-findings.js` `stopWithFinding`, which adds the
614
- coverage row and stops the extract with the finding's code. Never drop or zero-fill it.
615
-
616
- ## Target write contract
617
-
618
- Symmetrically, do **not** re-derive the Wix write surface here. It is identical for
619
- every migration and is pre-verified in the **`rp-target-wix`** internal resource (see
620
- `CONVENTIONS.md`), which ships shared Wix runtime code: verified request builders,
621
- executors, and reusable execution logic for retries, throttling, checkpoints, and
622
- reporting. **Vendor copies of the shared runtime modules from `rp-target-wix` into the
623
- project** (like the source transport) and generate thin project-specific write specs and
624
- transforms that **call those shared primitives/runtime functions**. Never hand-emit Wix
625
- endpoints/bodies inline —
626
- that path repeatedly shipped wrong shapes (lowercase Ricos plugin enums, `{tag:{…}}`
627
- tag bodies, `media.wixMedia.image.id` featured images) that only failed at execution.
628
-
629
- **Validate by real call, not by doc example.** MCP doc checks confirm an endpoint
630
- *exists*; they do not confirm the request *shape works* — public examples have been
631
- wrong (e.g. Ricos plugin enums shown lowercase that 400). Trust a Wix request shape only once a real call (or
632
- `tests/target-wix/contract-test.js` in live mode, run from the repo root) has succeeded. Treat the
633
- adapter's `// VERIFIED:` shapes as the source of truth over any docs example.
634
-
635
- Adding a new source platform requires no change here, and the Wix surface change for
636
- *all* migrations is a one-place edit in `rp-target-wix` (caught by its contract test),
637
- not a per-project regeneration.
638
-
639
- ## Workflow
640
-
641
- 1. Read the discovery, mapping, and setup artifacts, plus the source adapter's read
642
- contract and the `rp-target-wix` write contract.
643
- If a mapping entity includes `targetRef`, validate its selected writer and reliability
644
- against `rp-target-wix/scripts/domain-knowledge.js summarize-entities`; do not remap
645
- source entities independently.
646
- 2. Generate machine-readable execution artifacts:
647
- - `execution/execution-manifest.json`
648
- - `execution/llm-handoff.json`
649
- - when `SAFE_MODE=true` or `DRY_RUN=true`, `execution/review/code-safety-review.md`
650
- - optional supporting execution subplans only when the runtime truly needs them
651
- 3. Generate setup code that can verify/provision Wix prerequisites by executing the
652
- machine setup artifacts through the shared setup runtime.
653
- 4. Generate source reader code that can enumerate and fetch source entities. If the adapter
654
- ships a shared transport module (auth, pagination, throttling, retries), vendor a copy of
655
- it into the project (e.g. `migrations/<project>/src/lib/`) and import from it instead of
656
- re-emitting that plumbing — the reader should hold only per-project orchestration.
657
- The reader must extract source records to durable files on disk; it must not require the
658
- whole source dataset to live in memory before import begins.
659
- 5. Generate transform code that maps source records into Wix-shaped objects — thin glue over
660
- `wix-build.js`, which already owns slug sanitizing and the money/price/variant rules; see
661
- "Never hand-write slug sanitizing" below.
662
- **Wix Stores products: Catalog V3 only. Catalog V1 is not supported by this workflow —
663
- there is nothing to generate for it.** The only destination a migration ever writes to is
664
- a `V3_CATALOG` site (guaranteed at provisioning), so generated code may
665
- assume V3 unconditionally: no version detection, no branch, no V1 shape, no V1 fallback
666
- for a failing V3 write. A V1 site (only reachable on a pre-existing site this run did not
667
- create) is a **blocker the run halts on**, never a case codegen handles. Concretely: never
668
- emit Catalog V1-style top-level `price`, `sku`, or legacy inventory objects.
669
- V3 `variantsInfo.variants[].inventoryItem` is handled by the inventory contract below. For simple products, emit one variant under
670
- `variantsInfo.variants[]` with variant-level `price`, `sku`, and physical properties.
671
- When the source product is subscription-based and the source payload exposes explicit
672
- recurring cadence in structured product data, emit native
673
- `product.subscriptionDetails` with at least `allowOneTimePurchases` and one
674
- `subscriptions[]` entry carrying `title`, `description`, `frequency`, `interval`, and
675
- `autoRenewal`. Do not emit placeholder-only metadata or skip those products on create
676
- when that cadence can be inferred deterministically; live create coverage for this
677
- shape was verified on July 26, 2026.
678
- Product HTML descriptions land in `plainDescription`, which Wix converts to rich content
679
- server-side — it is HTML, not a plain-text flattening, so there is no fidelity loss to
680
- accept. Do NOT call the Ricos conversion endpoint on the product path: it costs one HTTP
681
- round-trip per product ahead of a bulk create, and that burst is what the endpoint throttles
682
- with a 403. Two traps: `plainDescription` is silently ignored when `description` is also set
683
- (set exactly one), and it is capped at 16,000 characters — longer bodies need truncation or
684
- an info section, recorded in `mapping-gaps.json`. Ricos conversion remains correct for blog
685
- posts, where `richContent` really is a Ricos document.
686
- **Never let lost product options become a silent default variant.** When the source
687
- declares variation attributes for a product, set canonical `sourceDeclaredOptionNames` to
688
- those names in source order, even when no variant choices can be resolved from them (the
689
- WooCommerce all-"Any" shape: the parent declares attributes, every variation's `attributes`
690
- is `[]`). Leave it unset when the source declares none. `buildProduct` then throws for a
691
- product that declared options but built zero. Catch that throw in the existing per-record
692
- error handling: do not create the product, and write a `mapping-gaps.json` entry naming the
693
- product (`sourceId`, name) and its declared-but-unresolved option names, so it is reported
694
- for human review rather than counted as created. Do not guess combinations or map them to
695
- modifiers. The only exception is an existing `orchestration/decisions.json` entry under
696
- `optionLoss:<product.sourceId>`: look it up and, if present, pass it as
697
- `buildProduct(product, { ..., optionLossDecision: { key, entry } })`. `buildProduct`
698
- accepts it only when `source` is `user`, `value.accept` is `default-variant`,
699
- `value.sourceProductId` is this product's `sourceId`, and `value.declaredOptionNames` equals
700
- `sourceDeclaredOptionNames`. Generated code never creates, infers or defaults that entry. It
701
- records only one user's explicit answer for that one product: never from inference, a prior
702
- artifact, a mapping or execution approval, 1-click approvals, or a blanket answer. Even with
703
- a valid decision, still write the `mapping-gaps.json` entry, marked accepted with the
704
- decision key, so the loss stays reported.
705
- Media scope should be relationship-driven by default: generate imports only for media
706
- referenced by mapped entities, not for the entire unattached source library. When the
707
- target adapter says an entity can ingest external URLs directly (for example Wix Stores
708
- product media), generate that entity-native path instead of routing those files through
709
- the slower generic Media Manager import flow.
710
- **Never let generated code name an image field.** A source image reference usually carries
711
- several URLs for the same picture at different sizes, and the field that holds the original
712
- differs between routes of the same source — so `images.map((i) => i.src)` is a codegen
713
- defect, not a shortcut. Emit a call to the source adapter's resolver instead (WordPress:
714
- `resolveImageUrls` in `rp-source-wordpress/lib/wp-image-url.js`), which returns the
715
- original at maximum resolution, percent-encoded, with `altText` and `displayName` attached.
716
- The destination copies the one URL it is given and cannot be upgraded later, so a crop
717
- emitted here is permanent.
718
- **Never let generated code treat a media write's 200 as done.** Entity-native ingestion is
719
- asynchronous: the destination acknowledges the queue entry and fetches the file afterwards,
720
- and the write is a full replace, so a fetch that fails later leaves the entity with **no
721
- image at all** — no error, and the previous image already discarded. Items can
722
- remain un-ingested well after an accepted write. Generated media code
723
- must therefore read the entity back and re-send whatever did not land, and must feed BOTH
724
- failure kinds — the write that was refused (429) and the write that was accepted but never
725
- ingested — into the same retry list. Call `patchStoresProductMediaVerified` rather than
726
- `patchStoresProductMedia`; emitting the unverified writer for a media-bearing import is a
727
- codegen defect. Whatever never lands is reported per entity, by name — never summarised
728
- into a count.
729
-
730
- **WordPress-family product media is fail-closed before any Stores request.**
731
- This applies only when `orchestration/decisions.json` → `sourcePlatform.value` is exactly
732
- `woocommerce` or `wordpress`; do not read the wrapper object or the nullable
733
- `orchestration/run.json` field. Quick mode remains unchanged. Vendor the shared root
734
- `lib/confirmed-pictures.js` and `lib/product-media-deferral.js` while preserving the normal
735
- toolkit layout, and emit this sequence in the generated importer:
736
-
737
- 1. Load the target writer module first and use its exported `confirmedPictures` object for
738
- every declaration, source-media read and confirmation; never require another toolkit-root
739
- or vendored copy of `confirmed-pictures.js`. At process start, read
740
- `decisions.sourcePlatform.value` and call
741
- `writers.confirmedPictures.declareMediaSource(sourcePlatformValue)` once. The first
742
- declaration is final. Do this for every source; the target guard activates only for the
743
- two identities above. Under either guarded identity call
744
- `writers.assertProductMediaGuardActive()` before the first product write. A failed
745
- assertion is a startup error: do not continue with an unguarded writer.
746
- 2. For the products selected in this pass (failed-only means only those products), resolve
747
- each complete ordered gallery with `resolveImageUrls`, collect its positive `sourceId`
748
- values, and call
749
- `readSourceMediaRecords({ ids, source: { baseUrl, request: authenticatedWpRequest } })`
750
- once before the first product media write. `authenticatedWpRequest` must delegate to the
751
- shared `fetchJson` while adding the run's existing WordPress auth headers. The helper
752
- de-duplicates/sorts ids and performs
753
- sequential authenticated `wp/v2/media` reads in chunks of 100 with one 60-second total
754
- deadline per chunk. Do not persist the read or source picture data.
755
- 3. Call `confirmPictures({ pictures, read })` for each whole gallery. Use only its frozen
756
- `pictures` list. Do not clone, rebuild, subset, extend, reorder, or hand-author that list;
757
- the shared Stores boundary rejects any URL gallery without that provenance before a
758
- request. `buildStoresProductMedia` is the one enforcement point used by create, bulk
759
- create, general product patch, media patch, and verified media patch. Wix media-id-only
760
- lists and non-WordPress sources keep their existing behavior.
761
- 4. If confirmation fails, omit media from the product create/update so other fields still
762
- write and an existing target gallery stays untouched. After the target id is known, call
763
- `buildProductMediaFailureRow({ confirmation, attemptId, sourceStableKey, sourceId,
764
- targetId, runId, phase })` from `lib/product-media-deferral.js` and append its result to
765
- the ordinary attempt journal. The helper owns the `stores-product-media-guard` row shape,
766
- source/target ids, bounded message and three-value `failure.reason` contract. Append
767
- exactly one such row for the product, **after** the product write's own terminal
768
- `markAttempt` row. The guard failure must be the last row appended for that source stable
769
- key in every pass that leaves media deferred; no ordinary product success, skip or other
770
- terminal row may follow it. Never send a partial gallery.
771
- 5. Treat a latest `stores-product-media-guard` failure as an ordering constraint in every
772
- later pass, including full, failed-only and unresolved-only passes. Before appending any
773
- later terminal row for that product key, repeat the source read and confirmation. If it
774
- still fails, finish the ordinary product attempt first and append a fresh guard failure
775
- last. Only a confirmed full-gallery write may be followed by a success row that clears
776
- the guard failure. On `--failed-only`, use the existing crosswalk target id, call
777
- `patchStoresProductMediaVerified` with the identical complete ordered confirmed gallery,
778
- and append an `imported` success row under the same source stable key. Do not create the
779
- product again. Every verified PATCH round re-sends that same full list; read-back results
780
- decide only whether another round is needed.
781
-
782
- After the products phase, load the whole real attempt journal once and pass all its rows to
783
- `withProductPicturesDeferredAction(completionInput, rows)` from
784
- `lib/product-media-deferral.js`. The helper folds the whole journal before filtering and
785
- returns the shared completion input with zero or one `product-pictures-deferred` action next
786
- to its existing actions. A later success row clears the action only after the source re-check
787
- and confirmed full-gallery write above; an older untouched failure stays; a manual destination
788
- edit appends no row and therefore does not clear it. This adds no
789
- recurring model tokens. Runtime adds
790
- `ceil(distinct selected picture ids / 100)` sequential source reads, each bounded by 60
791
- seconds including pacing/retries; surface that estimate in the import plan.
792
- 6. Generate thin project-specific write specs and import orchestration code that pass
793
- those Wix-shaped objects into the shared `rp-target-wix` runtime. Generated code should
794
- describe *what* to write and in what order, not *how* to implement retries, throttling,
795
- checkpointing, or audit logging.
796
- Prefer the selected entity's `preferredWrite`; if it is not `verified-live`, surface an
797
- execution warning before consent. Native mappings must have a known `writerId`, a
798
- direct REST plan that calls `notifyMissingWriter`, or an explicit unsupported/gap
799
- fallback.
800
- 7. Generate or wire the deterministic execution-state preparation step before any import
801
- write. The generated runner should call the shared `execution-state` preparation
802
- contract, or the execution instructions should run
803
- `skills/wix-replatform/scripts/execution-state-prepare.js` before the generated import
804
- entrypoint.
805
- 8. Generate the runnable setup/extraction/import entrypoints (see below) — the artifacts
806
- `rp-execute-setup` and `rp-execute-import` actually run. This is required, not optional.
807
- Then run the **sample-preview gate** (see below) before the execution-plan approval.
808
- 9. Generate `execution/review/import-plan.md` plus
809
- `execution/review/import-plan.freshness.json` using
810
- `skills/wix-replatform/scripts/artifact-freshness.js write ...`. The freshness metadata
811
- must include hashes for `source-schema.json`, `mapping/mapping-plan.json`,
812
- `setup/setup-verification.json`, generated import code, and the target contract ledger
813
- revision. If codegen follows a newly promoted write contract, regenerated code and this
814
- metadata must reflect that promotion.
815
- 10. Document any manual code follow-up still required.
816
- 11. Generate any post-import remediation helpers that are required to reach the accepted
817
- mapping fidelity when the source capture cannot express the relationship inline during
818
- the first create pass.
819
-
820
- ## Post-codegen code-safety review checkpoint
821
-
822
- When either `SAFE_MODE=true` or `DRY_RUN=true` for the active project, codegen must
823
- produce a mandatory review artifact before the execution approval gate:
824
-
825
- - `migrations/<project>/execution/review/code-safety-review.md`
826
-
827
- This review is performed by the agent, not delegated to the user. It verifies the
828
- generated code itself. It must check:
829
-
830
- - no generated file defines a local transient/retry predicate, and none tests an error's
831
- `message` for a status number or a status word; generated retry paths call the shared
832
- `isTransientWixError`
833
- - every generated write path that can carry email/phone data passes `safeModeOptions`
834
- into the shared Wix runtime or direct REST wrapper
835
- - every shared/native writer reached by generated code actually consumes those
836
- `safeModeOptions` and routes the request body through the shared sanitizer rather than
837
- silently ignoring the parameter
838
- - no generated writer path silently bypasses safe mode because of a missing function
839
- parameter or a direct call that skips the shared sanitizer
840
- - dry-run uses the same generated write code path as live import, with Wix calls skipped
841
- only at the shared `wix.send` boundary
842
- - dry-run reporting does not label would-send placeholders as live-created/imported site
843
- objects
844
- - mapping-declared `safeModeReplacements[]` are reflected in the generated write specs and
845
- runner wiring, and any entity that still carries outbound contact data without matching
846
- replacement-path coverage is treated as a review failure rather than left for the user
847
- to reason about manually
848
-
849
- If the review finds a gap, execution approval must remain pending until the generated
850
- code is corrected and the review artifact is regenerated with a passing verdict. The
851
- user's role at this checkpoint is final go/no-go approval after the agent has already
852
- completed the review and surfaced the findings.
853
-
854
- ## Codegen boundary
855
-
856
- This skill owns the **project-specific layer** only.
857
-
858
- It should generate:
859
-
860
- - setup plan renderings and setup runner wiring
861
- - source readers
862
- - transforms
863
- - per-entity write specs
864
- - import ordering and dependency wiring
865
- - project-local config loading
866
- - execution review artifacts
867
- - post-codegen code-safety review artifacts when safe mode or dry-run is enabled
868
-
869
- It must not regenerate for each migration:
870
-
871
- - raw Wix auth/client plumbing
872
- - generic retry loops
873
- - throttling behavior
874
- - audit log shape
875
- - compact execution report shape
876
- - checkpoint store mechanics
877
- - generic bulk-write orchestration
878
-
879
- Those behaviors belong in `rp-target-wix`.
880
-
881
- ## Final handoff expectations
882
-
883
- The generated plan and downstream execution path must make the final state legible to the
884
- user. Treat these as required handoff details, not optional niceties:
885
-
886
- - Surface the **dashboard URL** for the destination site.
887
- - Surface the **editor URL** only when editor work was actually performed or the next
888
- required step is explicitly in the editor.
889
- - Surface the **preview URL** only when public route/site verification is relevant to the
890
- completed work.
891
- - Distinguish clearly between:
892
- - **catalog/data imported**
893
- - **website/homepage built**
894
- - Do not imply that a successful Stores import means the site's homepage or full website
895
- experience exists. A migration can finish with valid catalog/product/cart/checkout
896
- routes while the homepage is still blank or unbuilt.
897
- - If Shopify quick mode relies on public collection feeds such as
898
- `/collections/{handle}/products.json` to recover category membership, bake that into the
899
- generated extractor/importer or emit an explicit remediation helper and call it out in
900
- `execution/review/import-plan.md`.
901
-
902
- ## Runnable setup/extraction/import entrypoints — the artifacts are the execution path (required)
903
-
904
- `rp-execute-setup` and `rp-execute-import` run the migration by **executing these
905
- artifacts**, never by the agent hand-issuing Wix MCP calls. So codegen must emit real,
906
- runnable entrypoints:
907
-
908
- - **`src/setup/run-setup.js`** or equivalent setup entrypoint — reads the machine setup
909
- artifacts and executes them through the shared setup runtime.
910
- - **`src/extract/run-extract.js`** or equivalent reader entrypoint — reads the source APIs and
911
- writes durable extracted files under the project. Extraction is a separate step from
912
- destination writes, so large migrations can be resumed or re-imported without re-reading
913
- the whole source.
914
- - **`src/import/run-import.js`** or equivalent import entrypoint — reads the extracted files,
915
- applies transforms, and writes to Wix in dependency order through the vendored shared
916
- write runtime. It must:
917
- - load project-local config files, then process env, for all expected env-style values
918
- (never hardcoded; fail fast if absent — do not fall back to any agent/MCP auth),
919
- - run the **notification-mute preflight** before the first entity write whenever mute
920
- is in effect (see "Notification-mute preflight" below),
921
- - consume extracted source files from disk rather than materializing the entire source in
922
- memory,
923
- - apply idempotent dedupe keyed by source ID, using either a client-controlled source-id
924
- field on the target or the authoritative local
925
- `state/crosswalk/crosswalk.ndjson` durable `sourceId -> targetId` crosswalk for native Wix
926
- entities with server-assigned IDs, reading existing target state under the rules in
927
- "Reading existing target state" below — an empty read is not an empty site,
928
- - honor `DRY_RUN=true` and `--dry-run` for the safe-validation pass; `--dry-run` takes
929
- precedence over config and enables the shared Wix runtime's dry-run mode.
930
- - `--sample` may be supported as a narrower dry-run-style validation mode, but it must
931
- not replace `--dry-run` as the primary safe-validation control.
932
- - honor deterministic selective resume flags: `--entity <entity>`,
933
- `--source-type <subtype>`, `--missing-only`, `--failed-only`, `--deferred-only`, and
934
- `--unresolved-only`. These are a **requirement on the generated importer**, not an operator
935
- convenience, and they are verified before the first write — see "The per-record ledger" and
936
- "Declaring the entry contract" below.
937
- - support `--describe` and call `assertGeneratedImporterContract` as the **first statement of
938
- `main()`** — see "Declaring the entry contract (required)" below.
939
- `--missing-only`, `--failed-only`, and `--deferred-only` are mutually exclusive. These
940
- flags must drive the same import path as a full run after selecting a stable record
941
- set; do not generate one-off recovery drivers.
942
- - support a `--rerun` catch-up mode built on `lib/import-recovery.js`'s
943
- `selectRerunRecords`, which buckets the refreshed scope into create / update / unchanged /
944
- unknown against the crosswalk baseline. **Do not build this out of `--missing-only` plus
945
- `--failed-only`** — those filters do not compare source versions. Contacts/products
946
- deliberately recheck crosswalk hits, while other entities may exclude them; failed-only
947
- only revisits what was already attempted. On every confirmed write,
948
- persist the version pair (`sourceVersionField` + `sourceVersion`, or the declared
949
- `sourceHash` fallback, plus the returned `targetRevision`) through
950
- `nextCrosswalkBaseline`, which refuses to advance on a failed, conflicted, dry-run or
951
- skipped write. Route an update through the entity's **verified revision-taking update
952
- writer** and, on a revision conflict, through `lib/rerun-conflicts.js` to a user decision —
953
- never through a silent revision refresh, and never through a same-id create/import as a
954
- substitute for an update. `updateSupportFor` enforces that in code rather than leaving it
955
- to this prose: declare `{ writerId, revisionParam }` per entity in the accepted mapping,
956
- and honour its `report_for_review` outcome. The order family is denied there outright, so a
957
- changed order must be reported, never written. Honour the selector's other buckets too —
958
- `needsReconciliation` (an uncertain prior attempt, or an unchanged contact/product
959
- that still needs target identity/value verification), `outOfScope` (pass the accepted scope as `inScope`), and `reportForReview`.
960
- - emit a stable `runId` at process start and include it in every audit event, dry-run
961
- request capture, placeholder crosswalk row, and summary artifact.
962
- - construct its progress logger via `parseProgressArgs`/`createProgressLogger`
963
- (`lib/progress-log.js`, already shipped — vendor it the same way as any other shared
964
- lib) and wrap the per-batch/per-record write loop with that logger's own
965
- `withHeartbeat(fields, fn)` (or the equivalent manual `startHeartbeat(fields)` /
966
- `stopHeartbeat()` pair around a longer-lived loop), carrying live counters as
967
- heartbeat fields: at minimum `recordsAttempted` (a running count), `currentEntity`
968
- (the entity/write-spec currently being processed), and `phase: 'import'`. **Mutate the
969
- same `fields` object's properties as the loop advances rather than passing a fresh
970
- object per record/batch** — `withHeartbeat`/`startHeartbeat` keep the one reference the
971
- ticker reads from, so a fresh object per iteration never reaches the ticker and the
972
- heartbeat goes stale even though the loop is progressing. This covers the write loop, not
973
- just process start, and must not change the progress-log format, event names, or polling
974
- contract in any way.
975
- - emit `execution/live-import-summary.json` and `execution/completion-report.json`
976
- through the shared completion-report runtime, with entity/subtype completeness counters
977
- for extracted, in-scope, attempted, imported, already present by crosswalk, deferred,
978
- failed, and skipped-out-of-scope records.
979
- - stop before dependent phases when required upstream entities fail. For example, do not
980
- create products after product-category failures unless the execution plan explicitly
981
- marks category assignments as non-blocking.
982
- - pass `safeModeOptions` to every relevant writer path, including direct REST fallback
983
- paths and unverified native writers that can carry contact values.
984
- - **The dry-run is the same import code path with Wix calls skipped at the shared Wix
985
- boundary** — not a separate driver. Generated code must construct the same Wix client,
986
- call the same writer helpers, run `SAFE_MODE` sanitization, and pass request metadata
987
- (`phase`, `operation`, `entity`, `sourceId`, `verification`, and when needed
988
- `responseShape`) into `wix.send` so the runtime can capture would-send requests and
989
- return live-compatible dry-run placeholders.
990
-
991
- Extraction format requirements:
992
-
993
- - write extracted data to project-local files, not process memory
994
- - chunk by entity and page/batch so a large source does not become one giant file
995
- - write a manifest that lets the import step discover which entity files exist and in what
996
- order to consume them
997
- - make the extracted files deterministic enough for resume, replay, and debugging
998
- - wrap the extraction loop with the same `lib/progress-log.js` heartbeat helper
999
- (`withHeartbeat`/`startHeartbeat`+`stopHeartbeat`) `run-import.js` uses above, carrying
1000
- `recordsAttempted`, `currentEntity`, and `phase: 'extract'` as heartbeat fields, mutated
1001
- in place as the read loop advances — a paginated source read can run long enough that the
1002
- same silent-heartbeat gap applies to `run-extract.js`, not only to the write phase
1003
-
1004
- ### Notification-mute preflight (required)
1005
-
1006
- When mute is in effect — **always** for `WIX_SITE_STRATEGY=new` (unconditional,
1007
- regardless of `WIX_MUTE_NOTIFICATIONS`), and for existing sites only when
1008
- `WIX_MUTE_NOTIFICATIONS=on` — the generated import script must contain a mandatory
1009
- preflight step, **before the first entity write**, that asserts the site is muted:
1010
-
1011
- - Primary: `getSiteMuteState(wix)` (rp-target-wix, VERIFIED 2026-08-04) returns
1012
- `muted: true`.
1013
- - Fallback (only if the status read is unavailable): an idempotent
1014
- `muteSiteNotifications(wix, { reason })` call — passing the **same
1015
- project-identifying reason as setup** (`RePlatform migration — <project>`), because a
1016
- re-mute overwrites the recorded reason (last caller wins).
1017
-
1018
- If the preflight call fails or reads `muted: false` and the re-mute fails, the script
1019
- **aborts before any write** with a clear error routing back to setup — exit non-zero, no
1020
- degraded mode, no `--skip-mute-preflight`-style flag, no warning-and-continue. For
1021
- new-site projects the preflight is emitted unconditionally and never appears as a
1022
- skippable option; for existing-site projects with `WIX_MUTE_NOTIFICATIONS=off` (the
1023
- default), no preflight is emitted. The preflight logs its re-verification and outcome
1024
- into the run's execution/progress log (keyed by `runId`) — terminal reports read this
1025
- recorded state, never strategy/config inference. In dry-run mode the preflight follows
1026
- the shared dry-run contract like any other Wix call: the intent is captured and the
1027
- state read is skipped (`stateKnown: false`), not asserted as muted. The generated script
1028
- must never call `unmuteSiteNotifications` — unmute is an explicit owner request outside
1029
- the import path. A preflight mute-verification failure is recorded through the standard
1030
- `rp-telemetry` recorder as an `error` event (with `error_code`) like any other run
1031
- event — no new telemetry surface.
1032
-
1033
- ### The per-record ledger (required)
1034
-
1035
- `state/attempts/write-attempts.ndjson` is **the** per-record log of what a generated importer
1036
- wrote to a merchant's store. A generated importer must **not** open a second, private results
1037
- log of its own, and must not write per-entity failure files beside it: failures live in the
1038
- journal with a terminal status, and the operator-facing views are the derived queue and the
1039
- recovery record described below.
1040
-
1041
- **It is only ever appended to.** `appendAttempt` (which appends through `fs.appendFile`) is the
1042
- only writer. No flag, no environment variable and no mode may open it in a way that can shorten
1043
- it, and rotation, compaction and in-place folding are not permitted. This is not a style rule:
1044
- an importer once opened its results log in overwrite mode whenever a record limit was set, and
1045
- the next thirty-record sampling pass replaced several hundred rows with thirty.
1046
-
1047
- **A validation pass writes somewhere else entirely.** `--dry-run` / `--sample` write to
1048
- `state/sample/attempts/write-attempts.ndjson` and `state/sample/…` generally; the real journal
1049
- is not opened at all. Pass `{ sample: true }` to `appendAttempt` / `markAttempt` /
1050
- `loadAttemptJournal`.
1051
-
1052
- ⚠️ **A limited pass that writes for real is not a sample.** The discriminator is whether live
1053
- writes occur, never whether a record limit is set — keying the sample path off the limit flag
1054
- reproduces the original defect with the file moved. A limited live pass writes to the **real**
1055
- journal and carries `selection: { offset, limit, filters }` so its scope is legible.
1056
-
1057
- **Every row says which pass wrote it.** `validateAttemptRow` requires `attemptId`,
1058
- `sourceStableKey`, `writeSpecId`, `operation`, `targetEntityType`, `status`, `runId`, `phase`
1059
- (and whatever that validator requires next) — `runId` is the id the importer mints at process
1060
- start and already stamps on audit events, request captures and dry-run crosswalk rows, and
1061
- `phase` uses the same vocabulary as `wix.send` request metadata, not a second one. Build the
1062
- row with the validator, not from this list.
1063
-
1064
- With both present, per-pass accounting falls out of the file: **group by `runId`** for a pass's
1065
- counts, **fold by `sourceStableKey`** (latest row wins, `foldAttemptsBySourceKey`) for record
1066
- counts. Without them every per-row count silently reads as a per-record count — a run
1067
- can hold several rows per order, and readers will mistake rows for records.
1068
-
1069
- A follow-on row appended by `markAttempt` carries the `runId` and `phase` of the run appending
1070
- it, which may differ from the row it supersedes. That is deliberate: it is how a later pass's
1071
- confirmation of an earlier pass's write stays legible.
1072
-
1073
- **A failure keeps its identifying facts as fields.** When `status` is `failed_retryable`,
1074
- `failed_terminal` or `succeeded_unverified`, the row carries a `failure` object:
1075
-
1076
- | Field | Shape |
1077
- | --- | --- |
1078
- | `failure.sourceId` | string — redundant with `sourceStableKey` by design, so the row reads on its own |
1079
- | `failure.targetEntityId` | string \| null — the target-side id the request named |
1080
- | `failure.operation` | string |
1081
- | `failure.httpStatus` | integer \| null |
1082
- | `failure.errorCode` | string \| null — the server's own code (`applicationErrorCode`), never parsed out of prose |
1083
- | `failure.reason` | optional `no-source-id` · `no-source-record` · `source-unreadable` for `stores-product-media-guard` |
1084
- | `failure.scopeGroupName` | string \| null — the named grouping the server rejected |
1085
- | `failure.outcome` | `failed_terminal` · `failed_retryable` · `unresolved` |
1086
- | `failure.message` | string — free text, **may** be truncated |
1087
-
1088
- ⚠️ **No identifier may exist only inside `failure.message`.** Extract the identifying fields
1089
- from the error **before** shortening the message, never after. A coupon importer kept the first
1090
- three hundred characters of each error and in every one of eleven scope failures the identifier
1091
- of the thing that failed sat past the cut; recovering it now means running against a live store
1092
- again. The validator rejects a row carrying a GUID that appears in no other field.
1093
-
1094
- `appendAttempt` / `markAttempt` redact-and-retry once around exactly that rejection — they strip
1095
- a GUID-shaped token from `failure.message` (never a structured field) and re-validate, so a genuine
1096
- Wix error whose message text happens to echo an id no caller extracted does not crash the run. That
1097
- is a safety net for the identifier the code failed to anticipate, not a substitute for populating
1098
- `failure.targetEntityId` correctly: **when an entity's target-side id is deterministic and
1099
- derivable *before* a write is even attempted** — a stable hash/derivation of the source id, the way
1100
- `order-history.js`'s `deriveImportedOrderId` works for orders — populate `failure.targetEntityId`
1101
- with that derived id on **every** attempt row, success or failure, rather than defaulting it to
1102
- `null` on failure. A hardcoded `null` here is exactly what turns a target-side id embedded in an
1103
- error message into an unstructured identifier, which is what trips the guard above in the first
1104
- place.
1105
-
1106
- **An uncertain write is not a failure.** `succeeded_unverified`, and a `started` row that never
1107
- got a terminal follow-on, are **unresolved**: the next action is to **read the target back**,
1108
- never to re-send, because re-sending an uncertain write is how a recovery pass creates a
1109
- duplicate on a live store. They select under `--unresolved-only`, count as `unresolved` in the
1110
- recovery record, and are rebuilt into `execution/unresolved-writes.ndjson`
1111
- (`rebuildUnresolvedWrites`) — a projection of the journal, so deleting it loses nothing and it
1112
- is never a source of truth. A `deferred` record, including a source-data-rule skip, is **not**
1113
- unresolved and never reaches that queue. Never sum unresolved into `failed`: a recovery record
1114
- that does reconciles arithmetically and is wrong.
1115
-
1116
- ### Declaring the entry contract (required)
1117
-
1118
- The flags above were asked for by this document for a long time and verified nowhere, so an
1119
- importer shipped that parsed no command-line arguments at all and still passed the import gate.
1120
- A boundary nothing crosses and nothing checks is not a boundary, so the generated importer now
1121
- declares its own capabilities and both sides check the declaration.
1122
-
1123
- **`--describe`** prints exactly one JSON object to stdout and exits 0 without touching the
1124
- target:
1125
-
1126
- ```json
1127
- {
1128
- "contractVersion": 1,
1129
- "runIdSource": "process-start",
1130
- "selectionModes": ["entity", "source-type", "missing-only", "failed-only",
1131
- "deferred-only", "unresolved-only", "rerun"],
1132
- "ledger": {
1133
- "attempts": "state/attempts/write-attempts.ndjson",
1134
- "sampleAttempts": "state/sample/attempts/write-attempts.ndjson",
1135
- "unresolved": "execution/unresolved-writes.ndjson",
1136
- "recoveryLog": "execution/recovery-log.json",
1137
- "appendOnly": true
1138
- }
1139
- }
1140
- ```
1141
-
1142
- **The importer checks itself.** `run-import.js` calls
1143
- `assertGeneratedImporterContract(descriptor)` from `lib/import-recovery.js` as the **first
1144
- statement of `main()`** — unconditionally, before argument dispatch and before the
1145
- notification-mute preflight. If a required mode is missing or a ledger path is wrong it throws
1146
- naming every problem at once and the importer refuses to run itself, exactly as the mute
1147
- preflight aborts before any write: exit non-zero, no degraded mode, no skip flag.
1148
-
1149
- `rp-execute-import` step 2d runs `import-contract-verify.js` against the same descriptor from
1150
- the outside before the first write. The two are not redundant: the self-check is what an
1151
- operator cannot skip, and the external gate is what catches an importer that never emitted the
1152
- self-check at all — such an importer also cannot answer `--describe`.
1153
-
1154
- ⚠️ The vendoring escape hatch ("use `import-recovery.js` where available, or vendor equivalent
1155
- helpers") survives for the helper **implementations**. It does **not** survive for the entry
1156
- contract: vendoring is not an excuse for a missing mode.
1157
-
1158
- ### Use the target's BULK write path (required)
1159
-
1160
- **A generated importer must write through the target's bulk endpoint whenever one exists.**
1161
- Per-record creates are acceptable only when the target has no bulk equivalent, or for a
1162
- deliberate single-record contract probe. This is not an optimization to add later: at 1000
1163
- products, per-record creates are 1000 round trips where bulk is ~11, and the latency
1164
- difference is the difference between a minute and half an hour.
1165
-
1166
- Derive the batch shape from the endpoint's **own limits, all of them at once**. Bulk
1167
- endpoints routinely cap several dimensions simultaneously and exceeding **any one** rejects
1168
- the entire request — Wix bulk product create caps products (100), variants (1000), options
1169
- (100), modifiers (100) and infoSections (100) per request, so with 2 options per product the
1170
- options cap binds at 50 products, not 100. Batch with `ndjson.readBatchesByLimits` and the
1171
- limits/cost helpers the target adapter exports (`BULK_PRODUCT_LIMITS`,
1172
- `storesProductBulkCost`); never batch on record count alone.
1173
-
1174
- Three properties of bulk responses that generated code must handle explicitly, because each
1175
- one silently corrupts a report if ignored:
1176
-
1177
- - **Bulk is not atomic.** A `200` can contain per-item failures. Walk every
1178
- `results[]` entry; never infer success from the HTTP status.
1179
- - **Correlate by the response's own index field** (`itemMetadata.originalIndex` for Wix), not
1180
- by response position, and verify that every input is accounted for. A mis-correlated
1181
- result crosswalks the wrong target id onto a source record.
1182
- - **Count the "undetailed failures" bucket.** Servers drop failure detail past a threshold;
1183
- those are still failures and must appear in the completion report.
1184
-
1185
- Dedupe **before** building the batch — a skipped record must never reach the API — and
1186
- retain each successful item’s source identity. The verified wrappers durably checkpoint
1187
- these outcomes in batches before publishing verified crosswalk references; do not rewrite
1188
- the full crosswalk and its indexes for each item.
1189
-
1190
- ### Contact and product identity verification
1191
-
1192
- Generate standard contact/product imports through `contact-import.js` and
1193
- `product-import.js`, not a loop that publishes `bulk.succeeded` directly. Vendor their
1194
- `bulk-results.js`, `write-verification.js`, and root `lib/write-verification-state.js`
1195
- dependencies with the existing local-state/runtime files, preserving the relative layout.
1196
- The wrappers own prewrite reconciliation, source-index correlation, candidate persistence,
1197
- read-back, and publication of verified mappings. Raw writers remain request primitives.
1198
-
1199
- Select `sourceKey` and all expected fields from the source mapping BEFORE sending a write.
1200
- Contact rows carry `{sourceKey, sourceId, sourceSystem, contact, safeModeOptions,
1201
- updateMember}`. Supply per-record safe-mode options so a batch cannot acquire one shared
1202
- replacement identity. Phone-only identities and conflicting source/target identifiers
1203
- block and report; there is no automatic merge or V4 fallback. Contact custom-field setup
1204
- must already be resolved; the shared DES setup helper still supports orders only.
1205
-
1206
- Product rows carry `{sourceKey, product, expected, marker, variantExpectations}`.
1207
- `expected` includes every mapped readable product field; exclude write-only inventory
1208
- fields and verify them through the inventory pass. `marker: {path, value}` is source-derived
1209
- (e.g. a unique mapped slug), not copied from a returned product. Each variant expectation
1210
- carries `{sourceKey, expected, inventory}` with an unambiguous source-fixed SKU/choice
1211
- projection. Include submitted variant choices in that projection; an unverified choice
1212
- shape must remain a gap, even when SKU and price agree. The wrapper resolves exact target
1213
- variant IDs, retaining the correspondence
1214
- in each verified receipt. A missing or ambiguous match remains a gap, never a positional zip.
1215
-
1216
- Use this caller shape; `toolkitRoot` is the installed or vendored wix-replatform root,
1217
- `dryRun` is the shared runtime's resolved dry-run flag, and `runId` is the shared runtime's run
1218
- id for the current pass — it stamps every execution-log entry, so a later recovery pass's
1219
- entries stay distinguishable from this one's. The post-execution handoff is not generated here:
1220
- that is the completion step's job, after the last write of the whole run.
1221
-
1222
- ```js
1223
- // BEGIN verified-contact-import
1224
- const { importContacts } = require(require('node:path').join(toolkitRoot, 'resources/rp-target-wix/lib/contact-import.js'));
1225
- const { createCompletionReport, writeCompletionReport, attachAttemptReasons } = require(require('node:path').join(toolkitRoot, 'lib/completion-report.js'));
1226
- const { loadAttemptJournal } = require(require('node:path').join(toolkitRoot, 'lib/local-state.js'));
1227
- const { writeLiveImportSummary } = require(require('node:path').join(toolkitRoot, 'lib/import-recovery.js'));
1228
- const contactVerification = await importContacts(wix, {
1229
- projectDir, siteId, rows: contactRows, dryRun,
1230
- });
1231
- // Opt-in: flush BEFORE building the completion input, not after, so the run's
1232
- // own `coverageCheck` (computed by this very flush) is available to pass through below. This is
1233
- // the only correctly-wired caller shape this repo tests -- see "Coverage check" below.
1234
- let coverageCheck = null;
1235
- if (!dryRun) {
1236
- // Flush the run's OWN held counters after every batch, not once at the end: an importer that
1237
- // holds them loses them to any interruption, and a long run redirected to a temp file loses
1238
- // them to the shell. If a mid-run fix must refresh a count from the real attempt ledger, first
1239
- // select the report's scope -- target entity type, run/phase when run-scoped -- then fold ALL
1240
- // rows in that scope with `foldAttemptsBySourceKey` (see "The per-record ledger" above), and
1241
- // only then classify the latest rows by counted status. The fold keeps the latest row per
1242
- // sourceStableKey of whatever it is given; it does not narrow scope. Never filter by status
1243
- // before folding: a record whose later row superseded a `succeeded` one would count again.
1244
- // Never count raw rows: retried batches and recovery re-entries leave several rows per record,
1245
- // which is how a run's counts inflate well past the number of records. Dry-run rows
1246
- // live in the separate state/sample/ journal; never mix that into a live report.
1247
- ({ coverageCheck } = await writeLiveImportSummary(projectDir, {
1248
- runId, imported: { contacts: contactVerification.verified },
1249
- }, { runId, phase: 'import', detail: 'contact batch flushed', entityTypes: { contacts: 'contact' } }));
1250
- }
1251
- // `sourceKeys` is required on every row: it is the only way a deferred or failed
1252
- // count can later be joined back to the journal row that names its reason.
1253
- const rawEntityCompleteness = [{
1254
- entity: 'contacts', verificationKind: 'contact', sourceKeys: contactRows.map(row => row.sourceKey),
1255
- inScope: contactRows.length, imported: contactVerification.verified,
1256
- deferred: contactRows.length - contactVerification.verified,
1257
- }];
1258
- // Load the journal ONCE per report and join it in before building the report
1259
- // -- never once per entity, and never read a second time by a later step of the same report.
1260
- const journal = dryRun ? null : await loadAttemptJournal(projectDir);
1261
- const completion = dryRun ? { status: 'simulated', verification: { contacts: contactVerification } } : createCompletionReport({
1262
- siteId, runId, lastWriteAt: new Date().toISOString(),
1263
- entityCompleteness: attachAttemptReasons(rawEntityCompleteness, journal.rows, { runIds: [runId] }),
1264
- verification: { contacts: contactVerification },
1265
- // This run called `writeLiveImportSummary` with a `runId` above, so it has opted into the
1266
- // coverage check -- pass the run-matched block straight through so a wrong or
1267
- // unconfirmed count escalates the completion status instead of looking clean by omission.
1268
- coverageCheck,
1269
- coverageCheckExpected: true,
1270
- });
1271
- if (!dryRun) {
1272
- // Persist the final outcome and render the user-facing summary from the same object. One
1273
- // shared writer: there is nothing here for a generated importer to reinvent, which is why
1274
- // none of them ever did.
1275
- await writeCompletionReport(projectDir, completion, { runId });
1276
- }
1277
- // END verified-contact-import
1278
- ```
1279
-
1280
- **Coverage check.** `writeLiveImportSummary` compares its held counters against the
1281
- live attempt journal for the same run (the `runId` passed in `options`, or `summary.runId`) and
1282
- adds the comparison as `coverageCheck` on the written summary and on this call's return value — it
1283
- never changes the held counters. This flush deliberately names only `imported`; the `deferred`
1284
- figure above is computed in the same pass for `entityCompleteness` but never carried into this
1285
- `writeLiveImportSummary` call, and that is fine — an ordinary bucket (`imported`, `deferred`,
1286
- `failed`, `attempted`) a flush does not name is simply not compared for that entity, never treated
1287
- as a mismatched `0`. `unexpectedSkipped` is the one exception: its absence defaults to a held `0`,
1288
- so always name it explicitly, even as `0`, once a flush's journal rows can produce a
1289
- `skipped_unexpected` row for that entity. The flush above passes
1290
- `options.entityTypes: { contacts: 'contact' }` because `contact-import.js`'s own verified writes
1291
- publish to the crosswalk under `targetEntityType: 'contact'` (singular) while this summary's key is
1292
- `contacts` (plural, matching the wrapper's own naming) — a generated importer's ledger-writing code
1293
- (the per-record `appendAttempt` calls the "per-record ledger" section above requires, alongside
1294
- this wrapper call, not shown again here) must stamp the same `targetEntityType` its `entityTypes`
1295
- option names, or pass a matching key directly. Both directions of the match are checked, so a
1296
- target type with real journal rows and no summary key naming it (directly or via `entityTypes`) is
1297
- still reported, never silently agreed with. Flush after the batch's journal rows are appended, not
1298
- before: this writer reads what has already landed in `state/attempts/write-attempts.ndjson`, and a
1299
- flush that races ahead of its own journal rows under-reports what the journal actually has so far.
1300
- A disagreement is reported whatever its cause, so this genuinely
1301
- produces `agrees: false` / `coverage_mismatch` for that flush, not merely a stale snapshot that
1302
- quietly resolves itself — order the write after the append so this does not happen.
1303
-
1304
- The example above reads the run's own `coverageCheck` straight off this flush's return value
1305
- because this is the run's only flush; a multi-batch importer instead reads it back from
1306
- `execution/live-import-summary.json` at the end of the run and matches it to `input.runId`, never
1307
- assuming "the last block in the file" is this run's, since that file is shared by every run.
1308
- Either way, pass `coverageCheckExpected: true` into `createCompletionReport` whenever this run
1309
- called `writeLiveImportSummary` with a `runId` at least once (`rp-execute-import/SKILL.md`'s
1310
- end-of-run instruction owns this for the generated script as a whole) — omitting it is what keeps
1311
- an existing, not-yet-wired importer's behavior unchanged.
1312
-
1313
- Both wrappers create absent records and verify existing ones; they never update an existing
1314
- record whose values differ. Route such differences to the declared revision-protected
1315
- update/conflict-review flow, then verify the result. Pass optional `sourceVersion`,
1316
- `sourceVersionField`, `sourceHash`, and `sourceHashDefinition` with each row to establish
1317
- its baseline after a confirmed create with a returned revision. Read-only reconciliation
1318
- preserves the saved baseline, including across verification failure and resume; it never
1319
- advances a source version or refreshes the saved revision. Run unchanged contact/product
1320
- rows from `selectRerunRecords().needsReconciliation` through these same wrappers.
1321
-
1322
- Declare `verificationKind` in the accepted mapping and carry it into completion and
1323
- selection options independently of source table names: `contact` or `product` for these
1324
- comparators, `none` for other entities with their own verification flow. Unknown names
1325
- without a declaration require verification; they cannot silently opt out. Reject undefined
1326
- projection fields before any API call, naming the field to omit or replace with a JSON value.
1327
-
1328
- Call `importProducts` with the equivalent product rows. Add its verification report and
1329
- source-key set to the same completion input, then pass its verified variant correspondence
1330
- to `reconcileInventory` below. Every intended variant stays in the inventory denominator,
1331
- including products that failed identity checks. Do not send stock or category membership
1332
- writes for an unverified product mapping. Keep inventory outcomes separate from product
1333
- identity outcomes.
1334
-
1335
- Run the SAME wrappers on retries, single-record batches, existing crosswalks and
1336
- `--missing-only`; selection is not evidence that a target exists. An uncertain write
1337
- remains pending until target reconciliation resolves it. Do not hand-code per-record
1338
- fallbacks that turn errors or missing indexes into create permission. When using dry-run,
1339
- pass `dryRun: true` and report simulated outcomes, not verified live completion.
1340
-
1341
- The inspected contact and products-with-inventory endpoints require unique request-local
1342
- `originalIndex`. Their endpoint adapters enforce this; Blog bulk is not covered by that
1343
- contract. `undetailedFailures` counts failures but cannot identify their input rows, which
1344
- stay unresolved. Counts, HTTP 200, and old presence-only receipts do not establish identity.
1345
-
1346
- ### Product SEO: proven per-field overrides only
1347
-
1348
- For a source with an SEO evidence contract (Yoast on WooCommerce), put the per-field
1349
- `seoEvidence` on the canonical product (see rp-source-wordpress "Yoast product SEO evidence")
1350
- and never fill `seoTitle`/`seoDescription` from rendered output. Then, per product:
1351
-
1352
- 1. `plan = planProductSeo({ productId, seoEvidence, decisions })` from
1353
- `rp-target-wix/lib/product-seo.js`, with `decisions` read from `orchestration/decisions.json`.
1354
- 2. `buildProduct(canonical, { seoPlan: plan })`; pass the row to `importProducts` with
1355
- `seo: plan` and **without** `seoData` in `expected`. The SEO stage verifies it per field
1356
- (product read-back plus `resolvedTags`), reconciles a create that dropped `seoData` with a
1357
- revision-protected PATCH, applies reruns as partial updates, and returns `report.seo`.
1358
- The stage already bounds its own concurrency, pacing and settle waits; do not wrap it in
1359
- a per-product loop or add sleeps around it.
1360
- 3. Include `plan.projection` (never the receipt) in the product's `sourceHash`.
1361
- 4. Add `report.seo.stageResiduals` to the completion input's `stageResiduals`.
1362
- 5. If `buildSeoDispositionQuestions(plans)` returns questions, ask the owner once per question
1363
- and record the answer with `recordSeoDisposition` (source `user`). Never answer it for them,
1364
- in any automation mode, including one-click.
1365
-
1366
- Never write product SEO through Item SEO Tags bulk set (`bulkSetItemSeoTags` refuses
1367
- `STORES_PRODUCT`), and never write canonical, robots, schema, social tags or an SEO pattern.
1368
-
1369
- ### Product inventory: inline create, then reconcile and verify
1370
-
1371
- For default-location inventory, the shared reconciler also reads Catalog products and verifies each exact variant's availability. Inventory success alone cannot complete the report; disagreement stays unverified after bounded read retries. Never toggle stock to force Catalog propagation. Non-default locations record this Catalog check as not applicable.
1372
-
1373
- Vendor `rp-target-wix/lib/stores-inventory.js` with `wix-build.js` and `wix-writers.js`.
1374
- The builder consumes each canonical variant's `inventoryTracked`, `inventoryQuantity`, and
1375
- `inStock`; it emits `inventoryItem` on variants when stock is known. Use the existing bulk
1376
- products-with-inventory writer so initial stock costs no separate write. Send `quantity`
1377
- OR `inStock`, never both; `trackQuantity` is read-only. Explicit non-default stock belongs
1378
- in the separate inventory pass, not in default-location inline fields.
1379
-
1380
- Collect inventory identities after each product batch, including products already found in
1381
- the crosswalk. Generate a resumable inventory pass through `reconcileInventory`. Resolve actual product and variant
1382
- IDs from returned/read-back entities and source-option correspondence; never zip unordered
1383
- results or variants by position. Persist that correspondence. Keep source gaps and failed
1384
- product-to-variant mappings in `inventoryGaps` so every intended source variant is counted.
1385
-
1386
- ```js
1387
- const { reconcileInventory, persistInventoryReport } = require('../resources/rp-target-wix/lib/stores-inventory.js');
1388
- // resolvedInventoryRows: [{productId, variantId, inventory: {inStock} OR {quantity}}]
1389
- // sourceVariantCount is counted BEFORE filtering gaps or failed product mappings.
1390
- const inventoryReport = await reconcileInventory(wix, resolvedInventoryRows, {
1391
- expectedCount: sourceVariantCount,
1392
- gaps: inventoryGaps,
1393
- // locationId may be supplied from verified setup; otherwise resolve the default.
1394
- });
1395
- await persistInventoryReport(inventoryReportPath, inventoryReport);
1396
- // The generated completion report consumes inventoryReport.status and counts directly.
1397
- ```
1398
-
1399
- Set `inventoryReportPath` to the project's `state/inventory-report.json`. Supply the full
1400
- intended variant set and gaps once per inventory pass; the helper handles bounded API batches
1401
- internally. Persist lightweight resolved identities during product import so interrupted runs
1402
- can reconstruct this set without recreating products. Do not replace the report with only the
1403
- last product batch. The completion report consumes `status`, `counts`, and `complete` directly;
1404
- only `complete:true` can mark the inventory checkpoint complete.
1405
-
1406
- Product and inventory checkpoints are independent. Product success followed by inventory
1407
- failure preserves the product crosswalk; resume queries existing inventory and repairs it.
1408
- A successful bulk product receipt or `inventoryResults` count never substitutes for per-variant
1409
- read-back. Report unresolved inventory as partial, while allowing independent records to
1410
- continue. The setup inventory probe must pass before stock writes. Existing notification
1411
- protection and dry-run boundaries still apply; a dry-run does not produce a live inventory
1412
- verification receipt.
1413
-
1414
- ### Product category attachment: attach, then verify (required)
1415
-
1416
- **Never call `bulkAddItemToCategories` inline and treat its resolved promise as proof the
1417
- product is in its categories.** It is an ADD against a bulk endpoint that takes exactly one
1418
- object argument, `{ productId, categoryIds }` -- not positional arguments. A generated
1419
- importer can call it as `bulkAddItemToCategories(wix, row.categoryIds, [r.targetId])`: three
1420
- positional arguments to a two-argument function. JS silently drops the third, so the function
1421
- received the bare `categoryIds` array as its whole `payload`; destructuring `{ productId,
1422
- categoryIds }` off an array yields `undefined` for both fields, on every call, forever. The
1423
- wrapping `catch {}` swallowed every resulting failure, and nothing downstream tracked this step
1424
- at all, so every product silently kept zero of its imported categories with a
1425
- clean completion report. Vendor `rp-target-wix/lib/stores-category-attach.js` with
1426
- `wix-writers.js` and route every category link through it instead.
1427
-
1428
- **This example assumes composition.** A generated importer runs this stage alongside
1429
- others -- order-history, member-pass -- that also extend `completionInput`, and `AGENTS.md`'s
1430
- single-write rule still holds: only ONE block in the whole generated file, whichever genuinely
1431
- writes last, ever calls `createCompletionReport`/`writeCompletionReport`. `writeCompletionReport`
1432
- always overwrites the one `execution/completion-report.json` file (`writeFileAtomic`, no merge),
1433
- so a second call anywhere in the same run silently discards whatever the first call alone
1434
- recorded. This section therefore never builds or writes a completion report itself -- it only
1435
- extends the SAME shared `completionInput` the order-history/member-pass sections already extend,
1436
- exactly the way member-pass does above
1437
- (`completionInput = MC.mergeMemberCompletion(completionInput, ...)`): a non-terminal stage folds
1438
- its residuals into the held accumulator and stops there.
1439
-
1440
- ```js
1441
- const { reconcileCategoryAttachment, persistCategoryAttachReport, collectCategoryAttachStageResiduals } =
1442
- require('../resources/rp-target-wix/lib/stores-category-attach.js');
1443
- // categoryAttachRows: [{ productId, categoryIds, sourceKey }] -- one row per product that has at
1444
- // least one category to attach. A product with none is simply never given a row.
1445
- const categoryAttachReport = await reconcileCategoryAttachment(wix, categoryAttachRows);
1446
- await persistCategoryAttachReport(categoryAttachReportPath, categoryAttachReport);
1447
- // Extend the SAME shared completionInput every other stage in this file extends -- never build
1448
- // or write a completion report here. Whichever stage genuinely runs last in this generated
1449
- // project (order-history-completion, when this project also imports orders; this stage's own
1450
- // trailing completion block, only on a project where category-attach really is the terminal
1451
- // write of the whole run) owns the single createCompletionReport/writeCompletionReport call and
1452
- // reads this same completionInput. Never stop at persisting the sidecar report alone: an
1453
- // unfolded categoryAttachReport is invisible to completeness accounting, which is the exact
1454
- // silent-failure shape this file exists to close.
1455
- completionInput = {
1456
- ...completionInput,
1457
- stageResiduals: [
1458
- ...(completionInput.stageResiduals || []).filter((row) => !(row.entity === 'product' && row.stage === 'category-attach')),
1459
- ...collectCategoryAttachStageResiduals(categoryAttachReport),
1460
- ],
1461
- };
1462
- ```
1463
-
1464
- `reconcileCategoryAttachment` calls the writer with the exact shape it declares --
1465
- `bulkAddItemToCategories(wix, { productId, categoryIds })` -- for every row, never swallows a
1466
- write error (each is caught per-row and recorded with its message), and then reads every written
1467
- product back before marking it attached. `bulkAddItemToCategories` is an ADD, not a replace: the
1468
- read-back only ever confirms every expected categoryId is present in the product's actual set,
1469
- never exact set equality, so a product's other, unrelated category memberships are never
1470
- disturbed or required. A row can resolve to `category-attach-written` (confirmed by read-back),
1471
- `category-attach-reconciled-existing` (already carried every expected id, no write needed),
1472
- `category-attach-unverified` (write attempted, read-back never confirmed within the retry
1473
- budget -- not the same as failure, and must never be reported as one), or
1474
- `category-attach-failed` (the write call itself threw). `collectCategoryAttachStageResiduals`
1475
- must be called and its output folded into the shared `completionInput.stageResiduals` as shown
1476
- above, on every run that calls `reconcileCategoryAttachment` -- persisting `categoryAttachReport`
1477
- alone and stopping there reproduces the exact silent-failure shape this file exists to stop: a
1478
- `category-attach-failed` or `category-attach-unverified` row that never reaches the final
1479
- completion report, so the import reports `complete` while categories are missing. On a project
1480
- where category-attach really is the only/last stage, extend `completionInput` here exactly as
1481
- shown, then run the ordinary completion block (as the verified-contact-import or
1482
- order-history-completion examples do) once, after this point -- do not special-case this
1483
- section into writing early just because, on that particular project, it happens to run last.
1484
-
1485
- Track source-side gaps too, the same way `stores-inventory.js` already asks its own caller to:
1486
- "Keep source gaps and failed product-to-variant mappings in `inventoryGaps` so every intended
1487
- source variant is counted" is a caller responsibility, not something the shared helper derives on
1488
- its own, because it can only account for rows it is actually given. The code that resolves source
1489
- categories into `categoryAttachRows` has the same obligation for category attachment: track any
1490
- source category that fails to resolve to a target category id (a `categoryAttachGaps`-style list)
1491
- so every intended source-to-category link is counted, not only the ones that made it into a row.
1492
-
1493
- ### Blog post author attribution (required)
1494
-
1495
- **Never hardcode a single `WIX_BLOG_AUTHOR_MEMBER_ID` and attribute every imported post to the
1496
- site owner.** `draftPost.memberId` is required by the Blog API and Wix Blog has no "unauthored"
1497
- option, but that does not make owner-attribution an acceptable default for every post. A
1498
- generated importer that did exactly this carried the same owner memberId on every imported post,
1499
- while the WordPress source named distinct authors (`_embedded.author`) that the
1500
- transform/import layers silently discarded. The fix is not "use the
1501
- owner" — it is to resolve a real Wix member per distinct source author, per Wix's own "How to
1502
- Create Blog Posts" skill doc (dev.wix.com/docs/api-reference/business-solutions/blog/skills/
1503
- how-to-create-blog-posts, "Part 0: Get an Author/Member ID"): when there is no existing member to
1504
- reuse for an author, **create one** — its `profile.nickname` (derived from `contact.firstName`)
1505
- is the name the blog displays, so pass the real author's name when the source names one. Create
1506
- Member sends no email and fires no signup-automation trigger (documented at `wix-writers.js`'s
1507
- members section) — this is silent, not a notification risk.
1508
-
1509
- **No source-reader change is needed.** WordPress's `/wp/v2/posts?_embed` (already how this
1510
- project's source reader populates `data/source-extract/post.ndjson`, per
1511
- `rp-source-wordpress/SKILL.md`'s "Embedded relations" guidance) inlines `_embedded.author[0].id`
1512
- and `_embedded.author[0].name` on every post whose author resolved. The bug was purely that the
1513
- blog transform discarded these fields rather than carrying them through to the import stage.
1514
-
1515
- ```js
1516
- const { resolveBlogAuthorMemberId } =
1517
- require('../resources/rp-target-wix/lib/blog-author-attribution.js');
1518
- const memberCrosswalk = require('./src/lib/member-crosswalk.js').createProjectMemberCrosswalk({
1519
- projectDir: ROOT, sourceEntityType: 'blog-author',
1520
- });
1521
- // post.sourceAuthorId / post.sourceAuthorName: carried through from the post transform's
1522
- // `_embedded.author[0].id` / `.name` (undefined/null when the source gave no author at all).
1523
- const authorResolution = await resolveBlogAuthorMemberId(wix, {
1524
- sourceAuthorId: post.sourceAuthorId,
1525
- sourceAuthorName: post.sourceAuthorName,
1526
- crosswalk: memberCrosswalk,
1527
- siteId,
1528
- ownerMemberId, // the existing single-owner fallback, resolved once via listMembers as today
1529
- });
1530
- if (authorResolution.created) {
1531
- // Caller records the crosswalk row -- resolveBlogAuthorMemberId only resolves/creates, it never
1532
- // persists, mirroring the member-pass's own ensure/persistCrosswalk split above.
1533
- await memberCrosswalk.put(post.sourceAuthorId, {
1534
- memberId: authorResolution.memberId, loginEmail: authorResolution.loginEmail,
1535
- });
1536
- }
1537
- draftPost.memberId = authorResolution.memberId;
1538
- ```
1539
-
1540
- - **One member per distinct source author, never per post.** `resolveBlogAuthorMemberId` looks
1541
- up the crosswalk by `sourceAuthorId` first and reuses whatever member id it already holds —
1542
- the same WordPress author across many posts, or a resumed/re-run import, must never create a
1543
- second member for the author it already provisioned. Never call `createMember` directly for a
1544
- blog author from generated code.
1545
- - **Fall back to single-owner attribution only when the post has no source author at all.** Not
1546
- every migration source populates author info on every record; pass `ownerMemberId` (the
1547
- existing fallback, resolved the same way it is today) and let the helper return
1548
- `blog-author-owner-fallback` rather than making author resolution a hard requirement that
1549
- blocks import when the data simply is not there.
1550
- - **The placeholder email is deterministic, non-deliverable, and collision-free, never the real
1551
- author's email.** `blog-author-{hash(sourceAuthorId)}@{hash(siteId)}.invalid` — an `.invalid`
1552
- TLD (RFC 2606) guarantees it never resolves or delivers. Each component is a hash of the RAW
1553
- value (not a human-readable sanitization of it): two different raw `sourceAuthorId` values can
1554
- never collide onto the same token, which a lossy sanitization (stripping/collapsing characters)
1555
- could not guarantee (`'1-2'` and `'1@2'` would collide under a sanitized-token scheme). The token is opaque and carries no part of the real name; the
1556
- real name goes ONLY into `profile.nickname`/`contact.firstName`, which is the whole point of
1557
- the fix.
1558
- - **Respects the documented Create Member rate-limit floor** (`MEMBER_WRITE_MIN_INTERVAL_MS`,
1559
- the same constant `ensureMember`'s own pacer uses) — do not add a second hand-rolled wait loop
1560
- around the generated call site.
1561
-
1562
- ### Record streams are NDJSON, single documents are JSON (required)
1563
-
1564
- **Every file that holds a stream of records must be newline-delimited JSON (`.ndjson`), one
1565
- record per line — never a `{ "records": [ … ] }` array.** Vendor
1566
- `rp-target-wix/lib/ndjson.js` into the project (like `wix-writers.js`) and use it; do not
1567
- hand-roll line splitting, which gets chunk boundaries and CRLF wrong.
1568
-
1569
- This applies to:
1570
-
1571
- - `data/source-extract/<entity>.ndjson` — the extractor's output
1572
- - the `sourceId -> targetId` crosswalk
1573
- - audit logs (already NDJSON)
1574
-
1575
- It exists because every downstream stage does the same three things with these files, and an
1576
- array is the wrong shape for all of them:
1577
-
1578
- - **scan** — `countRecords` counts lines; nothing is parsed and nothing is held in memory. A
1579
- JSON array must be fully parsed to be counted.
1580
- - **batch** — a bulk endpoint's page is `readBatches(file, 100)`. Use `readBatchesBy` when the
1581
- target caps more than one dimension: Wix bulk product create allows **≤100 products AND
1582
- ≤1000 variants per request**, which is `{ maxCount: 100, maxCost: 1000, cost: p =>
1583
- p.variantsInfo.variants.length }`.
1584
- - **cursor / resume** — `readSlice(file, { offset, limit })` skips what is already done
1585
- without rebuilding it into objects.
1586
-
1587
- Two more properties that matter in practice: a producer can append records as it finds them
1588
- instead of buffering the whole entity, and an interrupted write leaves a **valid readable
1589
- prefix** — a truncated JSON array is unparseable, so a crash mid-extract loses everything.
1590
-
1591
- **Do not line-delimit single documents.** A manifest, `mapping-plan.json`,
1592
- `decisions.json`, `execution-manifest.json`, `preview-result.json` or
1593
- `completion-report.json` is one object; it stays `.json`. NDJSON buys nothing there and makes
1594
- it unreadable.
1595
-
1596
- Generated importers must **stream** these files (`for await (const batch of
1597
- readBatches(...))`), not `readAllRecords` them. `readAllRecords` is an escape hatch for
1598
- genuinely small streams (a six-record category list) and is named to make its misuse on a
1599
- large stream obvious.
1600
-
1601
- Projects generated before this rule can be moved forward with
1602
- `convertLegacyJsonFile(jsonPath, ndjsonPath)` rather than re-extracting.
1603
-
1604
- ### Reading existing target state (required)
1605
-
1606
- An idempotent importer has to know what is already on the site before it writes. Every bug in
1607
- this section is the same bug: **a read that returns nothing looks exactly like a site that
1608
- contains nothing**, and nothing-on-the-site is the branch that writes. None of them throw, so
1609
- none of them show up in a dry-run.
1610
-
1611
- **The adapter's `query*` executors already return the array.** `queryStoresCategories`,
1612
- `queryStoresProducts`, `queryContacts`, `queryCoupons` and `queryOrders` unwrap the response
1613
- before returning it, so the value **is** `categories` / `products` / etc. Generated code must
1614
- use it directly:
1615
-
1616
- ```js
1617
- const existing = await W.queryStoresCategories(wix); // an array
1618
- const existing = (await W.queryStoresCategories(wix)).categories; // WRONG → undefined → []
1619
- ```
1620
-
1621
- In the second form, reading `.categories` off an array yields `undefined`,
1622
- which the usual `|| []` turns into an empty array. It silently disables a category dedupe
1623
- index, silently disables a product name-match safety net, and makes a setup verification
1624
- report **0 categories on a site that has some** — all without one error line.
1625
-
1626
- **Never cursor-page through those executors.** Unwrapping discards `pagingMetadata`, so the
1627
- cursor a loop needs is already gone; a loop built on them cannot advance past page one, and
1628
- reading `.pagingMetadata` off the returned array is the same `undefined` as above. Prefer the
1629
- adapter's sweep primitives, which own the loop and the failure semantics:
1630
-
1631
- - `queryAllStoresCategories(wix)`, `queryAllStoresProducts(wix)`, `queryAllDataItems(...)`
1632
-
1633
- When a sweep is needed for an entity that has no `queryAll*` primitive yet, generate the loop
1634
- against the **raw** response and add the primitive to `rp-target-wix` rather than leaving the
1635
- loop in project code:
1636
-
1637
- ```js
1638
- let cursor = null;
1639
- do {
1640
- const body = cursor ? { cursorPaging: { limit: 100, cursor } } : { cursorPaging: { limit: 100 } };
1641
- const response = await wix.send(W.buildQueryStoresProductsRequest(body)); // raw, not the executor
1642
- for (const p of response.products || []) { /* index it */ }
1643
- cursor = (response.pagingMetadata && response.pagingMetadata.cursors && response.pagingMetadata.cursors.next) || null;
1644
- } while (cursor);
1645
- ```
1646
-
1647
- **An incomplete sweep must throw, not fall through.** If any page fails, or the loop hits its
1648
- page ceiling with a cursor still outstanding, the generated code must abort the import with a
1649
- message naming the sweep. It must **not** continue with the partial index, and must not treat
1650
- "the sweep failed" as "nothing exists" — that is precisely the state in which a re-run
1651
- re-creates the entire catalog it already imported. The single most expensive failure in this
1652
- whole pipeline is a duplicate import, and it arrives through an empty net.
1653
-
1654
- **A match must be ADOPTED into the crosswalk, not skipped.** When a safety net (name match,
1655
- slug match, source-id field) finds that the target entity already exists, record it in the
1656
- crosswalk with its **target id and revision** and count it as reused. A bare `continue` that
1657
- skips the write without recording the id looks correct — nothing is duplicated — but every
1658
- later phase that resolves ids *from the crosswalk* then silently drops the record. Concretely:
1659
- the product was already on the site, so it was skipped, so it had no crosswalk row, so the
1660
- category-link phase could not resolve its id and it ended up in no category at all. Index the
1661
- net as `name -> { id, revision }`, not as a `Set` of names, so the id is available to adopt.
1662
-
1663
- An ambiguous match is the one case that must not be adopted: if the source key is not unique
1664
- (e.g. four source products share a title), the net cannot tell which existing entity
1665
- corresponds to which source record. Import rather than guess, and report the ambiguity in the
1666
- completion report.
1667
-
1668
- ### Never hand-write slug sanitizing (required)
1669
-
1670
- A source handle is **not** already a valid Wix slug. Wix rejects anything outside `[a-z0-9-]`,
1671
- and because slug validation happens before the batch is applied, **one bad slug fails the
1672
- entire bulk request** — 100 products lost for one character. Shopify mints underscores from
1673
- decimal titles ("pH 5.5" → `ph-5_5`), so this is routine input, not an edge case.
1674
-
1675
- It is already solved: `wix-build.js` exports `toWixSlug` and applies it automatically through the
1676
- `coerce: 'slug'` rule on `product.slug` in `wix-target-spec.js`. Generated transforms that call
1677
- the build layer (see the `src/import/transforms/` note under "File targets") get it for free and
1678
- must not re-derive it — a per-project copy is how the underscore bug reached a live site in the
1679
- first place.
1680
-
1681
- Two properties to preserve when a generated transform sets a slug explicitly:
1682
-
1683
- - **Sanitize in the build layer, not the writer**, and keep both values. URL preservation needs
1684
- the original `sourceSlug` alongside the `plannedTargetSlug` actually derived from it (see the
1685
- URL preservation rules under "Codegen rules"), which a silent rewrite inside the writer would
1686
- falsify. `normalizeStoresProductV3` therefore passes a slug through untouched.
1687
- - **`toWixSlug` throws when a value sanitizes to empty** (all-illegal-punctuation/whitespace
1688
- input, for example — an all-non-Latin title does NOT throw: `toWixSlug`'s sanitizer keeps
1689
- Unicode letters, marks and decimal digits, so an all-Hebrew or all-Chinese title survives as a
1690
- transliterated slug instead of collapsing to empty). A genuinely empty result is a signal to
1691
- supply a deterministic fallback — the record's source id — not to omit the slug and let Wix
1692
- derive one, which breaks URL preservation with no trace.
1693
-
1694
- **WordPress source: decode before sanitizing (required).** WordPress's REST API
1695
- (`wp/v2`/`wc/v3`) returns a post/product's `slug`/`post_name` percent-encoded whenever the
1696
- original text has non-Latin characters (Hebrew, etc.) — see `rp-source-wordpress/SKILL.md`.
1697
- Sanitizing that raw value strips the `%` characters and ships a garbled hex-with-dashes slug
1698
- instead of the real one. Decode it with `decodeURIComponent` first (wrapped in try/catch: a
1699
- slug that was never percent-encoded, or that isn't a valid escape sequence, must pass through
1700
- unchanged rather than throw) and only then hand the result to `toWixSlug`/`coerce: 'slug'`.
1701
- This is documented as domain knowledge on `rp-target-wix`'s `stores/entities/product.json` and
1702
- `blog/entities/post.json` pitfalls.
1703
-
1704
- ## Sample-preview gate
1705
-
1706
- Between the extractor being generated and the execution-plan approval, show the user what
1707
- their data actually became. The mapping review checkpoint validates *intent*; this validates
1708
- *structure* — how source rows turned into entities — before any full run.
1709
-
1710
- Required whenever the source cannot be read back from a live API — in particular every
1711
- `platform: "csv"` run, where a misread layout silently produces the wrong entity split.
1712
-
1713
- 1. Generate the extractor first (`src/extract/run-extract.js`).
1714
- 2. Run it in sample mode (`--sample`) to materialize a small `data/source-extract/` slice.
1715
- 3. Write two artifacts under `migrations/<project>/preview/`:
1716
- - `preview-summary.md` — a short human-readable structure preview: how rows became grouped
1717
- entities (e.g. one product with its variants and images), per-entity record counts, and
1718
- which columns landed in which Wix fields.
1719
- - `preview-result.json` — `{ "status": "pending", "decidedAt": null, "decidedBy": null,
1720
- "entityCounts": {...}, "warnings": [] }`.
1721
- 4. Pause and ask the user to validate the structure. On accept, set `status: "accepted"` with
1722
- `decidedAt`/`decidedBy`; on reject, set `status: "rejected"` and set
1723
- `approvals.mapping.status` back to `pending` so the router returns to `rp-mapper`.
1724
- In explicit user-requested `1-click mode` (`automationMode=one_click`, `source=user`),
1725
- validate the structure automatically from the preview artifacts, set the result to
1726
- `accepted` with `decidedBy: "agent"` when it passes, and continue without a user pause.
1727
- Otherwise every CSV run pauses here, including a run whose `one_click` value was inferred
1728
- or copied with a non-user decision source.
1729
- 5. Record the artifacts on the codegen checkpoint (`checkpoints.codegen.artifactRefs` +
1730
- `lastCompletedStep: "codegen.sample-preview"`).
1731
-
1732
- This is a **codegen sub-gate, not an orchestration phase**: it adds no state to
1733
- `orchestration-state.js`. While `preview-result.json` says `pending`, the router keeps routing
1734
- back to this skill instead of advancing to the execution-plan gate — that routing behavior is
1735
- the enforcement, so the artifact must be written honestly. The gate rides on the existing
1736
- extract→import split and does not change how import writes to Wix.
1737
-
1738
- ## Project-local config files
1739
-
1740
- Generated code should treat `migrations/<project>/config/` as the canonical home for all
1741
- values that are otherwise expected as environment variables. Use simple `.env` syntax and
1742
- load these files before reading config:
1743
-
1744
- - `config/wix.env` always exists. Default `DRY_RUN=false`, except when the user has
1745
- explicitly asked to start, create, prepare, or run the migration in dry-run mode; in
1746
- that case scaffold or preserve `DRY_RUN=true`:
1747
-
1748
- ```bash
1749
- WIX_SITE_STRATEGY=
1750
- WIX_SITE_ID=
1751
- WIX_AUTH_TOKEN=
1752
- WIX_MUTE_NOTIFICATIONS=
1753
- DRY_RUN=false
1754
- SAFE_MODE=true
1755
- SAFE_MODE_PHONE_NUMBER=+972 50 0000000
1756
- ```
1757
-
1758
- - `config/source.<platform>.env` exists after the source platform is known. For WordPress:
1759
-
1760
- ```bash
1761
- WP_BASE_URL=
1762
- WP_USERNAME=
1763
- WP_APPLICATION_PASSWORD=
1764
- WMH2_MIGRATION_KEY=
1765
- WP_MEDIA_URL_REWRITE_FROM=
1766
- WP_MEDIA_URL_REWRITE_TO=
1767
- WC_CONSUMER_KEY=
1768
- WC_CONSUMER_SECRET=
1769
- ```
1770
-
1771
- For CSV sources this is `config/source.csv.env`, which is **not** secret-bearing — all keys
1772
- are optional hints (`CSV_INPUT_ROOT`, `CSV_DELIMITER`, `CSV_ENCODING`, `CSV_VENDOR`,
1773
- `CSV_MEDIA_URL_REWRITE_FROM`, `CSV_MEDIA_URL_REWRITE_TO`). Generated readers must resolve
1774
- input paths against `CSV_INPUT_ROOT` (falling back to the project directory) rather than
1775
- baking absolute paths into generated code.
1776
-
1777
- Codegen rules:
1778
-
1779
- - Generate a small dependency-free config loader in the runnable entrypoint or `src/lib/`.
1780
- - Load `config/wix.env` and the selected source config before constructing source/Wix
1781
- clients.
1782
- - Default `DRY_RUN` to disabled. Treat `true`, `1`, `yes`, and `on` as enabled and
1783
- `false`, `0`, `no`, and `off` as disabled. `--dry-run` must override config and enable
1784
- dry-run. `--no-dry-run` may be supported to override `DRY_RUN=true`.
1785
- - When scaffolding a project that is in dry-run mode, generated review artifacts must say
1786
- that leaving dry-run later requires explicit user approval for any step other than
1787
- new-site creation, and that such overrides should be avoided when a dry-run or report
1788
- is sufficient.
1789
- - Preserve an explicit `DRY_RUN=true` from project config when regenerating code or
1790
- config. Do not reset it to `false` during later codegen passes.
1791
- - Default safe mode to enabled when `SAFE_MODE` is missing or blank. Honor
1792
- `SAFE_MODE=false` when the user set it before mapping: do not require
1793
- `safeModeReplacements[]`, do not replace contact values, and do not write safe-mode email
1794
- recovery rows.
1795
- - When safe mode is enabled, require `SAFE_MODE_PHONE_NUMBER`, default missing/blank values
1796
- to `+972 50 0000000`, and make the generated config explicit.
1797
- - Real process environment variables may override file values.
1798
- - Blank values in config files must not overwrite non-empty process env values.
1799
- - If a required key is still missing after loading file + env, fail fast with the key
1800
- name, not a downstream 401.
1801
- - In dry-run, missing or blank `WIX_AUTH_TOKEN` and `WIX_SITE_ID` are `would_block_live`
1802
- findings, not blockers, unless a generated local artifact requires the site ID as a
1803
- stable namespace. Do not mint a Wix CLI token solely for dry-run.
1804
- - `WIX_MUTE_NOTIFICATIONS` resolves by strategy when blank: `new` → `on`,
1805
- `existing` → `off`; record the resolved value explicitly (mirrored from
1806
- `orchestration/decisions.json`). **`WIX_MUTE_NOTIFICATIONS=off` together with
1807
- `WIX_SITE_STRATEGY=new` fails codegen validation** — halt with the config conflict
1808
- rather than generating artifacts (the same rule fails in `rp-setup-discovery`; fail
1809
- fast at whichever runs first). Note the preflight emission rule below ignores the
1810
- config for new sites anyway — it is unconditional.
1811
- - Treat `WIX_SITE_STRATEGY` as required. `WIX_SITE_ID` becomes required no later than the
1812
- point where generated code needs to construct Wix clients or destination-specific
1813
- artifacts. For `WIX_SITE_STRATEGY=new`, codegen should fail with a clear message to
1814
- create/select the new Wix site first rather than assuming an existing site flow.
1815
- - For RePlatform `new site` + `headless`, that unblock message must point back to
1816
- `resources/rp-destination/`, which scaffolds via `npm create @wix/new@latest headless`.
1817
- The account-level Projects API is deprecated for this workflow.
1818
- - `WIX_AUTH_TOKEN` is the canonical Wix auth key in project-local config and holds the
1819
- **site write credential** for import. With CLI-scaffolded headless sites this is a
1820
- short-lived CLI token sent as a **Bearer** token — the generated Wix client must send
1821
- `Authorization: Bearer <token>` (plus `wix-site-id`). Do not assume a raw, non-expiring API
1822
- key; mint the token at write time.
1823
- - Never log secret values. It is okay to log that a key is present/missing.
1824
- - Dry-run must write request captures under `state/attempts/wix-request-captures.ndjson`
1825
- and dry-run placeholder target IDs only under `state/crosswalk/dry-run-crosswalk.ndjson`
1826
- or in memory. It must never append simulated target IDs to
1827
- `state/crosswalk/crosswalk.ndjson`.
1828
- - Do not generate debug output that dumps config file contents, environment snapshots, or
1829
- request headers carrying credentials.
1830
-
1831
- ## WooCommerce subscription product codegen
1832
-
1833
- For WooCommerce Stores products, subscription products are a native Wix Stores product
1834
- subtype when the target metadata marks `product.subscriptionDetails` writable for
1835
- Catalog V3 create. Codegen must read the `stores/product` `fieldContracts[]` metadata and
1836
- vendor/use the matching `rp-target-wix/lib/wix-writers.js` helpers:
1837
-
1838
- - `STORES_SUBSCRIPTION_CONTRACT`
1839
- - `normalizeStoresProductSubscriptions`
1840
- - `validateStoresProductSubscriptionDetails`
1841
-
1842
- Generate deterministic native subscription mapping only from recognized structured
1843
- WooCommerce subscription fields. Accepted first-pass source keys include:
1844
-
1845
- - `_subscription_period_interval`
1846
- - `_subscription_period`
1847
- - `_subscription_length`
1848
- - `_subscription_trial_period`
1849
- - `_subscription_trial_length`
1850
- - `_subscription_sign_up_fee`
1851
- - `_subscription_price`
1852
-
1853
- Equivalent public REST fields may be used when discovery normalized them into the source
1854
- schema, but do not infer cadence from product titles, descriptions, prose, shortcode
1855
- blobs, or unrecognized plugin metadata.
1856
-
1857
- Mapping rules:
1858
-
1859
- - Treat source `type: "subscription"` or recognized subscription metadata as the product
1860
- subtype signal.
1861
- - Parse billing interval as an integer `>= 1`.
1862
- - Parse billing period from `day`, `week`, `month`, or `year`, then emit Wix frequency
1863
- `DAY`, `WEEK`, `MONTH`, or `YEAR`.
1864
- - Use subscription price when present, otherwise the normal product price, and still emit
1865
- the Catalog V3 variant-level money object required by the shared writer.
1866
- - Parse optional length/trial/signup-fee fields only when they are clean structured
1867
- values. If a field is present but malformed and needed for the selected mapping, defer
1868
- the record rather than guessing.
1869
- - Build subscription descriptions deterministically from source subscription labels or
1870
- product names, then pass them through `normalizeStoresProductSubscriptions` so the Wix
1871
- `description <= 60` contract is satisfied before write.
1872
- - Run `validateStoresProductSubscriptionDetails` in the generated dry-run path and before
1873
- live create for every product carrying `subscriptionDetails`.
1874
-
1875
- Do not generate a generic subscription skip gate. If a subscription product lacks
1876
- structured cadence or required values, emit an explicit deferred record such as
1877
- `unsupported_subscription_shape`, `missing_subscription_cadence`, or
1878
- `invalid_subscription_interval`, and include that reason in the execution summary. A
1879
- deferred subscription is a counted import outcome, not a silent skip.
1880
-
1881
- **Token minting — always route through `scripts/mint-token.sh`.** Every migration project
1882
- must include `scripts/mint-token.sh`. The script reads `WIX_SITE_ID` from `config/wix.env`,
1883
- runs `npx @wix/cli@latest token --site "$WIX_SITE_ID"`, captures the token (shape:
1884
- `OauthNG.JWS.<base64>.<base64>.<sig>` — single line, no JSON wrapper), and writes it
1885
- directly to `config/wix.env` as `WIX_AUTH_TOKEN` without printing the value. Run via Bash:
1886
-
1887
- ```bash
1888
- bash migrations/<project>/scripts/mint-token.sh
1889
- ```
1890
-
1891
- **Do NOT** run `npx @wix/cli@latest token` raw in a Bash tool call — it prints the
1892
- credential to stdout which lands in the transcript. Always use `mint-token.sh`.
1893
-
1894
- **During scaffolding, copy `scripts/mint-token.sh` from the canonical skills location** —
1895
- do not generate it from scratch:
1896
-
1897
- ```bash
1898
- cp skills/wix-replatform/resources/rp-execute-setup/scripts/mint-token.sh \
1899
- migrations/<project>/scripts/mint-token.sh
1900
- ```
1901
-
1902
- The preferred
1903
- pre-import flow is:
1904
-
1905
- 1. verify `wix whoami`,
1906
- 2. run `bash scripts/mint-token.sh` (writes `WIX_AUTH_TOKEN` silently),
1907
- 3. run the import — the generated client reads `WIX_AUTH_TOKEN` and sends it as
1908
- `Authorization: Bearer <token>` with `wix-site-id`.
1909
-
1910
- ## Members: full-fidelity create/reconcile, so orders can link to a buyer
1911
-
1912
- `resolveOrderBuyer` (below) links an order to a member only when the member crosswalk row for that
1913
- source customer carries a `loginEmail` — nothing produces that row unless the member pass runs
1914
- FIRST, in its own loop, before order history. Generate the call, not a hand-rolled `createMember`.
1915
-
1916
- ```js
1917
- const CM = require('./src/resources/rp-target-wix/lib/customer-member.js');
1918
- const MC = require('./src/lib/member-completion.js');
1919
- const localState = require('./src/lib/local-state.js');
1920
- const path = require('node:path');
1921
- const { execFileSync } = require('node:child_process');
1922
- const memberCrosswalk = require('./src/lib/member-crosswalk.js').createProjectMemberCrosswalk({ projectDir: ROOT });
1923
-
1924
- // BEGIN member-pass
1925
- // registeredCustomers: EVERY customer supplied to this pass (registered only -- guest buyers are
1926
- // out of scope here), each carrying the mapping's sourceStableKey. Pass the full list on subset passes too:
1927
- // the pass selects within it.
1928
- const memberPass = await MC.runMemberPass({
1929
- projectDir: ROOT, records: registeredCustomers, runId, phase: 'import', writeSpecId: 'members',
1930
- mode: selectionMode, // 'full' | 'failed-only' | 'deferred-only' | 'unresolved-only' | 'missing-only'
1931
- sample: dryRun || sampleMode, // sample journal path, and no population snapshot write
1932
- sourceFile: customersExtractPath, sourceChecksum: customersExtractSha256,
1933
- ensure: async (customer) => {
1934
- const { contact, findings } = CM.buildMemberContact({
1935
- firstName: customer.first_name, lastName: customer.last_name,
1936
- phone: customer.billing && customer.billing.phone, countryCode: customer.billing && customer.billing.country,
1937
- address: customer.billing && { addressLine: customer.billing.address_1, addressLine2: customer.billing.address_2, city: customer.billing.city, subdivision: customer.billing.state, postalCode: customer.billing.postcode, country: customer.billing.country },
1938
- });
1939
- const known = await memberCrosswalk.get(customer.id);
1940
- const knownMemberId = known && known.memberId;
1941
- const result = await w.ensureMember(wix, { loginEmail: customer.email, contact, knownMemberId });
1942
- return { result, builderFindings: findings, knownMemberId };
1943
- },
1944
- persistCrosswalk: (customer, result) => memberCrosswalk.put(customer.id, { memberId: result.memberId, loginEmail: customer.email, contactId: result.contactId }),
1945
- });
1946
- // Load the journal ONCE here and thread the same rows into both this projection
1947
- // and the `attachAttemptReasons` join at the final `createCompletionReport` call below --
1948
- // `projectMemberPass`'s own `journalRows` option exists exactly so a report with a member
1949
- // breakdown never reads the journal twice.
1950
- const journal = await localState.loadAttemptJournal(ROOT, { sample: dryRun || sampleMode });
1951
- const memberProjection = await MC.projectMemberPass(ROOT, { runId, pass: memberPass, journalRows: journal.rows });
1952
- completionInput = MC.mergeMemberCompletion(completionInput, memberProjection.completion);
1953
- await MC.emitMemberTelemetry(memberProjection.telemetryEvents, (event) => execFileSync('node',
1954
- [path.join(toolkitRoot, 'resources/rp-telemetry/scripts/rp-telemetry.js'), 'record', JSON.stringify(event), '--project', ROOT]));
1955
- if (!memberPass.ok) console.error('member pass refused: source keys missing, duplicated or unknown; no member was written');
1956
- // END member-pass
1957
- ```
1958
-
1959
- `selectionMode` comes from the importer's own `--failed-only` / `--deferred-only` /
1960
- `--unresolved-only` / `--missing-only` flags (`'full'` when none); `completionInput` is the run's
1961
- `let`-declared held completion input that the order-history block below also extends;
1962
- `customersExtractPath`/`customersExtractSha256` name the extraction file the customers came from.
1963
-
1964
- What the call does, so the generated code must not:
1965
-
1966
- - **Converts the phone to E.164 or drops it, never sends it as-is and never guesses.** Create
1967
- Member 400s on anything that is not already E.164 (VERIFIED LIVE). `normalizePhoneE164` covers a
1968
- small set of countries (`customer-member.js`'s `COUNTRY_PHONE_RULES`); an unlisted country is a
1969
- finding, not a guess -- extend the table rather than hand-rolling a conversion in generated code.
1970
- - **Queries Contacts by phone before every create that carries one.** Create Member reuses an
1971
- EXISTING contact that already carries the phone being sent -- even one belonging to a member
1972
- deleted earlier in the same run (deleting a member does not delete its contact). Two source
1973
- customers who share a phone would otherwise be silently merged into one Wix identity. Never call
1974
- Create Member directly with a phone; `ensureMember` is where this guard lives.
1975
- - **Reconciles by the crosswalk row first (read back and verified), then by `loginEmail`.** One
1976
- match reconciles, more than one is ambiguous and creates nothing. A stale crosswalk row (the
1977
- member was deleted, or the row points at a different email) falls through to the `loginEmail`
1978
- query rather than being trusted blind.
1979
- - **Persist `loginEmail` on the crosswalk row, or `resolveOrderBuyer` cannot link anything.** The
1980
- member pass must run to completion (or at least for every registered buyer the order pass will
1981
- see) BEFORE the order-history loop below.
1982
- - **Every customer's outcome is durable and reported.** `runMemberPass` writes a
1983
- `started` row before `ensureMember` and a terminal row with the same `attemptId` after it (the
1984
- crosswalk row first when a member id came back), in `state/attempts/write-attempts.ndjson`.
1985
- Never add a private member results log, a hand-rolled `report(result)`, or a second tally.
1986
- - **The whole pass stops before any write when source keys are bad.** A missing or duplicate
1987
- `sourceStableKey`, or (on a subset pass) a key the full pass never saw, means zero journal rows
1988
- and zero Wix calls; the refusal is projected into the report (`incomplete_with_mismatches`)
1989
- and one `pipeline_defect` event. Never deduplicate or skip records to get past it -- fix the
1990
- mapping. A full pass writes `execution/member-population.json` (keys only); subset passes
1991
- select within it, never shrink the report's denominator, and refuse when it is absent.
1992
- - **The report reads only the journal.** `projectMemberPass` folds the latest member row per
1993
- key into the `members` completeness row (with the `unresolved` counter), `unresolved[]`,
1994
- `droppedFields[]`, one operator action for ambiguous matches, and `memberOutcomes`. Declare no
1995
- separate members row. Uncertain writes (409 already-exists, 5xx/transport/no-id creates, a
1996
- dangling start) are **unresolved**: recover with `--unresolved-only`, which reads the member
1997
- back by login email before any create. Emit the returned telemetry events once, after the pass;
1998
- a telemetry failure never aborts the run.
1999
-
2000
- ## Order history: payments, refunds and preserved invoice documents
2001
-
2002
- An imported order reads back with `payments: []` — Import Order creates no payment record — and
2003
- the order write has no idempotency of its own unless it is given an id. Payment history, refunds
2004
- and the invoice document are separate target writes with an ordering constraint, a shared
2005
- identity and state that must outlive a crash. **All of that is sequenced by one library call.
2006
- Generate the call, not the sequence.**
2007
-
2008
- ```js
2009
- const H = require('./src/resources/rp-target-wix/lib/order-history.js'); // vendored layout, see below
2010
- const { resolveOrderBuyer } = require('./src/resources/rp-target-wix/lib/order-buyer.js');
2011
- const crosswalk = H.createProjectOrderCrosswalk({ projectDir: ROOT }); // state/crosswalk/crosswalk.ndjson
2012
- const setupVerification = readJson('setup/extended-field-verification.json'); // ONCE per run
2013
- const orderResults = []; // compact final stage outcomes, no payment payloads
2014
-
2015
- for (const source of orders) {
2016
- const result = await H.importOrderHistory(wix, {
2017
- order: toWixOrder(source), // the mapped Import Order body — never set `id`; buyerInfo.email = source.billing.email
2018
- sourceId: source.id,
2019
- sourceSiteHost, // new URL(WP_SITE_URL).host
2020
- evidence: { // FACTS from the source, nothing else
2021
- amount: source.total,
2022
- paidDate: source.date_paid_gmt, paidDateZone: 'utc',
2023
- altPaidDate: source.date_paid, altPaidDateZone: 'site-local',
2024
- currency: storeCurrency, // rp-source-wordpress/lib/store-currency.js, not order.currency
2025
- status: source.status,
2026
- methodName: gateway.displayName, providerTransactionId: gateway.reference, offlinePayment: gateway.offline,
2027
- refunds: qualified.map((r) => ({ sourceId: r.id, amount: r.amount, reason: r.reason })),
2028
- invoice: approval && { approval, sourceUrl: approval.documentUrl, invoiceNumber: approval.number },
2029
- },
2030
- crosswalk,
2031
- setupVerification,
2032
- // the buyer, corroborated from the member crosswalk (rows carry memberId + loginEmail)
2033
- // contactCrosswalk is OPTIONAL (a guest/contact-only buyer's contactId, when one is tracked
2034
- // separately from members) -- omit the property entirely when there is no such crosswalk in
2035
- // this project, never pass a variable that is not actually declared (it throws ReferenceError:
2036
- // contactCrosswalk is not defined the moment this line runs, not at require() time).
2037
- buyer: await resolveOrderBuyer({ sourceCustomerId: source.customer_id, billingEmail: source.billing.email, memberCrosswalk }),
2038
- });
2039
- // BEGIN order-stage-result
2040
- orderResults.push({ sourceKey: source.sourceStableKey, outcomes: result.outcomes });
2041
- // END order-stage-result
2042
- // result.findings also carries the contact-field codes: order-contact-email-dropped,
2043
- // order-contact-phone-dropped (removed after Import Order named the field), order-contact-email-suspect,
2044
- // order-contact-phone-suspect (warnings; value kept and sent) and buyer-email-unusable.
2045
- report(result); // result.outcomes.{order, payment, refunds[], invoice, buyer}, result.findings, result.errors
2046
- }
2047
-
2048
- // BEGIN order-history-completion
2049
- const { collectOrderStageResiduals, createCompletionReport, writeCompletionReport, attachAttemptReasons } =
2050
- require(require('node:path').join(toolkitRoot, 'lib/completion-report.js'));
2051
- // Reuse the SAME journal rows the member pass above already loaded (when
2052
- // this run has one -- `typeof journal === 'undefined'` means it does not) rather than reading
2053
- // the journal again here; join them in before building the report.
2054
- const orderHistoryJournal = typeof journal === 'undefined'
2055
- ? await require(require('node:path').join(toolkitRoot, 'lib/local-state.js')).loadAttemptJournal(ROOT)
2056
- : journal;
2057
- const completion = createCompletionReport({
2058
- ...completionInput,
2059
- entityCompleteness: attachAttemptReasons(completionInput.entityCompleteness, orderHistoryJournal.rows, { runIds: [runId] }),
2060
- orderHistoryInScope: true,
2061
- stageResiduals: [
2062
- ...(completionInput.stageResiduals || []).filter((row) => row.entity !== orderEntity),
2063
- ...collectOrderStageResiduals(orderResults, { entity: orderEntity }),
2064
- ],
2065
- });
2066
- await writeCompletionReport(ROOT, completion, { runId });
2067
- // END order-history-completion
2068
- ```
2069
-
2070
- A run with no member pass still loads the journal once, right before this call:
2071
- `const journal = await localState.loadAttemptJournal(ROOT, { sample: dryRun || sampleMode });` --
2072
- never once per entity, and never a second time for a report the member pass already loaded it for.
2073
-
2074
- Before the loop, mapping supplies each order's `sourceStableKey` and `orderEntity` (the
2075
- same entity name used by the completeness counters). `completionInput` contains the final
2076
- held counters for the run. Collect the latest outcomes for every in-scope order, including
2077
- orders adopted from the crosswalk; persist the compact outcomes with batch accounting and
2078
- restore them on resume. Run the completion block once after the last write, including recovery,
2079
- and only for a live run. Dry runs retain the existing simulated-report path. Do not substitute
2080
- an empty residual list or derive it by counting retry attempts.
2081
-
2082
- What the composite does, so the generated code must not:
2083
-
2084
- - **Derives the order id** from `sourceSiteHost` and `sourceId` (Import Order accepts a
2085
- caller-supplied GUID — verified live 2026-09-05), writes the crosswalk row before any further
2086
- write, and on a crosswalk miss asks the target by that id. A lost crosswalk is a read, not a
2087
- duplicated store. An order that already exists is reconciled, never re-sent: a re-send silently
2088
- replaces the whole body.
2089
- - **Runs the payment gate from `evidence`.** A key the contract does not read (`sourceAmount`,
2090
- `total`, `transaction_id`, …) throws, naming the key it should have been. It does not read as
2091
- "never paid".
2092
- - **Derives each paid stamp under the clock YOU declare for it**, which is why the two paid-date
2093
- lines above are paired with a zone. The source records one payment twice: the `_gmt` sibling is
2094
- universal by that key's definition in the source schema, so it never takes a shop offset, and the
2095
- plain field is the shop's wall clock with the zone stripped off, so it takes the shop's offset
2096
- when one is known and carries `paid-date-timezone-assumed` when one is not. Which field is which
2097
- is a fixed fact about the source platform, known before any shop is examined — state it here
2098
- rather than leaving it to be inferred from a value that no longer carries the distinction.
2099
- Supply `utcOffsetMinutes` as before when the shop's offset is known; it stays a fact about the
2100
- SHOP, and "already universal" is `paidDateZone: 'utc'`, never `utcOffsetMinutes: 0`. Supplying
2101
- the offset beside a present stamp whose clock you have not stated is REFUSED, naming the stamp:
2102
- that is exactly the case where guessing the clock moves the payment by hours.
2103
- - **Reads the order's payments once** and reconciles both the payment and every refund against
2104
- that read. Occurrence numbers for equal refunds are assigned from the refunds' source ids — pass
2105
- ALL of an order's refunds in the one call.
2106
- - **Returns every outcome**, including the payment's when a refund exists, and a named
2107
- partial-failure outcome (`payment-failed`, `refund-failed`, `invoice-failed`, with the cause in
2108
- `errors`) instead of throwing, so a resume retries only what failed.
2109
- - **Sanitises the mapped body's contact fields before the request is built**.
2110
- `sanitizeOrderContactFields` walks `safeModeContactFields[]` from
2111
- `rp-target-wix/domains/ecom/entities/order.json` — the same six paths safe mode replaces — and
2112
- either rewrites a phone into E.164 under the explicit country rule or removes the field and
2113
- emits `order-contact-email-dropped` / `order-contact-phone-dropped` carrying the field and a
2114
- stable reason, never the value. A removed buyer email also produces `buyer-email-unusable`,
2115
- because "we had a customer and lost them" must be countable apart from a real guest.
2116
- **Do not re-implement this in the generated runner and do not sanitise the body yourself**: a
2117
- second pass would double-count the findings, and one placed after the composite would run
2118
- after safe mode has already replaced every one of those paths with an always-valid mock. One
2119
- bad email or phone fails the entire Import Order, so this is a local refusal before the
2120
- network, never a send-then-retry loop.
2121
- - **Sets `buyerInfo.memberId` only from `resolveOrderBuyer`** (`rp-target-wix/lib/order-buyer.js`):
2122
- a member crosswalk row whose `loginEmail` equals the order's billing email. Persist `loginEmail`
2123
- on the member crosswalk row when creating members, or nothing can be linked. Never put a
2124
- `memberId` on the order body yourself; the composite refuses it.
2125
- - **Binds the body's `buyerInfo.email` to the buyer it was resolved for.** The mapped body must carry
2126
- the order's billing email (the same value passed as `billingEmail`); a linked buyer beside a body
2127
- carrying another email, or none, is refused — a resolution for one order must not attribute
2128
- another order to that member.
2129
- - **Handles contact fields Import Order refuses.** A contact
2130
- field is removed only when Import Order's 400 names it in `fieldViolations[]` (exactly a
2131
- `safeModeContactFields[]` path, and no other violation); the order is then sent once more under
2132
- the same id, and each removal is an `order-contact-*-dropped` finding naming the field, never the
2133
- value. A second refusal is `order-import-failed`. Before the first send, and so before safe mode,
2134
- the composite checks the source values and emits `order-contact-*-suspect` warnings without
2135
- changing them. Phones are never rewritten. On a `buyer-member-linked` order the retry keeps
2136
- `buyerInfo.memberId` and `contactId`. The generated runner must not remove, rewrite or pre-check
2137
- contact fields itself, and must not add its own retry around the composite.
2138
- - **Hands the invoice approval to the writer unchanged.** The merchant opt-in is an input to
2139
- `resolvePreservationPlan()`, which STAMPS every approval it issues; the writer believes an opt-in
2140
- only with that stamp, so an approval assembled by hand (or lifted from the plan's `refused` list)
2141
- is refused as `invoice-preservation-not-approved`. Pass the plan's objects, never rebuild them.
2142
- - **Remembers a document that reached Media but whose order patch failed** — in the crosswalk
2143
- (`state/crosswalk/pending-invoice-files.ndjson`), so the resume records that file instead of
2144
- importing a second private copy. No caller state; `createProjectOrderCrosswalk` holds it.
2145
- - **Verifies every crosswalk hit with one order read.** A row pointing at an order the target no
2146
- longer has (a migration cleared with Bulk Delete Imported Orders) is re-imported under the
2147
- derived id and counted as `order-reimported-after-deletion`.
2148
-
2149
- What is still the caller's, because it is source knowledge:
2150
-
2151
- - Which processor a payment-method slug names: `rp-source-wordpress/lib/gateway-source-keys.js`.
2152
- - Whether a refund is real: `lib/refund-corroboration.js` `qualifyRefund()` (its
2153
- `assignRefundOccurrences()` is no longer needed — the composite assigns them).
2154
- - Which document and whether it may be copied: `lib/invoice-discovery.js` through
2155
- `resolvePreservationPlan({ selection, merchantOptedIn })`.
2156
- - The store currency, resolved once at extract time — `order.currency` on WooCommerce REST is not
2157
- reliably ISO 4217 (a live store returned the HTML-encoded shekel symbol on every order).
2158
- - The verification receipt, read once, and the report: every `outcomes` value is a line for
2159
- `rp-execution-policy/lib/payments-report.js`, including the zeros.
2160
-
2161
- Vendoring mirrors the skill's layout: the shared modules require each other across resources
2162
- (`../../../lib/…`), so `src/` reproduces `lib/` + `resources/<resource>/lib/` — `order-history.js`
2163
- lives at `src/resources/rp-target-wix/lib/` and reaches `src/lib/local-state.js`. A flat copy loads
2164
- nothing.
2165
-
2166
- **Two dependency shapes `require()` alone will not surface, both hit live 2026-09-06 running a
2167
- generated-style project end to end:** a lib can read a **domain entity JSON file directly**
2168
- (`order-invoice-contract.js` reads `../domains/ecom/entities/order-invoice-document.json` with
2169
- `path.join` + `readFileSync`, not `require`, so a dependency scan that only follows `require` calls
2170
- misses it — vendor `domains/ecom/entities/order-invoice-document.json` alongside
2171
- `order-invoice-contract.js`), and a lib can have a **sibling JSON registry** next to its own `.js`
2172
- file (`gateway-source-keys.js` → `gateway-source-keys.json`, `refund-corroboration.js` →
2173
- `refund-corroboration.json`) that must be copied too, or it throws `ENOENT` the first time the
2174
- function that reads it is actually called — not at `require()` time, so a smoke test that only
2175
- does `require()` on every vendored file will not catch a missing one. Copy the `.json` file
2176
- wherever you copy the `.js` file next to it.
2177
-
2178
- If the source has no payment evidence at all, make the call anyway with what evidence there is and
2179
- let it report zero under a named reason. An importer that omits the path is indistinguishable from
2180
- one that ran and found nothing, and only one of those is a correct migration.
2181
-
2182
- **Never call the invoicing service**, and never generate a fallback that does. Re-issuing a
2183
- historical tax document mints a second legal document under a different number.
2184
- `tests/target-wix/no-invoices-api-contract-test.js` scans the published tree for this.
2185
-
2186
- ## Coupons, currency and pricing-plan definitions
2187
-
2188
- Generate three passes, in this order, and one report section. The decisions are in the libraries;
2189
- codegen supplies reads and facts.
2190
-
2191
- 1. **Currency gate, before any order write.** Read `wc/v3/settings/general` →
2192
- `store-currency.js` `readStoreCurrency`; resolve every order's currency with
2193
- `resolveOrderCurrency(order, storeCurrency)` (an order field can be an encoded currency symbol such as `&#8362;`);
2194
- read the target site currency; call `currency-gate.js` `evaluateCurrencyGate`. `halt: true`
2195
- stops the run at the approval gate with the named finding. `currency-validated` is a line too.
2196
- 2. **Coupons.** `sweepCoupons(page => wc/v3/coupons?per_page=100&page=N)` to exhaustion; the
2197
- `posts` count by `post_status` through the plugin bridge for reconciliation;
2198
- `summarizeCoupons` for the counts; per coupon `mapCouponToWix(coupon, { resolveProduct,
2199
- resolveCategory })` through the catalog crosswalks, then write them in BATCHES with
2200
- `bulkCreateCoupons(wix, specifications)` -- 100 per call, and it returns one outcome per input
2201
- specification in input order, so the coupon crosswalk row (`code → couponId`) is written per
2202
- coupon exactly as before and a re-run still resumes. A few thousand coupons is ordinary for
2203
- this source, and `ensureCoupon` one at a time makes it a few thousand round trips; reach for
2204
- `ensureCoupon` only for a single coupon or a repair.
2205
- ⚠️ **Batching and tagging cannot be combined.** The batch discards the tag field and still
2206
- answers 200, so `bulkCreateCoupons` refuses a tagged specification. Pick one and report which:
2207
- write tagged coupons with `ensureCoupon` one at a time, or batch them untagged and then tag
2208
- each with `updateCouponFields({ tags })`, which is a call per coupon.
2209
- ⚠️ **When most or all of the batch needs a tag (e.g. the `Imported` /
2210
- `Already_Used` / `Partially_Used`), the untagged-batch-then-tag path loses its throughput
2211
- advantage entirely.** `updateCouponFields`'s field-level PATCH is read-back VERIFIED
2212
- (its own TRAP 3) with a mandatory settle-and-verify wait, so once close to 100% of coupons
2213
- need that follow-up call, every coupon pays the wait regardless of the batch step ahead of
2214
- it, which makes the run slow end-to-end. Prefer per-coupon
2215
- `createCoupon(wix, specification)` instead, with `tags` already in the specification: the
2216
- normal-case response is the documented `{ id }`, trusted immediately with no settle wait,
2217
- which is far faster. A small tagged
2218
- minority still nets out better as bulk-untagged + per-item `updateCouponFields`, since only
2219
- that minority pays the wait; this is specifically for the near-all-tagged case. Note that
2220
- plain `createCoupon` does not reconcile a `code already exists` rejection the way
2221
- `ensureCoupon` does — it is for writing coupons not yet attempted, not for idempotent replay
2222
- over a batch that was already partly written; a resumed or repaired pass still needs a
2223
- query-by-code reconciliation step for any coupon a prior attempt may have already created.
2224
- ⚠️ Do NOT re-implement the batch call. `bulkCreateCoupons` already handles the two traps that
2225
- make it dangerous: a batch fails as a whole while reporting the same error against every row
2226
- in it (so any failure re-sends that batch as individual creates), and `originalIndex` is
2227
- omitted on the first result (so results are correlated by index, never by position, and a
2228
- duplicate or incomplete index cover reconciles per coupon rather than guessing). Outcomes
2229
- `coupon-reconciled-existing` and `coupon-write-unknown` are not failures: the first recovered
2230
- an id for a coupon that was already there, the second means the code exists but is not
2231
- readable yet and must be re-read rather than re-written. A
2232
- `coupon-type-unmappable` / `coupon-scope-unmappable` / `coupon-usage-limit-unresolved` result
2233
- is a report line, not a write. `sweepCoupons` runs to an EMPTY page when the server sends no
2234
- `X-WP-Total`; pass the header through as `total` whenever it is present.
2235
-
2236
- **Emit this call; do not describe it.** `toolkitRoot` is the installed or vendored
2237
- wix-replatform root. `fetchCouponPage(page)` returns `{ items, total }` from
2238
- `wc/v3/coupons?per_page=100&page=N` with `X-WP-Total` as `total`; `readFreshCoupon(id)` returns
2239
- `wc/v3/coupons/{id}` re-read NOW, or `null` if that read failed; `recordCouponCrosswalk(code,
2240
- couponId)` is the run's crosswalk writer; `couponUpdateOptions` is the shared runtime's settle
2241
- options for the read-back-verified PATCH.
2242
-
2243
- ```js
2244
- // BEGIN verified-coupon-import
2245
- const couponPath = require('node:path');
2246
- const { sweepCoupons, mapCouponToWix, summarizeCoupons } = require(couponPath.join(toolkitRoot, 'resources/rp-source-wordpress/lib/coupon-discovery.js'));
2247
- const { classifySnapshot, isBearerVoucher, classifyPreWriteDrift } = require(couponPath.join(toolkitRoot, 'resources/rp-target-wix/lib/coupon-redemption-drift.js'));
2248
- const { bulkCreateCoupons, updateCouponFields } = require(couponPath.join(toolkitRoot, 'resources/rp-target-wix/lib/wix-writers.js'));
2249
-
2250
- // Gate the WHOLE pass: a snapshot that cannot be dated is refused exactly like a stale one.
2251
- const snapshot = classifySnapshot({ capturedAt, now: Date.now(), maxAgeMs });
2252
- if (!snapshot.fresh) throw new Error(`coupon pass halted: ${snapshot.verdict} -- ${snapshot.reason}`);
2253
-
2254
- const { coupons: restCoupons } = await sweepCoupons(fetchCouponPage);
2255
- const mappings = [];
2256
- const toWrite = [];
2257
- const notWritten = [];
2258
- const writeOutcomes = [];
2259
- for (const coupon of restCoupons) {
2260
- // The mapper decides redeemability from the source usage count and REFUSES what it cannot
2261
- // express. A project-local toSpecification is not an acceptable substitute -- see the table.
2262
- const mapping = mapCouponToWix(coupon, { resolveProduct, resolveCategory });
2263
- mappings.push(mapping);
2264
- if (!mapping.specification) { notWritten.push({ code: coupon.code, outcome: mapping.outcome, reason: mapping.reason }); continue; }
2265
- let specification = mapping.specification;
2266
- if (isBearerVoucher(coupon)) {
2267
- const verdict = classifyPreWriteDrift({ snapshotCoupon: coupon, freshCoupon: await readFreshCoupon(coupon.id) });
2268
- if (!verdict.write) { notWritten.push({ code: coupon.code, outcome: verdict.outcome, reason: verdict.reason }); continue; }
2269
- // The verdict IS the final state. Rebuild from it; never send the mapper's snapshot.
2270
- specification = { ...specification, active: verdict.writeActive, tags: verdict.setTags };
2271
- if (verdict.usageUnlimited) { delete specification.usageLimit; specification.usageUnlimited = true; }
2272
- else if (verdict.writeUsageLimit != null) specification.usageLimit = verdict.writeUsageLimit;
2273
- }
2274
- toWrite.push({ code: specification.code, specification });
2275
- }
2276
- // 100 per call. The batch silently discards `tags` and still answers 200, so it goes out
2277
- // untagged and each written coupon is tagged afterwards with the minimal-specification PATCH.
2278
- for (let i = 0; i < toWrite.length; i += 100) {
2279
- const batch = toWrite.slice(i, i + 100);
2280
- const outcomes = await bulkCreateCoupons(wix, batch.map(({ specification }) => { const { tags, ...untagged } = specification; return untagged; }));
2281
- for (let j = 0; j < outcomes.length; j += 1) {
2282
- writeOutcomes.push(outcomes[j].outcome);
2283
- if (!outcomes[j].couponId) continue;
2284
- await recordCouponCrosswalk(batch[j].code, outcomes[j].couponId);
2285
- const tags = batch[j].specification.tags;
2286
- if (tags && tags.length) await updateCouponFields(wix, outcomes[j].couponId, { tags }, couponUpdateOptions);
2287
- }
2288
- }
2289
- const couponCounts = summarizeCoupons({ restCoupons, postsByStatus, mappings, writeOutcomes });
2290
- // END verified-coupon-import
2291
- ```
2292
-
2293
- ⚠️ **A project-local `toSpecification` is not an acceptable substitute for `mapCouponToWix`.**
2294
- It is not a style preference. The mapper performs a list of refusals that do not survive being
2295
- re-written by hand, and each one exists because the alternative writes something the merchant
2296
- never published:
2297
-
2298
- | Refusal the mapper performs | What a hand-rolled transform does instead |
2299
- | --- | --- |
2300
- | Exhausted at source → `active: false`, tagged `Already_Used` | Writes it live |
2301
- | Partly used → the uses **left**, tagged `Partially_Used` | Writes the source's original allowance |
2302
- | Unreadable usage fields → `coupon-usage-limit-unresolved`, no write | Writes an undecidable money instrument live |
2303
- | Unmappable discount type → `coupon-type-unmappable`, no write | Guesses a type, or drops a free gift silently |
2304
- | Scope the target cannot hold → `coupon-scope-unmappable`, no write | Widens it to the whole store |
2305
- | More than two decimal places → `coupon-amount-precision-unsupported`, no write | Rounds, so the target amount differs while every check reports a match |
2306
- | Free shipping beside an amount → keeps free shipping, reports the dropped amount | Sends both and loses one silently |
2307
-
2308
- ⚠️ The writer now REFUSES a specification that never decided redeemability: `active` must be
2309
- present and boolean, and `usageLimit` must be a positive integer unless `usageUnlimited: true`
2310
- is present. The refusal fires in `ensureCoupon`, `bulkCreateCoupons` and
2311
- `normalizeCouponSpecification`, before any network call, and it names the coupon code and the
2312
- missing field. It is a CREATE-path guard only — `updateCouponFields` must keep carrying only
2313
- the fields being changed.
2314
-
2315
- 2a. **Decide a coupon's final shape from the FRESH read, not from the mapped snapshot**
2316
- (`rp-target-wix/lib/coupon-redemption-drift.js`). Gate the whole coupon pass on
2317
- `classifySnapshot({ capturedAt, now, maxAgeMs })` and halt on `snapshot-stale` or
2318
- `snapshot-undated`.
2319
-
2320
- Then, for each coupon where `isBearerVoucher(coupon)` is true and only those, re-read
2321
- `wc/v3/coupons/{id}` immediately before writing and pass both rows to `classifyPreWriteDrift`.
2322
- **The verdict is not a boolean gate — it is the specification's final state**, and the payload
2323
- must be rebuilt from it rather than from the mapper's output:
2324
-
2325
- | Verdict field | What the outgoing specification must carry |
2326
- | --- | --- |
2327
- | `write: false` | nothing — the coupon is not written at all; report the outcome |
2328
- | `writeActive` | `specification.active`. `false` for a coupon the fresh read found used up |
2329
- | `writeUsageLimit` | `specification.usageLimit` — the uses the holder has LEFT |
2330
- | `usageUnlimited: true` | **delete** `specification.usageLimit` entirely; a stale limit left on an unlimited coupon caps it |
2331
- | `setTags` | `specification.tags` — replaces whatever the mapper stamped |
2332
-
2333
- ⚠️ Mapping and then only checking `write` is the failure this table exists to prevent: a coupon
2334
- spent after the snapshot comes back `write: true, writeActive: false` with `Already_Used`, and a
2335
- payload built from the mapper alone goes out `active: true` tagged only `Imported` — the
2336
- redeemable-again defect, reintroduced one layer down.
2337
-
2338
- **Why it cannot be skipped:** the target starts its own usage count at zero and nothing seeds a
2339
- prior redemption, so a coupon used up between the read and the write lands fully redeemable and
2340
- stays that way until somebody notices.
2341
-
2342
- ⚠️ `write: false` has two distinct causes and they are different report lines, not one:
2343
- `voucher-usage-unknown-at-write` (the fresh read failed) and
2344
- `voucher-face-value-precision-unsupported`. Neither is a skip.
2345
-
2346
- ⚠️ **Precision is gated for EVERY monetary coupon, not just bearer vouchers**, and that gate is
2347
- in the mapper: `mapCouponToWix` returns `coupon-amount-precision-unsupported` and no
2348
- specification for any amount carrying more than two decimal places. A fixed-money coupon with a
2349
- usage limit of 6, or a percentage of `12.3456`, never reaches `classifyPreWriteDrift` at all —
2350
- it is not a bearer instrument — and would otherwise put an amount on the target that differs
2351
- from the source while every later check reported a match.
2352
-
2353
- **Report lines.** `coupons-spent-after-capture` and `coupons-partially-used-limit-reduced` are
2354
- sub-counts of `coupons-written` — those coupons ARE created, just not as the snapshot described
2355
- them — and must NOT be added to the accounting identity. The identity term is
2356
- `coupons-face-value-precision-unsupported`, because that coupon is refused:
2357
-
2358
- ```
2359
- discovered = written + type-unmappable + scope-unmappable
2360
- + usage-limit-unresolved + face-value-precision-unsupported + skipped
2361
- ```
2362
-
2363
- 2b. **Reconcile voucher drift at cutover, never inside the import.** A trading source keeps
2364
- spending vouchers after the import finishes. When the source carried at least one
2365
- usage-limited coupon, generated code **MUST** emit the reconciliation entrypoint — a separate,
2366
- re-runnable entrypoint the operator invokes at `rp-execute-import` workflow step 6b, not a
2367
- branch of the coupon pass. Emitting it is a requirement, not a choice; an entrypoint that is
2368
- never emitted is a step that is never skipped and never noticed.
2369
- `classifyPostImportDrift` + `buildVoucherDriftPlan` take the receipts, a fresh source read and
2370
- a target read-back and produce a read-only remedy plan (face value and affected orders per
2371
- code). The entrypoint BUILDS and reports that plan and **must not act on it** — closing a
2372
- voucher needs an explicit owner decision per code, including in one-click mode. The plan stays
2373
- `readOnly: true`; do not add an apply path.
2374
- ⚠️ `classifyPostImportDrift` returns exactly five post-import outcomes
2375
- (`voucher-drift-reconciled`, `voucher-drift-remedy-pending`, `voucher-source-row-missing`,
2376
- `voucher-target-row-missing`, `voucher-target-state-unreadable`). Unsupported face-value
2377
- precision is the boolean `verdict.facePrecisionUnsupported` on whichever of those applies —
2378
- wire the check on that flag, never as a `switch (verdict.outcome)` branch on
2379
- `voucher-face-value-precision-unsupported`, which is a PRE-WRITE outcome and would produce a
2380
- branch nothing reaches. Feeding a pre-write outcome to `buildVoucherDriftPlan` throws.
2381
- The remedy is `buildRemedyRequest`: a `PATCH` carrying ONLY
2382
- `{active: false, tags: [...]}` plus `fieldMask: {paths: ['active','tags']}`, which keeps the
2383
- coupon id, code, dates and amount. **Never build that body from the coupon you just read** —
2384
- the full specification alongside a narrow mask returns 200 and writes nothing, so counting 2xx
2385
- responses produces a false pass on money. `verifyRemedyApplied` decides the outcome from a
2386
- post-settle read-back, never from the status code. The entrypoint reports its result as the
2387
- completion report's `voucherReconciliation` value (`not-applicable | reconciled |
2388
- plan-pending-owner | remedied`); a run where it never executed records `not-run`.
2389
- 3. **Coupon usage**, inside the order pass: `buildCouponAppliedDiscounts({ couponLines:
2390
- order.coupon_lines, resolveCoupon })` and put `appliedDiscounts` on the mapped order body handed
2391
- to `importOrderHistory`. Name the surface (`rest-order-coupon-lines` on legacy storage,
2392
- `wc_order_coupon_lookup` on HPOS, `none-found`).
2393
- 4. **Plan definitions.** Row counts for `B1`/`B2`/`B3` → `evaluatePlanSources`; when it runs,
2394
- normalize each source offer to `{ sourceId, name, description, amount, period, interval,
2395
- lengthCycles, trialDays, signupFee }`, `buildPlanDefinition`, `ensurePlanDefinition`. Install
2396
- the Pricing Plans app first (`WIX_PRICING_PLANS_APP_DEF_ID`); it is absent on a commerce site.
2397
- 5. **Report** through `rp-execution-policy/lib/coupon-currency-plan-report.js`
2398
- `buildCouponCurrencyPlanReport({ counts, notPublishedByStatus, couponUsageSurface,
2399
- voucherLiabilityLive })`. Build
2400
- `counts` from ONE surface: `summarizeCoupons({ restCoupons, postsByStatus, mappings,
2401
- writeOutcomes })`, where `mappings` is the per-coupon `mapCouponToWix` result (it owns the
2402
- unmappable numerators — the type table alone under-counts) and `writeOutcomes` the per-coupon
2403
- `ensureCoupon` outcome (`coupon-reconciled-existing` folds into `coupons-written`; failed,
2404
- read-failed and ambiguous fold into `coupons-skipped` with reasons as notes). Plans fold the
2405
- same way through `tallyPlanWriteOutcomes`. Every Appendix D line, zeros included; `written +
2406
- unmappable + skipped = discovered` or the gate blocks; "imported N coupons" and "not supported"
2407
- are refused.
2408
-
2409
- ## Shipping options: delivery estimate, verified write, recovery
2410
-
2411
- The shipping stage runs after the source extract. After it has created the delivery regions, it
2412
- creates shipping options **only** through
2413
- `rp-target-wix/lib/shipping-option-stage.js` `runShippingOptionStage(wix, { projectDir,
2414
- regionIdByZone, sourceSystem, runId, phase, passMode })`. Do not hand-write this loop:
2415
-
2416
- - It refuses to run without the final `data/source-extract/shipping-zone.ndjson` and its
2417
- `shipping-zone.manifest.json`; a `.tmp` extract is still in progress.
2418
- - It writes `execution/shipping-estimates.json` (the per-option estimate resolution) **before**
2419
- any write. Every payload carries only `estimatedDeliveryTime` (never `estimateDeliveryTime`),
2420
- resolved as merchant decision (`source: "user"` only) > source text > the reviewed default in
2421
- the **destination** site's language > none. It never writes English on a non-English site.
2422
- - It uses `createShippingOptionVerified` (create, read back, one estimate-only PATCH when the
2423
- stored estimate is blank) and `recoverShippingOption` (read back, never re-send). It appends
2424
- exactly what `shippingOptionWriteRecord` returns. Hand-built shipping accounting is not allowed.
2425
- - Crosswalked options always take the read-only path, in every pass mode, and are never POSTed.
2426
- A 404 target raises `shipping-option-target-missing` and is never recreated automatically.
2427
- - It creates nothing in a region whose snapshot scan was incomplete (`SNAPSHOT_INCOMPLETE`) or
2428
- after an unknown outcome there (`REGION_SNAPSHOT_UNPROVABLE`). The next run rescans the region.
2429
-
2430
- The stage returns `fragment`. Fold it into the completion input with
2431
- `shipping-build.js` `mergeShippingEstimateCompletion(completionInput, fragment)` before
2432
- `createCompletionReport`. That replaces the previous run's shipping `operatorActions`, so a
2433
- resolved item drops out on the next run. Pass `passMode` from the run's recovery flag
2434
- (`unfiltered`, `missing-only`, `unresolved-only`, `failed-only`).
2435
-
2436
- ## Localhost media sources
2437
-
2438
- If the source profile shows `localhost`, `127.0.0.1`, or another private-only source URL,
2439
- generated media import code must not assume Wix can fetch those URLs. Wix Media import is
2440
- URL-based (`rp-target-wix` import-from-URL primitive), so live media import needs a public
2441
- URL reachable by Wix servers. This is optional and, as far as we know today, only affects
2442
- media import. Entity-native background ingestion from external URLs follows the same
2443
- reachability requirement: if the target API ingests a source URL itself, that URL must
2444
- still be publicly reachable by Wix.
2445
-
2446
- Codegen/runtime should support one of these explicit paths:
2447
-
2448
- - Use a public HTTPS tunnel/source URL for live media import. For ngrok on macOS:
2449
-
2450
- ```bash
2451
- brew install ngrok
2452
- ngrok config add-authtoken "<YOUR_AUTHTOKEN>"
2453
- ngrok http 8090
2454
- export WP_BASE_URL=https://<id>.ngrok-free.app
2455
- ```
2456
-
2457
- - If the source REST responses still contain local media URLs, generate a configurable
2458
- rewrite from the local base URL to the public tunnel base URL. For WordPress, use
2459
- `WP_MEDIA_URL_REWRITE_FROM` and `WP_MEDIA_URL_REWRITE_TO`; when those are blank, it is
2460
- acceptable to rewrite localhost/private origins to public `WP_BASE_URL`.
2461
- - Or generate/allow a media-skip/defer mode and document that media-dependent references
2462
- such as hero images, galleries, and downloadable files will be absent until media is
2463
- imported.
2464
-
2465
- Surface the selected path in `execution/review/import-plan.md` and the execution plan before any live
2466
- write. Do not let a dry-run with localhost media URLs imply live Wix Media import is ready.
2467
-
2468
- ## File targets
2469
-
2470
- Write code under the project-local source tree:
2471
-
2472
- - `migrations/<project>/execution/`
2473
- - `migrations/<project>/src/setup/`
2474
- - `migrations/<project>/src/extract/`
2475
- - `migrations/<project>/src/import/`
2476
- - `migrations/<project>/src/lib/` — the source adapter's transport module, plus the project's
2477
- own local-state/runtime files
2478
- - `migrations/<project>/src/resources/rp-target-wix/lib/` — **`wix-build.js` and its siblings
2479
- are NOT copied flat into `src/lib/`: `ndjson.js`, `wix-writers.js`, `wix-target-spec.js` and
2480
- `wix-build.js` all vendor NESTED here.** Vendor `rp-target-wix/lib/` here preserving its own relative path (mirroring this
2481
- skill's own `resources/rp-target-wix/lib/` layout exactly), so the project runs standalone
2482
- with no external deps and every relative `require` between these files — and every relative
2483
- `require` FROM a `src/lib/` file back into this directory (e.g. `write-verification.js`'s own
2484
- `../resources/rp-target-wix/lib/wix-build.js`) — resolves unchanged from how it resolves
2485
- in-repo. Do not flatten this vendoring; a flattened copy breaks any relative require that
2486
- assumes the nested layout.
2487
- - `migrations/<project>/src/extract/readers/`
2488
- - `migrations/<project>/src/import/transforms/` — **thin glue only.** Vendor and call
2489
- `wix-build.js`; do **not** re-derive the canonical→Wix payload layer here. Money objects, slug
2490
- sanitization, the empty `physicalProperties` trap, choice-by-name variant references, the
2491
- compare-at-must-exceed-price rule and the regular/sale price resolution are all settled in
2492
- `wix-target-spec.js` + `wix-build.js` and regression-locked by
2493
- `tests/mapping/wix-build-oracle-test.js` against payloads recorded from live-verified imports.
2494
- That layer is vendor-independent, so a per-project reimplementation only re-introduces bugs
2495
- that were already found and fixed. This directory should hold at most the mapping from THIS
2496
- project's reader field names onto the canonical vocabulary — and once the reader emits canonical
2497
- records directly, nothing at all.
2498
- - `migrations/<project>/src/import/write-specs/`
2499
- - `migrations/<project>/src/setup/run-setup.js` — setup execution entrypoint
2500
- - `migrations/<project>/src/extract/run-extract.js` — extraction entrypoint. It must accept
2501
- `--sample` (extract a small slice only); that is what the sample-preview gate runs.
2502
- - `migrations/<project>/src/import/run-import.js` — the runnable import entrypoint
2503
- (required; see "Runnable setup/extraction/import entrypoints" above). `--dry-run`
2504
- drives the safe-validation pass through the same import code path.
2505
- - `migrations/<project>/data/source-extract/` — extracted `<entity>.ndjson` record streams plus
2506
- a `manifest.json` (the manifest is a single document, so it stays JSON)
2507
- - `migrations/<project>/state/crosswalk/crosswalk.ndjson` — authoritative local
2508
- `sourceId -> targetId` crosswalk for native Wix entities
2509
- - `migrations/<project>/state/attempts/write-attempts.ndjson` — append-only write attempt
2510
- journal for resume/reconciliation. When a write consumes blocked-source data, include
2511
- `blockedDataSnapshot: {sourceEntityRef, snapshotVersion, extractedAt, checksum}` from the exact
2512
- immutable snapshot loaded for that write.
2513
- - `migrations/<project>/state/url-preservation/base-paths.json` — entity-level public
2514
- route intent from mapping `urlPolicy`
2515
- - `migrations/<project>/state/url-preservation/url-ledger.ndjson` — append-only URL
2516
- preservation upsert log, one latest row per `sourceStableKey + sourceRelativeUrl`
2517
- - `migrations/<project>/state/url-preservation/redirects.ndjson` — planned redirect rows
2518
- when source and target relative URLs are both known and differ
2519
- - `migrations/<project>/state/url-preservation/unresolved.ndjson` — public source URLs
2520
- waiting on target route configuration, target lookup, or manual review
2521
- - `migrations/<project>/state/url-preservation/app-page-routes.json` — configured or
2522
- unresolved per-family route results; saved-state read-back is distinct from served proof
2523
- - `migrations/<project>/state/url-preservation/link-audit.json` — atomic local census,
2524
- pending/unresolved destination queues, writer integration and post-frontend evidence
2525
- - `migrations/<project>/logs/` — audit/error logs emitted by the shared runtime
2526
- - `migrations/<project>/execution/review/import-plan.md`
2527
- - `migrations/<project>/execution/review/code-safety-review.md` when `SAFE_MODE=true` or
2528
- `DRY_RUN=true`
2529
- - `migrations/<project>/execution/recovery-log.json` — append-only standard recovery
2530
- actions emitted by every resumed, partial, missing-only, failed-only, or deferred-only
2531
- run
2532
-
2533
- For app routing and internal-link reconciliation, generate `internal-link-audit.js plan` input from the
2534
- captured inventory and **confirmed** source-to-target crosswalk, not guessed slugs.
2535
- Supply declared exact source/destination hosts, containing source pages and their target
2536
- record type, target record ID, content field and planned `targetContentRevision`, the current
2537
- route read-back revision, target record IDs, page families, expected paths computed from
2538
- read-back patterns and slugs, explicit `supportedQueryKeys` when the target adapter has
2539
- verified query behavior, and per-family samples. Missing target identity goes to the
2540
- unresolved queue without an expected path. Generate a route transport only for a family
2541
- listed with exact request/read-back evidence in `rp-target-wix` capability knowledge;
2542
- other families stay unverified. The adapter for the later `verify` step must expose the current complete active-writer
2543
- inventory and order, `readRecord(id)` returning `{revision, fields}`,
2544
- `writeRecord(id, changedFields, baseRevision)`, read-only destination/sample checks,
2545
- and `checkRenderedLink(occurrence, {repairedPath, deploymentId, signal})` that confirms the
2546
- stored path is rendered. For fields owned by the shared non-blog reconciler, declare
2547
- `writer: "internal-link-reconciler"`, `compatible: true` and
2548
- `runsAfter: "post-frontend-verification"` in `getActiveWriters()`; include every other active
2549
- writer and its fields in that complete inventory. Supply `checkFragment` where anchor checks are supported. The
2550
- shared verifier groups links by containing logical record and field, applies
2551
- `lib/structured-link-patch.js`, journals before writing, checks revisions and reads back;
2552
- do not expose a per-occurrence `repair` callback. Unknown content revisions stay pending.
2553
- An interrupted response after a write resumes from journaled read-back without another
2554
- mutation. Unsupported rich-content nodes remain pending rather than replacing an entire
2555
- description with a path. Do not generate a blog-post link writer here: blog-link repairs
2556
- belong to the dedicated blog-post writer and its checkpointed post-frontend update,
2557
- publish and read-back lifecycle. The adapter must expose `inspectDestination()` and return `isLive: false` from a
2558
- current destination check before any route or content write; unknown/live state fails
2559
- closed. An
2560
- unknown or incompatible source-origin or content writer keeps affected fields pending before any
2561
- mutation. These are generated contracts, not evidence that a backend-only run has served
2562
- routes or repaired content. If the audit is at `budget-hold`, a same-run re-plan retains
2563
- the hold, calibration and elapsed time. Resume only with `budgetDecision: {decisionRef,
2564
- maxLiveMs, maxExceptionTokens}` from an explicit recorded owner decision; then run `verify` again.
2565
-
2566
- Recommended machine-readable artifacts:
2567
-
2568
- - `migrations/<project>/execution/setup-plan.json`
2569
- - `migrations/<project>/execution/execution-manifest.json`
2570
- - `migrations/<project>/execution/llm-handoff.json`
2571
- - `migrations/<project>/execution/live-import-summary.json`
2572
- - `migrations/<project>/execution/completion-report.json`
2573
-
2574
- ## Generated write spec contract
2575
-
2576
- Generated writer code should be thin. It should primarily define deterministic per-entity
2577
- write specs, then pass Wix-shaped objects into the shared runtime.
2578
-
2579
- Each write spec should define, as applicable:
2580
-
2581
- - `entity`
2582
- - `mode`: `create | update | upsert | bulk_create | bulk_upsert`
2583
- - `create`
2584
- - `update`
2585
- - `lookup`
2586
- - `bulk`
2587
- - `batchSize`
2588
- - `concurrency`
2589
- - `retryPolicy`
2590
- - `throttlePolicy`
2591
- - `auditKeys`
2592
- - `crosswalkAuthority: "local"`
2593
- - `cmsMirror: "none" | "download" | "upload" | "download-and-upload"`
2594
- - `crosswalkStrategy`
2595
- - `reconciliationStrategy`
2596
- - `dependencyRefs`: derive from each in-scope entity's domain-knowledge `dependsOn`,
2597
- topologically sorted across the whole mapping plan — not re-derived from prose per project.
2598
- - `verificationLevel`: `verified | unverified`
2599
- - `safeMode.replacePaths[]`: resolved request-body paths copied from mapper
2600
- `safeModeReplacements[]`, with `{ kind: "email" | "phone", path }` entries. Paths use
2601
- the shared safe-mode grammar (`field`, `field[]`, `field.items[]`) and are relative to
2602
- the request body passed to the shared writer builder. The mapper is required to surface
2603
- these replacements in both machine artifacts and review markdown; codegen should treat
2604
- missing `safeModeReplacements[]` for an entity with outbound contact fields as a mapper
2605
- contract failure, not as a cue to infer or invent replacements silently.
2606
-
2607
- The transform layer should output Wix-shaped objects only. Request-envelope building and
2608
- retry/reporting logic belong in the shared runtime.
2609
-
2610
- When safe mode is enabled, generated import code must derive one `safeModeOptions` object
2611
- per source record and pass it to every Wix writer call. The options must include origin
2612
- `entityType`, deterministic origin `entityId`, `safeModePhoneNumber`, and the write spec's
2613
- resolved replacement paths. `entityId` must be the source ID or deterministic
2614
- `sourceStableKey`, never the target Wix ID. If no deterministic origin identity exists,
2615
- fail before writing.
2616
-
2617
- Generated code must not query Wix by original email or phone values while safe mode is
2618
- enabled. Any destination lookup keyed by contact data must use the same mock value that the
2619
- writer will send, with local crosswalk state remaining the primary idempotency authority.
2620
-
2621
- ## Execution artifact contract
2622
-
2623
- Codegen should emit machine-readable execution artifacts alongside runnable code.
2624
-
2625
- - `execution/setup-plan.json`: execution-ready rendering of setup work
2626
- - `execution/execution-manifest.json`: authoritative ordered task graph covering setup,
2627
- extraction, transform/write execution, artifact refs, checkpoint ids, and plan version
2628
- - `execution/llm-handoff.json`: whether unresolved codegen/execution decisions remain
2629
-
2630
- This manifest is the primary downstream contract for `rp-execute-setup` and
2631
- `rp-execute-import`.
2632
-
2633
- For every import task that handles public routed entities, include:
2634
-
2635
- ```json
2636
- {
2637
- "urlPreservation": {
2638
- "enabled": true,
2639
- "basePathsPath": "state/url-preservation/base-paths.json",
2640
- "ledgerPath": "state/url-preservation/url-ledger.ndjson",
2641
- "redirectsPath": "state/url-preservation/redirects.ndjson",
2642
- "unresolvedPath": "state/url-preservation/unresolved.ndjson",
2643
- "applyRedirects": false
2644
- }
2645
- }
2646
- ```
2647
-
2648
- Do not set `applyRedirects` to `true`; the current phase records redirect plans but does
2649
- not configure Wix redirects or site routes.
2650
-
2651
- ## Import completeness accounting contract
2652
-
2653
- Generated import runners must also produce `stageResiduals[]` rows — a per-stage failure
2654
- against records the entity row already counts as `imported`:
2655
-
2656
- ```
2657
- { entity, stage, outcome, count, sourceKeys[] }
2658
- ```
2659
-
2660
- The runner calls `collectOrderStageResiduals` on the final order outcomes as shown above.
2661
- It counts failed payment/refund/invoice stages (including target-read failures and ambiguous
2662
- payment reconciliation) against
2663
- orders whose own outcome is `order-imported` or `order-already-imported`. `outcome` must be a
2664
- member of the payments outcome registry; anything else throws. Any row with `count > 0` forces
2665
- the completion status to no better than `incomplete_with_failures` and names the affected
2666
- source keys as recovery work. An order report with omitted `stageResiduals` is refused; pass
2667
- the collected array even when it is empty. For source-specific entity names, retain
2668
- `targetEntityType: 'ecom/order'` in the completeness input or set `orderHistoryInScope: true`.
2669
-
2670
- This is an **orthogonal axis** and is never folded into `failed`: the order did import, and the
2671
- `imported + alreadyPresentByCrosswalk + deferred + failed == inScope` identity below exists to
2672
- protect that partition. Without these rows a shop with live unpaid orders on it reconciles
2673
- perfectly and reports clean.
2674
-
2675
- **This requirement is not order-specific.** Any post-create sub-step whose failure must not be
2676
- hidden behind the parent entity's own `imported`/`alreadyPresentByCrosswalk` count needs its own
2677
- tracked residual -- the order payment/refund/invoice sub-stages above are the first instance of
2678
- this, not the only one it applies to. Product category attachment
2679
- (`stores-category-attach.js`, see "Product category attachment: attach, then verify" above) is
2680
- the second: `collectCategoryAttachStageResiduals(categoryAttachReport)` produces
2681
- `{ entity: 'product', stage: 'category-attach', outcome, count, sourceKeys }` rows the same way
2682
- `collectOrderStageResiduals` does for orders, and they fold into the same shared
2683
- `completionInput.stageResiduals[]` accumulator, read by whichever stage owns the single final
2684
- `createCompletionReport` call. This is not hypothetical: a completion report can show `product: reconciled, mismatch:
2685
- false` and `product-category: reconciled, mismatch: false` while no products actually carry
2686
- their categories live, because nothing tracked the link step itself. A new entity gaining a
2687
- post-create write that can fail independently of the parent create should be assumed to need the
2688
- same treatment until shown otherwise.
2689
-
2690
- For each mapped entity class and meaningful subtype, the runner must produce an
2691
- `entityCompleteness` row with these counters:
2692
-
2693
- - `extracted`
2694
- - `inScope`
2695
- - `attempted`
2696
- - `imported`
2697
- - `alreadyPresentByCrosswalk`
2698
- - `deferred`
2699
- - `failed`
2700
- - `skippedOutOfScope`
2701
- - `unexpectedSkipped`
2702
- - `sourceKeys` -- every in-scope record's source key, required on every row. It is
2703
- the only way `attachAttemptReasons` can join this row's deferred/failed counts back to the
2704
- journal rows that name their reasons; a row with no `sourceKeys` can never carry a structured
2705
- (counted) reason, only free-text notes, and is rejected by `normalizeCompletenessRow` if it
2706
- tries to.
2707
-
2708
- Subtype is required whenever source records with the same entity map through materially
2709
- different write paths or deferral rules, for example simple vs subscription Stores
2710
- products. `deferred` means intentionally not attempted with a recorded reason. `failed`
2711
- means attempted and not successfully written. `unexpectedSkipped` is for in-scope mapped
2712
- records that were skipped outside an accepted out-of-scope rule.
2713
-
2714
- The generated runner must call or vendor `skills/wix-replatform/lib/completion-report.js` and
2715
- write both:
2716
-
2717
- - `execution/live-import-summary.json` during/after live import for machine inspection
2718
- - `execution/completion-report.json` as the authoritative final outcome artifact
2719
-
2720
- For each row, reconciliation is:
2721
-
2722
- ```text
2723
- imported + alreadyPresentByCrosswalk + deferred + failed == inScope
2724
- ```
2725
-
2726
- Any reconciliation mismatch, non-zero `failed`, non-zero `deferred`, or non-zero
2727
- `unexpectedSkipped` count must appear in the report headline and in the deterministic
2728
- human completion summary. Do not report a run as cleanly complete when source, import, and
2729
- crosswalk counts do not reconcile.
2730
-
2731
- **A record removed by an accepted out-of-scope rule is counted in `skippedOutOfScope` and
2732
- never in `inScope`**: `skippedOutOfScope` is never added to `reconciled` and never
2733
- subtracted from `expectedReconciled`, so `inScope + skippedOutOfScope <= extracted` must hold
2734
- for every row. For example, a source with 2 shipping zones where one is an empty catch-all
2735
- excluded by an accepted rule is `extracted 2, inScope 1, skippedOutOfScope 1, imported 1`, and
2736
- it reconciles. Counting the excluded zone inside `inScope` as well (`extracted 2, inScope 2,
2737
- skippedOutOfScope 1, imported 1`) breaks that identity and reports as a mismatch naming the
2738
- counting error, not a lost record — it is not a correct way to represent an exclusion.
2739
-
2740
- ### Every in-scope record leaves a durable row
2741
-
2742
- No generated importer may end a source record's life without a row in
2743
- `state/attempts/write-attempts.ndjson`, written through `localState.appendAttempt` (so it passes
2744
- the current `validateAttemptRow`, including `runId` and `phase`). A count with no rows behind it
2745
- is a defect, not a summary.
2746
-
2747
- | Rule | |
2748
- | --- | --- |
2749
- | Terminal row per in-scope record | Every in-scope record ends with at least one terminal row. A record the runner never attempts gets a `deferred` or `skipped_unexpected` row at the point of the decision, before the loop continues |
2750
- | Accepted skip | Excluded by an accepted source-data rule: `status: 'deferred'` |
2751
- | Unexpected skip | Skipped without an accepted rule: `status: 'skipped_unexpected'`, never `deferred`, so it is not counted, reported or recovered as an accepted deferral |
2752
- | Named reason | Both carry `reason`, a stable kebab-case code naming the rule or runner branch, never prose built from the record |
2753
- | Findings ride along | A row for an attempted write carries the write's `findings[]`, suspects and drops alike |
2754
- | Counts are backed by rows | Per `entityCompleteness` row, take each in-scope record's latest row in the current `runId` for that entity and subtype: `deferred` equals the number whose status is `deferred`, `unexpectedSkipped` the number whose status is `skipped_unexpected` |
2755
- | Reasons are backed by rows | A `deferred` or `failed` count with no stated reason is not an accounted-for shortfall. Call `attachAttemptReasons(entityCompleteness, journal.rows, { runIds: [runId] })` once per report, after folding in any member fragment and before `createCompletionReport`, so every row's `reasons` carries a per-code tally (`reason` for `deferred`, `failure.errorCode` for `failed_retryable`/`failed_terminal`) joined by that row's own `sourceKeys` -- never guessed, never renamed, never read from the journal more than once per report |
2756
- | No values | A row never carries a contact value: `sourceStableKey`, `field`, `reason` |
2757
-
2758
- `skipped_unexpected` stays outside the reconciliation identity, so a non-zero count still makes
2759
- the entity row mismatch; recovery treats it as missing (never sent), not as uncertain. Reporting
2760
- which registered customers never received a login is the member-side instance of this rule; do not
2761
- build it twice.
2762
-
2763
- `writeLiveImportSummary`'s coverage check compares `entityCompleteness`'s neighbor,
2764
- `execution/live-import-summary.json`, against these same journal rows. Omitting one of `imported`,
2765
- `deferred`, `failed`, or `attempted` from a single flush is fine — that bucket is simply not
2766
- compared for that entity, never treated as a mismatched `0`. `unexpectedSkipped` is the opposite:
2767
- always name it explicitly, even as `0`, once this entity can produce a `skipped_unexpected` row —
2768
- its absence defaults to a held `0` instead of "not compared", precisely so a silently-skipped
2769
- record cannot agree by omission. See "Use this caller shape" above for the exact flush.
2770
-
2771
- ## Verifying Wix APIs
2772
-
2773
- **The primary control is the Target write contract above: call `rp-target-wix`'s
2774
- verified primitives when they exist.** That adapter is where each stable shape is
2775
- verified-once (by a real call) and where a Wix surface change is fixed in one place.
2776
- When Wix has a native entity but `rp-target-wix` does not yet have a dedicated writer,
2777
- codegen must generate a Wix REST call for that native entity via the adapter's generic
2778
- direct REST helper, log the missing writer, and call the RePlatform notification hook.
2779
- Do **not** route to CMS merely because the writer is missing.
2780
-
2781
- - If a Wix tool surface such as Wix MCP is available, prefer it to locate the endpoint
2782
- and request/response shape. If the shape is common enough, add a dedicated primitive to
2783
- `resources/rp-target-wix/lib/wix-writers.js`; otherwise generate a project-local native
2784
- REST call through `sendDirectRest` and mark it `UNVERIFIED`.
2785
- - **Confirm the shape with a real call, not a doc example** — public examples have been
2786
- wrong (e.g. lowercase Ricos plugin enums that 400). Cover the new primitive in
2787
- `tests/target-wix/contract-test.js` so drift stays visible.
2788
- - **Fallback when no Wix tool surface is available:** rely on published Wix REST/SDK docs
2789
- and conservative names. Mark the generated native call `// UNVERIFIED:` until a real
2790
- call confirms it — never ship an unchecked Wix call to a user's live site without
2791
- surfacing it in the execution plan.
2792
-
2793
- ## Runtime policy
2794
-
2795
- Verify each Wix endpoint and field at codegen time. The `// UNVERIFIED:` marker is a
2796
- fallback only for environments where no direct verification aid is available, not a way
2797
- to ship unchecked calls that fail later on the user's live site. Anything unverified must
2798
- be surfaced explicitly in downstream artifacts before execution.
2799
-
2800
- ## Missing writer policy
2801
-
2802
- CMS fallback is for source concepts that do **not** have a suitable native Wix entity, or
2803
- for native entities explicitly rejected because they cannot preserve fidelity or would
2804
- cause unsafe side effects. CMS is **not** a fallback for a missing writer, and it is not a
2805
- special default for coupons just because coupon scoping/restrictions need mapping.
2806
-
2807
- When the mapping targets a native Wix entity and no dedicated `rp-target-wix` writer
2808
- exists:
2809
-
2810
- 1. Generate project-local code that calls the native Wix REST endpoint through
2811
- `sendDirectRest`.
2812
- 2. Add a clear log line before the first use of that generated REST path.
2813
- 3. Call `notifyMissingWriter({ sourceEntity, wixEntity, method, path, reason })`. The
2814
- current implementation may be a no-op; the generated code must still call it.
2815
- 4. Mark the path `UNVERIFIED` in `execution/review/import-plan.md` and the execution-plan report until a
2816
- live/sandbox call promotes it.
2817
- 5. Maintain the same idempotency rules as dedicated writers: crosswalk by source ID in
2818
- local state and never dedupe by slug.
2819
-
2820
- ## Codegen rules
2821
-
2822
- - **Call a `rp-target-wix` writer with the exact parameter shape it declares -- usually one
2823
- options object -- never extra positional arguments.** JS silently drops them, and a wrapping
2824
- `catch` then hides the resulting undefined-field write failure. Not hypothetical: a generated
2825
- call site once passed `bulkAddItemToCategories(wix, categoryIds, [productId])` against a
2826
- `(wix, payload)` signature, and every category-attach write on that site silently failed.
2827
- - **Generated code must not define its own test for whether an error is transient.** Call
2828
- `isTransientWixError` from the shared `rp-target-wix` writer module. A project-local test
2829
- written against the error's message text cannot work: the shared writer truncates its message
2830
- at 400 characters by design, and its format (`\`${method} ${url} → ${status}: …\``) is not
2831
- the WordPress reader's `HTTP <status>` format — so a regex written from memory of the source
2832
- client matches nothing a destination write ever produces, and the project ships looking
2833
- retry-protected while retrying nothing. Four generated importers each carried such a copy and
2834
- not one of them could ever fire.
2835
- - **Never generate a reader that works around a route the source adapter's transport refuses.**
2836
- An adapter transport may refuse a route because its response carries live credentials — a
2837
- payment-gateway configuration route is the case that exists today. When it does, the adapter
2838
- exports a purpose-built reader that returns the facts without the secret; generate a call to
2839
- that, and record what it reports. Do not hand-roll a request, do not vendor or reconstruct a
2840
- raw transport, and do not add a flag to the shared one. A generated reader that reaches the
2841
- route directly puts a live credential into the extract files, the import logs and the
2842
- transcript, where nothing downstream can take it back.
2843
- - Keep reader and writer responsibilities separate.
2844
- - Keep setup, extraction, and import responsibilities separate.
2845
- - Do not generate a read-all-into-memory importer for the general case. The reader extracts
2846
- to disk first; the importer consumes extracted files from disk.
2847
- - Make transforms deterministic and testable.
2848
- - Make generated write specs deterministic and declarative.
2849
- - Preserve **source IDs** for traceability, but do not assume native Wix target IDs can
2850
- be preserved or client-assigned.
2851
- - Generate a stable selection layer before executing writes. Use the shared
2852
- `import-recovery.js` runtime helpers where available, or vendor equivalent helpers into
2853
- the project. Selection must:
2854
- - apply `--entity` before entity write execution,
2855
- - apply `--source-type` against deterministic source subtype fields such as
2856
- WooCommerce `type: "subscription"`,
2857
- - exclude local-crosswalk hits in `--missing-only` mode and count them as
2858
- `alreadyPresent`,
2859
- - select latest failed attempt rows for `--failed-only`,
2860
- - select latest deferred/needs-verification attempt rows for `--deferred-only`,
2861
- - emit an execution selection summary before the first write.
2862
- - Native Wix creates must perform a local crosswalk lookup immediately before every create,
2863
- even after pre-selection. If a crosswalk row is found, skip idempotently and count the
2864
- record as already present instead of writing a duplicate.
2865
- - Every selective/resumed/partial run must append `execution/recovery-log.json` with a
2866
- standard recovery entry containing: recovery id, timestamp, selection filters, reason,
2867
- records selected, records attempted, imported, already present, failed, deferred,
2868
- crosswalk changes, summary changes, operator-visible outcome, and links to detailed logs.
2869
- Recovery artifacts are append-only; never overwrite or collapse prior recovery actions.
2870
- - Update `execution/live-import-summary.json` through a shared summary writer, not by
2871
- hand-editing counters in recovery-specific code. The recovery-log entry must include
2872
- the summary delta so final state can be reconstructed from standard execution artifacts.
2873
- - For public routed entities, consume mapping `urlPolicy` and initialize local URL
2874
- preservation state before writes. Generate deterministic helpers, or vendor the shared
2875
- `url-preservation-state.js` behavior, to:
2876
- - write `base-paths.json` from entity-level route policies
2877
- - derive `sourceRelativeUrl` from `sourceUrlField` or `sourceBasePath + sourceSlugField`
2878
- - preserve the original `sourceSlug` before any normalization
2879
- - record `plannedTargetSlug` before create/update
2880
- - record `actualTargetSlug` and `actualTargetRelativeUrl` only after the Wix response or
2881
- a safe target lookup proves them
2882
- - append replacement rows to `url-ledger.ndjson` instead of editing rows in place
2883
- - write `redirects.ndjson` only when both relative URLs are known and differ
2884
- - write `unresolved.ndjson` with `pending_target_route`, `target_url_missing`,
2885
- `source_url_missing`, or `manual_review` when a concrete preserved URL or redirect
2886
- cannot be derived safely
2887
- - never silently overwrite a source slug with a normalized slug
2888
- - For a source with an imported redirect collection, adapt only captured redirect rules
2889
- into `kind: imported_redirect` records through `url-redirects.js sources`, then run
2890
- `url-redirects.js import-targets` if source domains were declared after capture. The
2891
- planner uses stable source-system rule IDs, original source and scalar destination,
2892
- status, match type, scope, source context, explicit website identities, capture revision
2893
- and rule-specific capability evidence. Never derive a rule
2894
- ID from array position or target, infer website membership from a host suffix, or feed
2895
- content/menu/message fields into this planner. A partial capture must set
2896
- `fullCapture: false`; it cannot retire absent rules. Preserve external targets in the
2897
- captured source records and audit; only cutover may execute after its compatible consumer
2898
- and verification checks exist. A pending or refused rule must not be treated as imported.
2899
- - Imports stay plan-only for redirects: never generate code that calls the SEO Redirects
2900
- API. Record source-authored redirect definitions through
2901
- `skills/wix-replatform/scripts/url-redirects.js sources` (bridge reads of the redirect
2902
- plugins' tables by bare name, or an operator export) and finish with the offline
2903
- `url-redirects.js report`; application and public verification are post-build helper
2904
- operations scheduled by cutover.
2905
- - URL ledger resume must replay the latest valid `url-ledger.ndjson` row by
2906
- `sourceStableKey + sourceRelativeUrl`, derive expected rows from extracted source
2907
- records and `urlPolicy`, merge target IDs from the local crosswalk, and query Wix only
2908
- when the write spec declares the lookup safe.
2909
- - **For EVERY native Wix entity, generate and use the shared local-state runtime
2910
- (`local-state.js`) — there is no exception, and generated code must not create any other
2911
- durable per-entity `sourceId -> targetId` store.** Not a per-entity JSON map, not a
2912
- `<entity>-crosswalk.json`, not an in-project index of its own: one shared ledger at
2913
- `state/crosswalk/crosswalk.ndjson`, written through the shared write primitives, for products
2914
- and coupons and contacts and members exactly as for orders. A private per-entity file is
2915
- invisible to resume, to recovery, and to the rerun/catch-up selection, all of which read the
2916
- shared ledger and nothing else — so an entity remembered only in a private file is classified
2917
- on the next run as never imported, and gets created a second time on a site the merchant is
2918
- trading on. A run that keeps orders in the shared ledger and other entity types in private per-entity
2919
- files leaves the ledger unusable for catch-up.
2920
- Where the target *does* expose a client-controlled source-id field, write that field **in
2921
- addition to** the ledger row, never instead of it.
2922
- Load `state/crosswalk/crosswalk.ndjson` at startup, rebuild missing or
2923
- corrupt index caches from it, append a `started` row to
2924
- `state/attempts/write-attempts.ndjson` before each native write, write the confirmed
2925
- crosswalk row after success, then mark the attempt `imported`. **Slug-based dedupe does
2926
- NOT work** — Wix rewrites slugs; never rely on it.
2927
- - **Every confirmed crosswalk row carries the version pair from the write that confirmed it.**
2928
- Populate `sourceVersionField` + `sourceVersion` (from the source record's own modification
2929
- field) and `targetRevision` (exactly as the Wix response returned it — never incremented
2930
- locally) through `nextCrosswalkBaseline`, on every confirmed write. These fields are optional
2931
- in the schema only so rows written before they existed still load; absence is read as
2932
- `unknown`, never as "unchanged". A generated importer that omits them produces a ledger that
2933
- is complete for resume and useless for catch-up: a later rerun of a fully-imported project
2934
- degrades to asking a human about records nothing actually changed.
2935
- - **The CMS mirror is a post-execution snapshot pass, generated only when a mirror was
2936
- requested.** Gate it on `task.state.cmsMirror.mode` (`execution-state.js` →
2937
- `hasUploadMirror`): `upload` and `download-and-upload` get exactly one pass; `none` and
2938
- `download` get **nothing at all** — no collection reads, no writes, and no empty pass
2939
- reported as a success. Generate it as a distinct step that runs **after the last entity
2940
- write task**, reading the finished ledger:
2941
- 1. `loadCrosswalk(projectDir)` for the settled rows.
2942
- 2. `crosswalkRowsToMirrorItemInputs(rows)` from `local-state.js` — the one shared projection.
2943
- A ledger row is **not** a mirror-item input: the ledger says `sourceEntityType` /
2944
- `targetEntityType`, `buildImportCrosswalkItem` wants `entityType` / `targetType` and
2945
- throws without the first. Never re-derive this mapping in the generated project.
2946
- 3. `syncImportCrosswalk(wix, inputs, { collectionId })` from
2947
- `rp-target-wix/lib/wix-writers.js`, which builds each item through
2948
- `buildImportCrosswalkItem` and batches at 1000 through `bulkSaveDataItems`.
2949
- **Never compose a mirror item id in generated code.** The id is the row's own
2950
- `sourceStableKey`; adding a project or source-instance prefix is specifically forbidden (see
2951
- the KNOWN, UNRESOLVED LIMITATION comment on `buildImportCrosswalkItem` — the cross-project
2952
- collision it describes is real and stays open, and closing it needs an approved
2953
- identity/schema decision, not a prefix invented in a writer).
2954
- **Never write the mirror inline per entity or per batch checkpoint.** Riding a mirror write
2955
- alongside each record multiplies the calls the site absorbs during the run and turns a slow
2956
- or failing collection into a drag on the import it only describes; a compact post-run
2957
- reference copy means one snapshot of a settled ledger.
2958
- **Never let the mirror fail the import.** `syncImportCrosswalk` returns
2959
- `{ totalSuccesses, totalFailures, errors }` and does not throw on per-item failure. Record all
2960
- three in the execution artifacts; a non-zero `totalFailures` or a thrown transport error marks
2961
- the **mirror step** failed and is reported as a shortfall, while every imported record stays
2962
- imported and the run is not failed. Never make native-entity import correctness depend on the
2963
- mirror succeeding.
2964
- - When a transform consumes a fulfilled blocked-data request, carry its exact
2965
- `sourceEntityRef`, snapshot version, extraction timestamp, and checksum into every related
2966
- write-attempt row as `blockedDataSnapshot`. Do not substitute whatever snapshot is newest at
2967
- report time; the journal records the version actually used to build that payload.
2968
- - Only fetch CMS `ImportCrosswalk` rows as a pre-execution seed for existing-site,
2969
- legacy, recovery, or delta flows, and only when valid local crosswalk state does not
2970
- already exist. Dedupe downloaded mirror rows by `sourceStableKey` and newest valid
2971
- `updatedAt`. Never use CMS as the ordinary resume source after local state exists.
2972
- - **Conditional-entity gating must match the verification artifact exactly.** When an
2973
- entity's import is gated on a `setup/setup-verification.json` item (e.g. an unverified
2974
- native path that setup must promote), the generated lookup must key on the same field
2975
- the generated setup runner writes (requirement id vs step id — pick one and use it in
2976
- both). A mismatched key silently reports "not verified" (fails safe into deferral, but
2977
- would wrongly defer a passing entity).
2978
- - The optional CMS mirror collection name is **`ImportCrosswalk`**. If upstream artifacts
2979
- still say `MigrationRefs`, normalize them to `ImportCrosswalk` in the generated code and
2980
- note the normalization in `execution/review/import-plan.md`.
2981
- - **Attach every related entity, don't just create it.** Creating a tag/category is not the
2982
- same as linking it. Blog posts attach tags via `tagIds` (GUIDs) and categories via
2983
- `categoryIds` on the draft-post create — collect the resolved ids and pass them, or the
2984
- taxonomy exists on the site but `postCount` stays 0.
2985
- - **Taxonomy creates are not idempotent**: treat `409 ALREADY_EXISTS` as success
2986
- AND resolve the existing entity's id (e.g. `listBlogTags`) so it can still be attached —
2987
- don't drop it.
2988
- - **Blog rich text is chunked for you.** `convertHtmlToRichContent` transparently splits HTML
2989
- over the 30k Ricos cap and merges the node arrays; pass full HTML, don't pre-truncate
2990
- or skip large posts. This is the **blog** path only — a blog post's `richContent` really is a
2991
- Ricos document. Stores products must NOT use it: their HTML goes in `plainDescription` and Wix
2992
- converts it server-side (see the product-description rule above).
2993
- - **Blog post media: cover and inline images go through `blog-post-media.js`.** A non-zero
2994
- WordPress `featured_media` is the post's cover; every `imageData.image.src.url` in the
2995
- converted Ricos document is an inline image. Do not re-implement this, and do not parse
2996
- raw HTML for images. **Failure policy (human-approved):** a post whose cover or any inline
2997
- image does not reach `READY` is written nowhere — no draft, no publish, no crosswalk row —
2998
- and stays pending and recoverable; the other posts continue. Never import the text with a
2999
- missing cover or an inline image still pointing at the source site. The blog post build
3000
- runner owns draft creation, finalization, publication, read-back and recovery. Do not
3001
- emit an independent `createDraftPost`/`publishDraftPost` pair for blog posts. Prepare
3002
- the inputs and call it once after all post bodies and media decisions are known:
3003
-
3004
- ```js
3005
- // BEGIN blog-post-media
3006
- const blogMedia = require(require('node:path').join(toolkitRoot, 'resources/rp-target-wix/lib/blog-post-media.js'));
3007
- const blogStage = require(require('node:path').join(toolkitRoot, 'resources/rp-target-wix/lib/blog-post-build-stage.js'));
3008
- const { createBlogBuildClient } = require(require('node:path').join(toolkitRoot, 'resources/rp-target-wix/lib/blog-post-build-client.js'));
3009
- const { toDraftPostBody } = require(require('node:path').join(toolkitRoot, 'resources/rp-target-wix/lib/wix-writers.js'));
3010
- const pendingMedia = blogMedia.createPendingBlogMediaState({ projectDir });
3011
- const preparedPosts = [];
3012
- // fetchFeaturedMedia(id): GET wp/v2/media/{id} through the source client; return the record.
3013
- for (const post of posts) {
3014
- const key = blogMedia.postStableKey(post);
3015
- const existing = await crosswalkLookup(key); // a hit still needs ledger/Wix reconciliation
3016
- const richContent = await convertHtmlToRichContent(wix, post.contentHtml); // no mediaBySourceUrl
3017
- let media;
3018
- try {
3019
- media = existing
3020
- ? await blogMedia.existingPostMedia({ post, richContent, state: pendingMedia,
3021
- resolveFromCrosswalk: lookupReadyBlogMedia })
3022
- : await blogMedia.importAndConfirmPostMedia(wix, { post, richContent, state: pendingMedia, fetchFeaturedMedia });
3023
- } catch (error) {
3024
- if (error.code !== 'BLOG_LEGACY_MEDIA_UNRESOLVED') throw error;
3025
- media = { allReady: false, resumed: false, reason: error.message };
3026
- }
3027
- if (media.allReady) blogMedia.applyPostMedia(richContent, media); // rewrites the SAME document
3028
- const postFields = buildBlogPostFields(post); // mapped author/date, excerpt, taxonomy and SEO fields
3029
- const desiredPost = media.allReady
3030
- ? toDraftPostBody({ ...postFields, richContent, heroImageId: media.heroImageId }) : null;
3031
- preparedPosts.push({
3032
- sourceStableKey: key, id: post.id, date: postFields.firstPublishedDate || post.date,
3033
- slug: postFields.slug, hasPostLinks: hasBlogPostLinks(post, richContent),
3034
- forceSlugUpdate: Boolean(desiredPost && postFields.slug && !desiredPost.seoSlug),
3035
- mediaReady: media.allReady, mediaPendingReason: media.reason || null,
3036
- mediaResumed: media.resumed, existingCrosswalk: Boolean(existing),
3037
- desiredPost,
3038
- });
3039
- }
3040
- const { report: blogReport, pending: blogPending } = await blogStage.runBlogBuildStage({
3041
- projectDir, sourcePosts: posts, preparedPosts,
3042
- client: createBlogBuildClient(wix, { fetchPage, fetchImage }), pendingMedia, runId,
3043
- crosswalk: { get: async (key) => crosswalkLookup(key), idOwners: (id) => crosswalkOwners(id),
3044
- set: async (key, id) => updateBlogCrosswalkTarget(key, id),
3045
- recordVerified: async (sourcePost, id) => writeBlogCrosswalk(sourcePost, id) },
3046
- checks: registeredBlogChecks, transforms: registeredBlogTransforms });
3047
- // END blog-post-media
3048
- ```
3049
-
3050
- Bind `lookupReadyBlogMedia` to the local media crosswalk (return a `ready` row with
3051
- `fileId` and non-zero `sizeInBytes` for a cover), plus `crosswalkOwners`,
3052
- `writeBlogCrosswalk`, `updateBlogCrosswalkTarget`, `buildBlogPostFields`, `hasBlogPostLinks`,
3053
- `fetchPage`, `fetchImage`, and the
3054
- registered checks/transforms to the generated importer's actual helpers. Never leave the
3055
- illustrative helper names undefined in generated code. `fetchPage` must return status
3056
- and HTML, and registered page checks must stay within the fixed sample budget.
3057
- `hasBlogPostLinks` reads the source/converted post for links to another source blog post;
3058
- it must return a boolean for every post so preflight can count the possible link update.
3059
- Feed `blogReport.operatorActions` into the existing `createCompletionReport` input's
3060
- `operatorActions`, merging by `code` so the spot-check appears once. The normalizer
3061
- keeps its schema fields. Keep affected addresses and prompts in
3062
- `state/crosswalk/blog-import-report.json`; the completion row points there. A later
3063
- standalone ledger action calls `refreshBlogHandoff` to update the spot-check row.
3064
- `state/crosswalk/pending-blog-media.ndjson` owns per-medium outcomes; the blog build ledger
3065
- owns lifecycle holds, including legacy crosswalk hits without local READY evidence.
3066
- Use the stage's merged `blogPending` result for completion counts and operator rows;
3067
- never maintain a second pending list. A pending entry with
3068
- `awaitingWrite: true` has every image `READY` but no recorded post write; it is still pending.
3069
- `markResolved` and `closeIfCrosswalked` own the recovery-log entry and repair a missed one on
3070
- the next run, so always call `closeIfCrosswalked` on the crosswalk-hit path. Put
3071
- `blogPending.pendingMedia` in `execution/live-import-summary.json` beside imported and
3072
- already-present blog posts, and list `blogPending.pending` (post and per-medium reason) in
3073
- the operator report. A blog step with pending posts is not complete: count them as
3074
- deferred in the completion report. This is separate from the "Localhost media sources"
3075
- skip/defer mode, which is an operator choice, not a media failure.
3076
- - Use the shared runtime for batching, retries, throttling, checkpoints, and audit/report
3077
- emission rather than generating those mechanics ad hoc.
3078
- - Use the deterministic execution-state preparation contract before native writes. Do not
3079
- let generated import code query CMS directly for resume; CMS mirror rows must be copied
3080
- into local crosswalk state first.
3081
- - Do not hardcode secrets.
3082
-
3083
- ## Headless storefront codegen rules
3084
-
3085
- When the delivery mode includes a headless storefront (i.e. a `wix-headless` Astro project
3086
- is generated alongside the import), apply these rules in addition to the general codegen
3087
- rules above.
3088
-
3089
- **Cursor-paginated catalog queries (required).** Generated shop and category pages must
3090
- never call `queryProducts().limit(N).find()` once and stop. Wix Stores returns at most 100
3091
- items per page — a store with more than 100 products silently truncates the listing.
3092
- Always generate a cursor loop in the shared catalog helper and call it from every page
3093
- that lists products:
3094
-
3095
- ```ts
3096
- async function listAllProducts(): Promise<Product[]> {
3097
- const all: Product[] = [];
3098
- let result = await products.queryProducts().limit(100).find();
3099
- all.push(...result.items);
3100
- while (result.hasNext()) {
3101
- result = await result.next();
3102
- all.push(...result.items);
3103
- }
3104
- return all;
3105
- }
3106
- ```
3107
-
3108
- Apply the same pattern to category-filtered product queries (`productsInCategory`). This
3109
- is a data-correctness requirement, not a UX preference.
3110
-
3111
- **Product count display (required).** Every generated shop and category page must display
3112
- the total product count (e.g. `מציג X מוצרים` for RTL Hebrew sites, or equivalent).
3113
- The count is `products.length` after the cursor loop completes and must appear above the
3114
- product grid.
3115
-
3116
- **`client:load` for primary CTA islands (required).** Generated product detail pages must
3117
- mount add-to-cart and cart-view React islands with `client:load`, not `client:only`.
3118
- `client:only` omits the component from the server-rendered HTML entirely — it is
3119
- invisible to crawlers and does not render until after full React hydration.
3120
- `client:load` hydrates immediately on page load. `client:only` must never be the default
3121
- for a primary call-to-action.
3122
-
3123
- **URL redirect file for WooCommerce sources (required).** When the source platform is
3124
- WooCommerce and the delivery includes a storefront, generate `frontend/public/_redirects`
3125
- mapping old WordPress URL patterns to the new Astro routes. At minimum:
3126
-
3127
- ```
3128
- /product/:slug /products/:slug 301
3129
- /product-category/:slug /category/:slug 301
3130
- /shop/ /shop 301
3131
- /?p=* / 301
3132
- ```
3133
-
3134
- Also generate `frontend/src/pages/product/[slug].astro` (singular path) as a redirect
3135
- shim to `/products/[slug]` (plural path). This prevents 404s on all inbound links from
3136
- the old site. Derive the source URL pattern from the source profile captured during
3137
- discovery; `/product/` is the standard WooCommerce single-product path.
3138
-
3139
- The `_redirects` file and redirect shim are required migration outputs, not optional
3140
- polish.
3141
-
3142
- ## When codegen must halt to LLM
3143
-
3144
- Emit `execution/llm-handoff.json` with `needsLlm: true` when any of the following occur:
3145
-
3146
- - a native Wix path exists but only an unverified write surface is available
3147
- - setup artifacts are insufficient to generate deterministic setup execution
3148
- - transform semantics remain unresolved
3149
- - a multi-pass relationship strategy cannot be derived deterministically
3150
- - the runtime contract required from `rp-target-wix` is missing for the mapped target
3151
-
3152
- ## Output
3153
-
3154
- Summarize which files were generated, which entities they cover, and any remaining implementation gaps.
3155
-
3156
- ## Discovered sub-part outcome accounting
3157
-
3158
- For every planned registry sub-part, generated importers emit its own `entityCompleteness` row using the registry subtype and back every count with journal rows, as for whole entities. Keep the whole-entity row separate. Attribute dropped sub-part fields with `droppedFields[].subtype` so a missing shipping line, for example, makes that sub-part partial without misreporting the parent row. When an exclusion corresponds to a discovered scope decision, include its `censusId`; `writeCompletionReport` derives `operatorGateRecorded` from the exact approved reconciliation, never from the importer's claim. Record-level filters without a census decision keep their existing exclusion behavior.