| name | rp-target-wix |
| description | Wix target adapter with verified write primitives, shared execution runtimes, and contract tests. Use when vendoring Wix writers, validating API shapes, or Wix provisioning mechanics. |
rp-target-wix
Wix target adapter. Owns the Wix-side write surface and shared execution runtime that every migration shares —
verified once here so the platform-agnostic stages and per-project codegen never
re-derive (and re-break) it. This is the symmetric counterpart to rp-source-wordpress:
that adapter owns reading a source platform; this one owns writing to Wix.
When this skill is used
Not a stage in the flow — a reference + shared library consulted by:
rp-import-codegen vendors lib/wix-writers.js into the project (like the
wp-http transport) plus the shared write/setup runtime modules, and generates thin
project-specific write specs/transforms that call these primitives/runtime APIs.
Codegen does not re-emit Wix API plumbing.
rp-setup-discovery / rp-execute-setup consult the "Verified endpoints" and
"Provisioning" notes for app-install / Wix-Data / collection mechanics.
Because the Wix surface is identical across source platforms, adding a new source
(rp-source-shopify, …) requires no change here.
Shared runtime boundary
This adapter owns the shared Wix layer written once for all migrations.
It should own:
- verified request builders and endpoint primitives
- shared Wix client/auth handling
- shared retry and throttling policy
- generic write execution runtime
- generic setup execution/runtime behavior
- checkpoint helpers
- local crosswalk helpers (
state/crosswalk/crosswalk.ndjson authority, append-only
attempt journal, optional CMS mirror seed/upload adapters)
- URL preservation helpers (
state/url-preservation/ base paths, ledger, planned
redirects, and unresolved route rows)
- NDJSON record-stream I/O (
lib/ndjson.js): streaming read/write, batching by count and by
cost, cursoring, and append-only producers. Migration data files are record streams, so the
scan/batch/cursor primitives live here once instead of being re-derived by codegen.
- audit log emission
- compact execution report generation
- contract tests for stable request/runtime behavior
Per-migration code generation should own only:
- field maps
- transforms
- write specs
- setup-plan renderings
- entity ordering
- dependency wiring — populate
WriteSpec.dependencyRefs from each in-scope entity's
domain-knowledge dependsOn field (spec 0041), not re-derived per project
Write contract (the verified Wix surface)
lib/wix-writers.js exposes pure request builders (build*Request) + executors. Shapes
marked VERIFIED were validated by a real call against a live site, not just read from
docs. Shapes marked UNVERIFIED are docs-schema/MCP-derived bootstrap primitives that
must be surfaced in execution plans until a live contract call promotes them. It also
exports sendDirectRest and notifyMissingWriter for generated native REST paths when
Wix has a native entity but this adapter does not yet ship a dedicated primitive.
This resource should also expose shared execution-runtime modules above those primitives,
so generated migrations use one common path for retries, throttling, checkpointing,
reporting, and audit logging.
Read/return contract for query* executors. Every query* executor
(queryStoresProducts, queryStoresCategories, queryContacts, queryCoupons,
queryOrders) unwraps the response to the entity array and returns one page, discarding
pagingMetadata. Two failure modes follow, both of which have shipped:
- The return value IS the array. Reading
.products / .categories off it a second time
gives undefined → []. A dedupe index or existing-entity safety net then comes back
empty without erroring, which is indistinguishable from a fresh site — so the import
duplicates what is already there, and a setup verification reports 0 categories on a site
with 25.
- These executors cannot be cursor-paged, because the cursor is in the metadata they
discarded. Use a
queryAll* primitive where one exists — queryAllStoresCategories,
queryAllStoresProducts, queryAllDataItems — otherwise send
wix.send(build<X>Request(body)) and read pagingMetadata.cursors.next off the raw
response.
Every sweep used for dedupe must throw on a partial result rather than return it. "Empty
net" and "empty store" look identical to the caller, and the caller assumes the second.
Domain entity suitability knowledge
Target entity suitability lives in domains/, not in a large prose table in this skill.
Use the deterministic reader as the normal access path:
node scripts/domain-knowledge.js list-domains
node scripts/domain-knowledge.js list-entities --domain stores
node scripts/domain-knowledge.js read-entity --ref stores/product
node scripts/domain-knowledge.js resolve-source --source-system woocommerce --source-entity product
node scripts/domain-knowledge.js resolve-source --route /wc/v3/products
node scripts/domain-knowledge.js list-flagged --flag IMPORT_UNRELIABLE
node scripts/domain-knowledge.js summarize-entities --refs stores/product,ecom/order
domains/index.json is generated from domain/entity files and checked in. After editing
domain knowledge, run:
node scripts/domain-knowledge-validate.js --write-index
Authoring or editing any entity file also requires the spec 0041 dependency check, both
directions: (1) what does this entity need to already exist and be crosswalked — record it in
dependsOn: string[] (a domain/entity ref array; empty is a real, reviewed answer, not the same
as the field being absent); (2) does this entity change what an already-written entity's
dependsOn should say (e.g. authoring gift-cards/gift-card should have prompted revisiting
ecom/order.json — it hadn't, until spec 0042 caught it by hand). domain-knowledge-validate.js
checks dependsOn refs resolve and the whole graph stays acyclic whenever the field is present, but
it is not yet required on every entity — node scripts/domain-knowledge-validate.js --list-missing-deps prints the live backlog of entities that have never had it authored at all;
that command is the source of truth, not a hand-maintained list.
writerId values in entity files must match exported functions from lib/wix-writers.js;
use null for direct REST plans, setup/manual work, or unsupported native gaps.
Entity files may also expose fieldContracts[] for verified nested write paths. These
are machine-readable contracts for codegen and dry-run validators, not replacements for
the human notes below. For Stores products, domains/stores/entities/product.json
records the verified Catalog V3 create contract for product.subscriptionDetails,
including required nested paths, allowed recurrence values, read-back behavior, and the
live-validated subscriptions[].description <= 60 constraint. Generated transforms
should consume that metadata through the vendored wix-writers.js exports instead of
copying limits into project-local code.
Live verification helpers must not edit domains/ directly. When a probe changes a
target write assumption, emit a proposal artifact and let the orchestrator promote it
deliberately:
node skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js stores subscription-create \
--artifact migrations/<project>/setup/stores-subscription-verification.json \
--proposal-artifact migrations/<project>/setup/contract-ledger-proposal.json
Use lib/contract-ledger.js to validate the handoff: a passed verification without a
proposal is incomplete, and a proposal that is neither promoted into fieldContracts[]
nor explicitly deferred is stale product knowledge. Codegen should consume
fieldContracts[] through scripts/domain-knowledge.js summarize-entities, which
includes promoted contracts.
| Capability | Endpoint | Notes / traps |
|---|
| HTML → rich content | POST /ricos/v1/ricos-document/convert/to-ricos | VERIFIED. options.plugins enum is UPPERCASE — the docs example shows lowercase and 400s. |
| Import media from URL | POST /site-media/v1/files/import | VERIFIED. Async: response is PENDING; poll GET /site-media/v1/files/{id} for READY before referencing. |
| Blog category | POST /blog/v3/categories | VERIFIED. body { category: { label, slug, description } }. |
| Blog tag | POST /blog/v3/tags | VERIFIED. Body is top-level { label, language } — NOT { tag: { label, slug } }; slug is derived. |
| Blog post | POST /blog/v3/draft-posts → …/{id}/publish | VERIFIED. memberId required (3rd-party). VERIFIED (2026-08-02): the site owner's auto-created user-member satisfies it — it was already present on our API-provisioned test site with zero Members-area interaction (single-site observation), so attribute-to-owner needs no member provisioning. Resolve it via listMembers + loginEmail match — never derive it from the account/user GUID (the observed id equality is n=1 on a solo account, undocumented). Also VERIFIED (2026-08-02): author is re-assignable after publish — PATCH /blog/v3/draft-posts/{id} { draftPost: { memberId } } then republish updates the published post's author (post id == draft id; republish fires non-suppressed events — do author-upgrade passes inside the notification-mute window). Visible custom cover media requires both heroImage.id and media: { displayed:true, custom:true, wixMedia:{ image:{ id }}}. heroImage.id alone leaves the cover hidden. |
| Blog posts (bulk) | POST /blog/v3/bulk/draft-posts/create | UNVERIFIED bootstrap (public docs + wix/skills wix-manage recipe, 2026-07-21). Max ; chunks larger inputs. Prefer over per-post creates for N ≥ 2 (single create runs ~25–30s/call per the recipe). Publish is still per-post — a bulk flag is unverified. Surface in the execution plan until the live contract call promotes it. |
Safe-mode contact replacement
lib/wix-writers.js exports the shared safe-mode runtime:
createSafeModeConfig(env)
mockEmailForEntity(entityType, entityId)
sanitizeContactFieldsForSafeMode(value, options)
sanitizeWixRequestBody(body, options)
Generated imports must pass safeModeOptions to writer builders/executors when
SAFE_MODE is enabled. Request builders sanitize a copied request body before returning
it and throw SafeModeBlockedError before any send when suspicious non-replaced email
values remain.
Safe-mode replacement paths use deterministic request-body paths with object fields,
array wildcards, and Wix wrapper arrays, for example:
contact.email.email
contact.additionalEmails[].email
contact.phone.phone
order.billingInfo.email
dataItem.data.submissions[].email
Replacement only touches primitive leaf values — a generic path that lands on an object
(such as the GA Contacts V5 email object) is skipped rather than clobbered, and any raw
email left inside a skipped object still trips the suspicious-value block.
Target domain entity files may define safeModeContactFields[] with { kind, targetPath, source, notes } entries. Mapper/codegen must merge those target hints with source-side
contact evidence and user mappings, then pass resolved request-body paths to the shared
writers.
Stores subscription validation
lib/wix-writers.js exports the Stores subscription contract and deterministic helpers:
STORES_SUBSCRIPTION_CONTRACT
normalizeStoresProductSubscriptions
validateStoresProductSubscriptionDetails
Generated import code should call the normalizer from product transforms and the
validator during dry-run and immediately before live create. Validation failures are
record-level preflight failures/deferred outcomes; do not send known-invalid
subscription payloads to Wix to discover field errors live.
Catalog V3 subscriptions are a native Stores target, not an automatic CMS fallback or
semantic loss. Product create with subscriptionDetails.allowOneTimePurchases and
subscriptions[] entries containing title, description, frequency, interval, and
autoRenewal was verified live on 2026-07-26 in the nopong migration; Wix returned
server-assigned subscription option ids. Mapping/codegen should emit native recurring product
creates when the source cadence is known. General subscription patching remains unverified.
Stores live verification helpers
Use the shared Stores verification CLI for live target checks that would otherwise become
migration-local snippets:
node skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js stores subscription-create \
--artifact migrations/<project>/setup/stores-subscription-verification.json \
--proposal-artifact migrations/<project>/setup/contract-ledger-proposal.json
node skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js stores product-count \
--artifact migrations/<project>/execution/stores-product-count-verification.json
node skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js stores product-by-source-marker \
--marker-path customFields.sourceId --marker-value <source-id> \
--artifact migrations/<project>/execution/stores-product-marker-verification.json
node skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js stores delete-probe \
--product-id <probe-product-id> \
--artifact migrations/<project>/setup/stores-probe-cleanup.json
The CLI reads WIX_AUTH_TOKEN and WIX_SITE_ID from the environment or
config/wix.env under the current project root, creates the shared Wix client, and sends
requests through wix.send(...). It writes machine-readable artifacts with the target
site id, endpoint, method, verified nested paths, discovered constraints, probe id,
cleanup status, warnings, recovery instructions, and timestamp. Failed probe cleanup is
not hidden: the artifact must remain with an explicit warning and a delete-probe
recovery command.
Routed URL preservation behavior
URL preservation is a mapping and execution-state concern. This adapter owns only the
Wix-side facts that generated code may rely on when deciding whether a final target URL is
known.
Current import-only phase rules:
- Do not configure Wix redirects or site routing.
- Do not guess a destination relative URL from a slug when Wix route behavior has not been
verified for that entity and delivery mode.
- When the destination base path or final URL is unknown, write URL ledger rows with
urlStatus: "pending_target_route" or target_url_missing and record unresolved rows
for the future website-builder phase.
- When a Wix create/update response or a safe declared lookup returns a final slug or
relative path, generated code may record it as
actualTargetSlug or
actualTargetRelativeUrl and write a redirect plan if it differs from the source URL.
Known routed entity status:
| Entity | URL behavior contract |
|---|
| Blog posts | Create/publish is verified for content writes. Final public route exposure after publish is not verified here; generated code must treat the target route as pending_target_route unless a safe lookup in the generated write spec proves the final relative URL. |
| Blog categories | Category create is verified. Final public category route exposure is not verified here; default to pending_target_route. |
| Stores products | Product create and slug-bearing product operations are verified, but arbitrary route/base-path configuration is not part of this phase. Generated code may record actual target slugs only when returned or safely looked up; target relative URLs remain pending_target_route unless the route pattern is explicitly verified in the project. |
| Stores categories/collections | Category create is verified. Final collection/category route exposure is not verified here; default to pending_target_route. |
| CMS items with dynamic pages | CMS item writes are verified. Dynamic page route patterns are site-builder configuration, not native item import output; default to pending_target_route unless website-builder artifacts define the route. |
Shared execution runtime contract
In addition to endpoint primitives, this adapter should define the reusable runtime API
that generated migrations call.
Conceptual contracts:
executeWriteSpec({
spec,
items,
client,
dryRun,
runContext,
}): Promise<WriteExecutionReport>
executeSetupPlan({
plan,
client,
dryRun,
runContext,
}): Promise<SetupExecutionReport>
The exact filenames are implementation details, but the shared runtime should be the
single place that owns generic Wix execution mechanics.
WriteSpec contract
Generated migrations should pass a declarative WriteSpec into the shared runtime.
Each spec should define, as applicable:
entity
mode: create | update | upsert | bulk_create | bulk_upsert
create
update
lookup
bulk
batchSize
concurrency
retryPolicy
throttlePolicy
auditKeys
crosswalkAuthority: "local"
cmsMirror
crosswalkStrategy
reconciliationStrategy
dependencyRefs
verificationLevel: verified | unverified
This resource should document how each field is interpreted by the runtime.
WriteExecutionReport contract
The shared runtime should return a compact, token-aware report suitable for the agent.
It should include:
- run ID
- entity
- counts: input, attempted, created, updated, skipped, failed, retried, throttled
- grouped error buckets
- references to fuller on-disk logs
The runtime should prefer grouped summaries over raw per-record detail.
These per-entity or per-phase reports are intermediate runtime outputs. They are inputs to
the final completion artifact, not replacements for it.
Completion artifact contract
The shared runtime should support deterministic emission of a final machine-readable
completion artifact for the whole run.
Preferred output:
execution/completion-report.json
This artifact should be the authoritative source for:
- final run status
- final imported/updated/skipped/failed counts
- separate dry-run counts when
dryRun: true: requests built, Wix calls skipped,
would-create/update/delete, would require live lookup, and local validation failures
- grouped skipped/deferred outcomes
- grouped failure outcomes
- dashboard/editor/preview destinations
- artifact references for audit/error/execution logs
The shared runtime should also support deterministic rendering inputs for:
execution/review/completion-summary.md
The rendered summary should be derived from completion-report.json, not independently
invented by the agent.
Audit log contract
The shared runtime should emit append-only machine-readable audit events.
Preferred format: NDJSON.
Each event should capture, at minimum:
- timestamp
- run ID
- phase:
setup | import
- entity
- operation
- source ID when applicable
- target ID when known
- endpoint or primitive used
- attempt number
- result status
- HTTP status or platform error code when applicable
- latency
The run ID is required because audit files are append-only across dry-runs, retries, and
recovery passes. Completion reports should be generated from runtime counters for the
current run and may use audit aggregation only when filtered by run ID. The shared
lib/audit-summary.js helper enforces this filter for fallback/verification summaries.
Dry-run runtime contract
The shared Wix runtime exports createDryRunConfig, normalizeDryRunValue,
createWixClient, and createWixSetupExecutor.
Dry-run is enabled by DRY_RUN=true or by the generated entrypoint's --dry-run flag.
It is disabled by default. true, 1, yes, and on enable it; false, 0, no,
and off disable it.
When createWixClient({ dryRun: true }) is used:
authToken and siteId may be blank unless the generated project requires the site ID
for a local artifact namespace;
send() must not call fetch, SDK, MCP, or CLI transports;
send() must capture the would-send request under
state/attempts/wix-request-captures.ndjson when projectDir or requestCapturePath
is supplied;
- captured headers must omit
Authorization and redact secret-like header/body keys;
- captured bodies must be after
SAFE_MODE contact-value sanitization;
- response payloads must preserve the live shape expected by writer helpers, for example
{ contact }, { product }, { dataItem }, { file }, or an expected collection
field such as { contacts: [] } for skipped queries.
Dry-run placeholder target IDs are not live authority. If downstream local execution
needs them, write them only to state/crosswalk/dry-run-crosswalk.ndjson or keep them
in memory. Never append simulated target IDs to state/crosswalk/crosswalk.ndjson and
never upload them to a CMS crosswalk mirror.
When createWixSetupExecutor({ dryRun: true }) is used, each setup step must first be
reduced to a structured intent, then captured as planned_dry_run without invoking MCP,
CLI, SDK, or REST transports that access or mutate Wix account/site state.
Artifact authority at run completion
The shared runtime contract should preserve explicit artifact authority:
execution-log.md for chronology and operator/debug context only
completion-report.json for final outcome
completion-summary.md for deterministic user-facing rendering
The runtime should not force downstream consumers to reconstruct final outcome by reading
raw audit logs or chronological execution logs.
Retry and throttling policy
This adapter should define one shared retry/throttling contract rather than letting each
migration invent its own.
It should classify, at minimum:
- retryable transport/network failures
- retryable
429 responses
- retryable
5xx responses
- non-retryable
4xx validation failures unless explicitly listed otherwise
When the server provides backoff hints such as Retry-After, the shared runtime should
honor them.
Bulk and fallback policy
The shared runtime should know:
- when a native bulk primitive exists and is preferred
- when to fall back from bulk to per-record writes
- when an upsert requires lookup + create/update orchestration
- when an unverified native path must be surfaced before execution rather than used
silently
Media policy by target
Do not treat all media the same. The target domain decides whether media must exist in Wix
first or can be ingested through the entity API itself.
- Wix Stores products: prefer external URLs on
media.itemsInfo.items[] and let the
product API ingest them in the background. This is the default path when the URLs are
publicly reachable.
- Wix Blog cover images and other surfaces that require a Wix media id at create/update
time: import media first, then pass the Wix media id into the entity payload.
- Generic Media Manager import is a fallback/shared primitive, not the default for
every entity carrying media.
Media import source URL reachability
The media primitive imports by URL: Wix servers fetch the provided sourceUrl. Public
HTTPS URLs are expected; localhost, 127.0.0.1, Docker-only hosts, and other
private-only URLs are not reachable by Wix during a live import. This is optional source
preparation and, as far as we know today, affects media import only.
If the source system is local, the migration should either:
- expose the source through a public HTTPS tunnel, then pass/rewire media URLs to that
public base URL; or
- skip/defer media import and clearly state which media-dependent references will be
missing until media is imported.
Ngrok quick setup for macOS:
brew install ngrok
ngrok config add-authtoken "<YOUR_AUTHTOKEN>"
ngrok http 8090
export WP_BASE_URL=https://<id>.ngrok-free.app
Validate by real call — do not trust doc examples
Codegen-time MCP doc checks confirm an endpoint exists; they do not confirm the
request shape works. The live wporg-news import proved doc examples can be wrong
(lowercase Ricos plugins → 400) or incomplete (featured image field, tag body). The
rule for this adapter:
-
Treat a shape as verified only after a real call succeeds — encode the working
shape here with a // VERIFIED: (or // VERIFIED-TRAP:) note and a date.
-
A // UNVERIFIED: primitive is allowed as a bootstrap point for generated code, but it
is not a silent live-write permission. The execution plan must call it out, and setup
verification must either promote it with a sandbox/live validation or route to fallback.
-
Keep tests/target-wix/contract-test.js current: it issues one real call per primitive
against a sandbox site and is the single place schema drift surfaces. Tests live in the
repo's tests/ tree rather than in this bundle, so the published product carries no test
harnesses. Run from the repo root:
bash tests/run-all.sh target-wix
WIX_AUTH_TOKEN=... WIX_SITE_ID=... bash tests/run-all.sh target-wix
Without credentials the shape checks still run and the live calls are skipped.
tests/target-wix/ndjson-contract-test.js covers lib/ndjson.js and never needs
credentials.
For live verification runs, pass --progress-log <path> and poll it per
CONVENTIONS.md#progress-log-polling.
Run on a cadence and after any Wix API change. A failing contract test — not a stranger's
broken import — is how we learn the surface moved.
Stores catalog is Catalog V3 only — Catalog V1 is not supported
A migration only ever writes to a V3_CATALOG site. Catalog V1 is not a supported
destination: there is no V1 write primitive here, no V1 fallback, and none should be added
(a former V1 fallback was removed — it only masked real V3 errors as spurious 428s). A V1
site is a blocker the run halts on, not a variant the adapter, the mapper, or codegen
accommodates. The V3 create traps are handled in lib/wix-writers.js
(normalizeStoresProductV3 + the top-level treeReference builder), so callers never
re-hit them.
On a site this run created, a Stores install is guaranteed Catalog V3. This section
used to require verifying that live, after a 2026-07-30 incident where a site scaffolded
from the blank headless template, with Stores installed afterwards through the App
Installation API, came up V1_CATALOG — Catalog version is fixed at provisioning, so
every V3 write on that site failed and the site had to be abandoned. The Wix platform has
since changed: Catalog V3 is now guaranteed at provisioning regardless of scaffold
template, so that failure mode can no longer occur for a newly-created site, and neither
the --site-template commerce requirement nor the pre-write version gate apply anymore —
see 0079-catalog-v3-guaranteed-retire-v1-gate.md.
This does not extend to an existing site this run did not create — rp-execute-setup
("A V1_CATALOG verdict is terminal here") still treats that case as a live risk and
halts on it, since a pre-existing site's catalog version predates this guarantee.
What stays in codegen (not here)
Per-project field maps and ordering (which source field → which data key, the
media/author/taxonomy ref maps, upsert-by-key) live in the generated transforms/write
specs. This adapter holds the invariant Wix request shapes, transport, and shared
execution mechanics. Collection names and schemas (PodcastEpisodes, …) are
project-specific and come from the mapping plan.
Provisioning pointers (see rp-execute-setup)
- Apps (Blog, Members) install via the App Installation API; ground
appDefId from the
official "Apps Created by Wix" table.
- Wix Data enablement (
WDE0110): install the Wix Data app appDefId e593b0bd-b783-45b8-97c2-873d42aacaf4 via the App Installation API; afterward POST /wix-data/v2/collections creates NATIVE collections with no WDE0110 (verified live).
Fallback: a custom app with a data-collections extension (declares collections at
install time, but can't express REFERENCE fields).
Scope & coverage
Wix has many apps/entities (Stores, Bookings, Events, Restaurants, Pricing Plans, CRM,
…). This adapter does not pre-build all of them. Coverage is demand-driven and
grows through reviewed releases. A migration may still target a native Wix entity before a
dedicated primitive exists; in that case codegen emits a native REST path using
sendDirectRest, logs the missing primitive, and calls notifyMissingWriter so the
RePlatform team can add the writer later.
Native target ladder when no dedicated writer exists:
- Use the dedicated
rp-target-wix primitive when one exists.
- If Wix has a native entity but no dedicated primitive, generate a native REST call
from Wix MCP/docs-schema, mark it
UNVERIFIED, log it, call notifyMissingWriter, and
surface it in the execution plan before any write. This is not a silent live write.
- Use CMS only when there is no suitable native Wix entity, or when the native entity
is explicitly rejected for fidelity/side-effect reasons. CMS is not a fallback for a
missing adapter writer.
- Halt if neither a native path nor an acceptable CMS/custom target exists.
The invariant: anything not backed by a verified primitive is surfaced to the user for
consent before execution — never written silently.
Setup runtime expectations
This adapter should also define the shared setup-side runtime used by setup execution.
It should cover reusable mechanics for:
- app installation and verification
- Wix Data enablement verification
- CMS collection and field provisioning when supported
- setup step ordering/checkpointing
- setup audit logging
- setup execution reporting
rp-execute-setup should consume setup artifacts through this runtime rather than
re-implementing setup mechanics in skill-local prose or one-off scripts.
Contract-test expectations
Contract tests should validate not only request builders, but also the most important
shared runtime invariants.
Examples:
- verified request shapes still serialize correctly
- retry policy classifies common Wix failures correctly
- throttle/backoff handling honors server hints
- compact reports preserve stable top-level fields
- completion reports preserve stable top-level fields
- audit events preserve the agreed machine-readable shape