Setting the file. One moment. Contract Ledger · Rp Target Wix · wix/skills · Skills DocsPost
Size5 KBlib/contract-ledger.js
JavaScript·141 lines·5 KB
function
normalizePathPresence
(
verifiedPaths
=
[]) {
10 return verifiedPaths
11 .filter((entry) => entry && entry.path && entry.present !== false)
12 .map((entry) => entry.path);
13}
14
15function createContractLedgerProposalFromStoresVerification(artifact) {
16 if (!artifact || typeof artifact !== 'object') {
17 throw new Error('verification artifact is required');
18 }
19 if (artifact.command !== 'stores subscription-create') {
20 throw new Error(`unsupported verification command: ${artifact.command || '<missing>'}`);
21 }
22 const verifiedPaths = normalizePathPresence(artifact.verifiedPaths);
23 return {
24 schemaVersion: SCHEMA_VERSION,
25 proposalId: `stores-product-subscription-create-${(artifact.timestamp || new Date().toISOString()).slice(0, 10)}`,
26 status: artifact.status === 'passed' ? 'proposed' : 'blocked',
27 sourceVerification: {
28 command: artifact.command,
29 artifactPath: artifact.artifactPath || null,
30 targetSiteIdentifier: artifact.targetSiteIdentifier || null,
31 timestamp: artifact.timestamp || null,
32 probeRecordId: artifact.probeRecordId || null,
33 },
34 targetRef: 'stores/product',
35 fieldContract: {
36 domain: 'stores',
37 entity: 'product',
38 surface: 'catalog-v3',
39 operation: 'createProduct',
40 path: 'product.subscriptionDetails',
41 verificationLevel: 'live-create-and-readback',
42 lastVerified: artifact.timestamp ? artifact.timestamp.slice(0, 10) : null,
43 verifiedBy: artifact.targetSiteIdentifier || artifact.sourceRunId || 'live-verification',
44 requiredPaths: verifiedPaths.filter((entry) => entry !== 'product.subscriptionDetails.subscriptions[].id'),
45 constraints: Array.isArray(artifact.constraintsDiscovered)
46 ? artifact.constraintsDiscovered.map((constraint) => ({ ...constraint }))
47 : [],
48 readback: {
49 'product.subscriptionDetails': verifiedPaths.includes('product.subscriptionDetails') ? 'returned-after-create' : 'unverified',
50 'product.subscriptionDetails.subscriptions[].id': verifiedPaths.includes('product.subscriptionDetails.subscriptions[].id') ? 'server-assigned' : 'unverified',
51 },
52 },
53 };
54}
55
56function proposalKey(proposal) {
57 const contract = proposal && proposal.fieldContract;
58 return [
59 proposal && proposal.targetRef,
60 contract && contract.surface,
61 contract && contract.operation,
62 contract && contract.path,
63 ].join('|');
64}
65
66function contractKey(contract, entity) {
67 return [
68 `${contract.domain || (entity && entity.domain)}/${contract.entity || (entity && entity.entity)}`,
69 contract.surface,
70 contract.operation,
71 contract.path,
72 ].join('|');
73}
74
75function promotedContractForProposal(domainsDir, proposal) {
76 if (!proposal || !proposal.targetRef || !proposal.fieldContract) return null;
77 const entity = readEntityByRef(domainsDir, proposal.targetRef);
78 return (entity.fieldContracts || []).find((contract) => contractKey(contract, entity) === proposalKey(proposal)) || null;
79}
80
81function isProposalPromoted(domainsDir, proposal) {
82 let promoted;
83 try {
84 promoted = promotedContractForProposal(domainsDir, proposal);
85 } catch (error) {
86 return false;
87 }
88 if (!promoted) return false;
89 const expected = proposal.fieldContract || {};
90 return promoted.verificationLevel === expected.verificationLevel;
91}
92
93function readJsonIfExists(filePath) {
94 if (!filePath || !fs.existsSync(filePath)) return null;
95 return JSON.parse(fs.readFileSync(filePath, 'utf8'));
96}
97
98function writeProposal(filePath, proposal) {
99 fs.mkdirSync(path.dirname(filePath), { recursive: true });
100 fs.writeFileSync(filePath, `${JSON.stringify(proposal, null, 2)}\n`);
101 return proposal;
102}
103
104function validateContractPromotion({ domainsDir, verificationArtifacts = [], proposalArtifacts = [] } = {}) {
105 const errors = [];
106 const proposals = proposalArtifacts
107 .map((artifactPath) => ({ artifactPath, proposal: readJsonIfExists(artifactPath) }))
108 .filter((entry) => entry.proposal);
109 const proposalsByVerification = new Map();
110 for (const { proposal, artifactPath } of proposals) {
111 const sourcePath = proposal.sourceVerification && proposal.sourceVerification.artifactPath;
112 if (sourcePath) proposalsByVerification.set(path.resolve(sourcePath), { proposal, artifactPath });
113 }
114
115 for (const artifactPath of verificationArtifacts) {
116 const artifact = readJsonIfExists(artifactPath);
117 if (!artifact || artifact.status !== 'passed') continue;
118 if (artifact.command !== 'stores subscription-create') continue;
119 const proposalEntry = proposalsByVerification.get(path.resolve(artifactPath));
120 if (!proposalEntry) {
121 errors.push(`${artifactPath}: passed verification is missing contract-ledger proposal`);
122 continue;
123 }
124 const { proposal } = proposalEntry;
125 if (proposal.status === 'deferred' || proposal.deferralReason) continue;
126 if (!isProposalPromoted(domainsDir, proposal)) {
127 errors.push(`${proposalEntry.artifactPath}: proposal is not promoted into shared target ledger`);
128 }
129 }
130
131 return { ok: errors.length === 0, errors };
132}
133
134module.exports = {
135 SCHEMA_VERSION,
136 createContractLedgerProposalFromStoresVerification,
137 promotedContractForProposal,
138 isProposalPromoted,
139 validateContractPromotion,
140 writeProposal,
141};