@wix/agent-skills 1.33.1 → 1.35.0

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 (619) hide show
  1. package/README.md +3 -0
  2. package/package.json +1 -1
  3. package/skills/wix-export/SKILL.md +83 -0
  4. package/skills/wix-harmony-editor/.publish-manifest.json +8 -0
  5. package/skills/wix-harmony-editor/COPY-SITE.md +494 -0
  6. package/skills/wix-harmony-editor/SKILL.md +317 -0
  7. package/skills/wix-harmony-editor/scripts/copy/build-description.mjs +249 -0
  8. package/skills/wix-harmony-editor/scripts/copy/check-description.mjs +225 -0
  9. package/skills/wix-harmony-editor/scripts/copy/check-mirror.mjs +89 -0
  10. package/skills/wix-harmony-editor/scripts/copy/compare-preview.mjs +76 -0
  11. package/skills/wix-harmony-editor/scripts/copy/deploy-description.mjs +212 -0
  12. package/skills/wix-harmony-editor/scripts/copy/desc-hash.mjs +44 -0
  13. package/skills/wix-harmony-editor/scripts/copy/editor-run.mjs +45 -0
  14. package/skills/wix-harmony-editor/scripts/copy/export-page.mjs +53 -0
  15. package/skills/wix-harmony-editor/scripts/copy/find-interactions.mjs +76 -0
  16. package/skills/wix-harmony-editor/scripts/copy/hide-scrollbars.mjs +16 -0
  17. package/skills/wix-harmony-editor/scripts/copy/import-media.mjs +150 -0
  18. package/skills/wix-harmony-editor/scripts/copy/link-url.mjs +27 -0
  19. package/skills/wix-harmony-editor/scripts/copy/lint-api.mjs +56 -0
  20. package/skills/wix-harmony-editor/scripts/copy/measure-source.mjs +211 -0
  21. package/skills/wix-harmony-editor/scripts/copy/merge-mirrors.mjs +29 -0
  22. package/skills/wix-harmony-editor/scripts/copy/mirror-site.mjs +92 -0
  23. package/skills/wix-harmony-editor/scripts/copy/mobile-stage.mjs +56 -0
  24. package/skills/wix-harmony-editor/scripts/copy/preview-widths.mjs +73 -0
  25. package/skills/wix-harmony-editor/scripts/copy/render-plate.mjs +67 -0
  26. package/skills/wix-harmony-editor/scripts/copy/serve-mirror.mjs +102 -0
  27. package/skills/wix-harmony-editor/scripts/copy/snapshot-capabilities.mjs +40 -0
  28. package/skills/wix-harmony-editor/scripts/copy/split-sections.mjs +191 -0
  29. package/skills/wix-harmony-editor/scripts/copy/upload-media-manager.mjs +61 -0
  30. package/skills/wix-harmony-editor/scripts/launch.sh +359 -0
  31. package/skills/wix-headless/SKILL.md +7 -2
  32. package/skills/wix-headless/references/CAPABILITIES.md +6 -1
  33. package/skills/wix-headless/references/DISCOVERY.md +2 -1
  34. package/skills/wix-headless/references/SDK_HANDOFF.md +6 -1
  35. package/skills/wix-headless/references/SEED.md +3 -2
  36. package/skills/wix-headless/references/SETUP.md +1 -0
  37. package/skills/wix-headless/references/inline-recipes/experience-donations.md +102 -0
  38. package/skills/wix-headless/references/inline-recipes/how-to-code-donations.md +146 -0
  39. package/skills/wix-headless/references/inline-recipes/setup-donations.md +15 -0
  40. package/skills/wix-headless/references/managed/CREATE.md +1 -1
  41. package/skills/wix-headless-kit/SKILL.md +110 -21
  42. package/skills/wix-headless-kit/guides/api-run.md +15 -13
  43. package/skills/wix-headless-kit/guides/existing-site.md +31 -0
  44. package/skills/wix-headless-kit/guides/migration.md +49 -17
  45. package/skills/wix-headless-kit/install/agents-md.mjs +1 -1
  46. package/skills/wix-headless-kit/install/context.mjs +61 -6
  47. package/skills/wix-headless-kit/install/pins.json +1 -1
  48. package/skills/wix-headless-kit/install/setup.mjs +107 -12
  49. package/skills/wix-headless-replatform/.publish-manifest.json +8 -0
  50. package/skills/wix-headless-replatform/SKILL.md +116 -0
  51. package/skills/wix-headless-replatform/agents/openai.yaml +6 -0
  52. package/skills/wix-headless-replatform/references/control-and-visual-fidelity-contract.md +43 -0
  53. package/skills/wix-headless-replatform/references/extraction-schema.md +187 -0
  54. package/skills/wix-headless-replatform/references/interaction-runtime-contract.md +137 -0
  55. package/skills/wix-headless-replatform/references/layout-blueprint-contract.md +41 -0
  56. package/skills/wix-headless-replatform/references/output-contract.md +93 -0
  57. package/skills/wix-headless-replatform/references/post-build-gap-contract.md +125 -0
  58. package/skills/wix-headless-replatform/references/qa-checklist.md +83 -0
  59. package/skills/wix-headless-replatform/references/safety.md +19 -0
  60. package/skills/wix-headless-replatform/references/ui-normalization-contract.md +70 -0
  61. package/skills/wix-headless-replatform/references/workflow.md +157 -0
  62. package/skills/wix-headless-replatform/registry/README.md +20 -0
  63. package/skills/wix-headless-replatform/registry/approvals/button/shadcn-default-ac60ef5.json +28 -0
  64. package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/button.tsx +54 -0
  65. package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/capabilities.json +60 -0
  66. package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/contract.md +48 -0
  67. package/skills/wix-headless-replatform/registry/licenses/shadcn-ui-ac60ef5-MIT.txt +21 -0
  68. package/skills/wix-headless-replatform/registry/registry.json +36 -0
  69. package/skills/wix-headless-replatform/resources/rp-facelift/SKILL.md +32 -0
  70. package/skills/wix-headless-replatform/resources/rp-project-context/SKILL.md +62 -0
  71. package/skills/wix-headless-replatform/resources/rp-qa-gap-loop/SKILL.md +64 -0
  72. package/skills/wix-headless-replatform/resources/rp-source-evidence/SKILL.md +42 -0
  73. package/skills/wix-headless-replatform/resources/rp-ui-implementation/SKILL.md +73 -0
  74. package/skills/wix-headless-replatform/schemas/build-plan.schema.json +10 -0
  75. package/skills/wix-headless-replatform/schemas/candidate.schema.json +19 -0
  76. package/skills/wix-headless-replatform/schemas/common.schema.json +33 -0
  77. package/skills/wix-headless-replatform/schemas/decision-patch.schema.json +10 -0
  78. package/skills/wix-headless-replatform/schemas/decision-request.schema.json +10 -0
  79. package/skills/wix-headless-replatform/schemas/extraction-gap.schema.json +28 -0
  80. package/skills/wix-headless-replatform/schemas/extraction-manifest.schema.json +34 -0
  81. package/skills/wix-headless-replatform/schemas/final-report.schema.json +10 -0
  82. package/skills/wix-headless-replatform/schemas/foundation.schema.json +11 -0
  83. package/skills/wix-headless-replatform/schemas/metadata.schema.json +11 -0
  84. package/skills/wix-headless-replatform/schemas/observation-packet.schema.json +17 -0
  85. package/skills/wix-headless-replatform/schemas/page-capture.schema.json +11 -0
  86. package/skills/wix-headless-replatform/schemas/page-resolution.schema.json +33 -0
  87. package/skills/wix-headless-replatform/schemas/shared-chrome.schema.json +11 -0
  88. package/skills/wix-headless-replatform/schemas/unit.schema.json +40 -0
  89. package/skills/wix-headless-replatform/scripts/assemble-extraction.mjs +844 -0
  90. package/skills/wix-headless-replatform/scripts/benchmark-extraction.mjs +163 -0
  91. package/skills/wix-headless-replatform/scripts/browser-extraction-preflight.mjs +42 -0
  92. package/skills/wix-headless-replatform/scripts/build-extracted-homepage.mjs +228 -0
  93. package/skills/wix-headless-replatform/scripts/clear-wix-starter.mjs +87 -0
  94. package/skills/wix-headless-replatform/scripts/create-wix-project.mjs +505 -0
  95. package/skills/wix-headless-replatform/scripts/discover-urls.mjs +142 -0
  96. package/skills/wix-headless-replatform/scripts/extract-assets.mjs +140 -0
  97. package/skills/wix-headless-replatform/scripts/extract-design-system.mjs +637 -0
  98. package/skills/wix-headless-replatform/scripts/extract-interactions.mjs +1903 -0
  99. package/skills/wix-headless-replatform/scripts/extract-page.mjs +2461 -0
  100. package/skills/wix-headless-replatform/scripts/extract-seo.mjs +25 -0
  101. package/skills/wix-headless-replatform/scripts/extract-tokens.mjs +63 -0
  102. package/skills/wix-headless-replatform/scripts/finalize-extraction-report.mjs +126 -0
  103. package/skills/wix-headless-replatform/scripts/finalize-gap-review.mjs +180 -0
  104. package/skills/wix-headless-replatform/scripts/finalize-gap-review.test.mjs +98 -0
  105. package/skills/wix-headless-replatform/scripts/finalize-migration-website.mjs +103 -0
  106. package/skills/wix-headless-replatform/scripts/generate-control-state-contract.mjs +176 -0
  107. package/skills/wix-headless-replatform/scripts/generate-design-md.mjs +261 -0
  108. package/skills/wix-headless-replatform/scripts/generate-extraction-validators.mjs +25 -0
  109. package/skills/wix-headless-replatform/scripts/generate-invoice-download-backend.mjs +342 -0
  110. package/skills/wix-headless-replatform/scripts/generate-layout-blueprint.mjs +140 -0
  111. package/skills/wix-headless-replatform/scripts/generate-repeater-cms.mjs +22 -0
  112. package/skills/wix-headless-replatform/scripts/generate-route-plan.mjs +143 -0
  113. package/skills/wix-headless-replatform/scripts/generate-scene-contract.mjs +288 -0
  114. package/skills/wix-headless-replatform/scripts/generate-ui-normalization.mjs +266 -0
  115. package/skills/wix-headless-replatform/scripts/generate-visual-assets.mjs +111 -0
  116. package/skills/wix-headless-replatform/scripts/lib/browser-tooling.mjs +570 -0
  117. package/skills/wix-headless-replatform/scripts/lib/common.mjs +329 -0
  118. package/skills/wix-headless-replatform/scripts/lib/component-capabilities.mjs +121 -0
  119. package/skills/wix-headless-replatform/scripts/lib/component-registry.mjs +282 -0
  120. package/skills/wix-headless-replatform/scripts/lib/content-boundary.mjs +61 -0
  121. package/skills/wix-headless-replatform/scripts/lib/extraction-contract.mjs +374 -0
  122. package/skills/wix-headless-replatform/scripts/lib/extraction-detectors.mjs +243 -0
  123. package/skills/wix-headless-replatform/scripts/lib/extraction-validators.generated.mjs +1139 -0
  124. package/skills/wix-headless-replatform/scripts/lib/font-contract.mjs +326 -0
  125. package/skills/wix-headless-replatform/scripts/lib/frontend-automation-state.mjs +184 -0
  126. package/skills/wix-headless-replatform/scripts/lib/gap-analysis.mjs +396 -0
  127. package/skills/wix-headless-replatform/scripts/lib/html-extract.mjs +168 -0
  128. package/skills/wix-headless-replatform/scripts/lib/interaction-bootstrap.mjs +10 -0
  129. package/skills/wix-headless-replatform/scripts/lib/interaction-runtime.mjs +194 -0
  130. package/skills/wix-headless-replatform/scripts/lib/interaction-timeline.mjs +348 -0
  131. package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/classifiers.mjs +141 -0
  132. package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/geometry.mjs +43 -0
  133. package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/vocabulary.mjs +18 -0
  134. package/skills/wix-headless-replatform/scripts/lib/ui-normalization.css +64 -0
  135. package/skills/wix-headless-replatform/scripts/optimize-svg.mjs +31 -0
  136. package/skills/wix-headless-replatform/scripts/post-build-gap.mjs +358 -0
  137. package/skills/wix-headless-replatform/scripts/provision-repeater-cms.mjs +25 -0
  138. package/skills/wix-headless-replatform/scripts/record-gap-decision.mjs +39 -0
  139. package/skills/wix-headless-replatform/scripts/replay-extraction.mjs +107 -0
  140. package/skills/wix-headless-replatform/scripts/resolve-home-page.mjs +96 -0
  141. package/skills/wix-headless-replatform/scripts/site-clone.mjs +658 -0
  142. package/skills/wix-headless-replatform/scripts/validate-font-contract.mjs +29 -0
  143. package/skills/wix-headless-replatform/scripts/verify-interactions.mjs +669 -0
  144. package/skills/wix-headless-replatform/scripts/visual-qa.mjs +370 -0
  145. package/skills/wix-headless-templates/donations/INSTRUCTIONS.md +37 -24
  146. package/skills/wix-headless-templates/donations/app/components/donations/DonateForm.tsx +49 -23
  147. package/skills/wix-headless-templates/donations/app/components/donations/DonationReceipt.tsx +6 -5
  148. package/skills/wix-headless-templates/donations/app/components/donations/GoalProgress.tsx +5 -5
  149. package/skills/wix-headless-templates/donations/app/wix/donations/campaigns.ts +4 -4
  150. package/skills/wix-headless-templates/donations/app/wix/donations/donate.ts +7 -3
  151. package/skills/wix-headless-templates/donations/app/wix/donations/donation-store.ts +2 -2
  152. package/skills/wix-headless-templates/donations/app/wix/donations/donations-core.ts +12 -9
  153. package/skills/wix-headless-templates/donations/project/src/components/donations/DonateForm.tsx +49 -23
  154. package/skills/wix-headless-templates/donations/project/src/components/donations/DonationReceipt.tsx +6 -5
  155. package/skills/wix-headless-templates/donations/project/src/components/donations/GoalProgress.tsx +5 -5
  156. package/skills/wix-headless-templates/donations/project/src/wix/donations/campaigns.ts +4 -4
  157. package/skills/wix-headless-templates/donations/project/src/wix/donations/donate.ts +7 -3
  158. package/skills/wix-headless-templates/donations/project/src/wix/donations/donation-store.ts +2 -2
  159. package/skills/wix-headless-templates/donations/project/src/wix/donations/donations-core.ts +12 -9
  160. package/skills/wix-headless-templates/donations/rest/campaigns.ts +4 -4
  161. package/skills/wix-headless-templates/donations/rest/donate.ts +7 -3
  162. package/skills/wix-headless-templates/donations/seed/SEED.md +12 -7
  163. package/skills/wix-headless-templates/donations/tests/donation-contract.test.mjs +71 -0
  164. package/skills/wix-headless-templates/donations/tests/donation-form.test.cjs +137 -0
  165. package/skills/wix-headless-templates/restaurants/INSTRUCTIONS.md +28 -14
  166. package/skills/wix-headless-templates/restaurants/app/components/restaurants/OrderCartDrawer.tsx +87 -5
  167. package/skills/wix-headless-templates/restaurants/app/hooks/restaurants/useOrderCart.ts +9 -1
  168. package/skills/wix-headless-templates/restaurants/app/wix/restaurants/order-store.ts +33 -4
  169. package/skills/wix-headless-templates/restaurants/app/wix/restaurants/ordering-core.ts +100 -5
  170. package/skills/wix-headless-templates/restaurants/app/wix/restaurants/ordering.ts +101 -15
  171. package/skills/wix-headless-templates/restaurants/app/wix/restaurants/types.ts +35 -1
  172. package/skills/wix-headless-templates/restaurants/project/src/components/restaurants/OrderCartDrawer.tsx +87 -5
  173. package/skills/wix-headless-templates/restaurants/project/src/hooks/restaurants/useOrderCart.ts +9 -1
  174. package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/order-store.ts +33 -4
  175. package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/ordering-core.ts +100 -5
  176. package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/ordering.ts +101 -15
  177. package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/types.ts +35 -1
  178. package/skills/wix-headless-templates/restaurants/rest/ordering.ts +91 -14
  179. package/skills/wix-import/SKILL.md +90 -0
  180. package/skills/wix-manage/SKILL.md +14 -4
  181. package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +2 -0
  182. package/skills/wix-manage/references/dashboard-navigation/dashboard-navigation.md +2 -1
  183. package/skills/wix-manage/references/dashboard-navigation/routes.json +249 -0
  184. package/skills/wix-manage/references/donations/create-and-update-donation-campaigns.md +139 -0
  185. package/skills/wix-manage/references/events/events-dashboard-navigation.md +81 -0
  186. package/skills/wix-manage/references/seo/manage-content-plan.md +207 -222
  187. package/skills/wix-manage/references/sites/manage-oauth-apps.md +1 -0
  188. package/skills/wix-manage/references/sites/upload-static-site.md +128 -8
  189. package/skills/wix-readme/SKILL.md +166 -0
  190. package/skills/wix-replatform/.publish-manifest.json +5 -5
  191. package/skills/wix-replatform/CLI.md +162 -0
  192. package/skills/wix-replatform/README.md +52 -0
  193. package/skills/wix-replatform/SKILL.md +229 -684
  194. package/skills/wix-replatform/console/account.css +1 -0
  195. package/skills/wix-replatform/console/app.css +1 -0
  196. package/skills/wix-replatform/console/app.js +117 -0
  197. package/skills/wix-replatform/console/index.html +4 -0
  198. package/skills/wix-replatform/console/json.css +11 -0
  199. package/skills/wix-replatform/lib/agent-adapters/index.js +71 -0
  200. package/skills/wix-replatform/lib/app-page-routing.js +75 -0
  201. package/skills/wix-replatform/lib/artifact-freshness.js +11 -1
  202. package/skills/wix-replatform/lib/blocked-data-requests.js +427 -0
  203. package/skills/wix-replatform/lib/census-reconciliation.js +426 -0
  204. package/skills/wix-replatform/lib/completion-report.js +1393 -13
  205. package/skills/wix-replatform/lib/config-env.js +85 -11
  206. package/skills/wix-replatform/lib/confirmed-pictures.js +212 -0
  207. package/skills/wix-replatform/lib/dependency-readiness.js +423 -0
  208. package/skills/wix-replatform/lib/dependency-recovery.js +21 -0
  209. package/skills/wix-replatform/lib/destination-choice.js +163 -0
  210. package/skills/wix-replatform/lib/destination-handoff.js +62 -0
  211. package/skills/wix-replatform/lib/destination-http.js +81 -0
  212. package/skills/wix-replatform/lib/destination-route.js +98 -0
  213. package/skills/wix-replatform/lib/destination.js +919 -0
  214. package/skills/wix-replatform/lib/discovered-census.js +231 -0
  215. package/skills/wix-replatform/lib/discovered-set-status.js +255 -0
  216. package/skills/wix-replatform/lib/document-url-guard.js +203 -0
  217. package/skills/wix-replatform/lib/entity-qa-attestation.js +339 -0
  218. package/skills/wix-replatform/lib/entity-verification.js +18 -0
  219. package/skills/wix-replatform/lib/execution-capability.js +120 -0
  220. package/skills/wix-replatform/lib/execution-log.js +170 -0
  221. package/skills/wix-replatform/lib/execution-manifest.js +20 -2
  222. package/skills/wix-replatform/lib/execution-state.js +22 -0
  223. package/skills/wix-replatform/lib/expectations-ledger.js +96 -0
  224. package/skills/wix-replatform/lib/generated-plugin-handoff.js +140 -0
  225. package/skills/wix-replatform/lib/import-readiness-contract.js +23 -0
  226. package/skills/wix-replatform/lib/import-recovery.js +800 -6
  227. package/skills/wix-replatform/lib/imported-redirect-targets.js +608 -0
  228. package/skills/wix-replatform/lib/internal-link-reconciliation.js +166 -0
  229. package/skills/wix-replatform/lib/link-audit.js +173 -0
  230. package/skills/wix-replatform/lib/link-census.js +194 -0
  231. package/skills/wix-replatform/lib/link-plan.js +148 -0
  232. package/skills/wix-replatform/lib/link-verification.js +274 -0
  233. package/skills/wix-replatform/lib/link-write-journal.js +42 -0
  234. package/skills/wix-replatform/lib/local-state.js +869 -30
  235. package/skills/wix-replatform/lib/member-completion.js +658 -0
  236. package/skills/wix-replatform/lib/member-crosswalk.js +69 -0
  237. package/skills/wix-replatform/lib/member-outcome-registry.js +265 -0
  238. package/skills/wix-replatform/lib/migration-completion.js +311 -0
  239. package/skills/wix-replatform/lib/operator-identity.js +47 -0
  240. package/skills/wix-replatform/lib/orchestration-decisions.js +248 -0
  241. package/skills/wix-replatform/lib/orchestration-preflight.js +401 -19
  242. package/skills/wix-replatform/lib/orchestration-router.js +608 -32
  243. package/skills/wix-replatform/lib/orchestration-state.js +188 -12
  244. package/skills/wix-replatform/lib/order-reference-readiness.js +30 -0
  245. package/skills/wix-replatform/lib/payments-outcome-registry.js +200 -0
  246. package/skills/wix-replatform/lib/plugin-cms-fallback.js +618 -0
  247. package/skills/wix-replatform/lib/preservation-approval-stamp.js +54 -0
  248. package/skills/wix-replatform/lib/product-media-deferral.js +96 -0
  249. package/skills/wix-replatform/lib/progress-log.js +19 -3
  250. package/skills/wix-replatform/lib/read-back-approval.js +184 -0
  251. package/skills/wix-replatform/lib/read-back-comparison.js +136 -0
  252. package/skills/wix-replatform/lib/read-back-contract.js +243 -0
  253. package/skills/wix-replatform/lib/read-back-extract.js +118 -0
  254. package/skills/wix-replatform/lib/read-back-gate.js +734 -0
  255. package/skills/wix-replatform/lib/read-back-reads.js +94 -0
  256. package/skills/wix-replatform/lib/read-back-state.js +107 -0
  257. package/skills/wix-replatform/lib/read-back-summary.js +81 -0
  258. package/skills/wix-replatform/lib/rerun-conflicts.js +334 -0
  259. package/skills/wix-replatform/lib/run-controller.js +401 -0
  260. package/skills/wix-replatform/lib/seo-outcome-registry.js +45 -0
  261. package/skills/wix-replatform/lib/setup-verification.js +148 -0
  262. package/skills/wix-replatform/lib/source-access.js +231 -0
  263. package/skills/wix-replatform/lib/source-coverage.js +1130 -0
  264. package/skills/wix-replatform/lib/source-secrets.js +277 -0
  265. package/skills/wix-replatform/lib/structured-link-patch.js +59 -0
  266. package/skills/wix-replatform/lib/task-state.js +110 -0
  267. package/skills/wix-replatform/lib/url-preservation-report.js +319 -0
  268. package/skills/wix-replatform/lib/url-preservation-state.js +349 -1
  269. package/skills/wix-replatform/lib/url-redirect-plan.js +728 -0
  270. package/skills/wix-replatform/lib/url-verification.js +315 -0
  271. package/skills/wix-replatform/lib/user-action-effects.js +42 -0
  272. package/skills/wix-replatform/lib/user-actions.js +264 -0
  273. package/skills/wix-replatform/lib/website-handoff.js +1220 -0
  274. package/skills/wix-replatform/lib/wix-client-callers.js +54 -0
  275. package/skills/wix-replatform/lib/wix-dashboard-url.js +35 -0
  276. package/skills/wix-replatform/lib/write-verification-state.js +92 -0
  277. package/skills/wix-replatform/lib/write-verification.js +274 -0
  278. package/skills/wix-replatform/resources/rp-destination/SKILL.md +137 -0
  279. package/skills/wix-replatform/resources/rp-discovery/SKILL.md +189 -16
  280. package/skills/wix-replatform/resources/rp-discovery/scripts/promote-source-coverage.js +47 -0
  281. package/skills/wix-replatform/resources/rp-execute-import/SKILL.md +869 -29
  282. package/skills/wix-replatform/resources/rp-execute-import/scripts/shipping-estimate-audit.js +70 -0
  283. package/skills/wix-replatform/resources/rp-execute-setup/SKILL.md +195 -29
  284. package/skills/wix-replatform/resources/rp-execute-setup/scripts/execute-setup.js +442 -0
  285. package/skills/wix-replatform/resources/rp-execution-policy/SKILL.md +112 -0
  286. package/skills/wix-replatform/resources/rp-execution-policy/lib/coupon-currency-plan-report.js +143 -0
  287. package/skills/wix-replatform/resources/rp-execution-policy/lib/payments-report.js +244 -0
  288. package/skills/wix-replatform/resources/rp-import-codegen/SKILL.md +2082 -38
  289. package/skills/wix-replatform/resources/rp-mapper/SKILL.md +528 -11
  290. package/skills/wix-replatform/resources/rp-mapper/lib/structure-bridge-planning.js +303 -0
  291. package/skills/wix-replatform/resources/rp-mapper/scripts/coverage-reconcile.js +88 -0
  292. package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +6 -5
  293. package/skills/wix-replatform/resources/rp-preflight/SKILL.md +40 -0
  294. package/skills/wix-replatform/resources/rp-project-intake/SKILL.md +55 -0
  295. package/skills/wix-replatform/resources/rp-quick-runtime/SKILL.md +60 -0
  296. package/skills/wix-replatform/resources/rp-quick-runtime/lib/import-progress.js +89 -0
  297. package/skills/wix-replatform/resources/rp-quick-runtime/lib/limit-recovery.js +36 -0
  298. package/skills/wix-replatform/resources/rp-quick-shopify/SKILL.md +123 -0
  299. package/skills/wix-replatform/resources/rp-quick-shopify/quick-mode.json +124 -0
  300. package/skills/wix-replatform/resources/rp-quick-shopify/scripts/quick-mode.js +522 -0
  301. package/skills/wix-replatform/resources/rp-quick-woocommerce/SKILL.md +56 -0
  302. package/skills/wix-replatform/resources/rp-quick-woocommerce/quick-mode.json +91 -0
  303. package/skills/wix-replatform/resources/rp-quick-woocommerce/scripts/quick-mode.js +255 -0
  304. package/skills/wix-replatform/resources/rp-setup-discovery/SKILL.md +80 -10
  305. package/skills/wix-replatform/resources/rp-setup-discovery/scripts/quick-setup-plan.js +44 -0
  306. package/skills/wix-replatform/resources/rp-source-csv/SKILL.md +3 -0
  307. package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fileset.js +0 -0
  308. package/skills/wix-replatform/resources/rp-source-csv/scripts/csv-discovery.js +21 -1
  309. package/skills/wix-replatform/resources/rp-source-inputs/SKILL.md +66 -0
  310. package/skills/wix-replatform/resources/rp-source-sfcc/SKILL.md +153 -0
  311. package/skills/wix-replatform/resources/rp-source-shopify/SKILL.md +394 -0
  312. package/skills/wix-replatform/resources/rp-source-shopify/lib/discovery-reads.js +200 -0
  313. package/skills/wix-replatform/resources/rp-source-shopify/lib/import-findings.js +34 -0
  314. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-bulk-sqlite-worker.js +44 -0
  315. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-bulk.js +1470 -0
  316. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-cli.js +446 -0
  317. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-gift-cards.js +474 -0
  318. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-protected-fields.js +879 -0
  319. package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-session.js +426 -0
  320. package/skills/wix-replatform/resources/rp-source-shopify/queries/articles.graphql +24 -0
  321. package/skills/wix-replatform/resources/rp-source-shopify/queries/blogs.graphql +18 -0
  322. package/skills/wix-replatform/resources/rp-source-shopify/queries/collections.graphql +40 -0
  323. package/skills/wix-replatform/resources/rp-source-shopify/queries/customers.graphql +76 -0
  324. package/skills/wix-replatform/resources/rp-source-shopify/queries/delivery-profiles.graphql +51 -0
  325. package/skills/wix-replatform/resources/rp-source-shopify/queries/discounts.graphql +35 -0
  326. package/skills/wix-replatform/resources/rp-source-shopify/queries/files.graphql +20 -0
  327. package/skills/wix-replatform/resources/rp-source-shopify/queries/gift-cards.graphql +18 -0
  328. package/skills/wix-replatform/resources/rp-source-shopify/queries/inventory.graphql +31 -0
  329. package/skills/wix-replatform/resources/rp-source-shopify/queries/locations.graphql +21 -0
  330. package/skills/wix-replatform/resources/rp-source-shopify/queries/menus.graphql +21 -0
  331. package/skills/wix-replatform/resources/rp-source-shopify/queries/metaobjects.graphql +30 -0
  332. package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-core.graphql +150 -0
  333. package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-fulfillments.graphql +36 -0
  334. package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-refunds.graphql +48 -0
  335. package/skills/wix-replatform/resources/rp-source-shopify/queries/pages.graphql +20 -0
  336. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-address.graphql +8 -0
  337. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-email.graphql +6 -0
  338. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-name.graphql +8 -0
  339. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-phone.graphql +8 -0
  340. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-address.graphql +12 -0
  341. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-email.graphql +6 -0
  342. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-name.graphql +13 -0
  343. package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-phone.graphql +13 -0
  344. package/skills/wix-replatform/resources/rp-source-shopify/queries/products.graphql +73 -0
  345. package/skills/wix-replatform/resources/rp-source-shopify/queries/url-redirects.graphql +13 -0
  346. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-discovery.js +693 -0
  347. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-extract.js +283 -0
  348. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-gift-card-remedy.js +191 -0
  349. package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-session-check.js +115 -0
  350. package/skills/wix-replatform/resources/rp-source-shopify/source-schema.example.json +201 -0
  351. package/skills/wix-replatform/resources/rp-source-wordpress/PLUGIN-READ-PLANS.md +146 -0
  352. package/skills/wix-replatform/resources/rp-source-wordpress/SKILL.md +736 -22
  353. package/skills/wix-replatform/resources/rp-source-wordpress/lib/blocked-data-handlers.js +73 -0
  354. package/skills/wix-replatform/resources/rp-source-wordpress/lib/checkout-field-csv-parser.js +115 -0
  355. package/skills/wix-replatform/resources/rp-source-wordpress/lib/checkout-field-csv-parser.test-fixture.js +91 -0
  356. package/skills/wix-replatform/resources/rp-source-wordpress/lib/coupon-discovery.js +440 -0
  357. package/skills/wix-replatform/resources/rp-source-wordpress/lib/currency-gate.js +125 -0
  358. package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-source-keys.js +252 -0
  359. package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-source-keys.json +183 -0
  360. package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-summary.mjs +333 -0
  361. package/skills/wix-replatform/resources/rp-source-wordpress/lib/invoice-discovery.js +426 -0
  362. package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-disposition.js +400 -0
  363. package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-entity-read-plan.js +633 -0
  364. package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-knowledge.js +1127 -0
  365. package/skills/wix-replatform/resources/rp-source-wordpress/lib/refund-corroboration.js +194 -0
  366. package/skills/wix-replatform/resources/rp-source-wordpress/lib/refund-corroboration.json +100 -0
  367. package/skills/wix-replatform/resources/rp-source-wordpress/lib/sampled-ids-batch.js +157 -0
  368. package/skills/wix-replatform/resources/rp-source-wordpress/lib/secret-value-guard.js +234 -0
  369. package/skills/wix-replatform/resources/rp-source-wordpress/lib/store-currency.js +101 -0
  370. package/skills/wix-replatform/resources/rp-source-wordpress/lib/structure-bridge-reader.js +755 -0
  371. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wix-wp-plugin-v2-client.js +1074 -0
  372. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wix-wp-plugin-v2-client.test-fixture.js +149 -0
  373. package/skills/wix-replatform/resources/rp-source-wordpress/lib/woo-inventory.js +25 -0
  374. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-http.js +204 -9
  375. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-image-url.js +186 -0
  376. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-plugin-detect.js +1422 -0
  377. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-redirect-sources.js +343 -0
  378. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-route-classifier.js +262 -26
  379. package/skills/wix-replatform/resources/rp-source-wordpress/lib/yoast-product-seo-evidence.js +272 -0
  380. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/README.md +373 -0
  381. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/advanced-custom-fields.json +101 -0
  382. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/back-in-stock-notifier-for-woocommerce.json +81 -0
  383. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/capabilities-pending-decision.json +128 -0
  384. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/cms-fallback-eligible.json +18 -0
  385. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/contact-form-7.json +104 -0
  386. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/facebook-for-woocommerce.json +44 -0
  387. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/fingerprint-aliases.json +49 -0
  388. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/free-gifts-for-woocommerce.json +94 -0
  389. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/index.json +863 -0
  390. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/mailpoet.json +52 -0
  391. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/membership-for-woocommerce.json +161 -0
  392. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/mwb-bookings-for-woocommerce.json +180 -0
  393. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/no-migration-needed.json +286 -0
  394. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/pricing-deals-for-woocommerce.json +79 -0
  395. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/product-extras-for-woocommerce.json +120 -0
  396. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/pw-woocommerce-gift-cards.json +128 -0
  397. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/requires-development.json +88 -0
  398. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/seo-by-rank-math.json +80 -0
  399. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/seriously-simple-podcasting.json +46 -0
  400. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/strong-testimonials.json +55 -0
  401. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/subscriptions-for-woocommerce.json +99 -0
  402. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/tablepress.json +55 -0
  403. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/the-events-calendar.json +160 -0
  404. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/ti-woocommerce-wishlist.json +81 -0
  405. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-checkout-field-editor-pro.json +60 -0
  406. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-discount-rules.json +74 -0
  407. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-product-bundle.json +75 -0
  408. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-gift-cards.json +60 -0
  409. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-other-payment-gateway.json +44 -0
  410. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-pdf-invoices-packing-slips.json +57 -0
  411. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-product-addon.json +141 -0
  412. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-progressive-discounts.json +54 -0
  413. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wordpress-seo.json +158 -0
  414. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wpc-composite-products.json +97 -0
  415. package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wpforms-lite.json +86 -0
  416. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/capture-store-settings.js +90 -0
  417. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/plugin-knowledge-validate.js +51 -0
  418. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/plugin-knowledge.js +103 -0
  419. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-discovery.js +1668 -33
  420. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-plugin-inventory.js +273 -0
  421. package/skills/wix-replatform/resources/rp-source-wordpress/secret-value-patterns.v1.json +11 -0
  422. package/skills/wix-replatform/resources/rp-source-wordpress/sub-parts.json +13 -0
  423. package/skills/wix-replatform/resources/rp-target-wix/SKILL.md +446 -32
  424. package/skills/wix-replatform/resources/rp-target-wix/app-page-routing-capabilities.json +36 -0
  425. package/skills/wix-replatform/resources/rp-target-wix/domains/README.md +38 -2
  426. package/skills/wix-replatform/resources/rp-target-wix/domains/SERVICE-PLUGINS.md +322 -0
  427. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/docs-survey.json +70 -0
  428. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/domain.json +19 -9
  429. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/category.json +42 -8
  430. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/comment.json +54 -8
  431. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/post.json +66 -10
  432. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/tag.json +38 -7
  433. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/docs-survey.json +332 -0
  434. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/domain.json +75 -0
  435. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/booking.json +181 -0
  436. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/service-add-on.json +130 -0
  437. package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/service.json +180 -0
  438. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/docs-survey.json +161 -0
  439. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/domain.json +15 -4
  440. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/collection.json +39 -7
  441. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/data-item.json +47 -8
  442. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/reference.json +7 -1
  443. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/docs-survey.json +204 -0
  444. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/domain.json +17 -4
  445. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/contact.json +159 -15
  446. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/email-subscription.json +80 -12
  447. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/label.json +48 -8
  448. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/docs-survey.json +306 -0
  449. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/domain.json +30 -7
  450. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/delivery-profile.json +171 -0
  451. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/discount-rule.json +182 -0
  452. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-applied-discount.json +129 -0
  453. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-invoice-document.json +192 -0
  454. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item-options.json +123 -0
  455. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item.json +60 -12
  456. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-merchant-note.json +112 -0
  457. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-payment.json +191 -0
  458. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-refund.json +140 -13
  459. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order.json +383 -18
  460. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/pickup-location.json +122 -0
  461. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/shipping-option.json +98 -0
  462. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/wishlist-item.json +86 -0
  463. package/skills/wix-replatform/resources/rp-target-wix/domains/events/docs-survey.json +128 -0
  464. package/skills/wix-replatform/resources/rp-target-wix/domains/events/domain.json +38 -0
  465. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/category.json +73 -0
  466. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/event.json +107 -0
  467. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/registration-form.json +89 -0
  468. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/rsvp.json +118 -0
  469. package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/ticket-definition.json +93 -0
  470. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/docs-survey.json +86 -0
  471. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/domain.json +56 -0
  472. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/entities/form-submission.json +121 -0
  473. package/skills/wix-replatform/resources/rp-target-wix/domains/forms/entities/form.json +134 -0
  474. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/docs-survey.json +41 -0
  475. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/domain.json +50 -0
  476. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/entities/gift-card-product.json +87 -0
  477. package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/entities/gift-card.json +130 -0
  478. package/skills/wix-replatform/resources/rp-target-wix/domains/index.json +914 -24
  479. package/skills/wix-replatform/resources/rp-target-wix/domains/media/docs-survey.json +41 -0
  480. package/skills/wix-replatform/resources/rp-target-wix/domains/media/domain.json +14 -4
  481. package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-file.json +45 -8
  482. package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-folder.json +41 -7
  483. package/skills/wix-replatform/resources/rp-target-wix/domains/members/docs-survey.json +119 -0
  484. package/skills/wix-replatform/resources/rp-target-wix/domains/members/domain.json +13 -3
  485. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/badge.json +41 -7
  486. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/custom-field.json +82 -0
  487. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member-about.json +77 -0
  488. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member.json +165 -13
  489. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/docs-survey.json +41 -0
  490. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/domain.json +46 -0
  491. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/entities/plan-order.json +137 -0
  492. package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/entities/plan.json +133 -0
  493. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/docs-survey.json +7 -0
  494. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/domain.json +36 -0
  495. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/amazon-shop-connect.json +71 -0
  496. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/ebay-shop-connect.json +67 -0
  497. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/google-youtube-connect.json +72 -0
  498. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/meta-catalog-connect.json +74 -0
  499. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/pinterest-catalog-connect.json +69 -0
  500. package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/tiktok-catalog-connect.json +61 -0
  501. package/skills/wix-replatform/resources/rp-target-wix/domains/schema.json +28 -1
  502. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/docs-survey.json +109 -0
  503. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/domain.json +73 -0
  504. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/item-seo-tags.json +148 -0
  505. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/seo-pattern.json +91 -0
  506. package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/site-seo-tags.json +79 -0
  507. package/skills/wix-replatform/resources/rp-target-wix/domains/site/docs-survey.json +32 -0
  508. package/skills/wix-replatform/resources/rp-target-wix/domains/site/domain.json +49 -0
  509. package/skills/wix-replatform/resources/rp-target-wix/domains/site/entities/business-contact.json +103 -0
  510. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/docs-survey.json +203 -0
  511. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/domain.json +27 -10
  512. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/back-in-stock-request.json +119 -0
  513. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/brand.json +111 -0
  514. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/category.json +52 -8
  515. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/coupon.json +214 -10
  516. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/inventory-item.json +93 -13
  517. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-customization.json +111 -0
  518. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-tag.json +52 -10
  519. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product.json +145 -14
  520. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/promotion.json +84 -0
  521. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/review.json +178 -0
  522. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/subscription-product.json +87 -0
  523. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/docs-survey.json +106 -0
  524. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/domain.json +64 -0
  525. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/manual-tax-mapping.json +119 -0
  526. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-group.json +94 -0
  527. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-region.json +113 -0
  528. package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-settings.json +65 -0
  529. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-author-attribution.js +165 -0
  530. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-client.js +60 -0
  531. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-duplicates.js +201 -0
  532. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-ledger.js +306 -0
  533. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-report.js +229 -0
  534. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-stage.js +55 -0
  535. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build.js +715 -0
  536. package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-media.js +567 -0
  537. package/skills/wix-replatform/resources/rp-target-wix/lib/bulk-results.js +73 -0
  538. package/skills/wix-replatform/resources/rp-target-wix/lib/contact-extended-fields.js +194 -0
  539. package/skills/wix-replatform/resources/rp-target-wix/lib/contact-import.js +133 -0
  540. package/skills/wix-replatform/resources/rp-target-wix/lib/contract-ledger.js +13 -4
  541. package/skills/wix-replatform/resources/rp-target-wix/lib/coupon-redemption-drift.js +491 -0
  542. package/skills/wix-replatform/resources/rp-target-wix/lib/customer-member.js +104 -0
  543. package/skills/wix-replatform/resources/rp-target-wix/lib/data-extension-schema.js +673 -0
  544. package/skills/wix-replatform/resources/rp-target-wix/lib/domain-knowledge.js +453 -2
  545. package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-balance-reconciliation.js +119 -0
  546. package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-build.js +566 -0
  547. package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-redemption-build.js +249 -0
  548. package/skills/wix-replatform/resources/rp-target-wix/lib/order-applied-discount-build.js +63 -0
  549. package/skills/wix-replatform/resources/rp-target-wix/lib/order-buyer.js +187 -0
  550. package/skills/wix-replatform/resources/rp-target-wix/lib/order-channel-build.js +64 -0
  551. package/skills/wix-replatform/resources/rp-target-wix/lib/order-history.js +1027 -0
  552. package/skills/wix-replatform/resources/rp-target-wix/lib/order-invoice-contract.js +90 -0
  553. package/skills/wix-replatform/resources/rp-target-wix/lib/order-payment-mapping.js +420 -0
  554. package/skills/wix-replatform/resources/rp-target-wix/lib/pricing-plan-definition.js +177 -0
  555. package/skills/wix-replatform/resources/rp-target-wix/lib/product-import.js +151 -0
  556. package/skills/wix-replatform/resources/rp-target-wix/lib/product-seo.js +723 -0
  557. package/skills/wix-replatform/resources/rp-target-wix/lib/seo-redirects.js +353 -0
  558. package/skills/wix-replatform/resources/rp-target-wix/lib/shipping-build.js +1437 -0
  559. package/skills/wix-replatform/resources/rp-target-wix/lib/shipping-option-stage.js +354 -0
  560. package/skills/wix-replatform/resources/rp-target-wix/lib/store-config-build.js +134 -0
  561. package/skills/wix-replatform/resources/rp-target-wix/lib/stores-category-attach.js +221 -0
  562. package/skills/wix-replatform/resources/rp-target-wix/lib/stores-inventory.js +248 -0
  563. package/skills/wix-replatform/resources/rp-target-wix/lib/stores-verification.js +86 -3
  564. package/skills/wix-replatform/resources/rp-target-wix/lib/tax-build.js +231 -0
  565. package/skills/wix-replatform/resources/rp-target-wix/lib/url-mapper-transport.js +28 -0
  566. package/skills/wix-replatform/resources/rp-target-wix/lib/value-utils.js +14 -0
  567. package/skills/wix-replatform/resources/rp-target-wix/lib/variant-correlation.js +34 -0
  568. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-build.js +166 -15
  569. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-target-spec.js +19 -2
  570. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +4079 -257
  571. package/skills/wix-replatform/resources/rp-target-wix/lib/write-verification.js +3 -0
  572. package/skills/wix-replatform/resources/rp-target-wix/scripts/docs-survey-sync.js +150 -0
  573. package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge-validate.js +40 -2
  574. package/skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js +6 -1
  575. package/skills/wix-replatform/resources/rp-telemetry/SKILL.md +148 -8
  576. package/skills/wix-replatform/resources/rp-telemetry/lib/bi-sink.js +83 -15
  577. package/skills/wix-replatform/resources/rp-telemetry/lib/telemetry-recorder.js +1119 -215
  578. package/skills/wix-replatform/resources/rp-telemetry/lib/transcript-digest.js +318 -0
  579. package/skills/wix-replatform/resources/rp-telemetry/scripts/meter-from-audit.js +2 -2
  580. package/skills/wix-replatform/resources/rp-telemetry/scripts/rp-telemetry.js +106 -4
  581. package/skills/wix-replatform/resources/rp-website-continuation/SKILL.md +94 -0
  582. package/skills/wix-replatform/scripts/census-reconciliation.js +25 -0
  583. package/skills/wix-replatform/scripts/cms-fallback.js +89 -0
  584. package/skills/wix-replatform/scripts/discovered-census.js +26 -0
  585. package/skills/wix-replatform/scripts/discovered-set-status.js +33 -0
  586. package/skills/wix-replatform/scripts/entity-qa-attestation.js +130 -0
  587. package/skills/wix-replatform/scripts/execution-health-check.js +415 -0
  588. package/skills/wix-replatform/scripts/execution-manifest-validate.js +1 -1
  589. package/skills/wix-replatform/scripts/execution-state-prepare.js +1 -1
  590. package/skills/wix-replatform/scripts/expectations-ledger.js +44 -0
  591. package/skills/wix-replatform/scripts/import-contract-verify.js +183 -0
  592. package/skills/wix-replatform/scripts/internal-link-audit.js +90 -0
  593. package/skills/wix-replatform/scripts/item-seo-tags-verify.js +205 -0
  594. package/skills/wix-replatform/scripts/migration-completion.js +76 -0
  595. package/skills/wix-replatform/scripts/orchestration-preflight.js +21 -2
  596. package/skills/wix-replatform/scripts/orchestration-route.js +32 -1
  597. package/skills/wix-replatform/scripts/orchestration-state.js +146 -10
  598. package/skills/wix-replatform/scripts/read-back-gate.js +175 -0
  599. package/skills/wix-replatform/scripts/replatform-console.js +146 -0
  600. package/skills/wix-replatform/scripts/replatform.js +436 -0
  601. package/skills/wix-replatform/scripts/source-secrets.js +77 -0
  602. package/skills/wix-replatform/scripts/url-redirects.js +286 -0
  603. package/skills/wix-replatform/scripts/user-action.js +58 -0
  604. package/skills/wix-replatform/scripts/website-handoff-generate.js +64 -0
  605. package/skills/wix-replatform/scripts/wix-dashboard-url.js +54 -0
  606. package/skills/wix-replatform/scripts/wix-destination.js +143 -0
  607. package/skills/wix-replatform/scripts/wix-headless-scaffold.js +762 -0
  608. package/skills/wix-vibe-headless/SKILL.md +7 -3
  609. package/skills/wix-vibe-headless/install/deploy.cjs +61 -34
  610. package/skills/wix-vibe-headless/platforms/base44.md +1 -0
  611. package/skills/wix-vibe-headless/references/donations/INSTRUCTIONS.md +88 -0
  612. package/skills/wix-vibe-headless/references/donations/app/rest/wix-donations.js +112 -0
  613. package/skills/wix-vibe-headless/references/donations/seed/SEED.md +11 -0
  614. package/skills/wix-vibe-headless/references/donations/tests/donations.test.mjs +258 -0
  615. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +5 -2
  616. package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +4 -1
  617. package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +67 -3
  618. package/skills/wix-vibe-headless/references/shared/app/rest/wix-client.js +4 -3
  619. package/skills/wix-replatform/resources/rp-target-wix/SPEC-product-reviews-and-blog-authorship.md +0 -330
