@noodleseed/one 0.163.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/banner.d.ts +6 -0
- package/dist/banner.d.ts.map +1 -0
- package/dist/banner.js +29 -0
- package/dist/banner.js.map +1 -0
- package/dist/commands/project-setup.d.ts.map +1 -1
- package/dist/commands/project-setup.js +2 -0
- package/dist/commands/project-setup.js.map +1 -1
- 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/dist/first-run.d.ts.map +1 -1
- package/dist/first-run.js +2 -0
- package/dist/first-run.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/index.d.ts +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/index.js +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/plugin-bootstrap-skill.js +10 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +4 -1
- package/node_modules/@noodle-borg/agent-kit/dist/welcome-wordmark.d.ts +3 -0
- package/node_modules/@noodle-borg/agent-kit/dist/welcome-wordmark.js +10 -0
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -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/assistant-gateway/package.json +1 -1
- 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/control-plane/dist/contracts.d.ts +2 -0
- package/node_modules/@noodle-borg/control-plane/dist/deploy-gates.js +1 -0
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +2 -0
- 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/control-plane.js +14 -1
- 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/service/package.json +1 -1
- 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/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
|
@@ -3,8 +3,10 @@ import { sanitizeConnectorFailureDetails } from './connector/failure-details.js'
|
|
|
3
3
|
import { isConnectorInvocationError } from './connector/types.js';
|
|
4
4
|
import { preflightOperationCustomerRoutes, resolveCustomerActionRouteBindings, resolveCustomerConnectorRoute, } from './customer-routing.js';
|
|
5
5
|
import { ExpressionEvalError } from './eval/evaluate.js';
|
|
6
|
+
import { nestedOperationHost } from './nested-operation-host.js';
|
|
7
|
+
import { admitCoordinatedAction, beginOperationCoordination, coordinatedParentId, } from './operation-coordination-execution.js';
|
|
6
8
|
import { acquireOperationCredential } from './operation-credential.js';
|
|
7
|
-
import { withOperationEvidence } from './operation-evidence.js';
|
|
9
|
+
import { safeOperationEvidence, withOperationEvidence } from './operation-evidence.js';
|
|
8
10
|
import { checkSignature, evalExprMap, validateAgainstSchema } from './operation-validation.js';
|
|
9
11
|
import { samePreparedAction } from './prepared-action-match.js';
|
|
10
12
|
import { splitResultMeta } from './result-meta.js';
|
|
@@ -116,6 +118,8 @@ async function invokeOperation(ref, args, sig, toolName, deps, policy, host, env
|
|
|
116
118
|
const initialCancellation = executionCancellation(deps.signal, beforeDispatch);
|
|
117
119
|
if (initialCancellation)
|
|
118
120
|
return { ok: false, error: initialCancellation };
|
|
121
|
+
if (sig.type === 'action' && !admitCoordinatedAction(host))
|
|
122
|
+
return fail('policy_denied', 'This coordinated invocation cannot dispatch another external action.');
|
|
119
123
|
const callKey = operationKey(ref);
|
|
120
124
|
if (hostCallStack(host).includes(callKey)) {
|
|
121
125
|
return fail('circular_call_dependency', `circular connector operation call detected at ${callKey}`, outputPath);
|
|
@@ -155,6 +159,9 @@ async function invokeOperation(ref, args, sig, toolName, deps, policy, host, env
|
|
|
155
159
|
const acquired = await acquireOperationCredential(ref, deps, customerRoute);
|
|
156
160
|
if (!acquired.ok)
|
|
157
161
|
return { ok: false, error: acquired.error };
|
|
162
|
+
const afterCredential = executionCancellation(deps.signal, beforeDispatch);
|
|
163
|
+
if (afterCredential)
|
|
164
|
+
return { ok: false, error: afterCredential };
|
|
158
165
|
const { credential } = acquired;
|
|
159
166
|
let connectorArgs;
|
|
160
167
|
try {
|
|
@@ -169,6 +176,8 @@ async function invokeOperation(ref, args, sig, toolName, deps, policy, host, env
|
|
|
169
176
|
const admitted = admittedExecutionSignal(deps.signal);
|
|
170
177
|
disposeSignal = admitted.dispose;
|
|
171
178
|
const execution = operationIdentity(host, deps, toolName, ref, argsPath);
|
|
179
|
+
const parentId = coordinatedParentId(host);
|
|
180
|
+
const coordinationConnection = connector.coordination?.(ref.operation)?.connectionId;
|
|
172
181
|
const executionBoundMs = connector.executionBoundMs?.(ref.operation);
|
|
173
182
|
const deadline = executionBoundMs === undefined ? undefined : AbortSignal.timeout(executionBoundMs);
|
|
174
183
|
const signal = deadline === undefined
|
|
@@ -178,6 +187,8 @@ async function invokeOperation(ref, args, sig, toolName, deps, policy, host, env
|
|
|
178
187
|
: AbortSignal.any([deadline, admitted.signal]);
|
|
179
188
|
return await withOperationEvidence(sig.type === 'action' ? deps.operationEvidence : undefined, {
|
|
180
189
|
id: execution.id,
|
|
190
|
+
...(parentId === undefined ? {} : { parentId }),
|
|
191
|
+
...(coordinationConnection === undefined ? {} : { connectionId: coordinationConnection }),
|
|
181
192
|
tool: toolName,
|
|
182
193
|
operation: ref,
|
|
183
194
|
arguments: connectorArgs,
|
|
@@ -187,108 +198,158 @@ async function invokeOperation(ref, args, sig, toolName, deps, policy, host, env
|
|
|
187
198
|
: { executionRevision: deps.executionBinding.revision }),
|
|
188
199
|
...(executionBoundMs === undefined ? {} : { executionBoundMs }),
|
|
189
200
|
}, async (reportOutcome) => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
...(signal === undefined ? {} : { signal }),
|
|
204
|
-
credential,
|
|
205
|
-
acquireTransportCredential: async () => {
|
|
206
|
-
const { credentialBinding: _accountBinding, ...transportRef } = ref;
|
|
207
|
-
const transport = await acquireOperationCredential(transportRef, deps, customerRoute);
|
|
208
|
-
if (!transport.ok)
|
|
209
|
-
throw new Error('Independent transport credential unavailable');
|
|
210
|
-
return transport.credential;
|
|
211
|
-
},
|
|
212
|
-
...(ref.credentialBinding === undefined
|
|
213
|
-
? {}
|
|
214
|
-
: { credentialPresentation: ref.credentialBinding.presentation }),
|
|
215
|
-
...(deps.caller !== undefined ? { caller: deps.caller } : {}),
|
|
216
|
-
...(customerRoute === undefined ? {} : { route: customerRoute }),
|
|
217
|
-
host,
|
|
218
|
-
...(deps.trace === undefined ? {} : { trace: deps.trace }),
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
catch (error) {
|
|
222
|
-
const hostCall = hostCallErrorSnapshot(error);
|
|
223
|
-
if (hostCall !== undefined) {
|
|
224
|
-
return fail(hostCall.code, hostCall.message, hostCall.path);
|
|
225
|
-
}
|
|
226
|
-
if (isConnectorInvocationError(error)) {
|
|
227
|
-
const failureDetails = sanitizeConnectorFailureDetails(error, {
|
|
228
|
-
includeResponseExcerpt: customerRoute === undefined,
|
|
201
|
+
const declaration = connector.coordination?.(ref.operation);
|
|
202
|
+
let coordinated;
|
|
203
|
+
let outcome;
|
|
204
|
+
if (declaration) {
|
|
205
|
+
try {
|
|
206
|
+
coordinated = await beginOperationCoordination({
|
|
207
|
+
declaration,
|
|
208
|
+
executionId: execution.id,
|
|
209
|
+
executionBoundMs,
|
|
210
|
+
args: connectorArgs,
|
|
211
|
+
env,
|
|
212
|
+
deps,
|
|
213
|
+
host,
|
|
229
214
|
});
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
reportOutcome({ outcome: 'rejected' });
|
|
218
|
+
return fail('execution_admission_error', 'External operation coordination unavailable; no action dispatched.');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
let output;
|
|
223
|
+
try {
|
|
224
|
+
if (signal?.aborted)
|
|
225
|
+
return fail('execution_cancelled', 'Execution deadline elapsed before connector dispatch.');
|
|
226
|
+
output = await connector.invoke({
|
|
234
227
|
operation: ref.operation,
|
|
235
|
-
|
|
228
|
+
execution,
|
|
229
|
+
reportOutcome: (value) => {
|
|
230
|
+
outcome = safeOperationEvidence(value);
|
|
231
|
+
reportOutcome(outcome);
|
|
232
|
+
},
|
|
233
|
+
...(coordinated === undefined
|
|
234
|
+
? {}
|
|
235
|
+
: {
|
|
236
|
+
coordination: {
|
|
237
|
+
acquired: coordinated.lease.acquired,
|
|
238
|
+
...(coordinated.lease.previous
|
|
239
|
+
? { previous: coordinated.lease.previous }
|
|
240
|
+
: {}),
|
|
241
|
+
},
|
|
242
|
+
resolveCoordination: () => {
|
|
243
|
+
if (signal?.aborted)
|
|
244
|
+
return Promise.reject(new Error('Coordination recovery expired'));
|
|
245
|
+
return coordinated.lease.resolvePrevious();
|
|
246
|
+
},
|
|
247
|
+
}),
|
|
248
|
+
...(deps.publicAdmission === undefined
|
|
249
|
+
? {}
|
|
250
|
+
: { publicAdmission: deps.publicAdmission }),
|
|
251
|
+
args: connectorArgs,
|
|
252
|
+
env,
|
|
253
|
+
...(signal === undefined ? {} : { signal }),
|
|
254
|
+
credential,
|
|
255
|
+
acquireTransportCredential: async () => {
|
|
256
|
+
const { credentialBinding: _accountBinding, ...transportRef } = ref;
|
|
257
|
+
const transport = await acquireOperationCredential(transportRef, deps, customerRoute);
|
|
258
|
+
if (!transport.ok)
|
|
259
|
+
throw new Error('Independent transport credential unavailable');
|
|
260
|
+
return transport.credential;
|
|
261
|
+
},
|
|
262
|
+
...(ref.credentialBinding === undefined
|
|
263
|
+
? {}
|
|
264
|
+
: { credentialPresentation: ref.credentialBinding.presentation }),
|
|
265
|
+
...(deps.caller !== undefined ? { caller: deps.caller } : {}),
|
|
266
|
+
...(customerRoute === undefined ? {} : { route: customerRoute }),
|
|
267
|
+
host: nestedOperationHost(ref, host, signal),
|
|
268
|
+
...(deps.trace === undefined ? {} : { trace: deps.trace }),
|
|
236
269
|
});
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
const hostCall = hostCallErrorSnapshot(error);
|
|
273
|
+
if (hostCall !== undefined) {
|
|
274
|
+
return fail(hostCall.code, hostCall.message, hostCall.path);
|
|
275
|
+
}
|
|
276
|
+
if (isConnectorInvocationError(error)) {
|
|
277
|
+
const failureDetails = sanitizeConnectorFailureDetails(error, {
|
|
278
|
+
includeResponseExcerpt: customerRoute === undefined,
|
|
279
|
+
});
|
|
280
|
+
deps.trace?.record({
|
|
281
|
+
kind: 'connector',
|
|
282
|
+
connectorId: ref.connectorId,
|
|
283
|
+
connectorVersion: ref.connectorVersion,
|
|
284
|
+
operation: ref.operation,
|
|
285
|
+
...failureDetails,
|
|
286
|
+
});
|
|
287
|
+
// Attribution keeps the sanitized classification (never the excerpt) so analytics can name
|
|
288
|
+
// the failing connector/operation/stage while the wire result stays generic (#1309).
|
|
289
|
+
const status = statusClass(failureDetails.status);
|
|
290
|
+
const reason = failureDetails.status === 401 || failureDetails.status === 403
|
|
291
|
+
? 'upstream_authentication_lost'
|
|
292
|
+
: failureDetails.category;
|
|
293
|
+
return fail('connector_error', `connector failed for operation "${ref.operation}"`, {
|
|
294
|
+
...(reason === undefined ? {} : { reason }),
|
|
295
|
+
connector: {
|
|
296
|
+
connectorId: ref.connectorId,
|
|
297
|
+
connectorVersion: ref.connectorVersion,
|
|
298
|
+
operation: ref.operation,
|
|
299
|
+
...(failureDetails.category === undefined
|
|
300
|
+
? {}
|
|
301
|
+
: { category: failureDetails.category }),
|
|
302
|
+
...(status === undefined ? {} : { statusClass: status }),
|
|
303
|
+
...(failureDetails.attempts === undefined
|
|
304
|
+
? {}
|
|
305
|
+
: { attempts: failureDetails.attempts }),
|
|
306
|
+
...(failureDetails.retryable === undefined
|
|
307
|
+
? {}
|
|
308
|
+
: { retryable: failureDetails.retryable }),
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
// Normalize connector failures; never surface backend internals or credentials.
|
|
243
313
|
return fail('connector_error', `connector failed for operation "${ref.operation}"`, {
|
|
244
|
-
...(reason === undefined ? {} : { reason }),
|
|
245
314
|
connector: {
|
|
246
315
|
connectorId: ref.connectorId,
|
|
247
316
|
connectorVersion: ref.connectorVersion,
|
|
248
317
|
operation: ref.operation,
|
|
249
|
-
...(failureDetails.category === undefined
|
|
250
|
-
? {}
|
|
251
|
-
: { category: failureDetails.category }),
|
|
252
|
-
...(status === undefined ? {} : { statusClass: status }),
|
|
253
|
-
...(failureDetails.attempts === undefined
|
|
254
|
-
? {}
|
|
255
|
-
: { attempts: failureDetails.attempts }),
|
|
256
|
-
...(failureDetails.retryable === undefined
|
|
257
|
-
? {}
|
|
258
|
-
: { retryable: failureDetails.retryable }),
|
|
259
318
|
},
|
|
260
319
|
});
|
|
261
320
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
if (customerRoute !== undefined) {
|
|
272
|
-
const cloned = cloneRoutedOutput(output, customerRoute.baseUrl);
|
|
273
|
-
if (!cloned.ok) {
|
|
274
|
-
return fail('connector_error', `connector failed for operation "${ref.operation}"`);
|
|
321
|
+
if (customerRoute !== undefined) {
|
|
322
|
+
const cloned = cloneRoutedOutput(output, customerRoute.baseUrl);
|
|
323
|
+
if (!cloned.ok) {
|
|
324
|
+
return fail('connector_error', `connector failed for operation "${ref.operation}"`);
|
|
325
|
+
}
|
|
326
|
+
output = cloned.value;
|
|
275
327
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
328
|
+
let redacted;
|
|
329
|
+
try {
|
|
330
|
+
redacted = await policy.after(context, output);
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return fail('policy_error', 'policy after hook failed');
|
|
334
|
+
}
|
|
335
|
+
// Projection metadata is a reserved runtime envelope, not part of the connector's visible output
|
|
336
|
+
// contract. Validate the visible value deeply while preserving policy-processed metadata for the
|
|
337
|
+
// flow/result projection channel.
|
|
338
|
+
const outputError = validateAgainstSchema(splitResultMeta(redacted).visible, sig.output, outputPath, 'output_invalid', 'output field');
|
|
339
|
+
if (outputError)
|
|
340
|
+
return { ok: false, error: outputError };
|
|
341
|
+
return { ok: true, output: redacted };
|
|
281
342
|
}
|
|
282
|
-
|
|
283
|
-
|
|
343
|
+
finally {
|
|
344
|
+
try {
|
|
345
|
+
await coordinated?.lease.finish(coordinated.didAdmitAction()
|
|
346
|
+
? (outcome ?? { outcome: 'unknown' })
|
|
347
|
+
: { outcome: 'rejected' });
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
coordinated?.dispose();
|
|
351
|
+
}
|
|
284
352
|
}
|
|
285
|
-
// Projection metadata is a reserved runtime envelope, not part of the connector's visible output
|
|
286
|
-
// contract. Validate the visible value deeply while preserving policy-processed metadata for the
|
|
287
|
-
// flow/result projection channel.
|
|
288
|
-
const outputError = validateAgainstSchema(splitResultMeta(redacted).visible, sig.output, outputPath, 'output_invalid', 'output field');
|
|
289
|
-
if (outputError)
|
|
290
|
-
return { ok: false, error: outputError };
|
|
291
|
-
return { ok: true, output: redacted };
|
|
292
353
|
});
|
|
293
354
|
}
|
|
294
355
|
finally {
|
|
@@ -301,7 +362,7 @@ export function createHost(toolName, deps, policy, env, callStack, beforeDispatc
|
|
|
301
362
|
const host = {
|
|
302
363
|
[CALL_STACK]: callStack,
|
|
303
364
|
[INVOCATION]: { id: deps.invocationId ?? randomUUID(), occurrences: new Map() },
|
|
304
|
-
async callOperation(ref, args, path) {
|
|
365
|
+
async callOperation(ref, args, path, parentSignal) {
|
|
305
366
|
const connector = deps.connectors.resolve(ref);
|
|
306
367
|
if (!connector)
|
|
307
368
|
throw new HostCallError('connector_unavailable', 'connector unavailable');
|
|
@@ -313,11 +374,29 @@ export function createHost(toolName, deps, policy, env, callStack, beforeDispatc
|
|
|
313
374
|
const argError = validateAgainstSchema(args, sig.input, `${path}.args`, 'arg_invalid', 'argument');
|
|
314
375
|
if (argError)
|
|
315
376
|
throw new HostCallError(argError.code, argError.message, argError.path);
|
|
316
|
-
const
|
|
317
|
-
if (
|
|
318
|
-
throw new HostCallError(
|
|
377
|
+
const cancellation = executionCancellation(deps.signal, beforeDispatch);
|
|
378
|
+
if (cancellation)
|
|
379
|
+
throw new HostCallError(cancellation.code, cancellation.message);
|
|
380
|
+
// A disconnected admitted request must not mask a later hard parent deadline in AbortSignal.any.
|
|
381
|
+
const inherited = parentSignal === undefined ? undefined : admittedExecutionSignal(deps.signal);
|
|
382
|
+
try {
|
|
383
|
+
const nestedDeps = parentSignal === undefined
|
|
384
|
+
? deps
|
|
385
|
+
: {
|
|
386
|
+
...deps,
|
|
387
|
+
signal: inherited?.signal === undefined
|
|
388
|
+
? parentSignal
|
|
389
|
+
: AbortSignal.any([inherited.signal, parentSignal]),
|
|
390
|
+
};
|
|
391
|
+
const result = await invokeOperation(ref, args, sig, toolName, nestedDeps, policy, this, env, `${path}.args`, `${path}.output`, beforeDispatch);
|
|
392
|
+
if (!result.ok) {
|
|
393
|
+
throw new HostCallError(result.error.code, result.error.message, result.error.path);
|
|
394
|
+
}
|
|
395
|
+
return result.output;
|
|
396
|
+
}
|
|
397
|
+
finally {
|
|
398
|
+
inherited?.dispose();
|
|
319
399
|
}
|
|
320
|
-
return result.output;
|
|
321
400
|
},
|
|
322
401
|
};
|
|
323
402
|
return host;
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { createHmac } from 'node:crypto';
|
|
2
2
|
import { canonicalJson, sha256Canonical } from '@noodle-borg/compiler';
|
|
3
|
+
import { ApplicationActivityListResponseSchema, ApplicationActivityPreviewResponseSchema, ApplicationActivitySettingsResponseSchema, ApplicationActivitySettingsSaveRequestSchema, OperationCoordinationListRequestSchema, OperationCoordinationListResponseSchema, OperationCoordinationResolveRequestSchema, OperationCoordinationResolveResponseSchema, } from '@noodle-borg/wire-contracts';
|
|
3
4
|
import { resolveApplicationRuntimeTarget } from './application-runtime-target.js';
|
|
5
|
+
import { createOperationCoordinationPort, } from './operation-coordination.js';
|
|
4
6
|
import { createOperationEvidencePort, } from './operation-evidence.js';
|
|
7
|
+
const POLICY_MESSAGES = {
|
|
8
|
+
activity_conflict: 'History settings or plan changed. Reload before continuing.',
|
|
9
|
+
activity_invalid: 'Activity paging or retention is invalid for the current policy.',
|
|
10
|
+
activity_unavailable: 'Verified activity history policy is unavailable.',
|
|
11
|
+
coordination_invalid: 'Invalid coordination request.',
|
|
12
|
+
coordination_conflict: 'The operation is active, changed, or unavailable. Inspect the current state before reviewing it again.',
|
|
13
|
+
coordination_unavailable: 'Verified coordination state is unavailable.',
|
|
14
|
+
};
|
|
5
15
|
export class ActivityPolicyError extends Error {
|
|
6
16
|
code;
|
|
7
17
|
constructor(code) {
|
|
8
|
-
super(code
|
|
9
|
-
? 'History settings or plan changed. Reload before continuing.'
|
|
10
|
-
: code === 'activity_invalid'
|
|
11
|
-
? 'Activity paging or retention is invalid for the current policy.'
|
|
12
|
-
: 'Verified activity history policy is unavailable.');
|
|
18
|
+
super(POLICY_MESSAGES[code]);
|
|
13
19
|
this.code = code;
|
|
14
20
|
}
|
|
15
21
|
}
|
|
@@ -19,6 +25,124 @@ export class ApplicationActivity {
|
|
|
19
25
|
constructor(options) {
|
|
20
26
|
this.options = options;
|
|
21
27
|
}
|
|
28
|
+
/** Shared strict operator projection. Callers authorize the live installation grant before and after it. */
|
|
29
|
+
async project(scope, action, input) {
|
|
30
|
+
const coordinating = action.startsWith('coordination');
|
|
31
|
+
const invalid = () => new ActivityPolicyError(coordinating ? 'coordination_invalid' : 'activity_invalid');
|
|
32
|
+
const allowed = action === 'coordination-list'
|
|
33
|
+
? ['limit', 'beforeResource']
|
|
34
|
+
: action === 'list' || action === 'export'
|
|
35
|
+
? ['limit', 'cursor']
|
|
36
|
+
: [];
|
|
37
|
+
const parameters = new Map();
|
|
38
|
+
for (const [key, value] of input.parameters) {
|
|
39
|
+
if (!allowed.includes(key) || parameters.has(key))
|
|
40
|
+
throw invalid();
|
|
41
|
+
parameters.set(key, value);
|
|
42
|
+
}
|
|
43
|
+
const mutation = (response, eventType, details) => ({
|
|
44
|
+
response,
|
|
45
|
+
audit: { eventType, details },
|
|
46
|
+
});
|
|
47
|
+
if (action === 'coordination-resolve') {
|
|
48
|
+
const parsed = OperationCoordinationResolveRequestSchema.safeParse(input.body);
|
|
49
|
+
if (!parsed.success)
|
|
50
|
+
throw invalid();
|
|
51
|
+
const store = this.options.coordination;
|
|
52
|
+
if (!store)
|
|
53
|
+
throw new ActivityPolicyError('coordination_unavailable');
|
|
54
|
+
let resolved;
|
|
55
|
+
try {
|
|
56
|
+
resolved = await store.resolve(scope, parsed.data.resource, parsed.data.token, {
|
|
57
|
+
reviewer: input.reviewer,
|
|
58
|
+
reason: parsed.data.reason,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new ActivityPolicyError('coordination_unavailable');
|
|
63
|
+
}
|
|
64
|
+
if (!resolved)
|
|
65
|
+
throw new ActivityPolicyError('coordination_conflict');
|
|
66
|
+
return mutation(OperationCoordinationResolveResponseSchema.parse({ ok: true, data: { resolved: true } }), 'config.operation.coordination_resolved', { resource: parsed.data.resource });
|
|
67
|
+
}
|
|
68
|
+
if (action === 'coordination-list') {
|
|
69
|
+
const parsed = OperationCoordinationListRequestSchema.safeParse({
|
|
70
|
+
...(parameters.has('limit') ? { limit: Number(parameters.get('limit')) } : {}),
|
|
71
|
+
...(parameters.has('beforeResource')
|
|
72
|
+
? { beforeResource: parameters.get('beforeResource') }
|
|
73
|
+
: {}),
|
|
74
|
+
});
|
|
75
|
+
if (!parsed.success)
|
|
76
|
+
throw invalid();
|
|
77
|
+
const store = this.options.coordination;
|
|
78
|
+
if (!store)
|
|
79
|
+
throw new ActivityPolicyError('coordination_unavailable');
|
|
80
|
+
try {
|
|
81
|
+
const limit = parsed.data.limit ?? 100;
|
|
82
|
+
const records = await store.list(scope, limit, parsed.data.beforeResource);
|
|
83
|
+
if (records.some((record) => canonicalJson(record.scope) !== canonicalJson(scope)))
|
|
84
|
+
throw new Error('Coordination scope mismatch');
|
|
85
|
+
return {
|
|
86
|
+
response: OperationCoordinationListResponseSchema.parse({
|
|
87
|
+
ok: true,
|
|
88
|
+
data: {
|
|
89
|
+
records: records.map(({ resource, token, reference, operationDigest, startedAt, deadline, state }) => ({
|
|
90
|
+
resource,
|
|
91
|
+
token,
|
|
92
|
+
reference,
|
|
93
|
+
operationDigest,
|
|
94
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
95
|
+
deadline: new Date(deadline).toISOString(),
|
|
96
|
+
state,
|
|
97
|
+
})),
|
|
98
|
+
...(records.length === limit ? { nextBeforeResource: records.at(-1)?.resource } : {}),
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
throw new ActivityPolicyError('coordination_unavailable');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (action === 'save-settings') {
|
|
108
|
+
const parsed = ApplicationActivitySettingsSaveRequestSchema.safeParse(input.body);
|
|
109
|
+
if (!parsed.success)
|
|
110
|
+
throw invalid();
|
|
111
|
+
const data = await this.save(scope, parsed.data);
|
|
112
|
+
return mutation(ApplicationActivitySettingsResponseSchema.parse({ ok: true, data }), 'config.activity.retention_changed', { retentionDays: data.retentionDays });
|
|
113
|
+
}
|
|
114
|
+
if (action === 'preview')
|
|
115
|
+
return {
|
|
116
|
+
response: ApplicationActivityPreviewResponseSchema.parse({
|
|
117
|
+
ok: true,
|
|
118
|
+
data: await this.preview(scope),
|
|
119
|
+
}),
|
|
120
|
+
};
|
|
121
|
+
if (action === 'settings')
|
|
122
|
+
return {
|
|
123
|
+
response: ApplicationActivitySettingsResponseSchema.parse({
|
|
124
|
+
ok: true,
|
|
125
|
+
data: (await this.settings(scope, input.canEdit)).projection,
|
|
126
|
+
}),
|
|
127
|
+
};
|
|
128
|
+
const limit = parameters.has('limit') ? Number(parameters.get('limit')) : 50;
|
|
129
|
+
const cursor = parameters.get('cursor');
|
|
130
|
+
if (!Number.isInteger(limit) ||
|
|
131
|
+
limit < 1 ||
|
|
132
|
+
limit > 100 ||
|
|
133
|
+
(cursor !== undefined && (!cursor || cursor.length > 2048)))
|
|
134
|
+
throw invalid();
|
|
135
|
+
return {
|
|
136
|
+
response: ApplicationActivityListResponseSchema.parse({
|
|
137
|
+
ok: true,
|
|
138
|
+
data: await this.page(scope, {
|
|
139
|
+
purpose: action,
|
|
140
|
+
limit,
|
|
141
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
142
|
+
}),
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
22
146
|
async settings(scope, canEdit) {
|
|
23
147
|
const allowance = await this.options.allowance(scope.org);
|
|
24
148
|
if (!allowance ||
|
|
@@ -174,6 +298,19 @@ export class ApplicationActivity {
|
|
|
174
298
|
const scope = installation.scope;
|
|
175
299
|
const revision = target.served.deps.executionBinding?.revision;
|
|
176
300
|
const epochRevision = sha256Canonical({ revision, epoch: this.options.epoch });
|
|
301
|
+
const authorize = async () => {
|
|
302
|
+
const active = await deps.registry.getActiveByTenant({
|
|
303
|
+
org: scope.org,
|
|
304
|
+
app: scope.app,
|
|
305
|
+
env: scope.env,
|
|
306
|
+
});
|
|
307
|
+
if (!active || active.deploymentId !== target.deploymentId)
|
|
308
|
+
return false;
|
|
309
|
+
const current = await resolveApplicationRuntimeTarget(active, deps.installations, deps.connections?.readGenerations);
|
|
310
|
+
return (!!current &&
|
|
311
|
+
revision !== undefined &&
|
|
312
|
+
current.served.deps.executionBinding?.revision === revision);
|
|
313
|
+
};
|
|
177
314
|
const evidence = createOperationEvidencePort({
|
|
178
315
|
...this.options,
|
|
179
316
|
scope,
|
|
@@ -181,20 +318,16 @@ export class ApplicationActivity {
|
|
|
181
318
|
historyDays: async () => (await this.settings(scope, false)).projection.retentionDays,
|
|
182
319
|
executionBoundMs: (intent) => intent.executionBoundMs,
|
|
183
320
|
connectionGeneration: (id) => target.served.deps.executionBinding?.connections[id] ?? revision,
|
|
184
|
-
authorize
|
|
185
|
-
const active = await deps.registry.getActiveByTenant({
|
|
186
|
-
org: scope.org,
|
|
187
|
-
app: scope.app,
|
|
188
|
-
env: scope.env,
|
|
189
|
-
});
|
|
190
|
-
if (!active || active.deploymentId !== target.deploymentId)
|
|
191
|
-
return false;
|
|
192
|
-
const current = await resolveApplicationRuntimeTarget(active, deps.installations, deps.connections?.readGenerations);
|
|
193
|
-
return (!!current &&
|
|
194
|
-
revision !== undefined &&
|
|
195
|
-
current.served.deps.executionBinding?.revision === revision);
|
|
196
|
-
},
|
|
321
|
+
authorize,
|
|
197
322
|
});
|
|
323
|
+
const coordination = this.options.coordination &&
|
|
324
|
+
createOperationCoordinationPort({
|
|
325
|
+
...this.options,
|
|
326
|
+
store: this.options.coordination,
|
|
327
|
+
scope,
|
|
328
|
+
authorize,
|
|
329
|
+
connectionGeneration: (id) => target.served.deps.executionBinding?.connections[id],
|
|
330
|
+
});
|
|
198
331
|
return {
|
|
199
332
|
...target,
|
|
200
333
|
served: {
|
|
@@ -202,6 +335,7 @@ export class ApplicationActivity {
|
|
|
202
335
|
deps: {
|
|
203
336
|
...target.served.deps,
|
|
204
337
|
operationEvidence: evidence,
|
|
338
|
+
...(coordination ? { operationCoordination: coordination } : {}),
|
|
205
339
|
executionBinding: {
|
|
206
340
|
revision: epochRevision,
|
|
207
341
|
connections: target.served.deps.executionBinding?.connections ?? {},
|
|
@@ -2,7 +2,7 @@ import { sha256Canonical } from '@noodle-borg/compiler';
|
|
|
2
2
|
import { resolveManagedOrigins } from '@noodle-borg/runtime';
|
|
3
3
|
import { projectApplicationBusinessNotice, } from './application-business-notice.js';
|
|
4
4
|
import { BusinessOnboarding } from './business-onboarding.js';
|
|
5
|
-
import { activateSolutionInstallation, } from './solution-installation-activation.js';
|
|
5
|
+
import { activateSolutionInstallation, readSolutionInstallationActivation, } from './solution-installation-activation.js';
|
|
6
6
|
/** Resolve operator-owned origins from one current configuration snapshot, never a deployment cache. */
|
|
7
7
|
export async function resolveTargetOrigins(target) {
|
|
8
8
|
const env = target.served.deps.env;
|
|
@@ -86,6 +86,7 @@ export function applicationServingResolver(input) {
|
|
|
86
86
|
installation.applicationGeneration === 'pending')) {
|
|
87
87
|
const activated = await input.activate({
|
|
88
88
|
installation,
|
|
89
|
+
purpose: 'runtime-refresh',
|
|
89
90
|
actor: { subject: 'noodle:managed-solution-release', email: '', superAdmin: false },
|
|
90
91
|
});
|
|
91
92
|
if (!activated.ok)
|
|
@@ -128,6 +129,13 @@ export function createApplicationServingRuntime(registry, providerOptions, contr
|
|
|
128
129
|
controlPlane,
|
|
129
130
|
audit,
|
|
130
131
|
});
|
|
132
|
+
const readInstallationActivation = (installation) => readSolutionInstallationActivation(installation, {
|
|
133
|
+
registry,
|
|
134
|
+
businessInformationStore,
|
|
135
|
+
options: providerOptions,
|
|
136
|
+
controlPlane,
|
|
137
|
+
audit,
|
|
138
|
+
});
|
|
131
139
|
const resolveRuntimeTarget = applicationServingResolver({
|
|
132
140
|
registry,
|
|
133
141
|
installations: businessInformationStore,
|
|
@@ -136,6 +144,11 @@ export function createApplicationServingRuntime(registry, providerOptions, contr
|
|
|
136
144
|
activate: activateInstallation,
|
|
137
145
|
...(businessOnboarding ? { businessOnboarding } : {}),
|
|
138
146
|
});
|
|
139
|
-
return {
|
|
147
|
+
return {
|
|
148
|
+
activateInstallation,
|
|
149
|
+
readInstallationActivation,
|
|
150
|
+
resolveRuntimeTarget,
|
|
151
|
+
businessOnboarding,
|
|
152
|
+
};
|
|
140
153
|
}
|
|
141
154
|
//# sourceMappingURL=application-runtime-target.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sha256Canonical } from '@noodle-borg/app-package';
|
|
2
2
|
import { projectManagedCollectionControls } from '@noodle-borg/compiler';
|
|
3
|
+
import { builtInDefinition } from './profiles.js';
|
|
3
4
|
/**
|
|
4
5
|
* Validate and freeze an active deployment as an immutable private solution definition.
|
|
5
6
|
* Authorization and active-deployment lookup happen before this pure boundary.
|
|
@@ -91,4 +92,22 @@ function summaryFields(schema) {
|
|
|
91
92
|
function singularize(title) {
|
|
92
93
|
return title.endsWith('s') && title.length > 1 ? title.slice(0, -1) : title;
|
|
93
94
|
}
|
|
95
|
+
export async function resolveInstallDefinition(selector, installingOrg, resolvePrivateDefinition) {
|
|
96
|
+
if (selector.kind === 'managed') {
|
|
97
|
+
return builtInDefinition(selector.profileId);
|
|
98
|
+
}
|
|
99
|
+
if (selector.publisherOrg !== installingOrg ||
|
|
100
|
+
selector.publisherOrg === undefined ||
|
|
101
|
+
selector.app === undefined ||
|
|
102
|
+
selector.environment === undefined ||
|
|
103
|
+
selector.deploymentId === undefined) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return resolvePrivateDefinition?.({
|
|
107
|
+
publisherOrg: selector.publisherOrg,
|
|
108
|
+
app: selector.app,
|
|
109
|
+
environment: selector.environment,
|
|
110
|
+
deploymentId: selector.deploymentId,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
94
113
|
//# sourceMappingURL=definition-resolver.js.map
|
|
@@ -65,7 +65,7 @@ export class InMemoryBusinessInformationStore {
|
|
|
65
65
|
return this.#lifecycle.setBusinessNotice(input);
|
|
66
66
|
}
|
|
67
67
|
async createInstallation(input) {
|
|
68
|
-
const normalized = normalizeInstallationInput(input);
|
|
68
|
+
const normalized = normalizeInstallationInput(input, this.#managedDefinition);
|
|
69
69
|
const key = scopeKey(normalized.scope);
|
|
70
70
|
return this.#locks.run(`installations:${normalized.scope.org}`, async () => {
|
|
71
71
|
const existingKey = this.#installationIds.get(installationIdKey(normalized.scope.org, normalized.scope.installationId));
|
package/node_modules/@noodle-borg/service/dist/business-information/managed-solution-blueprints.js
CHANGED
|
@@ -27,6 +27,13 @@ export function managedSolutionBlueprint(key, release) {
|
|
|
27
27
|
if (key === 'b2b_saas')
|
|
28
28
|
return undefined;
|
|
29
29
|
const blueprint = BLUEPRINTS[key];
|
|
30
|
+
if ((key === 'travel' || key === 'restaurant') && release === blueprint.release + 1)
|
|
31
|
+
return {
|
|
32
|
+
...blueprint,
|
|
33
|
+
release,
|
|
34
|
+
followUpContact: true,
|
|
35
|
+
description: `${blueprint.description} Ask for the guest's reply email before submission so staff can follow up. Explain that the business receives this address; never invent it or claim it is verified.`,
|
|
36
|
+
};
|
|
30
37
|
return blueprint.release === release ? blueprint : undefined;
|
|
31
38
|
}
|
|
32
39
|
export function managedSolutionVariables() {
|