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