@wix/agent-skills 1.36.0 → 1.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -3
- package/skills/wix-headless-kit/install/pins.json +1 -1
- package/skills/wix-manage/SKILL.md +3 -3
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +78 -1
- package/skills/wix-manage/references/google-ads/create-performance-max-campaign.md +11 -11
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +20 -51
- package/skills/wix-manage/references/marketing/generate-and-publish-marketing-plan.md +4 -4
- package/skills/wix-base44-connector/SKILL.md +0 -437
- package/skills/wix-base44-connector/references/CAPABILITY_MAP.md +0 -78
- package/skills/wix-base44-connector/scripts/utils.cjs +0 -466
- package/skills/wix-export/SKILL.md +0 -83
- package/skills/wix-harmony-editor/.publish-manifest.json +0 -8
- package/skills/wix-harmony-editor/COPY-SITE.md +0 -494
- package/skills/wix-harmony-editor/SKILL.md +0 -317
- package/skills/wix-harmony-editor/scripts/copy/build-description.mjs +0 -249
- package/skills/wix-harmony-editor/scripts/copy/check-description.mjs +0 -225
- package/skills/wix-harmony-editor/scripts/copy/check-mirror.mjs +0 -89
- package/skills/wix-harmony-editor/scripts/copy/compare-preview.mjs +0 -76
- package/skills/wix-harmony-editor/scripts/copy/deploy-description.mjs +0 -212
- package/skills/wix-harmony-editor/scripts/copy/desc-hash.mjs +0 -44
- package/skills/wix-harmony-editor/scripts/copy/editor-run.mjs +0 -45
- package/skills/wix-harmony-editor/scripts/copy/export-page.mjs +0 -53
- package/skills/wix-harmony-editor/scripts/copy/find-interactions.mjs +0 -76
- package/skills/wix-harmony-editor/scripts/copy/hide-scrollbars.mjs +0 -16
- package/skills/wix-harmony-editor/scripts/copy/import-media.mjs +0 -150
- package/skills/wix-harmony-editor/scripts/copy/link-url.mjs +0 -27
- package/skills/wix-harmony-editor/scripts/copy/lint-api.mjs +0 -56
- package/skills/wix-harmony-editor/scripts/copy/measure-source.mjs +0 -211
- package/skills/wix-harmony-editor/scripts/copy/merge-mirrors.mjs +0 -29
- package/skills/wix-harmony-editor/scripts/copy/mirror-site.mjs +0 -92
- package/skills/wix-harmony-editor/scripts/copy/mobile-stage.mjs +0 -56
- package/skills/wix-harmony-editor/scripts/copy/preview-widths.mjs +0 -73
- package/skills/wix-harmony-editor/scripts/copy/render-plate.mjs +0 -67
- package/skills/wix-harmony-editor/scripts/copy/serve-mirror.mjs +0 -102
- package/skills/wix-harmony-editor/scripts/copy/snapshot-capabilities.mjs +0 -40
- package/skills/wix-harmony-editor/scripts/copy/split-sections.mjs +0 -191
- package/skills/wix-harmony-editor/scripts/copy/upload-media-manager.mjs +0 -61
- package/skills/wix-harmony-editor/scripts/launch.sh +0 -359
- package/skills/wix-headless/SKILL.md +0 -151
- package/skills/wix-headless/references/AI_FEATURES.md +0 -178
- package/skills/wix-headless/references/CAPABILITIES.md +0 -117
- package/skills/wix-headless/references/CONTENT.md +0 -36
- package/skills/wix-headless/references/DESIGN.md +0 -55
- package/skills/wix-headless/references/DISCOVERY.md +0 -80
- package/skills/wix-headless/references/DOC_DISCOVERY.md +0 -43
- package/skills/wix-headless/references/FEEDBACK.md +0 -129
- package/skills/wix-headless/references/IMAGE_GENERATION.md +0 -80
- package/skills/wix-headless/references/SDK_HANDOFF.md +0 -108
- package/skills/wix-headless/references/SEED.md +0 -74
- package/skills/wix-headless/references/SETUP.md +0 -60
- package/skills/wix-headless/references/astro.md +0 -98
- package/skills/wix-headless/references/inline-recipes/experience-donations.md +0 -102
- package/skills/wix-headless/references/inline-recipes/experience-store.md +0 -49
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +0 -196
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +0 -590
- package/skills/wix-headless/references/inline-recipes/how-to-code-bookings.md +0 -279
- package/skills/wix-headless/references/inline-recipes/how-to-code-cms.md +0 -163
- package/skills/wix-headless/references/inline-recipes/how-to-code-donations.md +0 -146
- package/skills/wix-headless/references/inline-recipes/how-to-code-events.md +0 -203
- package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +0 -696
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +0 -124
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +0 -160
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +0 -118
- package/skills/wix-headless/references/inline-recipes/how-to-code-portfolio.md +0 -132
- package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +0 -126
- package/skills/wix-headless/references/inline-recipes/how-to-code-rentals.md +0 -254
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +0 -191
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +0 -213
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurants.md +0 -161
- package/skills/wix-headless/references/inline-recipes/setup-blog.md +0 -175
- package/skills/wix-headless/references/inline-recipes/setup-bookings.md +0 -232
- package/skills/wix-headless/references/inline-recipes/setup-cms.md +0 -200
- package/skills/wix-headless/references/inline-recipes/setup-donations.md +0 -15
- package/skills/wix-headless/references/inline-recipes/setup-events.md +0 -196
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +0 -288
- package/skills/wix-headless/references/inline-recipes/setup-online-store.md +0 -214
- package/skills/wix-headless/references/inline-recipes/setup-portfolio.md +0 -155
- package/skills/wix-headless/references/inline-recipes/setup-pricing-plans.md +0 -229
- package/skills/wix-headless/references/inline-recipes/setup-rentals.md +0 -254
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-experiences.md +0 -73
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-orders.md +0 -197
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-reservations.md +0 -136
- package/skills/wix-headless/references/inline-recipes/setup-restaurants.md +0 -152
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +0 -71
- package/skills/wix-headless/references/managed/CONNECT.md +0 -74
- package/skills/wix-headless/references/managed/CREATE.md +0 -149
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +0 -104
- package/skills/wix-headless/references/non-astro.md +0 -59
- package/skills/wix-headless/references/self-managed/AUTHENTICATION.md +0 -22
- package/skills/wix-headless/references/self-managed/DEPLOYMENT.md +0 -15
- package/skills/wix-headless/references/stripe/AUTHENTICATION.md +0 -55
- package/skills/wix-headless/references/stripe/DEPLOYMENT.md +0 -71
- package/skills/wix-headless/scripts/bootstrap.mjs +0 -156
- package/skills/wix-headless-replatform/.publish-manifest.json +0 -8
- package/skills/wix-headless-replatform/SKILL.md +0 -116
- package/skills/wix-headless-replatform/agents/openai.yaml +0 -6
- package/skills/wix-headless-replatform/references/control-and-visual-fidelity-contract.md +0 -43
- package/skills/wix-headless-replatform/references/extraction-schema.md +0 -187
- package/skills/wix-headless-replatform/references/interaction-runtime-contract.md +0 -137
- package/skills/wix-headless-replatform/references/layout-blueprint-contract.md +0 -41
- package/skills/wix-headless-replatform/references/output-contract.md +0 -93
- package/skills/wix-headless-replatform/references/post-build-gap-contract.md +0 -125
- package/skills/wix-headless-replatform/references/qa-checklist.md +0 -83
- package/skills/wix-headless-replatform/references/safety.md +0 -19
- package/skills/wix-headless-replatform/references/ui-normalization-contract.md +0 -70
- package/skills/wix-headless-replatform/references/workflow.md +0 -157
- package/skills/wix-headless-replatform/registry/README.md +0 -20
- package/skills/wix-headless-replatform/registry/approvals/button/shadcn-default-ac60ef5.json +0 -28
- package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/button.tsx +0 -54
- package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/capabilities.json +0 -60
- package/skills/wix-headless-replatform/registry/items/button/shadcn-default-ac60ef5/contract.md +0 -48
- package/skills/wix-headless-replatform/registry/licenses/shadcn-ui-ac60ef5-MIT.txt +0 -21
- package/skills/wix-headless-replatform/registry/registry.json +0 -36
- package/skills/wix-headless-replatform/resources/rp-facelift/SKILL.md +0 -32
- package/skills/wix-headless-replatform/resources/rp-project-context/SKILL.md +0 -62
- package/skills/wix-headless-replatform/resources/rp-qa-gap-loop/SKILL.md +0 -64
- package/skills/wix-headless-replatform/resources/rp-source-evidence/SKILL.md +0 -42
- package/skills/wix-headless-replatform/resources/rp-ui-implementation/SKILL.md +0 -73
- package/skills/wix-headless-replatform/schemas/build-plan.schema.json +0 -10
- package/skills/wix-headless-replatform/schemas/candidate.schema.json +0 -19
- package/skills/wix-headless-replatform/schemas/common.schema.json +0 -33
- package/skills/wix-headless-replatform/schemas/decision-patch.schema.json +0 -10
- package/skills/wix-headless-replatform/schemas/decision-request.schema.json +0 -10
- package/skills/wix-headless-replatform/schemas/extraction-gap.schema.json +0 -28
- package/skills/wix-headless-replatform/schemas/extraction-manifest.schema.json +0 -34
- package/skills/wix-headless-replatform/schemas/final-report.schema.json +0 -10
- package/skills/wix-headless-replatform/schemas/foundation.schema.json +0 -11
- package/skills/wix-headless-replatform/schemas/metadata.schema.json +0 -11
- package/skills/wix-headless-replatform/schemas/observation-packet.schema.json +0 -17
- package/skills/wix-headless-replatform/schemas/page-capture.schema.json +0 -11
- package/skills/wix-headless-replatform/schemas/page-resolution.schema.json +0 -33
- package/skills/wix-headless-replatform/schemas/shared-chrome.schema.json +0 -11
- package/skills/wix-headless-replatform/schemas/unit.schema.json +0 -40
- package/skills/wix-headless-replatform/scripts/assemble-extraction.mjs +0 -844
- package/skills/wix-headless-replatform/scripts/benchmark-extraction.mjs +0 -163
- package/skills/wix-headless-replatform/scripts/browser-extraction-preflight.mjs +0 -42
- package/skills/wix-headless-replatform/scripts/build-extracted-homepage.mjs +0 -228
- package/skills/wix-headless-replatform/scripts/clear-wix-starter.mjs +0 -87
- package/skills/wix-headless-replatform/scripts/create-wix-project.mjs +0 -505
- package/skills/wix-headless-replatform/scripts/discover-urls.mjs +0 -142
- package/skills/wix-headless-replatform/scripts/extract-assets.mjs +0 -140
- package/skills/wix-headless-replatform/scripts/extract-design-system.mjs +0 -637
- package/skills/wix-headless-replatform/scripts/extract-interactions.mjs +0 -1903
- package/skills/wix-headless-replatform/scripts/extract-page.mjs +0 -2461
- package/skills/wix-headless-replatform/scripts/extract-seo.mjs +0 -25
- package/skills/wix-headless-replatform/scripts/extract-tokens.mjs +0 -63
- package/skills/wix-headless-replatform/scripts/finalize-extraction-report.mjs +0 -126
- package/skills/wix-headless-replatform/scripts/finalize-gap-review.mjs +0 -180
- package/skills/wix-headless-replatform/scripts/finalize-gap-review.test.mjs +0 -98
- package/skills/wix-headless-replatform/scripts/finalize-migration-website.mjs +0 -103
- package/skills/wix-headless-replatform/scripts/generate-control-state-contract.mjs +0 -176
- package/skills/wix-headless-replatform/scripts/generate-design-md.mjs +0 -261
- package/skills/wix-headless-replatform/scripts/generate-extraction-validators.mjs +0 -25
- package/skills/wix-headless-replatform/scripts/generate-invoice-download-backend.mjs +0 -342
- package/skills/wix-headless-replatform/scripts/generate-layout-blueprint.mjs +0 -140
- package/skills/wix-headless-replatform/scripts/generate-repeater-cms.mjs +0 -22
- package/skills/wix-headless-replatform/scripts/generate-route-plan.mjs +0 -143
- package/skills/wix-headless-replatform/scripts/generate-scene-contract.mjs +0 -288
- package/skills/wix-headless-replatform/scripts/generate-ui-normalization.mjs +0 -266
- package/skills/wix-headless-replatform/scripts/generate-visual-assets.mjs +0 -111
- package/skills/wix-headless-replatform/scripts/lib/browser-tooling.mjs +0 -570
- package/skills/wix-headless-replatform/scripts/lib/common.mjs +0 -329
- package/skills/wix-headless-replatform/scripts/lib/component-capabilities.mjs +0 -121
- package/skills/wix-headless-replatform/scripts/lib/component-registry.mjs +0 -282
- package/skills/wix-headless-replatform/scripts/lib/content-boundary.mjs +0 -61
- package/skills/wix-headless-replatform/scripts/lib/extraction-contract.mjs +0 -374
- package/skills/wix-headless-replatform/scripts/lib/extraction-detectors.mjs +0 -243
- package/skills/wix-headless-replatform/scripts/lib/extraction-validators.generated.mjs +0 -1139
- package/skills/wix-headless-replatform/scripts/lib/font-contract.mjs +0 -326
- package/skills/wix-headless-replatform/scripts/lib/frontend-automation-state.mjs +0 -184
- package/skills/wix-headless-replatform/scripts/lib/gap-analysis.mjs +0 -396
- package/skills/wix-headless-replatform/scripts/lib/html-extract.mjs +0 -168
- package/skills/wix-headless-replatform/scripts/lib/interaction-bootstrap.mjs +0 -10
- package/skills/wix-headless-replatform/scripts/lib/interaction-runtime.mjs +0 -194
- package/skills/wix-headless-replatform/scripts/lib/interaction-timeline.mjs +0 -348
- package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/classifiers.mjs +0 -141
- package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/geometry.mjs +0 -43
- package/skills/wix-headless-replatform/scripts/lib/layout-blueprint/vocabulary.mjs +0 -18
- package/skills/wix-headless-replatform/scripts/lib/ui-normalization.css +0 -64
- package/skills/wix-headless-replatform/scripts/optimize-svg.mjs +0 -31
- package/skills/wix-headless-replatform/scripts/post-build-gap.mjs +0 -358
- package/skills/wix-headless-replatform/scripts/provision-repeater-cms.mjs +0 -25
- package/skills/wix-headless-replatform/scripts/record-gap-decision.mjs +0 -39
- package/skills/wix-headless-replatform/scripts/replay-extraction.mjs +0 -107
- package/skills/wix-headless-replatform/scripts/resolve-home-page.mjs +0 -96
- package/skills/wix-headless-replatform/scripts/site-clone.mjs +0 -658
- package/skills/wix-headless-replatform/scripts/validate-font-contract.mjs +0 -29
- package/skills/wix-headless-replatform/scripts/verify-interactions.mjs +0 -669
- package/skills/wix-headless-replatform/scripts/visual-qa.mjs +0 -370
- package/skills/wix-import/SKILL.md +0 -90
- package/skills/wix-replatform/.publish-manifest.json +0 -8
- package/skills/wix-replatform/CLI.md +0 -162
- package/skills/wix-replatform/README.md +0 -52
- package/skills/wix-replatform/SKILL.md +0 -238
- package/skills/wix-replatform/console/account.css +0 -1
- package/skills/wix-replatform/console/app.css +0 -1
- package/skills/wix-replatform/console/app.js +0 -117
- package/skills/wix-replatform/console/index.html +0 -4
- package/skills/wix-replatform/console/json.css +0 -11
- package/skills/wix-replatform/lib/agent-adapters/index.js +0 -71
- package/skills/wix-replatform/lib/app-page-routing.js +0 -75
- package/skills/wix-replatform/lib/artifact-freshness.js +0 -225
- package/skills/wix-replatform/lib/blocked-data-requests.js +0 -427
- package/skills/wix-replatform/lib/census-reconciliation.js +0 -426
- package/skills/wix-replatform/lib/completion-report.js +0 -1580
- package/skills/wix-replatform/lib/config-env.js +0 -133
- package/skills/wix-replatform/lib/confirmed-pictures.js +0 -212
- package/skills/wix-replatform/lib/dependency-readiness.js +0 -423
- package/skills/wix-replatform/lib/dependency-recovery.js +0 -21
- package/skills/wix-replatform/lib/destination-choice.js +0 -163
- package/skills/wix-replatform/lib/destination-handoff.js +0 -62
- package/skills/wix-replatform/lib/destination-http.js +0 -81
- package/skills/wix-replatform/lib/destination-route.js +0 -98
- package/skills/wix-replatform/lib/destination.js +0 -919
- package/skills/wix-replatform/lib/discovered-census.js +0 -231
- package/skills/wix-replatform/lib/discovered-set-status.js +0 -255
- package/skills/wix-replatform/lib/document-url-guard.js +0 -203
- package/skills/wix-replatform/lib/entity-qa-attestation.js +0 -339
- package/skills/wix-replatform/lib/entity-verification.js +0 -18
- package/skills/wix-replatform/lib/execution-capability.js +0 -120
- package/skills/wix-replatform/lib/execution-log.js +0 -170
- package/skills/wix-replatform/lib/execution-manifest.js +0 -203
- package/skills/wix-replatform/lib/execution-state.js +0 -195
- package/skills/wix-replatform/lib/expectations-ledger.js +0 -96
- package/skills/wix-replatform/lib/generated-plugin-handoff.js +0 -140
- package/skills/wix-replatform/lib/import-readiness-contract.js +0 -23
- package/skills/wix-replatform/lib/import-recovery.js +0 -1107
- package/skills/wix-replatform/lib/imported-redirect-targets.js +0 -608
- package/skills/wix-replatform/lib/internal-link-reconciliation.js +0 -166
- package/skills/wix-replatform/lib/link-audit.js +0 -173
- package/skills/wix-replatform/lib/link-census.js +0 -194
- package/skills/wix-replatform/lib/link-plan.js +0 -148
- package/skills/wix-replatform/lib/link-verification.js +0 -274
- package/skills/wix-replatform/lib/link-write-journal.js +0 -42
- package/skills/wix-replatform/lib/local-state.js +0 -1481
- package/skills/wix-replatform/lib/member-completion.js +0 -658
- package/skills/wix-replatform/lib/member-crosswalk.js +0 -69
- package/skills/wix-replatform/lib/member-outcome-registry.js +0 -265
- package/skills/wix-replatform/lib/migration-completion.js +0 -311
- package/skills/wix-replatform/lib/operator-identity.js +0 -47
- package/skills/wix-replatform/lib/orchestration-decisions.js +0 -248
- package/skills/wix-replatform/lib/orchestration-preflight.js +0 -525
- package/skills/wix-replatform/lib/orchestration-router.js +0 -698
- package/skills/wix-replatform/lib/orchestration-state.js +0 -631
- package/skills/wix-replatform/lib/order-reference-readiness.js +0 -30
- package/skills/wix-replatform/lib/payments-outcome-registry.js +0 -200
- package/skills/wix-replatform/lib/plugin-cms-fallback.js +0 -618
- package/skills/wix-replatform/lib/preservation-approval-stamp.js +0 -54
- package/skills/wix-replatform/lib/product-media-deferral.js +0 -96
- package/skills/wix-replatform/lib/progress-log.js +0 -240
- package/skills/wix-replatform/lib/progress-log.sh +0 -140
- package/skills/wix-replatform/lib/read-back-approval.js +0 -184
- package/skills/wix-replatform/lib/read-back-comparison.js +0 -136
- package/skills/wix-replatform/lib/read-back-contract.js +0 -243
- package/skills/wix-replatform/lib/read-back-extract.js +0 -118
- package/skills/wix-replatform/lib/read-back-gate.js +0 -734
- package/skills/wix-replatform/lib/read-back-reads.js +0 -94
- package/skills/wix-replatform/lib/read-back-state.js +0 -107
- package/skills/wix-replatform/lib/read-back-summary.js +0 -81
- package/skills/wix-replatform/lib/rerun-conflicts.js +0 -334
- package/skills/wix-replatform/lib/run-controller.js +0 -401
- package/skills/wix-replatform/lib/seo-outcome-registry.js +0 -45
- package/skills/wix-replatform/lib/setup-verification.js +0 -148
- package/skills/wix-replatform/lib/source-access.js +0 -231
- package/skills/wix-replatform/lib/source-coverage.js +0 -1130
- package/skills/wix-replatform/lib/source-secrets.js +0 -277
- package/skills/wix-replatform/lib/structured-link-patch.js +0 -59
- package/skills/wix-replatform/lib/task-state.js +0 -110
- package/skills/wix-replatform/lib/url-preservation-report.js +0 -319
- package/skills/wix-replatform/lib/url-preservation-state.js +0 -739
- package/skills/wix-replatform/lib/url-redirect-plan.js +0 -728
- package/skills/wix-replatform/lib/url-verification.js +0 -315
- package/skills/wix-replatform/lib/user-action-effects.js +0 -42
- package/skills/wix-replatform/lib/user-actions.js +0 -264
- package/skills/wix-replatform/lib/website-handoff.js +0 -1220
- package/skills/wix-replatform/lib/wix-client-callers.js +0 -54
- package/skills/wix-replatform/lib/wix-dashboard-url.js +0 -35
- package/skills/wix-replatform/lib/write-verification-state.js +0 -92
- package/skills/wix-replatform/lib/write-verification.js +0 -274
- package/skills/wix-replatform/resources/rp-destination/SKILL.md +0 -137
- package/skills/wix-replatform/resources/rp-discovery/SKILL.md +0 -357
- package/skills/wix-replatform/resources/rp-discovery/scripts/promote-source-coverage.js +0 -47
- package/skills/wix-replatform/resources/rp-execute-import/SKILL.md +0 -1369
- package/skills/wix-replatform/resources/rp-execute-import/scripts/shipping-estimate-audit.js +0 -70
- package/skills/wix-replatform/resources/rp-execute-setup/SKILL.md +0 -482
- package/skills/wix-replatform/resources/rp-execute-setup/scripts/execute-setup.js +0 -442
- package/skills/wix-replatform/resources/rp-execute-setup/scripts/mint-token.sh +0 -64
- package/skills/wix-replatform/resources/rp-execution-policy/SKILL.md +0 -112
- package/skills/wix-replatform/resources/rp-execution-policy/lib/coupon-currency-plan-report.js +0 -143
- package/skills/wix-replatform/resources/rp-execution-policy/lib/payments-report.js +0 -244
- package/skills/wix-replatform/resources/rp-import-codegen/SKILL.md +0 -3158
- package/skills/wix-replatform/resources/rp-mapper/SKILL.md +0 -947
- package/skills/wix-replatform/resources/rp-mapper/lib/mapping-resolve.js +0 -217
- package/skills/wix-replatform/resources/rp-mapper/lib/structure-bridge-planning.js +0 -303
- package/skills/wix-replatform/resources/rp-mapper/scripts/coverage-reconcile.js +0 -88
- package/skills/wix-replatform/resources/rp-mapper/scripts/resolve-mapping.js +0 -148
- package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +0 -378
- package/skills/wix-replatform/resources/rp-preflight/SKILL.md +0 -40
- package/skills/wix-replatform/resources/rp-project-intake/SKILL.md +0 -55
- package/skills/wix-replatform/resources/rp-quick-runtime/SKILL.md +0 -60
- package/skills/wix-replatform/resources/rp-quick-runtime/lib/import-progress.js +0 -89
- package/skills/wix-replatform/resources/rp-quick-runtime/lib/limit-recovery.js +0 -36
- package/skills/wix-replatform/resources/rp-quick-shopify/SKILL.md +0 -123
- package/skills/wix-replatform/resources/rp-quick-shopify/quick-mode.json +0 -124
- package/skills/wix-replatform/resources/rp-quick-shopify/scripts/quick-mode.js +0 -522
- package/skills/wix-replatform/resources/rp-quick-woocommerce/SKILL.md +0 -56
- package/skills/wix-replatform/resources/rp-quick-woocommerce/quick-mode.json +0 -91
- package/skills/wix-replatform/resources/rp-quick-woocommerce/scripts/quick-mode.js +0 -255
- package/skills/wix-replatform/resources/rp-setup-discovery/SKILL.md +0 -253
- package/skills/wix-replatform/resources/rp-setup-discovery/scripts/quick-setup-plan.js +0 -44
- package/skills/wix-replatform/resources/rp-source-csv/SKILL.md +0 -481
- package/skills/wix-replatform/resources/rp-source-csv/lib/canonical-record.js +0 -136
- package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fileset.js +0 -0
- package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fingerprint.js +0 -310
- package/skills/wix-replatform/resources/rp-source-csv/lib/csv-layout.js +0 -1081
- package/skills/wix-replatform/resources/rp-source-csv/lib/csv-parse.js +0 -618
- package/skills/wix-replatform/resources/rp-source-csv/scripts/csv-discovery.js +0 -969
- package/skills/wix-replatform/resources/rp-source-csv/source-schema.example.json +0 -196
- package/skills/wix-replatform/resources/rp-source-csv/vendors/bigcommerce.json +0 -66
- package/skills/wix-replatform/resources/rp-source-csv/vendors/magento.json +0 -60
- package/skills/wix-replatform/resources/rp-source-csv/vendors/shopify.json +0 -95
- package/skills/wix-replatform/resources/rp-source-csv/vendors/woocommerce.json +0 -82
- package/skills/wix-replatform/resources/rp-source-inputs/SKILL.md +0 -66
- package/skills/wix-replatform/resources/rp-source-sfcc/SKILL.md +0 -153
- package/skills/wix-replatform/resources/rp-source-shopify/SKILL.md +0 -394
- package/skills/wix-replatform/resources/rp-source-shopify/lib/discovery-reads.js +0 -200
- package/skills/wix-replatform/resources/rp-source-shopify/lib/import-findings.js +0 -34
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-bulk-sqlite-worker.js +0 -44
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-bulk.js +0 -1470
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-cli.js +0 -446
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-gift-cards.js +0 -474
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-protected-fields.js +0 -879
- package/skills/wix-replatform/resources/rp-source-shopify/lib/shopify-session.js +0 -426
- package/skills/wix-replatform/resources/rp-source-shopify/queries/articles.graphql +0 -24
- package/skills/wix-replatform/resources/rp-source-shopify/queries/blogs.graphql +0 -18
- package/skills/wix-replatform/resources/rp-source-shopify/queries/collections.graphql +0 -40
- package/skills/wix-replatform/resources/rp-source-shopify/queries/customers.graphql +0 -76
- package/skills/wix-replatform/resources/rp-source-shopify/queries/delivery-profiles.graphql +0 -51
- package/skills/wix-replatform/resources/rp-source-shopify/queries/discounts.graphql +0 -35
- package/skills/wix-replatform/resources/rp-source-shopify/queries/files.graphql +0 -20
- package/skills/wix-replatform/resources/rp-source-shopify/queries/gift-cards.graphql +0 -18
- package/skills/wix-replatform/resources/rp-source-shopify/queries/inventory.graphql +0 -31
- package/skills/wix-replatform/resources/rp-source-shopify/queries/locations.graphql +0 -21
- package/skills/wix-replatform/resources/rp-source-shopify/queries/menus.graphql +0 -21
- package/skills/wix-replatform/resources/rp-source-shopify/queries/metaobjects.graphql +0 -30
- package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-core.graphql +0 -150
- package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-fulfillments.graphql +0 -36
- package/skills/wix-replatform/resources/rp-source-shopify/queries/orders-refunds.graphql +0 -48
- package/skills/wix-replatform/resources/rp-source-shopify/queries/pages.graphql +0 -20
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-address.graphql +0 -8
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-email.graphql +0 -6
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-name.graphql +0 -8
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-customers-phone.graphql +0 -8
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-address.graphql +0 -12
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-email.graphql +0 -6
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-name.graphql +0 -13
- package/skills/wix-replatform/resources/rp-source-shopify/queries/probe-orders-phone.graphql +0 -13
- package/skills/wix-replatform/resources/rp-source-shopify/queries/products.graphql +0 -73
- package/skills/wix-replatform/resources/rp-source-shopify/queries/url-redirects.graphql +0 -13
- package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-discovery.js +0 -693
- package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-extract.js +0 -283
- package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-gift-card-remedy.js +0 -191
- package/skills/wix-replatform/resources/rp-source-shopify/scripts/shopify-session-check.js +0 -115
- package/skills/wix-replatform/resources/rp-source-shopify/source-schema.example.json +0 -201
- package/skills/wix-replatform/resources/rp-source-wordpress/PLUGIN-READ-PLANS.md +0 -146
- package/skills/wix-replatform/resources/rp-source-wordpress/SKILL.md +0 -970
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/blocked-data-handlers.js +0 -73
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/checkout-field-csv-parser.js +0 -115
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/checkout-field-csv-parser.test-fixture.js +0 -91
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/coupon-discovery.js +0 -440
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/currency-gate.js +0 -125
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-source-keys.js +0 -252
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-source-keys.json +0 -183
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/gateway-summary.mjs +0 -333
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/invoice-discovery.js +0 -426
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-disposition.js +0 -400
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-entity-read-plan.js +0 -633
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/plugin-knowledge.js +0 -1127
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/refund-corroboration.js +0 -194
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/refund-corroboration.json +0 -100
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/sampled-ids-batch.js +0 -157
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/secret-value-guard.js +0 -234
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/store-currency.js +0 -101
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/structure-bridge-reader.js +0 -755
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wix-wp-plugin-v2-client.js +0 -1074
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wix-wp-plugin-v2-client.test-fixture.js +0 -149
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/woo-inventory.js +0 -25
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-http.js +0 -444
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-image-url.js +0 -186
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-plugin-detect.js +0 -1422
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-redirect-sources.js +0 -343
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-route-classifier.js +0 -680
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/yoast-product-seo-evidence.js +0 -272
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/README.md +0 -373
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/advanced-custom-fields.json +0 -101
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/back-in-stock-notifier-for-woocommerce.json +0 -81
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/capabilities-pending-decision.json +0 -128
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/cms-fallback-eligible.json +0 -18
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/contact-form-7.json +0 -104
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/facebook-for-woocommerce.json +0 -44
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/fingerprint-aliases.json +0 -49
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/free-gifts-for-woocommerce.json +0 -94
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/index.json +0 -863
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/mailpoet.json +0 -52
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/membership-for-woocommerce.json +0 -161
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/mwb-bookings-for-woocommerce.json +0 -180
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/no-migration-needed.json +0 -286
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/pricing-deals-for-woocommerce.json +0 -79
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/product-extras-for-woocommerce.json +0 -120
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/pw-woocommerce-gift-cards.json +0 -128
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/requires-development.json +0 -88
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/seo-by-rank-math.json +0 -80
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/seriously-simple-podcasting.json +0 -46
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/strong-testimonials.json +0 -55
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/subscriptions-for-woocommerce.json +0 -99
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/tablepress.json +0 -55
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/the-events-calendar.json +0 -160
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/ti-woocommerce-wishlist.json +0 -81
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-checkout-field-editor-pro.json +0 -60
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-discount-rules.json +0 -74
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woo-product-bundle.json +0 -75
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-gift-cards.json +0 -60
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-other-payment-gateway.json +0 -44
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-pdf-invoices-packing-slips.json +0 -57
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-product-addon.json +0 -141
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/woocommerce-progressive-discounts.json +0 -54
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wordpress-seo.json +0 -158
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wpc-composite-products.json +0 -97
- package/skills/wix-replatform/resources/rp-source-wordpress/plugins/wpforms-lite.json +0 -86
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/capture-store-settings.js +0 -90
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/plugin-knowledge-validate.js +0 -51
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/plugin-knowledge.js +0 -103
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-discovery.js +0 -2409
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-plugin-inventory.js +0 -273
- package/skills/wix-replatform/resources/rp-source-wordpress/secret-value-patterns.v1.json +0 -11
- package/skills/wix-replatform/resources/rp-source-wordpress/source-schema.example.json +0 -71
- package/skills/wix-replatform/resources/rp-source-wordpress/sub-parts.json +0 -13
- package/skills/wix-replatform/resources/rp-target-wix/SKILL.md +0 -1046
- package/skills/wix-replatform/resources/rp-target-wix/app-page-routing-capabilities.json +0 -36
- package/skills/wix-replatform/resources/rp-target-wix/domains/README.md +0 -57
- package/skills/wix-replatform/resources/rp-target-wix/domains/SERVICE-PLUGINS.md +0 -322
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/docs-survey.json +0 -70
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/domain.json +0 -29
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/category.json +0 -71
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/comment.json +0 -83
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/post.json +0 -95
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/tag.json +0 -66
- package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/docs-survey.json +0 -332
- package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/domain.json +0 -75
- package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/booking.json +0 -181
- package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/service-add-on.json +0 -130
- package/skills/wix-replatform/resources/rp-target-wix/domains/bookings/entities/service.json +0 -180
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/docs-survey.json +0 -161
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/domain.json +0 -25
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/collection.json +0 -67
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/data-item.json +0 -75
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/reference.json +0 -41
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/docs-survey.json +0 -204
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/domain.json +0 -27
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/contact.json +0 -275
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/email-subscription.json +0 -105
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/label.json +0 -77
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/docs-survey.json +0 -297
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/domain.json +0 -44
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/delivery-profile.json +0 -171
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/discount-rule.json +0 -182
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-applied-discount.json +0 -129
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-invoice-document.json +0 -192
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item-options.json +0 -123
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item.json +0 -85
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-merchant-note.json +0 -112
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-payment.json +0 -191
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-refund.json +0 -164
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order.json +0 -462
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/pickup-location.json +0 -122
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/shipping-option.json +0 -98
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/wishlist-item.json +0 -86
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/docs-survey.json +0 -128
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/domain.json +0 -38
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/category.json +0 -73
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/event.json +0 -107
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/registration-form.json +0 -89
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/rsvp.json +0 -118
- package/skills/wix-replatform/resources/rp-target-wix/domains/events/entities/ticket-definition.json +0 -93
- package/skills/wix-replatform/resources/rp-target-wix/domains/forms/docs-survey.json +0 -86
- package/skills/wix-replatform/resources/rp-target-wix/domains/forms/domain.json +0 -56
- package/skills/wix-replatform/resources/rp-target-wix/domains/forms/entities/form-submission.json +0 -121
- package/skills/wix-replatform/resources/rp-target-wix/domains/forms/entities/form.json +0 -134
- package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/docs-survey.json +0 -41
- package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/domain.json +0 -50
- package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/entities/gift-card-product.json +0 -87
- package/skills/wix-replatform/resources/rp-target-wix/domains/gift-cards/entities/gift-card.json +0 -130
- package/skills/wix-replatform/resources/rp-target-wix/domains/index.json +0 -1418
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/docs-survey.json +0 -41
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/domain.json +0 -24
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-file.json +0 -75
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-folder.json +0 -71
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/docs-survey.json +0 -119
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/domain.json +0 -23
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/badge.json +0 -71
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/custom-field.json +0 -82
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member-about.json +0 -77
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member.json +0 -205
- package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/docs-survey.json +0 -41
- package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/domain.json +0 -46
- package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/entities/plan-order.json +0 -137
- package/skills/wix-replatform/resources/rp-target-wix/domains/pricing-plans/entities/plan.json +0 -133
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/docs-survey.json +0 -7
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/domain.json +0 -36
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/amazon-shop-connect.json +0 -71
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/ebay-shop-connect.json +0 -67
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/google-youtube-connect.json +0 -72
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/meta-catalog-connect.json +0 -74
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/pinterest-catalog-connect.json +0 -69
- package/skills/wix-replatform/resources/rp-target-wix/domains/sales-channels/entities/tiktok-catalog-connect.json +0 -61
- package/skills/wix-replatform/resources/rp-target-wix/domains/schema.json +0 -111
- package/skills/wix-replatform/resources/rp-target-wix/domains/seo/docs-survey.json +0 -109
- package/skills/wix-replatform/resources/rp-target-wix/domains/seo/domain.json +0 -73
- package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/item-seo-tags.json +0 -148
- package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/seo-pattern.json +0 -91
- package/skills/wix-replatform/resources/rp-target-wix/domains/seo/entities/site-seo-tags.json +0 -79
- package/skills/wix-replatform/resources/rp-target-wix/domains/site/docs-survey.json +0 -32
- package/skills/wix-replatform/resources/rp-target-wix/domains/site/domain.json +0 -49
- package/skills/wix-replatform/resources/rp-target-wix/domains/site/entities/business-contact.json +0 -103
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/docs-survey.json +0 -203
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/domain.json +0 -42
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/back-in-stock-request.json +0 -119
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/brand.json +0 -111
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/category.json +0 -81
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/coupon.json +0 -241
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/inventory-item.json +0 -118
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-customization.json +0 -111
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-tag.json +0 -79
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product.json +0 -221
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/promotion.json +0 -84
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/review.json +0 -178
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/subscription-product.json +0 -87
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/docs-survey.json +0 -106
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/domain.json +0 -64
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/manual-tax-mapping.json +0 -119
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-group.json +0 -94
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-region.json +0 -113
- package/skills/wix-replatform/resources/rp-target-wix/domains/tax/entities/tax-settings.json +0 -65
- package/skills/wix-replatform/resources/rp-target-wix/lib/audit-summary.js +0 -64
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-author-attribution.js +0 -165
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-client.js +0 -60
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-duplicates.js +0 -201
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-ledger.js +0 -306
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-report.js +0 -229
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build-stage.js +0 -55
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-build.js +0 -715
- package/skills/wix-replatform/resources/rp-target-wix/lib/blog-post-media.js +0 -567
- package/skills/wix-replatform/resources/rp-target-wix/lib/bulk-results.js +0 -73
- package/skills/wix-replatform/resources/rp-target-wix/lib/contact-extended-fields.js +0 -194
- package/skills/wix-replatform/resources/rp-target-wix/lib/contact-import.js +0 -133
- package/skills/wix-replatform/resources/rp-target-wix/lib/contract-ledger.js +0 -150
- package/skills/wix-replatform/resources/rp-target-wix/lib/coupon-redemption-drift.js +0 -491
- package/skills/wix-replatform/resources/rp-target-wix/lib/customer-member.js +0 -104
- package/skills/wix-replatform/resources/rp-target-wix/lib/data-extension-schema.js +0 -673
- package/skills/wix-replatform/resources/rp-target-wix/lib/domain-knowledge.js +0 -835
- package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-balance-reconciliation.js +0 -119
- package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-build.js +0 -566
- package/skills/wix-replatform/resources/rp-target-wix/lib/gift-card-redemption-build.js +0 -249
- package/skills/wix-replatform/resources/rp-target-wix/lib/ndjson.js +0 -260
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-applied-discount-build.js +0 -63
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-buyer.js +0 -187
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-channel-build.js +0 -64
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-history.js +0 -1027
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-invoice-contract.js +0 -90
- package/skills/wix-replatform/resources/rp-target-wix/lib/order-payment-mapping.js +0 -420
- package/skills/wix-replatform/resources/rp-target-wix/lib/pricing-plan-definition.js +0 -177
- package/skills/wix-replatform/resources/rp-target-wix/lib/product-import.js +0 -151
- package/skills/wix-replatform/resources/rp-target-wix/lib/product-seo.js +0 -723
- package/skills/wix-replatform/resources/rp-target-wix/lib/seo-redirects.js +0 -353
- package/skills/wix-replatform/resources/rp-target-wix/lib/shipping-build.js +0 -1437
- package/skills/wix-replatform/resources/rp-target-wix/lib/shipping-option-stage.js +0 -354
- package/skills/wix-replatform/resources/rp-target-wix/lib/store-config-build.js +0 -134
- package/skills/wix-replatform/resources/rp-target-wix/lib/stores-category-attach.js +0 -221
- package/skills/wix-replatform/resources/rp-target-wix/lib/stores-inventory.js +0 -248
- package/skills/wix-replatform/resources/rp-target-wix/lib/stores-verification.js +0 -321
- package/skills/wix-replatform/resources/rp-target-wix/lib/tax-build.js +0 -231
- package/skills/wix-replatform/resources/rp-target-wix/lib/url-mapper-transport.js +0 -28
- package/skills/wix-replatform/resources/rp-target-wix/lib/value-utils.js +0 -14
- package/skills/wix-replatform/resources/rp-target-wix/lib/variant-correlation.js +0 -34
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-build.js +0 -388
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-target-spec.js +0 -255
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +0 -6041
- package/skills/wix-replatform/resources/rp-target-wix/lib/write-verification.js +0 -3
- package/skills/wix-replatform/resources/rp-target-wix/scripts/docs-survey-sync.js +0 -150
- package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge-validate.js +0 -69
- package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge.js +0 -100
- package/skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js +0 -112
- package/skills/wix-replatform/resources/rp-telemetry/SKILL.md +0 -484
- package/skills/wix-replatform/resources/rp-telemetry/lib/bi-sink.js +0 -318
- package/skills/wix-replatform/resources/rp-telemetry/lib/telemetry-recorder.js +0 -2831
- package/skills/wix-replatform/resources/rp-telemetry/lib/transcript-digest.js +0 -318
- package/skills/wix-replatform/resources/rp-telemetry/scripts/meter-from-audit.js +0 -83
- package/skills/wix-replatform/resources/rp-telemetry/scripts/rp-telemetry.js +0 -243
- package/skills/wix-replatform/resources/rp-website-continuation/SKILL.md +0 -94
- package/skills/wix-replatform/scripts/artifact-freshness.js +0 -76
- package/skills/wix-replatform/scripts/census-reconciliation.js +0 -25
- package/skills/wix-replatform/scripts/cms-fallback.js +0 -89
- package/skills/wix-replatform/scripts/discovered-census.js +0 -26
- package/skills/wix-replatform/scripts/discovered-set-status.js +0 -33
- package/skills/wix-replatform/scripts/entity-qa-attestation.js +0 -130
- package/skills/wix-replatform/scripts/execution-health-check.js +0 -415
- package/skills/wix-replatform/scripts/execution-manifest-validate.js +0 -43
- package/skills/wix-replatform/scripts/execution-state-prepare.js +0 -128
- package/skills/wix-replatform/scripts/expectations-ledger.js +0 -44
- package/skills/wix-replatform/scripts/import-contract-verify.js +0 -183
- package/skills/wix-replatform/scripts/internal-link-audit.js +0 -90
- package/skills/wix-replatform/scripts/item-seo-tags-verify.js +0 -205
- package/skills/wix-replatform/scripts/migration-completion.js +0 -76
- package/skills/wix-replatform/scripts/orchestration-preflight.js +0 -67
- package/skills/wix-replatform/scripts/orchestration-route.js +0 -77
- package/skills/wix-replatform/scripts/orchestration-state.js +0 -381
- package/skills/wix-replatform/scripts/read-back-gate.js +0 -175
- package/skills/wix-replatform/scripts/replatform-console.js +0 -146
- package/skills/wix-replatform/scripts/replatform.js +0 -436
- package/skills/wix-replatform/scripts/source-secrets.js +0 -77
- package/skills/wix-replatform/scripts/url-redirects.js +0 -286
- package/skills/wix-replatform/scripts/user-action.js +0 -58
- package/skills/wix-replatform/scripts/website-handoff-generate.js +0 -64
- package/skills/wix-replatform/scripts/wix-dashboard-url.js +0 -54
- package/skills/wix-replatform/scripts/wix-destination.js +0 -143
- package/skills/wix-replatform/scripts/wix-headless-scaffold.js +0 -762
- package/skills/wix-vibe-headless/SKILL.md +0 -183
- package/skills/wix-vibe-headless/install/deploy.cjs +0 -221
- package/skills/wix-vibe-headless/install/pin-agents-md.cjs +0 -35
- package/skills/wix-vibe-headless/platforms/README.md +0 -26
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +0 -127
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +0 -121
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +0 -87
- package/skills/wix-vibe-headless/platforms/base44.md +0 -148
- package/skills/wix-vibe-headless/platforms/generic.md +0 -132
- package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +0 -30
- package/skills/wix-vibe-headless/references/_shared/routing.md +0 -86
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +0 -270
- package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +0 -43
- package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +0 -25
- package/skills/wix-vibe-headless/references/blog/app/components/PostGrid.jsx +0 -15
- package/skills/wix-vibe-headless/references/blog/app/context/TaxonomyContext.jsx +0 -35
- package/skills/wix-vibe-headless/references/blog/app/hooks/usePostDetail.js +0 -38
- package/skills/wix-vibe-headless/references/blog/app/pages/Blog.jsx +0 -40
- package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +0 -54
- package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +0 -65
- package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +0 -50
- package/skills/wix-vibe-headless/references/blog/app/rest/wix-blog.js +0 -217
- package/skills/wix-vibe-headless/references/blog/seed/SEED.md +0 -99
- package/skills/wix-vibe-headless/references/blog/seed/seed-blog.cjs +0 -269
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +0 -342
- package/skills/wix-vibe-headless/references/bookings/app/components/BookingForm.jsx +0 -65
- package/skills/wix-vibe-headless/references/bookings/app/components/RentalDurationPicker.jsx +0 -67
- package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +0 -47
- package/skills/wix-vibe-headless/references/bookings/app/components/ServiceGrid.jsx +0 -13
- package/skills/wix-vibe-headless/references/bookings/app/components/SlotPicker.jsx +0 -126
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useRentalDuration.js +0 -128
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useServiceDetail.js +0 -110
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useServices.js +0 -58
- package/skills/wix-vibe-headless/references/bookings/app/lib/serviceFacts.js +0 -95
- package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +0 -94
- package/skills/wix-vibe-headless/references/bookings/app/pages/Services.jsx +0 -89
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-checkout.js +0 -148
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-rentals.js +0 -250
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-services.js +0 -300
- package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +0 -154
- package/skills/wix-vibe-headless/references/bookings/seed/seed-bookings.cjs +0 -333
- package/skills/wix-vibe-headless/references/bookings/seed/seed-rentals.cjs +0 -267
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +0 -178
- package/skills/wix-vibe-headless/references/cms/app/collection.config.js +0 -31
- package/skills/wix-vibe-headless/references/cms/app/lib/wixImage.js +0 -18
- package/skills/wix-vibe-headless/references/cms/app/rest/wix-cms.js +0 -231
- package/skills/wix-vibe-headless/references/cms/seed/SEED.md +0 -145
- package/skills/wix-vibe-headless/references/donations/INSTRUCTIONS.md +0 -88
- package/skills/wix-vibe-headless/references/donations/app/rest/wix-donations.js +0 -112
- package/skills/wix-vibe-headless/references/donations/seed/SEED.md +0 -11
- package/skills/wix-vibe-headless/references/donations/tests/donations.test.mjs +0 -258
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +0 -267
- package/skills/wix-vibe-headless/references/events/app/components/CategoryFilter.jsx +0 -23
- package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +0 -43
- package/skills/wix-vibe-headless/references/events/app/components/EventGrid.jsx +0 -13
- package/skills/wix-vibe-headless/references/events/app/components/EventRegistration.jsx +0 -29
- package/skills/wix-vibe-headless/references/events/app/components/RsvpForm.jsx +0 -57
- package/skills/wix-vibe-headless/references/events/app/components/TicketPicker.jsx +0 -78
- package/skills/wix-vibe-headless/references/events/app/hooks/useEventDetail.js +0 -27
- package/skills/wix-vibe-headless/references/events/app/hooks/useEventsList.js +0 -54
- package/skills/wix-vibe-headless/references/events/app/hooks/useRsvpForm.js +0 -40
- package/skills/wix-vibe-headless/references/events/app/hooks/useTicketing.js +0 -81
- package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +0 -48
- package/skills/wix-vibe-headless/references/events/app/pages/Events.jsx +0 -36
- package/skills/wix-vibe-headless/references/events/app/rest/wix-events-browse.js +0 -136
- package/skills/wix-vibe-headless/references/events/app/rest/wix-events-registration.js +0 -187
- package/skills/wix-vibe-headless/references/events/seed/SEED.md +0 -103
- package/skills/wix-vibe-headless/references/events/seed/seed-events.cjs +0 -251
- package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +0 -487
- package/skills/wix-vibe-headless/references/forms/app/hooks/useWixForm.js +0 -425
- package/skills/wix-vibe-headless/references/forms/app/lib/wix-form-schema-utils.js +0 -187
- package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms-submissions.js +0 -238
- package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms.js +0 -108
- package/skills/wix-vibe-headless/references/forms/seed/SEED.md +0 -1836
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +0 -328
- package/skills/wix-vibe-headless/references/members/app/components/LoginForm.jsx +0 -101
- package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +0 -33
- package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +0 -21
- package/skills/wix-vibe-headless/references/members/app/components/SocialButtons.jsx +0 -29
- package/skills/wix-vibe-headless/references/members/app/context/MemberContext.jsx +0 -54
- package/skills/wix-vibe-headless/references/members/app/hooks/useLoginForm.js +0 -77
- package/skills/wix-vibe-headless/references/members/app/pages/Account.jsx +0 -36
- package/skills/wix-vibe-headless/references/members/app/pages/Callback.jsx +0 -32
- package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +0 -35
- package/skills/wix-vibe-headless/references/members/app/rest/wix-members-auth.js +0 -390
- package/skills/wix-vibe-headless/references/members/install/Register.jsx +0 -22
- package/skills/wix-vibe-headless/references/members/seed/SEED.md +0 -21
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +0 -270
- package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +0 -31
- package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionGrid.jsx +0 -15
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +0 -39
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectGrid.jsx +0 -15
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectMedia.jsx +0 -29
- package/skills/wix-vibe-headless/references/portfolio/app/hooks/useCollectionProjects.js +0 -35
- package/skills/wix-vibe-headless/references/portfolio/app/hooks/usePortfolioGallery.js +0 -27
- package/skills/wix-vibe-headless/references/portfolio/app/hooks/useProjectDetail.js +0 -25
- package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +0 -39
- package/skills/wix-vibe-headless/references/portfolio/app/pages/Portfolio.jsx +0 -27
- package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +0 -60
- package/skills/wix-vibe-headless/references/portfolio/app/rest/wix-portfolio.js +0 -223
- package/skills/wix-vibe-headless/references/portfolio/seed/SEED.md +0 -90
- package/skills/wix-vibe-headless/references/portfolio/seed/seed-portfolio.cjs +0 -236
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +0 -280
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/OrderCard.jsx +0 -35
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +0 -48
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanGrid.jsx +0 -16
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanPrice.jsx +0 -35
- package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/useMyPlans.js +0 -25
- package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/usePlanDetail.js +0 -28
- package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/usePlans.js +0 -30
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/MyPlans.jsx +0 -30
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +0 -53
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/Plans.jsx +0 -28
- package/skills/wix-vibe-headless/references/pricing-plans/app/rest/wix-pricing-plans.js +0 -157
- package/skills/wix-vibe-headless/references/pricing-plans/seed/SEED.md +0 -82
- package/skills/wix-vibe-headless/references/pricing-plans/seed/seed-pricing-plans.cjs +0 -251
- package/skills/wix-vibe-headless/references/rentals/INSTRUCTIONS.md +0 -13
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +0 -290
- package/skills/wix-vibe-headless/references/restaurants/app/components/ItemDialog.jsx +0 -81
- package/skills/wix-vibe-headless/references/restaurants/app/components/MenuItemCard.jsx +0 -71
- package/skills/wix-vibe-headless/references/restaurants/app/components/MenuList.jsx +0 -43
- package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartButton.jsx +0 -21
- package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartDrawer.jsx +0 -72
- package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +0 -66
- package/skills/wix-vibe-headless/references/restaurants/app/hooks/useItemOrder.js +0 -32
- package/skills/wix-vibe-headless/references/restaurants/app/hooks/useReservation.js +0 -73
- package/skills/wix-vibe-headless/references/restaurants/app/pages/Menu.jsx +0 -28
- package/skills/wix-vibe-headless/references/restaurants/app/pages/Reservations.jsx +0 -102
- package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-menu.js +0 -224
- package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +0 -218
- package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-reservations.js +0 -107
- package/skills/wix-vibe-headless/references/restaurants/seed/SEED.md +0 -176
- package/skills/wix-vibe-headless/references/restaurants/seed/seed-restaurants.cjs +0 -452
- package/skills/wix-vibe-headless/references/shared/app/components/WixManageBanner.jsx +0 -56
- package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +0 -9
- package/skills/wix-vibe-headless/references/shared/app/rest/wix-client.js +0 -185
- package/skills/wix-vibe-headless/references/shared/app/rest/wix-config.js +0 -8
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -471
- package/skills/wix-vibe-headless/references/storefront/app/components/CartButton.jsx +0 -20
- package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +0 -167
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +0 -83
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +0 -104
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +0 -163
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useShop.js +0 -83
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useVariantOptions.js +0 -76
- package/skills/wix-vibe-headless/references/storefront/app/lib/storeImage.js +0 -64
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +0 -176
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +0 -218
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +0 -174
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +0 -546
|
@@ -1,1074 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Client for wix-wp-plugin-v2 -- the ONLY bridge-plugin-style mechanism this
|
|
4
|
-
// skill uses for data with no REST route at all (db-only, or admin-page-only with an
|
|
5
|
-
// underlying table). There is no per-capability fixed adapter any more: any db-only
|
|
6
|
-
// entity's fulfillment can point at this SAME handler with a different `table`, because
|
|
7
|
-
// wix-wp-plugin-v2 itself has no per-capability code -- see plugins/wix-wp-plugin-v2/README.md.
|
|
8
|
-
//
|
|
9
|
-
// Two capabilities, matching the plugin's own two routes:
|
|
10
|
-
// - discoverStructure(): schema only (columns, types, key hints). Safe to call before a
|
|
11
|
-
// human has decided anything about a table -- no row data leaves the site.
|
|
12
|
-
// - queryStructure(): real row data, via one bounded, parameterized SELECT (select
|
|
13
|
-
// columns/aggregates, optional where/groupBy, required orderBy, cursor pagination).
|
|
14
|
-
// Pulling real data through this for a specific entity is a per-entity decision, not
|
|
15
|
-
// implied by schema-discoverability -- e.g. gift-card-activity's balance/redemption
|
|
16
|
-
// ledger still needs its own masking/correlation call even though its
|
|
17
|
-
// schema is freely discoverable.
|
|
18
|
-
|
|
19
|
-
const { createHash, createHmac, randomUUID, timingSafeEqual } = require('node:crypto');
|
|
20
|
-
|
|
21
|
-
const SHARED_CORE_TABLES_REQUIRING_WHERE = new Set(['posts', 'terms', 'term_taxonomy', 'term_relationships', 'comments']);
|
|
22
|
-
const MAX_IN_VALUES = 100;
|
|
23
|
-
const MAX_WHERE_CONDITIONS = 20;
|
|
24
|
-
const HANDLER_VERSION = '1.3.0'; // 1.3.0: signed migration-key transport plus strict response-envelope validation.
|
|
25
|
-
|
|
26
|
-
function bodyOf(response) {
|
|
27
|
-
return response && response.data !== undefined ? response.data : response;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function assertColumn(column, table) {
|
|
31
|
-
if (!column || typeof column.name !== 'string' || column.name.length === 0) {
|
|
32
|
-
throw new Error(`table ${table}: column name is required`);
|
|
33
|
-
}
|
|
34
|
-
if (typeof column.type !== 'string' || column.type.length === 0) {
|
|
35
|
-
throw new Error(`table ${table}: column ${column.name} is missing a type`);
|
|
36
|
-
}
|
|
37
|
-
if (typeof column.nullable !== 'boolean') {
|
|
38
|
-
throw new Error(`table ${table}: column ${column.name} is missing nullable`);
|
|
39
|
-
}
|
|
40
|
-
if (column.key !== null && !['primary', 'unique', 'index'].includes(column.key)) {
|
|
41
|
-
throw new Error(`table ${table}: column ${column.name} has an invalid key hint ${JSON.stringify(column.key)}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const EAV_TIERS = ['key-discovery', 'key-scoped'];
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* `redactionMetadata`, required on every response and validated against the
|
|
49
|
-
* schema's actual contract: a NON-NEGATIVE INTEGER count and an array of STRINGS.
|
|
50
|
-
*
|
|
51
|
-
* The loose version accepted `-1.5` and `[7]`, which was not merely untidy. Those values flow
|
|
52
|
-
* straight into `sourceReadCounts()` and on to rp-telemetry, whose validator correctly rejects
|
|
53
|
-
* them -- so a malformed count silently discarded the whole accumulated telemetry batch, far
|
|
54
|
-
* from the response that caused it. Validate at the boundary, where the error can still name
|
|
55
|
-
* the table.
|
|
56
|
-
*/
|
|
57
|
-
function assertRedactionMetadata(redactionMetadata, table) {
|
|
58
|
-
if (!redactionMetadata || typeof redactionMetadata !== 'object' || Array.isArray(redactionMetadata)) {
|
|
59
|
-
throw new Error(
|
|
60
|
-
`table ${table}: response is missing redactionMetadata, which is required on every `
|
|
61
|
-
+ 'query-structure response. The source site is running a wix-wp-plugin-v2 older than 0.4.0, which has no '
|
|
62
|
-
+ 'secret-value guard -- upgrade the plugin rather than treating this as an empty redaction set.',
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
const { redactedValueCount, redactedColumns } = redactionMetadata;
|
|
66
|
-
if (!Number.isInteger(redactedValueCount) || redactedValueCount < 0) {
|
|
67
|
-
throw new Error(`table ${table}: redactionMetadata.redactedValueCount must be a non-negative integer (got ${JSON.stringify(redactedValueCount)})`);
|
|
68
|
-
}
|
|
69
|
-
if (!Array.isArray(redactedColumns) || redactedColumns.some((c) => typeof c !== 'string')) {
|
|
70
|
-
throw new Error(`table ${table}: redactionMetadata.redactedColumns must be an array of column names`);
|
|
71
|
-
}
|
|
72
|
-
if (redactedValueCount > 0 && redactedColumns.length === 0) {
|
|
73
|
-
throw new Error(`table ${table}: redactionMetadata reports ${redactedValueCount} redaction(s) but names no column -- a count with no column is not actionable`);
|
|
74
|
-
}
|
|
75
|
-
return { redactedValueCount, redactedColumns: [...redactedColumns] };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** A {keyColumn, valueColumn} pair, as reported by either route. */
|
|
79
|
-
function assertPairShape(pair, table, where) {
|
|
80
|
-
if (!pair || typeof pair.keyColumn !== 'string' || typeof pair.valueColumn !== 'string') {
|
|
81
|
-
throw new Error(`table ${table}: ${where} must carry string keyColumn and valueColumn`);
|
|
82
|
-
}
|
|
83
|
-
return { keyColumn: pair.keyColumn, valueColumn: pair.valueColumn };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* discover-structure's `eavPair`. Absent for an ordinary table; when present it names the
|
|
88
|
-
* column a query must pin. `additionalPairs` appears only for the rare schema carrying more
|
|
89
|
-
* than one recognized pair -- and it is propagated rather than dropped, because every one of
|
|
90
|
-
* those value columns is independently guarded and a caller that saw only the first would
|
|
91
|
-
* build a request the plugin refuses.
|
|
92
|
-
*/
|
|
93
|
-
function assertEavPair(eavPair, table) {
|
|
94
|
-
if (eavPair === undefined || eavPair === null) return null;
|
|
95
|
-
const pair = assertPairShape(eavPair, table, 'eavPair');
|
|
96
|
-
if (eavPair.additionalPairs !== undefined) {
|
|
97
|
-
if (!Array.isArray(eavPair.additionalPairs)) throw new Error(`table ${table}: eavPair.additionalPairs must be an array`);
|
|
98
|
-
pair.additionalPairs = eavPair.additionalPairs.map((p) => assertPairShape(p, table, 'eavPair.additionalPairs[]'));
|
|
99
|
-
}
|
|
100
|
-
return pair;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* query-structure's `eavAccess`. Absent for a table with no key/value pair; present with a
|
|
105
|
-
* tier and the key names the request pinned otherwise. Validated rather than passed through
|
|
106
|
-
* so a caller can branch on `tier` without re-checking the shape.
|
|
107
|
-
*/
|
|
108
|
-
function assertAccessPair(pair, table, where) {
|
|
109
|
-
const shape = assertPairShape(pair, table, where);
|
|
110
|
-
if (!EAV_TIERS.includes(pair.tier)) {
|
|
111
|
-
throw new Error(`table ${table}: ${where}.tier ${JSON.stringify(pair.tier)} is not one of ${EAV_TIERS.join(', ')}`);
|
|
112
|
-
}
|
|
113
|
-
if (!Array.isArray(pair.pinnedKeys) || pair.pinnedKeys.some((k) => typeof k !== 'string')) {
|
|
114
|
-
throw new Error(`table ${table}: ${where}.pinnedKeys must be an array of key names`);
|
|
115
|
-
}
|
|
116
|
-
return { ...shape, tier: pair.tier, pinnedKeys: [...pair.pinnedKeys] };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* query-structure's `eavAccess`. Absent for a table with no key/value pair.
|
|
121
|
-
*
|
|
122
|
-
* EVERY pair goes through the SAME validation, primary and additional alike. An earlier
|
|
123
|
-
* version validated the primary strictly and then copied an additional pair's `tier`
|
|
124
|
-
* unchecked while coercing a malformed `pinnedKeys` to `[]` — so a response claiming
|
|
125
|
-
* `tier: "full"` on a second pair was accepted, and a caller branching on tier could conclude
|
|
126
|
-
* a guarded column was freely readable. Validating one of N is not validating.
|
|
127
|
-
*/
|
|
128
|
-
function assertEavAccess(eavAccess, table) {
|
|
129
|
-
if (eavAccess === undefined || eavAccess === null) return null;
|
|
130
|
-
const access = assertAccessPair(eavAccess, table, 'eavAccess');
|
|
131
|
-
if (eavAccess.additionalPairs !== undefined) {
|
|
132
|
-
if (!Array.isArray(eavAccess.additionalPairs)) throw new Error(`table ${table}: eavAccess.additionalPairs must be an array`);
|
|
133
|
-
access.additionalPairs = eavAccess.additionalPairs.map((p) => assertAccessPair(p, table, 'eavAccess.additionalPairs[]'));
|
|
134
|
-
}
|
|
135
|
-
return access;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Every pair in an eavAccess, primary first — what any per-pair reasoning should iterate. */
|
|
139
|
-
function eavAccessPairs(eavAccess) {
|
|
140
|
-
if (!eavAccess) return [];
|
|
141
|
-
const { keyColumn, valueColumn, tier, pinnedKeys } = eavAccess;
|
|
142
|
-
return [{ keyColumn, valueColumn, tier, pinnedKeys }, ...(eavAccess.additionalPairs || [])];
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Every value column this table guards -- the primary pair's plus any additional ones. What
|
|
147
|
-
* a caller needs to build a legal request: these columns are unreachable unless the matching
|
|
148
|
-
* key column is pinned.
|
|
149
|
-
*/
|
|
150
|
-
function guardedValueColumns(pairOrAccess) {
|
|
151
|
-
if (!pairOrAccess) return [];
|
|
152
|
-
return [pairOrAccess.valueColumn, ...(pairOrAccess.additionalPairs || []).map((p) => p.valueColumn)];
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Reports the live column list for one bare table name. Structure only, never row data --
|
|
157
|
-
* see modules/class-wmh2-structure-discovery.php's own contract. Throws on any
|
|
158
|
-
* malformed/incomplete response rather than returning a partial result silently.
|
|
159
|
-
*
|
|
160
|
-
* Two credential modes, matching the plugin's own two route registrations for
|
|
161
|
-
* `/structure`: the default is the WordPress Application Password path (GET, via the
|
|
162
|
-
* caller-supplied `httpClient`). Pass `auth: { type: 'migration-key', migrationKey, siteId,
|
|
163
|
-
* migrationId, timeoutMs }` to use the signed-JWT path instead -- required on a source site
|
|
164
|
-
* with no WooCommerce, where the Application Password routes are `manage_woocommerce`-gated
|
|
165
|
-
* and therefore unusable by any user at all (see "Signed migration-key path" below). That
|
|
166
|
-
* mode calls the plugin directly over `fetch` and does not use `httpClient`.
|
|
167
|
-
*/
|
|
168
|
-
async function discoverStructure({ httpClient, siteBaseUrl, auth, table } = {}) {
|
|
169
|
-
if (!siteBaseUrl || typeof siteBaseUrl !== 'string') throw new Error('siteBaseUrl is required');
|
|
170
|
-
if (!table || typeof table !== 'string' || !/^[A-Za-z0-9_]+$/.test(table)) {
|
|
171
|
-
throw new Error('table is required and must be a bare identifier (^[A-Za-z0-9_]+$)');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
let body;
|
|
175
|
-
if (auth && auth.type === 'migration-key') {
|
|
176
|
-
if (!auth.migrationKey || typeof auth.migrationKey !== 'string') {
|
|
177
|
-
throw new Error('auth.migrationKey is required for the migration-key credential path');
|
|
178
|
-
}
|
|
179
|
-
body = await callSignedOperation({
|
|
180
|
-
siteBaseUrl,
|
|
181
|
-
migrationKey: auth.migrationKey,
|
|
182
|
-
operation: 'discover-structure',
|
|
183
|
-
route: '/wix-wp-plugin/v2/structure',
|
|
184
|
-
siteId: auth.siteId || DEFAULT_MIGRATION_KEY_SITE_ID,
|
|
185
|
-
migrationId: auth.migrationId || randomUUID(),
|
|
186
|
-
operationClaims: { table },
|
|
187
|
-
timeoutMs: auth.timeoutMs,
|
|
188
|
-
});
|
|
189
|
-
} else {
|
|
190
|
-
if (!httpClient || typeof httpClient.get !== 'function') throw new Error('httpClient.get is required');
|
|
191
|
-
const base = siteBaseUrl.replace(/\/$/, '');
|
|
192
|
-
const response = await httpClient.get(`${base}/wp-json/wix-wp-plugin/v2/structure`, {
|
|
193
|
-
params: { table },
|
|
194
|
-
auth,
|
|
195
|
-
});
|
|
196
|
-
body = bodyOf(response);
|
|
197
|
-
}
|
|
198
|
-
if (!body || body.table !== table || !Array.isArray(body.columns)) {
|
|
199
|
-
throw new Error(`table ${table}: response did not match discover-structure's response-payload contract`);
|
|
200
|
-
}
|
|
201
|
-
for (const column of body.columns) assertColumn(column, table);
|
|
202
|
-
|
|
203
|
-
// `eavPair` MUST be propagated. Dropping it here was a real bug: it is the
|
|
204
|
-
// caller's only advance warning that this table is key-scoped, so without it a caller
|
|
205
|
-
// builds an unscoped "select every column" request and gets a 400 it cannot interpret.
|
|
206
|
-
return { table: body.table, columns: body.columns, eavPair: assertEavPair(body.eavPair, table) };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Runs one structure request. Returns one page of rows plus pagingMetadata -- the caller
|
|
211
|
-
* is responsible for looping on `hasNext`/`cursors.next` until a full read completes, same
|
|
212
|
-
* cursor-paging contract as any other WordPress source route this skill reads. This client
|
|
213
|
-
* does not re-validate the structure request's own shape (table/select/where/groupBy/orderBy)
|
|
214
|
-
* beyond requiring a `table` -- the plugin itself re-validates every identifier against a
|
|
215
|
-
* live DESCRIBE on every request, so duplicating that here would only create a second copy
|
|
216
|
-
* to keep in sync.
|
|
217
|
-
*
|
|
218
|
-
* Two credential modes, matching the plugin's own two route registrations: the default is
|
|
219
|
-
* the WordPress Application Password path (POST /query/admin-key, plain JSON, via the
|
|
220
|
-
* caller-supplied `httpClient`). Pass `auth: { type: 'migration-key', migrationKey, siteId,
|
|
221
|
-
* migrationId, timeoutMs }` to use the signed-JWT path instead (POST /query, no `/admin-key`
|
|
222
|
-
* suffix, JWT body) -- required on a source site with no WooCommerce, where the Application
|
|
223
|
-
* Password routes are `manage_woocommerce`-gated and therefore unusable by any user at all.
|
|
224
|
-
* That mode calls the plugin directly over `fetch` and does not use `httpClient`; it also
|
|
225
|
-
* requires an integer `cursorPaging.limit` on every request (the admin-key path lets the
|
|
226
|
-
* server default it), so an omitted `limit` falls back to this module's mirror of
|
|
227
|
-
* `WMH2_Contract::DEFAULT_LIMIT`.
|
|
228
|
-
*
|
|
229
|
-
* `onSourceRead` is the telemetry hook: called once per successful read with the
|
|
230
|
-
* counts for THAT read, for the caller to pass to rp-telemetry's `sourceRead()`. Optional, so
|
|
231
|
-
* a unit test or a one-off script need not wire telemetry, but every real reader should.
|
|
232
|
-
*/
|
|
233
|
-
async function queryStructure({ httpClient, siteBaseUrl, auth, structureRequest, cursor, limit, onSourceRead } = {}) {
|
|
234
|
-
if (!siteBaseUrl || typeof siteBaseUrl !== 'string') throw new Error('siteBaseUrl is required');
|
|
235
|
-
if (!structureRequest || typeof structureRequest !== 'object' || typeof structureRequest.table !== 'string') {
|
|
236
|
-
throw new Error('structureRequest with a table is required');
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
let body;
|
|
240
|
-
if (auth && auth.type === 'migration-key') {
|
|
241
|
-
if (!auth.migrationKey || typeof auth.migrationKey !== 'string') {
|
|
242
|
-
throw new Error('auth.migrationKey is required for the migration-key credential path');
|
|
243
|
-
}
|
|
244
|
-
const cursorPaging = { cursor: cursor === undefined ? null : cursor, limit: limit === undefined ? DEFAULT_QUERY_LIMIT : limit };
|
|
245
|
-
body = await callSignedOperation({
|
|
246
|
-
siteBaseUrl,
|
|
247
|
-
migrationKey: auth.migrationKey,
|
|
248
|
-
operation: 'query-structure',
|
|
249
|
-
route: '/wix-wp-plugin/v2/query',
|
|
250
|
-
siteId: auth.siteId || DEFAULT_MIGRATION_KEY_SITE_ID,
|
|
251
|
-
migrationId: auth.migrationId || randomUUID(),
|
|
252
|
-
operationClaims: { structureRequest, cursorPaging },
|
|
253
|
-
timeoutMs: auth.timeoutMs,
|
|
254
|
-
});
|
|
255
|
-
} else {
|
|
256
|
-
if (!httpClient || typeof httpClient.post !== 'function') throw new Error('httpClient.post is required');
|
|
257
|
-
const base = siteBaseUrl.replace(/\/$/, '');
|
|
258
|
-
const requestBody = { structureRequest };
|
|
259
|
-
if (cursor !== undefined) requestBody.cursor = cursor;
|
|
260
|
-
if (limit !== undefined) requestBody.limit = limit;
|
|
261
|
-
const response = await httpClient.post(`${base}/wp-json/wix-wp-plugin/v2/query/admin-key`, {
|
|
262
|
-
body: JSON.stringify(requestBody),
|
|
263
|
-
auth,
|
|
264
|
-
});
|
|
265
|
-
body = bodyOf(response);
|
|
266
|
-
}
|
|
267
|
-
if (!body || !Array.isArray(body.rows) || !body.pagingMetadata) {
|
|
268
|
-
throw new Error(`table ${structureRequest.table}: response did not match query-structure's response-payload contract`);
|
|
269
|
-
}
|
|
270
|
-
const { pagingMetadata } = body;
|
|
271
|
-
if (typeof pagingMetadata.hasNext !== 'boolean' || typeof pagingMetadata.total !== 'number'
|
|
272
|
-
|| !pagingMetadata.cursors || !('next' in pagingMetadata.cursors)) {
|
|
273
|
-
throw new Error(`table ${structureRequest.table}: pagingMetadata does not match query-structure's response-payload contract`);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// REQUIRED on every response, so its absence is a hard error rather than a
|
|
277
|
-
// defaulted zero. A plugin old enough not to send it is a plugin without the value guard
|
|
278
|
-
// at all, and silently treating that as "nothing was redacted" would report a guarded read
|
|
279
|
-
// that never happened. Refusing names the real problem: upgrade the plugin.
|
|
280
|
-
const redactionMetadata = assertRedactionMetadata(body.redactionMetadata, structureRequest.table);
|
|
281
|
-
|
|
282
|
-
const eavAccess = assertEavAccess(body.eavAccess, structureRequest.table);
|
|
283
|
-
if (onSourceRead) onSourceRead(sourceReadCounts(structureRequest.table, eavAccess, redactionMetadata));
|
|
284
|
-
return { rows: body.rows, pagingMetadata, redactionMetadata, eavAccess };
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// --- Signed migration-key path ("signed request/response" model, contract version 2) ---------
|
|
288
|
-
//
|
|
289
|
-
// A WordPress site with no `wc/*` REST namespace has no `manage_woocommerce` capability for
|
|
290
|
-
// ANYONE, including an administrator -- the Application Password routes above are then
|
|
291
|
-
// structurally unusable, for every user on that site, not just a permissions edge case. The
|
|
292
|
-
// signed migration-key path is the only credential the plugin's own connection page ever
|
|
293
|
-
// issues that does not depend on that capability: `WMH2_Request_Auth::verify_operation()`
|
|
294
|
-
// checks a JWT signature and request-binding claims, never a WordPress capability.
|
|
295
|
-
//
|
|
296
|
-
// This is a DIFFERENT wire shape from the Application Password routes above, not a header
|
|
297
|
-
// swap on the same request: POST only (both operations), a distinct un-suffixed route
|
|
298
|
-
// (`/structure`, `/query` -- never `/query/admin-key`), `Content-Type: application/jwt` with
|
|
299
|
-
// the compact JWT as the entire raw body (never a JSON envelope), and a signed JWT string
|
|
300
|
-
// back rather than a plain JSON object. That is why this path calls `fetch` directly instead
|
|
301
|
-
// of going through the caller-supplied `httpClient` the Application Password functions take --
|
|
302
|
-
// an httpClient built for "send this JSON, get back that JSON" has no seam for "the body IS
|
|
303
|
-
// the credential-bearing artifact". `plugins/wix-wp-plugin-v2/examples/jwt-smoke-test.mjs` is
|
|
304
|
-
// the reference implementation this mirrors; reuse its claim set and response validation
|
|
305
|
-
// rather than inventing a second one that can drift from the plugin's own contract test
|
|
306
|
-
// (`tests/source-wordpress/wix-wp-plugin-v2-jwt-contract-v2-test.js`).
|
|
307
|
-
|
|
308
|
-
const JWT_REQUEST_ISSUER = 'wix-siteImportService';
|
|
309
|
-
const JWT_REQUEST_AUDIENCE = 'wix-wp-plugin-v2';
|
|
310
|
-
const JWT_RESPONSE_ISSUER = 'wix-wp-plugin-v2';
|
|
311
|
-
const JWT_RESPONSE_AUDIENCE = 'wix-siteImportService';
|
|
312
|
-
const JWT_CONTRACT_VERSION = 2;
|
|
313
|
-
const JWT_REQUEST_LIFETIME_SECONDS = 60; // must stay under the plugin's MAX_REQUEST_TOKEN_LIFETIME_SECONDS (120)
|
|
314
|
-
const DEFAULT_MIGRATION_KEY_SITE_ID = 'rp-source-wordpress';
|
|
315
|
-
const DEFAULT_QUERY_LIMIT = 50; // mirrors WMH2_Contract::DEFAULT_LIMIT; cursorPaging.limit is a required int on this path
|
|
316
|
-
const DEFAULT_SIGNED_TIMEOUT_MS = 60000;
|
|
317
|
-
|
|
318
|
-
function base64url(value) {
|
|
319
|
-
return (Buffer.isBuffer(value) ? value : Buffer.from(value, 'utf8')).toString('base64url');
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function base64urlDecode(value) {
|
|
323
|
-
return Buffer.from(value, 'base64url');
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/** Signs one operation's claims with the migration key. Returns the compact JWT plus the claims, so the caller can bind the response back to this exact request via `jti`. */
|
|
327
|
-
function signMigrationKeyRequest({ migrationKey, operation, route, siteId, migrationId, operationClaims }) {
|
|
328
|
-
const now = Math.floor(Date.now() / 1000);
|
|
329
|
-
const claims = {
|
|
330
|
-
iss: JWT_REQUEST_ISSUER,
|
|
331
|
-
aud: JWT_REQUEST_AUDIENCE,
|
|
332
|
-
site_id: siteId,
|
|
333
|
-
migration_id: migrationId,
|
|
334
|
-
operation,
|
|
335
|
-
contract_version: JWT_CONTRACT_VERSION,
|
|
336
|
-
method: 'POST',
|
|
337
|
-
route,
|
|
338
|
-
...operationClaims,
|
|
339
|
-
iat: now,
|
|
340
|
-
nbf: now,
|
|
341
|
-
exp: now + JWT_REQUEST_LIFETIME_SECONDS,
|
|
342
|
-
jti: randomUUID(),
|
|
343
|
-
};
|
|
344
|
-
const signingInput = [{ alg: 'HS256', typ: 'JWT' }, claims].map((part) => base64url(JSON.stringify(part))).join('.');
|
|
345
|
-
const signature = createHmac('sha256', migrationKey).update(signingInput).digest();
|
|
346
|
-
return { token: `${signingInput}.${base64url(signature)}`, requestClaims: claims };
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/** Verifies a signed response's signature and every request-binding claim before trusting its payload. Never returns an unverified payload. */
|
|
350
|
-
function verifyMigrationKeyResponse({ migrationKey, jwt, requestClaims, operation, siteId, migrationId }) {
|
|
351
|
-
const parts = String(jwt).split('.');
|
|
352
|
-
if (parts.length !== 3) {
|
|
353
|
-
throw new Error(`${operation}: response is not a compact three-part JWT`);
|
|
354
|
-
}
|
|
355
|
-
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
356
|
-
const header = JSON.parse(base64urlDecode(encodedHeader).toString('utf8'));
|
|
357
|
-
if (header.alg !== 'HS256') {
|
|
358
|
-
throw new Error(`${operation}: unexpected response JWT algorithm ${header.alg}`);
|
|
359
|
-
}
|
|
360
|
-
const expectedSignature = createHmac('sha256', migrationKey).update(`${encodedHeader}.${encodedPayload}`).digest();
|
|
361
|
-
const actualSignature = base64urlDecode(encodedSignature);
|
|
362
|
-
if (expectedSignature.length !== actualSignature.length || !timingSafeEqual(expectedSignature, actualSignature)) {
|
|
363
|
-
throw new Error(`${operation}: response JWT signature is invalid`);
|
|
364
|
-
}
|
|
365
|
-
const claims = JSON.parse(base64urlDecode(encodedPayload).toString('utf8'));
|
|
366
|
-
if (!claims || typeof claims !== 'object' || Array.isArray(claims)) {
|
|
367
|
-
throw new Error(`${operation}: response JWT claims must be an object`);
|
|
368
|
-
}
|
|
369
|
-
const now = Math.floor(Date.now() / 1000);
|
|
370
|
-
const requiredClaims = [
|
|
371
|
-
'iss', 'aud', 'site_id', 'migration_id', 'request_jti', 'operation',
|
|
372
|
-
'contract_version', 'snapshot_id', 'payload', 'iat', 'exp', 'jti',
|
|
373
|
-
];
|
|
374
|
-
const allowedClaims = new Set([...requiredClaims, 'wp_installation_id']);
|
|
375
|
-
const unknownClaims = Object.keys(claims).filter((name) => !allowedClaims.has(name));
|
|
376
|
-
if (unknownClaims.length > 0) {
|
|
377
|
-
throw new Error(`${operation}: response JWT contains unknown claim(s): ${unknownClaims.join(', ')}`);
|
|
378
|
-
}
|
|
379
|
-
const missingClaims = requiredClaims.filter((name) => !Object.prototype.hasOwnProperty.call(claims, name));
|
|
380
|
-
if (missingClaims.length > 0) {
|
|
381
|
-
throw new Error(`${operation}: response JWT is missing required claim(s): ${missingClaims.join(', ')}`);
|
|
382
|
-
}
|
|
383
|
-
const expectedClaims = {
|
|
384
|
-
iss: JWT_RESPONSE_ISSUER,
|
|
385
|
-
aud: JWT_RESPONSE_AUDIENCE,
|
|
386
|
-
site_id: siteId,
|
|
387
|
-
migration_id: migrationId,
|
|
388
|
-
request_jti: requestClaims.jti,
|
|
389
|
-
operation,
|
|
390
|
-
contract_version: JWT_CONTRACT_VERSION,
|
|
391
|
-
};
|
|
392
|
-
for (const [name, value] of Object.entries(expectedClaims)) {
|
|
393
|
-
if (claims[name] !== value) {
|
|
394
|
-
throw new Error(`${operation}: unexpected response claim ${name} (expected ${JSON.stringify(value)}, got ${JSON.stringify(claims[name])})`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
if (!Number.isInteger(claims.iat)) {
|
|
398
|
-
throw new Error(`${operation}: response JWT has no integer iat claim`);
|
|
399
|
-
}
|
|
400
|
-
if (!Number.isInteger(claims.exp) || claims.exp < now) {
|
|
401
|
-
throw new Error(`${operation}: response JWT is expired or has no integer exp claim`);
|
|
402
|
-
}
|
|
403
|
-
for (const name of ['snapshot_id', 'jti']) {
|
|
404
|
-
if (typeof claims[name] !== 'string' || claims[name] === '') {
|
|
405
|
-
throw new Error(`${operation}: response JWT claim ${name} must be a non-empty string`);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (Object.prototype.hasOwnProperty.call(claims, 'wp_installation_id')
|
|
409
|
-
&& (typeof claims.wp_installation_id !== 'string' || claims.wp_installation_id === '')) {
|
|
410
|
-
throw new Error(`${operation}: response JWT claim wp_installation_id must be a non-empty string when present`);
|
|
411
|
-
}
|
|
412
|
-
if (!claims.payload || typeof claims.payload !== 'object' || Array.isArray(claims.payload)) {
|
|
413
|
-
throw new Error(`${operation}: response JWT has no object payload claim`);
|
|
414
|
-
}
|
|
415
|
-
return claims.payload;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/** POSTs one signed operation and returns its verified payload. Refuses redirects: a 301/302 can rewrite the physical POST into a GET, which the plugin's own method binding then correctly rejects -- surfacing that as a redirect error is more actionable than the resulting 400. */
|
|
419
|
-
async function callSignedOperation({ siteBaseUrl, migrationKey, operation, route, siteId, migrationId, operationClaims, timeoutMs }) {
|
|
420
|
-
const base = siteBaseUrl.replace(/\/$/, '');
|
|
421
|
-
const { token, requestClaims } = signMigrationKeyRequest({ migrationKey, operation, route, siteId, migrationId, operationClaims });
|
|
422
|
-
const effectiveTimeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_SIGNED_TIMEOUT_MS;
|
|
423
|
-
const controller = new AbortController();
|
|
424
|
-
const timer = setTimeout(() => controller.abort(), effectiveTimeoutMs);
|
|
425
|
-
let response;
|
|
426
|
-
try {
|
|
427
|
-
response = await fetch(`${base}/wp-json${route}`, {
|
|
428
|
-
method: 'POST',
|
|
429
|
-
redirect: 'manual',
|
|
430
|
-
headers: { accept: 'application/json', 'content-type': 'application/jwt' },
|
|
431
|
-
body: token,
|
|
432
|
-
signal: controller.signal,
|
|
433
|
-
});
|
|
434
|
-
} finally {
|
|
435
|
-
clearTimeout(timer);
|
|
436
|
-
}
|
|
437
|
-
if (response.status >= 300 && response.status < 400) {
|
|
438
|
-
throw new Error(`${operation}: redirected to ${response.headers.get('location') || '(unknown location)'} -- use the site's canonical HTTPS URL`);
|
|
439
|
-
}
|
|
440
|
-
const responseText = await response.text();
|
|
441
|
-
if (!response.ok) {
|
|
442
|
-
const error = new Error(`${operation}: HTTP ${response.status}: ${responseText}`);
|
|
443
|
-
error.status = response.status;
|
|
444
|
-
throw error;
|
|
445
|
-
}
|
|
446
|
-
let responseJwt;
|
|
447
|
-
try {
|
|
448
|
-
responseJwt = JSON.parse(responseText);
|
|
449
|
-
} catch {
|
|
450
|
-
responseJwt = responseText;
|
|
451
|
-
}
|
|
452
|
-
if (typeof responseJwt !== 'string') {
|
|
453
|
-
throw new Error(`${operation}: response body is not a signed JWT string`);
|
|
454
|
-
}
|
|
455
|
-
return verifyMigrationKeyResponse({ migrationKey, jwt: responseJwt, requestClaims, operation, siteId, migrationId });
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* The per-read telemetry counts, derived HERE because this client is the single point every
|
|
460
|
-
* bridge read passes through -- deriving them at any call site would mean every future caller
|
|
461
|
-
* has to remember, and the one that forgets makes the run's total quietly wrong.
|
|
462
|
-
*
|
|
463
|
-
* Note what is NOT in here: key names. A run's own artifacts hold those. `redacted_values` is
|
|
464
|
-
* the field worth aggregating across runs, because a non-zero total means some site keeps a
|
|
465
|
-
* credential where nobody expected one.
|
|
466
|
-
*/
|
|
467
|
-
function sourceReadCounts(table, eavAccess, redactionMetadata) {
|
|
468
|
-
const counts = { redacted_values: redactionMetadata.redactedValueCount };
|
|
469
|
-
const pairs = eavAccessPairs(eavAccess);
|
|
470
|
-
if (pairs.length > 0) {
|
|
471
|
-
// MIXED-TIER RULE, stated rather than left to whichever pair happens to be first: a
|
|
472
|
-
// request counts as SCOPED if ANY pair was pinned. The counts answer "how many reads
|
|
473
|
-
// returned key/value DATA versus only key names", and one pinned pair means values came
|
|
474
|
-
// back — so classifying by the primary pair alone would file a read that returned values
|
|
475
|
-
// as a name-only discovery read. Reading the first pair's tier was the earlier behaviour
|
|
476
|
-
// and it misclassified exactly this case.
|
|
477
|
-
const anyPinned = pairs.some((p) => p.tier === 'key-scoped');
|
|
478
|
-
counts[anyPinned ? 'scoped_queries' : 'discovery_queries'] = 1;
|
|
479
|
-
counts[anyPinned ? 'scoped_tables' : 'discovery_tables'] = [table];
|
|
480
|
-
}
|
|
481
|
-
return counts;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Would this request violate the tier rule -- i.e. does it name a guarded value column without
|
|
486
|
-
* pinning that pair's key with `eq`/`in`?
|
|
487
|
-
*
|
|
488
|
-
* Decided from the request and the schema, NOT from the response, because the plugin answers a
|
|
489
|
-
* tier violation with the same 400 as a nonexistent column, a malformed request, or a bad
|
|
490
|
-
* cursor -- deliberately, so a refusal never discloses which column holds values. Treating
|
|
491
|
-
* every 400 on a key/value table as a tier refusal was the earlier behaviour and it
|
|
492
|
-
* misclassified the common case: a profile naming a column the site has since dropped is an
|
|
493
|
-
* outdated profile, and filing it as a security-control refusal makes the one number that
|
|
494
|
-
* should mean "something tried to reach values it had not named" mean nothing.
|
|
495
|
-
*
|
|
496
|
-
* Mirrors the plugin's own predicate, which is why it lives beside the client
|
|
497
|
-
* rather than at a call site.
|
|
498
|
-
*
|
|
499
|
-
* @param eavPair discover-structure's `eavPair` (with any `additionalPairs`), or null.
|
|
500
|
-
*/
|
|
501
|
-
function violatesTierRule(structureRequest, eavPair) {
|
|
502
|
-
if (!eavPair) return false;
|
|
503
|
-
const pairs = [{ keyColumn: eavPair.keyColumn, valueColumn: eavPair.valueColumn }, ...(eavPair.additionalPairs || [])];
|
|
504
|
-
const where = Array.isArray(structureRequest.where) ? structureRequest.where : [];
|
|
505
|
-
const names = new Set([
|
|
506
|
-
...(structureRequest.select || []).map((item) => item && item.column),
|
|
507
|
-
...where.map((condition) => condition && condition.column),
|
|
508
|
-
...(structureRequest.groupBy || []),
|
|
509
|
-
...(structureRequest.orderBy || []).map((item) => item && item.column),
|
|
510
|
-
]);
|
|
511
|
-
return pairs.some(({ keyColumn, valueColumn }) => {
|
|
512
|
-
if (!names.has(valueColumn)) return false;
|
|
513
|
-
const pinned = where.some((c) => c && c.column === keyColumn && (c.op === 'eq' || c.op === 'in'));
|
|
514
|
-
return !pinned;
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/** A tier-rule refusal, counted separately once `violatesTierRule` has established it is one. */
|
|
519
|
-
function recordTierRefusal(onSourceRead) {
|
|
520
|
-
if (onSourceRead) onSourceRead({ tier_refusals: 1 });
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Two caps, both the plugin's, both load-bearing here.
|
|
524
|
-
const MAX_PINNED_KEYS = 50; // per scoped request
|
|
525
|
-
const MAX_PAGE_LIMIT = 200; // per page, WMH2_Contract::MAX_LIMIT
|
|
526
|
-
const MAX_SELECT_COLUMNS = 50; // WMH2_Structure_Query::MAX_SELECT_COLUMNS
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Page the discovery tier to exhaustion and return EVERY distinct key.
|
|
530
|
-
*
|
|
531
|
-
* Exhaustion is the whole contract. A truncated key list reads as "these are all the keys" and
|
|
532
|
-
* sends a mapper down a wrong path, so this either returns a complete list or says plainly that
|
|
533
|
-
* it did not: `truncated` is true when `maxKeys` was hit, and a caller must treat that as an
|
|
534
|
-
* incomplete extraction rather than a slightly-short one. There is no silent cap.
|
|
535
|
-
*/
|
|
536
|
-
function validateStructureRequest(request) {
|
|
537
|
-
const identifier = (value) => typeof value === 'string' && /^[A-Za-z0-9_]+$/.test(value);
|
|
538
|
-
const keys = (value, allowed) => value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).every((key) => allowed.includes(key));
|
|
539
|
-
if (!keys(request, ['table', 'select', 'where', 'groupBy', 'orderBy']) || !identifier(request.table)) throw new Error('invalid structure request/table');
|
|
540
|
-
if (!Array.isArray(request.select) || !request.select.length || request.select.length > MAX_SELECT_COLUMNS) throw new Error('structure select must contain 1..50 columns');
|
|
541
|
-
for (const field of request.select) {
|
|
542
|
-
if (!keys(field, ['column', 'aggregate', 'as']) || !identifier(field.column)
|
|
543
|
-
|| (field.aggregate !== undefined && !['sum', 'count', 'avg', 'min', 'max'].includes(field.aggregate))
|
|
544
|
-
|| (field.as !== undefined && (typeof field.as !== 'string' || !field.as))) throw new Error('invalid structure select field');
|
|
545
|
-
}
|
|
546
|
-
if (request.where !== undefined && !Array.isArray(request.where)) throw new Error('structure where must be an array');
|
|
547
|
-
validateEavScope(request.where || []);
|
|
548
|
-
if (request.groupBy !== undefined && (!Array.isArray(request.groupBy) || request.groupBy.some((column) => !identifier(column)))) throw new Error('invalid structure groupBy');
|
|
549
|
-
if (request.groupBy?.length && request.select.some((field) => !field.aggregate && !request.groupBy.includes(field.column))) throw new Error('nonaggregate fields must be grouped');
|
|
550
|
-
if (!Array.isArray(request.orderBy) || !request.orderBy.length || request.orderBy.some((item) => !keys(item, ['column', 'direction']) || !identifier(item.column) || !['asc', 'desc'].includes(item.direction))) throw new Error('structure orderBy is required');
|
|
551
|
-
return request;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function validateEavScope(scope, reserved = 0) {
|
|
555
|
-
if (!Array.isArray(scope) || scope.length + reserved > MAX_WHERE_CONDITIONS) throw new Error('EAV scope exceeds where limit');
|
|
556
|
-
const scalar = (value, nullable) => (nullable && value === null) || ['string', 'boolean'].includes(typeof value) || (typeof value === 'number' && Number.isFinite(value));
|
|
557
|
-
for (const condition of scope) {
|
|
558
|
-
if (!condition || Object.keys(condition).some((key) => !['column', 'op', 'value'].includes(key)) || !Object.hasOwn(condition, 'value')) throw new Error('invalid EAV scope condition');
|
|
559
|
-
if (!condition || !/^[A-Za-z0-9_]+$/.test(condition.column)
|
|
560
|
-
|| !['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in'].includes(condition.op)) throw new Error('invalid EAV scope');
|
|
561
|
-
if (condition.op === 'in' && (!Array.isArray(condition.value) || condition.value.length < 1 || condition.value.length > MAX_IN_VALUES || condition.value.some((value) => !scalar(value, false)))) throw new Error('EAV scope IN must contain 1..100 scalar values');
|
|
562
|
-
if (condition.op !== 'in' && !scalar(condition.value, true)) throw new Error('EAV scope comparison must use a scalar');
|
|
563
|
-
}
|
|
564
|
-
return scope;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
async function discoverEavKeys({ httpClient, siteBaseUrl, auth, table, keyColumn, maxKeys = 10000, onSourceRead, scope = [] } = {}) {
|
|
568
|
-
// `maxKeys` bounds how many more PAGES we will fetch, not the exact list length: the check
|
|
569
|
-
// runs after exhaustion, so a sweep can return up to one page beyond the cap while still
|
|
570
|
-
// being complete. That is the right trade -- the alternative reported a table with exactly
|
|
571
|
-
// `maxKeys` keys as truncated and blocked a complete extraction.
|
|
572
|
-
const keys = [];
|
|
573
|
-
const keyCounts = {};
|
|
574
|
-
let cursor = null;
|
|
575
|
-
let pages = 0;
|
|
576
|
-
let truncated = false;
|
|
577
|
-
for (;;) {
|
|
578
|
-
// eslint-disable-next-line no-await-in-loop
|
|
579
|
-
const page = await queryStructure({
|
|
580
|
-
httpClient,
|
|
581
|
-
siteBaseUrl,
|
|
582
|
-
auth,
|
|
583
|
-
structureRequest: {
|
|
584
|
-
table,
|
|
585
|
-
// The COUNT is not decoration. It is what lets the plan state how many 200-row pages
|
|
586
|
-
// each batch will need, so "how expensive is this extraction" is answerable before
|
|
587
|
-
// running it rather than discovered while running it.
|
|
588
|
-
select: [{ column: keyColumn }, { column: keyColumn, aggregate: 'count', as: 'n' }],
|
|
589
|
-
...(scope.length ? { where: validateEavScope(scope) } : {}),
|
|
590
|
-
groupBy: [keyColumn],
|
|
591
|
-
orderBy: [{ column: keyColumn, direction: 'asc' }],
|
|
592
|
-
},
|
|
593
|
-
cursor,
|
|
594
|
-
limit: MAX_PAGE_LIMIT,
|
|
595
|
-
onSourceRead,
|
|
596
|
-
});
|
|
597
|
-
pages += 1;
|
|
598
|
-
for (const row of page.rows) {
|
|
599
|
-
if (typeof row[keyColumn] !== 'string') continue;
|
|
600
|
-
keys.push(row[keyColumn]);
|
|
601
|
-
const count = Number(row.n);
|
|
602
|
-
keyCounts[row[keyColumn]] = Number.isFinite(count) ? count : null;
|
|
603
|
-
}
|
|
604
|
-
cursor = page.pagingMetadata.cursors.next;
|
|
605
|
-
// Exhausted: complete, whatever the count. Checking the cap FIRST reported a table with
|
|
606
|
-
// exactly `maxKeys` keys as truncated and blocked a complete extraction from promotion.
|
|
607
|
-
if (!cursor) break;
|
|
608
|
-
if (keys.length >= maxKeys) { truncated = true; break; }
|
|
609
|
-
}
|
|
610
|
-
return { keys: [...new Set(keys)], keyCounts, pages, truncated };
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Turn discovered key lists into the exact requests a generated reader runs to extract VALUES.
|
|
615
|
-
*
|
|
616
|
-
* This exists because "discover the keys, then read them" is two steps, and only the first was
|
|
617
|
-
* ever executable from a persisted artifact. Discovery's `structure-request.json` for a
|
|
618
|
-
* key/value table deliberately omits the guarded value columns, so a reader that runs it
|
|
619
|
-
* verbatim extracts key NAMES and nothing else. The values are the whole point, so the second
|
|
620
|
-
* step has to be an artifact too, not a paragraph telling a generator to improvise one.
|
|
621
|
-
*
|
|
622
|
-
* Three things this has to get right, each of which produced a broken plan when it did not:
|
|
623
|
-
*
|
|
624
|
-
* 1. **Carry the record identity.** A `postmeta` row is `{meta_key, meta_value}` plus the
|
|
625
|
-
* `post_id` it belongs to. Without that column the extraction is a bag of values that cannot
|
|
626
|
-
* be attributed to an order, which is worse than no extraction because it looks like data.
|
|
627
|
-
* Every non-key, non-value column rides along; once a key is pinned they are all readable.
|
|
628
|
-
* 2. **One pair at a time.** Selecting a second pair's value column while pinning only the
|
|
629
|
-
* first pair's key is a request the plugin REFUSES (each value answers only to its own key).
|
|
630
|
-
* So each pair gets its own key list, its own batches and its own select.
|
|
631
|
-
* 3. **Order by something unique.** Pagination here is offset-based, so ordering by a
|
|
632
|
-
* non-unique column (the key column, which repeats once per record) lets rows shift between
|
|
633
|
-
* pages. Prefer the primary key, then a unique column; fall back to the key column only when
|
|
634
|
-
* the table offers nothing better, and say so in the plan.
|
|
635
|
-
*
|
|
636
|
-
* @param keysByPair { [keyColumn]: string[] } — one discovered key list per pair.
|
|
637
|
-
*/
|
|
638
|
-
function buildEavReadPlan({ table, eavPair, schemaColumns = [], keysByPair, countsByPair = null, batchSize = MAX_PINNED_KEYS, truncated = false, scope = [] }) {
|
|
639
|
-
if (!eavPair) throw new Error(`table ${table}: buildEavReadPlan needs the eavPair from discover-structure`);
|
|
640
|
-
if (!keysByPair || typeof keysByPair !== 'object') throw new Error(`table ${table}: keysByPair must be an object of keyColumn -> keys[]`);
|
|
641
|
-
if (batchSize < 1 || batchSize > MAX_PINNED_KEYS) {
|
|
642
|
-
throw new Error(`table ${table}: batchSize must be 1..${MAX_PINNED_KEYS} (the plugin's pinned-key cap)`);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
const pairs = [{ keyColumn: eavPair.keyColumn, valueColumn: eavPair.valueColumn }, ...(eavPair.additionalPairs || [])];
|
|
646
|
-
const keyColumns = new Set(pairs.map((pair) => pair.keyColumn));
|
|
647
|
-
const valueColumns = new Set(pairs.map((pair) => pair.valueColumn));
|
|
648
|
-
|
|
649
|
-
const named = schemaColumns.map((column) => (typeof column === 'string' ? { name: column, key: null } : column));
|
|
650
|
-
const allIdentity = named.filter((column) => !keyColumns.has(column.name) && !valueColumns.has(column.name));
|
|
651
|
-
|
|
652
|
-
// Offset pagination needs a stable, unique sort. Anything else lets a row move between pages.
|
|
653
|
-
// ⚠️ MySQL permits MANY NULLs in a unique index, so a NULLABLE unique column can tie and is
|
|
654
|
-
// not a stable sort. Only a primary key, or a unique column the schema reports as NOT NULL,
|
|
655
|
-
// earns orderByIsUnique. A column whose nullability is unknown does not get the benefit of
|
|
656
|
-
// the doubt -- claiming stable pagination and not having it is the failure being avoided.
|
|
657
|
-
const uniqueColumn = named.find((c) => c.key === 'primary')
|
|
658
|
-
|| named.find((c) => c.key === 'unique' && c.nullable === false);
|
|
659
|
-
const orderColumn = uniqueColumn ? uniqueColumn.name : null;
|
|
660
|
-
|
|
661
|
-
// ⚠️ Two slots are RESERVED for the pair's key and value columns before identity columns are
|
|
662
|
-
// taken. Appending them last and truncating the whole list to the select cap silently dropped
|
|
663
|
-
// the value column on a table with 49+ identity columns -- a plan that looks complete, runs
|
|
664
|
-
// without error, and extracts no values at all. When identity has to be trimmed, the columns
|
|
665
|
-
// most likely to identify a record survive: the sort column, then indexed ones, then the rest.
|
|
666
|
-
const identityBudget = MAX_SELECT_COLUMNS - 2;
|
|
667
|
-
const ranked = [...allIdentity].sort((a, b) => rankIdentity(a, orderColumn) - rankIdentity(b, orderColumn));
|
|
668
|
-
const identityColumns = ranked.slice(0, identityBudget).map((column) => column.name);
|
|
669
|
-
const droppedIdentityColumns = ranked.slice(identityBudget).map((column) => column.name);
|
|
670
|
-
|
|
671
|
-
let batchIndex = 0;
|
|
672
|
-
const planPairs = pairs.map((pair) => {
|
|
673
|
-
const counts = (countsByPair && countsByPair[pair.keyColumn]) || null;
|
|
674
|
-
const keys = [...new Set((keysByPair[pair.keyColumn] || []).filter((k) => typeof k === 'string' && k !== ''))].sort();
|
|
675
|
-
// identity + this pair's key + this pair's value, capped at the plugin's select limit. The
|
|
676
|
-
// key column rides so a value can be attributed to its key without relying on request order,
|
|
677
|
-
// which breaks the moment a key has no rows.
|
|
678
|
-
const select = [...identityColumns, pair.keyColumn, pair.valueColumn].map((column) => ({ column }));
|
|
679
|
-
const order = orderColumn || pair.keyColumn;
|
|
680
|
-
const batches = [];
|
|
681
|
-
for (let i = 0; i < keys.length; i += batchSize) {
|
|
682
|
-
const batchKeys = keys.slice(i, i + batchSize);
|
|
683
|
-
// Rows this batch will return, and therefore pages, when discovery supplied counts.
|
|
684
|
-
// null when it did not -- an unknown is reported as unknown, never as zero.
|
|
685
|
-
const expectedRows = counts ? batchKeys.reduce((sum, key) => sum + (Number.isFinite(counts[key]) ? counts[key] : 0), 0) : null;
|
|
686
|
-
batches.push({
|
|
687
|
-
batchIndex: batchIndex++,
|
|
688
|
-
keyColumn: pair.keyColumn,
|
|
689
|
-
keys: batchKeys,
|
|
690
|
-
expectedRows,
|
|
691
|
-
expectedPages: expectedRows === null ? null : Math.max(1, Math.ceil(expectedRows / MAX_PAGE_LIMIT)),
|
|
692
|
-
structureRequest: {
|
|
693
|
-
table,
|
|
694
|
-
select,
|
|
695
|
-
where: [...validateEavScope(scope, 1), { column: pair.keyColumn, op: 'in', value: batchKeys }],
|
|
696
|
-
orderBy: [{ column: order, direction: 'asc' }],
|
|
697
|
-
},
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
return { ...pair, distinctKeyCount: keys.length, batches };
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
const plan = {
|
|
704
|
-
planVersion: 1,
|
|
705
|
-
table,
|
|
706
|
-
identityColumns,
|
|
707
|
-
// Non-empty means the table is wider than the select cap allows, so extracted rows carry
|
|
708
|
-
// less than the full record context. Reported, never silent.
|
|
709
|
-
droppedIdentityColumns,
|
|
710
|
-
orderColumn: orderColumn || null,
|
|
711
|
-
// ⚠️ false means the sort column repeats, so offset pagination can shift rows between
|
|
712
|
-
// pages. A reader must treat that extraction as best-effort and say so.
|
|
713
|
-
orderByIsUnique: Boolean(orderColumn),
|
|
714
|
-
batchSize,
|
|
715
|
-
pageLimit: MAX_PAGE_LIMIT,
|
|
716
|
-
keysTruncated: truncated,
|
|
717
|
-
distinctKeyCount: planPairs.reduce((sum, pair) => sum + pair.distinctKeyCount, 0),
|
|
718
|
-
expectedBatches: batchIndex,
|
|
719
|
-
// The real request cost, not a batch count: a single batch whose keys hold thousands of
|
|
720
|
-
// rows needs many 200-row pages. null when discovery could not supply counts.
|
|
721
|
-
expectedScopedRequests: planPairs.every((pair) => pair.batches.every((b) => b.expectedPages !== null))
|
|
722
|
-
? planPairs.reduce((sum, pair) => sum + pair.batches.reduce((n, b) => n + b.expectedPages, 0), 0)
|
|
723
|
-
: null,
|
|
724
|
-
pairs: planPairs,
|
|
725
|
-
};
|
|
726
|
-
// Identity for the checkpoint. A resume must be able to tell that the plan it is resuming
|
|
727
|
-
// into is the plan it started: discovery can rewrite the plan between runs (the site gained
|
|
728
|
-
// or lost keys), and continuing a half-finished extraction against a different batch layout
|
|
729
|
-
// silently mixes two reads.
|
|
730
|
-
plan.planHash = eavReadPlanHash(plan);
|
|
731
|
-
return plan;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// Excluded from the hash. `planHash` because a hash cannot cover itself -- including it made the
|
|
735
|
-
// embedded value un-recomputable from the persisted file, so nothing could ever verify it.
|
|
736
|
-
// `acknowledgedDroppedColumns` because it is a human annotation ABOUT the plan, not part of the
|
|
737
|
-
// read the plan describes; acknowledging an omission must not invalidate an in-flight resume.
|
|
738
|
-
//
|
|
739
|
-
// ⚠️ That exclusion has a consequence, and it is handled in the MANIFEST rather than here: the
|
|
740
|
-
// acknowledgement flips reconciliation from failure to success, so if it lived nowhere else,
|
|
741
|
-
// withdrawing it later would leave the plan hash unchanged and an incomplete extraction would
|
|
742
|
-
// still look reusable. `eavExtractionIsReusable()` below is where that is caught -- the
|
|
743
|
-
// approval is provenance, and provenance belongs with the artifact it approved.
|
|
744
|
-
const PLAN_HASH_EXCLUDED_KEYS = new Set(['planHash', 'acknowledgedDroppedColumns']);
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* The canonical hash of a read plan's CONTENT.
|
|
748
|
-
*
|
|
749
|
-
* One function, used in three places that must agree: stamping the plan at build time,
|
|
750
|
-
* re-deriving it from the persisted file before promotion, and checking a checkpoint on resume.
|
|
751
|
-
* Three call sites hashing "the plan" three slightly different ways is how an integrity check
|
|
752
|
-
* ends up unable to detect anything -- which is exactly what happened when the stamp was taken
|
|
753
|
-
* before `planHash` existed and codegen was told to hash the finished file.
|
|
754
|
-
*
|
|
755
|
-
* Keys are sorted at every level so serialization order cannot change the answer.
|
|
756
|
-
*/
|
|
757
|
-
function eavReadPlanHash(plan) {
|
|
758
|
-
const canonical = (value) => {
|
|
759
|
-
if (Array.isArray(value)) return value.map(canonical);
|
|
760
|
-
if (value && typeof value === 'object') {
|
|
761
|
-
return Object.keys(value).sort()
|
|
762
|
-
.filter((key) => !PLAN_HASH_EXCLUDED_KEYS.has(key))
|
|
763
|
-
.reduce((out, key) => { out[key] = canonical(value[key]); return out; }, {});
|
|
764
|
-
}
|
|
765
|
-
return value;
|
|
766
|
-
};
|
|
767
|
-
return createHash('sha256').update(JSON.stringify(canonical(plan))).digest('hex').slice(0, 16);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
/**
|
|
771
|
-
* Does this plan's embedded hash actually match its contents?
|
|
772
|
-
*
|
|
773
|
-
* Without this, `planHash` proves only that a checkpoint and a plan carry the same string --
|
|
774
|
-
* so editing a batch's request while leaving the hash alone reconciled clean, which is the
|
|
775
|
-
* tamper case an integrity check exists to catch.
|
|
776
|
-
*/
|
|
777
|
-
function verifyEavReadPlanIntegrity(plan) {
|
|
778
|
-
const actual = eavReadPlanHash(plan);
|
|
779
|
-
if (plan.planHash !== actual) {
|
|
780
|
-
return `plan ${plan.table} declares planHash ${plan.planHash} but its contents hash to ${actual} -- the plan file was edited after discovery wrote it; regenerate it rather than extracting from it`;
|
|
781
|
-
}
|
|
782
|
-
return null;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
/** Sort key for identity columns when the select cap forces a trim: keep what identifies a row. */
|
|
786
|
-
function rankIdentity(column, orderColumn) {
|
|
787
|
-
if (column.name === orderColumn) return 0;
|
|
788
|
-
if (column.key === 'primary') return 1;
|
|
789
|
-
if (column.key === 'unique') return 2;
|
|
790
|
-
if (column.key === 'index') return 3;
|
|
791
|
-
return 4;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
/** Every batch across every pair, in execution order. */
|
|
795
|
-
function eavReadPlanBatches(plan) {
|
|
796
|
-
return (plan.pairs || []).flatMap((pair) => pair.batches);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
* Did a plan's execution actually complete? Returns the problems; empty means clean.
|
|
801
|
-
*
|
|
802
|
-
* Separate from execution on purpose. "It did not throw" is not completeness, and the earlier
|
|
803
|
-
* version of this accepted any non-negative row count, so a one-row extraction of a five-batch
|
|
804
|
-
* plan passed. Completeness here means three things, and the third is the one that catches a
|
|
805
|
-
* page loop that stopped early:
|
|
806
|
-
*
|
|
807
|
-
* - every batch the plan expects was run, identified by index, not merely counted;
|
|
808
|
-
* - each batch read as many rows as its own first page said it would (`snapshotTotal`);
|
|
809
|
-
* - the rows actually written equal the sum of those totals.
|
|
810
|
-
*
|
|
811
|
-
* @param observed { batches: [{batchIndex, rowsRead, snapshotTotal}], rowsWritten }
|
|
812
|
-
*/
|
|
813
|
-
function reconcileEavReadPlan(plan, observed) {
|
|
814
|
-
const problems = [];
|
|
815
|
-
const batches = (observed && Array.isArray(observed.batches)) ? observed.batches : null;
|
|
816
|
-
if (!batches) return ['reconcileEavReadPlan needs observed.batches: [{batchIndex, rowsRead, snapshotTotal}]'];
|
|
817
|
-
|
|
818
|
-
if (plan.keysTruncated) {
|
|
819
|
-
problems.push(`key discovery hit its cap, so the key list is INCOMPLETE -- this extraction cannot be treated as whole-table`);
|
|
820
|
-
}
|
|
821
|
-
if (Array.isArray(plan.droppedIdentityColumns) && plan.droppedIdentityColumns.length > 0
|
|
822
|
-
&& plan.acknowledgedDroppedColumns !== true) {
|
|
823
|
-
problems.push(
|
|
824
|
-
`this table is wider than the ${MAX_SELECT_COLUMNS}-column select cap, so ${plan.droppedIdentityColumns.length} column(s) are absent from every extracted row `
|
|
825
|
-
+ `(${plan.droppedIdentityColumns.join(', ')}). The extraction is INCOMPLETE per record. Either narrow the table's mapping and regenerate the plan with an explicit column choice, `
|
|
826
|
-
+ 'or set acknowledgedDroppedColumns: true on the plan to record that the omission was chosen deliberately',
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
if (plan.orderByIsUnique === false) {
|
|
830
|
-
problems.push(`this table has no primary or unique column, so batches were ordered by "${plan.orderColumn}", which repeats -- offset pagination may have shifted rows between pages and the extraction is best-effort`);
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
// ⚠️ Duplicates and strangers are rejected BEFORE any total is computed. Collapsing them into
|
|
834
|
-
// a Map while summing the original array let a resume that re-ran batch 0 report double its
|
|
835
|
-
// rows and reconcile clean -- duplicated output, promoted as complete.
|
|
836
|
-
const expectedIndices = new Set(eavReadPlanBatches(plan).map((b) => b.batchIndex));
|
|
837
|
-
// The plan must first be internally consistent: comparing a checkpoint against a plan whose
|
|
838
|
-
// own hash no longer describes it just confirms two strings match.
|
|
839
|
-
const tampered = verifyEavReadPlanIntegrity(plan);
|
|
840
|
-
if (tampered) return [tampered];
|
|
841
|
-
|
|
842
|
-
// ⚠️ REQUIRED, not optional. An optional integrity check is one the documented call path
|
|
843
|
-
// forgets to pass, and then two different plans reconcile against each other.
|
|
844
|
-
if (observed.planHash === undefined || observed.planHash === null) {
|
|
845
|
-
return ['reconcileEavReadPlan requires observed.planHash -- without it a checkpoint from a different plan reconciles clean'];
|
|
846
|
-
}
|
|
847
|
-
if (observed.planHash !== plan.planHash) {
|
|
848
|
-
return [`this checkpoint was written against plan ${observed.planHash}, but the plan on disk is ${plan.planHash} -- discovery rewrote it, so the extraction must restart rather than resume into a different batch layout`];
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
const seen = new Map();
|
|
852
|
-
for (const observation of batches) {
|
|
853
|
-
if (!expectedIndices.has(observation.batchIndex)) {
|
|
854
|
-
problems.push(`observed batch ${observation.batchIndex} is not in this plan -- the checkpoint belongs to a different plan`);
|
|
855
|
-
continue;
|
|
856
|
-
}
|
|
857
|
-
if (seen.has(observation.batchIndex)) {
|
|
858
|
-
problems.push(`batch ${observation.batchIndex} was recorded more than once -- a resume re-ran it and its rows are duplicated in the output`);
|
|
859
|
-
continue;
|
|
860
|
-
}
|
|
861
|
-
seen.set(observation.batchIndex, observation);
|
|
862
|
-
}
|
|
863
|
-
if (problems.some((p) => /recorded more than once|not in this plan/.test(p))) return problems;
|
|
864
|
-
|
|
865
|
-
for (const batch of eavReadPlanBatches(plan)) {
|
|
866
|
-
const ran = seen.get(batch.batchIndex);
|
|
867
|
-
if (!ran) {
|
|
868
|
-
problems.push(`batch ${batch.batchIndex} (${batch.keyColumn}, ${batch.keys.length} key(s)) never ran -- the extraction is partial`);
|
|
869
|
-
continue;
|
|
870
|
-
}
|
|
871
|
-
if (!Number.isInteger(ran.snapshotTotal) || !Number.isInteger(ran.rowsRead)) {
|
|
872
|
-
problems.push(`batch ${batch.batchIndex} reported a non-integer rowsRead/snapshotTotal`);
|
|
873
|
-
continue;
|
|
874
|
-
}
|
|
875
|
-
if (ran.rowsRead !== ran.snapshotTotal) {
|
|
876
|
-
problems.push(`batch ${batch.batchIndex} read ${ran.rowsRead} of ${ran.snapshotTotal} rows -- its page loop stopped early`);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
const expectedRows = [...seen.values()].reduce((sum, b) => sum + (Number.isInteger(b.rowsRead) ? b.rowsRead : 0), 0);
|
|
881
|
-
if (!Number.isInteger(observed.rowsWritten) || observed.rowsWritten < 0) {
|
|
882
|
-
problems.push('rowsWritten must be a non-negative integer');
|
|
883
|
-
} else if (observed.rowsWritten !== expectedRows) {
|
|
884
|
-
problems.push(`wrote ${observed.rowsWritten} rows but the batches read ${expectedRows} -- the output does not match what was fetched`);
|
|
885
|
-
}
|
|
886
|
-
return problems;
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
/**
|
|
890
|
-
* What the manifest must record about a completed EAV extraction, so a later run can decide
|
|
891
|
-
* whether the file on disk is still usable without re-reading the source.
|
|
892
|
-
*/
|
|
893
|
-
function eavExtractionManifest(plan, { planFile, completedBatches, rowsWritten, extractedAt, ndjsonDigest }) {
|
|
894
|
-
if (typeof planFile !== 'string' || planFile === '') {
|
|
895
|
-
throw new Error(`table ${plan.table}: eavExtractionManifest needs the planFile it was extracted from`);
|
|
896
|
-
}
|
|
897
|
-
if (typeof ndjsonDigest !== 'string' || ndjsonDigest === '') {
|
|
898
|
-
throw new Error(`table ${plan.table}: eavExtractionManifest needs the ndjsonDigest of the promoted file -- a manifest not bound to its data describes nothing`);
|
|
899
|
-
}
|
|
900
|
-
return {
|
|
901
|
-
kind: 'eav-read-plan',
|
|
902
|
-
// Which plan produced this file. The hash proves the CONTENT matches; this says which
|
|
903
|
-
// artifact on disk to go and re-hash, which a later run needs before it can compare.
|
|
904
|
-
planFile,
|
|
905
|
-
planHash: plan.planHash,
|
|
906
|
-
completedBatches,
|
|
907
|
-
rowsWritten,
|
|
908
|
-
// These three qualify what the file CONTAINS, so they travel with it. A consumer must be
|
|
909
|
-
// able to learn "rows in this file are missing columns" from the artifact rather than from
|
|
910
|
-
// whoever happened to run the extraction.
|
|
911
|
-
keysTruncated: Boolean(plan.keysTruncated),
|
|
912
|
-
droppedIdentityColumns: plan.droppedIdentityColumns || [],
|
|
913
|
-
orderByIsUnique: plan.orderByIsUnique !== false,
|
|
914
|
-
// The APPROVAL that allowed an incomplete extraction to promote. Recorded because it is
|
|
915
|
-
// deliberately outside planHash: without it here, withdrawing the acknowledgement would
|
|
916
|
-
// change nothing a later run could see.
|
|
917
|
-
acknowledgedDroppedColumns: plan.acknowledgedDroppedColumns === true,
|
|
918
|
-
// Binds the manifest to the ACTUAL FILE. Everything else here describes the read that was
|
|
919
|
-
// supposed to happen; this is the only field that says what landed on disk. Without it a
|
|
920
|
-
// truncated, replaced or half-written NDJSON sitting beside a perfectly valid manifest is
|
|
921
|
-
// accepted, and the manifest's authority is exactly what makes that dangerous.
|
|
922
|
-
ndjsonDigest,
|
|
923
|
-
extractedAt,
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
/**
|
|
928
|
-
* The digest recorded in a manifest and recomputed on reuse. One function so the two can never
|
|
929
|
-
* be computed differently -- the same reasoning as eavReadPlanHash().
|
|
930
|
-
*/
|
|
931
|
-
function eavExtractionFileDigest(contents) {
|
|
932
|
-
return createHash('sha256').update(contents).digest('hex');
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
const EAV_MANIFEST_REQUIRED_FIELDS = [
|
|
936
|
-
'planFile', 'planHash', 'completedBatches', 'rowsWritten', 'keysTruncated',
|
|
937
|
-
'droppedIdentityColumns', 'orderByIsUnique', 'acknowledgedDroppedColumns', 'ndjsonDigest',
|
|
938
|
-
'extractedAt',
|
|
939
|
-
];
|
|
940
|
-
|
|
941
|
-
/**
|
|
942
|
-
* Can an existing extraction be reused, or must it be re-read? Returns the reasons it cannot;
|
|
943
|
-
* empty means reuse is safe.
|
|
944
|
-
*
|
|
945
|
-
* **Re-verifies the recorded extraction rather than trusting that it happened.** An earlier
|
|
946
|
-
* version checked only `kind`, `planHash` and the acknowledgement, so a manifest carrying
|
|
947
|
-
* nothing but those two fields — or one recording zero completed batches, or a `rowsWritten`
|
|
948
|
-
* that matched nothing — was accepted, and a run would skip re-extraction on the strength of a
|
|
949
|
-
* manifest that described no extraction at all. A manifest is an assertion about a file; the
|
|
950
|
-
* point of reading it is to check the assertion, not to accept it.
|
|
951
|
-
*
|
|
952
|
-
* So the checks are, in order of what makes later ones meaningless:
|
|
953
|
-
* 1. shape — every field present and the right type;
|
|
954
|
-
* 2. identity — the plan hash still matches, and the plan matches its own hash;
|
|
955
|
-
* 3. completeness — the batches the manifest records must RECONCILE against the plan, the
|
|
956
|
-
* same reconciliation the extraction itself had to pass;
|
|
957
|
-
* 4. qualifiers — what the manifest says about the file must still be true of the plan;
|
|
958
|
-
* 5. approval — the dropped-column acknowledgement, which is outside the hash.
|
|
959
|
-
*/
|
|
960
|
-
function eavExtractionIsReusable(plan, manifest, observed) {
|
|
961
|
-
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest) || manifest.kind !== 'eav-read-plan') {
|
|
962
|
-
return ['no EAV extraction manifest -- treat the existing file as stale'];
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
// ⚠️ REQUIRED, and required for the same reason planHash is: a check the caller may omit is a
|
|
966
|
-
// check the caller omits. `observed` is what is actually on disk right now -- the sidecar path
|
|
967
|
-
// the caller itself resolved, and the digest and line count of the final file it is deciding
|
|
968
|
-
// whether to reuse. Everything else here reasons about a read that was SUPPOSED to happen.
|
|
969
|
-
if (!observed || typeof observed !== 'object'
|
|
970
|
-
|| typeof observed.planFile !== 'string' || observed.planFile === ''
|
|
971
|
-
|| typeof observed.ndjsonDigest !== 'string' || observed.ndjsonDigest === ''
|
|
972
|
-
|| !Number.isInteger(observed.ndjsonRowCount) || observed.ndjsonRowCount < 0) {
|
|
973
|
-
return ['eavExtractionIsReusable requires observed: { planFile, ndjsonDigest, ndjsonRowCount } read from disk -- without them the manifest is only checked against itself'];
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// 1. Shape. Reported all at once, and nothing else runs: reasoning about completeness from a
|
|
977
|
-
// manifest that is missing fields produces confident nonsense.
|
|
978
|
-
const shape = [];
|
|
979
|
-
for (const field of EAV_MANIFEST_REQUIRED_FIELDS) {
|
|
980
|
-
if (manifest[field] === undefined || manifest[field] === null) shape.push(`manifest is missing ${field}`);
|
|
981
|
-
}
|
|
982
|
-
if (shape.length === 0) {
|
|
983
|
-
if (typeof manifest.planFile !== 'string' || manifest.planFile === '') shape.push('manifest planFile must be a non-empty string');
|
|
984
|
-
if (typeof manifest.ndjsonDigest !== 'string' || manifest.ndjsonDigest === '') shape.push('manifest ndjsonDigest must be a non-empty string');
|
|
985
|
-
if (!Array.isArray(manifest.completedBatches)) shape.push('manifest completedBatches must be an array');
|
|
986
|
-
if (!Number.isInteger(manifest.rowsWritten) || manifest.rowsWritten < 0) shape.push('manifest rowsWritten must be a non-negative integer');
|
|
987
|
-
if (!Array.isArray(manifest.droppedIdentityColumns)) shape.push('manifest droppedIdentityColumns must be an array');
|
|
988
|
-
if (typeof manifest.keysTruncated !== 'boolean') shape.push('manifest keysTruncated must be a boolean');
|
|
989
|
-
if (typeof manifest.orderByIsUnique !== 'boolean') shape.push('manifest orderByIsUnique must be a boolean');
|
|
990
|
-
if (typeof manifest.acknowledgedDroppedColumns !== 'boolean') shape.push('manifest acknowledgedDroppedColumns must be a boolean');
|
|
991
|
-
}
|
|
992
|
-
if (shape.length > 0) return shape;
|
|
993
|
-
|
|
994
|
-
// 2. Identity.
|
|
995
|
-
const integrity = verifyEavReadPlanIntegrity(plan);
|
|
996
|
-
if (integrity) return [integrity];
|
|
997
|
-
if (manifest.planHash !== plan.planHash) {
|
|
998
|
-
return [`the plan changed since this file was written (${manifest.planHash} -> ${plan.planHash}) -- re-extract`];
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
const reasons = [];
|
|
1002
|
-
|
|
1003
|
-
// 2b. The manifest must describe THESE artifacts, not merely valid-looking ones.
|
|
1004
|
-
//
|
|
1005
|
-
// ⚠️ `manifest.planFile` is compared against the path the CALLER resolved; it is never opened.
|
|
1006
|
-
// A manifest is untrusted input -- it can name `../../something-else.json` -- so treating its
|
|
1007
|
-
// path as something to follow would let the file choose what it is validated against.
|
|
1008
|
-
if (manifest.planFile !== observed.planFile) {
|
|
1009
|
-
reasons.push(`manifest names plan file ${manifest.planFile} but this entity's plan is ${observed.planFile} -- the manifest belongs to a different extraction`);
|
|
1010
|
-
}
|
|
1011
|
-
if (manifest.ndjsonDigest !== observed.ndjsonDigest) {
|
|
1012
|
-
reasons.push('the extracted file does not match the digest its manifest recorded -- it was truncated, replaced or half-written since promotion; re-extract');
|
|
1013
|
-
}
|
|
1014
|
-
if (manifest.rowsWritten !== observed.ndjsonRowCount) {
|
|
1015
|
-
reasons.push(`the extracted file holds ${observed.ndjsonRowCount} row(s) but its manifest recorded ${manifest.rowsWritten} -- re-extract`);
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// 3. Completeness -- the same bar the extraction had to clear, applied to what it recorded.
|
|
1019
|
-
reasons.push(...reconcileEavReadPlan(plan, {
|
|
1020
|
-
planHash: manifest.planHash,
|
|
1021
|
-
batches: manifest.completedBatches,
|
|
1022
|
-
rowsWritten: manifest.rowsWritten,
|
|
1023
|
-
}).map((problem) => `the recorded extraction does not reconcile: ${problem}`));
|
|
1024
|
-
|
|
1025
|
-
// 4. Qualifiers. If the manifest says the file was paged on a tying sort key and the plan now
|
|
1026
|
-
// says otherwise, one of them is describing a different read.
|
|
1027
|
-
if (manifest.keysTruncated !== Boolean(plan.keysTruncated)) {
|
|
1028
|
-
reasons.push(`manifest records keysTruncated: ${manifest.keysTruncated}, the plan says ${Boolean(plan.keysTruncated)} -- they describe different reads`);
|
|
1029
|
-
}
|
|
1030
|
-
if (manifest.orderByIsUnique !== (plan.orderByIsUnique !== false)) {
|
|
1031
|
-
reasons.push(`manifest records orderByIsUnique: ${manifest.orderByIsUnique}, the plan says ${plan.orderByIsUnique !== false} -- they describe different reads`);
|
|
1032
|
-
}
|
|
1033
|
-
const dropped = plan.droppedIdentityColumns || [];
|
|
1034
|
-
if (JSON.stringify([...manifest.droppedIdentityColumns].sort()) !== JSON.stringify([...dropped].sort())) {
|
|
1035
|
-
reasons.push('manifest and plan disagree about which identity columns were dropped -- re-extract');
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
// 5. Approval, which is deliberately outside the hash and so cannot be caught by step 2.
|
|
1039
|
-
// Both sides are checked. The MANIFEST must record that this file's promotion was approved
|
|
1040
|
-
// -- a file with missing columns and no recorded approval was never sanctioned, whatever the
|
|
1041
|
-
// plan says today -- and the PLAN must still carry the approval, or it has been withdrawn.
|
|
1042
|
-
if (dropped.length > 0 && manifest.acknowledgedDroppedColumns !== true) {
|
|
1043
|
-
reasons.push(`this file has ${dropped.length} column(s) missing from every row and its manifest does not record an approval for that -- it was promoted unapproved, so re-extract rather than reuse it`);
|
|
1044
|
-
}
|
|
1045
|
-
if (dropped.length > 0 && plan.acknowledgedDroppedColumns !== true) {
|
|
1046
|
-
reasons.push(`this file was promoted with ${dropped.length} column(s) missing from every row, and that omission is no longer acknowledged -- re-extract or re-acknowledge`);
|
|
1047
|
-
}
|
|
1048
|
-
if (manifest.acknowledgedDroppedColumns === true && dropped.length === 0) {
|
|
1049
|
-
reasons.push('the file was promoted under a dropped-column acknowledgement that the current plan no longer needs -- re-extract so the rows carry the full record');
|
|
1050
|
-
}
|
|
1051
|
-
return reasons;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
module.exports = {
|
|
1055
|
-
SHARED_CORE_TABLES_REQUIRING_WHERE,
|
|
1056
|
-
validateStructureRequest,
|
|
1057
|
-
MAX_IN_VALUES,
|
|
1058
|
-
MAX_WHERE_CONDITIONS,
|
|
1059
|
-
MAX_PINNED_KEYS,
|
|
1060
|
-
MAX_PAGE_LIMIT,
|
|
1061
|
-
MAX_SELECT_COLUMNS,
|
|
1062
|
-
discoverEavKeys,
|
|
1063
|
-
validateEavScope,
|
|
1064
|
-
buildEavReadPlan,
|
|
1065
|
-
eavReadPlanHash,
|
|
1066
|
-
verifyEavReadPlanIntegrity,
|
|
1067
|
-
eavExtractionManifest,
|
|
1068
|
-
eavExtractionFileDigest,
|
|
1069
|
-
eavExtractionIsReusable,
|
|
1070
|
-
EAV_MANIFEST_REQUIRED_FIELDS,
|
|
1071
|
-
eavReadPlanBatches,
|
|
1072
|
-
reconcileEavReadPlan, HANDLER_VERSION, discoverStructure, queryStructure, guardedValueColumns, eavAccessPairs, sourceReadCounts, recordTierRefusal, violatesTierRule,
|
|
1073
|
-
signMigrationKeyRequest, verifyMigrationKeyResponse,
|
|
1074
|
-
};
|