@@ -29,16 +29,58 @@ const { execFileSync } = require('node:child_process');
29
29
  const biSink = require('./bi-sink.js');
30
30
 
31
31
  // 1.1.0 — added `skills_commit` to the run-start record and rollup (additive).
32
- // 1.2.0 — BI sink (spec 0007): added `telemetry_health.bi_push_failures`, the
32
+ // 1.2.0 — BI sink: added `telemetry_health.bi_push_failures`, the
33
33
  // `bi_push_failed`/`bi_push_truncated` journal record types (additive;
34
34
  // readers per the external reader contract skip unknown record types).
35
- const TELEMETRY_SCHEMA_VERSION = '1.2.0';
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';
36
59
  const FREE_TEXT_MAX = 400;
37
60
  const SHAPES_KEPT_PER_CLASS = 3;
38
61
  const SHAPES_MAX_PER_CALL = 10;
39
62
  const EVIDENCE_REFS_MAX = 5;
40
63
  // A resume gap below this is treated as in-session noise, not a wait interval.
41
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;
42
84
  // A wait at least this long with no halt_needs_user event in its stage means the
43
85
  // capture lost *why* the run stalled — flagged at finalize, never silent.
44
86
  const WAIT_WITHOUT_HALT_FLAG_MS = 5 * 60 * 1000;
