@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.
Files changed (170) hide show
  1. package/dist/commands/solutions-activity.js +1 -1
  2. package/dist/commands/solutions-activity.js.map +1 -1
  3. package/dist/commands/solutions-drafts.d.ts.map +1 -1
  4. package/dist/commands/solutions-drafts.js +36 -9
  5. package/dist/commands/solutions-drafts.js.map +1 -1
  6. package/dist/commands/solutions-native-lifecycle.d.ts +5 -0
  7. package/dist/commands/solutions-native-lifecycle.d.ts.map +1 -0
  8. package/dist/commands/solutions-native-lifecycle.js +77 -0
  9. package/dist/commands/solutions-native-lifecycle.js.map +1 -0
  10. package/dist/commands/solutions-ops-args.d.ts +42 -0
  11. package/dist/commands/solutions-ops-args.d.ts.map +1 -0
  12. package/dist/commands/solutions-ops-args.js +47 -0
  13. package/dist/commands/solutions-ops-args.js.map +1 -0
  14. package/dist/commands/solutions-ops.d.ts.map +1 -1
  15. package/dist/commands/solutions-ops.js +9 -46
  16. package/dist/commands/solutions-ops.js.map +1 -1
  17. package/dist/commands/solutions-workspace.d.ts +5 -0
  18. package/dist/commands/solutions-workspace.d.ts.map +1 -0
  19. package/dist/commands/solutions-workspace.js +157 -0
  20. package/dist/commands/solutions-workspace.js.map +1 -0
  21. package/dist/commands/whatsapp-output.d.ts.map +1 -1
  22. package/dist/commands/whatsapp-output.js +20 -4
  23. package/dist/commands/whatsapp-output.js.map +1 -1
  24. package/node_modules/@noodle-borg/admission-limits/dist/public-record-admission.d.ts +16 -9
  25. package/node_modules/@noodle-borg/admission-limits/dist/public-record-admission.js +5 -2
  26. package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +9 -0
  27. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +5 -5
  28. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +9 -0
  29. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +1 -1
  30. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +13 -1
  31. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  32. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +5 -3
  33. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +5 -4
  34. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-inbox.js +6 -2
  35. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.d.ts +1 -1
  36. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.d.ts +12 -0
  37. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.d.ts +7 -2
  38. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.js +14 -4
  39. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-capture.js +104 -0
  40. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-judgment.js +93 -0
  41. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-ledger.d.ts +105 -0
  42. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-ledger.js +183 -0
  43. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-model-call.js +38 -0
  44. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-parsers.d.ts +22 -0
  45. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-parsers.js +109 -0
  46. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-review.d.ts +21 -0
  47. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-review.js +44 -0
  48. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-spec.d.ts +18 -0
  49. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-spec.js +86 -0
  50. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-store.d.ts +11 -0
  51. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-store.js +18 -0
  52. package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.d.ts +20 -1
  53. package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.js +38 -2
  54. package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +3 -0
  55. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +5 -0
  56. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +5 -0
  57. package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -0
  58. package/node_modules/@noodle-borg/authoring/dist/interaction.d.ts +49 -0
  59. package/node_modules/@noodle-borg/authoring/dist/interaction.js +25 -0
  60. package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -0
  61. package/node_modules/@noodle-borg/authoring/dist/server.js +4 -0
  62. package/node_modules/@noodle-borg/authoring/package.json +6 -1
  63. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +1 -1
  64. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-drafts.js +7 -0
  65. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-native-lifecycle.js +43 -0
  66. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-records.js +154 -0
  67. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-workspace.js +95 -0
  68. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solutions.js +4 -149
  69. package/node_modules/@noodle-borg/compiler/dist/artifact/assemble.js +2 -1
  70. package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +33 -0
  71. package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
  72. package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
  73. package/node_modules/@noodle-borg/compiler/dist/compile.js +5 -0
  74. package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
  75. package/node_modules/@noodle-borg/compiler/dist/index.d.ts +2 -2
  76. package/node_modules/@noodle-borg/compiler/dist/index.js +1 -1
  77. package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-schema.d.ts +41 -0
  78. package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-schema.js +62 -0
  79. package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-validation.js +220 -0
  80. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +60 -4
  81. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +7 -1
  82. package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.d.ts +9 -1
  83. package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.js +58 -10
  84. package/node_modules/@noodle-borg/control-plane/dist/in-memory-atomic-state.d.ts +8 -0
  85. package/node_modules/@noodle-borg/control-plane/dist/in-memory-atomic-state.js +136 -0
  86. package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.d.ts +7 -0
  87. package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.js +16 -24
  88. package/node_modules/@noodle-borg/control-plane/dist/in-memory-organization-store.d.ts +4 -2
  89. package/node_modules/@noodle-borg/control-plane/dist/in-memory-organization-store.js +8 -6
  90. package/node_modules/@noodle-borg/control-plane/dist/mcp-subdomain-claims.d.ts +2 -1
  91. package/node_modules/@noodle-borg/control-plane/dist/mcp-subdomain-claims.js +13 -8
  92. package/node_modules/@noodle-borg/control-plane/dist/organization-agreements.d.ts +13 -4
  93. package/node_modules/@noodle-borg/control-plane/dist/organization-agreements.js +23 -7
  94. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -1
  95. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  96. package/node_modules/@noodle-borg/module/dist/platform-human-identity.d.ts +5 -2
  97. package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +16 -4
  98. package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +3 -6
  99. package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -1
  100. package/node_modules/@noodle-borg/service/dist/application-activity.js +85 -63
  101. package/node_modules/@noodle-borg/service/dist/application-drafts/contracts.js +9 -0
  102. package/node_modules/@noodle-borg/service/dist/application-drafts/paths.js +2 -2
  103. package/node_modules/@noodle-borg/service/dist/application-runtime-target.js +1 -1
  104. package/node_modules/@noodle-borg/service/dist/business-information/contracts.d.ts +18 -4
  105. package/node_modules/@noodle-borg/service/dist/business-information/in-memory-business-pages.js +3 -5
  106. package/node_modules/@noodle-borg/service/dist/business-information/in-memory-installation-lifecycle.js +8 -6
  107. package/node_modules/@noodle-borg/service/dist/business-information/in-memory-native-lifecycle.js +80 -0
  108. package/node_modules/@noodle-borg/service/dist/business-information/in-memory-store.js +27 -31
  109. package/node_modules/@noodle-borg/service/dist/business-information/memory-native-retention.js +29 -0
  110. package/node_modules/@noodle-borg/service/dist/business-information/model.js +9 -2
  111. package/node_modules/@noodle-borg/service/dist/business-information/native-query.js +3 -1
  112. package/node_modules/@noodle-borg/service/dist/business-information/native-record-lifecycle.d.ts +27 -0
  113. package/node_modules/@noodle-borg/service/dist/business-information/native-record-lifecycle.js +63 -0
  114. package/node_modules/@noodle-borg/service/dist/business-information/principal-authority.d.ts +3 -1
  115. package/node_modules/@noodle-borg/service/dist/business-information/principal-authority.js +9 -4
  116. package/node_modules/@noodle-borg/service/dist/business-information/staff-authority.d.ts +16 -0
  117. package/node_modules/@noodle-borg/service/dist/business-information/staff-authority.js +60 -0
  118. package/node_modules/@noodle-borg/service/dist/business-information/workspace-permissions.js +18 -0
  119. package/node_modules/@noodle-borg/service/dist/business-information-runtime.js +1 -0
  120. package/node_modules/@noodle-borg/service/dist/business-onboarding.js +16 -3
  121. package/node_modules/@noodle-borg/service/dist/business-workspaces/contracts.d.ts +110 -0
  122. package/node_modules/@noodle-borg/service/dist/business-workspaces/contracts.js +52 -0
  123. package/node_modules/@noodle-borg/service/dist/business-workspaces/paths.js +17 -0
  124. package/node_modules/@noodle-borg/service/dist/business-workspaces/permissions.d.ts +7 -0
  125. package/node_modules/@noodle-borg/service/dist/business-workspaces/store.d.ts +102 -0
  126. package/node_modules/@noodle-borg/service/dist/channels/360dialog.js +70 -8
  127. package/node_modules/@noodle-borg/service/dist/channels/capability-report.js +73 -0
  128. package/node_modules/@noodle-borg/service/dist/channels/collection-turn.js +232 -0
  129. package/node_modules/@noodle-borg/service/dist/channels/composition.js +1 -0
  130. package/node_modules/@noodle-borg/service/dist/channels/messaging-eligibility.js +45 -0
  131. package/node_modules/@noodle-borg/service/dist/channels/reply-buttons.js +23 -0
  132. package/node_modules/@noodle-borg/service/dist/channels/runtime.js +127 -33
  133. package/node_modules/@noodle-borg/service/dist/native-record-connector.js +19 -4
  134. package/node_modules/@noodle-borg/service/dist/routes/application-drafts.js +44 -2
  135. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +4 -0
  136. package/node_modules/@noodle-borg/service/dist/routes/business-authoring-dispatch.js +23 -0
  137. package/node_modules/@noodle-borg/service/dist/routes/business-information-access.js +39 -0
  138. package/node_modules/@noodle-borg/service/dist/routes/business-information-activity.js +1 -1
  139. package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +2 -2
  140. package/node_modules/@noodle-borg/service/dist/routes/business-information-connections.js +10 -4
  141. package/node_modules/@noodle-borg/service/dist/routes/business-information-dispatch.js +11 -6
  142. package/node_modules/@noodle-borg/service/dist/routes/business-information-installation.js +3 -1
  143. package/node_modules/@noodle-borg/service/dist/routes/business-information-notice.js +6 -2
  144. package/node_modules/@noodle-borg/service/dist/routes/business-information-pages.js +1 -1
  145. package/node_modules/@noodle-borg/service/dist/routes/business-information-paths.js +1 -1
  146. package/node_modules/@noodle-borg/service/dist/routes/business-information-record-create.js +7 -2
  147. package/node_modules/@noodle-borg/service/dist/routes/business-information-record-reads.js +2 -2
  148. package/node_modules/@noodle-borg/service/dist/routes/business-information-records.js +332 -0
  149. package/node_modules/@noodle-borg/service/dist/routes/business-information-settings.js +6 -3
  150. package/node_modules/@noodle-borg/service/dist/routes/business-information-staff.js +9 -5
  151. package/node_modules/@noodle-borg/service/dist/routes/business-information.js +12 -356
  152. package/node_modules/@noodle-borg/service/dist/routes/business-native-lifecycle.js +46 -0
  153. package/node_modules/@noodle-borg/service/dist/routes/business-workspaces.js +119 -0
  154. package/node_modules/@noodle-borg/service/dist/routes/service-probes.js +2 -1
  155. package/node_modules/@noodle-borg/service/dist/service.js +9 -2
  156. package/node_modules/@noodle-borg/service/dist/web-capabilities.js +3 -1
  157. package/node_modules/@noodle-borg/service/package.json +2 -1
  158. package/node_modules/@noodle-borg/wire-contracts/dist/application-draft-validation.d.ts +59 -0
  159. package/node_modules/@noodle-borg/wire-contracts/dist/application-draft-validation.js +38 -0
  160. package/node_modules/@noodle-borg/wire-contracts/dist/business-information.d.ts +144 -50
  161. package/node_modules/@noodle-borg/wire-contracts/dist/business-information.js +44 -4
  162. package/node_modules/@noodle-borg/wire-contracts/dist/business-workspaces.d.ts +258 -0
  163. package/node_modules/@noodle-borg/wire-contracts/dist/business-workspaces.js +118 -1
  164. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
  165. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
  166. package/node_modules/@noodle-borg/wire-contracts/dist/native-record-lifecycle.d.ts +81 -0
  167. package/node_modules/@noodle-borg/wire-contracts/dist/native-record-lifecycle.js +39 -0
  168. package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.d.ts +26 -0
  169. package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.js +24 -1
  170. package/package.json +2 -2
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Compile-time checks for the `collect` interaction block (ADR 0240). Every field must be an input of
3
+ * the action it prepares with a control its schema can honor; every required action input must be
4
+ * collected or seeded from the opener's output; consent is a single fixed-true boolean; and the
5
+ * developer may only shorten the confirmation expiry within the platform ceiling. Valid blocks are
6
+ * emitted keyed by opener tool as system-owned artifact data, never onto the MCP-visible descriptor.
7
+ */
8
+ /** The 24-hour platform ceiling (email profile default); every profile default sits below it. */
9
+ const MAX_CONFIRMATION_EXPIRY_SECONDS = 86_400;
10
+ export function compileToolInteractions(tools, artifactTools, errors) {
11
+ const byName = new Map(artifactTools.map((tool) => [tool.name, tool]));
12
+ const emitted = {};
13
+ tools.forEach((tool, index) => {
14
+ if (tool.interaction === undefined)
15
+ return;
16
+ const opener = byName.get(tool.name);
17
+ if (opener === undefined)
18
+ return; // a structural error on this tool was already recorded
19
+ const before = errors.length;
20
+ validateCollect(tool.interaction, opener, byName, `tools.${index}.interaction`, errors);
21
+ if (errors.length === before)
22
+ emitted[tool.name] = toArtifact(tool.interaction);
23
+ });
24
+ return Object.keys(emitted).length > 0 ? emitted : undefined;
25
+ }
26
+ function validateCollect(interaction, opener, byName, path, errors) {
27
+ const action = byName.get(interaction.action);
28
+ if (action === undefined) {
29
+ errors.push({
30
+ code: 'interaction_action_invalid',
31
+ path: `${path}.action`,
32
+ message: `interaction action "${interaction.action}" is not a declared tool`,
33
+ });
34
+ return;
35
+ }
36
+ if (action.annotations?.confirm !== true || action.annotations?.readOnlyHint === true) {
37
+ errors.push({
38
+ code: 'interaction_action_invalid',
39
+ path: `${path}.action`,
40
+ message: `interaction action "${action.name}" must set { confirm: true } and must not be read-only`,
41
+ });
42
+ return;
43
+ }
44
+ const inputs = objectProperties(action.inputSchema);
45
+ const required = new Set(stringList(action.inputSchema.required));
46
+ const outputs = objectProperties(opener.outputSchema);
47
+ const covered = new Set();
48
+ let consents = 0;
49
+ interaction.fields.forEach((field, index) => {
50
+ const fieldPath = `${path}.fields.${index}`;
51
+ const property = inputs[field.key];
52
+ if (property === undefined) {
53
+ errors.push({
54
+ code: 'interaction_field_unknown',
55
+ path: fieldPath,
56
+ message: `field "${field.key}" is not an input of action "${action.name}"`,
57
+ });
58
+ return;
59
+ }
60
+ covered.add(field.key);
61
+ if (field.control === 'consent')
62
+ consents += 1;
63
+ const mismatch = controlMismatch(field.control, property);
64
+ if (mismatch !== undefined) {
65
+ errors.push({
66
+ code: 'interaction_field_control_mismatch',
67
+ path: fieldPath,
68
+ message: `field "${field.key}" uses control "${field.control}" but its input schema ${mismatch}`,
69
+ });
70
+ }
71
+ if (field.optional === true) {
72
+ if (required.has(field.key) && !acceptsEmptyString(property)) {
73
+ errors.push({
74
+ code: 'interaction_field_missing',
75
+ path: fieldPath,
76
+ message: `optional field "${field.key}" is required by action "${action.name}" and rejects the empty value; make the property optional or let it accept ""`,
77
+ });
78
+ }
79
+ }
80
+ else if (!required.has(field.key)) {
81
+ errors.push({
82
+ code: 'interaction_field_missing',
83
+ path: fieldPath,
84
+ message: `field "${field.key}" is not required by action "${action.name}"; mark it optional: true or require it`,
85
+ });
86
+ }
87
+ });
88
+ if (consents > 1) {
89
+ errors.push({
90
+ code: 'interaction_consent_invalid',
91
+ path: `${path}.fields`,
92
+ message: 'a collect interaction poses at most one consent question',
93
+ });
94
+ }
95
+ for (const [target, source] of Object.entries(interaction.initialValues ?? {})) {
96
+ const valuePath = `${path}.initialValues.${target}`;
97
+ if (inputs[target] === undefined) {
98
+ errors.push({
99
+ code: 'interaction_initial_value_unknown',
100
+ path: valuePath,
101
+ message: `initial value "${target}" is not an input of action "${action.name}"`,
102
+ });
103
+ }
104
+ else {
105
+ covered.add(target);
106
+ }
107
+ if (outputs[source.fromOutput] === undefined) {
108
+ errors.push({
109
+ code: 'interaction_initial_value_unknown',
110
+ path: `${valuePath}.fromOutput`,
111
+ message: `initial value "${target}" reads "${source.fromOutput}", which "${opener.name}" does not output`,
112
+ });
113
+ }
114
+ }
115
+ for (const key of required) {
116
+ if (covered.has(key))
117
+ continue;
118
+ errors.push({
119
+ code: 'interaction_field_missing',
120
+ path: `${path}.fields`,
121
+ message: `required input "${key}" of action "${action.name}" is neither a field nor an initial value`,
122
+ });
123
+ }
124
+ const seconds = interaction.confirmationExpiry?.seconds;
125
+ if (seconds !== undefined &&
126
+ !(Number.isInteger(seconds) && seconds >= 1 && seconds <= MAX_CONFIRMATION_EXPIRY_SECONDS)) {
127
+ errors.push({
128
+ code: 'interaction_expiry_invalid',
129
+ path: `${path}.confirmationExpiry.seconds`,
130
+ message: `confirmationExpiry.seconds must be a whole number from 1 to ${MAX_CONFIRMATION_EXPIRY_SECONDS}`,
131
+ });
132
+ }
133
+ }
134
+ /** Why `control` cannot render `property`, or undefined when the schema supports it. */
135
+ function controlMismatch(control, property) {
136
+ switch (control) {
137
+ case 'consent':
138
+ return property.type === 'boolean' && isFixedTrue(property)
139
+ ? undefined
140
+ : 'is not a boolean fixed to true';
141
+ case 'select':
142
+ return property.type === 'string' && isStringEnum(property.enum)
143
+ ? undefined
144
+ : 'is not an enum of strings';
145
+ case 'email':
146
+ if (property.type !== 'string')
147
+ return 'is not a string';
148
+ return property.format === undefined || property.format === 'email'
149
+ ? undefined
150
+ : `declares format "${String(property.format)}"`;
151
+ default:
152
+ return property.type === 'string' ? undefined : 'is not a string';
153
+ }
154
+ }
155
+ function isFixedTrue(property) {
156
+ if (property.const === true)
157
+ return true;
158
+ return Array.isArray(property.enum) && property.enum.length === 1 && property.enum[0] === true;
159
+ }
160
+ function isStringEnum(value) {
161
+ return Array.isArray(value) && value.length > 0 && value.every((v) => typeof v === 'string');
162
+ }
163
+ /**
164
+ * Whether `""` satisfies a string property, so an optional field the action still requires can be
165
+ * submitted blank. Any format, a positive minLength, or a pattern/enum/const rejecting `""` means no.
166
+ */
167
+ function acceptsEmptyString(property) {
168
+ if (property.type !== 'string' || property.format !== undefined)
169
+ return false;
170
+ if (typeof property.minLength === 'number' && property.minLength > 0)
171
+ return false;
172
+ if (property.const !== undefined && property.const !== '')
173
+ return false;
174
+ if (Array.isArray(property.enum) && !property.enum.includes(''))
175
+ return false;
176
+ return typeof property.pattern === 'string' ? patternMatchesEmpty(property.pattern) : true;
177
+ }
178
+ function patternMatchesEmpty(pattern) {
179
+ for (const flags of ['u', '']) {
180
+ try {
181
+ return new RegExp(pattern, flags).test('');
182
+ }
183
+ catch {
184
+ // An invalid pattern under this flag set; try the next, then fail closed.
185
+ }
186
+ }
187
+ return false;
188
+ }
189
+ function objectProperties(schema) {
190
+ const properties = schema?.properties;
191
+ if (typeof properties !== 'object' || properties === null || Array.isArray(properties))
192
+ return {};
193
+ return Object.fromEntries(Object.entries(properties).flatMap(([key, value]) => typeof value === 'object' && value !== null && !Array.isArray(value)
194
+ ? [[key, value]]
195
+ : []));
196
+ }
197
+ function stringList(value) {
198
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : [];
199
+ }
200
+ function toArtifact(interaction) {
201
+ return {
202
+ kind: 'collect',
203
+ action: interaction.action,
204
+ fields: interaction.fields.map((field) => ({
205
+ key: field.key,
206
+ control: field.control,
207
+ ...(field.private === true ? { private: true } : {}),
208
+ ...(field.optional === true ? { optional: true } : {}),
209
+ })),
210
+ ...(interaction.initialValues === undefined
211
+ ? {}
212
+ : { initialValues: interaction.initialValues }),
213
+ review: 'all',
214
+ outcome: { success: interaction.outcome.success },
215
+ ...(interaction.confirmationExpiry === undefined
216
+ ? {}
217
+ : { confirmationExpirySeconds: interaction.confirmationExpiry.seconds }),
218
+ };
219
+ }
220
+ //# sourceMappingURL=interaction-validation.js.map
@@ -11,10 +11,10 @@ export declare const manifestV1Schema: z.ZodObject<{
11
11
  state: z.ZodOptional<z.ZodObject<{
12
12
  handles: z.ZodRecord<z.ZodString, z.ZodObject<{
13
13
  kind: z.ZodEnum<{
14
+ search: "search";
14
15
  session: "session";
15
16
  draft: "draft";
16
17
  selection: "selection";
17
- search: "search";
18
18
  cart: "cart";
19
19
  workflow: "workflow";
20
20
  }>;
@@ -557,10 +557,10 @@ export declare const manifestV2Schema: z.ZodObject<{
557
557
  state: z.ZodOptional<z.ZodObject<{
558
558
  handles: z.ZodRecord<z.ZodString, z.ZodObject<{
559
559
  kind: z.ZodEnum<{
560
+ search: "search";
560
561
  session: "session";
561
562
  draft: "draft";
562
563
  selection: "selection";
563
- search: "search";
564
564
  cart: "cart";
565
565
  workflow: "workflow";
566
566
  }>;
@@ -1238,6 +1238,34 @@ export declare const manifestV2Schema: z.ZodObject<{
1238
1238
  output: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1239
1239
  }, z.core.$strip>;
1240
1240
  contextProvider: z.ZodOptional<z.ZodLiteral<true>>;
1241
+ interaction: z.ZodOptional<z.ZodObject<{
1242
+ kind: z.ZodLiteral<"collect">;
1243
+ action: z.ZodString;
1244
+ initialValues: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
1245
+ fromOutput: z.ZodString;
1246
+ }, z.core.$strict>>>;
1247
+ fields: z.ZodArray<z.ZodObject<{
1248
+ key: z.ZodString;
1249
+ control: z.ZodEnum<{
1250
+ email: "email";
1251
+ text: "text";
1252
+ textarea: "textarea";
1253
+ phone: "phone";
1254
+ url: "url";
1255
+ select: "select";
1256
+ consent: "consent";
1257
+ }>;
1258
+ private: z.ZodOptional<z.ZodLiteral<true>>;
1259
+ optional: z.ZodOptional<z.ZodLiteral<true>>;
1260
+ }, z.core.$strict>>;
1261
+ review: z.ZodLiteral<"all">;
1262
+ outcome: z.ZodObject<{
1263
+ success: z.ZodString;
1264
+ }, z.core.$strict>;
1265
+ confirmationExpiry: z.ZodOptional<z.ZodObject<{
1266
+ seconds: z.ZodNumber;
1267
+ }, z.core.$strict>>;
1268
+ }, z.core.$strict>>;
1241
1269
  }, z.core.$strip>>;
1242
1270
  connectors: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
1243
1271
  id: z.ZodString;
@@ -1285,10 +1313,10 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1285
1313
  state: z.ZodOptional<z.ZodObject<{
1286
1314
  handles: z.ZodRecord<z.ZodString, z.ZodObject<{
1287
1315
  kind: z.ZodEnum<{
1316
+ search: "search";
1288
1317
  session: "session";
1289
1318
  draft: "draft";
1290
1319
  selection: "selection";
1291
- search: "search";
1292
1320
  cart: "cart";
1293
1321
  workflow: "workflow";
1294
1322
  }>;
@@ -1830,10 +1858,10 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1830
1858
  state: z.ZodOptional<z.ZodObject<{
1831
1859
  handles: z.ZodRecord<z.ZodString, z.ZodObject<{
1832
1860
  kind: z.ZodEnum<{
1861
+ search: "search";
1833
1862
  session: "session";
1834
1863
  draft: "draft";
1835
1864
  selection: "selection";
1836
- search: "search";
1837
1865
  cart: "cart";
1838
1866
  workflow: "workflow";
1839
1867
  }>;
@@ -2511,6 +2539,34 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2511
2539
  output: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
2512
2540
  }, z.core.$strip>;
2513
2541
  contextProvider: z.ZodOptional<z.ZodLiteral<true>>;
2542
+ interaction: z.ZodOptional<z.ZodObject<{
2543
+ kind: z.ZodLiteral<"collect">;
2544
+ action: z.ZodString;
2545
+ initialValues: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
2546
+ fromOutput: z.ZodString;
2547
+ }, z.core.$strict>>>;
2548
+ fields: z.ZodArray<z.ZodObject<{
2549
+ key: z.ZodString;
2550
+ control: z.ZodEnum<{
2551
+ email: "email";
2552
+ text: "text";
2553
+ textarea: "textarea";
2554
+ phone: "phone";
2555
+ url: "url";
2556
+ select: "select";
2557
+ consent: "consent";
2558
+ }>;
2559
+ private: z.ZodOptional<z.ZodLiteral<true>>;
2560
+ optional: z.ZodOptional<z.ZodLiteral<true>>;
2561
+ }, z.core.$strict>>;
2562
+ review: z.ZodLiteral<"all">;
2563
+ outcome: z.ZodObject<{
2564
+ success: z.ZodString;
2565
+ }, z.core.$strict>;
2566
+ confirmationExpiry: z.ZodOptional<z.ZodObject<{
2567
+ seconds: z.ZodNumber;
2568
+ }, z.core.$strict>>;
2569
+ }, z.core.$strict>>;
2514
2570
  }, z.core.$strip>>;
2515
2571
  connectors: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
2516
2572
  id: z.ZodString;
@@ -7,6 +7,7 @@ import { MAX_COMPILED_WIDGET_HTML_BYTES, MAX_RAW_WIDGET_HTML_BYTES } from '../wi
7
7
  import { agentGuideSchema } from './agent-guide-schema.js';
8
8
  import { serverAuthSchema, serverAuthV2Schema } from './auth-schema.js';
9
9
  import { assistantOriginOrVariableSchema, httpsUrlSchema, managedVariableExpressionSchema, serverBrandingSchema, } from './branding-schema.js';
10
+ import { toolInteractionSchema } from './interaction-schema.js';
10
11
  import { NAME_PATTERN } from './naming.js';
11
12
  /**
12
13
  * Zod schema for Core v1 and v2 manifests (ADRs 0150 and 0157). V1 remains accepted unchanged;
@@ -115,7 +116,12 @@ const toolSchema = z.object({
115
116
  .optional(),
116
117
  fulfilment: fulfilmentSchema,
117
118
  });
118
- const toolV2Schema = toolSchema.extend({ contextProvider: z.literal(true).optional() });
119
+ const toolV2Schema = toolSchema.extend({
120
+ contextProvider: z.literal(true).optional(),
121
+ // Additive optional field: a v1.x minor under ADR 0150. Bounded `collect` interaction metadata
122
+ // (ADR 0240) an opener tool declares for one confirmed action; references are checked in compile().
123
+ interaction: toolInteractionSchema.optional(),
124
+ });
119
125
  /**
120
126
  * An MCP resource. Like a tool, it carries a `fulfilment` (it can return a constant or call connectors);
121
127
  * its `uri` is a fixed URI or a simple `{var}` URI template (validated in compile()). The compile pass
@@ -1,4 +1,4 @@
1
- import type { ArtifactPrompt, ArtifactResource, ArtifactTool } from '../artifact/types.js';
1
+ import type { ArtifactCollectInteraction, ArtifactPrompt, ArtifactResource, ArtifactTool, OperationRef } from '../artifact/types.js';
2
2
  import type { CompileError } from '../errors.js';
3
3
  /**
4
4
  * Compile-time enforcement of website assistant surfaces (ADR 0201, amended 2026-08-12). These are
@@ -36,9 +36,17 @@ export interface WebsiteProjectionSurfaces {
36
36
  readonly knowledge?: readonly {
37
37
  readonly name: string;
38
38
  }[] | undefined;
39
+ /** `collect` interactions keyed by opener (ADR 0240); a messaging surface presents them natively. */
40
+ readonly interactions?: Readonly<Record<string, ArtifactCollectInteraction>> | undefined;
41
+ /** Declared connector aliases, so a shape-only operation ref still names its connector id. */
42
+ readonly connectors?: Readonly<Record<string, {
43
+ readonly id: string;
44
+ }>> | undefined;
39
45
  }
40
46
  /** A tool touches identity when it reads `${user}` or requires verified claims (ADR 0185). */
41
47
  export declare function anonymousBehavior(tool: ArtifactTool): 'public-safe' | 'requires-identity';
42
48
  export declare function validateWebsiteProjection(assistant: AssistantProjectionInput | undefined, surfaces: WebsiteProjectionSurfaces): readonly CompileError[];
49
+ /** Resolved refs carry the connector id; a shape-only ref is looked up through its declared alias. */
50
+ export declare function isNativeRecordsOperation(operation: OperationRef, connectors: WebsiteProjectionSurfaces['connectors']): boolean;
43
51
  export {};
44
52
  //# sourceMappingURL=website-projection.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { RECORD_CONNECTOR_ID } from '../native-record-operations.js';
1
2
  import { collectPaths } from './expression.js';
2
3
  /** A tool touches identity when it reads `${user}` or requires verified claims (ADR 0185). */
3
4
  export function anonymousBehavior(tool) {
@@ -40,16 +41,11 @@ export function validateWebsiteProjection(assistant, surfaces) {
40
41
  if (tool === undefined)
41
42
  return;
42
43
  const path = `tools.${tool.name}`;
43
- if (surface.kind === 'messaging' &&
44
- (tool.annotations?.readOnlyHint !== true ||
45
- tool._meta?.ui !== undefined ||
46
- tool.fulfilment.kind === 'operation' ||
47
- tool.fulfilment.steps.some((step) => step.kind === 'operation'))) {
48
- errors.push({
49
- code: 'assistant_messaging_unsupported',
50
- path,
51
- message: `"${tool.name}" requires a write, connector, or UI renderer not enabled for this messaging slice`,
52
- });
44
+ if (surface.kind === 'messaging') {
45
+ const selected = new Set(surface.capabilities?.flatMap((entry) => (entry.kind === 'tool' ? [entry.name] : [])));
46
+ const requirement = messagingRequirement(tool, selected, surfaces);
47
+ if (requirement !== undefined)
48
+ errors.push({ ...requirement, path });
53
49
  }
54
50
  if (surface.mode === 'public' && anonymousBehavior(tool) === 'requires-identity') {
55
51
  errors.push({
@@ -73,6 +69,58 @@ export function validateWebsiteProjection(assistant, surfaces) {
73
69
  });
74
70
  return errors;
75
71
  }
72
+ /**
73
+ * What a messaging profile can present (ADR 0240): pure reads and knowledge as before, plus a
74
+ * `collect` opener whose confirmed action is selected beside it and saves one native record. The
75
+ * opener may carry a browser widget (the website keeps its React view) because the profile renders
76
+ * the same block as natural collection. Anything else names the requirement it lacks.
77
+ */
78
+ function messagingRequirement(tool, selected, surfaces) {
79
+ const operations = operationRefs(tool.fulfilment);
80
+ const interaction = surfaces.interactions?.[tool.name];
81
+ if (interaction !== undefined && !selected.has(interaction.action)) {
82
+ return {
83
+ code: 'channel_dependency_missing',
84
+ message: `"${tool.name}" requires its confirmed action "${interaction.action}", which is not selected on this messaging surface`,
85
+ };
86
+ }
87
+ const opensCollection = interaction !== undefined;
88
+ const isCollectAction = [...selected].some((name) => surfaces.interactions?.[name]?.action === tool.name);
89
+ if (isCollectAction) {
90
+ const [operation] = operations;
91
+ if (operations.length === 1 &&
92
+ operation !== undefined &&
93
+ isNativeRecordsOperation(operation, surfaces.connectors)) {
94
+ return undefined;
95
+ }
96
+ return {
97
+ code: 'channel_requirement_unsupported',
98
+ message: `"${tool.name}" requires a connector call the messaging profile does not provide; a collect action saves exactly one native record`,
99
+ };
100
+ }
101
+ const requirements = [
102
+ ...(tool.annotations?.readOnlyHint !== true ? ['a write'] : []),
103
+ ...(operations.length > 0 ? ['a connector call'] : []),
104
+ ...(tool._meta?.ui?.resourceUri !== undefined && !opensCollection ? ['a UI renderer'] : []),
105
+ ];
106
+ if (requirements.length === 0)
107
+ return undefined;
108
+ return {
109
+ code: 'channel_requirement_unsupported',
110
+ message: `"${tool.name}" requires ${requirements.join(' and ')} that the messaging profile does not provide without a collect interaction`,
111
+ };
112
+ }
113
+ function operationRefs(fulfilment) {
114
+ return fulfilment.kind === 'operation'
115
+ ? [fulfilment.operationRef]
116
+ : fulfilment.steps.flatMap((step) => (step.kind === 'operation' ? [step.operationRef] : []));
117
+ }
118
+ /** Resolved refs carry the connector id; a shape-only ref is looked up through its declared alias. */
119
+ export function isNativeRecordsOperation(operation, connectors) {
120
+ return operation.resolved
121
+ ? operation.connectorId === RECORD_CONNECTOR_ID
122
+ : connectors?.[operation.connector]?.id === RECORD_CONNECTOR_ID;
123
+ }
76
124
  /**
77
125
  * Reads every expression a fulfilment evaluates — one operation's args, or a flow's output plus each
78
126
  * step's args and its `if` guard, since a condition can read `${user}` just as an argument can.
@@ -0,0 +1,8 @@
1
+ /** Development-only composition. PostgreSQL remains the production transaction authority. */
2
+ export declare class InMemoryAtomicState {
3
+ #private;
4
+ /** Values retain normal Map reference semantics; owners validate and copy their records. */
5
+ map<K, V>(): Map<K, V>;
6
+ run<T>(operation: () => Promise<T>): Promise<T>;
7
+ }
8
+ //# sourceMappingURL=in-memory-atomic-state.d.ts.map
@@ -0,0 +1,136 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ /** Development-only composition. PostgreSQL remains the production transaction authority. */
3
+ export class InMemoryAtomicState {
4
+ #current = new AsyncLocalStorage();
5
+ #tail = Promise.resolve();
6
+ /** Values retain normal Map reference semantics; owners validate and copy their records. */
7
+ map() {
8
+ return new AtomicMap(() => {
9
+ const transaction = this.#current.getStore();
10
+ if (transaction?.closed)
11
+ throw new Error('memory transaction is closed');
12
+ return transaction;
13
+ });
14
+ }
15
+ async run(operation) {
16
+ const inherited = this.#current.getStore();
17
+ if (inherited) {
18
+ if (inherited.closed)
19
+ throw new Error('memory transaction is closed');
20
+ inherited.pending++;
21
+ try {
22
+ return await operation();
23
+ }
24
+ catch (error) {
25
+ inherited.failure ??= { error };
26
+ throw error;
27
+ }
28
+ finally {
29
+ inherited.pending--;
30
+ }
31
+ }
32
+ const prior = this.#tail;
33
+ let release = () => { };
34
+ this.#tail = new Promise((done) => {
35
+ release = done;
36
+ });
37
+ await prior;
38
+ const transaction = { participants: new Map(), closed: false, pending: 0 };
39
+ try {
40
+ const result = await this.#current.run(transaction, operation);
41
+ if (transaction.failure)
42
+ throw transaction.failure.error;
43
+ if (transaction.pending !== 0)
44
+ throw new Error('memory transaction has unfinished work');
45
+ // Validate every generation before any publication. Nonparticipating mutations survive a conflict.
46
+ for (const participant of transaction.participants.values())
47
+ participant.validate();
48
+ for (const participant of transaction.participants.values())
49
+ participant.commit();
50
+ return result;
51
+ }
52
+ finally {
53
+ transaction.closed = true;
54
+ release();
55
+ }
56
+ }
57
+ }
58
+ /** Private maps never restore a process-wide snapshot or expose staged entries to another request. */
59
+ class AtomicMap {
60
+ transaction;
61
+ [Symbol.toStringTag] = 'Map';
62
+ #entries = new Map();
63
+ #generation = 0;
64
+ constructor(transaction) {
65
+ this.transaction = transaction;
66
+ }
67
+ #view(mutate = false) {
68
+ const transaction = this.transaction();
69
+ if (!transaction) {
70
+ if (mutate)
71
+ this.#generation++;
72
+ return this.#entries;
73
+ }
74
+ let participant = transaction.participants.get(this);
75
+ if (!participant) {
76
+ const generation = this.#generation;
77
+ const draft = { entries: new Map(this.#entries), dirty: false };
78
+ participant = {
79
+ value: draft,
80
+ validate: () => {
81
+ if (generation !== this.#generation)
82
+ throw new Error('memory transaction conflict');
83
+ },
84
+ commit: () => {
85
+ if (!draft.dirty)
86
+ return;
87
+ this.#entries = draft.entries;
88
+ this.#generation++;
89
+ },
90
+ };
91
+ transaction.participants.set(this, participant);
92
+ }
93
+ // The unexposed map instance is the type-stable key for this participant.
94
+ const draft = participant.value;
95
+ if (mutate)
96
+ draft.dirty = true;
97
+ return draft.entries;
98
+ }
99
+ get size() {
100
+ return this.#view().size;
101
+ }
102
+ get(key) {
103
+ return this.#view().get(key);
104
+ }
105
+ has(key) {
106
+ return this.#view().has(key);
107
+ }
108
+ set(key, value) {
109
+ this.#view(true).set(key, value);
110
+ return this;
111
+ }
112
+ delete(key) {
113
+ return this.#view(true).delete(key);
114
+ }
115
+ clear() {
116
+ this.#view(true).clear();
117
+ }
118
+ entries() {
119
+ return this.#view().entries();
120
+ }
121
+ keys() {
122
+ return this.#view().keys();
123
+ }
124
+ values() {
125
+ return this.#view().values();
126
+ }
127
+ [Symbol.iterator]() {
128
+ return this.entries();
129
+ }
130
+ forEach(callback, thisArg) {
131
+ this.#view().forEach((value, key) => {
132
+ callback.call(thisArg, value, key, this);
133
+ });
134
+ }
135
+ }
136
+ //# sourceMappingURL=in-memory-atomic-state.js.map
@@ -1,10 +1,17 @@
1
1
  import type { CreateOrgWithOwnerInput, OrgMemberRecord, OrgRecord, OrgRole, PersonalWorkspaceProvisionInput, PersonalWorkspaceProvisionResult, WelcomeEmailRecord } from './contracts.js';
2
+ import { InMemoryAtomicState } from './in-memory-atomic-state.js';
2
3
  import { InMemoryOrganizationStore } from './in-memory-organization-store.js';
3
4
  /** Portable organization state. Commercial account provisioning is an optional module side effect. */
4
5
  export declare class InMemoryControlPlaneStore extends InMemoryOrganizationStore {
5
6
  #private;
6
7
  constructor(options?: {
7
8
  readonly now?: () => Date;
9
+ readonly transactions?: InMemoryAtomicState;
10
+ /** Fresh-only local participant; all stores must share the supplied transaction context. */
11
+ readonly personalWorkspaceCreated?: (input: {
12
+ readonly org: string;
13
+ readonly ownerSubject: string;
14
+ }) => Promise<void>;
8
15
  });
9
16
  provisionPersonalWorkspace(input: PersonalWorkspaceProvisionInput): Promise<PersonalWorkspaceProvisionResult>;
10
17
  createOrgWithOwner(input: CreateOrgWithOwnerInput): Promise<OrgRecord>;