@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,2831 +0,0 @@
1
- 'use strict';
2
-
3
- // Telemetry recorder for RePlatform migration runs.
4
- //
5
- // The recorder owns everything mechanical and error-prone: schema validation
6
- // (enums, subtype-per-event-type), timestamping, per-class event aggregation,
7
- // the run/attempt/session resume model, the mechanical privacy scrub, atomic
8
- // journal appends, and the well-formedness gate at finalize. The rp-telemetry
9
- // companion skill carries judgment only (what to record, which subtype,
10
- // observation-only prose) and always persists through this module — the signal
11
- // layer is never hand-written.
12
- //
13
- // Storage layout, per migration project:
14
- // telemetry/events.jsonl append-only journal of the
15
- // current (unfinalized) run
16
- // run-telemetry.json latest finalized signal doc
17
- // telemetry/runs/run-<attempt>-<run_id>.json archived finalized signal docs
18
- // telemetry/runs/run-<attempt>-<run_id>.jsonl archived raw journals
19
- //
20
- // The journal is the source of truth during a run; the finalized document is a
21
- // pure function of the journal (assembleDocument), so a crash between the
22
- // finalize append and the document write is recoverable on the next call.
23
-
24
- const fs = require('node:fs');
25
- const path = require('node:path');
26
- const os = require('node:os');
27
- const crypto = require('node:crypto');
28
- const { execFileSync } = require('node:child_process');
29
- const biSink = require('./bi-sink.js');
30
-
31
- // 1.1.0 — added `skills_commit` to the run-start record and rollup (additive).
32
- // 1.2.0 — BI sink: added `telemetry_health.bi_push_failures`, the
33
- // `bi_push_failed`/`bi_push_truncated` journal record types (additive;
34
- // readers per the external reader contract skip unknown record types).
35
- // 1.3.0 — added the `discovered_entity_types` dimension and its finalize
36
- // cross-check flags (`discovered_entity_types_null_after_discovery`,
37
- // `volume_row_missing:<type>`), so a discovered entity class silently
38
- // absent from `volumes` is mechanically visible (additive; local-only —
39
- // not a registered BI column).
40
- // 1.4.0 — BI backlog healing: `bi_push_ok` watermark + `bi_push_skipped`
41
- // journal record types, `telemetry_health.bi_push_skipped`, and a full
42
- // idempotent re-push whenever a session boundary (resume, identity
43
- // arrival) or an event push finds journal rows with no confirming
44
- // watermark. Before this, mid-run push failures were healed only by
45
- // finalize's full re-push — a run that halted and never finalized
46
- // stranded its tail in the local journal forever.
47
- // Additive; readers per the external
48
- // reader contract skip unknown record types.
49
- // 1.5.0 — `transcript_digest` record type — a deterministic parse
50
- // of the Claude Code session transcript (cost, turn counts, tool
51
- // failures, retry-loop signatures, hook errors, per-stage wall time),
52
- // computed by a script rather than self-reported. Carried by
53
- // `replay()`/`assembleDocument()` into `rollup.transcript_digest`, so
54
- // `finalize`'s existing BI push carries it too — no new BI call, no
55
- // new free text, no scrub work (every field is counts/versions/tool
56
- // names). Additive; readers per the external reader contract skip
57
- // unknown record types.
58
- const TELEMETRY_SCHEMA_VERSION = '1.5.0';
59
- const FREE_TEXT_MAX = 400;
60
- const SHAPES_KEPT_PER_CLASS = 3;
61
- const SHAPES_MAX_PER_CALL = 10;
62
- const EVIDENCE_REFS_MAX = 5;
63
- // A resume gap below this is treated as in-session noise, not a wait interval.
64
- const IMPLICIT_WAIT_MIN_MS = 60 * 1000;
65
- // Automatic resume of a stale, unfinalized journal (resumeIfStale). Every value here is an
66
- // unverified engineering setting — confirm at the first live runs, never report as measured.
67
- // The manual route script cannot know which sitting it belongs to, so it treats this much
68
- // journal silence as an execution boundary (a heuristic, not a detected session): one quiet
69
- // sitting of >= 15 minutes is counted as two sessions, and a real break shorter than this is
70
- // left to the agent's own `start`. Deliberately NOT IMPLICIT_WAIT_MIN_MS — stages routinely run
71
- // longer than a minute without a journal write.
72
- const ROUTE_RESUME_MIN_GAP_MS = 15 * 60 * 1000;
73
- // A session opened less than this long ago belongs to the same execution: the supervisor joins
74
- // it instead of opening another, and an explicit `start` joins an automatic one. Separate from
75
- // IMPLICIT_WAIT_MIN_MS even though the value matches.
76
- const SESSION_ADOPT_WINDOW_MS = 60 * 1000;
77
- // Transport cap on the automatic heal; at the cap the request is cancelled, not abandoned.
78
- const HEAL_DEADLINE_MS = 3000;
79
- // Journal-lock waits: automatic resume gives up (busy no-op), marker appends are skipped
80
- // (backlog stays for the next heal), explicit commands throw (non-gating in the CLI).
81
- const LOCK_WAIT_MS = 2000;
82
- const MARKER_LOCK_WAIT_MS = 1000;
83
- const COMMAND_LOCK_WAIT_MS = 5000;
84
- // A wait at least this long with no halt_needs_user event in its stage means the
85
- // capture lost *why* the run stalled — flagged at finalize, never silent.
86
- const WAIT_WITHOUT_HALT_FLAG_MS = 5 * 60 * 1000;
87
- // Metered durations may legitimately overlap slightly with elapsed time (clock granularity,
88
- // concurrent API calls inside one stage), so allow modest slack before calling it a contradiction.
89
- const OVER_ATTRIBUTION_TOLERANCE = 1.25;
90
- // Absolute slack on top of the ratio, so a very short stage is not flagged over a few hundred ms.
91
- const OVER_ATTRIBUTION_FLOOR_MS = 5 * 1000;
92
- const EVIDENCE_SIZE_FLAG_BYTES = 2 * 1024 * 1024;
93
-
94
- const STAGES = [
95
- 'config', 'discovery', 'mcp_gate', 'mapping', 'mapping_review', 'setup_discovery',
96
- 'codegen', 'approval_gate', 'setup_provisioning', 'storefront_build', 'extract',
97
- 'import', 'finish',
98
- ];
99
- const STAGE_OUTCOMES = ['passed', 'halted', 'failed', 'skipped'];
100
- const TERMINAL_STATES = ['completed', 'halted_needs_user', 'failed', 'abandoned_by_user'];
101
- const SEVERITIES = ['blocking', 'degraded', 'cosmetic', 'info'];
102
- const SOURCE_PLATFORMS = ['wordpress', 'woocommerce', 'shopify', 'csv', 'other'];
103
- const DELIVERY_MODES = ['management', 'website', 'management_and_website'];
104
- const DESTINATION_STRATEGIES = ['new_site', 'existing_site'];
105
- const OPERATOR_ACCEPTANCE = ['accepted', 'rework_needed', 'rejected', 'unknown'];
106
- const VOLUME_TARGETS = ['native', 'cms', 'none'];
107
- const VERIFICATION_METHODS = ['query_back', 'route_check', 'manual_inspection'];
108
-
109
- const EVENT_SUBTYPES = {
110
- halt_needs_user: ['missing_input', 'manual_only', 'systemic_failure'],
111
- manual_action_required: ['plan_or_billing', 'dashboard_only', 'external_dependency', 'other'],
112
- error: null,
113
- fidelity_loss: ['dropped_field', 'unverified_enum', 'no_target', 'coerced_value'],
114
- api_gap: ['missing_api', 'missing_capability', 'internal_only', 'other'],
115
- skill_coverage_gap: ['guessed_value', 'undocumented_workaround', 'ambiguous_instruction', 'path_not_covered'],
116
- user_decision: ['accepted', 'declined', 'deferred', 'amended'],
117
- pipeline_defect: ['state_inconsistency', 'ordering_violation', 'record_defect', 'other'],
118
- };
119
- const EVENT_TYPES = Object.keys(EVENT_SUBTYPES);
120
-
121
- // Privacy tiers travel with the record so no sink can ignore them. Floors, not
122
- // defaults: identifying:* fields are never transmitted raw without the
123
- // pseudonymization floor or the locus-appropriate authority.
124
- const FIELD_TIERS = {
125
- 'rollup.source_site_url': 'identifying:client',
126
- 'rollup.wix_user_id': 'identifying:operator',
127
- '*': 'behavioral',
128
- };
129
-
130
- const OUTCOME_FOR_TERMINAL = {
131
- completed: 'passed',
132
- failed: 'failed',
133
- halted_needs_user: 'halted',
134
- abandoned_by_user: 'halted',
135
- };
136
-
137
- // Last-line-of-defense scrub over free text, shape field names, and locators.
138
- // Order matters: broader assignment/JWT shapes before generic opaque strings.
139
- const SCRUB_PATTERNS = [
140
- ['secret_assignment', /\b(?:token|secret|password|passwd|api[_-]?key|authorization|bearer)\b\s*[:=]\s*\S+/gi],
141
- ['jwt', /\beyJ[A-Za-z0-9_-]{8,}(?:\.[A-Za-z0-9_-]{4,}){1,4}\b/g],
142
- ['url', /\b(?:https?|ftp):\/\/\S+/gi],
143
- ['email', /[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g],
144
- ['opaque', /(?=[A-Za-z0-9+/_-]*[0-9])[A-Za-z0-9+/_-]{32,}={0,2}/g],
145
- ];
146
-
147
- class ValidationError extends Error {
148
- constructor(errors, hint) {
149
- super(Array.isArray(errors) ? errors.join('; ') : String(errors));
150
- this.name = 'ValidationError';
151
- this.errors = Array.isArray(errors) ? errors : [String(errors)];
152
- this.hint = hint || null;
153
- }
154
- }
155
-
156
- function nowIso(now) {
157
- if (now instanceof Date) return now.toISOString();
158
- if (typeof now === 'string') return new Date(now).toISOString();
159
- return new Date().toISOString();
160
- }
161
-
162
- function tsMs(iso) {
163
- return Date.parse(iso);
164
- }
165
-
166
- function telemetryDir(projectDir) {
167
- return path.join(projectDir, 'telemetry');
168
- }
169
-
170
- function journalPath(projectDir) {
171
- return path.join(telemetryDir(projectDir), 'events.jsonl');
172
- }
173
-
174
- function runsDir(projectDir) {
175
- return path.join(telemetryDir(projectDir), 'runs');
176
- }
177
-
178
- function signalPath(projectDir) {
179
- return path.join(projectDir, 'run-telemetry.json');
180
- }
181
-
182
- function scrubText(value) {
183
- let hits = 0;
184
- let text = String(value);
185
- for (const [name, re] of SCRUB_PATTERNS) {
186
- text = text.replace(re, () => {
187
- hits += 1;
188
- return `[scrubbed:${name}]`;
189
- });
190
- }
191
- return { text, hits };
192
- }
193
-
194
- function normalizeToken(value) {
195
- return String(value)
196
- .toLowerCase()
197
- .trim()
198
- .replace(/[^a-z0-9]+/g, '_')
199
- .replace(/^_+|_+$/g, '');
200
- }
201
-
202
- function stableStringify(value) {
203
- if (Array.isArray(value)) {
204
- return `[${value.map(stableStringify).join(',')}]`;
205
- }
206
- if (value && typeof value === 'object') {
207
- const keys = Object.keys(value).sort();
208
- return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(',')}}`;
209
- }
210
- return JSON.stringify(value);
211
- }
212
-
213
- function checkEnum(errors, field, value, allowed) {
214
- if (!allowed.includes(value)) {
215
- errors.push(`${field} must be one of: ${allowed.join(', ')} (got: ${JSON.stringify(value)})`);
216
- return false;
217
- }
218
- return true;
219
- }
220
-
221
- function checkBoundedText(errors, field, value, { required = false, max = FREE_TEXT_MAX } = {}) {
222
- if (value === undefined || value === null) {
223
- if (required) errors.push(`${field} is required (a sentence or two, observation-only)`);
224
- return null;
225
- }
226
- if (typeof value !== 'string' || value.trim() === '') {
227
- errors.push(`${field} must be a non-empty string`);
228
- return null;
229
- }
230
- if (value.length > max) {
231
- errors.push(`${field} exceeds ${max} chars (got ${value.length}) — keep it to a sentence or two; the coded fields carry the structure`);
232
- return null;
233
- }
234
- return value.trim();
235
- }
236
-
237
- function checkApiSurface(errors, field, value) {
238
- if (value === undefined || value === null) return null;
239
- const str = String(value);
240
- if (/:\/\//.test(str) || /[?\s]/.test(str)) {
241
- errors.push(`${field} must be an API/endpoint class like "stores/v3" or "wp/v2/posts", never a URL (URLs can embed auth)`);
242
- return null;
243
- }
244
- if (str.length > 64 || !/^[A-Za-z0-9/_.:-]+$/.test(str)) {
245
- errors.push(`${field} must be a short endpoint class matching [A-Za-z0-9/_.:-], max 64 chars`);
246
- return null;
247
- }
248
- return str;
249
- }
250
-
251
- function checkOpaqueId(errors, field, value, { max = 64 } = {}) {
252
- if (value === undefined || value === null) return null;
253
- const str = String(value);
254
- if (/[\s]/.test(str) || /:\/\//.test(str) || str.length > max) {
255
- errors.push(`${field} must be an opaque identifier (no whitespace, no URL), max ${max} chars`);
256
- return null;
257
- }
258
- return str;
259
- }
260
-
261
- function checkCount(errors, field, value, { defaultValue = 0, min = 0 } = {}) {
262
- if (value === undefined || value === null) return defaultValue;
263
- if (!Number.isInteger(value) || value < min) {
264
- errors.push(`${field} must be an integer >= ${min}`);
265
- return defaultValue;
266
- }
267
- return value;
268
- }
269
-
270
- function normalizeOrigin(errors, value) {
271
- try {
272
- const url = new URL(String(value));
273
- if (url.protocol !== 'http:' && url.protocol !== 'https:') {
274
- errors.push('source_site_url must be an http(s) URL');
275
- return null;
276
- }
277
- // Bare origin only — drops path, query, and any embedded credentials.
278
- return `${url.protocol}//${url.host}`;
279
- } catch {
280
- errors.push(`source_site_url is not a parseable URL: ${JSON.stringify(String(value))}`);
281
- return null;
282
- }
283
- }
284
-
285
- function sanitizeShapeValue(value, errors, keyPath, depth, counter) {
286
- if (value === null || typeof value === 'number' || typeof value === 'boolean') {
287
- return value;
288
- }
289
- if (typeof value === 'string') {
290
- if (value.length > 64) {
291
- errors.push(`observed_shapes${keyPath} string value exceeds 64 chars — shapes carry field names and types, never record values`);
292
- return null;
293
- }
294
- const { text, hits } = scrubText(value);
295
- counter.hits += hits;
296
- return text;
297
- }
298
- if (depth >= 4) {
299
- errors.push(`observed_shapes${keyPath} nests deeper than 4 levels`);
300
- return null;
301
- }
302
- if (Array.isArray(value)) {
303
- return value.map((item, i) => sanitizeShapeValue(item, errors, `${keyPath}[${i}]`, depth + 1, counter));
304
- }
305
- if (typeof value === 'object') {
306
- const out = {};
307
- for (const [key, item] of Object.entries(value)) {
308
- // Custom field names on a client site can themselves be identifying.
309
- const { text, hits } = scrubText(key);
310
- counter.hits += hits;
311
- out[text] = sanitizeShapeValue(item, errors, `${keyPath}.${key}`, depth + 1, counter);
312
- }
313
- return out;
314
- }
315
- errors.push(`observed_shapes${keyPath} has unsupported value type ${typeof value}`);
316
- return null;
317
- }
318
-
319
- function validateEvidenceRefs(errors, value) {
320
- if (value === undefined || value === null) return null;
321
- if (!Array.isArray(value) || value.length === 0) {
322
- errors.push('evidence_refs must be a non-empty array of {artifact, locator?} or omitted');
323
- return null;
324
- }
325
- if (value.length > EVIDENCE_REFS_MAX) {
326
- errors.push(`evidence_refs is capped at ${EVIDENCE_REFS_MAX} sample refs per event`);
327
- return null;
328
- }
329
- const refs = [];
330
- for (const [i, ref] of value.entries()) {
331
- if (!ref || typeof ref !== 'object' || typeof ref.artifact !== 'string') {
332
- errors.push(`evidence_refs[${i}] must be {artifact: <project-relative path>, locator?: <heading/anchor>}`);
333
- continue;
334
- }
335
- const artifact = ref.artifact.replace(/\\/g, '/');
336
- if (path.isAbsolute(artifact) || artifact.split('/').includes('..')) {
337
- errors.push(`evidence_refs[${i}].artifact must be a project-root-relative path (no absolute paths, no ..)`);
338
- continue;
339
- }
340
- if (/(^|\/)config\//.test(artifact) || artifact.endsWith('.env')) {
341
- errors.push(`evidence_refs[${i}].artifact must never point at secret-bearing config files`);
342
- continue;
343
- }
344
- let locator = null;
345
- if (ref.locator !== undefined && ref.locator !== null) {
346
- if (typeof ref.locator !== 'string' || ref.locator.length > 120) {
347
- errors.push(`evidence_refs[${i}].locator must be a string of at most 120 chars (prefer a section heading — line ranges break on regeneration)`);
348
- continue;
349
- }
350
- locator = scrubText(ref.locator).text;
351
- }
352
- refs.push({ artifact, locator });
353
- }
354
- return refs;
355
- }
356
-
357
- // --- dimension (rollup identity) fields -------------------------------------
358
-
359
- // --- cost/latency metering -------------------------------------------------
360
- // Duration fields split `active_ms` into its three real components. Before this existed,
361
- // active_ms was a single wall-clock number fusing agent reasoning, subprocess execution, remote
362
- // API latency and defect-repair time — which made it impossible to localize a bottleneck.
363
- //
364
- // Anything NOT attributed by a meter call stays visible as `unattributed_ms`. That is deliberate:
365
- // a partially-metered stage must not look fully explained.
366
- const METER_DURATION_KEYS = ['model_ms', 'api_ms', 'script_ms'];
367
- const METER_COUNT_KEYS = ['input_tokens', 'output_tokens', 'cache_read_tokens', 'cache_write_tokens', 'api_calls', 'api_retries'];
368
- const METER_ALLOWED_KEYS = new Set([...METER_DURATION_KEYS, ...METER_COUNT_KEYS, 'stage']);
369
-
370
- function validateMeter(input, { openStage, lastStage }) {
371
- const errors = [];
372
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
373
- throw new ValidationError(['meter payload must be a JSON object']);
374
- }
375
- for (const key of Object.keys(input)) {
376
- if (!METER_ALLOWED_KEYS.has(key)) {
377
- errors.push(`unknown meter field: ${key} (allowed: ${[...METER_ALLOWED_KEYS].join(', ')})`);
378
- }
379
- }
380
- const stageName = input.stage || openStage || lastStage;
381
- if (!stageName) errors.push('no stage is open and none was named; pass --stage <stage>');
382
- else checkEnum(errors, 'stage', stageName, STAGES);
383
-
384
- const out = { stage: stageName };
385
- let any = false;
386
- for (const key of [...METER_DURATION_KEYS, ...METER_COUNT_KEYS]) {
387
- if (input[key] === undefined || input[key] === null) continue;
388
- const value = input[key];
389
- if (!Number.isFinite(value) || value < 0 || Math.floor(value) !== value) {
390
- errors.push(`${key} must be a non-negative integer`);
391
- continue;
392
- }
393
- out[key] = value;
394
- any = true;
395
- }
396
- if (!any) errors.push(`meter needs at least one measurement (${[...METER_DURATION_KEYS, ...METER_COUNT_KEYS].join(', ')})`);
397
- if (errors.length > 0) {
398
- throw new ValidationError(errors, 'meter records measured numbers only — never estimate them by hand');
399
- }
400
- return out;
401
- }
402
-
403
- // --- source key/value reads ----------------------------------------------
404
- //
405
- // Per-run accounting for the ONE capability that widened what this pipeline can read out of a
406
- // source site: key-scoped access to key/value ("EAV") tables. Four counts and a table list.
407
- //
408
- // KEY NAMES ARE NOT TELEMETRY. A run's own artifacts hold those; what is worth aggregating
409
- // ACROSS runs is `redacted_values` -- a non-zero total means some site somewhere keeps a
410
- // credential where nobody expected one, and that is a fleet-level signal no single run can
411
- // see. Table names are bare WordPress table names (`postmeta`), which are structural, not
412
- // customer data. Anything finer-grained stays local.
413
- const SOURCE_READ_COUNT_KEYS = ['discovery_queries', 'scoped_queries', 'tier_refusals', 'redacted_values'];
414
- const SOURCE_READ_ALLOWED_KEYS = new Set([...SOURCE_READ_COUNT_KEYS, 'discovery_tables', 'scoped_tables']);
415
- const SOURCE_READ_TABLE_KEYS = ['discovery_tables', 'scoped_tables'];
416
- const SOURCE_READ_MAX_TABLES = 50;
417
-
418
- function validateSourceRead(input) {
419
- const errors = [];
420
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
421
- throw new ValidationError(['source-read payload must be a JSON object']);
422
- }
423
- for (const key of Object.keys(input)) {
424
- if (!SOURCE_READ_ALLOWED_KEYS.has(key)) {
425
- errors.push(`unknown source-read field: ${key} (allowed: ${[...SOURCE_READ_ALLOWED_KEYS].join(', ')})`);
426
- }
427
- }
428
- const out = {};
429
- let any = false;
430
- for (const key of SOURCE_READ_COUNT_KEYS) {
431
- if (input[key] === undefined || input[key] === null) continue;
432
- const value = input[key];
433
- if (!Number.isFinite(value) || value < 0 || Math.floor(value) !== value) {
434
- errors.push(`${key} must be a non-negative integer`);
435
- continue;
436
- }
437
- out[key] = value;
438
- any = true;
439
- }
440
- for (const key of SOURCE_READ_TABLE_KEYS) {
441
- if (input[key] === undefined || input[key] === null) continue;
442
- const value = input[key];
443
- if (!Array.isArray(value) || value.some((t) => typeof t !== 'string' || !/^[A-Za-z0-9_]+$/.test(t))) {
444
- errors.push(`${key} must be an array of bare table names (^[A-Za-z0-9_]+$)`);
445
- continue;
446
- }
447
- if (value.length > SOURCE_READ_MAX_TABLES) {
448
- errors.push(`${key} lists ${value.length} tables, more than the ${SOURCE_READ_MAX_TABLES} this field carries`);
449
- continue;
450
- }
451
- out[key] = [...new Set(value)];
452
- any = true;
453
- }
454
- if (!any) errors.push(`source-read needs at least one measurement (${[...SOURCE_READ_ALLOWED_KEYS].join(', ')})`);
455
- if (errors.length > 0) {
456
- throw new ValidationError(errors, 'source-read records counted reads only — never key names, and never a value');
457
- }
458
- return out;
459
- }
460
-
461
- // A pricing snapshot makes historical cost recomputable when list prices change. Rates are per
462
- // MILLION tokens, matching how model pricing is published.
463
- function validatePricingSnapshot(errors, value) {
464
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
465
- errors.push('model_pricing_snapshot must be an object of { <model>: { input_per_mtok, output_per_mtok, ... } }');
466
- return undefined;
467
- }
468
- const out = {};
469
- const rateKeys = ['input_per_mtok', 'output_per_mtok', 'cache_read_per_mtok', 'cache_write_per_mtok'];
470
- for (const [model, rates] of Object.entries(value)) {
471
- if (typeof model !== 'string' || model.length > 64) {
472
- errors.push('model_pricing_snapshot keys must be short model ids');
473
- continue;
474
- }
475
- if (!rates || typeof rates !== 'object' || Array.isArray(rates)) {
476
- errors.push(`model_pricing_snapshot.${model} must be an object of numeric rates`);
477
- continue;
478
- }
479
- const entry = {};
480
- for (const [key, rate] of Object.entries(rates)) {
481
- if (!rateKeys.includes(key)) {
482
- errors.push(`model_pricing_snapshot.${model}.${key} is not a known rate (allowed: ${rateKeys.join(', ')})`);
483
- continue;
484
- }
485
- if (!Number.isFinite(rate) || rate < 0) {
486
- errors.push(`model_pricing_snapshot.${model}.${key} must be a non-negative number`);
487
- continue;
488
- }
489
- entry[key] = rate;
490
- }
491
- out[model] = entry;
492
- }
493
- return out;
494
- }
495
-
496
- const DIM_VALIDATORS = {
497
- model_pricing_snapshot: (errors, v) => validatePricingSnapshot(errors, v),
498
- source_platform: (errors, v) => (checkEnum(errors, 'source_platform', v, SOURCE_PLATFORMS) ? v : undefined),
499
- source_platform_version: (errors, v) => {
500
- if (typeof v !== 'string' || v.length > 32) {
501
- errors.push('source_platform_version must be a short version string');
502
- return undefined;
503
- }
504
- return v;
505
- },
506
- source_site_url: (errors, v) => {
507
- const origin = normalizeOrigin(errors, v);
508
- return origin === null ? undefined : origin;
509
- },
510
- source_extensions: (errors, v) => {
511
- if (!Array.isArray(v) || v.length > 64 || v.some((item) => typeof item !== 'string' || item.length > 48)) {
512
- errors.push('source_extensions must be an array of short extension/plugin class names');
513
- return undefined;
514
- }
515
- return [...new Set(v.map(normalizeToken).filter(Boolean))];
516
- },
517
- discovered_entity_types: (errors, v) => {
518
- if (!Array.isArray(v) || v.length > 64 || v.some((item) => typeof item !== 'string' || item.length > 48)) {
519
- errors.push('discovered_entity_types must be an array of short entity class tokens (e.g. product, post, event)');
520
- return undefined;
521
- }
522
- return [...new Set(v.map(normalizeToken).filter(Boolean))];
523
- },
524
- source_acquisition: (errors, v) => {
525
- const token = normalizeToken(v);
526
- if (!token || token.length > 32) {
527
- errors.push('source_acquisition must be a short class token, e.g. admin_api | public_storefront | file_export');
528
- return undefined;
529
- }
530
- return token;
531
- },
532
- delivery_mode: (errors, v) => (checkEnum(errors, 'delivery_mode', v, DELIVERY_MODES) ? v : undefined),
533
- destination_strategy: (errors, v) => (checkEnum(errors, 'destination_strategy', v, DESTINATION_STRATEGIES) ? v : undefined),
534
- site_id: (errors, v) => {
535
- const id = checkOpaqueId(errors, 'site_id', v);
536
- return id === null ? undefined : id;
537
- },
538
- wix_user_id: (errors, v) => {
539
- const id = checkOpaqueId(errors, 'wix_user_id', v);
540
- return id === null ? undefined : id;
541
- },
542
- skills_version: (errors, v) => {
543
- if (typeof v !== 'string' || v.length > 32) {
544
- errors.push('skills_version must be a short version string');
545
- return undefined;
546
- }
547
- return v.trim();
548
- },
549
- skills_commit: (errors, v) => {
550
- if (typeof v !== 'string' || !/^[0-9a-f]{7,40}$/.test(v.trim())) {
551
- errors.push('skills_commit must be a git commit SHA (7-40 hex chars)');
552
- return undefined;
553
- }
554
- return v.trim();
555
- },
556
- runtime_env: (errors, v) => {
557
- if (!v || typeof v !== 'object' || Array.isArray(v)) {
558
- errors.push('runtime_env must be an object with {os?, node_version?, agent_runtime?, model?}');
559
- return undefined;
560
- }
561
- const out = {};
562
- for (const key of ['os', 'node_version', 'agent_runtime', 'model']) {
563
- if (v[key] === undefined || v[key] === null) continue;
564
- if (typeof v[key] !== 'string' || v[key].length > 64 || /[/\\]/.test(v[key])) {
565
- errors.push(`runtime_env.${key} must be a coarse platform fact (short string, no paths)`);
566
- continue;
567
- }
568
- out[key] = v[key];
569
- }
570
- const unknown = Object.keys(v).filter((k) => !['os', 'node_version', 'agent_runtime', 'model'].includes(k));
571
- if (unknown.length > 0) {
572
- errors.push(`runtime_env has unknown keys: ${unknown.join(', ')}`);
573
- return undefined;
574
- }
575
- return out;
576
- },
577
- };
578
-
579
- function validateDims(input) {
580
- if (input === undefined || input === null) return {};
581
- if (typeof input !== 'object' || Array.isArray(input)) {
582
- throw new ValidationError(['dimensions must be a JSON object']);
583
- }
584
- const errors = [];
585
- const dims = {};
586
- for (const [key, value] of Object.entries(input)) {
587
- if (value === undefined || value === null) continue;
588
- const validator = DIM_VALIDATORS[key];
589
- if (!validator) {
590
- errors.push(`unknown dimension field: ${key} (allowed: ${Object.keys(DIM_VALIDATORS).join(', ')})`);
591
- continue;
592
- }
593
- const normalized = validator(errors, value);
594
- if (normalized !== undefined) dims[key] = normalized;
595
- }
596
- if (errors.length > 0) throw new ValidationError(errors);
597
- return dims;
598
- }
599
-
600
- // --- event validation --------------------------------------------------------
601
-
602
- const EVENT_ALLOWED_KEYS = new Set([
603
- 'event_type', 'subtype', 'stage', 'skill', 'entity_type', 'severity',
604
- 'wix_api_surface', 'source_api_surface', 'wix_app_id', 'error_code',
605
- 'what_happened', 'expected', 'actual', 'observed_shapes', 'evidence_refs',
606
- 'count', 'retry_count', 'recovered', 'decision_point',
607
- ]);
608
-
609
- function validateEvent(input, context) {
610
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
611
- throw new ValidationError(['event must be a JSON object']);
612
- }
613
- const errors = [];
614
- const scrub = { hits: 0 };
615
-
616
- for (const key of Object.keys(input)) {
617
- if (!EVENT_ALLOWED_KEYS.has(key)) {
618
- errors.push(`unknown event field: ${key} — there is deliberately no fix/root_cause/recommendation field; record the observation only`);
619
- }
620
- }
621
-
622
- const eventType = input.event_type;
623
- checkEnum(errors, 'event_type', eventType, EVENT_TYPES);
624
-
625
- let subtype = null;
626
- if (EVENT_SUBTYPES[eventType] === null) {
627
- if (input.subtype !== undefined && input.subtype !== null) {
628
- errors.push(`event_type ${eventType} defines no subtypes (error_code is its discriminator)`);
629
- }
630
- } else if (EVENT_SUBTYPES[eventType]) {
631
- if (checkEnum(errors, `subtype (for ${eventType})`, input.subtype, EVENT_SUBTYPES[eventType])) {
632
- subtype = input.subtype;
633
- }
634
- }
635
-
636
- let stage = input.stage;
637
- if (stage === undefined || stage === null) {
638
- stage = context.openStage || context.lastStage || null;
639
- if (!stage) {
640
- errors.push('stage is required (no stage has been started yet to default to)');
641
- }
642
- } else {
643
- checkEnum(errors, 'stage', stage, STAGES);
644
- }
645
-
646
- if (typeof input.skill !== 'string' || !/^[a-z][a-z0-9-]*$/.test(input.skill)) {
647
- errors.push('skill is required — the active rp-* resource (e.g. "rp-mapper") or "wix-replatform"');
648
- }
649
-
650
- checkEnum(errors, 'severity', input.severity, SEVERITIES);
651
-
652
- let entityType = null;
653
- if (input.entity_type !== undefined && input.entity_type !== null) {
654
- entityType = normalizeToken(input.entity_type);
655
- if (!entityType || entityType.length > 32) {
656
- errors.push('entity_type must be a short entity class token (e.g. product, post, media)');
657
- entityType = null;
658
- }
659
- }
660
-
661
- const wixApiSurface = checkApiSurface(errors, 'wix_api_surface', input.wix_api_surface);
662
- const sourceApiSurface = checkApiSurface(errors, 'source_api_surface', input.source_api_surface);
663
- const wixAppId = checkOpaqueId(errors, 'wix_app_id', input.wix_app_id);
664
-
665
- let errorCode = null;
666
- if (input.error_code !== undefined && input.error_code !== null) {
667
- const str = String(input.error_code);
668
- if (str.length > 48 || /\s/.test(str)) {
669
- errors.push('error_code must be a short machine code (e.g. 428, WDE0110)');
670
- } else {
671
- errorCode = str;
672
- }
673
- }
674
- if (eventType === 'error' && errorCode === null) {
675
- errors.push('error events require error_code — it is the discriminator for this type');
676
- }
677
- if (eventType === 'api_gap' && wixApiSurface === null) {
678
- errors.push('api_gap events require wix_api_surface — it is part of the stable signature');
679
- }
680
-
681
- const whatHappened = checkBoundedText(errors, 'what_happened', input.what_happened, { required: true });
682
- const expected = checkBoundedText(errors, 'expected', input.expected);
683
- const actual = checkBoundedText(errors, 'actual', input.actual);
684
-
685
- const count = checkCount(errors, 'count', input.count, { defaultValue: 1, min: 1 });
686
-
687
- let retryCount;
688
- let recovered;
689
- if (eventType === 'error') {
690
- retryCount = checkCount(errors, 'retry_count', input.retry_count, { defaultValue: 0, min: 0 });
691
- recovered = input.recovered === undefined || input.recovered === null ? false : input.recovered;
692
- if (typeof recovered !== 'boolean') {
693
- errors.push('recovered must be a boolean');
694
- recovered = false;
695
- }
696
- } else {
697
- if (input.retry_count !== undefined || input.recovered !== undefined) {
698
- errors.push('retry_count/recovered are only valid on error events');
699
- }
700
- }
701
-
702
- let decisionPoint;
703
- if (eventType === 'user_decision') {
704
- decisionPoint = typeof input.decision_point === 'string' ? normalizeToken(input.decision_point) : '';
705
- if (!decisionPoint || decisionPoint.length > 48) {
706
- errors.push('user_decision events require decision_point — which checkpoint or fork (e.g. mapping_review, approval_gate)');
707
- }
708
- } else if (input.decision_point !== undefined) {
709
- errors.push('decision_point is only valid on user_decision events');
710
- }
711
-
712
- let observedShapes = null;
713
- if (input.observed_shapes !== undefined && input.observed_shapes !== null) {
714
- if (!Array.isArray(input.observed_shapes) || input.observed_shapes.length === 0) {
715
- errors.push('observed_shapes must be a non-empty array of sanitized shape objects or omitted');
716
- } else if (input.observed_shapes.length > SHAPES_MAX_PER_CALL) {
717
- errors.push(`observed_shapes is capped at ${SHAPES_MAX_PER_CALL} shapes per call (folding keeps ${SHAPES_KEPT_PER_CLASS} distinct per class)`);
718
- } else {
719
- observedShapes = input.observed_shapes.map((shape, i) => {
720
- if (!shape || typeof shape !== 'object' || Array.isArray(shape)) {
721
- errors.push(`observed_shapes[${i}] must be an object of field names and types`);
722
- return null;
723
- }
724
- return sanitizeShapeValue(shape, errors, `[${i}]`, 0, scrub);
725
- });
726
- }
727
- }
728
-
729
- const evidenceRefs = validateEvidenceRefs(errors, input.evidence_refs);
730
-
731
- if (errors.length > 0) {
732
- throw new ValidationError(errors, classKeyFor({
733
- event_type: eventType, stage, entity_type: entityType, wix_api_surface: wixApiSurface,
734
- source_api_surface: sourceApiSurface, wix_app_id: wixAppId, error_code: errorCode,
735
- subtype, decision_point: decisionPoint,
736
- }));
737
- }
738
-
739
- const scrubbedWhat = scrubText(whatHappened);
740
- const scrubbedExpected = expected === null ? null : scrubText(expected);
741
- const scrubbedActual = actual === null ? null : scrubText(actual);
742
- scrub.hits += scrubbedWhat.hits + (scrubbedExpected ? scrubbedExpected.hits : 0) + (scrubbedActual ? scrubbedActual.hits : 0);
743
-
744
- const event = {
745
- event_type: eventType,
746
- subtype,
747
- stage,
748
- skill: input.skill,
749
- entity_type: entityType,
750
- severity: input.severity,
751
- wix_api_surface: wixApiSurface,
752
- source_api_surface: sourceApiSurface,
753
- wix_app_id: wixAppId,
754
- error_code: errorCode,
755
- what_happened: scrubbedWhat.text,
756
- expected: scrubbedExpected ? scrubbedExpected.text : null,
757
- actual: scrubbedActual ? scrubbedActual.text : null,
758
- observed_shapes: observedShapes,
759
- evidence_refs: evidenceRefs,
760
- count,
761
- };
762
- if (eventType === 'error') {
763
- event.retry_count = retryCount;
764
- event.recovered = recovered;
765
- }
766
- if (eventType === 'user_decision') {
767
- event.decision_point = decisionPoint;
768
- }
769
- return { event, classKey: classKeyFor(event), scrubHits: scrub.hits };
770
- }
771
-
772
- function classKeyFor(event) {
773
- const parts = [
774
- event.event_type, event.stage, event.entity_type, event.wix_api_surface,
775
- event.source_api_surface, event.wix_app_id, event.error_code, event.subtype,
776
- ];
777
- if (event.event_type === 'user_decision') parts.push(event.decision_point);
778
- return parts.map((p) => (p === null || p === undefined ? '' : String(p))).join('|');
779
- }
780
-
781
- // --- rollup (finalize) validation --------------------------------------------
782
-
783
- function validateVolumes(errors, input) {
784
- if (input === undefined || input === null) return [];
785
- if (!Array.isArray(input)) {
786
- errors.push('volumes must be an array of per-entity-type volume records');
787
- return [];
788
- }
789
- const volumes = [];
790
- for (const [i, raw] of input.entries()) {
791
- if (!raw || typeof raw !== 'object') {
792
- errors.push(`volumes[${i}] must be an object`);
793
- continue;
794
- }
795
- const entityType = typeof raw.entity_type === 'string' ? normalizeToken(raw.entity_type) : '';
796
- if (!entityType) {
797
- errors.push(`volumes[${i}].entity_type is required`);
798
- continue;
799
- }
800
- let target = null;
801
- if (raw.target !== undefined && raw.target !== null) {
802
- if (checkEnum(errors, `volumes[${i}].target`, raw.target, VOLUME_TARGETS)) target = raw.target;
803
- }
804
- const targetSurface = checkApiSurface(errors, `volumes[${i}].target_surface`, raw.target_surface);
805
- const volume = {
806
- entity_type: entityType,
807
- target,
808
- target_surface: targetSurface,
809
- discovered: checkCount(errors, `volumes[${i}].discovered`, raw.discovered),
810
- planned: raw.planned === undefined || raw.planned === null
811
- ? null
812
- : checkCount(errors, `volumes[${i}].planned`, raw.planned),
813
- attempted: checkCount(errors, `volumes[${i}].attempted`, raw.attempted),
814
- succeeded: checkCount(errors, `volumes[${i}].succeeded`, raw.succeeded),
815
- failed: checkCount(errors, `volumes[${i}].failed`, raw.failed),
816
- skipped: checkCount(errors, `volumes[${i}].skipped`, raw.skipped),
817
- already_imported: checkCount(errors, `volumes[${i}].already_imported`, raw.already_imported),
818
- };
819
- if (volume.attempted !== volume.succeeded + volume.failed) {
820
- errors.push(`volumes[${i}] (${entityType}): attempted must equal succeeded + failed (${volume.attempted} != ${volume.succeeded} + ${volume.failed})`);
821
- }
822
- volumes.push(volume);
823
- }
824
- return volumes;
825
- }
826
-
827
- function validateVerification(errors, input) {
828
- if (input === undefined || input === null) return [];
829
- if (!Array.isArray(input)) {
830
- errors.push('verification must be an array of verification records');
831
- return [];
832
- }
833
- const records = [];
834
- for (const [i, raw] of input.entries()) {
835
- if (!raw || typeof raw !== 'object') {
836
- errors.push(`verification[${i}] must be an object`);
837
- continue;
838
- }
839
- const subject = typeof raw.subject === 'string' ? normalizeToken(raw.subject) : '';
840
- if (!subject) {
841
- errors.push(`verification[${i}].subject is required (an entity_type, or "routes")`);
842
- continue;
843
- }
844
- if (!checkEnum(errors, `verification[${i}].method`, raw.method, VERIFICATION_METHODS)) continue;
845
- const record = {
846
- subject,
847
- method: raw.method,
848
- checked: checkCount(errors, `verification[${i}].checked`, raw.checked),
849
- passed: checkCount(errors, `verification[${i}].passed`, raw.passed),
850
- failed: checkCount(errors, `verification[${i}].failed`, raw.failed),
851
- };
852
- if (record.passed + record.failed > record.checked) {
853
- errors.push(`verification[${i}] (${subject}): passed + failed must not exceed checked`);
854
- }
855
- records.push(record);
856
- }
857
- return records;
858
- }
859
-
860
- function validateFinalizeInput(input, context) {
861
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
862
- throw new ValidationError(['finalize payload must be a JSON object']);
863
- }
864
- const errors = [];
865
- const dimKeys = {};
866
- const known = new Set(['terminal_state', 'stopped_at_stage', 'operator_acceptance', 'volumes', 'verification']);
867
- for (const [key, value] of Object.entries(input)) {
868
- if (known.has(key)) continue;
869
- if (DIM_VALIDATORS[key]) {
870
- dimKeys[key] = value;
871
- continue;
872
- }
873
- errors.push(`unknown finalize field: ${key}`);
874
- }
875
-
876
- checkEnum(errors, 'terminal_state', input.terminal_state, TERMINAL_STATES);
877
-
878
- let stoppedAtStage = null;
879
- if (input.terminal_state === 'completed') {
880
- if (input.stopped_at_stage !== undefined && input.stopped_at_stage !== null) {
881
- errors.push('stopped_at_stage must be null when terminal_state is completed');
882
- }
883
- } else if (input.stopped_at_stage !== undefined && input.stopped_at_stage !== null) {
884
- if (checkEnum(errors, 'stopped_at_stage', input.stopped_at_stage, STAGES)) {
885
- stoppedAtStage = input.stopped_at_stage;
886
- }
887
- } else {
888
- stoppedAtStage = context.openStage || context.lastStage || 'config';
889
- }
890
-
891
- let operatorAcceptance = 'unknown';
892
- if (input.operator_acceptance !== undefined && input.operator_acceptance !== null) {
893
- if (checkEnum(errors, 'operator_acceptance', input.operator_acceptance, OPERATOR_ACCEPTANCE)) {
894
- operatorAcceptance = input.operator_acceptance;
895
- }
896
- }
897
-
898
- const volumes = validateVolumes(errors, input.volumes);
899
- const verification = validateVerification(errors, input.verification);
900
-
901
- let dims = {};
902
- try {
903
- dims = validateDims(dimKeys);
904
- } catch (error) {
905
- errors.push(...error.errors);
906
- }
907
-
908
- if (errors.length > 0) throw new ValidationError(errors);
909
- return {
910
- terminal_state: input.terminal_state,
911
- stopped_at_stage: stoppedAtStage,
912
- operator_acceptance: operatorAcceptance,
913
- volumes,
914
- verification,
915
- dims,
916
- };
917
- }
918
-
919
- // --- transcript_digest validation ---------------------------------
920
- // Every field is derived, machine-computed data (transcript-digest.js), never
921
- // agent free text — so the gate here checks shape and bounds, not prose rules.
922
-
923
- const TOOL_NAME_MAX = 80;
924
-
925
- function checkToolName(errors, field, value) {
926
- if (typeof value !== 'string' || value.length === 0 || value.length > TOOL_NAME_MAX || /\s/.test(value)) {
927
- errors.push(`${field} must be a short tool name (no whitespace, max ${TOOL_NAME_MAX} chars)`);
928
- return null;
929
- }
930
- return value;
931
- }
932
-
933
- function validateNamedCountList(errors, field, value, { max = 10 } = {}) {
934
- if (value === undefined || value === null) return [];
935
- if (!Array.isArray(value) || value.length > max) {
936
- errors.push(`${field} must be an array of at most ${max} entries`);
937
- return [];
938
- }
939
- return value.map((entry, i) => {
940
- if (!entry || typeof entry !== 'object') {
941
- errors.push(`${field}[${i}] must be an object`);
942
- return null;
943
- }
944
- return entry;
945
- }).filter(Boolean);
946
- }
947
-
948
- function validateTranscriptDigest(input) {
949
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
950
- throw new ValidationError(['transcript_digest payload must be a JSON object']);
951
- }
952
- const errors = [];
953
- const known = new Set(['source', 'cost', 'interaction', 'time', 'tools', 'signals']);
954
- for (const key of Object.keys(input)) {
955
- if (!known.has(key)) errors.push(`unknown transcript_digest field: ${key}`);
956
- }
957
-
958
- const source = input.source || {};
959
- const sourceOut = {
960
- sessions: checkCount(errors, 'source.sessions', source.sessions, { defaultValue: 1, min: 1 }),
961
- transcript_turns: checkCount(errors, 'source.transcript_turns', source.transcript_turns),
962
- cc_version: null,
963
- tail_complete: typeof source.tail_complete === 'boolean' ? source.tail_complete : null,
964
- };
965
- if (sourceOut.tail_complete === null) errors.push('source.tail_complete must be a boolean');
966
- if (source.cc_version !== undefined && source.cc_version !== null) {
967
- if (typeof source.cc_version !== 'string' || source.cc_version.length > 32) {
968
- errors.push('source.cc_version must be a short version string');
969
- } else {
970
- sourceOut.cc_version = source.cc_version;
971
- }
972
- }
973
-
974
- const cost = input.cost || {};
975
- const costOut = {
976
- input_tokens: checkCount(errors, 'cost.input_tokens', cost.input_tokens),
977
- output_tokens: checkCount(errors, 'cost.output_tokens', cost.output_tokens),
978
- cache_read_input_tokens: checkCount(errors, 'cost.cache_read_input_tokens', cost.cache_read_input_tokens),
979
- cache_creation_input_tokens: checkCount(errors, 'cost.cache_creation_input_tokens', cost.cache_creation_input_tokens),
980
- };
981
-
982
- const interaction = input.interaction || {};
983
- const interactionOut = {
984
- human_turns: checkCount(errors, 'interaction.human_turns', interaction.human_turns),
985
- agent_turns: checkCount(errors, 'interaction.agent_turns', interaction.agent_turns),
986
- sidechain_turns: checkCount(errors, 'interaction.sidechain_turns', interaction.sidechain_turns),
987
- };
988
-
989
- const time = input.time || {};
990
- const timeOut = {
991
- wall_ms: checkCount(errors, 'time.wall_ms', time.wall_ms),
992
- active_ms: checkCount(errors, 'time.active_ms', time.active_ms),
993
- idle_ms: checkCount(errors, 'time.idle_ms', time.idle_ms),
994
- longest_gap_ms: checkCount(errors, 'time.longest_gap_ms', time.longest_gap_ms),
995
- };
996
-
997
- const tools = input.tools || {};
998
- const topFailing = validateNamedCountList(errors, 'tools.top_failing', tools.top_failing).map((entry, i) => ({
999
- tool: checkToolName(errors, `tools.top_failing[${i}].tool`, entry.tool),
1000
- n: checkCount(errors, `tools.top_failing[${i}].n`, entry.n, { defaultValue: 1, min: 1 }),
1001
- }));
1002
- const toolsOut = {
1003
- calls: checkCount(errors, 'tools.calls', tools.calls),
1004
- failures: checkCount(errors, 'tools.failures', tools.failures),
1005
- top_failing: topFailing,
1006
- };
1007
-
1008
- const signals = input.signals || {};
1009
- const retryLoops = validateNamedCountList(errors, 'signals.retry_loops', signals.retry_loops).map((entry, i) => {
1010
- let stage = null;
1011
- if (entry.stage !== undefined && entry.stage !== null) {
1012
- if (checkEnum(errors, `signals.retry_loops[${i}].stage`, entry.stage, STAGES)) stage = entry.stage;
1013
- }
1014
- return {
1015
- tool: checkToolName(errors, `signals.retry_loops[${i}].tool`, entry.tool),
1016
- repeats: checkCount(errors, `signals.retry_loops[${i}].repeats`, entry.repeats, { defaultValue: 1, min: 1 }),
1017
- stage,
1018
- };
1019
- });
1020
- const signalsOut = {
1021
- retry_loops: retryLoops,
1022
- hook_blocks: checkCount(errors, 'signals.hook_blocks', signals.hook_blocks),
1023
- hook_errors: checkCount(errors, 'signals.hook_errors', signals.hook_errors),
1024
- files_edited_mid_run: checkCount(errors, 'signals.files_edited_mid_run', signals.files_edited_mid_run),
1025
- };
1026
-
1027
- if (errors.length > 0) throw new ValidationError(errors);
1028
- return {
1029
- source: sourceOut, cost: costOut, interaction: interactionOut, time: timeOut, tools: toolsOut, signals: signalsOut,
1030
- };
1031
- }
1032
-
1033
- // --- journal ------------------------------------------------------------------
1034
-
1035
- function readJournalFile(file) {
1036
- const records = [];
1037
- let malformed = 0;
1038
- for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
1039
- if (line.trim() === '') continue;
1040
- try {
1041
- records.push(JSON.parse(line));
1042
- } catch {
1043
- malformed += 1;
1044
- }
1045
- }
1046
- return { records, malformed };
1047
- }
1048
-
1049
- function readJournal(projectDir) {
1050
- const file = journalPath(projectDir);
1051
- if (!fs.existsSync(file)) return null;
1052
- return readJournalFile(file);
1053
- }
1054
-
1055
- function appendJournal(projectDir, record) {
1056
- const file = journalPath(projectDir);
1057
- fs.mkdirSync(path.dirname(file), { recursive: true });
1058
- fs.appendFileSync(file, `${JSON.stringify(record)}\n`, 'utf8');
1059
- }
1060
-
1061
- // Several records in ONE appendFileSync, so a transaction is all-or-a-torn-suffix.
1062
- function appendJournalRecords(projectDir, records) {
1063
- if (records.length === 0) return;
1064
- const file = journalPath(projectDir);
1065
- fs.mkdirSync(path.dirname(file), { recursive: true });
1066
- fs.appendFileSync(file, records.map((r) => `${JSON.stringify(r)}\n`).join(''), 'utf8');
1067
- }
1068
-
1069
- // --- journal lock -----------------------------------------------------------------
1070
-
1071
- const LOCK_CLAIM_RE = /^([^.]+)\.(\d+)\.[a-f0-9-]+\.owner$/;
1072
-
1073
- function sleepSync(ms) {
1074
- if (ms > 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1075
- }
1076
-
1077
- function lockBusyError(lockPath, detail) {
1078
- const error = new ValidationError(
1079
- [`telemetry journal is locked by another process (${detail}): ${lockPath}`],
1080
- 'another telemetry writer is active on this project; retry the call',
1081
- );
1082
- error.code = 'ELOCKBUSY';
1083
- return error;
1084
- }
1085
-
1086
- // Test-only hook: when RP_TELEMETRY_TEST_LOCK_HOLD_UNTIL names a file, a lock holder waits
1087
- // (bounded) until that file exists before running its critical section, so a test can force
1088
- // the lock acquisition order between two processes. Unset in every real run.
1089
- function testHoldHook() {
1090
- const until = process.env.RP_TELEMETRY_TEST_LOCK_HOLD_UNTIL;
1091
- if (!until) return;
1092
- const deadline = Date.now() + 10000;
1093
- while (!fs.existsSync(until) && Date.now() < deadline) sleepSync(20);
1094
- }
1095
-
1096
- // One writer per project journal. Same claim-file algorithm as local-state's withStateLock
1097
- // (a unique <host>.<pid>.<uuid>.owner claim published before scanning; a same-host dead-PID
1098
- // claim is removed; the container directory is never deleted), in `telemetry/.lock/` — never
1099
- // the importer's `state/.lock`. Synchronous on purpose: the critical section is local I/O
1100
- // only (a fresh read, seq computation and one append), never a network call, so the lock is
1101
- // never held across an await and the command API keeps its sync functions sync. Contention
1102
- // retries every ~50 ms up to `waitMs`; a claim from another host cannot be checked and is
1103
- // treated as busy at once. Busy → ValidationError with code ELOCKBUSY.
1104
- function withJournalLock(projectDir, fn, { waitMs = COMMAND_LOCK_WAIT_MS } = {}) {
1105
- const lockPath = path.join(telemetryDir(projectDir), '.lock');
1106
- fs.mkdirSync(lockPath, { recursive: true });
1107
- const host = Buffer.from(os.hostname()).toString('base64url');
1108
- const deadline = Date.now() + waitMs;
1109
- for (;;) {
1110
- const claim = `${host}.${process.pid}.${crypto.randomUUID()}.owner`;
1111
- const claimPath = path.join(lockPath, claim);
1112
- fs.writeFileSync(claimPath, JSON.stringify({ pid: process.pid, host, acquiredAt: new Date().toISOString() }), { flag: 'wx', mode: 0o600 });
1113
- let blocker = null;
1114
- try {
1115
- for (const entry of fs.readdirSync(lockPath)) {
1116
- if (entry === claim) continue;
1117
- const match = LOCK_CLAIM_RE.exec(entry);
1118
- if (!match || match[1] !== host) { blocker = { foreign: true, detail: `claim ${entry} cannot be checked locally` }; break; }
1119
- try {
1120
- process.kill(Number(match[2]), 0);
1121
- } catch (error) {
1122
- if (error.code === 'ESRCH') { fs.rmSync(path.join(lockPath, entry), { force: true }); continue; }
1123
- if (error.code !== 'EPERM') throw error;
1124
- }
1125
- blocker = { foreign: false, detail: `held by PID ${match[2]}` };
1126
- break;
1127
- }
1128
- } catch (error) {
1129
- fs.rmSync(claimPath, { force: true });
1130
- throw error;
1131
- }
1132
- if (!blocker) {
1133
- try {
1134
- testHoldHook();
1135
- return fn();
1136
- } finally {
1137
- fs.rmSync(claimPath, { force: true });
1138
- }
1139
- }
1140
- fs.rmSync(claimPath, { force: true });
1141
- const remaining = deadline - Date.now();
1142
- if (blocker.foreign || remaining <= 0) throw lockBusyError(lockPath, blocker.detail);
1143
- sleepSync(Math.min(remaining, 25 + Math.floor(Math.random() * 50)));
1144
- }
1145
- }
1146
-
1147
- function replay(records) {
1148
- const state = {
1149
- runStart: null,
1150
- dims: {},
1151
- sessionCount: 1,
1152
- seq: 0,
1153
- events: [],
1154
- rejected: [],
1155
- entries: [],
1156
- waits: [],
1157
- meters: [],
1158
- sourceReads: [],
1159
- openStage: null,
1160
- openStageStart: null,
1161
- lastStage: null,
1162
- openWait: null,
1163
- scrubHits: 0,
1164
- biPushFailures: 0,
1165
- biPushTruncated: 0,
1166
- biPushSkipped: 0,
1167
- lastBiPushOkSeq: 0,
1168
- lastEventSeq: 0,
1169
- finalize: null,
1170
- lastTs: null,
1171
- // The activity clock: the MAXIMUM parseable ts over every record that is not a transport
1172
- // marker (bi_push_*). Markers are bookkeeping about delivery and may be appended late with
1173
- // the initiating call's older ts, so they never move it, and a maximum never goes backwards.
1174
- lastActivityTs: null,
1175
- // The current attempt's latest session opener (run_start or session_start), in file order.
1176
- lastOpener: null,
1177
- transcriptDigest: null,
1178
- };
1179
- let lastActivityMs = -Infinity;
1180
- for (const record of records) {
1181
- state.seq = Math.max(state.seq, record.seq || 0);
1182
- state.lastTs = record.ts || state.lastTs;
1183
- if (typeof record.type === 'string' && !record.type.startsWith('bi_push_') && typeof record.ts === 'string') {
1184
- const ms = tsMs(record.ts);
1185
- if (Number.isFinite(ms) && ms > lastActivityMs) {
1186
- lastActivityMs = ms;
1187
- state.lastActivityTs = record.ts;
1188
- }
1189
- }
1190
- switch (record.type) {
1191
- case 'run_start':
1192
- state.runStart = record;
1193
- state.lastOpener = record;
1194
- Object.assign(state.dims, record.dims || {});
1195
- break;
1196
- case 'session_start':
1197
- state.sessionCount = record.session;
1198
- state.lastOpener = record;
1199
- break;
1200
- case 'dims':
1201
- Object.assign(state.dims, record.dims || {});
1202
- break;
1203
- case 'stage_start':
1204
- state.openStage = record.stage;
1205
- state.openStageStart = record.ts;
1206
- state.lastStage = record.stage;
1207
- break;
1208
- case 'stage_end':
1209
- if (state.openStage === record.stage) {
1210
- state.entries.push({ stage: record.stage, start: state.openStageStart, end: record.ts, outcome: record.outcome });
1211
- state.openStage = null;
1212
- state.openStageStart = null;
1213
- }
1214
- break;
1215
- case 'wait_start':
1216
- state.openWait = { stage: record.stage, start: record.ts };
1217
- break;
1218
- case 'wait_end':
1219
- if (state.openWait) {
1220
- state.waits.push({ ...state.openWait, end: record.ts });
1221
- state.openWait = null;
1222
- }
1223
- break;
1224
- case 'meter':
1225
- // Accumulate: a stage may be entered more than once, and each generated script reports its
1226
- // own invocation. Summing is the only reading that survives a resume.
1227
- state.meters.push(record.meter);
1228
- break;
1229
- case 'source_read':
1230
- // Same accumulate-don't-overwrite reasoning: a run reads a source site across several
1231
- // stages and possibly across a resume, and only the sum is the run's real total.
1232
- state.sourceReads.push(record.source_read);
1233
- break;
1234
- case 'event':
1235
- state.events.push(record);
1236
- state.scrubHits += record.scrub_hits || 0;
1237
- state.lastEventSeq = Math.max(state.lastEventSeq, record.seq || 0);
1238
- break;
1239
- case 'rejected':
1240
- state.rejected.push(record);
1241
- break;
1242
- case 'bi_push_failed':
1243
- state.biPushFailures += 1;
1244
- break;
1245
- case 'bi_push_truncated':
1246
- state.biPushTruncated += record.rows || 1;
1247
- break;
1248
- case 'bi_push_skipped':
1249
- state.biPushSkipped += record.rows || 1;
1250
- break;
1251
- case 'bi_push_ok':
1252
- state.lastBiPushOkSeq = Math.max(state.lastBiPushOkSeq, record.through_seq || 0);
1253
- break;
1254
- case 'finalize':
1255
- state.finalize = record;
1256
- break;
1257
- case 'transcript_digest':
1258
- state.transcriptDigest = {
1259
- source: record.source,
1260
- cost: record.cost,
1261
- interaction: record.interaction,
1262
- time: record.time,
1263
- tools: record.tools,
1264
- signals: record.signals,
1265
- };
1266
- break;
1267
- default:
1268
- break;
1269
- }
1270
- }
1271
- return state;
1272
- }
1273
-
1274
- function requireActiveRun(projectDir) {
1275
- const journal = readJournal(projectDir);
1276
- if (!journal) {
1277
- throw new ValidationError(
1278
- ['no active telemetry run in this project'],
1279
- "call `rp-telemetry.js start '<dims-json>'` first",
1280
- );
1281
- }
1282
- const state = replay(journal.records);
1283
- if (!state.runStart) {
1284
- throw new ValidationError(['telemetry journal is corrupt: missing run-start record']);
1285
- }
1286
- if (state.finalize) {
1287
- // A crash after the finalize append but before archival: finish the archival
1288
- // now, then report no active run.
1289
- archiveFinalizedJournal(projectDir, journal, state);
1290
- throw new ValidationError(
1291
- ['the previous run is already finalized'],
1292
- "call `rp-telemetry.js start '<dims-json>'` to open the next run",
1293
- );
1294
- }
1295
- return { journal, state };
1296
- }
1297
-
1298
- // --- fold + document assembly ---------------------------------------------------
1299
-
1300
- function foldEvents(eventRecords, runId) {
1301
- const byClass = new Map();
1302
- for (const record of eventRecords) {
1303
- const e = record.event;
1304
- let folded = byClass.get(record.class_key);
1305
- if (!folded) {
1306
- folded = {
1307
- run_id: runId,
1308
- seq: record.seq,
1309
- count: 0,
1310
- stage: e.stage,
1311
- skill: e.skill,
1312
- event_type: e.event_type,
1313
- subtype: e.subtype,
1314
- entity_type: e.entity_type,
1315
- severity: e.severity,
1316
- wix_api_surface: e.wix_api_surface,
1317
- source_api_surface: e.source_api_surface,
1318
- wix_app_id: e.wix_app_id,
1319
- error_code: e.error_code,
1320
- what_happened: e.what_happened,
1321
- expected: e.expected,
1322
- actual: e.actual,
1323
- observed_shapes: null,
1324
- evidence_refs: null,
1325
- _shapes: new Map(),
1326
- _refs: new Map(),
1327
- };
1328
- if (e.event_type === 'error') {
1329
- folded.retry_count = 0;
1330
- folded.recovered = false;
1331
- }
1332
- if (e.event_type === 'user_decision') folded.decision_point = e.decision_point;
1333
- byClass.set(record.class_key, folded);
1334
- }
1335
- folded.count += e.count;
1336
- if (e.event_type === 'error') {
1337
- folded.retry_count += e.retry_count || 0;
1338
- // A retry chain is often recorded per attempt. The latest occurrence knows
1339
- // whether the operation ultimately succeeded, and a recovering occurrence's
1340
- // `actual` carries the change that made it succeed — the first (failing)
1341
- // attempt's fields must never erase that half of the observation.
1342
- folded.recovered = Boolean(e.recovered);
1343
- if (e.recovered && e.actual) folded.actual = e.actual;
1344
- }
1345
- for (const shape of e.observed_shapes || []) {
1346
- const key = stableStringify(shape);
1347
- if (!folded._shapes.has(key)) folded._shapes.set(key, shape);
1348
- }
1349
- for (const ref of e.evidence_refs || []) {
1350
- const key = `${ref.artifact}#${ref.locator || ''}`;
1351
- if (!folded._refs.has(key)) folded._refs.set(key, ref);
1352
- }
1353
- }
1354
- const events = [...byClass.values()].sort((a, b) => a.seq - b.seq);
1355
- for (const event of events) {
1356
- const shapes = [...event._shapes.values()];
1357
- if (shapes.length > 0) {
1358
- event.observed_shapes = shapes.slice(0, SHAPES_KEPT_PER_CLASS);
1359
- // Surfaced bound, never a silent one.
1360
- if (shapes.length > SHAPES_KEPT_PER_CLASS) event.shapes_seen = shapes.length;
1361
- }
1362
- const refs = [...event._refs.values()];
1363
- if (refs.length > 0) event.evidence_refs = refs.slice(0, EVIDENCE_REFS_MAX);
1364
- delete event._shapes;
1365
- delete event._refs;
1366
- }
1367
- return events;
1368
- }
1369
-
1370
- // Event types that mean the stage did not run clean. `contained_recovery` is DERIVED from these
1371
- // rather than self-reported, because a stage that spent its time debugging is exactly the stage an
1372
- // agent is least likely to remember to flag.
1373
- const RECOVERY_EVENT_TYPES = new Set(['error', 'pipeline_defect']);
1374
-
1375
- function computeStages(state, finalizeTs) {
1376
- const entries = [...state.entries];
1377
- const waits = [...state.waits];
1378
- if (state.openWait) {
1379
- waits.push({ ...state.openWait, end: finalizeTs });
1380
- }
1381
- const byStage = new Map();
1382
- const order = [];
1383
- const stageRecord = (stage) => {
1384
- if (!byStage.has(stage)) {
1385
- byStage.set(stage, { stage, outcome: 'halted', active_ms: 0, waiting_ms: 0, _entries: [] });
1386
- order.push(stage);
1387
- }
1388
- return byStage.get(stage);
1389
- };
1390
- for (const entry of entries) {
1391
- const record = stageRecord(entry.stage);
1392
- record.outcome = entry.outcome;
1393
- record._entries.push(entry);
1394
- }
1395
- for (const wait of waits) {
1396
- const record = stageRecord(wait.stage || 'config');
1397
- record.waiting_ms += Math.max(0, tsMs(wait.end) - tsMs(wait.start));
1398
- }
1399
- // Metered measurements, summed per stage. A stage named by a meter but never opened still gets a
1400
- // record, so a mis-attributed meter is visible instead of silently discarded.
1401
- for (const meter of state.meters || []) {
1402
- const record = stageRecord(meter.stage);
1403
- for (const key of [...METER_DURATION_KEYS, ...METER_COUNT_KEYS]) {
1404
- if (meter[key] === undefined) continue;
1405
- record[key] = (record[key] || 0) + meter[key];
1406
- }
1407
- }
1408
-
1409
- // Stages that contained an error or a pipeline defect. Without this, a clean run and a thrash are
1410
- // indistinguishable in the rollup, and every per-stage duration silently includes repair time.
1411
- const recoveryStages = new Set(
1412
- (state.events || [])
1413
- .filter((e) => RECOVERY_EVENT_TYPES.has(e.event && e.event.event_type ? e.event.event_type : e.event_type))
1414
- .map((e) => (e.event && e.event.stage ? e.event.stage : e.stage))
1415
- .filter(Boolean),
1416
- );
1417
-
1418
- for (const stage of order) {
1419
- const record = byStage.get(stage);
1420
- let active = 0;
1421
- for (const entry of record._entries) {
1422
- active += Math.max(0, tsMs(entry.end) - tsMs(entry.start));
1423
- for (const wait of waits) {
1424
- if ((wait.stage || 'config') !== stage) continue;
1425
- const overlap = Math.min(tsMs(entry.end), tsMs(wait.end)) - Math.max(tsMs(entry.start), tsMs(wait.start));
1426
- if (overlap > 0) active -= overlap;
1427
- }
1428
- }
1429
- record.active_ms = Math.max(0, active);
1430
- record.contained_recovery = recoveryStages.has(stage);
1431
- // The remainder of active_ms that no meter explained. This is the honesty field: it is how much
1432
- // of the stage is still a black box, and it must never be silently folded into a component.
1433
- const attributed = METER_DURATION_KEYS.reduce((sum, key) => sum + (record[key] || 0), 0);
1434
- record.unattributed_ms = Math.max(0, record.active_ms - attributed);
1435
- delete record._entries;
1436
- }
1437
- return order.map((stage) => byStage.get(stage));
1438
- }
1439
-
1440
- // Cost is DERIVED from token counts and the pricing snapshot, never recorded as a figure — a stored
1441
- // dollar amount silently goes wrong the moment list prices change, while tokens are a fact.
1442
- function computeCost(stages, pricingSnapshot, model) {
1443
- const totals = { input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_write_tokens: 0 };
1444
- for (const stage of stages) {
1445
- for (const key of Object.keys(totals)) totals[key] += stage[key] || 0;
1446
- }
1447
- const anyTokens = Object.values(totals).some((v) => v > 0);
1448
- const rates = pricingSnapshot && model ? pricingSnapshot[model] : null;
1449
- if (!anyTokens || !rates) {
1450
- return { tokens: totals, estimated_cost_usd: null, cost_basis: rates ? 'no_tokens_recorded' : 'no_pricing_snapshot' };
1451
- }
1452
- const perM = (count, rate) => (rate === undefined ? 0 : (count / 1e6) * rate);
1453
- const usd = perM(totals.input_tokens, rates.input_per_mtok)
1454
- + perM(totals.output_tokens, rates.output_per_mtok)
1455
- + perM(totals.cache_read_tokens, rates.cache_read_per_mtok)
1456
- + perM(totals.cache_write_tokens, rates.cache_write_per_mtok);
1457
- return { tokens: totals, estimated_cost_usd: Number(usd.toFixed(6)), cost_basis: 'derived_from_snapshot' };
1458
- }
1459
-
1460
- const MANIFEST_EXCLUDE_DIRS = new Set(['config', 'data', 'preview', 'node_modules', 'frontend', 'telemetry']);
1461
- // .ndjson: the pipeline's audit logs (logs/audit-*.ndjson) are named
1462
- // evidence — a pipeline_defect deep-dive depends on them being in the bundle.
1463
- const MANIFEST_EXTENSIONS = new Set(['.md', '.json', '.jsonl', '.ndjson', '.js', '.mjs', '.cjs', '.ts', '.sh', '.yaml', '.yml']);
1464
-
1465
- function walkEvidence(projectDir) {
1466
- const files = [];
1467
- const walk = (rel) => {
1468
- const abs = rel === '' ? projectDir : path.join(projectDir, rel);
1469
- const entries = fs.readdirSync(abs, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
1470
- for (const entry of entries) {
1471
- if (entry.name.startsWith('.')) continue;
1472
- const relPath = rel === '' ? entry.name : `${rel}/${entry.name}`;
1473
- if (entry.isDirectory()) {
1474
- if (MANIFEST_EXCLUDE_DIRS.has(entry.name)) continue;
1475
- walk(relPath);
1476
- continue;
1477
- }
1478
- if (!entry.isFile()) continue;
1479
- if (entry.name.endsWith('.env')) continue;
1480
- if (relPath === 'run-telemetry.json') continue; // added explicitly, first
1481
- if (!MANIFEST_EXTENSIONS.has(path.extname(entry.name))) continue;
1482
- files.push({ path: relPath, bytes: fs.statSync(path.join(projectDir, relPath)).size });
1483
- }
1484
- };
1485
- walk('');
1486
- return files;
1487
- }
1488
-
1489
- function assembleDocument(projectDir, records, malformedLines = 0) {
1490
- const state = replay(records);
1491
- state.malformedLines = malformedLines;
1492
- const runStart = state.runStart;
1493
- const payload = state.finalize.payload;
1494
- const finalizeTs = state.finalize.ts;
1495
- const dims = { ...state.dims, ...payload.dims };
1496
-
1497
- const events = foldEvents(state.events, runStart.run_id);
1498
- const stages = computeStages(state, finalizeTs);
1499
- const activeMs = stages.reduce((sum, s) => sum + s.active_ms, 0);
1500
- const waitingMs = stages.reduce((sum, s) => sum + s.waiting_ms, 0);
1501
-
1502
- // The headline question this schema exists to answer: how much of the run was deterministic code
1503
- // versus model reasoning. `unattributed_ms` is reported alongside so the split is never mistaken
1504
- // for a complete accounting of active_ms.
1505
- const sumOver = (key) => stages.reduce((sum, s) => sum + (s[key] || 0), 0);
1506
- const agenticMs = sumOver('model_ms');
1507
- const deterministicMs = sumOver('script_ms') + sumOver('api_ms');
1508
- const unattributedMs = sumOver('unattributed_ms');
1509
- const recoveryStages = stages.filter((s) => s.contained_recovery).map((s) => s.stage);
1510
- const cost = computeCost(
1511
- stages,
1512
- dims.model_pricing_snapshot || null,
1513
- (runStart.runtime_env && runStart.runtime_env.model) || (dims.runtime_env && dims.runtime_env.model) || null,
1514
- );
1515
- const timing = {
1516
- active_ms: activeMs,
1517
- waiting_ms: waitingMs,
1518
- agentic_ms: agenticMs,
1519
- deterministic_ms: deterministicMs,
1520
- unattributed_ms: unattributedMs,
1521
- // What fraction of the EXPLAINED time was the model. Null when nothing was metered, rather
1522
- // than a misleading 0.
1523
- agentic_share: agenticMs + deterministicMs > 0
1524
- ? Number((agenticMs / (agenticMs + deterministicMs)).toFixed(4))
1525
- : null,
1526
- api_calls: sumOver('api_calls'),
1527
- api_retries: sumOver('api_retries'),
1528
- stages_with_recovery: recoveryStages,
1529
- };
1530
-
1531
- const eventCountByType = {};
1532
- for (const event of events) {
1533
- const entry = eventCountByType[event.event_type] || { classes: 0, occurrences: 0 };
1534
- entry.classes += 1;
1535
- entry.occurrences += event.count;
1536
- eventCountByType[event.event_type] = entry;
1537
- }
1538
-
1539
- const acceptedClasses = new Set(state.events.map((r) => r.class_key));
1540
- const dropped = state.rejected.filter((r) => !r.class_key || !acceptedClasses.has(r.class_key)).length;
1541
-
1542
- const flags = [];
1543
- const hasEventType = (...types) => events.some((e) => types.includes(e.event_type));
1544
- if (payload.volumes.some((v) => v.failed > 0) && !hasEventType('error')) {
1545
- flags.push('volumes_report_failures_but_no_error_events');
1546
- }
1547
- for (const volume of payload.volumes) {
1548
- if (volume.planned !== null && volume.attempted > volume.planned + volume.already_imported) {
1549
- // Execution diverging from the approved plan must surface mechanically.
1550
- flags.push(`attempted_exceeds_planned:${volume.entity_type}`);
1551
- }
1552
- if (volume.attempted + volume.skipped + volume.already_imported > volume.discovered) {
1553
- flags.push(`volumes_exceed_discovered:${volume.entity_type}`);
1554
- }
1555
- }
1556
- // Metering flags cover data that is WRONG, never data that is merely ABSENT.
1557
- //
1558
- // A run that meters nothing is not unhealthy — it is a runtime that does not report usage, which
1559
- // describes every run recorded before metering existed. Flagging those would fire on ~all
1560
- // historical runs and train readers to ignore the flag list. Absence is already visible, and
1561
- // queryable, in `timing`: `agentic_share` is null and `unattributed_ms` carries the whole stage.
1562
- // So only contradictions are flagged here, and each one implies metering was in use.
1563
- for (const stage of stages) {
1564
- const attributed = METER_DURATION_KEYS.reduce((sum, key) => sum + (stage[key] || 0), 0);
1565
- if (attributed === 0) continue;
1566
- // Partially metered is more dangerous than not metered at all: it looks explained.
1567
- if (stage.unattributed_ms > stage.active_ms * 0.5) {
1568
- flags.push(`stage_mostly_unattributed:${stage.stage}`);
1569
- }
1570
- // Attributed time exceeding elapsed time is impossible, so it means the measurements are wrong:
1571
- // usually the same interval metered twice across a resume, or concurrent work summed serially.
1572
- // unattributed_ms clamps at 0, so without this flag the contradiction would be invisible.
1573
- if (attributed > stage.active_ms * OVER_ATTRIBUTION_TOLERANCE + OVER_ATTRIBUTION_FLOOR_MS) {
1574
- flags.push(`stage_over_attributed:${stage.stage}`);
1575
- }
1576
- }
1577
- if (cost.cost_basis === 'no_pricing_snapshot' && (cost.tokens.input_tokens > 0 || cost.tokens.output_tokens > 0)) {
1578
- flags.push('tokens_recorded_without_pricing_snapshot');
1579
- }
1580
- if (payload.verification.some((v) => v.failed > 0) && !hasEventType('error', 'fidelity_loss')) {
1581
- flags.push('verification_failures_but_no_matching_events');
1582
- }
1583
- if (state.malformedLines > 0) {
1584
- flags.push(`journal_malformed_lines:${state.malformedLines}`);
1585
- }
1586
- if (state.biPushTruncated > 0) {
1587
- flags.push(`bi_push_truncated:${state.biPushTruncated}`);
1588
- }
1589
- // Rows swallowed by RP_TELEMETRY_BI_DISABLED while operator identity was
1590
- // known. Legitimate only for tests/offline dev — on a production run this
1591
- // flag means the kill-switch leaked into the session.
1592
- if (state.biPushSkipped > 0) {
1593
- flags.push('bi_sink_disabled_during_run');
1594
- }
1595
- // No operator identity ever resolved, so this run pushed NOTHING to BI and is
1596
- // invisible to cross-run review — the run is complete and correct on disk, and
1597
- // that is exactly why this needs saying out loud. Before this flag existed the
1598
- // only trace was `sent: 0` inside the finalize payload, which is how four of
1599
- // six local projects went missing unnoticed.
1600
- if (!biSink.isGuid(dims.wix_user_id)) {
1601
- flags.push('operator_identity_unresolved');
1602
- }
1603
- // A stalled wait whose stage has no halt_needs_user event lost the "why" of
1604
- // the stall — the single most actionable signal for where runs stall.
1605
- const haltStages = new Set(events.filter((e) => e.event_type === 'halt_needs_user').map((e) => e.stage));
1606
- const allWaits = [...state.waits, ...(state.openWait ? [{ ...state.openWait, end: finalizeTs }] : [])];
1607
- const flaggedWaitStages = new Set();
1608
- for (const w of allWaits) {
1609
- const waitStage = w.stage || 'config';
1610
- if (tsMs(w.end) - tsMs(w.start) < WAIT_WITHOUT_HALT_FLAG_MS) continue;
1611
- if (haltStages.has(waitStage) || flaggedWaitStages.has(waitStage)) continue;
1612
- flaggedWaitStages.add(waitStage);
1613
- flags.push(`wait_without_halt_event:${waitStage}`);
1614
- }
1615
- // Discovery is where extension classes become known; a null left after a
1616
- // passed discovery usually means the dims call was skipped, not that the
1617
- // source has no extensions (stamp [] explicitly for that).
1618
- const discoveryPassed = state.entries.some((e) => e.stage === 'discovery' && e.outcome === 'passed');
1619
- if (discoveryPassed && (dims.source_extensions === undefined || dims.source_extensions === null)
1620
- && dims.source_platform !== 'csv') {
1621
- flags.push('source_extensions_null_after_discovery');
1622
- }
1623
- // Same rationale for entity classes — and they exist for every source platform,
1624
- // csv included, so no exemption. Stamp [] explicitly for an empty source.
1625
- if (discoveryPassed && (dims.discovered_entity_types === undefined || dims.discovered_entity_types === null)) {
1626
- flags.push('discovered_entity_types_null_after_discovery');
1627
- }
1628
- // The demand signal for unsupported verticals is the volume rows (target
1629
- // "none", discovered/skipped counts). A discovered class with no volume row
1630
- // under-reports that demand invisibly, so cross-check on a completed run —
1631
- // earlier terminal states legitimately finalize before volumes exist.
1632
- if (payload.terminal_state === 'completed' && Array.isArray(dims.discovered_entity_types)) {
1633
- const volumeTypes = new Set(payload.volumes.map((v) => v.entity_type));
1634
- for (const entityType of dims.discovered_entity_types) {
1635
- if (!volumeTypes.has(entityType)) flags.push(`volume_row_missing:${entityType}`);
1636
- }
1637
- }
1638
-
1639
- const evidence = walkEvidence(projectDir);
1640
- const evidenceBytes = evidence.reduce((sum, f) => sum + f.bytes, 0);
1641
- if (evidenceBytes > EVIDENCE_SIZE_FLAG_BYTES) {
1642
- flags.push(`evidence_unusually_large:${Math.round(evidenceBytes / 1024)}kb`);
1643
- }
1644
-
1645
- const rollup = {
1646
- run_id: runStart.run_id,
1647
- project_id: runStart.project_id,
1648
- attempt: runStart.attempt,
1649
- session_count: state.sessionCount,
1650
- schema_version: TELEMETRY_SCHEMA_VERSION,
1651
- skills_version: dims.skills_version || runStart.skills_version || null,
1652
- skills_commit: dims.skills_commit || runStart.skills_commit || null,
1653
- runtime_env: runStart.runtime_env || dims.runtime_env
1654
- ? { ...(runStart.runtime_env || {}), ...(dims.runtime_env || {}) }
1655
- : null,
1656
- site_id: dims.site_id ?? null,
1657
- wix_user_id: dims.wix_user_id ?? null,
1658
- source_platform: dims.source_platform ?? null,
1659
- source_platform_version: dims.source_platform_version ?? null,
1660
- source_site_url: dims.source_site_url ?? null,
1661
- source_extensions: dims.source_extensions ?? null,
1662
- discovered_entity_types: dims.discovered_entity_types ?? null,
1663
- source_acquisition: dims.source_acquisition ?? null,
1664
- delivery_mode: dims.delivery_mode ?? null,
1665
- destination_strategy: dims.destination_strategy ?? null,
1666
- terminal_state: payload.terminal_state,
1667
- stopped_at_stage: payload.stopped_at_stage,
1668
- stages,
1669
- volumes: payload.volumes,
1670
- verification: payload.verification,
1671
- operator_acceptance: payload.operator_acceptance,
1672
- telemetry_health: {
1673
- rejected_calls: state.rejected.length,
1674
- dropped_events: dropped,
1675
- scrub_hits: state.scrubHits,
1676
- bi_push_failures: state.biPushFailures,
1677
- bi_push_skipped: state.biPushSkipped,
1678
- flags,
1679
- },
1680
- event_count_by_type: eventCountByType,
1681
- active_ms: activeMs,
1682
- waiting_ms: waitingMs,
1683
- timing,
1684
- cost,
1685
- transcript_digest: state.transcriptDigest,
1686
- // ALWAYS present, even when the run never touched a key/value table -- an
1687
- // all-zero object says "this run read no key/value data", which is a different and useful
1688
- // fact from a missing field, and only one of the two can be aggregated across runs.
1689
- source_reads: summarizeSourceReads(state),
1690
- model_pricing_snapshot: dims.model_pricing_snapshot ?? null,
1691
- run_started: runStart.ts,
1692
- run_ended: finalizeTs,
1693
- bundle_manifest: ['run-telemetry.json', ...evidence.map((f) => f.path)],
1694
- };
1695
-
1696
- return {
1697
- document: {
1698
- schema_version: TELEMETRY_SCHEMA_VERSION,
1699
- field_tiers: FIELD_TIERS,
1700
- rollup,
1701
- events,
1702
- },
1703
- evidenceBytes,
1704
- evidenceFiles: evidence,
1705
- };
1706
- }
1707
-
1708
- function writeJsonAtomic(filePath, data) {
1709
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
1710
- const tempPath = `${filePath}.${process.pid}.tmp`;
1711
- fs.writeFileSync(tempPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
1712
- fs.renameSync(tempPath, filePath);
1713
- }
1714
-
1715
- function archiveName(state) {
1716
- return `run-${state.runStart.attempt}-${state.runStart.run_id}`;
1717
- }
1718
-
1719
- function archiveFinalizedJournal(projectDir, journal, state) {
1720
- // Deterministic completion of a finalize: rebuild the document from the
1721
- // journal (idempotent), write signal + archive copies, move the journal.
1722
- const assembled = assembleDocument(projectDir, journal.records, journal.malformed);
1723
- const base = archiveName(state);
1724
- fs.mkdirSync(runsDir(projectDir), { recursive: true });
1725
- writeJsonAtomic(signalPath(projectDir), assembled.document);
1726
- writeJsonAtomic(path.join(runsDir(projectDir), `${base}.json`), assembled.document);
1727
- fs.renameSync(journalPath(projectDir), path.join(runsDir(projectDir), `${base}.jsonl`));
1728
- return assembled;
1729
- }
1730
-
1731
- function lastFinalizedAttempt(projectDir) {
1732
- let last = 0;
1733
- const signal = signalPath(projectDir);
1734
- if (fs.existsSync(signal)) {
1735
- try {
1736
- const doc = JSON.parse(fs.readFileSync(signal, 'utf8'));
1737
- if (doc && doc.rollup && Number.isInteger(doc.rollup.attempt)) last = Math.max(last, doc.rollup.attempt);
1738
- } catch {
1739
- // unreadable prior signal file — fall through to the archive scan
1740
- }
1741
- }
1742
- const dir = runsDir(projectDir);
1743
- if (fs.existsSync(dir)) {
1744
- for (const name of fs.readdirSync(dir)) {
1745
- const match = /^run-(\d+)-/.exec(name);
1746
- if (match) last = Math.max(last, Number(match[1]));
1747
- }
1748
- }
1749
- return last;
1750
- }
1751
-
1752
- function resolveSkillsVersion() {
1753
- let dir = __dirname;
1754
- for (let depth = 0; depth < 7; depth += 1) {
1755
- const candidate = path.join(dir, 'VERSION');
1756
- if (fs.existsSync(candidate)) {
1757
- const version = fs.readFileSync(candidate, 'utf8').trim();
1758
- if (/^\d+\.\d+\.\d+$/.test(version)) return version;
1759
- }
1760
- const parent = path.dirname(dir);
1761
- if (parent === dir) break;
1762
- dir = parent;
1763
- }
1764
- return null;
1765
- }
1766
-
1767
- function resolveSkillsCommit() {
1768
- // The exact source commit behind this bundle — the drill-down provenance the
1769
- // coarse, hand-bumped `skills_version` (semver) cannot give. Two sources, in
1770
- // order of authority:
1771
- //
1772
- // 1. `.publish-manifest.json`, stamped by publish-skills-to-wix.sh next to
1773
- // VERSION at publish time. This is the only source that works in a partner
1774
- // runtime (no git there) AND the only correct one once the bundle lives
1775
- // inside the consuming wix/skills repo — where a git probe would return
1776
- // that repo's HEAD, not the replatform source commit. So it wins.
1777
- // 2. Dev-mode git fallback: runs straight from the replatform checkout, where
1778
- // the recorder's own directory is inside the source repo, have no manifest
1779
- // but do have git — so `git -C __dirname rev-parse --short HEAD` is the source
1780
- // commit. Never reached once a manifest is present (case 1).
1781
- let dir = __dirname;
1782
- for (let depth = 0; depth < 7; depth += 1) {
1783
- const candidate = path.join(dir, '.publish-manifest.json');
1784
- if (fs.existsSync(candidate)) {
1785
- try {
1786
- const manifest = JSON.parse(fs.readFileSync(candidate, 'utf8'));
1787
- if (typeof manifest.sourceCommit === 'string' && /^[0-9a-f]{7,40}$/.test(manifest.sourceCommit)) {
1788
- return manifest.sourceCommit;
1789
- }
1790
- } catch {
1791
- // unreadable manifest — fall through to the dev-mode git probe
1792
- }
1793
- }
1794
- const parent = path.dirname(dir);
1795
- if (parent === dir) break;
1796
- dir = parent;
1797
- }
1798
- try {
1799
- const sha = execFileSync('git', ['-C', __dirname, 'rev-parse', '--short', 'HEAD'], {
1800
- encoding: 'utf8',
1801
- stdio: ['ignore', 'pipe', 'ignore'],
1802
- }).trim();
1803
- if (/^[0-9a-f]{7,40}$/.test(sha)) return sha;
1804
- } catch {
1805
- // no git, not a repo, or git unavailable — commit provenance stays null
1806
- }
1807
- return null;
1808
- }
1809
-
1810
- // The operator's Wix user id — the routing key the BI sink refuses to push
1811
- // without (bi-sink rule 3). Auto-stamped like skills_version/skills_commit
1812
- // above, and for the same reason: a value nobody thinks to pass is a value that
1813
- // never arrives. Runs from 2026-07 to 2026-08 shipped with no resolution step
1814
- // at all, so any run whose caller didn't happen to hand over a GUID pushed
1815
- // exactly zero rows and left no trace in BI.
1816
- //
1817
- // Source: `~/.wix/auth/account.json`, written by `wix login`. The migration
1818
- // flow already requires that login (rp-execute-setup mints its site token from
1819
- // the same CLI session), so the file is present by the time telemetry starts,
1820
- // and it is the operator's own artifact — this works identically for a Wix
1821
- // employee and a customer. `userInfo.userId` there is the same GUID that lands
1822
- // in `events.dbo.users_10` as `logged_user_id` (verified 2026-09-01).
1823
- //
1824
- // Deliberately NOT used, for the record:
1825
- // - `wix whoami` returns only an email ("Logged in as <email>"), and BI
1826
- // routing needs a GUID. No public endpoint resolves email -> user id.
1827
- // - the site-mute response's `mutedBy.wixUserId` would work but makes
1828
- // identity depend on a side-effecting call that only happens after
1829
- // provisioning; identity is needed at `start`.
1830
- // - `account.id` / `accountOwner` from the account APIs are the ACCOUNT
1831
- // guid, not the user's — one user can own several accounts, so those
1832
- // would silently attribute runs to the wrong id.
1833
- // The right long-term fix is a public "who am I" endpoint; the capability
1834
- // exists internally (`Oauth2Ng/UserInfo` returns `sub`) but is not exposed.
1835
- //
1836
- // Fail-soft by contract: the path is CLI-internal and undocumented, so any
1837
- // miss (no login, moved file, malformed JSON, non-GUID value) returns null and
1838
- // the run proceeds unattributed but never blocked. Telemetry must never fail a
1839
- // migration.
1840
- //
1841
- // An unattributed run still reaches BI as nothing at all, and that is on
1842
- // purpose: routing such runs under a fabricated subject was implemented and
1843
- // tested against live BI on 2026-09-02 — frog answered 2xx and ingested zero
1844
- // rows, so it reported success while losing the data. Journaling the loss
1845
- // (`no_operator_identity` + the `operator_identity_unresolved` flag) beats
1846
- // faking a subject. Making unattributed runs countable needs a BI-side
1847
- // anonymous event.
1848
- function resolveOperatorIdentity() {
1849
- return require('../../../lib/operator-identity').resolveOperatorIdentity();
1850
- }
1851
-
1852
- // --- BI sink ----------------------------------------------------------
1853
-
1854
- // Push rows to BI and journal any failures/truncation/skips so telemetry loss
1855
- // is visible in telemetry_health, never silent. Never throws — BI unreachable,
1856
- // slow, or rejecting is a telemetry failure, not a run failure. Returns the
1857
- // last journal seq used plus sent/failed/skipped row counts.
1858
- //
1859
- // `throughSeq` is the healing watermark: when every row of a push lands, a
1860
- // `bi_push_ok` record asserts "all pushable journal content with seq <=
1861
- // through_seq is in BI". Anything journaled past the watermark — because a
1862
- // push failed, the sink was disabled, or the journal predates the watermark
1863
- // record type — is backlog, and the next push opportunity re-sends it in full
1864
- // (idempotent: the reviewer dedupes on the natural keys, latest row wins).
1865
- //
1866
- // The transport runs with no journal lock held. Its markers are appended afterwards under a
1867
- // fresh lock with a fresh `seq` from a fresh read (the `seq` argument is only the fallback
1868
- // return value when nothing is appended), keeping the initiating call's `ts` — safe because
1869
- // the activity clock ignores bi_push_* records. `signal` (optional) cancels the transport;
1870
- // `markerLockWaitMs` bounds the marker lock, and a busy marker lock skips the markers (no
1871
- // bi_push_ok → the backlog stays and the next heal re-sends). Returns `aborted` and
1872
- // `markersSkipped` besides the counts.
1873
- async function pushToBi(projectDir, ts, seq, rows, loggedUserId, target, {
1874
- throughSeq, signal, markerLockWaitMs = COMMAND_LOCK_WAIT_MS,
1875
- } = {}) {
1876
- const result = await biSink.pushRows(rows, loggedUserId, signal ? { signal } : undefined);
1877
- const markers = [];
1878
- if (result.skipped > 0) {
1879
- markers.push({ type: 'bi_push_skipped', ts, target, rows: result.skipped });
1880
- }
1881
- if (result.truncated > 0) {
1882
- markers.push({ type: 'bi_push_truncated', ts, target, rows: result.truncated });
1883
- }
1884
- for (const failure of result.failures) {
1885
- markers.push({ type: 'bi_push_failed', ts, target, rows: failure.rows, detail: failure.detail });
1886
- }
1887
- if (throughSeq !== undefined && result.sent > 0 && result.failures.length === 0 && result.skipped === 0) {
1888
- markers.push({ type: 'bi_push_ok', ts, target, through_seq: throughSeq });
1889
- }
1890
- let markersSkipped = false;
1891
- if (markers.length > 0) {
1892
- try {
1893
- seq = withJournalLock(projectDir, () => {
1894
- const fresh = readJournal(projectDir);
1895
- // The journal was archived meanwhile: never create a marker-only journal.
1896
- if (!fresh) return null;
1897
- let next = replay(fresh.records).seq;
1898
- const stamped = markers.map((marker) => {
1899
- next += 1;
1900
- const { type, ...rest } = marker;
1901
- return { type, ts: rest.ts, seq: next, ...rest };
1902
- });
1903
- appendJournalRecords(projectDir, stamped);
1904
- return next;
1905
- }, { waitMs: markerLockWaitMs }) ?? seq;
1906
- } catch (error) {
1907
- if (error && error.code !== 'ELOCKBUSY') throw error;
1908
- markersSkipped = true;
1909
- }
1910
- }
1911
- return {
1912
- seq,
1913
- sent: result.sent,
1914
- failed: result.failures.reduce((sum, f) => sum + f.rows, 0),
1915
- skipped: result.skipped,
1916
- aborted: result.failures.some((f) => f.detail === 'aborted'),
1917
- markersSkipped,
1918
- };
1919
- }
1920
-
1921
- // True when journal content that belongs in BI has no confirming watermark:
1922
- // a prior push failed or was skipped, or the journal predates schema 1.4.0.
1923
- function biBacklog(state) {
1924
- return state.lastBiPushOkSeq === 0 || state.lastBiPushOkSeq < state.lastEventSeq;
1925
- }
1926
-
1927
- // The full idempotent re-push set: the started row plus every folded event
1928
- // class (the finalized rollup only exists at finalize, which builds its own).
1929
- function healRows(state, wixUserId, extraEventRecords = []) {
1930
- const folded = foldEvents([...state.events, ...extraEventRecords], state.runStart.run_id);
1931
- return [biSink.startedRunRow(state.runStart, wixUserId), ...folded.map(biSink.eventRow)];
1932
- }
1933
-
1934
- // The folded BI row for one event class: prior journal records of the class plus
1935
- // the just-appended one. Re-pushes of a growing class are safe — the reviewer
1936
- // dedupes on (run_id, seq), latest row wins with the cumulative count.
1937
- function foldedRowForClass(state, eventRecord) {
1938
- const classRecords = [
1939
- ...state.events.filter((r) => r.class_key === eventRecord.class_key),
1940
- eventRecord,
1941
- ];
1942
- return biSink.eventRow(foldEvents(classRecords, state.runStart.run_id)[0]);
1943
- }
1944
-
1945
- // --- public command API ---------------------------------------------------------
1946
-
1947
- // --- supervisor liveness -------------------------------------------------------
1948
-
1949
- // The command-line supervisor's liveness record (run-controller.js `activePath`). Read directly
1950
- // rather than required, so the recorder never depends on the orchestrator's modules.
1951
- function supervisorActivePath(projectDir) {
1952
- return path.join(projectDir, 'state', 'runner', 'active.json');
1953
- }
1954
-
1955
- function pidAlive(pid) {
1956
- if (!Number.isInteger(pid) || pid <= 0) return false;
1957
- try {
1958
- process.kill(pid, 0);
1959
- return true;
1960
- } catch (error) {
1961
- return Boolean(error && error.code === 'EPERM');
1962
- }
1963
- }
1964
-
1965
- // The live supervisor record, or null: `active.json` exists, has `status: 'running'`, and its
1966
- // `pid` is alive on this host. Missing, unreadable, non-running or dead-PID → null.
1967
- function liveSupervisor(projectDir) {
1968
- try {
1969
- const active = JSON.parse(fs.readFileSync(supervisorActivePath(projectDir), 'utf8'));
1970
- if (!active || active.status !== 'running' || !pidAlive(active.pid)) return null;
1971
- return active;
1972
- } catch {
1973
- return null;
1974
- }
1975
- }
1976
-
1977
- function defaultIsSupervised(projectDir) {
1978
- return liveSupervisor(projectDir) !== null;
1979
- }
1980
-
1981
- // `startedAt` of the live supervisor run, or null when there is no live supervisor or its
1982
- // record carries no parseable `startedAt`.
1983
- function defaultSupervisorStartedAt(projectDir) {
1984
- const active = liveSupervisor(projectDir);
1985
- if (!active || typeof active.startedAt !== 'string' || !Number.isFinite(tsMs(active.startedAt))) return null;
1986
- return active.startedAt;
1987
- }
1988
-
1989
- // --- shared resume records --------------------------------------------------------
1990
-
1991
- // The one place the resume records are built, for both explicit `start()` and automatic
1992
- // `resumeIfStale()`: close an open wait, or impute the dead interval as waiting when the gap
1993
- // since the last ACTIVITY is at least IMPLICIT_WAIT_MIN_MS; then one `session_start` (carrying
1994
- // `trigger` only when automatic); then a `dims` record when there is anything to add. Identity
1995
- // is resolved exactly as `start()` always has when neither the call nor the journal has it.
1996
- // Pure: returns the records with their seqs; the caller appends them in one write.
1997
- function buildResumeRecords(state, ts, dims, { trigger = null, resolveIdentity = resolveOperatorIdentity } = {}) {
1998
- const records = [];
1999
- let seq = state.seq;
2000
- let closedWait = false;
2001
- let imputedWaitMs = 0;
2002
- if (state.openWait) {
2003
- seq += 1;
2004
- records.push({ type: 'wait_end', ts, seq });
2005
- closedWait = true;
2006
- } else if (state.lastActivityTs && tsMs(ts) - tsMs(state.lastActivityTs) >= IMPLICIT_WAIT_MIN_MS) {
2007
- // The run stopped without an open wait (e.g. a crash); the dead interval
2008
- // is waiting time, not active time.
2009
- const stage = state.openStage || state.lastStage || 'config';
2010
- seq += 1;
2011
- records.push({ type: 'wait_start', ts: state.lastActivityTs, seq, stage, imputed: true });
2012
- seq += 1;
2013
- records.push({ type: 'wait_end', ts, seq, imputed: true });
2014
- closedWait = true;
2015
- imputedWaitMs = tsMs(ts) - tsMs(state.lastActivityTs);
2016
- }
2017
- seq += 1;
2018
- records.push(trigger
2019
- ? { type: 'session_start', ts, seq, session: state.sessionCount + 1, trigger }
2020
- : { type: 'session_start', ts, seq, session: state.sessionCount + 1 });
2021
- // A run that opened without identity — begun before the recorder resolved
2022
- // it, or with the CLI not yet logged in — gets it here, so the heal has a
2023
- // route. Journaled as dims like any other late-arriving dimension.
2024
- const identity = resolveMissingIdentity(state, dims, resolveIdentity);
2025
- if (Object.keys(dims).length > 0) {
2026
- seq += 1;
2027
- records.push({ type: 'dims', ts, seq, dims });
2028
- }
2029
- return { records, seq, closedWait, imputedWaitMs, userId: identity };
2030
- }
2031
-
2032
- // Adds a resolved wix_user_id to `dims` when neither the call nor the journal carries one, and
2033
- // returns the identity the heal routes under (call dims first, then the journal), or null.
2034
- function resolveMissingIdentity(state, dims, resolveIdentity) {
2035
- if (!biSink.isGuid(dims.wix_user_id) && !biSink.isGuid(state.dims.wix_user_id)) {
2036
- const resolvedUserId = resolveIdentity();
2037
- if (resolvedUserId) dims.wix_user_id = resolvedUserId;
2038
- }
2039
- if (biSink.isGuid(dims.wix_user_id)) return dims.wix_user_id;
2040
- return biSink.isGuid(state.dims.wix_user_id) ? state.dims.wix_user_id : null;
2041
- }
2042
-
2043
- // Does an explicit `start()` join the current attempt's latest session opener instead of
2044
- // opening another session? Measured from the opener itself, never from the latest activity:
2045
- // recent activity after an old automatic opener does not make that session "just opened".
2046
- // - window: the opener is an automatic (`trigger`-bearing) session_start and
2047
- // 0 <= now - opener.ts < SESSION_ADOPT_WINDOW_MS;
2048
- // - live supervisor run: a live supervisor owns the project with a parseable startedAt, and
2049
- // startedAt - opener.ts < SESSION_ADOPT_WINDOW_MS (one supervisor process is one session).
2050
- function startAdopts(state, ts, supervisorStartedAt) {
2051
- const opener = state.lastOpener;
2052
- if (!opener || typeof opener.ts !== 'string') return false;
2053
- const openerMs = tsMs(opener.ts);
2054
- if (!Number.isFinite(openerMs)) return false;
2055
- const age = tsMs(ts) - openerMs;
2056
- if (opener.type === 'session_start' && opener.trigger && age >= 0 && age < SESSION_ADOPT_WINDOW_MS) return true;
2057
- const startedAt = supervisorStartedAt();
2058
- const startedMs = typeof startedAt === 'string' ? tsMs(startedAt) : NaN;
2059
- // Strictly inside the window, like every other adoption boundary: an opener exactly a
2060
- // window before startedAt is an earlier execution's session, not this run's.
2061
- return Number.isFinite(startedMs) && openerMs > startedMs - SESSION_ADOPT_WINDOW_MS;
2062
- }
2063
-
2064
- // --- public command API ---------------------------------------------------------
2065
-
2066
- // `resolveIdentity` is injectable for one reason: tests need to construct a run
2067
- // with no operator identity, and on any real machine with a logged-in CLI that
2068
- // state is otherwise unreachable. Production callers (the CLI) never pass it.
2069
- // `supervisorStartedAt` is injectable for the same reason (tests need no real supervisor);
2070
- // the default reads the live supervisor's `active.json`.
2071
- async function start(projectDir, dimsInput, {
2072
- now, resolveIdentity = resolveOperatorIdentity, supervisorStartedAt,
2073
- } = {}) {
2074
- const ts = nowIso(now);
2075
- const dims = validateDims(dimsInput);
2076
- const readStartedAt = supervisorStartedAt || (() => defaultSupervisorStartedAt(projectDir));
2077
-
2078
- // Everything local happens under the journal lock from a fresh read, so a concurrent
2079
- // automatic resume and this call can never both open a session or reuse a seq. The BI push
2080
- // runs after the lock is released.
2081
- const plan = withJournalLock(projectDir, () => {
2082
- const journal = readJournal(projectDir);
2083
- if (journal) {
2084
- const state = replay(journal.records);
2085
- if (!state.runStart) {
2086
- throw new ValidationError(['telemetry journal is corrupt: missing run-start record']);
2087
- }
2088
- if (state.finalize) {
2089
- archiveFinalizedJournal(projectDir, journal, state);
2090
- } else if (startAdopts(state, ts, readStartedAt)) {
2091
- // Join the session automatic recovery (or this supervisor run) already opened: no
2092
- // second session_start, no imputed wait. The caller's dims (and resolved identity)
2093
- // still land; the heal runs only when there is backlog.
2094
- const userId = resolveMissingIdentity(state, dims, resolveIdentity);
2095
- let seq = state.seq;
2096
- if (Object.keys(dims).length > 0) {
2097
- seq += 1;
2098
- appendJournal(projectDir, { type: 'dims', ts, seq, dims });
2099
- }
2100
- return {
2101
- kind: 'adopted', state, seq, userId, heal: Boolean(userId) && biBacklog(state),
2102
- };
2103
- } else {
2104
- // Resume: same run_id, same attempt, one more session. Cross-session user
2105
- // latency lands in waiting_ms, never in a phantom second run.
2106
- const built = buildResumeRecords(state, ts, dims, { resolveIdentity });
2107
- appendJournalRecords(projectDir, built.records);
2108
- return {
2109
- kind: 'resumed', state, seq: built.seq, userId: built.userId, heal: Boolean(built.userId),
2110
- };
2111
- }
2112
- }
2113
-
2114
- const attempt = lastFinalizedAttempt(projectDir) + 1;
2115
- const runtimeEnv = {
2116
- os: `${os.platform()} ${os.release()}`,
2117
- node_version: process.version,
2118
- agent_runtime: (dims.runtime_env && dims.runtime_env.agent_runtime) || null,
2119
- model: (dims.runtime_env && dims.runtime_env.model) || null,
2120
- ...(dims.runtime_env || {}),
2121
- };
2122
- delete dims.runtime_env;
2123
- const skillsVersion = dims.skills_version || resolveSkillsVersion();
2124
- delete dims.skills_version;
2125
- const skillsCommit = dims.skills_commit || resolveSkillsCommit();
2126
- delete dims.skills_commit;
2127
- // Same precedence as the provenance stamps above: an explicitly passed
2128
- // wix_user_id wins (a runtime with better provenance than the CLI session),
2129
- // otherwise resolve it ourselves. Unlike those, it stays in `dims` — it is a
2130
- // run dimension the rollup and every BI push key off.
2131
- if (!biSink.isGuid(dims.wix_user_id)) {
2132
- const resolvedUserId = resolveIdentity();
2133
- if (resolvedUserId) dims.wix_user_id = resolvedUserId;
2134
- }
2135
-
2136
- const record = {
2137
- type: 'run_start',
2138
- ts,
2139
- seq: 1,
2140
- run_id: crypto.randomUUID(),
2141
- project_id: path.basename(path.resolve(projectDir)),
2142
- attempt,
2143
- session: 1,
2144
- schema_version: TELEMETRY_SCHEMA_VERSION,
2145
- skills_version: skillsVersion,
2146
- skills_commit: skillsCommit,
2147
- runtime_env: runtimeEnv,
2148
- dims,
2149
- };
2150
- appendJournal(projectDir, record);
2151
- return { kind: 'new', record, attempt, skillsVersion, skillsCommit };
2152
- });
2153
-
2154
- if (plan.kind === 'adopted' || plan.kind === 'resumed') {
2155
- const { state } = plan;
2156
- // Session-boundary heal: re-push the started row and every folded event
2157
- // class whenever operator identity is known — from this call's dims or
2158
- // the journal. A halted run may never reach finalize's full re-push, so
2159
- // the resume is the one reliable moment to recover rows the previous
2160
- // session's transport lost (re-sends are harmless: reviewer-side dedup).
2161
- if (plan.heal) {
2162
- await pushToBi(projectDir, ts, plan.seq, healRows(state, plan.userId),
2163
- plan.userId, 'resume_heal', { throughSeq: plan.seq });
2164
- }
2165
- return {
2166
- resumed: true,
2167
- ...(plan.kind === 'adopted' ? { adopted: true } : {}),
2168
- run_id: state.runStart.run_id,
2169
- attempt: state.runStart.attempt,
2170
- session_count: plan.kind === 'adopted' ? state.sessionCount : state.sessionCount + 1,
2171
- open_stage: state.openStage,
2172
- };
2173
- }
2174
-
2175
- const { record, attempt, skillsVersion, skillsCommit } = plan;
2176
- // Emit `replatform_run` phase:started. Without a GUID operator identity there is
2177
- // no BI route yet — a later dims call carrying wix_user_id (or the finalize
2178
- // re-push) sends it then. Routing an unattributed run under a fabricated subject
2179
- // was tried and verified not to work: BI returns 2xx and ingests
2180
- // nothing, which is a silent loss dressed as a success.
2181
- if (biSink.isGuid(record.dims.wix_user_id)) {
2182
- await pushToBi(projectDir, ts, record.seq,
2183
- [biSink.startedRunRow(record, record.dims.wix_user_id)], record.dims.wix_user_id, 'run_started',
2184
- { throughSeq: record.seq });
2185
- }
2186
- return {
2187
- resumed: false,
2188
- run_id: record.run_id,
2189
- project_id: record.project_id,
2190
- attempt,
2191
- session_count: 1,
2192
- skills_version: skillsVersion,
2193
- skills_commit: skillsCommit,
2194
- };
2195
- }
2196
-
2197
- // --- automatic resume of a stale, unfinalized journal ------------------------------
2198
-
2199
- const RESUME_TRIGGERS = ['route', 'supervisor'];
2200
-
2201
- function validateResumeOptions(options) {
2202
- const errors = [];
2203
- if (!RESUME_TRIGGERS.includes(options.trigger)) {
2204
- errors.push(`trigger must be one of ${RESUME_TRIGGERS.join(', ')} (got: ${options.trigger})`);
2205
- }
2206
- for (const key of ['minGapMs', 'healDeadlineMs', 'lockWaitMs', 'markerLockWaitMs']) {
2207
- const value = options[key];
2208
- if (value !== undefined && !(Number.isInteger(value) && value >= 0)) {
2209
- errors.push(`${key} must be a finite integer >= 0 (got: ${value})`);
2210
- }
2211
- }
2212
- if (options.now !== undefined) {
2213
- const parsed = options.now instanceof Date ? options.now.getTime() : (typeof options.now === 'string' ? Date.parse(options.now) : NaN);
2214
- if (!Number.isFinite(parsed)) errors.push(`now must parse to a finite time (got: ${options.now})`);
2215
- }
2216
- if (options.isSupervised !== undefined && typeof options.isSupervised !== 'function') {
2217
- errors.push('isSupervised must be a function');
2218
- }
2219
- if (errors.length > 0) throw new ValidationError(errors);
2220
- }
2221
-
2222
- // Classification rows 2–8 (row 1, `supervised`, is decided before any read). Returns the
2223
- // no-op reason, or null when the journal should be resumed.
2224
- function classifyForResume(journal, ts, trigger, minGapMs) {
2225
- if (!journal) return { reason: 'absent' };
2226
- const state = replay(journal.records);
2227
- if (!state.runStart) return { reason: 'unreadable' };
2228
- if (state.finalize) return { reason: 'finalized' };
2229
- const lastActivityMs = state.lastActivityTs ? tsMs(state.lastActivityTs) : NaN;
2230
- if (!Number.isFinite(lastActivityMs)) return { reason: 'invalid_timestamp' };
2231
- const nowMs = tsMs(ts);
2232
- if (nowMs < lastActivityMs) return { reason: 'clock_skew' };
2233
- if (trigger === 'supervisor' && state.lastOpener) {
2234
- const openerMs = tsMs(state.lastOpener.ts);
2235
- if (Number.isFinite(openerMs) && nowMs - openerMs >= 0 && nowMs - openerMs < SESSION_ADOPT_WINDOW_MS) {
2236
- return { reason: 'adopted' };
2237
- }
2238
- }
2239
- if (nowMs - lastActivityMs < minGapMs) return { reason: 'recent' };
2240
- return { reason: null, state };
2241
- }
2242
-
2243
- function readJournalOrNull(projectDir) {
2244
- const file = journalPath(projectDir);
2245
- if (!fs.existsSync(file)) return null;
2246
- return readJournalFile(file);
2247
- }
2248
-
2249
- // Called by the two fixed places that notice a resumed execution — the start of a
2250
- // supervisor run (`trigger: 'supervisor'`, once per process) and the manual route script
2251
- // (`trigger: 'route'`, before every stage). Records "a new session started" on an OPEN,
2252
- // readable journal that nobody told; everything else (no journal, only finished ones, a
2253
- // damaged file) belongs to preflight recovery and is left untouched.
2254
- //
2255
- // Only invalid options throw (ValidationError). Every journal, lock, clock and transport
2256
- // condition returns a result, and a no-op performs local reads only: no lock, no write, no
2257
- // network. A resume takes the journal lock, re-classifies from a fresh read, appends its
2258
- // records in one write, releases the lock, then heals BI under a hard, cancelling deadline.
2259
- async function resumeIfStale(projectDir, options = {}) {
2260
- validateResumeOptions(options);
2261
- const {
2262
- trigger,
2263
- now,
2264
- minGapMs = trigger === 'supervisor' ? 0 : ROUTE_RESUME_MIN_GAP_MS,
2265
- healDeadlineMs = HEAL_DEADLINE_MS,
2266
- lockWaitMs = LOCK_WAIT_MS,
2267
- markerLockWaitMs = MARKER_LOCK_WAIT_MS,
2268
- isSupervised = () => defaultIsSupervised(projectDir),
2269
- resolveIdentity = resolveOperatorIdentity,
2270
- } = options;
2271
- const ts = nowIso(now);
2272
- const noop = (reason) => ({ resumed: false, reason, trigger });
2273
-
2274
- // Row 1: while a supervisor run is live, it already recorded this execution.
2275
- if (trigger === 'route' && isSupervised()) return noop('supervised');
2276
-
2277
- // Unlocked pre-read: a no-op stays read-only. Only a candidate for resume takes the lock.
2278
- const pre = classifyForResume(readJournalOrNull(projectDir), ts, trigger, minGapMs);
2279
- if (pre.reason) return noop(pre.reason);
2280
-
2281
- let tx;
2282
- try {
2283
- tx = withJournalLock(projectDir, () => {
2284
- const fresh = classifyForResume(readJournalOrNull(projectDir), ts, trigger, minGapMs);
2285
- if (fresh.reason) return { reason: fresh.reason };
2286
- const built = buildResumeRecords(fresh.state, ts, {}, { trigger, resolveIdentity });
2287
- appendJournalRecords(projectDir, built.records);
2288
- return { reason: null, state: fresh.state, built };
2289
- }, { waitMs: lockWaitMs });
2290
- } catch (error) {
2291
- if (error && error.code === 'ELOCKBUSY') return noop('busy');
2292
- throw error;
2293
- }
2294
- if (tx.reason) return noop(tx.reason);
2295
-
2296
- const { state, built } = tx;
2297
- let heal = 'no_identity';
2298
- if (built.userId) {
2299
- const controller = new AbortController();
2300
- const timer = setTimeout(() => controller.abort(), healDeadlineMs);
2301
- try {
2302
- const pushed = await pushToBi(projectDir, ts, built.seq, healRows(state, built.userId),
2303
- built.userId, 'resume_heal', { throughSeq: built.seq, signal: controller.signal, markerLockWaitMs });
2304
- if (pushed.aborted) heal = 'deadline';
2305
- else if (pushed.skipped > 0) heal = 'disabled';
2306
- else if (pushed.failed === 0) heal = 'ok';
2307
- else if (pushed.sent > 0) heal = 'partial';
2308
- else heal = 'failed';
2309
- } catch {
2310
- heal = controller.signal.aborted ? 'deadline' : 'failed';
2311
- } finally {
2312
- clearTimeout(timer);
2313
- }
2314
- }
2315
- return {
2316
- resumed: true,
2317
- reason: 'resumed',
2318
- trigger,
2319
- run_id: state.runStart.run_id,
2320
- attempt: state.runStart.attempt,
2321
- session_count: state.sessionCount + 1,
2322
- closed_wait: built.closedWait,
2323
- imputed_wait_ms: built.imputedWaitMs,
2324
- heal,
2325
- };
2326
- }
2327
-
2328
- async function dims(projectDir, dimsInput, { now } = {}) {
2329
- const ts = nowIso(now);
2330
- const { state, validated } = withJournalLock(projectDir, () => {
2331
- const { state: fresh } = requireActiveRun(projectDir);
2332
- const checked = validateDims(dimsInput);
2333
- if (Object.keys(checked).length === 0) {
2334
- throw new ValidationError(['dims payload contains no known dimension fields']);
2335
- }
2336
- appendJournal(projectDir, { type: 'dims', ts, seq: fresh.seq + 1, dims: checked });
2337
- return { state: fresh, validated: checked };
2338
- });
2339
- // Identity becoming known mid-run unlocks the BI route: heal the whole
2340
- // backlog (started row + every folded event class), not just the started
2341
- // row — held-back event rows must not wait on a finalize that may never come.
2342
- if (biSink.isGuid(validated.wix_user_id)) {
2343
- await pushToBi(projectDir, ts, state.seq + 1,
2344
- healRows(state, validated.wix_user_id), validated.wix_user_id, 'identity_heal',
2345
- { throughSeq: state.seq + 1 });
2346
- }
2347
- return { recorded: Object.keys(validated) };
2348
- }
2349
-
2350
- async function record(projectDir, eventInput, { now } = {}) {
2351
- const ts = nowIso(now);
2352
- const { state, validated, eventRecord } = withJournalLock(projectDir, () => {
2353
- const { state: fresh } = requireActiveRun(projectDir);
2354
- let checked;
2355
- try {
2356
- checked = validateEvent(eventInput, fresh);
2357
- } catch (error) {
2358
- if (error instanceof ValidationError) {
2359
- // The schema gate refused the call: count it so telemetry loss is never
2360
- // silent, then surface the errors for a retry.
2361
- appendJournal(projectDir, {
2362
- type: 'rejected',
2363
- ts,
2364
- seq: fresh.seq + 1,
2365
- class_key: error.hint || null,
2366
- reasons: error.errors,
2367
- });
2368
- error.hint = 'fix the listed fields and retry the record call';
2369
- }
2370
- throw error;
2371
- }
2372
- const built = {
2373
- type: 'event',
2374
- ts,
2375
- seq: fresh.seq + 1,
2376
- class_key: checked.classKey,
2377
- scrub_hits: checked.scrubHits,
2378
- event: checked.event,
2379
- };
2380
- appendJournal(projectDir, built);
2381
- return { state: fresh, validated: checked, eventRecord: built };
2382
- });
2383
- // Emit the folded `replatform_run_event` row. No identity yet →
2384
- // hold back; the next heal point (resume, identity dims, finalize) sends
2385
- // every folded class. With backlog behind the watermark, this push carries
2386
- // the full heal set instead of just the new class — an event push is often
2387
- // the first working transport after a failure.
2388
- if (biSink.isGuid(state.dims.wix_user_id)) {
2389
- const rows = biBacklog(state)
2390
- ? healRows(state, state.dims.wix_user_id, [eventRecord])
2391
- : [foldedRowForClass(state, eventRecord)];
2392
- await pushToBi(projectDir, ts, eventRecord.seq, rows, state.dims.wix_user_id, 'run_event',
2393
- { throughSeq: eventRecord.seq });
2394
- }
2395
- const priorInClass = state.events.filter((r) => r.class_key === validated.classKey).length;
2396
- return {
2397
- recorded: true,
2398
- seq: state.seq + 1,
2399
- event_type: validated.event.event_type,
2400
- folded_into_existing_class: priorInClass > 0,
2401
- scrub_hits: validated.scrubHits,
2402
- };
2403
- }
2404
-
2405
- function stage(projectDir, action, stageName, { outcome, now } = {}) {
2406
- return withJournalLock(projectDir, () => stageLocked(projectDir, action, stageName, { outcome, now }));
2407
- }
2408
-
2409
- function stageLocked(projectDir, action, stageName, { outcome, now } = {}) {
2410
- const ts = nowIso(now);
2411
- const { state } = requireActiveRun(projectDir);
2412
- const errors = [];
2413
- checkEnum(errors, 'stage', stageName, STAGES);
2414
- if (errors.length > 0) throw new ValidationError(errors);
2415
- if (state.openWait) {
2416
- throw new ValidationError(
2417
- [`a wait interval is open (stage ${state.openWait.stage})`],
2418
- 'call `wait end` before stage boundaries',
2419
- );
2420
- }
2421
- if (action === 'start') {
2422
- if (state.openStage) {
2423
- throw new ValidationError(
2424
- [`stage ${state.openStage} is still open`],
2425
- `call \`stage end ${state.openStage} --outcome <passed|halted|failed|skipped>\` first`,
2426
- );
2427
- }
2428
- appendJournal(projectDir, { type: 'stage_start', ts, seq: state.seq + 1, stage: stageName });
2429
- return { stage: stageName, started: true };
2430
- }
2431
- if (action === 'end') {
2432
- if (state.openStage !== stageName) {
2433
- throw new ValidationError([
2434
- state.openStage
2435
- ? `open stage is ${state.openStage}, not ${stageName}`
2436
- : `no stage is open (last stage: ${state.lastStage || 'none'})`,
2437
- ]);
2438
- }
2439
- const resolvedOutcome = outcome === undefined || outcome === null ? 'passed' : outcome;
2440
- const outcomeErrors = [];
2441
- checkEnum(outcomeErrors, 'outcome', resolvedOutcome, STAGE_OUTCOMES);
2442
- if (outcomeErrors.length > 0) throw new ValidationError(outcomeErrors);
2443
- appendJournal(projectDir, { type: 'stage_end', ts, seq: state.seq + 1, stage: stageName, outcome: resolvedOutcome });
2444
- return { stage: stageName, ended: true, outcome: resolvedOutcome };
2445
- }
2446
- throw new ValidationError([`stage action must be start or end (got: ${action})`]);
2447
- }
2448
-
2449
- // Attach measured latency/token counts to a stage. Separate from `stage end` on purpose: a stage may
2450
- // be metered several times (each generated script reports its own invocation), and token counts are
2451
- // known by the agent runtime rather than by whatever closed the stage.
2452
- function meter(projectDir, input, { now } = {}) {
2453
- const ts = nowIso(now);
2454
- const payload = withJournalLock(projectDir, () => {
2455
- const { state } = requireActiveRun(projectDir);
2456
- const checked = validateMeter(input, { openStage: state.openStage, lastStage: state.lastStage });
2457
- appendJournal(projectDir, { type: 'meter', ts, seq: state.seq + 1, stage: checked.stage, meter: checked });
2458
- return checked;
2459
- });
2460
- const recorded = Object.keys(payload).filter((k) => k !== 'stage');
2461
- return { metered: true, stage: payload.stage, fields: recorded };
2462
- }
2463
-
2464
- // Counts key/value source reads for the run. Accumulates across calls, so a
2465
- // caller may report per-batch rather than having to hold a running total itself.
2466
- function sourceRead(projectDir, input, { now } = {}) {
2467
- const ts = nowIso(now);
2468
- const payload = withJournalLock(projectDir, () => {
2469
- const { state } = requireActiveRun(projectDir);
2470
- const checked = validateSourceRead(input);
2471
- appendJournal(projectDir, { type: 'source_read', ts, seq: state.seq + 1, source_read: checked });
2472
- return checked;
2473
- });
2474
- return { recorded: true, fields: Object.keys(payload) };
2475
- }
2476
-
2477
- /**
2478
- * The run's totals, as the source-read accounting defines them. `redacted_values` is summed from EVERY
2479
- * response's redactionMetadata, not only key/value reads -- the guard applies to every read,
2480
- * and an earlier draft of the response contract made that total impossible to compute
2481
- * correctly by nesting the counter inside a key/value-only object.
2482
- */
2483
- function summarizeSourceReads(state) {
2484
- const totals = { discovery_queries: 0, scoped_queries: 0, tier_refusals: 0, redacted_values: 0 };
2485
- const discoveryTables = new Set();
2486
- const scopedTables = new Set();
2487
- for (const read of state.sourceReads || []) {
2488
- for (const key of SOURCE_READ_COUNT_KEYS) totals[key] += read[key] || 0;
2489
- for (const table of read.discovery_tables || []) discoveryTables.add(table);
2490
- for (const table of read.scoped_tables || []) scopedTables.add(table);
2491
- }
2492
- return {
2493
- ...totals,
2494
- discovery_tables: [...discoveryTables].sort(),
2495
- scoped_tables: [...scopedTables].sort(),
2496
- };
2497
- }
2498
-
2499
- // Records the one `transcript_digest` per run: a script-computed
2500
- // parse of the Claude Code session transcript, never an agent self-report. No
2501
- // BI push here — the fields ride to BI inside `finalize`'s existing push, once
2502
- // `rollup.transcript_digest` carries them. A second call in the same run
2503
- // overwrites the prior one (replay keeps the latest), which is what an offline
2504
- // re-run against a more complete transcript is for.
2505
- async function transcriptDigest(projectDir, payload, { now } = {}) {
2506
- const ts = nowIso(now);
2507
- const seq = withJournalLock(projectDir, () => {
2508
- const { state } = requireActiveRun(projectDir);
2509
- const validated = validateTranscriptDigest(payload);
2510
- const next = state.seq + 1;
2511
- appendJournal(projectDir, { type: 'transcript_digest', ts, seq: next, run_id: state.runStart.run_id, ...validated });
2512
- return next;
2513
- });
2514
- return { recorded: true, seq };
2515
- }
2516
-
2517
- async function wait(projectDir, action, { now, haltSubtype, skill, what } = {}) {
2518
- const ts = nowIso(now);
2519
- const first = withJournalLock(projectDir, () => {
2520
- const { state } = requireActiveRun(projectDir);
2521
- if (action === 'start') {
2522
- if (state.openWait) {
2523
- throw new ValidationError(['a wait interval is already open']);
2524
- }
2525
- const attributedStage = state.openStage || state.lastStage || 'config';
2526
- if (haltSubtype !== undefined && haltSubtype !== null) {
2527
- // The paired halt_needs_user event is emitted mechanically so a stalled
2528
- // run never loses the "why" to a missed record call.
2529
- const validated = validateEvent({
2530
- event_type: 'halt_needs_user',
2531
- subtype: haltSubtype,
2532
- stage: attributedStage,
2533
- skill: skill || 'wix-replatform',
2534
- severity: 'blocking',
2535
- what_happened: what || `Run halted at ${attributedStage} waiting on the user (${haltSubtype}).`,
2536
- }, state);
2537
- const eventRecord = {
2538
- type: 'event', ts, seq: state.seq + 1, class_key: validated.classKey, scrub_hits: validated.scrubHits, event: validated.event,
2539
- };
2540
- appendJournal(projectDir, eventRecord);
2541
- return { kind: 'halt', state, attributedStage, eventRecord };
2542
- }
2543
- if (skill !== undefined || what !== undefined) {
2544
- throw new ValidationError(['--skill/--what on wait start are only valid together with --halt <subtype>']);
2545
- }
2546
- appendJournal(projectDir, { type: 'wait_start', ts, seq: state.seq + 1, stage: attributedStage });
2547
- return { kind: 'started', attributedStage };
2548
- }
2549
- if (action === 'end') {
2550
- if (!state.openWait) {
2551
- throw new ValidationError(['no wait interval is open']);
2552
- }
2553
- appendJournal(projectDir, { type: 'wait_end', ts, seq: state.seq + 1 });
2554
- return { kind: 'ended', waitedMs: Math.max(0, tsMs(ts) - tsMs(state.openWait.start)) };
2555
- }
2556
- throw new ValidationError([`wait action must be start or end (got: ${action})`]);
2557
- });
2558
- if (first.kind === 'ended') return { waiting: false, waited_ms: first.waitedMs };
2559
- if (first.kind === 'started') return { waiting: true, stage: first.attributedStage, halt_recorded: false };
2560
-
2561
- const { state, attributedStage, eventRecord } = first;
2562
- if (biSink.isGuid(state.dims.wix_user_id)) {
2563
- const rows = biBacklog(state)
2564
- ? healRows(state, state.dims.wix_user_id, [eventRecord])
2565
- : [foldedRowForClass(state, eventRecord)];
2566
- await pushToBi(projectDir, ts, eventRecord.seq, rows, state.dims.wix_user_id, 'run_event',
2567
- { throughSeq: eventRecord.seq });
2568
- }
2569
- // The wait opens after the push's markers, with a seq from a fresh read.
2570
- withJournalLock(projectDir, () => {
2571
- const { state: fresh } = requireActiveRun(projectDir);
2572
- appendJournal(projectDir, { type: 'wait_start', ts, seq: fresh.seq + 1, stage: attributedStage });
2573
- });
2574
- return { waiting: true, stage: attributedStage, halt_recorded: true };
2575
- }
2576
-
2577
- async function finalize(projectDir, rollupInput, { now } = {}) {
2578
- const ts = nowIso(now);
2579
- const { state, payload, provisionalJournal, noIdentityRows } = withJournalLock(projectDir, () => {
2580
- const { state: fresh } = requireActiveRun(projectDir);
2581
- const checked = validateFinalizeInput(rollupInput, fresh);
2582
- const records = [];
2583
- let seq = fresh.seq;
2584
- if (fresh.openWait) {
2585
- seq += 1;
2586
- records.push({ type: 'wait_end', ts, seq });
2587
- }
2588
- if (fresh.openStage) {
2589
- seq += 1;
2590
- records.push({
2591
- type: 'stage_end',
2592
- ts,
2593
- seq,
2594
- stage: fresh.openStage,
2595
- outcome: OUTCOME_FOR_TERMINAL[checked.terminal_state],
2596
- });
2597
- }
2598
- seq += 1;
2599
- records.push({ type: 'finalize', ts, seq, payload: checked });
2600
- appendJournalRecords(projectDir, records);
2601
- let provisionalJournal = readJournal(projectDir);
2602
- // A no-identity outcome has no transport call to await, so persist its
2603
- // failure marker in this same transaction as finalize. Otherwise another
2604
- // process can archive the finalized journal before the later archival
2605
- // transaction gets a chance to record the loss.
2606
- let noIdentityRows = 0;
2607
- if (!biSink.disabled()) {
2608
- const provisional = assembleDocument(projectDir, provisionalJournal.records, provisionalJournal.malformed);
2609
- if (!biSink.isGuid(provisional.document.rollup.wix_user_id)) {
2610
- noIdentityRows = provisional.document.events.length + 2;
2611
- appendJournal(projectDir, {
2612
- type: 'bi_push_failed', ts, seq: replay(provisionalJournal.records).seq + 1,
2613
- target: 'finalize', rows: noIdentityRows, detail: 'no_operator_identity',
2614
- });
2615
- provisionalJournal = readJournal(projectDir);
2616
- }
2617
- }
2618
- return { state: fresh, payload: checked, provisionalJournal, noIdentityRows };
2619
- });
2620
- const finalizeSeq = replay(provisionalJournal.records).seq;
2621
-
2622
- // BI push before archival, so a failed push is journaled into the run and
2623
- // lands in the archived telemetry_health. The full re-push (started row +
2624
- // every folded event class + the finalized rollup) heals any mid-run failure
2625
- // or identity-not-yet-known hold-back: the stream is append-only and the
2626
- // reviewer dedupes on the natural keys, latest row wins. The pushed rollup's
2627
- // own health can't include this push's outcome (it is assembled first) — if
2628
- // the push fails there is no BI row at all, and the local document, which is
2629
- // authoritative, records the failure.
2630
- let biPush = { sent: 0, failed: 0, skipped: 0 };
2631
- {
2632
- const provisional = assembleDocument(projectDir, provisionalJournal.records, provisionalJournal.malformed);
2633
- const rollup = provisional.document.rollup;
2634
- if (biSink.isGuid(rollup.wix_user_id)) {
2635
- const rows = [
2636
- biSink.startedRunRow(replay(provisionalJournal.records).runStart, rollup.wix_user_id),
2637
- ...provisional.document.events.map(biSink.eventRow),
2638
- biSink.finalizedRunRow(rollup),
2639
- ];
2640
- // A disabled sink lands here too: pushRows reports the rows as skipped
2641
- // and pushToBi journals them, so the kill-switch is never silent.
2642
- const pushed = await pushToBi(projectDir, ts, finalizeSeq, rows, rollup.wix_user_id, 'finalize',
2643
- { throughSeq: finalizeSeq });
2644
- biPush = { sent: pushed.sent, failed: pushed.failed, skipped: pushed.skipped };
2645
- } else if (!biSink.disabled()) {
2646
- // No operator identity by run end: the run is unroutable in BI. Journal
2647
- // it as a push failure — this is real telemetry loss, not a hold-back,
2648
- // and recording it is strictly better than faking a routable subject
2649
- // (verified 2026-09-02: fabricated subjects ingest nothing).
2650
- biPush = { sent: 0, failed: noIdentityRows, skipped: 0 };
2651
- }
2652
- }
2653
-
2654
- const { finalState, assembled } = withJournalLock(projectDir, () => {
2655
- let finalJournal = readJournal(projectDir);
2656
- let current = finalJournal ? replay(finalJournal.records) : null;
2657
- if (!current || !current.runStart || current.runStart.run_id !== state.runStart.run_id || !current.finalize) {
2658
- // Another writer already completed this finalize's archival (for example a `start`
2659
- // that found the finalize record); read the archived journal instead.
2660
- finalJournal = readJournalFile(path.join(runsDir(projectDir), `${archiveName(state)}.jsonl`));
2661
- current = replay(finalJournal.records);
2662
- return {
2663
- finalState: current,
2664
- assembled: assembleDocument(projectDir, finalJournal.records, finalJournal.malformed),
2665
- };
2666
- }
2667
- return { finalState: current, assembled: archiveFinalizedJournal(projectDir, finalJournal, current) };
2668
- });
2669
-
2670
- const health = assembled.document.rollup.telemetry_health;
2671
- return {
2672
- finalized: true,
2673
- run_id: finalState.runStart.run_id,
2674
- attempt: finalState.runStart.attempt,
2675
- terminal_state: payload.terminal_state,
2676
- bi_push: biPush,
2677
- signal_file: 'run-telemetry.json',
2678
- event_classes: assembled.document.events.length,
2679
- telemetry_health: health,
2680
- evidence_bytes: assembled.evidenceBytes,
2681
- ...(assembled.evidenceBytes > EVIDENCE_SIZE_FLAG_BYTES
2682
- ? {
2683
- evidence_breakdown: assembled.evidenceFiles
2684
- .sort((a, b) => b.bytes - a.bytes)
2685
- .slice(0, 10),
2686
- }
2687
- : {}),
2688
- };
2689
- }
2690
-
2691
- async function rebuild(projectDir, { attempt, push } = {}) {
2692
- // Re-assemble a finalized run's signal document from its archived journal.
2693
- // The document is a pure function of the journal, so recorder fixes (fold
2694
- // semantics, manifest rules, health flags) can be applied to past runs
2695
- // without touching the captured record itself. With `push`, the rebuilt run
2696
- // is re-emitted to BI (outage backfill / post-fix re-push) — idempotent by
2697
- // the reviewer's query-time dedup on the natural keys.
2698
- const dir = runsDir(projectDir);
2699
- const archived = fs.existsSync(dir)
2700
- ? fs.readdirSync(dir)
2701
- .map((name) => {
2702
- const match = /^run-(\d+)-.+\.jsonl$/.exec(name);
2703
- return match ? { attempt: Number(match[1]), name } : null;
2704
- })
2705
- .filter(Boolean)
2706
- : [];
2707
- if (archived.length === 0) {
2708
- throw new ValidationError(
2709
- ['no archived run journals to rebuild from'],
2710
- 'rebuild re-assembles a finalized signal document from telemetry/runs/run-<attempt>-<run_id>.jsonl',
2711
- );
2712
- }
2713
- const latestAttempt = archived.reduce((max, a) => Math.max(max, a.attempt), 0);
2714
- const target = attempt === undefined || attempt === null
2715
- ? archived.find((a) => a.attempt === latestAttempt)
2716
- : archived.find((a) => a.attempt === attempt);
2717
- if (!target) {
2718
- throw new ValidationError([`no archived journal for attempt ${attempt} (have: ${archived.map((a) => a.attempt).join(', ')})`]);
2719
- }
2720
- const journal = readJournalFile(path.join(dir, target.name));
2721
- const state = replay(journal.records);
2722
- if (!state.runStart || !state.finalize) {
2723
- throw new ValidationError([`archived journal ${target.name} is not a finalized run`]);
2724
- }
2725
- const assembled = assembleDocument(projectDir, journal.records, journal.malformed);
2726
- writeJsonAtomic(path.join(dir, `${archiveName(state)}.json`), assembled.document);
2727
- const signalUpdated = target.attempt === latestAttempt;
2728
- if (signalUpdated) {
2729
- writeJsonAtomic(signalPath(projectDir), assembled.document);
2730
- }
2731
- // The archived journal is immutable, so a backfill push's outcome cannot be
2732
- // journaled into the run — it is reported here, to the operator running the
2733
- // explicit backfill, instead.
2734
- let biPush;
2735
- if (push) {
2736
- const rollup = assembled.document.rollup;
2737
- const rows = [
2738
- biSink.startedRunRow(state.runStart, rollup.wix_user_id),
2739
- ...assembled.document.events.map(biSink.eventRow),
2740
- biSink.finalizedRunRow(rollup),
2741
- ];
2742
- const result = await biSink.pushRows(rows, rollup.wix_user_id);
2743
- biPush = {
2744
- sent: result.sent,
2745
- failed: result.failures.reduce((sum, f) => sum + f.rows, 0),
2746
- ...(result.failures.length > 0 ? { failure_details: [...new Set(result.failures.map((f) => f.detail))] } : {}),
2747
- };
2748
- }
2749
- return {
2750
- rebuilt: true,
2751
- run_id: state.runStart.run_id,
2752
- attempt: target.attempt,
2753
- signal_file_updated: signalUpdated,
2754
- event_classes: assembled.document.events.length,
2755
- telemetry_health: assembled.document.rollup.telemetry_health,
2756
- ...(biPush ? { bi_push: biPush } : {}),
2757
- };
2758
- }
2759
-
2760
- function status(projectDir) {
2761
- const journal = readJournal(projectDir);
2762
- if (!journal) {
2763
- const lastAttempt = lastFinalizedAttempt(projectDir);
2764
- return { active: false, finalized_attempts: lastAttempt };
2765
- }
2766
- const state = replay(journal.records);
2767
- return {
2768
- active: !state.finalize,
2769
- run_id: state.runStart ? state.runStart.run_id : null,
2770
- attempt: state.runStart ? state.runStart.attempt : null,
2771
- session_count: state.sessionCount,
2772
- open_stage: state.openStage,
2773
- open_wait: Boolean(state.openWait),
2774
- events_recorded: state.events.length,
2775
- distinct_classes: new Set(state.events.map((r) => r.class_key)).size,
2776
- rejected_calls: state.rejected.length,
2777
- last_seq: state.seq,
2778
- bi_push_ok_seq: state.lastBiPushOkSeq,
2779
- bi_backlog: state.runStart ? biBacklog(state) : true,
2780
- };
2781
- }
2782
-
2783
- module.exports = {
2784
- TELEMETRY_SCHEMA_VERSION,
2785
- STAGES,
2786
- STAGE_OUTCOMES,
2787
- TERMINAL_STATES,
2788
- SEVERITIES,
2789
- EVENT_SUBTYPES,
2790
- FIELD_TIERS,
2791
- FREE_TEXT_MAX,
2792
- SHAPES_KEPT_PER_CLASS,
2793
- ValidationError,
2794
- scrubText,
2795
- validateDims,
2796
- validateEvent,
2797
- validateFinalizeInput,
2798
- validateTranscriptDigest,
2799
- journalPath,
2800
- signalPath,
2801
- runsDir,
2802
- readJournal,
2803
- replay,
2804
- start,
2805
- resumeIfStale,
2806
- withJournalLock,
2807
- ROUTE_RESUME_MIN_GAP_MS,
2808
- SESSION_ADOPT_WINDOW_MS,
2809
- HEAL_DEADLINE_MS,
2810
- LOCK_WAIT_MS,
2811
- MARKER_LOCK_WAIT_MS,
2812
- IMPLICIT_WAIT_MIN_MS,
2813
- biBacklog,
2814
- dims,
2815
- record,
2816
- stage,
2817
- meter,
2818
- sourceRead,
2819
- summarizeSourceReads,
2820
- validateSourceRead,
2821
- SOURCE_READ_COUNT_KEYS,
2822
- transcriptDigest,
2823
- wait,
2824
- finalize,
2825
- rebuild,
2826
- status,
2827
- validateMeter,
2828
- computeCost,
2829
- METER_DURATION_KEYS,
2830
- METER_COUNT_KEYS,
2831
- };