1'use strict';23// What happens when a rerun's update collides with a change somebody4// made in Wix since the previous import.5//6// The whole point of this module is that it CANNOT resolve a conflict on its own. There is no7// "fetch the latest revision and retry" function here, deliberately: that is the one behaviour8// is forbidden outright, because it silently overwrites a merchant's own edit with source9// data and leaves no trace that a decision was ever made. Every path out of a conflict runs10// through a recorded user answer, including in one-click mode.1112const crypto = require('node:crypto');13
36const CONFLICT_CHOICES = new Set(['override', 'keep']);
37
38// Enforcement, not documentation. "Each entity needs a verified update
39// operation and conflict protection; if either is unavailable, report the changed record for
40// review rather than overwriting it through its create/import operation."
41//
42// Orders are named explicitly because the failure mode is silent data loss, and live evidence
43// recorded it: Update Order exposes a restricted field set with no general revision
44// parameter, and re-sending Import Order with the same id REPLACES the whole body, clearing
45// omitted fields. A future importer must not be able to route a changed order through this flow
46// just because the prose said not to.
47const UPDATE_DENIED_ENTITIES = new Map([
48 ['ecom/order', 'Update Order exposes a restricted field set and no general revision parameter, and a same-id Import Order is an unguarded whole-body replacement that clears omitted fields. Report the changed order for review.'],
49 ['ecom/order-line-item', 'Written through the parent order, which has no revision-protected update path.'],
50 ['ecom/order-line-item-options', 'Written through the parent order, which has no revision-protected update path.'],
51 ['ecom/order-applied-discount', 'Written through the parent order, which has no revision-protected update path.'],
52 ['ecom/order-merchant-note', 'Written through the parent order, which has no revision-protected update path.'],
53 ['ecom/order-refund', 'Written through the parent order, which has no revision-protected update path.'],
54]);
55
56// `declaredUpdateWriters` comes from the accepted mapping: { 'stores/product': { writerId,
57// revisionParam } }. An entity absent from it has no verified update path as far as this run is
58// concerned, which is a review outcome — never a create/import fallback.
129 return { kind: 'auth', code, httpStatus: status };
130 }
131 if (status === 404) {
132 return { kind: 'not_found', code, httpStatus: status };
133 }
134 // A 409 on a revision-carrying update is the ambiguous case the allowlist cannot settle. It is
135 // NOT auto-treated as a conflict (that would let a real ALREADY_EXISTS drive an override), and
136 // NOT dropped to `other` either. It is surfaced for review with its real code intact.
137 if (status === 409 && sentRevision) {
138 return {
139 kind: 'possible_revision_conflict',
140 code,
141 httpStatus: status,
142 needsHumanClassification: true,
143 note: 'A 409 on a revision-carrying update whose code is not in the observed or declared conflict list. Do not auto-retry; classify it, then add the code to the entity\'s declared conflict codes.',
144 };
145 }
146 if (status === 400 || status === 422 || status === 428) {
147 return { kind: 'validation', code, httpStatus: status };
148 }
149 // Everything else keeps its real API error and stays an ordinary failure.
150 return { kind: 'other', code, httpStatus: status };
151}
152
153function stableStringify(value) {
154 if (value === null || typeof value !== 'object') {