@noodleseed/one 0.177.0 → 0.178.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.
Files changed (71) hide show
  1. package/dist/commands/solutions-activity.d.ts.map +1 -1
  2. package/dist/commands/solutions-activity.js +9 -35
  3. package/dist/commands/solutions-activity.js.map +1 -1
  4. package/dist/commands/solutions-conversations.d.ts.map +1 -1
  5. package/dist/commands/solutions-conversations.js +60 -6
  6. package/dist/commands/solutions-conversations.js.map +1 -1
  7. package/dist/commands/solutions-history.d.ts +9 -0
  8. package/dist/commands/solutions-history.d.ts.map +1 -0
  9. package/dist/commands/solutions-history.js +166 -0
  10. package/dist/commands/solutions-history.js.map +1 -0
  11. package/dist/commands/solutions-ops.d.ts.map +1 -1
  12. package/dist/commands/solutions-ops.js +3 -0
  13. package/dist/commands/solutions-ops.js.map +1 -1
  14. package/dist/commands/whatsapp-ops.d.ts.map +1 -1
  15. package/dist/commands/whatsapp-ops.js +0 -7
  16. package/dist/commands/whatsapp-ops.js.map +1 -1
  17. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
  18. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +14 -0
  19. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  20. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +6 -0
  21. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +5 -2
  22. package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
  23. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +18 -0
  24. package/node_modules/@noodle-borg/authoring/dist/assistant.js +14 -1
  25. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +0 -3
  26. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-conversations.js +50 -5
  27. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-history.js +82 -0
  28. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solutions.js +3 -42
  29. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +8 -0
  30. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +7 -0
  31. package/node_modules/@noodle-borg/service/dist/application-activity.js +76 -21
  32. package/node_modules/@noodle-borg/service/dist/application-history-settings.js +108 -0
  33. package/node_modules/@noodle-borg/service/dist/assistant-composition.js +11 -2
  34. package/node_modules/@noodle-borg/service/dist/channels/composition.js +10 -1
  35. package/node_modules/@noodle-borg/service/dist/channels/provider.js +13 -13
  36. package/node_modules/@noodle-borg/service/dist/channels/runtime.js +31 -5
  37. package/node_modules/@noodle-borg/service/dist/conversation-history/capture.js +63 -7
  38. package/node_modules/@noodle-borg/service/dist/conversation-history/contracts.js +13 -0
  39. package/node_modules/@noodle-borg/service/dist/conversation-history/customer.js +118 -0
  40. package/node_modules/@noodle-borg/service/dist/conversation-history/operator.js +133 -24
  41. package/node_modules/@noodle-borg/service/dist/routes/assistant-conversations.js +52 -0
  42. package/node_modules/@noodle-borg/service/dist/routes/assistant-dispatch.js +6 -0
  43. package/node_modules/@noodle-borg/service/dist/routes/assistant-elevation.js +3 -0
  44. package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-stream.js +2 -1
  45. package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +6 -11
  46. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +6 -0
  47. package/node_modules/@noodle-borg/service/dist/routes/assistant-route-http.js +16 -0
  48. package/node_modules/@noodle-borg/service/dist/routes/assistant.js +5 -17
  49. package/node_modules/@noodle-borg/service/dist/routes/business-information-activity.js +6 -5
  50. package/node_modules/@noodle-borg/service/dist/routes/business-information-conversations.js +36 -11
  51. package/node_modules/@noodle-borg/service/dist/routes/business-information-dispatch.js +4 -2
  52. package/node_modules/@noodle-borg/service/dist/routes/business-information-paths.js +4 -2
  53. package/node_modules/@noodle-borg/service/dist/routes/business-information.js +4 -0
  54. package/node_modules/@noodle-borg/service/dist/routes/whatsapp.js +0 -10
  55. package/node_modules/@noodle-borg/service/dist/service-history-projections.js +32 -4
  56. package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.js +32 -0
  57. package/node_modules/@noodle-borg/service/dist/service.js +4 -3
  58. package/node_modules/@noodle-borg/service/package.json +1 -1
  59. package/node_modules/@noodle-borg/wire-contracts/dist/application-activity.d.ts +0 -28
  60. package/node_modules/@noodle-borg/wire-contracts/dist/application-activity.js +0 -18
  61. package/node_modules/@noodle-borg/wire-contracts/dist/application-history-settings.d.ts +190 -0
  62. package/node_modules/@noodle-borg/wire-contracts/dist/application-history-settings.js +92 -0
  63. package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +3 -0
  64. package/node_modules/@noodle-borg/wire-contracts/dist/assistant.js +6 -0
  65. package/node_modules/@noodle-borg/wire-contracts/dist/conversation-history.d.ts +176 -0
  66. package/node_modules/@noodle-borg/wire-contracts/dist/conversation-history.js +74 -2
  67. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +1 -0
  68. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +1 -0
  69. package/node_modules/@noodleseed/assistant/README.md +19 -0
  70. package/node_modules/@noodleseed/assistant/package.json +1 -1
  71. package/package.json +2 -2
