Setting the file. One moment.
Migration Review Mapping · Wix Replatform · wix/skills · Skills Docs
ContentsBack to the top of the page async function createMappingReviewAdapter
— line 111
This file
Number 45.107
Position 107 of 182
Type JavaScript
Size 21 KB
Lines 228 lib/ migration-review-mapping.js
JavaScript · 228 lines · 21 KB
'mapping/mapping-plan.json'
;
8 const LEDGER = 'mapping/review/mapping-gaps.json' ;
9 const CATEGORIES = [ 'source_scope' , 'mapping_fidelity' , 'execution_recovery' , 'setup_plugin' , 'explanations_checks' ];
10 const hashBytes = value => crypto. createHash ( 'sha256' ). update (value). digest ( 'hex' );
11 const unique = values => [ ...new Set (values)]. sort ();
12 function fail ( code , message ) { throw Object. assign ( new Error (message), {code}); }
13 function mappingScope ( sourceEntity ) {
14 if ( typeof sourceEntity !== 'string' || ! sourceEntity) fail ( 'mapping_binding_required' , 'source entity identity is required' );
15 return `mapping:${ hashBytes ( sourceEntity ). slice ( 0 , 24 ) }` ;
16 }
17 function mappingScopes ( plan , entityRefs = plan.executionScope?.selectedEntityRefs) {
18 if ( ! Array. isArray (entityRefs) || ! entityRefs. length || entityRefs. some ( ref => ! plan.executionScope?.selectedEntityRefs?. includes (ref))) fail ( 'mapping_binding_required' , 'mapping scopes must name selected source entities' );
19 return unique (entityRefs. filter ( ref => fields. entityRows (plan). find ( row => fields. entityRef (row) === ref)?.targetClassification !== 'manual-mapping' ). map (mappingScope));
20 }
21 async function readFrame ( projectDir , expectedDigest ) {
22 const plan = JSON . parse ( await fs. readFile (path. join (projectDir, PLAN ), 'utf8' ));
23 if (plan.fieldContractVersion !== 1 || ! expectedDigest || fields. digest (plan) !== expectedDigest) fail ( 'mapping_binding_required' , 'current exact mapping authority must match the importer pin' );
24 const rows = fields. entityRows (plan), refs = rows. map (fields.entityRef), scope = plan.executionScope;
25 if ( ! scope || ! scope.selectedEntityRefs?. length || unique (refs). length !== refs. length || unique (scope.selectedEntityRefs). length !== scope.selectedEntityRefs. length || scope.selectedEntityRefs. some ( ref =>! refs. includes (ref))) fail ( 'mapping_binding_required' , 'mapping execution scope is invalid' );
26 for ( const row of rows) {
27 const errors = fields. validateEntity (row); if (errors. length ) fail ( 'mapping_binding_required' ,errors. join ( '; ' ));
28 if (row.targetClassification !== 'manual-mapping' ) for ( const d of row.fieldMappings) {
29 const scope = fields. affectedScope (d.affectedScope,row.discoveryCoverage);
30 if (scope.sourceSnapshotDigest !== row.discoveryCoverage.sourceSnapshotDigest || scope.inventoryDigest !== row.discoveryCoverage.inventoryDigest) fail ( 'mapping_binding_required' , 'decision scope proof belongs to a different source snapshot or inventory' );
31 }
32 }
33 const deferred = (scope.deferredEntities || []). map ( d => d.entityRef);
34 if ( ! Array. isArray (scope.resolvedEntityRefs) || unique (deferred). length !== deferred. length || unique (scope.resolvedEntityRefs). length !== scope.resolvedEntityRefs. length || unique ([ ... scope.resolvedEntityRefs, ... deferred]). length !== scope.selectedEntityRefs. length || scope.selectedEntityRefs. some ( ref => scope.resolvedEntityRefs. includes (ref) === deferred. includes (ref))) fail ( 'mapping_binding_required' , 'resolved and deferred mapping scopes must partition selected entities' );
35 for ( const ref of scope.resolvedEntityRefs) if (rows. find ( row => fields. entityRef (row) === ref).targetClassification !== 'manual-mapping' ) fields. assertExecutionScope (plan,ref,expectedDigest,projectDir);
36 const projected = fields. compareMappingProjections (projectDir,plan);
37 if ( ! projected.ok) fail ( 'mapping_binding_required' ,projected.errors. join ( '; ' ));
38 const ledger = JSON . parse ( await fs. readFile (path. join (projectDir, LEDGER ), 'utf8' ));
39 const gaps = Array. isArray (ledger) ? ledger : ledger.gaps;
40 if ( ! Array. isArray (gaps)) fail ( 'mapping_binding_required' , 'field-gap ledger is missing' );
41 const active = rows. flatMap (fields.gapRows), activeGaps = [];
42 for ( const expected of active) {
43 const matches = gaps. filter ( g => g.id === expected.id);
44 if (matches. length !== 1 ) fail ( 'mapping_binding_required' , `missing or duplicate gap ${ expected . id }` );
45 const actual = matches[ 0 ];
46 for ( const key of [ 'sourceEntity' , 'decisionId' , 'sourcePaths' , 'disposition' , 'storage' , 'affectedScope' , 'recovery' ]) {
47 if (fields. canonical (actual[key]) !== fields. canonical (expected[key])) fail ( 'mapping_binding_required' , `gap ${ expected . id } has stale ${ key }` );
48 }
49 activeGaps. push (actual);
50 }
51 const recovery = require ( './mapping-recovery' );
52 // A mapping revision plans a repair; it cannot erase a still-unrepaired field.
53 for ( const gap of gaps. filter ( g => g.recovery && ! activeGaps. some ( a => a.id === g.id))) {
54 const verifiedSourceIds = recovery. verifiedIds (projectDir,gap);
55 const complete = ! gap.affectedScope.sourceIds. some ( id => ! verifiedSourceIds. includes ( String (id)));
56 if ( ! complete || gap.nativeBehaviorMissing?. length ) activeGaps. push ({ ... gap,verifiedSourceIds,recoveryStatus:complete ? 'verified' : 'pending' });
57 }
58 return {plan,ledger,rows,activeGaps,mappingPlanDigest:expectedDigest,gapLedgerDigest:fields. digest (ledger),executionScopeDigest:fields. digest (scope)};
59 }
60 function commonEvidence ( frame , ref ) {
61 return {mappingPlanDigest:frame.mappingPlanDigest,gapLedgerDigest:frame.gapLedgerDigest,executionScopeDigest:frame.executionScopeDigest,sourceEntity:ref};
62 }
63 function facets ( gap , row , authorizedOmissions = false ) {
64 const d = row.fieldMappings?. find ( d => d.id === gap.decisionId);
65 if (gap.recovery) return [ ... (gap.recoveryStatus === 'verified' ? [] : [ 'storage' ]), ... (gap.nativeBehaviorMissing?. length ? [ 'behavior' ] : [])];
66 // Mapping consent permits the write; it does not resolve an absent behavior.
67 if (gap.gapKind === 'excluded' && d && ( ! fields. pendingDecision (row,d) || authorizedOmissions)) return gap.nativeBehaviorMissing?. length ? [ 'behavior' ] : [];
68 if (gap.gapKind === 'no-native-target' ) return [ 'storage' , ... (gap.nativeBehaviorMissing?. length ? [ 'behavior' ] : [])];
69 if (gap.gapKind === 'setup-blocked' ) return [ 'setup' ];
70 return [gap.gapKind === 'lossy' ? 'behavior' : 'decision' ];
71 }
72 function finding ( gap , facet ) {
73 const unknown = gap.affectedScope.countStatus !== 'known' ;
74 const native = (gap.nativeBehaviorMissing || []). join ( '; ' );
75 const consequence = facet === 'behavior' ? native || gap.description : facet === 'storage' ? 'Retained values require matching storage and write verification; native behavior is assessed separately.' : gap.description;
76 return {findingKey: `${ gap . id }:${ facet }` ,scopeKey: mappingScope (gap.sourceEntity),group: 'Data' ,
77 title: `${ gap . sourceEntity }: ${ facet }` ,summary:gap.description,affected:gap.affectedScope.sourceIds || [],
78 affectedCount:unknown ? null : gap.affectedScope.count,unknownAreas:unknown ? [ 'affected-count' ] : [],
79 intendedBehavior:fields. canonical ({sourcePaths:gap.sourcePaths,targetRef:gap.targetRef,writeOperation:gap.writeOperation,storage:gap.storage,nativeBehaviorMissing:gap.nativeBehaviorMissing,limitation:gap.limitation,evidenceScopeDigest:fields. digest (gap.affectedScope)}),
80 consequence,action:facet === 'behavior' ? gap.nextAction?.cta || 'Review and explicitly accept or resolve the remaining behavior consequence.' : `Requires Import Operator: ${ gap . nextAction ?. cta || 'Inspect the referenced mapping/setup evidence and run the existing matching check.'}` ,
81 prerequisites: unique (gap.nextAction?.prerequisites || []),requirement: 'required' ,
82 verificationPolicy:facet === 'behavior' ? 'manual' : 'machine_required' ,evidenceStatus: 'unassessed' ,
83 dismissalAcknowledgment:consequence};
84 }
85 function pinned ( capture , frame , ref ) {
86 const evidence = capture.evidence;
87 return evidence?.sourceEntity === ref && [ 'mappingPlanDigest' , 'gapLedgerDigest' , 'executionScopeDigest' ]. every ( key => evidence[key] === frame[key]);
88 }
89 async function outcomeEvidence ( projectDir , artifact , sourceEntity ) {
90 if ( typeof artifact !== 'string' || path. isAbsolute (artifact)) fail ( 'mapping_outcome_required' , 'outcome artifact must be project-relative' );
91 let root,file,stat,bytes,row;
92 try {
93 root =await fs. realpath (projectDir);file =await fs. realpath (path. resolve (root,artifact));
94 stat =await fs. stat (file);
95 if ( ! file. startsWith ( `${ root }${ path . sep }` )) fail ( 'mapping_outcome_required' , 'outcome artifact escapes the project' );
96 if ( ! stat. isFile () || stat.size > 1024 * 1024 ) fail ( 'mapping_outcome_required' , 'outcome artifact must be a bounded final entity row' );
97 bytes =await fs. readFile (file);row = JSON . parse (bytes);
98 } catch (cause) {
99 if (cause.code === 'mapping_outcome_required' ) throw cause;
100 throw Object. assign ( new Error ( 'read the durable final outcome artifact before resuming mapping publication' ,{cause}),{code: 'mapping_outcome_required' });
101 }
102 if ( ! row || typeof row !== 'object' || row.sourceEntity !== sourceEntity || ! row.outcomeCounters) fail ( 'mapping_outcome_required' , 'outcome artifact must bind the source entity and final counters' );
103 const counters = {};
104 for ( const key of [ 'attempted' , 'succeeded' , 'failed' , 'deferred' , 'unresolved' ]) {
105 const n = row.outcomeCounters[key]; if ( ! Number. isSafeInteger (n) || n < 0 ) fail ( 'mapping_outcome_required' , `outcome counter ${ key } must be measured` );counters[key] = n;
106 }
107 return {outcomeCounters:counters,outcomeArtifact:{path:artifact,sha256: hashBytes (bytes)}};
108 }
109
110 // Registration belongs to the generated importer. All capture producers share its queue.
111 async function createMappingReviewAdapter ({ projectDir , producerId , executionId , migrationId , mappingPlanDigest , captureQueue , assessedSourceEntityRefs = null , repairOf = null , probeNonce = null , review = null }) {
112 review = review || require ( './migration-review.js' );
113 const frame =await readFrame (projectDir,mappingPlanDigest);
114 const expectedScopes = mappingScopes (frame.plan,assessedSourceEntityRefs || frame.plan.executionScope.selectedEntityRefs);
115 // An initial execution registers every selected scope, held ones included. Only a repair
116 // naming an earlier import execution that registered the subset may assess fewer.
117 if (fields. canonical (expectedScopes) !== fields. canonical ( mappingScopes (frame.plan))) {
118 const prior = repairOf && repairOf !== executionId ? await review. readExecutionEvidence (projectDir,repairOf). catch (() => null ) : null ;
119 if ( ! prior || prior.execution.producerId !== producerId || prior.execution.executionKind !== 'import' || expectedScopes. some ( s =>! prior.execution.assessedScope. includes (s))) fail ( 'mapping_execution_required' , 'an initial execution registers selected and held mapping scopes; a subset needs repairOf naming the earlier import execution that registered it' );
120 }
121 async function current () {
122 const next =await readFrame (projectDir,mappingPlanDigest);
123 if (next.gapLedgerDigest !== frame.gapLedgerDigest) fail ( 'mapping_binding_required' , 'gap ledger changed; resume with explicit current mapping evidence' );
124 const retained =await review. readExecutionEvidence (projectDir,executionId);
125 if (retained.execution.producerId !== producerId || retained.execution.executionKind !== (probeNonce ? 'probe' : 'import' )) fail ( 'mapping_execution_required' , 'mapping evidence requires the stored canonical owner and explicit execution kind' );
126 if (fields. canonical ( unique (retained.execution.assessedScope. filter ( s => s. startsWith ( 'mapping:' )))) !== fields. canonical (expectedScopes)) fail ( 'mapping_execution_required' , 'registered mapping scopes must match the explicit initial/repair assessment' );
127 if (retained.captures. some ( c => c.category === 'mapping_fidelity' && ! c.evidence.probeNonce && Object. hasOwn (c.evidence, 'gapLedgerDigest' ) && c.evidence.gapLedgerDigest !== frame.gapLedgerDigest)) fail ( 'mapping_binding_required' , 'gap ledger changed; start an explicit repair execution and recapture current mapping evidence' );
128 const report =await review. readReport (projectDir);
129 if (report.migrationId !== migrationId) fail ( 'mapping_binding_required' , 'review store belongs to a different migration' );
130 const selected = frame.plan.executionScope.selectedEntityRefs. filter ( ref => frame.rows. find ( row => fields. entityRef (row) === ref).targetClassification !== 'manual-mapping' && retained.execution.assessedScope. includes ( mappingScope (ref)));
131 if ( ! selected. length ) fail ( 'mapping_execution_required' , 'execution has no selected mapping scopes' );
132 return { ... retained,selected,report};
133 }
134 await current ();
135 async function capture ( ref , phase , evidence , sequence = 0 ) {
136 if ( ! captureQueue) fail ( 'mapping_capture_queue_required' , 'use the importer’s shared evidence capture queue' );
137 if ( ! Number. isSafeInteger (sequence) || sequence < 0 ) fail ( 'mapping_binding_required' , 'capture sequence must be a nonnegative checkpoint ordinal' );
138 const state =await current ();
139 if ( ! state.selected. includes (ref)) fail ( 'mapping_execution_required' , 'entity is outside this execution’s registered mapping scope' );
140 if (probeNonce) evidence = { ... evidence,probeNonce};
141 const identity = fields. digest ({producerId,executionId,scope: mappingScope (ref),phase,sequence,evidence});
142 return captureQueue ({contractVersion:review. CONTRACT_VERSION ,migrationId,executionId,requestId: `mapping-capture:${ identity }` ,captureId: `mapping-capture:${ identity }` ,category: 'mapping_fidelity' ,scope:[ mappingScope (ref)],phase,sequence,evidence});
143 }
144 async function captureBeforeWrite () {
145 const state =await current (), receipts = [];
146 for ( const ref of state.selected) {
147 const row = frame.rows. find ( r => fields. entityRef (r) === ref), deferred = frame.plan.executionScope.deferredEntities. find ( d => d.entityRef === ref);
148 const unknownAreas = unique ([ ... (row.discoveryCoverage?.uninspectedAreas || []). map ( x =>typeof x === 'string' ? x : fields. canonical (x)), ... (deferred ? [ 'deferred-mapping' ] : [])]);
149 const evidence = { ... commonEvidence (frame,ref),fieldGaps:frame.activeGaps. filter ( g => g.sourceEntity === ref),unknownAreas,
150 ... (deferred ? {executionDisposition: 'deferred' ,decisionIds:deferred.decisionIds,reason:deferred.reason,blockedByEntityRefs:deferred.blockedByEntityRefs} : {executionDisposition: 'resolved' })};
151 receipts. push ( await capture (ref, 'before-write' ,evidence));
152 }
153 return receipts;
154 }
155 async function captureWriteOutcome ( sourceEntity ,{ outcomeArtifact , unknownAreas = [], sequence = 0 }) {
156 if ( ! frame.plan.executionScope.resolvedEntityRefs. includes (sourceEntity)) fail ( 'mapping_outcome_required' , 'deferred mapping has no write outcome' );
157 if ( ! Array. isArray (unknownAreas) || unknownAreas. some ( x =>typeof x !== 'string' )) fail ( 'mapping_outcome_required' , 'outcome unknown areas must be named' );
158 const { captures } =await current ();
159 if ( ! captures. some ( c => c.category === 'mapping_fidelity' && c.phase === 'before-write' && c.scope. includes ( mappingScope (sourceEntity)) && pinned (c,frame,sourceEntity))) fail ( 'mapping_outcome_required' , 'capture current before-write mapping evidence before outcomes' );
160 const observed =await outcomeEvidence (projectDir,outcomeArtifact,sourceEntity);
161 const evidence = { ... commonEvidence (frame,sourceEntity), ... observed,unknownAreas: unique (unknownAreas), ... (probeNonce ? {probeNonce} : {})};
162 if (captures. some ( c => c.category === 'mapping_fidelity' && c.phase === 'write-outcome' && c.scope. includes ( mappingScope (sourceEntity)) && pinned (c,frame,sourceEntity) && c.sequence === sequence && fields. canonical (c.evidence) !== fields. canonical (evidence))) fail ( 'mapping_outcome_required' , 'changed outcome evidence requires a new checkpoint ordinal' );
163 return capture (sourceEntity, 'write-outcome' ,evidence,sequence);
164 }
165 async function publish ({ captureIds , completionReportDigest = null } = {}) {
166 if (probeNonce) fail ( 'mapping_execution_required' , 'isolated probe evidence cannot publish import mapping tasks' );
167 const state =await current (), all = state.captures;
168 const ids = captureIds === undefined ? all. map ( c => c.captureId) : unique (captureIds);
169 if (ids. some ( id =>! all. some ( c => c.captureId === id))) fail ( 'mapping_binding_required' , 'publication references missing or foreign execution captures' );
170 const selected = all. filter ( c => ids. includes (c.captureId) && ! c.evidence.probeNonce);
171 const receipts = [];
172 for ( const ref of state.selected) {
173 const scopeKey = mappingScope (ref);
174 let scoped = selected. filter ( c => c.scope. includes (scopeKey));
175 const outcomes = all. filter ( c => c.scope. includes (scopeKey) && ! c.evidence.probeNonce && c.category === 'mapping_fidelity' && c.phase === 'write-outcome' && pinned (c,frame,ref));
176 if (outcomes. length ) {
177 const latest = Math. max ( ... outcomes. map ( c => c.sequence));
178 const final = outcomes. filter ( c => c.sequence === latest);
179 if ( unique (final. map ( c => fields. canonical (c.evidence))). length !== 1 ) fail ( 'mapping_outcome_required' , 'final outcome checkpoint has conflicting evidence' );
180 const proof = final[ 0 ], actual =await outcomeEvidence (projectDir,proof.evidence.outcomeArtifact.path,ref);
181 if (fields. canonical (actual.outcomeArtifact) !== fields. canonical (proof.evidence.outcomeArtifact) || fields. canonical (actual.outcomeCounters) !== fields. canonical (proof.evidence.outcomeCounters)) fail ( 'mapping_outcome_required' , 'durable outcome changed without a matching final capture' );
182 scoped = scoped. filter ( c =>! outcomes. includes (c) || c.sequence === latest);
183 }
184 const unknown = unique (scoped. flatMap ( c => c.evidence.unknownAreas || c.evidence.unassessed || c.evidence.unknown || []));
185 for ( const phase of [ 'before-write' , 'write-outcome' ]) {
186 if ( ! scoped. some ( c => c.category === 'mapping_fidelity' && c.phase === phase && pinned (c,frame,ref))) unknown. push ( `mapping-boundary:${ phase }` );
187 }
188 const complete =! unknown. length && CATEGORIES . every ( category => scoped. some ( c => c.category === category));
189 const row = frame.rows. find ( r => fields. entityRef (r) === ref), gaps = frame.activeGaps. filter ( g => g.sourceEntity === ref);
190 const authorizedOmissions = row.omissionReviewRequested && frame.plan.executionScope.resolvedEntityRefs. includes (ref); // readFrame already verified the exact approval
191 const tasks = gaps. flatMap ( g => facets (g,row,authorizedOmissions). map ( facet => finding (g,facet)));
192 for ( const task of tasks. filter ( t => t.verificationPolicy === 'machine_required' && ! t.findingKey. endsWith ( ':decision' ))) {
193 const fingerprint = review. materialFingerprint (task);
194 const proof = scoped. find ( c => c.category === 'explanations_checks' && (c.evidence.checks || []). some ( check => check.findingKey === task.findingKey && check.materialFingerprint === fingerprint && check.kind === 'machine' && check.status === 'passed' && check.checkId && Number. isFinite (Date. parse (check.observedAt))));
195 if (proof) Object. assign (task,{evidenceStatus: 'verified' ,verificationCaptureId:proof.captureId});
196 }
197 const results = gaps. filter ( g => g.gapKind === 'excluded' && (authorizedOmissions || ! fields. pendingDecision (row,row.fieldMappings. find ( d => d.id === g.decisionId)))). map ( g => ({resultKey: `${ g . id }:decision` ,scopeKey,group: 'Data' ,title: `${ ref }: exclusion recorded` ,status: 'notMigrated' ,summary:g.description,affectedCount:g.affectedScope.count,affected:g.affectedScope.sourceIds || [],intendedBehavior:fields. canonical ({sourcePaths:g.sourcePaths,evidenceScopeDigest:fields. digest (g.affectedScope)})}));
198 // Only explicit matching committed dispositions close historical facets. Omission never does.
199 const changes =new Map ();
200 const oldTasks = state.report.tasks. filter ( t => t.producerId === producerId && t.scopeKey === scopeKey && t.findingKey. startsWith ( 'field-gap:' ) && ! tasks. some ( n => n.findingKey === t.findingKey));
201 for ( const task of oldTasks) for ( const proof of scoped) for ( const d of proof.evidence.dispositions || []) {
202 if (d.findingKey !== task.findingKey || d.materialFingerprint !== task.materialFingerprint || ! d.reason) continue ;
203 if (d.status === 'not_applicable' || d.status === 'superseded' && tasks. some ( t => t.findingKey === d.supersededBy && gaps. some ( g => t.findingKey. startsWith ( `${ g . id }:` ) && g.supersedes?. some ( old => task.findingKey. startsWith ( `${ old }:` ))))) {
204 if (changes. has (d.findingKey) && fields. canonical (changes. get (d.findingKey)) !== fields. canonical (d)) fail ( 'mapping_binding_required' , 'conflicting finding dispositions require explicit reconciliation' );
205 changes. set (d.findingKey,d); break ;
206 }
207 }
208 const applicabilityChanges = [ ... changes. values ()]. map ( d => ({findingKey:d.findingKey,status:d.status,reason:d.reason,captureIds:scoped. filter ( c => (c.evidence.dispositions || []). some ( x => fields. canonical (x) === fields. canonical (d))). map ( c => c.captureId). sort (), ... (d.supersededBy ? {supersededBy:d.supersededBy} : {})}));
209 const payload = {contractVersion:review. CONTRACT_VERSION ,migrationId,producerId,executionId,assessedScope:[scopeKey],captureIds:scoped. map ( c => c.captureId). sort (),coverage:{status:complete ? 'complete' : 'incomplete' ,unknownAreas: unique (unknown)},tasks,results,applicabilityChanges, ... (completionReportDigest ? {completionReportDigest} : {})};
210 const report =await review. readReport (projectDir);
211 receipts. push ( await review. publishEvidence (projectDir,{ ... payload,requestId: `mapping-publish:${ fields . digest ( payload ) }` ,expectedStateVersion:report.stateVersion}));
212 }
213 const report =await review. readReport (projectDir), overview =await review. overview (projectDir);
214 return {status: 'published' ,receipts,report,overview};
215 }
216 return {captureBeforeWrite,captureWriteOutcome,publish};
217 }
218 async function publishMappingReview ( options , publication = {}) {
219 if ( ! options.executionId) {
220 const frame =await readFrame (options.projectDir,options.mappingPlanDigest);
221 if (frame.plan.executionScope.resolvedEntityRefs. some ( ref => frame.rows. find ( row => fields. entityRef (row) === ref).targetClassification !== 'manual-mapping' )) fail ( 'mapping_execution_required' , 'resolved mapping publication needs its actual importer execution' );
222 const fallback = fields. mappingReviewFallback (frame.plan,frame.ledger);
223 fields. persistMappingReviewFallback (options.projectDir,fallback);
224 return fallback;
225 }
226 return ( await createMappingReviewAdapter (options)). publish (publication);
227 }
228 module . exports = {mappingScope,mappingScopes,createMappingReviewAdapter,publishMappingReview};