@@ -58,7 +100,7 @@ const STAGE_OUTCOMES = ['passed', 'halted', 'failed', 'skipped'];
58
100
  const TERMINAL_STATES = ['completed', 'halted_needs_user', 'failed', 'abandoned_by_user'];
59
101
  const SEVERITIES = ['blocking', 'degraded', 'cosmetic', 'info'];
60
102
  const SOURCE_PLATFORMS = ['wordpress', 'woocommerce', 'shopify', 'csv', 'other'];
61
- const DELIVERY_MODES = ['management', 'website'];
103
+ const DELIVERY_MODES = ['management', 'website', 'management_and_website'];
62
104
  const DESTINATION_STRATEGIES = ['new_site', 'existing_site'];
63
105
  const OPERATOR_ACCEPTANCE = ['accepted', 'rework_needed', 'rejected', 'unknown'];
64
106
  const VOLUME_TARGETS = ['native', 'cms', 'none'];
@@ -358,6 +400,64 @@ function validateMeter(input, { openStage, lastStage }) {
358
400
  return out;
359
401
  }
360
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
+
361
461
  // A pricing snapshot makes historical cost recomputable when list prices change. Rates are per
362
462
  // MILLION tokens, matching how model pricing is published.
363
463
  function validatePricingSnapshot(errors, value) {
@@ -414,6 +514,13 @@ const DIM_VALIDATORS = {
414
514
  }
415
515
  return [...new Set(v.map(normalizeToken).filter(Boolean))];
416
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
+ },
417
524
  source_acquisition: (errors, v) => {
418
525
  const token = normalizeToken(v);
419
526
  if (!token || token.length > 32) {
@@ -537,7 +644,7 @@ function validateEvent(input, context) {
537
644
  }
538
645
 
539
646
  if (typeof input.skill !== 'string' || !/^[a-z][a-z0-9-]*$/.test(input.skill)) {
540
- errors.push('skill is required — the active rp-* resource (e.g. "rp-mapper") or "replatform"');
647
+ errors.push('skill is required — the active rp-* resource (e.g. "rp-mapper") or "wix-replatform"');
541
648
  }
542
649
 
543
650
  checkEnum(errors, 'severity', input.severity, SEVERITIES);
@@ -809,6 +916,120 @@ function validateFinalizeInput(input, context) {
809
916
  };
810
917
  }
811
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
+
812
1033
  // --- journal ------------------------------------------------------------------
813
1034
 
814
1035
  function readJournalFile(file) {
@@ -837,6 +1058,92 @@ function appendJournal(projectDir, record) {
837
1058
  fs.appendFileSync(file, `${JSON.stringify(record)}\n`, 'utf8');
838
1059
  }
839
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
+
840
1147
  function replay(records) {
841
1148
  const state = {
842
1149
  runStart: null,
@@ -848,6 +1155,7 @@ function replay(records) {
848
1155
  entries: [],
849
1156
  waits: [],
850
1157
  meters: [],
1158
+ sourceReads: [],
851
1159
  openStage: null,
852
1160
  openStageStart: null,
853
1161
  lastStage: null,
@@ -855,19 +1163,39 @@ function replay(records) {
855
1163
  scrubHits: 0,
856
1164
  biPushFailures: 0,
857
1165
  biPushTruncated: 0,
1166
+ biPushSkipped: 0,
1167
+ lastBiPushOkSeq: 0,
1168
+ lastEventSeq: 0,
858
1169
  finalize: null,
859
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,
860
1178
  };
1179
+ let lastActivityMs = -Infinity;
861
1180
  for (const record of records) {
862
1181
  state.seq = Math.max(state.seq, record.seq || 0);
863
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
+ }
864
1190
  switch (record.type) {
865
1191
  case 'run_start':
866
1192
  state.runStart = record;
1193
+ state.lastOpener = record;
867
1194
  Object.assign(state.dims, record.dims || {});
868
1195
  break;
869
1196
  case 'session_start':
870
1197
  state.sessionCount = record.session;
1198
+ state.lastOpener = record;
871
1199
  break;
872
1200
  case 'dims':
873
1201
  Object.assign(state.dims, record.dims || {});
@@ -898,9 +1226,15 @@ function replay(records) {
898
1226
  // own invocation. Summing is the only reading that survives a resume.
899
1227
  state.meters.push(record.meter);
900
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;
901
1234
  case 'event':
902
1235
  state.events.push(record);
903
1236
  state.scrubHits += record.scrub_hits || 0;
1237
+ state.lastEventSeq = Math.max(state.lastEventSeq, record.seq || 0);
904
1238
  break;
905
1239
  case 'rejected':
906
1240
  state.rejected.push(record);
@@ -911,9 +1245,25 @@ function replay(records) {
911
1245
  case 'bi_push_truncated':
912
1246
  state.biPushTruncated += record.rows || 1;
913
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;
914
1254
  case 'finalize':
915
1255
  state.finalize = record;
916
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;
917
1267
  default:
918
1268
  break;
919
1269
  }
@@ -1108,7 +1458,7 @@ function computeCost(stages, pricingSnapshot, model) {
1108
1458
  }
1109
1459
 
1110
1460
  const MANIFEST_EXCLUDE_DIRS = new Set(['config', 'data', 'preview', 'node_modules', 'frontend', 'telemetry']);
1111
- // .ndjson: the pipeline's audit logs (logs/audit-*.ndjson) are spec-named
1461
+ // .ndjson: the pipeline's audit logs (logs/audit-*.ndjson) are named
1112
1462
  // evidence — a pipeline_defect deep-dive depends on them being in the bundle.
1113
1463
  const MANIFEST_EXTENSIONS = new Set(['.md', '.json', '.jsonl', '.ndjson', '.js', '.mjs', '.cjs', '.ts', '.sh', '.yaml', '.yml']);
1114
1464
 
@@ -1236,6 +1586,20 @@ function assembleDocument(projectDir, records, malformedLines = 0) {
1236
1586
  if (state.biPushTruncated > 0) {
1237
1587
  flags.push(`bi_push_truncated:${state.biPushTruncated}`);
1238
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
+ }
1239
1603
  // A stalled wait whose stage has no halt_needs_user event lost the "why" of
1240
1604
  // the stall — the single most actionable signal for where runs stall.
1241
1605
  const haltStages = new Set(events.filter((e) => e.event_type === 'halt_needs_user').map((e) => e.stage));
@@ -1256,6 +1620,21 @@ function assembleDocument(projectDir, records, malformedLines = 0) {
1256
1620
  && dims.source_platform !== 'csv') {
1257
1621
  flags.push('source_extensions_null_after_discovery');
1258
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
+ }
1259
1638
 
1260
1639
  const evidence = walkEvidence(projectDir);
1261
1640
  const evidenceBytes = evidence.reduce((sum, f) => sum + f.bytes, 0);
@@ -1280,6 +1659,7 @@ function assembleDocument(projectDir, records, malformedLines = 0) {
1280
1659
  source_platform_version: dims.source_platform_version ?? null,
1281
1660
  source_site_url: dims.source_site_url ?? null,
1282
1661
  source_extensions: dims.source_extensions ?? null,
1662
+ discovered_entity_types: dims.discovered_entity_types ?? null,
1283
1663
  source_acquisition: dims.source_acquisition ?? null,
1284
1664
  delivery_mode: dims.delivery_mode ?? null,
1285
1665
  destination_strategy: dims.destination_strategy ?? null,
@@ -1294,6 +1674,7 @@ function assembleDocument(projectDir, records, malformedLines = 0) {
1294
1674
  dropped_events: dropped,
1295
1675
  scrub_hits: state.scrubHits,
1296
1676
  bi_push_failures: state.biPushFailures,
1677
+ bi_push_skipped: state.biPushSkipped,
1297
1678
  flags,
1298
1679
  },
1299
1680
  event_count_by_type: eventCountByType,
@@ -1301,6 +1682,11 @@ function assembleDocument(projectDir, records, malformedLines = 0) {
1301
1682
  waiting_ms: waitingMs,
1302
1683
  timing,
1303
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),
1304
1690
  model_pricing_snapshot: dims.model_pricing_snapshot ?? null,
1305
1691
  run_started: runStart.ts,
1306
1692
  run_ended: finalizeTs,
@@ -1421,31 +1807,130 @@ function resolveSkillsCommit() {
1421
1807
  return null;
1422
1808
  }
1423
1809
 
1424
- // --- BI sink (spec 0007) ----------------------------------------------------------
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 ----------------------------------------------------------
1425
1853
 
1426
- // Push rows to BI and journal any failures/truncation so telemetry loss is
1427
- // visible in telemetry_health, never silent. Never throws — BI unreachable,
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,
1428
1856
  // slow, or rejecting is a telemetry failure, not a run failure. Returns the
1429
- // last journal seq used plus sent/failed row counts.
1430
- async function pushToBi(projectDir, ts, seq, rows, loggedUserId, target) {
1431
- const result = await biSink.pushRows(rows, loggedUserId);
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
+ }
1432
1881
  if (result.truncated > 0) {
1433
- seq += 1;
1434
- appendJournal(projectDir, { type: 'bi_push_truncated', ts, seq, target, rows: result.truncated });
1882
+ markers.push({ type: 'bi_push_truncated', ts, target, rows: result.truncated });
1435
1883
  }
1436
1884
  for (const failure of result.failures) {
1437
- seq += 1;
1438
- appendJournal(projectDir, {
1439
- type: 'bi_push_failed', ts, seq, target, rows: failure.rows, detail: failure.detail,
1440
- });
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
+ }
1441
1910
  }
1442
1911
  return {
1443
1912
  seq,
1444
1913
  sent: result.sent,
1445
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,
1446
1918
  };
1447
1919
  }
1448
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
+
1449
1934
  // The folded BI row for one event class: prior journal records of the class plus
1450
1935
  // the just-appended one. Re-pushes of a growing class are safe — the reviewer
1451
1936
  // dedupes on (run_id, seq), latest row wins with the cumulative count.
@@ -1459,91 +1944,244 @@ function foldedRowForClass(state, eventRecord) {
1459
1944
 
1460
1945
  // --- public command API ---------------------------------------------------------
1461
1946
 
1462
- async function start(projectDir, dimsInput, { now } = {}) {
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
+ } = {}) {
1463
2074
  const ts = nowIso(now);
1464
2075
  const dims = validateDims(dimsInput);
1465
- const journal = readJournal(projectDir);
1466
-
1467
- if (journal) {
1468
- const state = replay(journal.records);
1469
- if (!state.runStart) {
1470
- throw new ValidationError(['telemetry journal is corrupt: missing run-start record']);
1471
- }
1472
- if (state.finalize) {
1473
- archiveFinalizedJournal(projectDir, journal, state);
1474
- } else {
1475
- // Resume: same run_id, same attempt, one more session. Cross-session user
1476
- // latency lands in waiting_ms, never in a phantom second run.
1477
- let seq = state.seq;
1478
- if (state.openWait) {
1479
- seq += 1;
1480
- appendJournal(projectDir, { type: 'wait_end', ts, seq });
1481
- } else if (state.lastTs && tsMs(ts) - tsMs(state.lastTs) >= IMPLICIT_WAIT_MIN_MS) {
1482
- // The run stopped without an open wait (e.g. a crash); the dead interval
1483
- // is waiting time, not active time.
1484
- const stage = state.openStage || state.lastStage || 'config';
1485
- seq += 1;
1486
- appendJournal(projectDir, { type: 'wait_start', ts: state.lastTs, seq, stage, imputed: true });
1487
- seq += 1;
1488
- appendJournal(projectDir, { type: 'wait_end', ts, seq, imputed: true });
1489
- }
1490
- seq += 1;
1491
- appendJournal(projectDir, { type: 'session_start', ts, seq, session: state.sessionCount + 1 });
1492
- if (Object.keys(dims).length > 0) {
1493
- seq += 1;
1494
- appendJournal(projectDir, { type: 'dims', ts, seq, dims });
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']);
1495
2087
  }
1496
- // Operator identity arriving on a resume means the phase:started row could
1497
- // not have been sent yet — send it now (dedup makes a re-send harmless).
1498
- if (biSink.isGuid(dims.wix_user_id)) {
1499
- await pushToBi(projectDir, ts, seq,
1500
- [biSink.startedRunRow(state.runStart, dims.wix_user_id)], dims.wix_user_id, 'run_started');
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
+ };
1501
2111
  }
1502
- return {
1503
- resumed: true,
1504
- run_id: state.runStart.run_id,
1505
- attempt: state.runStart.attempt,
1506
- session_count: state.sessionCount + 1,
1507
- open_stage: state.openStage,
1508
- };
1509
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
+ };
1510
2173
  }
1511
2174
 
1512
- const attempt = lastFinalizedAttempt(projectDir) + 1;
1513
- const runtimeEnv = {
1514
- os: `${os.platform()} ${os.release()}`,
1515
- node_version: process.version,
1516
- agent_runtime: (dims.runtime_env && dims.runtime_env.agent_runtime) || null,
1517
- model: (dims.runtime_env && dims.runtime_env.model) || null,
1518
- ...(dims.runtime_env || {}),
1519
- };
1520
- delete dims.runtime_env;
1521
- const skillsVersion = dims.skills_version || resolveSkillsVersion();
1522
- delete dims.skills_version;
1523
- const skillsCommit = dims.skills_commit || resolveSkillsCommit();
1524
- delete dims.skills_commit;
1525
-
1526
- const record = {
1527
- type: 'run_start',
1528
- ts,
1529
- seq: 1,
1530
- run_id: crypto.randomUUID(),
1531
- project_id: path.basename(path.resolve(projectDir)),
1532
- attempt,
1533
- session: 1,
1534
- schema_version: TELEMETRY_SCHEMA_VERSION,
1535
- skills_version: skillsVersion,
1536
- skills_commit: skillsCommit,
1537
- runtime_env: runtimeEnv,
1538
- dims,
1539
- };
1540
- appendJournal(projectDir, record);
1541
- // Emit `replatform_run` phase:started (spec 0007). Without a GUID operator
1542
- // identity there is no BI route yet — a later dims call carrying wix_user_id
1543
- // (or the finalize re-push) sends it then.
1544
- if (biSink.isGuid(dims.wix_user_id)) {
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)) {
1545
2182
  await pushToBi(projectDir, ts, record.seq,
1546
- [biSink.startedRunRow(record, dims.wix_user_id)], dims.wix_user_id, 'run_started');
2183
+ [biSink.startedRunRow(record, record.dims.wix_user_id)], record.dims.wix_user_id, 'run_started',
2184
+ { throughSeq: record.seq });
1547
2185
  }
1548
2186
  return {
1549
2187
  resumed: false,
@@ -1556,58 +2194,203 @@ async function start(projectDir, dimsInput, { now } = {}) {
1556
2194
  };
1557
2195
  }
1558
2196
 
1559
- async function dims(projectDir, dimsInput, { now } = {}) {
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;
1560
2271
  const ts = nowIso(now);
1561
- const { state } = requireActiveRun(projectDir);
1562
- const validated = validateDims(dimsInput);
1563
- if (Object.keys(validated).length === 0) {
1564
- throw new ValidationError(['dims payload contains no known dimension fields']);
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;
1565
2293
  }
1566
- appendJournal(projectDir, { type: 'dims', ts, seq: state.seq + 1, dims: validated });
1567
- // Identity becoming known mid-run unlocks the BI route: send the
1568
- // phase:started row that start() had to hold back.
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.
1569
2342
  if (biSink.isGuid(validated.wix_user_id)) {
1570
2343
  await pushToBi(projectDir, ts, state.seq + 1,
1571
- [biSink.startedRunRow(state.runStart, validated.wix_user_id)], validated.wix_user_id, 'run_started');
2344
+ healRows(state, validated.wix_user_id), validated.wix_user_id, 'identity_heal',
2345
+ { throughSeq: state.seq + 1 });
1572
2346
  }
1573
2347
  return { recorded: Object.keys(validated) };
1574
2348
  }
1575
2349
 
1576
2350
  async function record(projectDir, eventInput, { now } = {}) {
1577
2351
  const ts = nowIso(now);
1578
- const { state } = requireActiveRun(projectDir);
1579
- let validated;
1580
- try {
1581
- validated = validateEvent(eventInput, state);
1582
- } catch (error) {
1583
- if (error instanceof ValidationError) {
1584
- // The schema gate refused the call: count it so telemetry loss is never
1585
- // silent, then surface the errors for a retry.
1586
- appendJournal(projectDir, {
1587
- type: 'rejected',
1588
- ts,
1589
- seq: state.seq + 1,
1590
- class_key: error.hint || null,
1591
- reasons: error.errors,
1592
- });
1593
- error.hint = 'fix the listed fields and retry the record call';
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;
1594
2371
  }
1595
- throw error;
1596
- }
1597
- const eventRecord = {
1598
- type: 'event',
1599
- ts,
1600
- seq: state.seq + 1,
1601
- class_key: validated.classKey,
1602
- scrub_hits: validated.scrubHits,
1603
- event: validated.event,
1604
- };
1605
- appendJournal(projectDir, eventRecord);
1606
- // Emit the folded `replatform_run_event` row (spec 0007). No identity yet →
1607
- // hold back; the finalize full re-push sends every folded class regardless.
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.
1608
2388
  if (biSink.isGuid(state.dims.wix_user_id)) {
1609
- await pushToBi(projectDir, ts, eventRecord.seq,
1610
- [foldedRowForClass(state, eventRecord)], state.dims.wix_user_id, 'run_event');
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 });
1611
2394
  }
1612
2395
  const priorInClass = state.events.filter((r) => r.class_key === validated.classKey).length;
1613
2396
  return {
@@ -1620,6 +2403,10 @@ async function record(projectDir, eventInput, { now } = {}) {
1620
2403
  }
1621
2404
 
1622
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 } = {}) {
1623
2410
  const ts = nowIso(now);
1624
2411
  const { state } = requireActiveRun(projectDir);
1625
2412
  const errors = [];
@@ -1664,84 +2451,173 @@ function stage(projectDir, action, stageName, { outcome, now } = {}) {
1664
2451
  // known by the agent runtime rather than by whatever closed the stage.
1665
2452
  function meter(projectDir, input, { now } = {}) {
1666
2453
  const ts = nowIso(now);
1667
- const { state } = requireActiveRun(projectDir);
1668
- const payload = validateMeter(input, { openStage: state.openStage, lastStage: state.lastStage });
1669
- appendJournal(projectDir, { type: 'meter', ts, seq: state.seq + 1, stage: payload.stage, meter: payload });
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
+ });
1670
2460
  const recorded = Object.keys(payload).filter((k) => k !== 'stage');
1671
2461
  return { metered: true, stage: payload.stage, fields: recorded };
1672
2462
  }
1673
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
+
1674
2517
  async function wait(projectDir, action, { now, haltSubtype, skill, what } = {}) {
1675
2518
  const ts = nowIso(now);
1676
- const { state } = requireActiveRun(projectDir);
1677
- if (action === 'start') {
1678
- if (state.openWait) {
1679
- throw new ValidationError(['a wait interval is already open']);
1680
- }
1681
- const attributedStage = state.openStage || state.lastStage || 'config';
1682
- let seq = state.seq;
1683
- let haltRecorded = false;
1684
- if (haltSubtype !== undefined && haltSubtype !== null) {
1685
- // The paired halt_needs_user event is emitted mechanically so a stalled
1686
- // run never loses the "why" to a missed record call.
1687
- const validated = validateEvent({
1688
- event_type: 'halt_needs_user',
1689
- subtype: haltSubtype,
1690
- stage: attributedStage,
1691
- skill: skill || 'replatform',
1692
- severity: 'blocking',
1693
- what_happened: what || `Run halted at ${attributedStage} waiting on the user (${haltSubtype}).`,
1694
- }, state);
1695
- seq += 1;
1696
- const eventRecord = {
1697
- type: 'event', ts, seq, class_key: validated.classKey, scrub_hits: validated.scrubHits, event: validated.event,
1698
- };
1699
- appendJournal(projectDir, eventRecord);
1700
- if (biSink.isGuid(state.dims.wix_user_id)) {
1701
- const pushed = await pushToBi(projectDir, ts, seq,
1702
- [foldedRowForClass(state, eventRecord)], state.dims.wix_user_id, 'run_event');
1703
- seq = pushed.seq;
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']);
1704
2524
  }
1705
- haltRecorded = true;
1706
- } else if (skill !== undefined || what !== undefined) {
1707
- throw new ValidationError(['--skill/--what on wait start are only valid together with --halt <subtype>']);
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 };
1708
2548
  }
1709
- seq += 1;
1710
- appendJournal(projectDir, { type: 'wait_start', ts, seq, stage: attributedStage });
1711
- return { waiting: true, stage: attributedStage, halt_recorded: haltRecorded };
1712
- }
1713
- if (action === 'end') {
1714
- if (!state.openWait) {
1715
- throw new ValidationError(['no wait interval is open']);
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)) };
1716
2555
  }