@@ -1,25 +1,38 @@
1
1
  import { readJsonBody, sendJson } from '@noodle-borg/transport-http';
2
+ import { ConversationReviewRequestSchema } from '@noodle-borg/wire-contracts';
2
3
  import { ConversationHistoryError, } from '../conversation-history/operator.js';
3
4
  import { requireIdentity, requireInstallationPermission, } from './business-information.js';
4
- /** ADR 0241 decision 10: readers see text; only export and erasure need Owner/Administrator authority. */
5
+ /**
6
+ * ADR 0241 decision 10: readers see text; reviewing and noting need the record-handling permissions an
7
+ * Operator holds; only export and erasure need Owner/Administrator authority.
8
+ */
5
9
  const PERMISSIONS = {
6
10
  list: 'records:read',
7
11
  show: 'records:read',
8
12
  export: 'records:export',
9
13
  forget: 'records:delete',
10
14
  };
15
+ const METHODS = { list: ['GET'], show: ['GET', 'PATCH'], export: ['GET'], forget: ['POST'] };
11
16
  export async function handleApplicationConversations(req, res, url, ref, deps) {
12
- const action = ref.conversationAction ?? 'list';
13
- const method = action === 'forget' ? 'POST' : 'GET';
14
- if (req.method !== method) {
15
- res.setHeader('allow', method);
17
+ const route = ref.conversationAction ?? 'list';
18
+ const methods = METHODS[route];
19
+ if (!methods.includes(req.method ?? '')) {
20
+ res.setHeader('allow', methods.join(', '));
16
21
  return sendJson(res, 405, { error: 'method not allowed' });
17
22
  }
23
+ const action = req.method === 'PATCH' ? 'review' : route;
18
24
  const identity = await requireIdentity(req, res, deps);
19
25
  if (identity === false)
20
26
  return;
21
- const permission = PERMISSIONS[action];
22
- const authorize = () => requireInstallationPermission(res, ref, identity, permission, deps);
27
+ // A review's permissions follow its fields, so it is read-authorized before its body is parsed.
28
+ let permissions = [PERMISSIONS[route]];
29
+ const authorize = async () => {
30
+ const [last = PERMISSIONS[route], ...earlier] = [...permissions].reverse();
31
+ for (const permission of earlier)
32
+ if (!(await requireInstallationPermission(res, ref, identity, permission, deps)))
33
+ return false;
34
+ return requireInstallationPermission(res, ref, identity, last, deps);
35
+ };
23
36
  const authorized = await authorize();
24
37
  if (!authorized)
25
38
  return;
@@ -28,22 +41,34 @@ export async function handleApplicationConversations(req, res, url, ref, deps) {
28
41
  if (!deps.conversations)
29
42
  throw new ConversationHistoryError('conversation_unavailable');
30
43
  let body;
31
- if (method === 'POST') {
44
+ if (req.method !== 'GET') {
32
45
  const parsed = await readJsonBody(req, deps.maxBody);
33
46
  if (!parsed.ok)
34
47
  return sendJson(res, parsed.status, { error: parsed.error });
35
48
  body = parsed.value;
49
+ if (action === 'review') {
50
+ const review = ConversationReviewRequestSchema.safeParse(body);
51
+ if (!review.success)
52
+ throw new ConversationHistoryError('conversation_invalid');
53
+ permissions = [
54
+ ...(review.data.reviewStatus === undefined ? [] : ['records:status']),
55
+ ...(review.data.note === undefined ? [] : ['records:note']),
56
+ ];
57
+ }
36
58
  if (!(await authorize()))
37
59
  return;
38
60
  }
61
+ const fence = permissions.at(-1) ?? PERMISSIONS[route];
39
62
  const result = await deps.conversations.project(authorized.scope, action, {
40
63
  parameters: [...url.searchParams.entries()],
41
64
  ...(ref.conversationId === undefined ? {} : { conversationId: ref.conversationId }),
42
65
  body,
43
- }, (operation) => deps.store.staff.run(authorized.scope, identity.subject, permission, operation));
44
- if ('audit' in result && result.audit)
66
+ actor: identity.subject,
67
+ ...(deps.forgetWhatsApp ? { forgetWorkingMemory: deps.forgetWhatsApp } : {}),
68
+ }, (operation) => deps.store.staff.run(authorized.scope, identity.subject, fence, operation));
69
+ for (const event of result.audits)
45
70
  await deps.audit?.emit({
46
- ...result.audit,
71
+ ...event,
47
72
  org: ref.org,
48
73
  app: authorized.scope.app,
49
74
  env: authorized.scope.env,
@@ -9,7 +9,7 @@ import { SourceCapacityError } from '../business-information/source-custody-budg
9
9
  import { BusinessWorkspaceError } from '../business-workspaces/contracts.js';
10
10
  import { respondRouteError } from '../http-util.js';
11
11
  import { handleBusinessGrants, handleManagedRecords, handleSolutionCatalog, handleSolutionInstallations, } from './business-information.js';
12
- import { handleApplicationActivity, } from './business-information-activity.js';
12
+ import { handleApplicationActivity } from './business-information-activity.js';
13
13
  import { handleBusinessChannels, } from './business-information-channels.js';
14
14
  import { handleApplicationConnectionCallback, handleApplicationConnections, } from './business-information-connections.js';
15
15
  import { handleApplicationConversations, } from './business-information-conversations.js';
@@ -79,7 +79,9 @@ export function dispatchBusinessInformationRoutes(req, res, url, deps) {
79
79
  return run(req, res, deps, () => handleNativeRecordLifecycle(req, res, installationRef, deps));
80
80
  if (installationRef.action === 'notice')
81
81
  return run(req, res, deps, () => handleBusinessNotice(req, res, installationRef, deps));
82
- if ((installationRef.action === 'activity' || installationRef.action === 'coordination') &&
82
+ if ((installationRef.action === 'activity' ||
83
+ installationRef.action === 'history' ||
84
+ installationRef.action === 'coordination') &&
83
85
  installationRef.collection === undefined)
84
86
  return run(req, res, deps, () => handleApplicationActivity(req, res, url, installationRef, deps));
85
87
  if (installationRef.action === 'conversations')
@@ -43,7 +43,7 @@ export function parseSolutionInstallationPath(pathname) {
43
43
  : { conversationAction: 'show', conversationId: segment }),
44
44
  };
45
45
  }
46
- const application = /^\/v1\/orgs\/([^/]+)\/solution-installations\/([^/]+)\/(activate|settings|notice|record-lifecycle|channels|operations\/coordination(?:\/resolve)?|activity(?:\/(?:settings|export|preview))?)$/.exec(pathname);
46
+ const application = /^\/v1\/orgs\/([^/]+)\/solution-installations\/([^/]+)\/(activate|settings|notice|record-lifecycle|channels|operations\/coordination(?:\/resolve)?|history\/settings|activity(?:\/(?:export|preview))?)$/.exec(pathname);
47
47
  if (application !== null) {
48
48
  const org = decoded(application, 1);
49
49
  const installationId = decoded(application, 2);
@@ -56,7 +56,9 @@ export function parseSolutionInstallationPath(pathname) {
56
56
  ? 'coordination'
57
57
  : application[3]?.startsWith('activity')
58
58
  ? 'activity'
59
- : application[3],
59
+ : application[3] === 'history/settings'
60
+ ? 'history'
61
+ : application[3],
60
62
  };
61
63
  }
62
64
  let match = /^\/v1\/orgs\/([^/]+)\/solution-installations$/.exec(pathname);
@@ -68,6 +68,10 @@ export async function handleSolutionInstallations(req, res, ref, deps) {
68
68
  code: 'installation_conflict',
69
69
  });
70
70
  }
71
+ // Only a created installation opts in (ADR 0241 decision 8), before activation can read the setting.
72
+ // Without a verified allowance it stays not enabled: recording nothing is the safe failure.
73
+ if (result.disposition === 'created')
74
+ await deps.activity?.initializeHistory(result.installation.scope).catch(() => false);
71
75
  const grant = await resolveBusinessStaffGrant(deps, result.installation.scope, identity.subject);
72
76
  if (grant === undefined || grant.revokedAt !== undefined) {
73
77
  return sendForbidden(res, 'business grant required');
@@ -209,16 +209,6 @@ async function operate(req, res, url, tenant, path, runtime, grants) {
209
209
  }
210
210
  return respond(res, wire.WhatsAppCooldownResponseSchema, await channels.cooldown(id, value));
211
211
  }
212
- if (kind === 'participants' &&
213
- value &&
214
- participant.test(value) &&
215
- action === 'forget' &&
216
- req.method === 'POST') {
217
- await body(req, wire.WhatsAppEmptyRequestSchema);
218
- await channels.forget(id, value, identity.subject, idempotency);
219
- await audit('conversation_forgotten');
220
- return sendJson(res, 200, { ok: true });
221
- }
222
212
  if (kind === 'events' &&
223
213
  (!value || eventId.test(value)) &&
224
214
  (read || (action === 'reconcile' && req.method === 'POST'))) {
@@ -1,24 +1,52 @@
1
1
  import { randomBytes } from 'node:crypto';
2
- import { ApplicationActivity } from './application-activity.js';
2
+ import { ActivityPolicyError, ApplicationActivity } from './application-activity.js';
3
+ import { historyDisabledSurfaces } from './application-history-settings.js';
3
4
  import { ApplicationConversations } from './conversation-history/operator.js';
4
5
  /** Installation history projections over the optional durable stores the service was given. */
5
- export function createHistoryProjections(options, moduleHost) {
6
+ export function createHistoryProjections(options, moduleHost, installations, registry) {
7
+ const history = options.conversationHistory;
6
8
  const activity = options.operationEvidence === undefined
7
9
  ? undefined
8
10
  : new ApplicationActivity({
9
11
  ...options.operationEvidence,
10
12
  allowance: async (org, request) => moduleHost.resolveActivityHistoryAllowance?.(org, request),
13
+ ...(history === undefined ? {} : { conversationHistory: history.store }),
14
+ historyDisabledSurfaces: async (scope) => historyDisabledSurfaces((await registry.getActiveByTenant(scope))?.served.artifact.server.assistant),
11
15
  });
12
- const history = options.conversationHistory;
16
+ const conversationPolicy = history?.policy ?? settingsConversationPolicy(installations, activity);
13
17
  const conversations = history === undefined
14
18
  ? undefined
15
19
  : new ApplicationConversations({
16
20
  store: history.store,
21
+ policy: conversationPolicy,
17
22
  identityKey: history.identityKey ?? randomBytes(32).toString('hex'),
18
23
  ...(options.clock === undefined
19
24
  ? {}
20
25
  : { now: () => options.clock?.().getTime() ?? Date.now() }),
21
26
  });
22
- return { activity, conversations };
27
+ return { activity, conversations, conversationPolicy };
28
+ }
29
+ /**
30
+ * Capture follows the installation history setting under the live plan allowance (ADR 0241). A tenant
31
+ * with no single installation, setting opt-in or verified allowance records nothing, and the read never
32
+ * creates a setting.
33
+ */
34
+ function settingsConversationPolicy(installations, activity) {
35
+ return async (tenant) => {
36
+ if (!installations || !activity)
37
+ return undefined;
38
+ const matching = (await installations.listInstallations(tenant.org)).filter((item) => item.scope.app === tenant.app && item.scope.env === tenant.env);
39
+ const installation = matching.length === 1 ? matching[0] : undefined;
40
+ if (!installation)
41
+ return undefined;
42
+ try {
43
+ return await activity.conversationPolicy(installation.scope);
44
+ }
45
+ catch (error) {
46
+ if (error instanceof ActivityPolicyError)
47
+ return undefined;
48
+ throw error;
49
+ }
50
+ };
23
51
  }
24
52
  //# sourceMappingURL=service-history-projections.js.map
@@ -24,6 +24,38 @@ export async function closeServiceResources(input) {
24
24
  if (errors.length > 0)
25
25
  throw new AggregateError(errors, 'service resource cleanup failed');
26
26
  }
27
+ /**
28
+ * Clean up after a failed boot without letting a cleanup failure replace the error that aborted startup:
29
+ * operators must see the bind or boot cause, while the cleanup failure is still logged.
30
+ */
31
+ export async function cleanupAfterStartupFailure(logger, phase, cleanup) {
32
+ try {
33
+ await cleanup();
34
+ }
35
+ catch (error) {
36
+ const code = firstErrorCode(error);
37
+ logger.error('service.startup_cleanup_failed', {
38
+ phase,
39
+ name: error instanceof Error ? error.name : 'unknown',
40
+ ...(code === undefined ? {} : { code }),
41
+ });
42
+ }
43
+ }
44
+ /** The first scalar driver/system code, searching nested cleanup aggregates depth-first. */
45
+ function firstErrorCode(error, depth = 0) {
46
+ if (typeof error !== 'object' || error === null || depth > 4)
47
+ return undefined;
48
+ if ('code' in error && typeof error.code === 'string')
49
+ return error.code;
50
+ if (!(error instanceof AggregateError))
51
+ return undefined;
52
+ for (const nested of error.errors) {
53
+ const code = firstErrorCode(nested, depth + 1);
54
+ if (code !== undefined)
55
+ return code;
56
+ }
57
+ return undefined;
58
+ }
27
59
  export function listenHttpServer(http, port, host) {
28
60
  return new Promise((resolve, reject) => {
29
61
  const onError = (error) => {
@@ -14,7 +14,7 @@ import { assistantRouteDependencies } from './assistant-composition.js';
14
14
  import { resolveBuildInfo } from './build-info.js';
15
15
  import { createBusinessInformationRuntime, resolvePrivateInstallationDefinition, } from './business-information-runtime.js';
16
16
  import { serviceCapabilityReport } from './capabilities.js';
17
- import { createWhatsAppRuntime } from './channels/composition.js';
17
+ import { createWhatsAppRuntime, whatsappBusinessRoutes } from './channels/composition.js';
18
18
  import { rejectIncompatibleCli } from './client-compatibility.js';
19
19
  import { createDataPlaneMembershipAuthorizer } from './data-plane-membership.js';
20
20
  import { DEVELOPER_MCP_PATH, handleDeveloperMcpRequest } from './developer-mcp/mount.js';
@@ -95,7 +95,7 @@ export function createServiceHandler(registry, options = {}) {
95
95
  activeAudit = audit;
96
96
  const { publicTenantRouting } = mcp.mcpRoutingOptions(options, controlPlane);
97
97
  const resolveEndpointOptions = (org) => mcp.endpointUrlOptionsForOrg(options, controlPlane, org);
98
- const { activity, conversations } = createHistoryProjections(options, moduleHost);
98
+ const { activity, conversations, conversationPolicy } = createHistoryProjections(options, moduleHost, businessInformationStore, registry);
99
99
  const withIntentMode = createIntentTargetResolver(intentSettings, intentPreviewOrgs);
100
100
  const { activateInstallation, readInstallationActivation, resolveRuntimeTarget, businessOnboarding, } = createApplicationServingRuntime(registry, providerOptions, controlPlane, audit, activity);
101
101
  const runtimeTarget = async (target) => target === undefined ? undefined : resolveRuntimeTarget(target);
@@ -192,6 +192,7 @@ export function createServiceHandler(registry, options = {}) {
192
192
  maxBody,
193
193
  serviceBase: (request) => options.publicBaseUrl ?? baseFromRequest(request, tls),
194
194
  logger,
195
+ conversationPolicy,
195
196
  });
196
197
  const whatsapp = createWhatsAppRuntime(options.whatsapp, assistantDeps, {
197
198
  allowance: moduleHost.resolveActivityHistoryAllowance,
@@ -302,7 +303,7 @@ export function createServiceHandler(registry, options = {}) {
302
303
  dispatchBusinessInformationRoutes(req, res, url, {
303
304
  pageOrigin: options.businessPageOrigin,
304
305
  pageServiceUrl: options.publicBaseUrl,
305
- ...(whatsapp ? { readWhatsApp: whatsapp.projection.bind(whatsapp) } : {}),
306
+ ...whatsappBusinessRoutes(whatsapp),
306
307
  store: businessInformationStore,
307
308
  ...(options.businessAuthoring ? { workspaces: options.businessAuthoring.workspaces } : {}),
308
309
  ...(businessOnboarding ? { businessOnboarding } : {}),
@@ -41,7 +41,7 @@
41
41
  "@noodle-borg/managed-capabilities": "0.0.0",
42
42
  "@modelcontextprotocol/sdk": "^1.29.0",
43
43
  "@noodle-borg/admission-limits": "0.0.0",
44
- "@noodle-borg/agent-kit": "0.107.0",
44
+ "@noodle-borg/agent-kit": "0.108.0",
45
45
  "@noodle-borg/app-package": "0.0.0",
46
46
  "@noodle-borg/assistant-gateway": "0.0.0",
47
47
  "@noodle-borg/auth": "0.0.0",
@@ -42,25 +42,6 @@ export declare const ApplicationActivityListResponseSchema: z.ZodObject<{
42
42
  historyDays: z.ZodNumber;
43
43
  }, z.core.$strict>;
44
44
  }, z.core.$strict>;
45
- export declare const ApplicationActivitySettingsSchema: z.ZodObject<{
46
- revision: z.ZodString;
47
- retentionDays: z.ZodNumber;
48
- maximumDays: z.ZodNumber;
49
- canEdit: z.ZodBoolean;
50
- }, z.core.$strict>;
51
- export declare const ApplicationActivitySettingsResponseSchema: z.ZodObject<{
52
- ok: z.ZodLiteral<true>;
53
- data: z.ZodObject<{
54
- revision: z.ZodString;
55
- retentionDays: z.ZodNumber;
56
- maximumDays: z.ZodNumber;
57
- canEdit: z.ZodBoolean;
58
- }, z.core.$strict>;
59
- }, z.core.$strict>;
60
- export declare const ApplicationActivitySettingsSaveRequestSchema: z.ZodObject<{
61
- expectedRevision: z.ZodString;
62
- retentionDays: z.ZodNumber;
63
- }, z.core.$strict>;
64
45
  /** Clients tolerate additive response fields; writes and server output remain strict. */
65
46
  export declare const ApplicationActivityListClientResponseSchema: z.ZodObject<{
66
47
  ok: z.ZodLiteral<true>;
@@ -87,15 +68,6 @@ export declare const ApplicationActivityListClientResponseSchema: z.ZodObject<{
87
68
  historyDays: z.ZodNumber;
88
69
  }, z.core.$strip>;
89
70
  }, z.core.$strip>;
90
- export declare const ApplicationActivitySettingsClientResponseSchema: z.ZodObject<{
91
- ok: z.ZodLiteral<true>;
92
- data: z.ZodObject<{
93
- revision: z.ZodString;
94
- retentionDays: z.ZodNumber;
95
- maximumDays: z.ZodNumber;
96
- canEdit: z.ZodBoolean;
97
- }, z.core.$strip>;
98
- }, z.core.$strip>;
99
71
  export declare const ApplicationActivityPreviewResponseSchema: z.ZodObject<{
100
72
  ok: z.ZodLiteral<true>;
101
73
  data: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -18,20 +18,6 @@ export const ApplicationActivityListResponseSchema = z.strictObject({
18
18
  historyDays: z.number().int().min(1).max(365),
19
19
  }),
20
20
  });
21
- export const ApplicationActivitySettingsSchema = z.strictObject({
22
- revision: z.string().regex(/^[a-f0-9]{64}$/),
23
- retentionDays: z.number().int().min(1).max(365),
24
- maximumDays: z.number().int().min(1).max(365),
25
- canEdit: z.boolean(),
26
- });
27
- export const ApplicationActivitySettingsResponseSchema = z.strictObject({
28
- ok: z.literal(true),
29
- data: ApplicationActivitySettingsSchema,
30
- });
31
- export const ApplicationActivitySettingsSaveRequestSchema = z.strictObject({
32
- expectedRevision: z.string().regex(/^[a-f0-9]{64}$/),
33
- retentionDays: z.number().int().min(1).max(365),
34
- });
35
21
  /** Clients tolerate additive response fields; writes and server output remain strict. */
36
22
  export const ApplicationActivityListClientResponseSchema = z.object({
37
23
  ok: z.literal(true),
@@ -45,10 +31,6 @@ export const ApplicationActivityListClientResponseSchema = z.object({
45
31
  .max(100),
46
32
  }),
47
33
  });
48
- export const ApplicationActivitySettingsClientResponseSchema = z.object({
49
- ok: z.literal(true),
50
- data: z.object(ApplicationActivitySettingsSchema.shape),
51
- });
52
34
  const ActivityPreviewScenarioSchema = z.strictObject({
53
35
  id: z.string().min(1).max(128),
54
36
  label: z.string().min(1).max(128),
@@ -0,0 +1,190 @@
1
+ import { z } from 'zod';
2
+ export declare const HistorySourcesSchema: z.ZodObject<{
3
+ websiteVisitors: z.ZodBoolean;
4
+ signedInCustomers: z.ZodBoolean;
5
+ whatsapp: z.ZodBoolean;
6
+ }, z.core.$strict>;
7
+ /** A surface the application declared `history: false` on, named by its authoring constructor. */
8
+ export declare const ApplicationHistoryDisabledSurfaceSchema: z.ZodEnum<{
9
+ publicWebsite: "publicWebsite";
10
+ authenticatedWebsite: "authenticatedWebsite";
11
+ publicMessaging: "publicMessaging";
12
+ }>;
13
+ export declare const ApplicationHistorySettingsSchema: z.ZodObject<{
14
+ revision: z.ZodString;
15
+ canEdit: z.ZodBoolean;
16
+ activity: z.ZodObject<{
17
+ retentionDays: z.ZodNumber;
18
+ maximumDays: z.ZodNumber;
19
+ defaultDays: z.ZodNumber;
20
+ }, z.core.$strict>;
21
+ conversations: z.ZodObject<{
22
+ state: z.ZodEnum<{
23
+ not_enabled: "not_enabled";
24
+ off: "off";
25
+ on: "on";
26
+ }>;
27
+ retentionDays: z.ZodOptional<z.ZodNumber>;
28
+ sources: z.ZodObject<{
29
+ websiteVisitors: z.ZodBoolean;
30
+ signedInCustomers: z.ZodBoolean;
31
+ whatsapp: z.ZodBoolean;
32
+ }, z.core.$strict>;
33
+ disabledByApplication: z.ZodOptional<z.ZodArray<z.ZodEnum<{
34
+ publicWebsite: "publicWebsite";
35
+ authenticatedWebsite: "authenticatedWebsite";
36
+ publicMessaging: "publicMessaging";
37
+ }>>>;
38
+ }, z.core.$strict>;
39
+ }, z.core.$strict>;
40
+ export declare const ApplicationHistorySettingsResponseSchema: z.ZodObject<{
41
+ ok: z.ZodLiteral<true>;
42
+ data: z.ZodObject<{
43
+ revision: z.ZodString;
44
+ canEdit: z.ZodBoolean;
45
+ activity: z.ZodObject<{
46
+ retentionDays: z.ZodNumber;
47
+ maximumDays: z.ZodNumber;
48
+ defaultDays: z.ZodNumber;
49
+ }, z.core.$strict>;
50
+ conversations: z.ZodObject<{
51
+ state: z.ZodEnum<{
52
+ not_enabled: "not_enabled";
53
+ off: "off";
54
+ on: "on";
55
+ }>;
56
+ retentionDays: z.ZodOptional<z.ZodNumber>;
57
+ sources: z.ZodObject<{
58
+ websiteVisitors: z.ZodBoolean;
59
+ signedInCustomers: z.ZodBoolean;
60
+ whatsapp: z.ZodBoolean;
61
+ }, z.core.$strict>;
62
+ disabledByApplication: z.ZodOptional<z.ZodArray<z.ZodEnum<{
63
+ publicWebsite: "publicWebsite";
64
+ authenticatedWebsite: "authenticatedWebsite";
65
+ publicMessaging: "publicMessaging";
66
+ }>>>;
67
+ }, z.core.$strict>;
68
+ }, z.core.$strict>;
69
+ }, z.core.$strict>;
70
+ /** Omitted fields keep their value; `dryRun` reports the effect without saving. */
71
+ export declare const ApplicationHistorySettingsSaveRequestSchema: z.ZodObject<{
72
+ expectedRevision: z.ZodString;
73
+ activityDays: z.ZodOptional<z.ZodNumber>;
74
+ conversations: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
75
+ retentionDays: z.ZodNumber;
76
+ }, z.core.$strict>, z.ZodLiteral<"off">]>>;
77
+ sources: z.ZodOptional<z.ZodObject<{
78
+ websiteVisitors: z.ZodOptional<z.ZodBoolean>;
79
+ signedInCustomers: z.ZodOptional<z.ZodBoolean>;
80
+ whatsapp: z.ZodOptional<z.ZodBoolean>;
81
+ }, z.core.$strict>>;
82
+ dryRun: z.ZodOptional<z.ZodBoolean>;
83
+ }, z.core.$strict>;
84
+ export declare const ApplicationHistorySettingsSaveResponseSchema: z.ZodObject<{
85
+ ok: z.ZodLiteral<true>;
86
+ data: z.ZodObject<{
87
+ dryRun: z.ZodBoolean;
88
+ shortensConversations: z.ZodBoolean;
89
+ impact: z.ZodObject<{
90
+ conversations: z.ZodNumber;
91
+ items: z.ZodNumber;
92
+ }, z.core.$strict>;
93
+ settings: z.ZodObject<{
94
+ revision: z.ZodString;
95
+ canEdit: z.ZodBoolean;
96
+ activity: z.ZodObject<{
97
+ retentionDays: z.ZodNumber;
98
+ maximumDays: z.ZodNumber;
99
+ defaultDays: z.ZodNumber;
100
+ }, z.core.$strict>;
101
+ conversations: z.ZodObject<{
102
+ state: z.ZodEnum<{
103
+ not_enabled: "not_enabled";
104
+ off: "off";
105
+ on: "on";
106
+ }>;
107
+ retentionDays: z.ZodOptional<z.ZodNumber>;
108
+ sources: z.ZodObject<{
109
+ websiteVisitors: z.ZodBoolean;
110
+ signedInCustomers: z.ZodBoolean;
111
+ whatsapp: z.ZodBoolean;
112
+ }, z.core.$strict>;
113
+ disabledByApplication: z.ZodOptional<z.ZodArray<z.ZodEnum<{
114
+ publicWebsite: "publicWebsite";
115
+ authenticatedWebsite: "authenticatedWebsite";
116
+ publicMessaging: "publicMessaging";
117
+ }>>>;
118
+ }, z.core.$strict>;
119
+ }, z.core.$strict>;
120
+ }, z.core.$strict>;
121
+ }, z.core.$strict>;
122
+ export declare const ApplicationHistorySettingsClientResponseSchema: z.ZodObject<{
123
+ ok: z.ZodLiteral<true>;
124
+ data: z.ZodObject<{
125
+ activity: z.ZodObject<{
126
+ retentionDays: z.ZodNumber;
127
+ maximumDays: z.ZodNumber;
128
+ defaultDays: z.ZodNumber;
129
+ }, z.core.$strip>;
130
+ conversations: z.ZodObject<{
131
+ sources: z.ZodObject<{
132
+ websiteVisitors: z.ZodBoolean;
133
+ signedInCustomers: z.ZodBoolean;
134
+ whatsapp: z.ZodBoolean;
135
+ }, z.core.$strip>;
136
+ state: z.ZodEnum<{
137
+ not_enabled: "not_enabled";
138
+ off: "off";
139
+ on: "on";
140
+ }>;
141
+ retentionDays: z.ZodOptional<z.ZodNumber>;
142
+ disabledByApplication: z.ZodOptional<z.ZodArray<z.ZodEnum<{
143
+ publicWebsite: "publicWebsite";
144
+ authenticatedWebsite: "authenticatedWebsite";
145
+ publicMessaging: "publicMessaging";
146
+ }>>>;
147
+ }, z.core.$strip>;
148
+ revision: z.ZodString;
149
+ canEdit: z.ZodBoolean;
150
+ }, z.core.$strip>;
151
+ }, z.core.$strip>;
152
+ export declare const ApplicationHistorySettingsSaveClientResponseSchema: z.ZodObject<{
153
+ ok: z.ZodLiteral<true>;
154
+ data: z.ZodObject<{
155
+ impact: z.ZodObject<{
156
+ conversations: z.ZodNumber;
157
+ items: z.ZodNumber;
158
+ }, z.core.$strip>;
159
+ settings: z.ZodObject<{
160
+ activity: z.ZodObject<{
161
+ retentionDays: z.ZodNumber;
162
+ maximumDays: z.ZodNumber;
163
+ defaultDays: z.ZodNumber;
164
+ }, z.core.$strip>;
165
+ conversations: z.ZodObject<{
166
+ sources: z.ZodObject<{
167
+ websiteVisitors: z.ZodBoolean;
168
+ signedInCustomers: z.ZodBoolean;
169
+ whatsapp: z.ZodBoolean;
170
+ }, z.core.$strip>;
171
+ state: z.ZodEnum<{
172
+ not_enabled: "not_enabled";
173
+ off: "off";
174
+ on: "on";
175
+ }>;
176
+ retentionDays: z.ZodOptional<z.ZodNumber>;
177
+ disabledByApplication: z.ZodOptional<z.ZodArray<z.ZodEnum<{
178
+ publicWebsite: "publicWebsite";
179
+ authenticatedWebsite: "authenticatedWebsite";
180
+ publicMessaging: "publicMessaging";
181
+ }>>>;
182
+ }, z.core.$strip>;
183
+ revision: z.ZodString;
184
+ canEdit: z.ZodBoolean;
185
+ }, z.core.$strip>;
186
+ dryRun: z.ZodBoolean;
187
+ shortensConversations: z.ZodBoolean;
188
+ }, z.core.$strip>;
189
+ }, z.core.$strip>;
190
+ //# sourceMappingURL=application-history-settings.d.ts.map
@@ -0,0 +1,92 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * One installation history setting (ADR 0241 decision 6): an Activity duration and a conversation
4
+ * duration (which may be Off) under the plan cap, plus per-channel recording switches, sharing one
5
+ * revision. Server output is strict; the `*ClientResponseSchema` variants tolerate additive fields.
6
+ */
7
+ const DaysSchema = z.number().int().min(1).max(365);
8
+ const RevisionSchema = z.string().regex(/^[a-f0-9]{64}$/);
9
+ const CountSchema = z.number().int().nonnegative();
10
+ export const HistorySourcesSchema = z.strictObject({
11
+ websiteVisitors: z.boolean(),
12
+ signedInCustomers: z.boolean(),
13
+ whatsapp: z.boolean(),
14
+ });
15
+ /** A surface the application declared `history: false` on, named by its authoring constructor. */
16
+ export const ApplicationHistoryDisabledSurfaceSchema = z.enum([
17
+ 'publicWebsite',
18
+ 'authenticatedWebsite',
19
+ 'publicMessaging',
20
+ ]);
21
+ export const ApplicationHistorySettingsSchema = z.strictObject({
22
+ revision: RevisionSchema,
23
+ canEdit: z.boolean(),
24
+ activity: z.strictObject({
25
+ retentionDays: DaysSchema,
26
+ maximumDays: DaysSchema,
27
+ defaultDays: DaysSchema,
28
+ }),
29
+ /** `not_enabled`: never opted in, so nothing is recorded; `off`: an Owner/Admin turned it off. */
30
+ conversations: z.strictObject({
31
+ state: z.enum(['not_enabled', 'off', 'on']),
32
+ retentionDays: DaysSchema.optional(),
33
+ sources: HistorySourcesSchema,
34
+ /**
35
+ * Surfaces whose chats are never kept, whatever this setting says, because the application
36
+ * declared `history: false` in server.ts (ADR 0241 decision 11). Absent when none do.
37
+ */
38
+ disabledByApplication: z.array(ApplicationHistoryDisabledSurfaceSchema).min(1).optional(),
39
+ }),
40
+ });
41
+ export const ApplicationHistorySettingsResponseSchema = z.strictObject({
42
+ ok: z.literal(true),
43
+ data: ApplicationHistorySettingsSchema,
44
+ });
45
+ /** Omitted fields keep their value; `dryRun` reports the effect without saving. */
46
+ export const ApplicationHistorySettingsSaveRequestSchema = z
47
+ .strictObject({
48
+ expectedRevision: RevisionSchema,
49
+ activityDays: DaysSchema.optional(),
50
+ conversations: z
51
+ .union([z.strictObject({ retentionDays: DaysSchema }), z.literal('off')])
52
+ .optional(),
53
+ sources: HistorySourcesSchema.partial().optional(),
54
+ dryRun: z.boolean().optional(),
55
+ })
56
+ .refine((value) => value.activityDays !== undefined ||
57
+ value.conversations !== undefined ||
58
+ value.sources !== undefined, { message: 'Change at least one history setting.' });
59
+ /** Existing conversation history older than the proposed conversation window. */
60
+ const HistoryImpactSchema = z.strictObject({ conversations: CountSchema, items: CountSchema });
61
+ export const ApplicationHistorySettingsSaveResponseSchema = z.strictObject({
62
+ ok: z.literal(true),
63
+ data: z.strictObject({
64
+ dryRun: z.boolean(),
65
+ /** Whether the change shortens or turns off conversation history, which needs confirmation. */
66
+ shortensConversations: z.boolean(),
67
+ impact: HistoryImpactSchema,
68
+ /** The saved setting, or the unchanged current one for a dry run. */
69
+ settings: ApplicationHistorySettingsSchema,
70
+ }),
71
+ });
72
+ const ClientSettingsSchema = z.object({
73
+ ...ApplicationHistorySettingsSchema.shape,
74
+ activity: z.object(ApplicationHistorySettingsSchema.shape.activity.shape),
75
+ conversations: z.object({
76
+ ...ApplicationHistorySettingsSchema.shape.conversations.shape,
77
+ sources: z.object(HistorySourcesSchema.shape),
78
+ }),
79
+ });
80
+ export const ApplicationHistorySettingsClientResponseSchema = z.object({
81
+ ok: z.literal(true),
82
+ data: ClientSettingsSchema,
83
+ });
84
+ export const ApplicationHistorySettingsSaveClientResponseSchema = z.object({
85
+ ok: z.literal(true),
86
+ data: z.object({
87
+ ...ApplicationHistorySettingsSaveResponseSchema.shape.data.shape,
88
+ impact: z.object(HistoryImpactSchema.shape),
89
+ settings: ClientSettingsSchema,
90
+ }),
91
+ });
92
+ //# sourceMappingURL=application-history-settings.js.map