@noodleseed/one 0.164.0 → 0.165.0
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/dist/commands/solutions-coordination.d.ts +5 -0
- package/dist/commands/solutions-coordination.d.ts.map +1 -0
- package/dist/commands/solutions-coordination.js +94 -0
- package/dist/commands/solutions-coordination.js.map +1 -0
- package/dist/commands/solutions-ops.d.ts.map +1 -1
- package/dist/commands/solutions-ops.js +8 -4
- package/dist/commands/solutions-ops.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +2 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +2 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +4 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.d.ts +4 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.d.ts +4 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.js +6 -2
- package/node_modules/@noodle-borg/authoring/dist/config.d.ts +8 -2
- package/node_modules/@noodle-borg/authoring/dist/config.js +28 -1
- package/node_modules/@noodle-borg/authoring/dist/connectors.d.ts +37 -0
- package/node_modules/@noodle-borg/authoring/dist/connectors.js +4 -0
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/authoring/dist/server.js +1 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-flags.js +83 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solutions.js +70 -92
- package/node_modules/@noodle-borg/compiler/dist/artifact/operation-ref.d.ts +2 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +4 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +4 -1
- package/node_modules/@noodle-borg/compiler/dist/catalog/types.d.ts +7 -0
- package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
- package/node_modules/@noodle-borg/compiler/dist/fulfilment-emit.js +15 -2
- package/node_modules/@noodle-borg/compiler/dist/nested-operations.js +71 -0
- package/node_modules/@noodle-borg/compute/dist/code-connector.js +14 -2
- package/node_modules/@noodle-borg/compute/dist/deterministic-helpers.js +138 -0
- package/node_modules/@noodle-borg/compute/dist/quickjs-engine.js +48 -0
- package/node_modules/@noodle-borg/compute/dist/sandbox-eval.js +39 -5
- package/node_modules/@noodle-borg/compute/dist/worker-entry.js +7 -0
- package/node_modules/@noodle-borg/compute/dist/worker-pool.js +14 -3
- package/node_modules/@noodle-borg/connector-defs/dist/compile-http.js +31 -2
- package/node_modules/@noodle-borg/connector-defs/dist/compile.d.ts +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/compile.js +20 -0
- package/node_modules/@noodle-borg/connector-defs/dist/schema.d.ts +84 -0
- package/node_modules/@noodle-borg/connector-defs/dist/schema.js +44 -1
- package/node_modules/@noodle-borg/connector-http/dist/http-connector.d.ts +7 -0
- package/node_modules/@noodle-borg/connector-http/dist/http-connector.js +34 -6
- package/node_modules/@noodle-borg/connector-http/dist/http-response.d.ts +8 -1
- package/node_modules/@noodle-borg/connector-http/dist/http-response.js +19 -10
- package/node_modules/@noodle-borg/connector-http/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +7 -1
- package/node_modules/@noodle-borg/runtime/dist/connector-snapshot.js +8 -0
- package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +2 -0
- package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/runtime/dist/nested-operation-host.js +25 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-coordination-execution.js +50 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-coordination.d.ts +34 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-evidence.d.ts +4 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +173 -94
- package/node_modules/@noodle-borg/service/dist/application-activity.js +152 -18
- package/node_modules/@noodle-borg/service/dist/application-runtime-target.js +15 -2
- package/node_modules/@noodle-borg/service/dist/business-information/definition-resolver.js +19 -0
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-store.js +1 -1
- package/node_modules/@noodle-borg/service/dist/business-information/managed-solution-blueprints.js +7 -0
- package/node_modules/@noodle-borg/service/dist/business-information/managed-solution-executable.js +11 -1
- package/node_modules/@noodle-borg/service/dist/business-information/model.js +2 -2
- package/node_modules/@noodle-borg/service/dist/business-information/portable.js +1 -1
- package/node_modules/@noodle-borg/service/dist/business-information/profiles.js +37 -1
- package/node_modules/@noodle-borg/service/dist/credential-binding-index.js +19 -1
- package/node_modules/@noodle-borg/service/dist/deployment-execution.js +1 -1
- package/node_modules/@noodle-borg/service/dist/operation-coordination.js +185 -0
- package/node_modules/@noodle-borg/service/dist/operation-evidence.js +3 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-activity.js +48 -53
- package/node_modules/@noodle-borg/service/dist/routes/business-information-dispatch.js +2 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-installation.js +51 -17
- package/node_modules/@noodle-borg/service/dist/routes/business-information-paths.js +6 -4
- package/node_modules/@noodle-borg/service/dist/routes/business-information-staff.js +4 -3
- package/node_modules/@noodle-borg/service/dist/routes/business-information.js +11 -6
- package/node_modules/@noodle-borg/service/dist/routes/deploy-public-embed.js +7 -2
- package/node_modules/@noodle-borg/service/dist/service.js +2 -1
- package/node_modules/@noodle-borg/service/dist/solution-installation-activation.js +58 -3
- package/node_modules/@noodle-borg/wire-contracts/dist/business-information.d.ts +52 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/business-information.js +6 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +1 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/operation-coordination.d.ts +59 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/operation-coordination.js +51 -0
- package/package.json +1 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/** Worker-local pure capabilities: bounded arguments and calls, never an ambient host clock. */
|
|
3
|
+
export function installDeterministicHelpers(context) {
|
|
4
|
+
let calls = 0;
|
|
5
|
+
const formatters = new Map();
|
|
6
|
+
const fn = context.newFunction('__explicitHelper', (operationHandle, argsHandle) => {
|
|
7
|
+
try {
|
|
8
|
+
calls += 1;
|
|
9
|
+
if (calls > 4096)
|
|
10
|
+
throw new Error('explicit helper call budget exceeded');
|
|
11
|
+
const operation = context.getString(operationHandle);
|
|
12
|
+
const json = context.getString(argsHandle);
|
|
13
|
+
if (json.length > 16_384)
|
|
14
|
+
throw new Error('explicit helper input exceeds its bound');
|
|
15
|
+
const args = JSON.parse(json);
|
|
16
|
+
if (!Array.isArray(args))
|
|
17
|
+
throw new Error('explicit helper arguments are invalid');
|
|
18
|
+
let result;
|
|
19
|
+
if (operation === 'parse') {
|
|
20
|
+
const value = args[0];
|
|
21
|
+
if (typeof value !== 'string' ||
|
|
22
|
+
value.length > 64 ||
|
|
23
|
+
!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,9})?(?:Z|[+-]\d\d:\d\d)$/.test(value))
|
|
24
|
+
throw new Error('explicit instant is invalid');
|
|
25
|
+
const year = Number(value.slice(0, 4));
|
|
26
|
+
const month = Number(value.slice(5, 7));
|
|
27
|
+
const day = Number(value.slice(8, 10));
|
|
28
|
+
const maxDay = new Date(`${value.slice(0, 7)}-01T00:00:00Z`);
|
|
29
|
+
maxDay.setUTCMonth(month);
|
|
30
|
+
maxDay.setUTCDate(0);
|
|
31
|
+
const millis = Date.parse(value);
|
|
32
|
+
if (!Number.isFinite(millis) ||
|
|
33
|
+
month < 1 ||
|
|
34
|
+
month > 12 ||
|
|
35
|
+
day < 1 ||
|
|
36
|
+
day > maxDay.getUTCDate() ||
|
|
37
|
+
year < 1 ||
|
|
38
|
+
Number(value.slice(11, 13)) > 23 ||
|
|
39
|
+
Number(value.slice(14, 16)) > 59 ||
|
|
40
|
+
Number(value.slice(17, 19)) > 59)
|
|
41
|
+
throw new Error('explicit instant is invalid');
|
|
42
|
+
result = millis;
|
|
43
|
+
}
|
|
44
|
+
else if (operation === 'format' || operation === 'parts') {
|
|
45
|
+
const epoch = args[0];
|
|
46
|
+
if (typeof epoch !== 'number' ||
|
|
47
|
+
!Number.isInteger(epoch) ||
|
|
48
|
+
epoch < -62135596800000 ||
|
|
49
|
+
epoch > 253402300799999)
|
|
50
|
+
throw new Error('explicit instant is invalid');
|
|
51
|
+
if (operation === 'format')
|
|
52
|
+
result = new Date(epoch).toISOString();
|
|
53
|
+
else {
|
|
54
|
+
const zone = args[1];
|
|
55
|
+
if (typeof zone !== 'string' ||
|
|
56
|
+
zone.length < 1 ||
|
|
57
|
+
zone.length > 64 ||
|
|
58
|
+
zone.trim() !== zone)
|
|
59
|
+
throw new Error('explicit time zone is invalid');
|
|
60
|
+
let formatter = formatters.get(zone);
|
|
61
|
+
if (!formatter) {
|
|
62
|
+
if (formatters.size >= 16)
|
|
63
|
+
throw new Error('explicit time zone count exceeds its bound');
|
|
64
|
+
formatter = new Intl.DateTimeFormat('en-US', {
|
|
65
|
+
timeZone: zone,
|
|
66
|
+
weekday: 'short',
|
|
67
|
+
year: 'numeric',
|
|
68
|
+
month: '2-digit',
|
|
69
|
+
day: '2-digit',
|
|
70
|
+
hour: '2-digit',
|
|
71
|
+
minute: '2-digit',
|
|
72
|
+
hourCycle: 'h23',
|
|
73
|
+
});
|
|
74
|
+
formatters.set(zone, formatter);
|
|
75
|
+
}
|
|
76
|
+
const parts = Object.fromEntries(formatter.formatToParts(epoch).map(({ type, value }) => [type, value]));
|
|
77
|
+
const weekday = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].indexOf(parts.weekday ?? '') + 1;
|
|
78
|
+
result = {
|
|
79
|
+
date: `${parts.year}-${parts.month}-${parts.day}`,
|
|
80
|
+
weekday,
|
|
81
|
+
hour: Number(parts.hour),
|
|
82
|
+
minute: Number(parts.minute),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (operation === 'digest') {
|
|
87
|
+
const value = args[0];
|
|
88
|
+
const encoding = args[1] ?? 'hex';
|
|
89
|
+
if (typeof value !== 'string' ||
|
|
90
|
+
value.length > 8192 ||
|
|
91
|
+
(encoding !== 'hex' && encoding !== 'base32hex'))
|
|
92
|
+
throw new Error('explicit digest input is invalid');
|
|
93
|
+
const digest = createHash('sha256').update(value, 'utf8').digest();
|
|
94
|
+
if (encoding === 'hex')
|
|
95
|
+
result = digest.toString('hex');
|
|
96
|
+
else {
|
|
97
|
+
let bits = 0;
|
|
98
|
+
let value = 0;
|
|
99
|
+
let output = '';
|
|
100
|
+
const alphabet = '0123456789abcdefghijklmnopqrstuv';
|
|
101
|
+
for (const byte of digest) {
|
|
102
|
+
value = (value << 8) | byte;
|
|
103
|
+
bits += 8;
|
|
104
|
+
while (bits >= 5) {
|
|
105
|
+
output += alphabet[(value >>> (bits - 5)) & 31];
|
|
106
|
+
bits -= 5;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (bits > 0)
|
|
110
|
+
output += alphabet[(value << (5 - bits)) & 31];
|
|
111
|
+
result = output;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else
|
|
115
|
+
throw new Error('explicit helper is not available');
|
|
116
|
+
return context.newString(JSON.stringify({ ok: true, value: result }));
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return context.newString(JSON.stringify({ ok: false, error: 'invalid or exhausted explicit helper request' }));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
context.setProp(context.global, '__explicitHelper', fn);
|
|
123
|
+
fn.dispose();
|
|
124
|
+
}
|
|
125
|
+
export const helperPrelude = `
|
|
126
|
+
var __helper = function(operation, args) {
|
|
127
|
+
var result = JSON.parse(globalThis.__explicitHelper(operation, JSON.stringify(args)));
|
|
128
|
+
if (!result.ok) throw new Error(result.error);
|
|
129
|
+
return result.value;
|
|
130
|
+
};
|
|
131
|
+
var __timeHelpers = Object.freeze({
|
|
132
|
+
parse: function(value) { return __helper('parse', [value]); },
|
|
133
|
+
format: function(value) { return __helper('format', [value]); },
|
|
134
|
+
parts: function(value, timeZone) { return __helper('parts', [value, timeZone]); }
|
|
135
|
+
});
|
|
136
|
+
var __digestHelper = function(value, encoding) { return __helper('digest', [value, encoding]); };
|
|
137
|
+
`;
|
|
138
|
+
//# sourceMappingURL=deterministic-helpers.js.map
|
|
@@ -111,6 +111,54 @@ class QuickJsComputeInstance {
|
|
|
111
111
|
*/
|
|
112
112
|
function envelopeBridge(host) {
|
|
113
113
|
return {
|
|
114
|
+
...(host.execution === undefined ? {} : { execution: { id: host.execution.id } }),
|
|
115
|
+
...(host.coordination === undefined
|
|
116
|
+
? {}
|
|
117
|
+
: {
|
|
118
|
+
coordination: {
|
|
119
|
+
acquired: host.coordination.acquired,
|
|
120
|
+
...(host.coordination.previous
|
|
121
|
+
? {
|
|
122
|
+
previous: {
|
|
123
|
+
reference: host.coordination.previous.reference,
|
|
124
|
+
operationDigest: host.coordination.previous.operationDigest,
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
: {}),
|
|
128
|
+
},
|
|
129
|
+
}),
|
|
130
|
+
async control(name, argsJson) {
|
|
131
|
+
try {
|
|
132
|
+
const args = JSON.parse(argsJson);
|
|
133
|
+
if (name === 'resolveCoordination' && host.resolveCoordination) {
|
|
134
|
+
await host.resolveCoordination();
|
|
135
|
+
}
|
|
136
|
+
else if (name === 'reportOutcome' &&
|
|
137
|
+
host.reportOutcome &&
|
|
138
|
+
args !== null &&
|
|
139
|
+
typeof args === 'object' &&
|
|
140
|
+
!Array.isArray(args)) {
|
|
141
|
+
const evidence = args;
|
|
142
|
+
if (!['completed', 'rejected', 'unknown'].includes(String(evidence.outcome)) ||
|
|
143
|
+
(evidence.reference !== undefined &&
|
|
144
|
+
(typeof evidence.reference !== 'string' || evidence.reference.length > 1024)))
|
|
145
|
+
throw new Error('invalid execution evidence');
|
|
146
|
+
await host.reportOutcome({
|
|
147
|
+
outcome: evidence.outcome,
|
|
148
|
+
...(typeof evidence.reference === 'string' ? { reference: evidence.reference } : {}),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else
|
|
152
|
+
throw new Error('host control unavailable');
|
|
153
|
+
return JSON.stringify({ ok: true, output: null });
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return JSON.stringify({
|
|
157
|
+
ok: false,
|
|
158
|
+
error: { code: 'host_call_denied', message: 'host control unavailable or invalid' },
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
},
|
|
114
162
|
async callOperation(name, argsJson) {
|
|
115
163
|
const envelope = await (async () => {
|
|
116
164
|
try {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { shouldInterruptAfterDeadline } from 'quickjs-emscripten';
|
|
2
|
+
import { helperPrelude, installDeterministicHelpers } from './deterministic-helpers.js';
|
|
2
3
|
import { ComputeError, } from './engine.js';
|
|
3
4
|
/**
|
|
4
5
|
* Normalize a host-call failure into a bounded error envelope. The code/path pass through only as
|
|
@@ -24,6 +25,7 @@ export function runPure(wasm, source, input, limits) {
|
|
|
24
25
|
runtime.setInterruptHandler(shouldInterruptAfterDeadline(Date.now() + limits.timeoutMs));
|
|
25
26
|
const context = runtime.newContext();
|
|
26
27
|
try {
|
|
28
|
+
installDeterministicHelpers(context);
|
|
27
29
|
const wrapper = buildWrapper(source, input);
|
|
28
30
|
const result = context.evalCode(wrapper, 'module.js');
|
|
29
31
|
if (result.error) {
|
|
@@ -47,10 +49,11 @@ export async function runHosted(wasm, source, input, limits, bridge) {
|
|
|
47
49
|
runtime.setMemoryLimit(limits.memoryBytes);
|
|
48
50
|
runtime.setInterruptHandler(shouldInterruptAfterDeadline(Date.now() + limits.timeoutMs));
|
|
49
51
|
try {
|
|
52
|
+
installDeterministicHelpers(context);
|
|
50
53
|
installHostCall(context, bridge, limits);
|
|
51
54
|
if (bridge.log !== undefined)
|
|
52
55
|
installConsole(context, bridge);
|
|
53
|
-
const result = await context.evalCodeAsync(buildWrapper(source, input, true, bridge.log !== undefined), 'module.js');
|
|
56
|
+
const result = await context.evalCodeAsync(buildWrapper(source, input, true, bridge.log !== undefined, bridge.execution, bridge.coordination), 'module.js');
|
|
54
57
|
if (result.error) {
|
|
55
58
|
const described = describeError(context, result.error);
|
|
56
59
|
result.error.dispose();
|
|
@@ -91,9 +94,14 @@ async function drainPendingJobs(runtime, context) {
|
|
|
91
94
|
* nondeterministic globals so "deterministic by default" holds for the future resume/idempotency
|
|
92
95
|
* model ([ADR 0004](../../../docs/decisions/0004-code-as-sandboxed-connector.md)).
|
|
93
96
|
*/
|
|
94
|
-
function buildWrapper(source, input, hostEnabled = false, consoleEnabled = false) {
|
|
97
|
+
function buildWrapper(source, input, hostEnabled = false, consoleEnabled = false, execution, coordination) {
|
|
95
98
|
const callOperationPrelude = hostEnabled
|
|
96
99
|
? `
|
|
100
|
+
var __control = function(name, args) {
|
|
101
|
+
var result = JSON.parse(globalThis.__hostControl(name, JSON.stringify(args)));
|
|
102
|
+
if (!result.ok) { var error = new Error(result.error.message); error.name = result.error.code; throw error; }
|
|
103
|
+
return result.output;
|
|
104
|
+
};
|
|
97
105
|
var callOperation = function(name, args) {
|
|
98
106
|
var __hostEnvelope = JSON.parse(globalThis.__hostCallOperation(String(name), JSON.stringify(args ?? {})));
|
|
99
107
|
if (!__hostEnvelope.ok) {
|
|
@@ -107,9 +115,16 @@ var callOperation = function(name, args) {
|
|
|
107
115
|
};
|
|
108
116
|
`
|
|
109
117
|
: '';
|
|
110
|
-
const handlerArgs =
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
const handlerArgs = `${jsLiteral(input ?? null)}, Object.freeze({
|
|
119
|
+
time: __timeHelpers, digest: __digestHelper,
|
|
120
|
+
${hostEnabled
|
|
121
|
+
? `callOperation: callOperation,
|
|
122
|
+
reportOutcome: function(evidence) { return __control('reportOutcome', evidence); },
|
|
123
|
+
resolveCoordination: function() { return __control('resolveCoordination', {}); },`
|
|
124
|
+
: ''}
|
|
125
|
+
${coordination === undefined ? '' : `coordination: Object.freeze(${jsLiteral(coordination)}),`}
|
|
126
|
+
${execution === undefined ? '' : `execution: Object.freeze(${jsLiteral(execution)}),`}
|
|
127
|
+
})`;
|
|
113
128
|
const consolePrelude = consoleEnabled
|
|
114
129
|
? `
|
|
115
130
|
var console = Object.freeze({
|
|
@@ -123,7 +138,9 @@ var console = Object.freeze({
|
|
|
123
138
|
: '';
|
|
124
139
|
const bodyPrefix = `"use strict";
|
|
125
140
|
delete globalThis.Date;
|
|
141
|
+
delete globalThis.Intl;
|
|
126
142
|
delete Math.random;
|
|
143
|
+
${helperPrelude}
|
|
127
144
|
${callOperationPrelude}
|
|
128
145
|
${consolePrelude}
|
|
129
146
|
var __handler = (${source});
|
|
@@ -141,6 +158,23 @@ var __out = JSON.stringify(__handler(${handlerArgs}));
|
|
|
141
158
|
}
|
|
142
159
|
function installHostCall(context, bridge, limits) {
|
|
143
160
|
let calls = 0;
|
|
161
|
+
let controls = 0;
|
|
162
|
+
const control = context.newAsyncifiedFunction('__hostControl', async (name, args) => {
|
|
163
|
+
if (++controls > 4 || !bridge.control)
|
|
164
|
+
return context.newString(JSON.stringify({
|
|
165
|
+
ok: false,
|
|
166
|
+
error: { code: 'host_call_denied', message: 'host control budget exceeded' },
|
|
167
|
+
}));
|
|
168
|
+
const argsJson = context.getString(args);
|
|
169
|
+
if (argsJson.length > 2048)
|
|
170
|
+
return context.newString(JSON.stringify({
|
|
171
|
+
ok: false,
|
|
172
|
+
error: { code: 'host_call_denied', message: 'host control input exceeds bound' },
|
|
173
|
+
}));
|
|
174
|
+
return context.newString(await bridge.control(context.getString(name), argsJson));
|
|
175
|
+
});
|
|
176
|
+
context.setProp(context.global, '__hostControl', control);
|
|
177
|
+
control.dispose();
|
|
144
178
|
const fn = context.newAsyncifiedFunction('__hostCallOperation', async (nameHandle, argsJsonHandle) => {
|
|
145
179
|
const envelopeJson = await (async () => {
|
|
146
180
|
if (calls >= limits.maxHostCalls) {
|
|
@@ -29,6 +29,13 @@ function startWorker(port) {
|
|
|
29
29
|
});
|
|
30
30
|
function bridgeFor(run) {
|
|
31
31
|
return {
|
|
32
|
+
...(run.execution === undefined ? {} : { execution: run.execution }),
|
|
33
|
+
...(run.coordination === undefined ? {} : { coordination: run.coordination }),
|
|
34
|
+
control: (name, argsJson) => new Promise((resolve) => {
|
|
35
|
+
const callId = nextCallId++;
|
|
36
|
+
pendingHostReplies.set(callId, resolve);
|
|
37
|
+
post({ t: 'host_control', id: run.id, callId, name, argsJson });
|
|
38
|
+
}),
|
|
32
39
|
callOperation: (name, argsJson) => new Promise((resolve) => {
|
|
33
40
|
const callId = nextCallId;
|
|
34
41
|
nextCallId += 1;
|
|
@@ -107,6 +107,8 @@ export class ComputeWorkerPool {
|
|
|
107
107
|
limits: { ...job.limits, timeoutMs: remainingMs },
|
|
108
108
|
hostEnabled: job.host !== undefined,
|
|
109
109
|
consoleEnabled: job.consoleEnabled,
|
|
110
|
+
...(job.host?.execution === undefined ? {} : { execution: job.host.execution }),
|
|
111
|
+
...(job.host?.coordination === undefined ? {} : { coordination: job.host.coordination }),
|
|
110
112
|
};
|
|
111
113
|
slot.worker.postMessage(message);
|
|
112
114
|
}
|
|
@@ -129,7 +131,7 @@ export class ComputeWorkerPool {
|
|
|
129
131
|
this.#release(slot);
|
|
130
132
|
return;
|
|
131
133
|
}
|
|
132
|
-
if (message.t === 'host_call') {
|
|
134
|
+
if (message.t === 'host_call' || message.t === 'host_control') {
|
|
133
135
|
const host = active.job.host;
|
|
134
136
|
const respond = (envelopeJson) => {
|
|
135
137
|
if (slot.active === active && !active.settled && !slot.dropped) {
|
|
@@ -148,8 +150,15 @@ export class ComputeWorkerPool {
|
|
|
148
150
|
}));
|
|
149
151
|
return;
|
|
150
152
|
}
|
|
151
|
-
host
|
|
152
|
-
|
|
153
|
+
const invoke = message.t === 'host_control' ? host.control : host.callOperation;
|
|
154
|
+
if (!invoke) {
|
|
155
|
+
respond(JSON.stringify({
|
|
156
|
+
ok: false,
|
|
157
|
+
error: { code: 'host_call_denied', message: 'host control unavailable' },
|
|
158
|
+
}));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
invoke(message.name, message.argsJson)
|
|
153
162
|
.then(respond)
|
|
154
163
|
.catch(() => respond(JSON.stringify({
|
|
155
164
|
ok: false,
|
|
@@ -157,6 +166,8 @@ export class ComputeWorkerPool {
|
|
|
157
166
|
})));
|
|
158
167
|
return;
|
|
159
168
|
}
|
|
169
|
+
if (message.t !== 'log')
|
|
170
|
+
return;
|
|
160
171
|
// 'log': deliver to the sink (fire-and-forget failures) and always release the sandbox.
|
|
161
172
|
const ack = () => {
|
|
162
173
|
if (slot.active === active && !active.settled && !slot.dropped) {
|
|
@@ -139,7 +139,7 @@ export function compileHttpConnector(def, mode, errors, catalog, connectors, sec
|
|
|
139
139
|
const pagination = compilePagination(op, `${base}.pagination`, errors);
|
|
140
140
|
const projection = compileProjection(op, `${base}.projection`, errors);
|
|
141
141
|
const fake = httpFake(op.fake);
|
|
142
|
-
operations[opName] = buildOperation(op, signature, requestAst, responseAst, headersAst, pagination, fake, projection, evidenceAst);
|
|
142
|
+
operations[opName] = buildOperation(op, signature, requestAst, responseAst, headersAst, pagination, fake, projection, evidenceAst, compileStatusResponses(op.responses, `${base}.responses`, errors));
|
|
143
143
|
// Per-operation auth declares its own secret reference (overrides any connector-level default).
|
|
144
144
|
addAuthBinding({
|
|
145
145
|
auth: op.auth,
|
|
@@ -268,7 +268,7 @@ function compileRequest(request, path, errors) {
|
|
|
268
268
|
function isRecord(value) {
|
|
269
269
|
return value !== null && typeof value === 'object';
|
|
270
270
|
}
|
|
271
|
-
function buildOperation(op, signature, requestAst, responseAst, headersAst, pagination, fake, projection, evidenceAst) {
|
|
271
|
+
function buildOperation(op, signature, requestAst, responseAst, headersAst, pagination, fake, projection, evidenceAst, responses) {
|
|
272
272
|
const resilience = httpResilience(op.resilience);
|
|
273
273
|
return {
|
|
274
274
|
method: op.method ?? 'GET',
|
|
@@ -287,6 +287,7 @@ function buildOperation(op, signature, requestAst, responseAst, headersAst, pagi
|
|
|
287
287
|
...(op.limits !== undefined ? { maxResponseBytes: op.limits.maxResponseBytes } : {}),
|
|
288
288
|
...(op.responseType !== undefined ? { responseType: op.responseType } : {}),
|
|
289
289
|
...(op.requestEncoding !== undefined ? { requestEncoding: op.requestEncoding } : {}),
|
|
290
|
+
...(responses !== undefined ? { responses } : {}),
|
|
290
291
|
...(evidenceAst === undefined
|
|
291
292
|
? {}
|
|
292
293
|
: {
|
|
@@ -321,6 +322,34 @@ function buildOperation(op, signature, requestAst, responseAst, headersAst, pagi
|
|
|
321
322
|
: {}),
|
|
322
323
|
};
|
|
323
324
|
}
|
|
325
|
+
function compileStatusResponses(responses, path, errors) {
|
|
326
|
+
if (responses === undefined)
|
|
327
|
+
return undefined;
|
|
328
|
+
return Object.fromEntries(Object.entries(responses).map(([status, response]) => {
|
|
329
|
+
const responseAst = compileExprMap(response.response, RESPONSE_ROOTS, `${path}.${status}.response`, errors);
|
|
330
|
+
const evidenceAst = response.evidence === undefined
|
|
331
|
+
? undefined
|
|
332
|
+
: compileExprMap(response.evidence, new Set(['response']), `${path}.${status}.evidence`, errors);
|
|
333
|
+
return [
|
|
334
|
+
status,
|
|
335
|
+
{
|
|
336
|
+
...(response.responseType === undefined ? {} : { responseType: response.responseType }),
|
|
337
|
+
mapResponse: (json, args) => evalMap(responseAst, { args, response: json }),
|
|
338
|
+
...(evidenceAst === undefined
|
|
339
|
+
? {}
|
|
340
|
+
: {
|
|
341
|
+
evidence: (json) => {
|
|
342
|
+
const value = evalMap(evidenceAst, { response: json });
|
|
343
|
+
return {
|
|
344
|
+
outcome: value.outcome === 'rejected' ? 'rejected' : 'unknown',
|
|
345
|
+
...(typeof value.reference === 'string' ? { reference: value.reference } : {}),
|
|
346
|
+
};
|
|
347
|
+
},
|
|
348
|
+
}),
|
|
349
|
+
},
|
|
350
|
+
];
|
|
351
|
+
}));
|
|
352
|
+
}
|
|
324
353
|
function usesExecutionIdentity(node) {
|
|
325
354
|
if (node.kind === 'path')
|
|
326
355
|
return node.root === 'execution';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CatalogConnector } from '@noodle-borg/compiler';
|
|
2
2
|
import type { Connector } from '@noodle-borg/runtime';
|
|
3
|
-
import type
|
|
3
|
+
import { type ConnectorCompileError } from './compile-expr.js';
|
|
4
4
|
export type { ConnectorCompileError } from './compile-expr.js';
|
|
5
5
|
/**
|
|
6
6
|
* A binding from a connector operation to the *named* secret its auth scheme needs. It carries the
|
|
@@ -2,6 +2,7 @@ import { computeSignatureHash, } from '@noodle-borg/compiler';
|
|
|
2
2
|
import { CodeConnector, DEFAULT_LIMITS, QuickJsComputeEngine, } from '@noodle-borg/compute';
|
|
3
3
|
import { parse as parseYaml } from 'yaml';
|
|
4
4
|
import { validateComputeCallGraph } from './call-graph.js';
|
|
5
|
+
import { compileExpr } from './compile-expr.js';
|
|
5
6
|
import { compileHttpConnector, toSignature } from './compile-http.js';
|
|
6
7
|
import { compileMcpConnector } from './compile-mcp.js';
|
|
7
8
|
import { catalogCredentialMetadata } from './credential-profiles.js';
|
|
@@ -119,6 +120,16 @@ function compileComputeConnector(def, engine, targets, errors, catalog, connecto
|
|
|
119
120
|
module: engine.registerSource(op.code),
|
|
120
121
|
limits,
|
|
121
122
|
...(calls ? { calls } : {}),
|
|
123
|
+
...(op.coordination
|
|
124
|
+
? {
|
|
125
|
+
coordination: {
|
|
126
|
+
connectionId: op.coordination.connectionId,
|
|
127
|
+
namespace: op.coordination.namespace,
|
|
128
|
+
key: compileExpr(op.coordination.key, new Set(['args', 'env', 'execution']), `${base}.coordination.key`, errors),
|
|
129
|
+
reference: compileExpr(op.coordination.reference, new Set(['args', 'env', 'execution']), `${base}.coordination.reference`, errors),
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
: {}),
|
|
122
133
|
};
|
|
123
134
|
}
|
|
124
135
|
catalog.push({
|
|
@@ -127,6 +138,15 @@ function compileComputeConnector(def, engine, targets, errors, catalog, connecto
|
|
|
127
138
|
kind: def.kind ?? 'custom',
|
|
128
139
|
...catalogCredentialMetadata(def),
|
|
129
140
|
operations: signatures,
|
|
141
|
+
operationCalls: Object.fromEntries(Object.entries(operations).map(([name, operation]) => [
|
|
142
|
+
name,
|
|
143
|
+
Object.values(operation.calls ?? {}).map(({ connectorId, connectorVersion, operation, signatureHash }) => ({
|
|
144
|
+
connectorId,
|
|
145
|
+
connectorVersion,
|
|
146
|
+
operation,
|
|
147
|
+
signatureHash,
|
|
148
|
+
})),
|
|
149
|
+
])),
|
|
130
150
|
});
|
|
131
151
|
connectors.push(new CodeConnector({ id: def.id, version: def.version, engine, operations }));
|
|
132
152
|
}
|
|
@@ -99,6 +99,21 @@ declare const httpOperationSchema: z.ZodObject<{
|
|
|
99
99
|
"form-urlencoded": "form-urlencoded";
|
|
100
100
|
}>>;
|
|
101
101
|
response: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
102
|
+
responses: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
103
|
+
response: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
104
|
+
responseType: z.ZodOptional<z.ZodEnum<{
|
|
105
|
+
json: "json";
|
|
106
|
+
text: "text";
|
|
107
|
+
empty: "empty";
|
|
108
|
+
}>>;
|
|
109
|
+
evidence: z.ZodOptional<z.ZodObject<{
|
|
110
|
+
outcome: z.ZodEnum<{
|
|
111
|
+
unknown: "unknown";
|
|
112
|
+
rejected: "rejected";
|
|
113
|
+
}>;
|
|
114
|
+
reference: z.ZodOptional<z.ZodString>;
|
|
115
|
+
}, z.core.$strict>>;
|
|
116
|
+
}, z.core.$strict>>>;
|
|
102
117
|
evidence: z.ZodOptional<z.ZodObject<{
|
|
103
118
|
outcome: z.ZodString;
|
|
104
119
|
reference: z.ZodOptional<z.ZodString>;
|
|
@@ -340,6 +355,12 @@ declare const computeOperationSchema: z.ZodObject<{
|
|
|
340
355
|
maxHostCalls: z.ZodOptional<z.ZodNumber>;
|
|
341
356
|
}, z.core.$strict>>;
|
|
342
357
|
calls: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
358
|
+
coordination: z.ZodOptional<z.ZodObject<{
|
|
359
|
+
connectionId: z.ZodString;
|
|
360
|
+
namespace: z.ZodString;
|
|
361
|
+
key: z.ZodString;
|
|
362
|
+
reference: z.ZodString;
|
|
363
|
+
}, z.core.$strict>>;
|
|
343
364
|
credentials: z.ZodOptional<z.ZodObject<{
|
|
344
365
|
profiles: z.ZodArray<z.ZodString>;
|
|
345
366
|
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -453,6 +474,21 @@ declare const httpConnectorDefSchema: z.ZodObject<{
|
|
|
453
474
|
"form-urlencoded": "form-urlencoded";
|
|
454
475
|
}>>;
|
|
455
476
|
response: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
477
|
+
responses: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
478
|
+
response: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
479
|
+
responseType: z.ZodOptional<z.ZodEnum<{
|
|
480
|
+
json: "json";
|
|
481
|
+
text: "text";
|
|
482
|
+
empty: "empty";
|
|
483
|
+
}>>;
|
|
484
|
+
evidence: z.ZodOptional<z.ZodObject<{
|
|
485
|
+
outcome: z.ZodEnum<{
|
|
486
|
+
unknown: "unknown";
|
|
487
|
+
rejected: "rejected";
|
|
488
|
+
}>;
|
|
489
|
+
reference: z.ZodOptional<z.ZodString>;
|
|
490
|
+
}, z.core.$strict>>;
|
|
491
|
+
}, z.core.$strict>>>;
|
|
456
492
|
evidence: z.ZodOptional<z.ZodObject<{
|
|
457
493
|
outcome: z.ZodString;
|
|
458
494
|
reference: z.ZodOptional<z.ZodString>;
|
|
@@ -796,6 +832,12 @@ declare const computeConnectorDefSchema: z.ZodObject<{
|
|
|
796
832
|
maxHostCalls: z.ZodOptional<z.ZodNumber>;
|
|
797
833
|
}, z.core.$strict>>;
|
|
798
834
|
calls: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
835
|
+
coordination: z.ZodOptional<z.ZodObject<{
|
|
836
|
+
connectionId: z.ZodString;
|
|
837
|
+
namespace: z.ZodString;
|
|
838
|
+
key: z.ZodString;
|
|
839
|
+
reference: z.ZodString;
|
|
840
|
+
}, z.core.$strict>>;
|
|
799
841
|
credentials: z.ZodOptional<z.ZodObject<{
|
|
800
842
|
profiles: z.ZodArray<z.ZodString>;
|
|
801
843
|
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -927,6 +969,21 @@ declare const connectorDefSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
927
969
|
"form-urlencoded": "form-urlencoded";
|
|
928
970
|
}>>;
|
|
929
971
|
response: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
972
|
+
responses: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
973
|
+
response: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
974
|
+
responseType: z.ZodOptional<z.ZodEnum<{
|
|
975
|
+
json: "json";
|
|
976
|
+
text: "text";
|
|
977
|
+
empty: "empty";
|
|
978
|
+
}>>;
|
|
979
|
+
evidence: z.ZodOptional<z.ZodObject<{
|
|
980
|
+
outcome: z.ZodEnum<{
|
|
981
|
+
unknown: "unknown";
|
|
982
|
+
rejected: "rejected";
|
|
983
|
+
}>;
|
|
984
|
+
reference: z.ZodOptional<z.ZodString>;
|
|
985
|
+
}, z.core.$strict>>;
|
|
986
|
+
}, z.core.$strict>>>;
|
|
930
987
|
evidence: z.ZodOptional<z.ZodObject<{
|
|
931
988
|
outcome: z.ZodString;
|
|
932
989
|
reference: z.ZodOptional<z.ZodString>;
|
|
@@ -1266,6 +1323,12 @@ declare const connectorDefSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
1266
1323
|
maxHostCalls: z.ZodOptional<z.ZodNumber>;
|
|
1267
1324
|
}, z.core.$strict>>;
|
|
1268
1325
|
calls: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
1326
|
+
coordination: z.ZodOptional<z.ZodObject<{
|
|
1327
|
+
connectionId: z.ZodString;
|
|
1328
|
+
namespace: z.ZodString;
|
|
1329
|
+
key: z.ZodString;
|
|
1330
|
+
reference: z.ZodString;
|
|
1331
|
+
}, z.core.$strict>>;
|
|
1269
1332
|
credentials: z.ZodOptional<z.ZodObject<{
|
|
1270
1333
|
profiles: z.ZodArray<z.ZodString>;
|
|
1271
1334
|
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -1394,6 +1457,21 @@ export declare const connectorFileSchema: z.ZodObject<{
|
|
|
1394
1457
|
"form-urlencoded": "form-urlencoded";
|
|
1395
1458
|
}>>;
|
|
1396
1459
|
response: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
1460
|
+
responses: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
1461
|
+
response: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
1462
|
+
responseType: z.ZodOptional<z.ZodEnum<{
|
|
1463
|
+
json: "json";
|
|
1464
|
+
text: "text";
|
|
1465
|
+
empty: "empty";
|
|
1466
|
+
}>>;
|
|
1467
|
+
evidence: z.ZodOptional<z.ZodObject<{
|
|
1468
|
+
outcome: z.ZodEnum<{
|
|
1469
|
+
unknown: "unknown";
|
|
1470
|
+
rejected: "rejected";
|
|
1471
|
+
}>;
|
|
1472
|
+
reference: z.ZodOptional<z.ZodString>;
|
|
1473
|
+
}, z.core.$strict>>;
|
|
1474
|
+
}, z.core.$strict>>>;
|
|
1397
1475
|
evidence: z.ZodOptional<z.ZodObject<{
|
|
1398
1476
|
outcome: z.ZodString;
|
|
1399
1477
|
reference: z.ZodOptional<z.ZodString>;
|
|
@@ -1733,6 +1811,12 @@ export declare const connectorFileSchema: z.ZodObject<{
|
|
|
1733
1811
|
maxHostCalls: z.ZodOptional<z.ZodNumber>;
|
|
1734
1812
|
}, z.core.$strict>>;
|
|
1735
1813
|
calls: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
1814
|
+
coordination: z.ZodOptional<z.ZodObject<{
|
|
1815
|
+
connectionId: z.ZodString;
|
|
1816
|
+
namespace: z.ZodString;
|
|
1817
|
+
key: z.ZodString;
|
|
1818
|
+
reference: z.ZodString;
|
|
1819
|
+
}, z.core.$strict>>;
|
|
1736
1820
|
credentials: z.ZodOptional<z.ZodObject<{
|
|
1737
1821
|
profiles: z.ZodArray<z.ZodString>;
|
|
1738
1822
|
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -308,6 +308,25 @@ const httpOperationSchema = z
|
|
|
308
308
|
/** How to encode a non-GET request mapping. JSON remains the default. */
|
|
309
309
|
requestEncoding: z.enum(['json', 'form-urlencoded']).optional(),
|
|
310
310
|
response: exprMapSchema.optional(),
|
|
311
|
+
/** Explicit expected rejection outcomes. Authentication, redirects, rate limits and 5xx fail closed. */
|
|
312
|
+
responses: z
|
|
313
|
+
.record(z
|
|
314
|
+
.string()
|
|
315
|
+
.regex(/^4\d\d$/u)
|
|
316
|
+
.refine((status) => !['401', '403', '429'].includes(status)), z
|
|
317
|
+
.object({
|
|
318
|
+
response: exprMapSchema,
|
|
319
|
+
responseType: z.enum(['json', 'text', 'empty']).optional(),
|
|
320
|
+
evidence: z
|
|
321
|
+
.object({
|
|
322
|
+
outcome: z.enum(['rejected', 'unknown']),
|
|
323
|
+
reference: z.string().min(1).optional(),
|
|
324
|
+
})
|
|
325
|
+
.strict()
|
|
326
|
+
.optional(),
|
|
327
|
+
})
|
|
328
|
+
.strict())
|
|
329
|
+
.optional(),
|
|
311
330
|
/** Explicit provider evidence mapping; never infer completion from an HTTP status or tool name. */
|
|
312
331
|
evidence: z
|
|
313
332
|
.object({ outcome: z.string().min(1), reference: z.string().min(1).optional() })
|
|
@@ -339,6 +358,13 @@ const httpOperationSchema = z
|
|
|
339
358
|
})
|
|
340
359
|
.strict()
|
|
341
360
|
.superRefine((operation, ctx) => {
|
|
361
|
+
if (operation.responses !== undefined && operation.pagination !== undefined) {
|
|
362
|
+
ctx.addIssue({
|
|
363
|
+
code: z.ZodIssueCode.custom,
|
|
364
|
+
path: ['responses'],
|
|
365
|
+
message: 'HTTP response status mappings cannot be combined with pagination',
|
|
366
|
+
});
|
|
367
|
+
}
|
|
342
368
|
if (operation.requestEncoding !== 'form-urlencoded')
|
|
343
369
|
return;
|
|
344
370
|
if (operation.method === undefined || operation.method === 'GET') {
|
|
@@ -426,9 +452,26 @@ const computeOperationSchema = z
|
|
|
426
452
|
code: z.string().min(1),
|
|
427
453
|
limits: computeLimitsSchema.optional(),
|
|
428
454
|
calls: z.record(z.string(), z.string().min(1)).optional(),
|
|
455
|
+
coordination: z
|
|
456
|
+
.object({
|
|
457
|
+
connectionId: z.string().regex(/^[A-Za-z0-9_-]{1,128}$/),
|
|
458
|
+
namespace: z.string().regex(/^[A-Za-z0-9_-]{1,80}$/),
|
|
459
|
+
key: z.string().min(1).max(4096),
|
|
460
|
+
reference: z.string().min(1).max(4096),
|
|
461
|
+
})
|
|
462
|
+
.strict()
|
|
463
|
+
.optional(),
|
|
429
464
|
credentials: operationCredentialsSchema.optional(),
|
|
430
465
|
})
|
|
431
|
-
.strict()
|
|
466
|
+
.strict()
|
|
467
|
+
.superRefine((operation, ctx) => {
|
|
468
|
+
if (operation.coordination && operation.type !== 'action')
|
|
469
|
+
ctx.addIssue({
|
|
470
|
+
code: 'custom',
|
|
471
|
+
path: ['coordination'],
|
|
472
|
+
message: 'Coordination requires an action operation',
|
|
473
|
+
});
|
|
474
|
+
});
|
|
432
475
|
const managedVariableExpressionSchema = z
|
|
433
476
|
.string()
|
|
434
477
|
.regex(/^\$\{env\.[A-Za-z0-9_]+\}$/, 'must be an exact managed variable expression');
|