1717
- appendJournal(projectDir, { type: 'wait_end', ts, seq: state.seq + 1 });
1718
- return { waiting: false, waited_ms: Math.max(0, tsMs(ts) - tsMs(state.openWait.start)) };
1719
- }
1720
- throw new ValidationError([`wait action must be start or end (got: ${action})`]);
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 };
1721
2575
  }
1722
2576
 
1723
2577
  async function finalize(projectDir, rollupInput, { now } = {}) {
1724
2578
  const ts = nowIso(now);
1725
- const { journal, state } = requireActiveRun(projectDir);
1726
- const payload = validateFinalizeInput(rollupInput, state);
1727
-
1728
- let seq = state.seq;
1729
- if (state.openWait) {
1730
- seq += 1;
1731
- appendJournal(projectDir, { type: 'wait_end', ts, seq });
1732
- }
1733
- if (state.openStage) {
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
+ }
1734
2598
  seq += 1;
1735
- appendJournal(projectDir, {
1736
- type: 'stage_end',
1737
- ts,
1738
- seq,
1739
- stage: state.openStage,
1740
- outcome: OUTCOME_FOR_TERMINAL[payload.terminal_state],
1741
- });
1742
- }
1743
- seq += 1;
1744
- appendJournal(projectDir, { type: 'finalize', ts, seq, payload });
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;
1745
2621
 
