@visa/cli 4.1.0-rc.99 → 5.0.0-rc.335
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -156
- package/dist/cli.js +333 -532
- package/dist/managed-runtime/resolve-and-update.mjs +268 -0
- package/dist/managed-runtime/runtime-readiness.mjs +126 -0
- package/dist/managed-runtime/update-and-restart.mjs +1079 -0
- package/dist/mcp-apps/ucp-checkout.html +280 -0
- package/dist/mcp-server/index.js +234 -387
- package/dist/merchant-ucp-mcp/index.js +7 -0
- package/dist/skills/pair-visa-agent/RUNTIMES.md +56 -26
- package/dist/skills/pair-visa-agent/SKILL.md +319 -320
- package/dist/skills/pair-visa-agent/scripts/__tests__/setup.test.mjs +407 -0
- package/dist/skills/pair-visa-agent/scripts/setup.mjs +310 -30
- package/dist/skills/visa-shopify-checkout/SKILL.md +122 -0
- package/dist/skills/visa-shopify-checkout/references/evidence-and-states.md +37 -0
- package/dist/skills/visa-ucp-shopping/SKILL.md +86 -0
- package/install.ps1 +10 -6
- package/install.sh +7 -2
- package/native/bin/darwin-arm64/visa-runtime-signer +0 -0
- package/native/bin/darwin-x64/visa-runtime-signer +0 -0
- package/native/bin/linux-arm64/visa-runtime-signer +0 -0
- package/native/bin/linux-x64/visa-runtime-signer +0 -0
- package/native/bin/win32-arm64/visa-runtime-signer.exe +0 -0
- package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
- package/native/bin/win32-x64/visa-runtime-signer.exe +0 -0
- package/package.json +23 -31
- package/server.json +3 -3
- package/dist/checkout-engine/adapters/generic.d.ts +0 -23
- package/dist/checkout-engine/adapters/generic.js +0 -216
- package/dist/checkout-engine/adapters/index.d.ts +0 -10
- package/dist/checkout-engine/adapters/index.js +0 -24
- package/dist/checkout-engine/adapters/shopify.d.ts +0 -31
- package/dist/checkout-engine/adapters/shopify.js +0 -423
- package/dist/checkout-engine/adapters/stripe-like.d.ts +0 -10
- package/dist/checkout-engine/adapters/stripe-like.js +0 -21
- package/dist/checkout-engine/amount.d.ts +0 -15
- package/dist/checkout-engine/amount.js +0 -72
- package/dist/checkout-engine/browser-launch.d.ts +0 -46
- package/dist/checkout-engine/browser-launch.js +0 -81
- package/dist/checkout-engine/ceremony.d.ts +0 -64
- package/dist/checkout-engine/ceremony.js +0 -261
- package/dist/checkout-engine/cli-engine.d.ts +0 -227
- package/dist/checkout-engine/cli-engine.js +0 -779
- package/dist/checkout-engine/detect.d.ts +0 -61
- package/dist/checkout-engine/detect.js +0 -398
- package/dist/checkout-engine/evidence.d.ts +0 -25
- package/dist/checkout-engine/evidence.js +0 -104
- package/dist/checkout-engine/executor.d.ts +0 -176
- package/dist/checkout-engine/executor.js +0 -1325
- package/dist/checkout-engine/hosted-approval.d.ts +0 -187
- package/dist/checkout-engine/hosted-approval.js +0 -478
- package/dist/checkout-engine/index.d.ts +0 -6
- package/dist/checkout-engine/index.js +0 -8
- package/dist/checkout-engine/inline-target.d.ts +0 -13
- package/dist/checkout-engine/inline-target.js +0 -37
- package/dist/checkout-engine/instrument.d.ts +0 -61
- package/dist/checkout-engine/instrument.js +0 -87
- package/dist/checkout-engine/live-fill-approval.d.ts +0 -43
- package/dist/checkout-engine/live-fill-approval.js +0 -90
- package/dist/checkout-engine/mandate/card-mandate.d.ts +0 -121
- package/dist/checkout-engine/mandate/card-mandate.js +0 -227
- package/dist/checkout-engine/mandate/mandate-ledger.d.ts +0 -142
- package/dist/checkout-engine/mandate/mandate-ledger.js +0 -338
- package/dist/checkout-engine/mandate.d.ts +0 -25
- package/dist/checkout-engine/mandate.js +0 -100
- package/dist/checkout-engine/outcome.d.ts +0 -30
- package/dist/checkout-engine/outcome.js +0 -225
- package/dist/checkout-engine/owner-only-file.d.ts +0 -19
- package/dist/checkout-engine/owner-only-file.js +0 -41
- package/dist/checkout-engine/package.json +0 -3
- package/dist/checkout-engine/receipt.d.ts +0 -81
- package/dist/checkout-engine/receipt.js +0 -109
- package/dist/checkout-engine/repo-env.d.ts +0 -11
- package/dist/checkout-engine/repo-env.js +0 -23
- package/dist/checkout-engine/trace-handles.d.ts +0 -8
- package/dist/checkout-engine/trace-handles.js +0 -12
- package/dist/checkout-engine/types.d.ts +0 -44
- package/dist/checkout-engine/types.js +0 -2
- package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +0 -74
- package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +0 -248
- package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +0 -82
- package/dist/checkout-engine/vgs-gateway/server-mint-client.js +0 -180
- package/dist/checkout-engine/vgs-live-instrument.d.ts +0 -179
- package/dist/checkout-engine/vgs-live-instrument.js +0 -296
- package/dist/checkout-engine/vic-confirmation.d.ts +0 -34
- package/dist/checkout-engine/vic-confirmation.js +0 -39
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Resolve the server-authorized exact RC, then run one managed convergence transaction. */
|
|
3
|
+
|
|
4
|
+
import { randomUUID } from 'node:crypto'
|
|
5
|
+
import { realpathSync } from 'node:fs'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { resolve } from 'node:path'
|
|
8
|
+
import {
|
|
9
|
+
ManagedRuntimeUpdateError,
|
|
10
|
+
compareExactRc,
|
|
11
|
+
readCurrentRelease,
|
|
12
|
+
resolveManagedUpdateConfig,
|
|
13
|
+
runManagedRuntimeUpdate,
|
|
14
|
+
} from './update-and-restart.mjs'
|
|
15
|
+
|
|
16
|
+
const PREFIX = '[managed-runtime-converge]'
|
|
17
|
+
const BOOTSTRAP_RC = '0.0.0-rc.0'
|
|
18
|
+
const EXACT_RC_RE = /^\d+\.\d+\.\d+-rc\.\d+$/
|
|
19
|
+
const DEFAULT_DISCOVERY_TIMEOUT_MS = 5_000
|
|
20
|
+
const MAX_DISCOVERY_TIMEOUT_MS = 60_000
|
|
21
|
+
|
|
22
|
+
function event(logger, name, fields = {}) {
|
|
23
|
+
logger.log(`${PREFIX} ${JSON.stringify({ event: name, ...fields })}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function positiveInteger(value, fallback, name) {
|
|
27
|
+
if (value === undefined || value === '') return fallback
|
|
28
|
+
const parsed = Number(value)
|
|
29
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0 || parsed > MAX_DISCOVERY_TIMEOUT_MS) {
|
|
30
|
+
throw new ManagedRuntimeUpdateError(
|
|
31
|
+
'invalid_configuration',
|
|
32
|
+
`${name} must be a positive integer no greater than ${MAX_DISCOVERY_TIMEOUT_MS}`
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
return parsed
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveReleaseSourceConfig(env = process.env) {
|
|
39
|
+
const raw = env.VISA_RUNTIME_RELEASE_SOURCE_URL?.trim() || ''
|
|
40
|
+
if (!raw) {
|
|
41
|
+
throw new ManagedRuntimeUpdateError(
|
|
42
|
+
'release_source_required',
|
|
43
|
+
'VISA_RUNTIME_RELEASE_SOURCE_URL must name the trusted Visa /v1/status endpoint'
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let source
|
|
48
|
+
try {
|
|
49
|
+
source = new URL(raw)
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new ManagedRuntimeUpdateError(
|
|
52
|
+
'invalid_configuration',
|
|
53
|
+
'VISA_RUNTIME_RELEASE_SOURCE_URL must be an absolute URL',
|
|
54
|
+
{ cause: error }
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const expectedEnvironment = env.VISA_RUNTIME_EXPECTED_ENVIRONMENT?.trim() || ''
|
|
59
|
+
const loopback = source.hostname === '127.0.0.1' || source.hostname === 'localhost'
|
|
60
|
+
if (
|
|
61
|
+
source.username ||
|
|
62
|
+
source.password ||
|
|
63
|
+
source.search ||
|
|
64
|
+
source.hash ||
|
|
65
|
+
(source.protocol !== 'https:' && !(expectedEnvironment === 'development' && loopback))
|
|
66
|
+
) {
|
|
67
|
+
throw new ManagedRuntimeUpdateError(
|
|
68
|
+
'invalid_configuration',
|
|
69
|
+
'release source must be credential-free HTTPS (loopback HTTP is development-only) with no query or fragment'
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
sourceUrl: source.toString(),
|
|
75
|
+
timeoutMs: positiveInteger(
|
|
76
|
+
env.VISA_RUNTIME_RELEASE_DISCOVERY_TIMEOUT_MS,
|
|
77
|
+
DEFAULT_DISCOVERY_TIMEOUT_MS,
|
|
78
|
+
'VISA_RUNTIME_RELEASE_DISCOVERY_TIMEOUT_MS'
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function discoverExactRc(env = process.env, dependencies = {}) {
|
|
84
|
+
const config = resolveReleaseSourceConfig(env)
|
|
85
|
+
const fetchImpl = dependencies.fetch ?? globalThis.fetch
|
|
86
|
+
const logger = dependencies.logger ?? console
|
|
87
|
+
const correlationId = dependencies.correlationId ?? randomUUID()
|
|
88
|
+
const currentVersion = dependencies.currentVersion ?? BOOTSTRAP_RC
|
|
89
|
+
if (!EXACT_RC_RE.test(currentVersion)) {
|
|
90
|
+
throw new ManagedRuntimeUpdateError(
|
|
91
|
+
'invalid_runtime_state',
|
|
92
|
+
'current managed runtime version must be an exact RC'
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const controller = new AbortController()
|
|
97
|
+
const timer = setTimeout(() => controller.abort(), config.timeoutMs)
|
|
98
|
+
timer.unref?.()
|
|
99
|
+
const startedAt = Date.now()
|
|
100
|
+
event(logger, 'external_request', {
|
|
101
|
+
correlationId,
|
|
102
|
+
service: 'visa_release_authority',
|
|
103
|
+
operation: 'discover_exact_rc',
|
|
104
|
+
currentVersion,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetchImpl(config.sourceUrl, {
|
|
109
|
+
method: 'GET',
|
|
110
|
+
redirect: 'error',
|
|
111
|
+
signal: controller.signal,
|
|
112
|
+
headers: {
|
|
113
|
+
Accept: 'application/json',
|
|
114
|
+
'User-Agent': 'visa-managed-runtime-converge',
|
|
115
|
+
'X-Visa-CLI-Version': currentVersion,
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
const targetVersion = response.headers.get('x-latest-version')?.trim() || ''
|
|
119
|
+
const acceptedStatus = response.status === 200 || response.status === 401
|
|
120
|
+
await response.body?.cancel?.()
|
|
121
|
+
event(logger, 'external_response', {
|
|
122
|
+
correlationId,
|
|
123
|
+
service: 'visa_release_authority',
|
|
124
|
+
operation: 'discover_exact_rc',
|
|
125
|
+
ok: acceptedStatus && EXACT_RC_RE.test(targetVersion),
|
|
126
|
+
status: response.status,
|
|
127
|
+
durationMs: Date.now() - startedAt,
|
|
128
|
+
})
|
|
129
|
+
if (!acceptedStatus || !EXACT_RC_RE.test(targetVersion)) {
|
|
130
|
+
throw new ManagedRuntimeUpdateError(
|
|
131
|
+
'target_not_authorized',
|
|
132
|
+
'Visa release authority did not advertise an exact RC'
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
return { correlationId, targetVersion }
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error instanceof ManagedRuntimeUpdateError) throw error
|
|
138
|
+
event(logger, 'external_response', {
|
|
139
|
+
correlationId,
|
|
140
|
+
service: 'visa_release_authority',
|
|
141
|
+
operation: 'discover_exact_rc',
|
|
142
|
+
ok: false,
|
|
143
|
+
status: null,
|
|
144
|
+
durationMs: Date.now() - startedAt,
|
|
145
|
+
})
|
|
146
|
+
throw new ManagedRuntimeUpdateError(
|
|
147
|
+
'release_source_unavailable',
|
|
148
|
+
'Visa release authority could not be reached',
|
|
149
|
+
{ cause: error }
|
|
150
|
+
)
|
|
151
|
+
} finally {
|
|
152
|
+
clearTimeout(timer)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Read the known-good release, tolerating the receipt-less `current` pointer an
|
|
158
|
+
* activation interrupted between `activateRelease()` and `writeReceipt()` leaves behind.
|
|
159
|
+
* `runManagedRuntimeUpdate` repairs exactly that state with `recoverInterruptedActivation()`
|
|
160
|
+
* under the update lock, so a scheduled convergence must reach the transaction instead of
|
|
161
|
+
* exiting non-zero on every invocation until a human intervenes.
|
|
162
|
+
*/
|
|
163
|
+
function readKnownGood(config, dependencies, logger, correlationId) {
|
|
164
|
+
const read = dependencies.readCurrentRelease ?? readCurrentRelease
|
|
165
|
+
try {
|
|
166
|
+
return read(config.managedDir)
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (!(error instanceof ManagedRuntimeUpdateError) || error.code !== 'invalid_runtime_state') {
|
|
169
|
+
throw error
|
|
170
|
+
}
|
|
171
|
+
event(logger, 'known_good_unreadable', {
|
|
172
|
+
correlationId,
|
|
173
|
+
reason: error.message,
|
|
174
|
+
repair: 'delegated_to_update_transaction',
|
|
175
|
+
})
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function runManagedRuntimeConvergence(env = process.env, dependencies = {}) {
|
|
181
|
+
const bootstrapConfig = resolveManagedUpdateConfig({
|
|
182
|
+
...env,
|
|
183
|
+
VISA_CLI_EXACT_VERSION: BOOTSTRAP_RC,
|
|
184
|
+
})
|
|
185
|
+
const logger = dependencies.logger ?? console
|
|
186
|
+
const correlationId = dependencies.correlationId ?? randomUUID()
|
|
187
|
+
const current = readKnownGood(bootstrapConfig, dependencies, logger, correlationId)
|
|
188
|
+
if (current && current.receipt.targetEnvironment !== bootstrapConfig.expectedEnvironment) {
|
|
189
|
+
// Distinct from `invalid_runtime_state`: a mismatched environment always needs a human,
|
|
190
|
+
// where a wedged runtime state is repaired by the update transaction. On-call triages
|
|
191
|
+
// the two differently, so the exit codes must differ. Matches the updater's own code.
|
|
192
|
+
throw new ManagedRuntimeUpdateError(
|
|
193
|
+
'target_environment_mismatch',
|
|
194
|
+
`known-good runtime targets ${current.receipt.targetEnvironment}, expected ${bootstrapConfig.expectedEnvironment}`
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
const currentVersion = current?.receipt.version ?? BOOTSTRAP_RC
|
|
198
|
+
const discovery = await discoverExactRc(env, {
|
|
199
|
+
fetch: dependencies.fetch,
|
|
200
|
+
logger,
|
|
201
|
+
correlationId,
|
|
202
|
+
currentVersion,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
if (current) {
|
|
206
|
+
const order = compareExactRc(discovery.targetVersion, currentVersion)
|
|
207
|
+
if (order <= 0) {
|
|
208
|
+
const status = order === 0 ? 'current' : 'ahead_of_recommendation'
|
|
209
|
+
event(logger, 'convergence_not_required', {
|
|
210
|
+
correlationId,
|
|
211
|
+
status,
|
|
212
|
+
currentVersion,
|
|
213
|
+
recommendedVersion: discovery.targetVersion,
|
|
214
|
+
})
|
|
215
|
+
return {
|
|
216
|
+
ok: true,
|
|
217
|
+
status,
|
|
218
|
+
correlationId,
|
|
219
|
+
version: currentVersion,
|
|
220
|
+
recommendedVersion: discovery.targetVersion,
|
|
221
|
+
updated: false,
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const result = await (dependencies.runManagedRuntimeUpdate ?? runManagedRuntimeUpdate)(
|
|
227
|
+
{
|
|
228
|
+
...env,
|
|
229
|
+
VISA_CLI_EXACT_VERSION: discovery.targetVersion,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
...dependencies.updateDependencies,
|
|
233
|
+
logger,
|
|
234
|
+
correlationId,
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
return {
|
|
238
|
+
...result,
|
|
239
|
+
status: 'updated',
|
|
240
|
+
correlationId,
|
|
241
|
+
recommendedVersion: discovery.targetVersion,
|
|
242
|
+
updated: true,
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function main() {
|
|
247
|
+
try {
|
|
248
|
+
const result = await runManagedRuntimeConvergence(process.env)
|
|
249
|
+
process.stdout.write(`${JSON.stringify(result)}\n`)
|
|
250
|
+
} catch (error) {
|
|
251
|
+
const code = error instanceof ManagedRuntimeUpdateError ? error.code : 'convergence_failed'
|
|
252
|
+
process.stderr.write(`${JSON.stringify({ ok: false, code, message: error.message })}\n`)
|
|
253
|
+
process.exitCode = 1
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function isDirectExecution() {
|
|
258
|
+
if (!process.argv[1]) return false
|
|
259
|
+
try {
|
|
260
|
+
return realpathSync(resolve(process.argv[1])) === realpathSync(fileURLToPath(import.meta.url))
|
|
261
|
+
} catch {
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (isDirectExecution()) {
|
|
267
|
+
void main()
|
|
268
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export const DEFAULT_RUNTIME_READINESS_TIMEOUT_MS = 60_000
|
|
2
|
+
const COMMERCE_RUNTIMES = new Set(['openclaw', 'hermes'])
|
|
3
|
+
|
|
4
|
+
/** Fixed argv only: callers cannot substitute a package, command, or script path. */
|
|
5
|
+
export function commerceSkillArgs(env = process.env, check = false) {
|
|
6
|
+
const runtime = env.VISA_COMMERCE_RUNTIME || ''
|
|
7
|
+
if (runtime && !COMMERCE_RUNTIMES.has(runtime)) {
|
|
8
|
+
throw new Error('VISA_COMMERCE_RUNTIME must be openclaw or hermes')
|
|
9
|
+
}
|
|
10
|
+
return [
|
|
11
|
+
'agent',
|
|
12
|
+
'skill',
|
|
13
|
+
'--commerce',
|
|
14
|
+
...(runtime ? ['--runtime', runtime] : []),
|
|
15
|
+
...(check ? ['--check'] : []),
|
|
16
|
+
'--format',
|
|
17
|
+
'json',
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isPlainRecord(value) {
|
|
22
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildFrom(value) {
|
|
26
|
+
if (!isPlainRecord(value)) return null
|
|
27
|
+
return value?.data?.build ?? value?.build ?? value
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function parseRuntimeReadiness(raw) {
|
|
31
|
+
let parsed
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(raw)
|
|
34
|
+
} catch {
|
|
35
|
+
throw new Error('runtime readiness command must emit one JSON document')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const build = buildFrom(parsed)
|
|
39
|
+
if (
|
|
40
|
+
!isPlainRecord(build) ||
|
|
41
|
+
typeof build.version !== 'string' ||
|
|
42
|
+
build.version.trim() === '' ||
|
|
43
|
+
typeof build.targetEnvironment !== 'string' ||
|
|
44
|
+
build.targetEnvironment.trim() === ''
|
|
45
|
+
) {
|
|
46
|
+
throw new Error('runtime readiness JSON must include version and targetEnvironment')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
version: build.version,
|
|
51
|
+
targetEnvironment: build.targetEnvironment,
|
|
52
|
+
shopifyRuntime: isPlainRecord(build.shopifyRuntime)
|
|
53
|
+
? build.shopifyRuntime
|
|
54
|
+
: isPlainRecord(parsed.shopifyRuntime)
|
|
55
|
+
? parsed.shopifyRuntime
|
|
56
|
+
: null,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function assertSameShopifyRuntime(actual, expected) {
|
|
61
|
+
if (!isPlainRecord(actual)) {
|
|
62
|
+
throw new Error('loaded runtime readiness must attest the named shopify-ucp MCP')
|
|
63
|
+
}
|
|
64
|
+
if (actual.serverName !== 'shopify-ucp') {
|
|
65
|
+
throw new Error('loaded runtime readiness did not attest the canonical shopify-ucp mount')
|
|
66
|
+
}
|
|
67
|
+
for (const field of ['installedVersion', 'toolSchemaFingerprint', 'command']) {
|
|
68
|
+
if (actual[field] !== expected[field]) {
|
|
69
|
+
throw new Error(`loaded Shopify UCP ${field} does not match the certified local runtime`)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (
|
|
73
|
+
!Array.isArray(actual.args) ||
|
|
74
|
+
actual.args.length !== expected.args.length ||
|
|
75
|
+
!actual.args.every((value, index) => value === expected.args[index])
|
|
76
|
+
) {
|
|
77
|
+
throw new Error('loaded Shopify UCP command arguments do not match the certified local runtime')
|
|
78
|
+
}
|
|
79
|
+
if (!Array.isArray(actual.blockedTools) || !actual.blockedTools.includes('checkout_complete')) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
'loaded Shopify UCP runtime does not attest the checkout completion deny boundary'
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function resolveRuntimeUpdateConfig(env = process.env) {
|
|
87
|
+
const restartCommand = env.RUNTIME_RESTART_COMMAND || env.OPENCLAW_RESTART_COMMAND || ''
|
|
88
|
+
const readinessCommand = env.RUNTIME_READINESS_COMMAND || ''
|
|
89
|
+
if (restartCommand && !readinessCommand) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
'RUNTIME_READINESS_COMMAND is required with a restart command so the loaded MCP build is verified'
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let readinessTimeoutMs = DEFAULT_RUNTIME_READINESS_TIMEOUT_MS
|
|
96
|
+
if (env.RUNTIME_READINESS_TIMEOUT_MS !== undefined) {
|
|
97
|
+
const candidate = Number(env.RUNTIME_READINESS_TIMEOUT_MS)
|
|
98
|
+
if (!Number.isSafeInteger(candidate) || candidate <= 0) {
|
|
99
|
+
throw new Error('RUNTIME_READINESS_TIMEOUT_MS must be a positive integer')
|
|
100
|
+
}
|
|
101
|
+
readinessTimeoutMs = candidate
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { restartCommand, readinessCommand, readinessTimeoutMs }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function runtimeReadinessExecOptions(timeout) {
|
|
108
|
+
return {
|
|
109
|
+
encoding: 'utf-8',
|
|
110
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
111
|
+
timeout,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function assertSameRuntimeBuild(actual, expected) {
|
|
116
|
+
if (actual.version !== expected.version) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`loaded MCP version ${actual.version} does not match installed CLI ${expected.version}`
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
if (actual.targetEnvironment !== expected.targetEnvironment) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`loaded MCP environment ${actual.targetEnvironment} does not match installed CLI ${expected.targetEnvironment}`
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|