@noodleseed/one 0.173.0 → 0.175.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-activity.js +1 -1
- package/dist/commands/solutions-activity.js.map +1 -1
- package/dist/commands/solutions-drafts.d.ts.map +1 -1
- package/dist/commands/solutions-drafts.js +36 -9
- package/dist/commands/solutions-drafts.js.map +1 -1
- package/dist/commands/solutions-native-lifecycle.d.ts +5 -0
- package/dist/commands/solutions-native-lifecycle.d.ts.map +1 -0
- package/dist/commands/solutions-native-lifecycle.js +77 -0
- package/dist/commands/solutions-native-lifecycle.js.map +1 -0
- package/dist/commands/solutions-ops-args.d.ts +42 -0
- package/dist/commands/solutions-ops-args.d.ts.map +1 -0
- package/dist/commands/solutions-ops-args.js +47 -0
- package/dist/commands/solutions-ops-args.js.map +1 -0
- package/dist/commands/solutions-ops.d.ts.map +1 -1
- package/dist/commands/solutions-ops.js +9 -46
- package/dist/commands/solutions-ops.js.map +1 -1
- package/dist/commands/solutions-workspace.d.ts +5 -0
- package/dist/commands/solutions-workspace.d.ts.map +1 -0
- package/dist/commands/solutions-workspace.js +157 -0
- package/dist/commands/solutions-workspace.js.map +1 -0
- package/dist/commands/whatsapp-output.d.ts.map +1 -1
- package/dist/commands/whatsapp-output.js +20 -4
- package/dist/commands/whatsapp-output.js.map +1 -1
- package/node_modules/@noodle-borg/admission-limits/dist/public-record-admission.d.ts +16 -9
- package/node_modules/@noodle-borg/admission-limits/dist/public-record-admission.js +5 -2
- package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +9 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +5 -5
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +9 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +13 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +5 -3
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +5 -4
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-inbox.js +6 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.d.ts +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.d.ts +12 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.d.ts +7 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.js +14 -4
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-capture.js +104 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-judgment.js +93 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-ledger.d.ts +105 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-ledger.js +183 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-model-call.js +38 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-parsers.d.ts +22 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-parsers.js +109 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-review.d.ts +21 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-review.js +44 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-spec.d.ts +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-spec.js +86 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-store.d.ts +11 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/collection-store.js +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.d.ts +20 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.js +38 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +5 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +5 -0
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/authoring/dist/interaction.d.ts +49 -0
- package/node_modules/@noodle-borg/authoring/dist/interaction.js +25 -0
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -0
- package/node_modules/@noodle-borg/authoring/dist/server.js +4 -0
- package/node_modules/@noodle-borg/authoring/package.json +6 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-drafts.js +7 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-native-lifecycle.js +43 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-records.js +154 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-workspace.js +95 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solutions.js +4 -149
- package/node_modules/@noodle-borg/compiler/dist/artifact/assemble.js +2 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +33 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
- package/node_modules/@noodle-borg/compiler/dist/compile.js +5 -0
- package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
- package/node_modules/@noodle-borg/compiler/dist/index.d.ts +2 -2
- package/node_modules/@noodle-borg/compiler/dist/index.js +1 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-schema.d.ts +41 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-schema.js +62 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-validation.js +220 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +60 -4
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +7 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.d.ts +9 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.js +58 -10
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-atomic-state.d.ts +8 -0
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-atomic-state.js +136 -0
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.d.ts +7 -0
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.js +16 -24
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-organization-store.d.ts +4 -2
- package/node_modules/@noodle-borg/control-plane/dist/in-memory-organization-store.js +8 -6
- package/node_modules/@noodle-borg/control-plane/dist/mcp-subdomain-claims.d.ts +2 -1
- package/node_modules/@noodle-borg/control-plane/dist/mcp-subdomain-claims.js +13 -8
- package/node_modules/@noodle-borg/control-plane/dist/organization-agreements.d.ts +13 -4
- package/node_modules/@noodle-borg/control-plane/dist/organization-agreements.js +23 -7
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -1
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/module/dist/platform-human-identity.d.ts +5 -2
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +16 -4
- package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +3 -6
- package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/application-activity.js +85 -63
- package/node_modules/@noodle-borg/service/dist/application-drafts/contracts.js +9 -0
- package/node_modules/@noodle-borg/service/dist/application-drafts/paths.js +2 -2
- package/node_modules/@noodle-borg/service/dist/application-runtime-target.js +1 -1
- package/node_modules/@noodle-borg/service/dist/business-information/contracts.d.ts +18 -4
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-business-pages.js +3 -5
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-installation-lifecycle.js +8 -6
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-native-lifecycle.js +80 -0
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-store.js +27 -31
- package/node_modules/@noodle-borg/service/dist/business-information/memory-native-retention.js +29 -0
- package/node_modules/@noodle-borg/service/dist/business-information/model.js +9 -2
- package/node_modules/@noodle-borg/service/dist/business-information/native-query.js +3 -1
- package/node_modules/@noodle-borg/service/dist/business-information/native-record-lifecycle.d.ts +27 -0
- package/node_modules/@noodle-borg/service/dist/business-information/native-record-lifecycle.js +63 -0
- package/node_modules/@noodle-borg/service/dist/business-information/principal-authority.d.ts +3 -1
- package/node_modules/@noodle-borg/service/dist/business-information/principal-authority.js +9 -4
- package/node_modules/@noodle-borg/service/dist/business-information/staff-authority.d.ts +16 -0
- package/node_modules/@noodle-borg/service/dist/business-information/staff-authority.js +60 -0
- package/node_modules/@noodle-borg/service/dist/business-information/workspace-permissions.js +18 -0
- package/node_modules/@noodle-borg/service/dist/business-information-runtime.js +1 -0
- package/node_modules/@noodle-borg/service/dist/business-onboarding.js +16 -3
- package/node_modules/@noodle-borg/service/dist/business-workspaces/contracts.d.ts +110 -0
- package/node_modules/@noodle-borg/service/dist/business-workspaces/contracts.js +52 -0
- package/node_modules/@noodle-borg/service/dist/business-workspaces/paths.js +17 -0
- package/node_modules/@noodle-borg/service/dist/business-workspaces/permissions.d.ts +7 -0
- package/node_modules/@noodle-borg/service/dist/business-workspaces/store.d.ts +102 -0
- package/node_modules/@noodle-borg/service/dist/channels/360dialog.js +70 -8
- package/node_modules/@noodle-borg/service/dist/channels/capability-report.js +73 -0
- package/node_modules/@noodle-borg/service/dist/channels/collection-turn.js +232 -0
- package/node_modules/@noodle-borg/service/dist/channels/composition.js +1 -0
- package/node_modules/@noodle-borg/service/dist/channels/messaging-eligibility.js +45 -0
- package/node_modules/@noodle-borg/service/dist/channels/reply-buttons.js +23 -0
- package/node_modules/@noodle-borg/service/dist/channels/runtime.js +127 -33
- package/node_modules/@noodle-borg/service/dist/native-record-connector.js +19 -4
- package/node_modules/@noodle-borg/service/dist/routes/application-drafts.js +44 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +4 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-authoring-dispatch.js +23 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-access.js +39 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-activity.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +2 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-connections.js +10 -4
- package/node_modules/@noodle-borg/service/dist/routes/business-information-dispatch.js +11 -6
- package/node_modules/@noodle-borg/service/dist/routes/business-information-installation.js +3 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-notice.js +6 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-pages.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-paths.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-record-create.js +7 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-record-reads.js +2 -2
- package/node_modules/@noodle-borg/service/dist/routes/business-information-records.js +332 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-settings.js +6 -3
- package/node_modules/@noodle-borg/service/dist/routes/business-information-staff.js +9 -5
- package/node_modules/@noodle-borg/service/dist/routes/business-information.js +12 -356
- package/node_modules/@noodle-borg/service/dist/routes/business-native-lifecycle.js +46 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-workspaces.js +119 -0
- package/node_modules/@noodle-borg/service/dist/routes/service-probes.js +2 -1
- package/node_modules/@noodle-borg/service/dist/service.js +9 -2
- package/node_modules/@noodle-borg/service/dist/web-capabilities.js +3 -1
- package/node_modules/@noodle-borg/service/package.json +2 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/application-draft-validation.d.ts +59 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/application-draft-validation.js +38 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/business-information.d.ts +144 -50
- package/node_modules/@noodle-borg/wire-contracts/dist/business-information.js +44 -4
- package/node_modules/@noodle-borg/wire-contracts/dist/business-workspaces.d.ts +258 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/business-workspaces.js +118 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/native-record-lifecycle.d.ts +81 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/native-record-lifecycle.js +39 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.d.ts +26 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.js +24 -1
- package/package.json +2 -2
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import { beginChannelProviderBlock, CHANNEL_RETENTION_MS, ChannelCoordinator, ChannelError, channelDigest, channelProviderBlocks, finishChannelProviderBlock, messagingSurfaceOf, projectArtifactForSurface, resolveInvocationContextSnapshot, withAssistantTurnExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
1
|
+
import { beginChannelProviderBlock, CHANNEL_RETENTION_MS, ChannelCoordinator, ChannelError, channelDigest, channelProviderBlocks, collectionSpecFor, finishChannelProviderBlock, messagingSurfaceOf, projectArtifactForSurface, resolveInvocationContextSnapshot, WHATSAPP_CONFIRMATION_EXPIRY_MS, withAssistantTurnExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
import { guardedFetch } from '@noodle-borg/connector-http';
|
|
3
|
+
import { executePreparedTool, prepareToolForConfirmation } from '@noodle-borg/runtime';
|
|
2
4
|
import { runAgentTurn } from '../routes/assistant-agent.js';
|
|
3
5
|
import { resolveAssistantKnowledge } from '../routes/assistant-knowledge.js';
|
|
4
6
|
import { resolveAssistantModelBinding } from '../routes/assistant-model-binding.js';
|
|
5
7
|
import { activeAssistantTarget } from '../routes/assistant-session-target.js';
|
|
6
8
|
import { resolveConfigScope } from '../store.js';
|
|
7
9
|
import { Dialog360, verifyWhatsAppWebhookSecret, WHATSAPP_CALLBACK_HEADER } from './360dialog.js';
|
|
10
|
+
import { capabilityReport } from './capability-report.js';
|
|
11
|
+
import { collectionCustody, messagingAdmissionDigest, redactPrivate, runCollectionTurn, } from './collection-turn.js';
|
|
8
12
|
import { verifiedInferenceBound, withChannelInferenceGuard } from './inference-guard.js';
|
|
13
|
+
import { messagingProjectionIneligibility } from './messaging-eligibility.js';
|
|
14
|
+
import { reviewButtonBinding } from './reply-buttons.js';
|
|
9
15
|
export class WhatsAppRuntime {
|
|
10
16
|
options;
|
|
11
17
|
deps;
|
|
@@ -41,14 +47,9 @@ export class WhatsAppRuntime {
|
|
|
41
47
|
if (binding.capabilities.some((cap) => !surface.capabilities.some((authored) => authored.kind === cap.kind && authored.name === cap.name)))
|
|
42
48
|
throw new ChannelError('capability_not_authored');
|
|
43
49
|
const artifact = projectArtifactForSurface(target.served.artifact, binding.capabilities);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// the existing corpus search remain available; external lookups require their owning slice.
|
|
48
|
-
const external = (fulfilment) => fulfilment.kind === 'operation' || fulfilment.steps.some((step) => step.kind === 'operation');
|
|
49
|
-
if (artifact.tools.some((tool) => external(tool.fulfilment)) ||
|
|
50
|
-
(artifact.server.context?.ambient && external(artifact.server.context.ambient.fulfilment)))
|
|
51
|
-
throw new ChannelError('external_lookup_not_enabled');
|
|
50
|
+
const ineligible = messagingProjectionIneligibility(artifact);
|
|
51
|
+
if (ineligible !== undefined)
|
|
52
|
+
throw new ChannelError(ineligible);
|
|
52
53
|
return { ...target, served: { ...target.served, artifact } };
|
|
53
54
|
}
|
|
54
55
|
async doctor(id, req) {
|
|
@@ -75,8 +76,9 @@ export class WhatsAppRuntime {
|
|
|
75
76
|
if (!this.options.worker.ready())
|
|
76
77
|
throw new ChannelError('worker_unavailable');
|
|
77
78
|
});
|
|
79
|
+
let target;
|
|
78
80
|
await check('deployment', async () => {
|
|
79
|
-
await this.target(binding);
|
|
81
|
+
target = await this.target(binding);
|
|
80
82
|
});
|
|
81
83
|
await check('model_cost', async () => {
|
|
82
84
|
const model = await resolveAssistantModelBinding(await this.target(binding), binding.tenant, binding.deploymentId, this.deps, 'whatsapp');
|
|
@@ -109,7 +111,20 @@ export class WhatsAppRuntime {
|
|
|
109
111
|
const ready = checks.every((check) => check.status === 'ready');
|
|
110
112
|
if (ready)
|
|
111
113
|
await this.channels.markReady(id, binding.revision);
|
|
112
|
-
|
|
114
|
+
// The compatibility report rides beside readiness and never changes it (ADR 0240 decision 7).
|
|
115
|
+
const capabilities = await capabilityReport({
|
|
116
|
+
artifact: target?.served.artifact,
|
|
117
|
+
tenant: binding.tenant,
|
|
118
|
+
durable: this.options.store.durable,
|
|
119
|
+
installations: this.options.installations,
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
ready,
|
|
123
|
+
revision: binding.revision,
|
|
124
|
+
checks,
|
|
125
|
+
capabilities,
|
|
126
|
+
webhookUrl: this.webhookUrl(req, binding),
|
|
127
|
+
};
|
|
113
128
|
}
|
|
114
129
|
async webhook(id, req) {
|
|
115
130
|
const binding = await this.channels.internal(id);
|
|
@@ -247,7 +262,11 @@ export class WhatsAppRuntime {
|
|
|
247
262
|
const { adapter } = await this.provider(send.binding);
|
|
248
263
|
await this.channels.assertSend(id, send.event.id, send.event.lease);
|
|
249
264
|
this.options.worker.signal.throwIfAborted();
|
|
250
|
-
await this.channels.sent(id, send.event.id, send.event.lease, await adapter.send({
|
|
265
|
+
await this.channels.sent(id, send.event.id, send.event.lease, await adapter.send({
|
|
266
|
+
to: send.participant.address,
|
|
267
|
+
text: send.event.reply,
|
|
268
|
+
...(send.event.buttons === undefined ? {} : { buttons: send.event.buttons }),
|
|
269
|
+
}));
|
|
251
270
|
}
|
|
252
271
|
}
|
|
253
272
|
catch {
|
|
@@ -277,9 +296,11 @@ export class WhatsAppRuntime {
|
|
|
277
296
|
timer.unref?.();
|
|
278
297
|
try {
|
|
279
298
|
const target = await this.target(binding);
|
|
299
|
+
const artifact = target.served.artifact;
|
|
280
300
|
const model = await resolveAssistantModelBinding(target, binding.tenant, binding.deploymentId, this.deps, 'whatsapp');
|
|
281
301
|
if (!model)
|
|
282
302
|
throw new ChannelError('model_not_configured');
|
|
303
|
+
const guarded = withChannelInferenceGuard(model, this.channels, binding.id, event.id, beforeStep);
|
|
283
304
|
const session = {
|
|
284
305
|
kind: 'messaging',
|
|
285
306
|
channel: 'whatsapp',
|
|
@@ -292,34 +313,107 @@ export class WhatsAppRuntime {
|
|
|
292
313
|
history: participant.history.map(({ role, content }) => ({ role, content })),
|
|
293
314
|
modelToolUses: participant.modelToolUses ?? [],
|
|
294
315
|
};
|
|
316
|
+
const executeDeps = withAssistantTurnExecutionAuthority({ ...target.served.deps, signal }, artifact, session);
|
|
295
317
|
const context = await resolveInvocationContextSnapshot({
|
|
296
|
-
artifact
|
|
297
|
-
executeDeps
|
|
318
|
+
artifact,
|
|
319
|
+
executeDeps,
|
|
298
320
|
caller: session.caller,
|
|
299
321
|
instant: new Date(this.channels.now()),
|
|
300
322
|
});
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
323
|
+
const now = this.channels.now();
|
|
324
|
+
const inbound = event.text ?? '';
|
|
325
|
+
// A tapped button is input only for the pending review: it never reaches the model as text,
|
|
326
|
+
// and history keeps the label the participant saw.
|
|
327
|
+
const button = event.button;
|
|
328
|
+
const heard = event.text ?? button?.title ?? '';
|
|
329
|
+
const custody = collectionCustody(this.options.store, binding.id, participant.id);
|
|
330
|
+
let ledger = await custody.load();
|
|
331
|
+
let spec = ledger === undefined
|
|
332
|
+
? undefined
|
|
333
|
+
: collectionSpecFor(artifact, ledger.interactionId, WHATSAPP_CONFIRMATION_EXPIRY_MS);
|
|
334
|
+
if (ledger !== undefined && spec === undefined) {
|
|
335
|
+
// The deployment no longer declares this interaction: the stale ledger is dropped.
|
|
336
|
+
await custody.remove();
|
|
337
|
+
ledger = undefined;
|
|
338
|
+
}
|
|
339
|
+
let outcome = button === undefined ? { kind: 'none' } : { kind: 'ignored', code: 'button_unbound' };
|
|
340
|
+
if (ledger !== undefined && spec !== undefined) {
|
|
341
|
+
const toolDeps = { ...executeDeps, caller: session.caller, context };
|
|
342
|
+
outcome = await runCollectionTurn(ledger, inbound, {
|
|
343
|
+
spec,
|
|
344
|
+
artifact,
|
|
345
|
+
now,
|
|
346
|
+
supportEmail: binding.supportEmail,
|
|
347
|
+
buttons: reviewButtonBinding(binding.indexKey, binding.id, participant.id),
|
|
348
|
+
model: {
|
|
349
|
+
binding: guarded,
|
|
350
|
+
fetcher: this.deps.modelFetch ?? ((url, init) => guardedFetch(new URL(url), init)),
|
|
351
|
+
signal,
|
|
352
|
+
},
|
|
353
|
+
prepare: (action, input) => prepareToolForConfirmation(artifact, action, input, toolDeps),
|
|
354
|
+
execute: async (proposalId, continuation) => {
|
|
355
|
+
await beforeStep();
|
|
356
|
+
return executePreparedTool(artifact, continuation, {
|
|
357
|
+
...toolDeps,
|
|
358
|
+
invocationId: proposalId,
|
|
359
|
+
publicAdmission: {
|
|
360
|
+
messaging: messagingAdmissionDigest(binding.id, participant.id),
|
|
361
|
+
provenance: { kind: 'messaging', channel: 'whatsapp' },
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
persist: custody.persist,
|
|
366
|
+
remove: custody.remove,
|
|
367
|
+
}, button?.id);
|
|
368
|
+
ledger = outcome.kind === 'reply' || outcome.kind === 'model' ? outcome.ledger : undefined;
|
|
369
|
+
}
|
|
370
|
+
if (outcome.kind === 'ignored') {
|
|
371
|
+
// An unknown, stale or foreign token: no reply, no model call, one content-free code.
|
|
372
|
+
await this.channels.fail(binding.id, event.id, event.lease, outcome.code, 'cancelled');
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
let answer;
|
|
376
|
+
if (outcome.kind === 'reply')
|
|
377
|
+
answer = outcome.reply;
|
|
378
|
+
else {
|
|
379
|
+
let text = '', failure;
|
|
380
|
+
await runAgentTurn(target, session, outcome.kind === 'model' ? outcome.utterance : inbound, context, this.deps, (result) => {
|
|
381
|
+
if (result.event === 'content' && typeof result.data.delta === 'string')
|
|
382
|
+
text += result.data.delta;
|
|
383
|
+
if (result.event === 'error')
|
|
384
|
+
failure = String(result.data.code ?? 'answer_failed');
|
|
385
|
+
}, undefined, undefined, undefined, false, {
|
|
386
|
+
binding: guarded,
|
|
387
|
+
beforeStep,
|
|
388
|
+
claimTool: (name) => this.channels.claimTool(binding.id, participant.id, name),
|
|
389
|
+
signal,
|
|
390
|
+
collection: {
|
|
391
|
+
open: ledger,
|
|
392
|
+
now,
|
|
393
|
+
retentionMs: historyRetention,
|
|
394
|
+
confirmationExpiryMs: WHATSAPP_CONFIRMATION_EXPIRY_MS,
|
|
395
|
+
save: async (opened) => {
|
|
396
|
+
await custody.persist(opened);
|
|
397
|
+
ledger = opened;
|
|
398
|
+
spec = collectionSpecFor(artifact, opened.interactionId, WHATSAPP_CONFIRMATION_EXPIRY_MS);
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
...(outcome.kind === 'model' ? { systemNote: outcome.note } : {}),
|
|
402
|
+
});
|
|
403
|
+
if (failure || !text.trim())
|
|
404
|
+
throw new ChannelError(failure ?? 'answer_unavailable');
|
|
405
|
+
const disclosure = participant.history.length === 0
|
|
406
|
+
? `I’m ${artifact.server.branding?.name ?? artifact.server.title}’s AI assistant.\n\n`
|
|
407
|
+
: '';
|
|
408
|
+
answer = disclosure + text.trim();
|
|
409
|
+
}
|
|
319
410
|
const bounded = answer.length <= 3500
|
|
320
411
|
? answer
|
|
321
412
|
: `${answer.slice(0, 3350).replace(/[\uD800-\uDBFF]$/u, '')}\n\nAsk me to expand on any part.`;
|
|
322
|
-
|
|
413
|
+
// Marked-private values never persist: history and the event row keep redacted text, and the
|
|
414
|
+
// full review outlives only its own delivery.
|
|
415
|
+
const scrub = (text) => ledger !== undefined && spec !== undefined ? redactPrivate(spec, ledger, text) : text;
|
|
416
|
+
await this.channels.complete(binding.id, event.id, event.lease, bounded, historyRetention, undefined, { user: scrub(heard), assistant: scrub(bounded) }, outcome.kind === 'reply' ? outcome.buttons : undefined);
|
|
323
417
|
}
|
|
324
418
|
catch (error) {
|
|
325
419
|
const code = error instanceof ChannelError ? error.code : 'answer_failed';
|
|
@@ -98,7 +98,7 @@ class NativeRecordConnector {
|
|
|
98
98
|
idempotencyKey: key,
|
|
99
99
|
payload,
|
|
100
100
|
...(publicInput ? { publicInput: true } : {}),
|
|
101
|
-
origin:
|
|
101
|
+
origin: trustedOrigin(call),
|
|
102
102
|
actorSubject,
|
|
103
103
|
};
|
|
104
104
|
const completed = await store.probeRequest(request);
|
|
@@ -113,8 +113,10 @@ class NativeRecordConnector {
|
|
|
113
113
|
if (publicInput) {
|
|
114
114
|
if (!installation.intakeActive || this.dependencies.publicIntakeEnabled === false)
|
|
115
115
|
return fail(call, 503, 'Public record intake is paused.');
|
|
116
|
-
if (call.publicAdmission === undefined ||
|
|
117
|
-
|
|
116
|
+
if (call.publicAdmission === undefined ||
|
|
117
|
+
(!call.publicAdmission.network && !call.publicAdmission.messaging))
|
|
118
|
+
return fail(call, 503, 'Trusted network or messaging admission context is required for public record writes.');
|
|
119
|
+
const { provenance: _provenance, ...buckets } = call.publicAdmission;
|
|
118
120
|
const quota = await admitPublicRecord({
|
|
119
121
|
counters: this.dependencies.counters,
|
|
120
122
|
surfaceId: installation.publicId,
|
|
@@ -122,7 +124,7 @@ class NativeRecordConnector {
|
|
|
122
124
|
key: `solution-intake:logical:${installation.publicId}:${idempotencyDigest(key)}`,
|
|
123
125
|
fingerprint: requestFingerprint(request),
|
|
124
126
|
},
|
|
125
|
-
buckets
|
|
127
|
+
buckets,
|
|
126
128
|
now: this.dependencies.now?.() ?? new Date(),
|
|
127
129
|
});
|
|
128
130
|
if (!quota.allowed)
|
|
@@ -196,6 +198,19 @@ class NativeRecordConnector {
|
|
|
196
198
|
: { ok: true, record: recordToWire(result.record) };
|
|
197
199
|
}
|
|
198
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Record provenance comes only from the trusted call context (ADR 0240): a messaging channel names
|
|
203
|
+
* itself through `publicAdmission.provenance` and must arrive with its participant bucket; anything
|
|
204
|
+
* a caller claims inside tool input has no effect.
|
|
205
|
+
*/
|
|
206
|
+
function trustedOrigin(call) {
|
|
207
|
+
const provenance = call.publicAdmission?.provenance;
|
|
208
|
+
if (provenance === undefined)
|
|
209
|
+
return { kind: 'mcp', reference: 'native-tool' };
|
|
210
|
+
if (!call.publicAdmission?.messaging)
|
|
211
|
+
return fail(call, 503, 'Messaging provenance requires a trusted participant admission bucket.');
|
|
212
|
+
return { kind: 'messaging', reference: provenance.channel };
|
|
213
|
+
}
|
|
199
214
|
function receipt(record) {
|
|
200
215
|
return { ok: true, recordId: record.id, revision: record.revision };
|
|
201
216
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readJsonBody, sendJson } from '@noodle-borg/transport-http';
|
|
2
|
-
import { APPLICATION_DRAFT_LIMITS, ApplicationDraftCreateRequestSchema, ApplicationDraftDiffRequestSchema, ApplicationDraftDiffResponseSchema, ApplicationDraftEditRequestSchema, ApplicationDraftHistoryResponseSchema, ApplicationDraftListResponseSchema, ApplicationDraftResponseSchema, ApplicationDraftRevisionRequestSchema, ApplicationDraftUndoRequestSchema, } from '@noodle-borg/wire-contracts';
|
|
3
|
-
import { ApplicationDraftError } from '../application-drafts/contracts.js';
|
|
2
|
+
import { APPLICATION_DRAFT_LIMITS, ApplicationDraftCreateRequestSchema, ApplicationDraftDiffRequestSchema, ApplicationDraftDiffResponseSchema, ApplicationDraftEditRequestSchema, ApplicationDraftHistoryResponseSchema, ApplicationDraftListResponseSchema, ApplicationDraftResponseSchema, ApplicationDraftRevisionRequestSchema, ApplicationDraftUndoRequestSchema, ApplicationDraftValidationResponseSchema, } from '@noodle-borg/wire-contracts';
|
|
3
|
+
import { ApplicationDraftError, DraftValidationUnavailableError, } from '../application-drafts/contracts.js';
|
|
4
4
|
import { applicationDraftMethods, parseApplicationDraftPath } from '../application-drafts/paths.js';
|
|
5
5
|
import { admitBusinessTarget, authorizeBusinessApi } from '../business-api-admission.js';
|
|
6
6
|
import { requestIdempotencyKey } from './business-information-request.js';
|
|
@@ -11,6 +11,14 @@ export async function handleApplicationDraftRoute(req, res, url, deps) {
|
|
|
11
11
|
await handle(req, res, url, deps);
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
|
+
if (error instanceof DraftValidationUnavailableError) {
|
|
15
|
+
if (error.code === 'busy')
|
|
16
|
+
res.setHeader('retry-after', '2');
|
|
17
|
+
return sendJson(res, error.code === 'busy' ? 429 : 503, {
|
|
18
|
+
code: error.code === 'busy' ? 'validation_busy' : 'validation_unavailable',
|
|
19
|
+
error: 'Draft validation is temporarily unavailable. Nothing was published.',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
14
22
|
if (error instanceof ApplicationDraftError) {
|
|
15
23
|
const status = error.code === 'forbidden'
|
|
16
24
|
? 403
|
|
@@ -94,6 +102,40 @@ async function handle(req, res, url, deps) {
|
|
|
94
102
|
const body = await readJsonBody(req, Math.min(deps.maxBody, APPLICATION_DRAFT_LIMITS.totalBytes * 6 + 32_768));
|
|
95
103
|
if (!body.ok)
|
|
96
104
|
return sendJson(res, body.status, { error: body.error });
|
|
105
|
+
if (id && action === 'validate' && req.method === 'POST') {
|
|
106
|
+
const parsed = ApplicationDraftRevisionRequestSchema.safeParse(body.value);
|
|
107
|
+
if (!parsed.success)
|
|
108
|
+
return invalid(res);
|
|
109
|
+
if (!deps.compiler)
|
|
110
|
+
return sendJson(res, 503, {
|
|
111
|
+
code: 'validation_unavailable',
|
|
112
|
+
error: 'Draft validation is not configured. Nothing was published.',
|
|
113
|
+
});
|
|
114
|
+
const input = { scope, id, actorSubject, expectedRevision: parsed.data.expectedRevision };
|
|
115
|
+
const draft = await deps.drafts.forValidation(input);
|
|
116
|
+
const result = await deps.compiler.compile(draft.source);
|
|
117
|
+
// Do not hold a database lock during customer compilation. Recheck under the authority lock afterwards.
|
|
118
|
+
const current = await deps.drafts.forValidation(input);
|
|
119
|
+
if (current.sourceDigest !== draft.sourceDigest ||
|
|
120
|
+
(result.ok && result.sourceDigest !== draft.sourceDigest))
|
|
121
|
+
throw new ApplicationDraftError('revision_conflict', current.revision);
|
|
122
|
+
const validation = {
|
|
123
|
+
draftId: id,
|
|
124
|
+
revision: draft.revision,
|
|
125
|
+
sourceDigest: draft.sourceDigest,
|
|
126
|
+
check: 'source-and-manifest',
|
|
127
|
+
published: false,
|
|
128
|
+
...(result.ok
|
|
129
|
+
? {
|
|
130
|
+
status: 'valid',
|
|
131
|
+
compilerDigest: result.compilerDigest,
|
|
132
|
+
artifactDigest: result.artifactDigest,
|
|
133
|
+
issues: [],
|
|
134
|
+
}
|
|
135
|
+
: { status: 'invalid', issues: result.issues }),
|
|
136
|
+
};
|
|
137
|
+
return sendJson(res, 200, ApplicationDraftValidationResponseSchema.parse({ ok: true, data: { validation } }));
|
|
138
|
+
}
|
|
97
139
|
if (id !== undefined && req.method === 'DELETE') {
|
|
98
140
|
const parsed = ApplicationDraftRevisionRequestSchema.safeParse(body.value);
|
|
99
141
|
if (!parsed.success)
|
|
@@ -94,6 +94,9 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
94
94
|
...(pageContext === undefined ? {} : { pageContext }),
|
|
95
95
|
...(modelContext === undefined ? {} : { modelContext }),
|
|
96
96
|
}),
|
|
97
|
+
...(transport?.systemNote === undefined
|
|
98
|
+
? []
|
|
99
|
+
: [{ role: 'system', content: transport.systemNote }]),
|
|
97
100
|
...session.history,
|
|
98
101
|
{ role: 'user', content: message },
|
|
99
102
|
];
|
|
@@ -272,6 +275,7 @@ export async function runAgentTurn(target, session, message, context, deps, emit
|
|
|
272
275
|
context,
|
|
273
276
|
session,
|
|
274
277
|
claimTool: transport?.claimTool ?? (async () => false),
|
|
278
|
+
...(transport?.collection === undefined ? {} : { collection: transport.collection }),
|
|
275
279
|
})
|
|
276
280
|
: await dispatchAssistantTool({
|
|
277
281
|
artifact: target.served.artifact,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { applySecurityHeaders, enforceHttps } from '@noodle-borg/transport-http';
|
|
2
|
+
import { parseApplicationDraftPath } from '../application-drafts/paths.js';
|
|
3
|
+
import { parseBusinessWorkspacePath } from '../business-workspaces/paths.js';
|
|
4
|
+
import { handleApplicationDraftRoute, } from './application-drafts.js';
|
|
5
|
+
import { handleBusinessWorkspaceRoute } from './business-workspaces.js';
|
|
6
|
+
/** Explicit versioned authority, independent of the managed-record switch. */
|
|
7
|
+
export function dispatchBusinessAuthoringRoutes(req, res, url, authoring, deps) {
|
|
8
|
+
if (!authoring)
|
|
9
|
+
return false;
|
|
10
|
+
const workspace = url.pathname === '/v1/me/business-workspaces' || parseBusinessWorkspacePath(url.pathname);
|
|
11
|
+
if (!workspace && !parseApplicationDraftPath(url.pathname))
|
|
12
|
+
return false;
|
|
13
|
+
applySecurityHeaders(res, deps.tls);
|
|
14
|
+
if (!enforceHttps(req, res, deps.tls)) {
|
|
15
|
+
const merged = { ...deps, ...authoring };
|
|
16
|
+
if (workspace)
|
|
17
|
+
void handleBusinessWorkspaceRoute(req, res, url, merged);
|
|
18
|
+
else
|
|
19
|
+
void handleApplicationDraftRoute(req, res, url, merged);
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=business-authoring-dispatch.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { sendJson } from '@noodle-borg/transport-http';
|
|
2
|
+
import { admitBusinessTarget, authorizeBusinessApi } from '../business-api-admission.js';
|
|
3
|
+
import { businessGrantAllows, } from '../business-information/portable.js';
|
|
4
|
+
import { sendForbidden } from '../http-util.js';
|
|
5
|
+
export async function requireIdentity(req, res, deps) {
|
|
6
|
+
return authorizeBusinessApi(req, res, deps);
|
|
7
|
+
}
|
|
8
|
+
export async function requireInstallationGrant(res, ref, identity, deps, permission) {
|
|
9
|
+
if (ref.installationId === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
const installation = await deps.store.getInstallationById(ref.org, ref.installationId);
|
|
12
|
+
if (installation === undefined) {
|
|
13
|
+
sendJson(res, 404, { error: 'installation not found' });
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const grant = await resolveBusinessStaffGrant(deps, installation.scope, identity.subject);
|
|
17
|
+
if (grant?.revokedAt !== undefined || grant === undefined) {
|
|
18
|
+
sendForbidden(res, 'business grant required');
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (permission && !businessGrantAllows(grant, permission)) {
|
|
22
|
+
sendForbidden(res, `business permission required: ${permission}`);
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
if (!(await admitBusinessTarget(res, installation.scope)))
|
|
26
|
+
return undefined;
|
|
27
|
+
return { installation, scope: installation.scope, grant };
|
|
28
|
+
}
|
|
29
|
+
export async function resolveBusinessStaffGrant(deps, scope, subject) {
|
|
30
|
+
return deps.store.staff.resolve(scope, subject);
|
|
31
|
+
}
|
|
32
|
+
export async function requireInstallationPermission(res, ref, identity, permission, deps) {
|
|
33
|
+
return requireInstallationGrant(res, ref, identity, deps, permission);
|
|
34
|
+
}
|
|
35
|
+
/** Local store effects only: respond after the authority transaction commits. */
|
|
36
|
+
export async function runBusinessStaffOperation(deps, scope, actor, permission, operation) {
|
|
37
|
+
return deps.store.staff.run(scope, actor, permission, operation);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=business-information-access.js.map
|
|
@@ -57,7 +57,7 @@ export async function handleApplicationActivity(req, res, url, ref, deps) {
|
|
|
57
57
|
body,
|
|
58
58
|
reviewer: identity.subject,
|
|
59
59
|
canEdit: businessGrantAllows(authorized.grant, 'installation:administer'),
|
|
60
|
-
});
|
|
60
|
+
}, (operation) => deps.store.staff.run(authorized.scope, identity.subject, permission, operation));
|
|
61
61
|
if ('audit' in result)
|
|
62
62
|
await deps.audit?.emit({
|
|
63
63
|
...result.audit,
|
|
@@ -37,12 +37,12 @@ export async function handleBusinessChannels(req, res, ref, deps) {
|
|
|
37
37
|
code: 'business_setup_required',
|
|
38
38
|
error: 'Complete business setup before publishing agent channels.',
|
|
39
39
|
});
|
|
40
|
-
const result = await deps.store.setIntakeState({
|
|
40
|
+
const result = await deps.store.staff.run(authorized.scope, identity.subject, 'installation:administer', () => deps.store.setIntakeState({
|
|
41
41
|
scope: authorized.scope,
|
|
42
42
|
expectedRevision: parsed.data.expectedRevision,
|
|
43
43
|
active: parsed.data.active,
|
|
44
44
|
actorSubject: identity.subject,
|
|
45
|
-
});
|
|
45
|
+
}));
|
|
46
46
|
if (!result.ok)
|
|
47
47
|
return mutationFailure(res, result);
|
|
48
48
|
installation = result.installation;
|
|
@@ -26,10 +26,16 @@ export async function handleApplicationConnections(req, res, ref, deps) {
|
|
|
26
26
|
try {
|
|
27
27
|
const targets = await deps.resolveConnectionTargets(authorized.installation);
|
|
28
28
|
const connections = deps.connections;
|
|
29
|
-
const projection = async () =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const projection = async () => {
|
|
30
|
+
const views = await Promise.all(targets.map((target) => connections.inspect(target, (operation) => deps.store.staff.run(authorized.scope, identity.subject, 'records:read', operation, () => new ConnectionError('connection_denied')))));
|
|
31
|
+
const current = await deps.store.staff.resolve(authorized.scope, identity.subject);
|
|
32
|
+
if (!businessGrantAllows(current, 'records:read') || current?.role === 'viewer')
|
|
33
|
+
throw new ConnectionError('connection_denied');
|
|
34
|
+
return {
|
|
35
|
+
connections: views,
|
|
36
|
+
canEdit: businessGrantAllows(current, 'installation:administer'),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
33
39
|
if (!mutation)
|
|
34
40
|
return sendJson(res, 200, ApplicationConnectionsResponseSchema.parse({ ok: true, data: await projection() }));
|
|
35
41
|
const target = targets.find((item) => item.key.connectionId === ref.connectionId);
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { OrganizationAgreementError } from '@noodle-borg/control-plane/portable';
|
|
2
2
|
import { sendJson } from '@noodle-borg/transport-http';
|
|
3
|
-
import { parseApplicationDraftPath } from '../application-drafts/paths.js';
|
|
4
3
|
import { BusinessNoticeError } from '../business-information/business-notice.js';
|
|
5
4
|
import { parseBusinessPagePath } from '../business-information/business-page-paths.js';
|
|
6
5
|
import { InstallationCapacityError } from '../business-information/installation-capacity.js';
|
|
7
6
|
import { NativeStorageLimitError } from '../business-information/native-storage-budget.js';
|
|
8
7
|
import { SourceCredentialError } from '../business-information/source-credential-fence.js';
|
|
9
8
|
import { SourceCapacityError } from '../business-information/source-custody-budget.js';
|
|
9
|
+
import { BusinessWorkspaceError } from '../business-workspaces/contracts.js';
|
|
10
10
|
import { respondRouteError } from '../http-util.js';
|
|
11
|
-
import { handleApplicationDraftRoute } from './application-drafts.js';
|
|
12
11
|
import { handleBusinessGrants, handleManagedRecords, handleSolutionCatalog, handleSolutionInstallations, } from './business-information.js';
|
|
13
12
|
import { handleApplicationActivity, } from './business-information-activity.js';
|
|
14
13
|
import { handleBusinessChannels, } from './business-information-channels.js';
|
|
@@ -22,6 +21,7 @@ import { handleBusinessInformationReaderFloor } from './business-information-rea
|
|
|
22
21
|
import { handleBusinessSettings } from './business-information-settings.js';
|
|
23
22
|
import { handleCollectionSource } from './business-information-source.js';
|
|
24
23
|
import { handleBusinessInvitationAccept, handleBusinessInvitations, handleEligibleBusinessAssignees, handleMySolutionInstallations, } from './business-information-staff.js';
|
|
24
|
+
import { handleNativeRecordLifecycle } from './business-native-lifecycle.js';
|
|
25
25
|
export function dispatchBusinessInformationRoutes(req, res, url, deps) {
|
|
26
26
|
const pageRef = parseBusinessPagePath(url.pathname);
|
|
27
27
|
const publicPage = /^\/v1\/business-pages\/(sol_[a-f0-9]{32})$/.exec(url.pathname);
|
|
@@ -37,10 +37,6 @@ export function dispatchBusinessInformationRoutes(req, res, url, deps) {
|
|
|
37
37
|
? handleBusinessPage(req, res, pageRef, pageDeps)
|
|
38
38
|
: handlePublicBusinessPage(req, res, publicPage[1], pageDeps));
|
|
39
39
|
}
|
|
40
|
-
if (deps.authoring && parseApplicationDraftPath(url.pathname)) {
|
|
41
|
-
const draftDeps = { ...deps, ...deps.authoring, maxBody: deps.maxDraftBody ?? deps.maxBody };
|
|
42
|
-
return run(req, res, deps, () => handleApplicationDraftRoute(req, res, url, draftDeps));
|
|
43
|
-
}
|
|
44
40
|
const agreement = /^\/v1\/orgs\/([a-z0-9][a-z0-9-]{0,62})\/agreement$/.exec(url.pathname);
|
|
45
41
|
if (agreement?.[1])
|
|
46
42
|
return run(req, res, deps, () => handleOrganizationAgreement(req, res, agreement[1], deps));
|
|
@@ -78,6 +74,8 @@ export function dispatchBusinessInformationRoutes(req, res, url, deps) {
|
|
|
78
74
|
const installationRef = parseSolutionInstallationPath(url.pathname);
|
|
79
75
|
if (installationRef === undefined)
|
|
80
76
|
return false;
|
|
77
|
+
if (installationRef.action === 'record-lifecycle')
|
|
78
|
+
return run(req, res, deps, () => handleNativeRecordLifecycle(req, res, installationRef, deps));
|
|
81
79
|
if (installationRef.action === 'notice')
|
|
82
80
|
return run(req, res, deps, () => handleBusinessNotice(req, res, installationRef, deps));
|
|
83
81
|
if ((installationRef.action === 'activity' || installationRef.action === 'coordination') &&
|
|
@@ -131,6 +129,13 @@ function run(req, res, deps, handle) {
|
|
|
131
129
|
return true;
|
|
132
130
|
}
|
|
133
131
|
function respondBusinessError(deps, res, event, error) {
|
|
132
|
+
if (error instanceof BusinessWorkspaceError) {
|
|
133
|
+
sendJson(res, error.code === 'forbidden' ? 403 : 409, {
|
|
134
|
+
error: 'Business access changed. Refresh before continuing.',
|
|
135
|
+
code: error.code,
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
134
139
|
if (error instanceof OrganizationAgreementError || error instanceof BusinessNoticeError) {
|
|
135
140
|
const forbidden = error.code === 'agreement_owner_required' || error.code === 'business_notice_forbidden';
|
|
136
141
|
sendJson(res, forbidden ? 403 : 409, { error: error.message, code: error.code });
|
|
@@ -11,12 +11,14 @@ export function stableInstallationId(org, app, env) {
|
|
|
11
11
|
/** Read current activation authority; intake is an independent operator setting. */
|
|
12
12
|
export async function installationProjection(installation, role, identity, deps) {
|
|
13
13
|
const state = (await deps.readInstallationActivation?.(installation)) ?? 'unavailable';
|
|
14
|
+
const access = await deps.workspaces?.resolveAccess(installation.scope.org, identity.subject);
|
|
14
15
|
const canRetry = state === 'pending' &&
|
|
15
16
|
deps.activateInstallation !== undefined &&
|
|
16
17
|
role === 'administrator' &&
|
|
17
18
|
(await canManageMembers(deps.controlPlane, installation.scope.org, identity));
|
|
18
19
|
return {
|
|
19
|
-
...installationToWire(installation, role),
|
|
20
|
+
...installationToWire(installation, access?.role ?? role),
|
|
21
|
+
...(access ? { authorityVersion: 1 } : {}),
|
|
20
22
|
activation: state === 'pending' ? { state, canRetry } : { state, canRetry: false },
|
|
21
23
|
};
|
|
22
24
|
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { readJsonBody, sendJson } from '@noodle-borg/transport-http';
|
|
2
2
|
import { BusinessNoticeResponseSchema, BusinessNoticeSaveRequestSchema, OrganizationAgreementAcceptRequestSchema, OrganizationAgreementResponseSchema, } from '@noodle-borg/wire-contracts';
|
|
3
3
|
import { admitBusinessTarget } from '../business-api-admission.js';
|
|
4
|
+
import { businessGrantAllows } from '../business-information/model.js';
|
|
4
5
|
import { sendForbidden } from '../http-util.js';
|
|
5
6
|
import { invalid, methodNotAllowed, requireIdentity, requireInstallationGrant, requireInstallationPermission, } from './business-information.js';
|
|
6
7
|
import { canManageMembers } from './org-admin.js';
|
|
7
8
|
export async function handleOrganizationAgreement(req, res, org, deps) {
|
|
8
9
|
if (req.method !== 'GET' && req.method !== 'POST')
|
|
9
10
|
return methodNotAllowed(res);
|
|
11
|
+
res.setHeader('cache-control', 'private, no-store');
|
|
10
12
|
const identity = await requireIdentity(req, res, deps);
|
|
11
13
|
if (identity === false)
|
|
12
14
|
return;
|
|
13
|
-
|
|
15
|
+
const selected = await deps.workspaces?.authorize(org, identity.subject, 'settings:manage');
|
|
16
|
+
if (selected === 'denied' ||
|
|
17
|
+
(selected !== 'allowed' && !(await canManageMembers(deps.controlPlane, org, identity))))
|
|
14
18
|
return sendForbidden(res, 'organization owner required');
|
|
15
19
|
if (!(await admitBusinessTarget(res, { org })))
|
|
16
20
|
return;
|
|
@@ -62,7 +66,7 @@ export async function handleBusinessNotice(req, res, ref, deps) {
|
|
|
62
66
|
data: {
|
|
63
67
|
revision: record?.revision ?? 0,
|
|
64
68
|
notice: record?.notice ?? null,
|
|
65
|
-
canEdit: authorized.grant
|
|
69
|
+
canEdit: businessGrantAllows(authorized.grant, 'installation:administer'),
|
|
66
70
|
},
|
|
67
71
|
}));
|
|
68
72
|
}
|
|
@@ -59,7 +59,7 @@ export async function handleBusinessPage(req, res, ref, deps) {
|
|
|
59
59
|
if (!authorized)
|
|
60
60
|
return;
|
|
61
61
|
try {
|
|
62
|
-
let record = await deps.store.pages.get(authorized.scope);
|
|
62
|
+
let record = await deps.store.staff.run(authorized.scope, identity.subject, 'installation:administer', () => deps.store.pages.get(authorized.scope), () => new BusinessPageError('business_page_forbidden'));
|
|
63
63
|
if (req.method !== 'GET') {
|
|
64
64
|
const body = await readJsonBody(req, Math.min(deps.maxBody, 256 * 1024));
|
|
65
65
|
if (!body.ok)
|
|
@@ -27,7 +27,7 @@ export function parseSolutionInstallationPath(pathname) {
|
|
|
27
27
|
...(connectionAction === undefined ? {} : { connectionAction }),
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
const application = /^\/v1\/orgs\/([^/]+)\/solution-installations\/([^/]+)\/(activate|settings|notice|channels|operations\/coordination(?:\/resolve)?|activity(?:\/(?:settings|export|preview))?)$/.exec(pathname);
|
|
30
|
+
const application = /^\/v1\/orgs\/([^/]+)\/solution-installations\/([^/]+)\/(activate|settings|notice|record-lifecycle|channels|operations\/coordination(?:\/resolve)?|activity(?:\/(?:settings|export|preview))?)$/.exec(pathname);
|
|
31
31
|
if (application !== null) {
|
|
32
32
|
const org = decoded(application, 1);
|
|
33
33
|
const installationId = decoded(application, 2);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sendJson } from '@noodle-borg/transport-http';
|
|
2
2
|
import { PayloadValidationError, } from '../business-information/portable.js';
|
|
3
|
+
import { runBusinessStaffOperation } from './business-information-access.js';
|
|
3
4
|
export async function createNativeRecord(res, deps, input) {
|
|
4
5
|
try {
|
|
5
6
|
const request = {
|
|
@@ -11,13 +12,17 @@ export async function createNativeRecord(res, deps, input) {
|
|
|
11
12
|
actorSubject: input.actorSubject,
|
|
12
13
|
};
|
|
13
14
|
if (input.admit) {
|
|
14
|
-
const receipt =
|
|
15
|
+
const receipt = input.origin.kind === 'portal'
|
|
16
|
+
? await runBusinessStaffOperation(deps, request.scope, input.actorSubject, 'records:create', () => deps.store.probeRequest(request))
|
|
17
|
+
: await deps.store.probeRequest(request);
|
|
15
18
|
if (receipt.disposition !== 'missing')
|
|
16
19
|
return receipt;
|
|
17
20
|
if (!(await input.admit()))
|
|
18
21
|
return undefined;
|
|
19
22
|
}
|
|
20
|
-
return
|
|
23
|
+
return input.origin.kind === 'portal'
|
|
24
|
+
? await runBusinessStaffOperation(deps, request.scope, input.actorSubject, 'records:create', () => deps.store.createRequest(request))
|
|
25
|
+
: await deps.store.createRequest(request);
|
|
21
26
|
}
|
|
22
27
|
catch (error) {
|
|
23
28
|
if (!(error instanceof PayloadValidationError))
|