1746
2622
  // BI push before archival, so a failed push is journaled into the run and
1747
2623
  // lands in the archived telemetry_health. The full re-push (started row +
@@ -1751,9 +2627,8 @@ async function finalize(projectDir, rollupInput, { now } = {}) {
1751
2627
  // own health can't include this push's outcome (it is assembled first) — if
1752
2628
  // the push fails there is no BI row at all, and the local document, which is
1753
2629
  // authoritative, records the failure.
1754
- let biPush = { sent: 0, failed: 0 };
1755
- if (!biSink.disabled()) {
1756
- const provisionalJournal = readJournal(projectDir);
2630
+ let biPush = { sent: 0, failed: 0, skipped: 0 };
2631
+ {
1757
2632
  const provisional = assembleDocument(projectDir, provisionalJournal.records, provisionalJournal.malformed);
1758
2633
  const rollup = provisional.document.rollup;
1759
2634
  if (biSink.isGuid(rollup.wix_user_id)) {
@@ -1762,24 +2637,35 @@ async function finalize(projectDir, rollupInput, { now } = {}) {
1762
2637
  ...provisional.document.events.map(biSink.eventRow),
1763
2638
  biSink.finalizedRunRow(rollup),
1764
2639
  ];
1765
- const pushed = await pushToBi(projectDir, ts, seq, rows, rollup.wix_user_id, 'finalize');
1766
- seq = pushed.seq;
1767
- biPush = { sent: pushed.sent, failed: pushed.failed };
1768
- } else {
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()) {
1769
2646
  // No operator identity by run end: the run is unroutable in BI. Journal
1770
- // it as a push failure — this is real telemetry loss, not a hold-back.
1771
- const rowCount = provisional.document.events.length + 2;
1772
- seq += 1;
1773
- appendJournal(projectDir, {
1774
- type: 'bi_push_failed', ts, seq, target: 'finalize', rows: rowCount, detail: 'no_operator_identity',
1775
- });
1776
- biPush = { sent: 0, failed: rowCount };
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 };
1777
2651
  }
1778
2652
  }
1779
2653
 
1780
- const finalJournal = readJournal(projectDir);
1781
- const finalState = replay(finalJournal.records);
1782
- const assembled = archiveFinalizedJournal(projectDir, finalJournal, finalState);
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
+ });
1783
2669
 
1784
2670
  const health = assembled.document.rollup.telemetry_health;
1785
2671
  return {
@@ -1889,6 +2775,8 @@ function status(projectDir) {
1889
2775
  distinct_classes: new Set(state.events.map((r) => r.class_key)).size,
1890
2776
  rejected_calls: state.rejected.length,
1891
2777
  last_seq: state.seq,
2778
+ bi_push_ok_seq: state.lastBiPushOkSeq,
2779
+ bi_backlog: state.runStart ? biBacklog(state) : true,
1892
2780
  };
1893
2781
  }
1894
2782
 
@@ -1907,15 +2795,31 @@ module.exports = {
1907
2795
  validateDims,
1908
2796
  validateEvent,
1909
2797
  validateFinalizeInput,
2798
+ validateTranscriptDigest,
1910
2799
  journalPath,
1911
2800
  signalPath,
1912
2801
  runsDir,
1913
2802
  readJournal,
2803
+ replay,
1914
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,
1915
2814
  dims,
1916
2815
  record,
1917
2816
  stage,
1918
2817
  meter,
2818
+ sourceRead,
2819
+ summarizeSourceReads,
2820
+ validateSourceRead,
2821
+ SOURCE_READ_COUNT_KEYS,
2822
+ transcriptDigest,
1919
2823
  wait,
1920
2824
  finalize,
1921
2825
  rebuild,