@noodleseed/one 0.174.0 → 0.175.1

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 (164) 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.d.ts.map +1 -1
  11. package/dist/commands/solutions-ops.js +3 -0
  12. package/dist/commands/solutions-ops.js.map +1 -1
  13. package/dist/commands/solutions-workspace.d.ts.map +1 -1
  14. package/dist/commands/solutions-workspace.js +36 -11
  15. package/dist/commands/solutions-workspace.js.map +1 -1
  16. package/dist/commands/whatsapp-output.d.ts.map +1 -1
  17. package/dist/commands/whatsapp-output.js +20 -4
  18. package/dist/commands/whatsapp-output.js.map +1 -1
  19. package/node_modules/@noodle-borg/admission-limits/dist/public-record-admission.d.ts +16 -9
  20. package/node_modules/@noodle-borg/admission-limits/dist/public-record-admission.js +5 -2
  21. package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +9 -0
  22. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +5 -5
  23. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +9 -0
  24. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +1 -1
  25. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +13 -1
  26. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  27. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.d.ts +5 -3
  28. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-coordinator.js +5 -4
  29. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-inbox.js +6 -2
  30. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-store.d.ts +1 -1
  31. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-types.d.ts +12 -0
  32. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.d.ts +7 -2
  33. package/node_modules/@noodle-borg/assistant-gateway/dist/channel-work.js +14 -4
  34. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-capture.js +104 -0
  35. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-judgment.js +93 -0
  36. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-ledger.d.ts +105 -0
  37. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-ledger.js +183 -0
  38. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-model-call.js +38 -0
  39. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-parsers.d.ts +22 -0
  40. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-parsers.js +109 -0
  41. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-review.d.ts +21 -0
  42. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-review.js +44 -0
  43. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-spec.d.ts +18 -0
  44. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-spec.js +86 -0
  45. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-store.d.ts +11 -0
  46. package/node_modules/@noodle-borg/assistant-gateway/dist/collection-store.js +18 -0
  47. package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.d.ts +20 -1
  48. package/node_modules/@noodle-borg/assistant-gateway/dist/messaging-tool-dispatch.js +38 -2
  49. package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +3 -0
  50. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +5 -0
  51. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +5 -0
  52. package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
  53. package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -0
  54. package/node_modules/@noodle-borg/authoring/dist/interaction.d.ts +49 -0
  55. package/node_modules/@noodle-borg/authoring/dist/interaction.js +25 -0
  56. package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -0
  57. package/node_modules/@noodle-borg/authoring/dist/server.js +4 -0
  58. package/node_modules/@noodle-borg/authoring/package.json +6 -1
  59. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-channels.js +1 -1
  60. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-drafts.js +7 -0
  61. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-native-lifecycle.js +43 -0
  62. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-records.js +154 -0
  63. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-workspace.js +8 -1
  64. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solutions.js +2 -149
  65. package/node_modules/@noodle-borg/compiler/dist/artifact/assemble.js +2 -1
  66. package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +33 -0
  67. package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
  68. package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
  69. package/node_modules/@noodle-borg/compiler/dist/compile.js +5 -0
  70. package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
  71. package/node_modules/@noodle-borg/compiler/dist/index.d.ts +2 -2
  72. package/node_modules/@noodle-borg/compiler/dist/index.js +1 -1
  73. package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-schema.d.ts +41 -0
  74. package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-schema.js +62 -0
  75. package/node_modules/@noodle-borg/compiler/dist/manifest/interaction-validation.js +220 -0
  76. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +60 -4
  77. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +7 -1
  78. package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.d.ts +9 -1
  79. package/node_modules/@noodle-borg/compiler/dist/manifest/website-projection.js +58 -10
  80. package/node_modules/@noodle-borg/control-plane/dist/in-memory-atomic-state.d.ts +8 -0
  81. package/node_modules/@noodle-borg/control-plane/dist/in-memory-atomic-state.js +136 -0
  82. package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.d.ts +7 -0
  83. package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.js +16 -24
  84. package/node_modules/@noodle-borg/control-plane/dist/in-memory-organization-store.d.ts +4 -2
  85. package/node_modules/@noodle-borg/control-plane/dist/in-memory-organization-store.js +8 -6
  86. package/node_modules/@noodle-borg/control-plane/dist/mcp-subdomain-claims.d.ts +2 -1
  87. package/node_modules/@noodle-borg/control-plane/dist/mcp-subdomain-claims.js +13 -8
  88. package/node_modules/@noodle-borg/control-plane/dist/organization-agreements.d.ts +13 -4
  89. package/node_modules/@noodle-borg/control-plane/dist/organization-agreements.js +23 -7
  90. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -1
  91. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  92. package/node_modules/@noodle-borg/module/dist/platform-human-identity.d.ts +5 -2
  93. package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +16 -4
  94. package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +3 -6
  95. package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -1
  96. package/node_modules/@noodle-borg/service/dist/application-activity.js +85 -63
  97. package/node_modules/@noodle-borg/service/dist/application-drafts/contracts.js +9 -0
  98. package/node_modules/@noodle-borg/service/dist/application-drafts/paths.js +2 -2
  99. package/node_modules/@noodle-borg/service/dist/application-runtime-target.js +1 -1
  100. package/node_modules/@noodle-borg/service/dist/business-information/contracts.d.ts +18 -4
  101. package/node_modules/@noodle-borg/service/dist/business-information/in-memory-business-pages.js +3 -5
  102. package/node_modules/@noodle-borg/service/dist/business-information/in-memory-installation-lifecycle.js +8 -6
  103. package/node_modules/@noodle-borg/service/dist/business-information/in-memory-native-lifecycle.js +80 -0
  104. package/node_modules/@noodle-borg/service/dist/business-information/in-memory-store.js +27 -31
  105. package/node_modules/@noodle-borg/service/dist/business-information/memory-native-retention.js +29 -0
  106. package/node_modules/@noodle-borg/service/dist/business-information/model.js +9 -2
  107. package/node_modules/@noodle-borg/service/dist/business-information/native-query.js +3 -1
  108. package/node_modules/@noodle-borg/service/dist/business-information/native-record-lifecycle.d.ts +27 -0
  109. package/node_modules/@noodle-borg/service/dist/business-information/native-record-lifecycle.js +63 -0
  110. package/node_modules/@noodle-borg/service/dist/business-information/principal-authority.d.ts +3 -1
  111. package/node_modules/@noodle-borg/service/dist/business-information/principal-authority.js +9 -4
  112. package/node_modules/@noodle-borg/service/dist/business-information/staff-authority.d.ts +16 -0
  113. package/node_modules/@noodle-borg/service/dist/business-information/staff-authority.js +60 -0
  114. package/node_modules/@noodle-borg/service/dist/business-information/workspace-permissions.js +18 -0
  115. package/node_modules/@noodle-borg/service/dist/business-information-runtime.js +1 -0
  116. package/node_modules/@noodle-borg/service/dist/business-onboarding.js +16 -3
  117. package/node_modules/@noodle-borg/service/dist/business-workspaces/contracts.d.ts +110 -0
  118. package/node_modules/@noodle-borg/service/dist/business-workspaces/permissions.d.ts +7 -0
  119. package/node_modules/@noodle-borg/service/dist/business-workspaces/store.d.ts +102 -0
  120. package/node_modules/@noodle-borg/service/dist/channels/360dialog.js +70 -8
  121. package/node_modules/@noodle-borg/service/dist/channels/capability-report.js +73 -0
  122. package/node_modules/@noodle-borg/service/dist/channels/collection-turn.js +232 -0
  123. package/node_modules/@noodle-borg/service/dist/channels/composition.js +1 -0
  124. package/node_modules/@noodle-borg/service/dist/channels/messaging-eligibility.js +45 -0
  125. package/node_modules/@noodle-borg/service/dist/channels/reply-buttons.js +23 -0
  126. package/node_modules/@noodle-borg/service/dist/channels/runtime.js +127 -33
  127. package/node_modules/@noodle-borg/service/dist/native-record-connector.js +19 -4
  128. package/node_modules/@noodle-borg/service/dist/routes/application-drafts.js +44 -2
  129. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +4 -0
  130. package/node_modules/@noodle-borg/service/dist/routes/business-authoring-dispatch.js +1 -1
  131. package/node_modules/@noodle-borg/service/dist/routes/business-information-access.js +39 -0
  132. package/node_modules/@noodle-borg/service/dist/routes/business-information-activity.js +1 -1
  133. package/node_modules/@noodle-borg/service/dist/routes/business-information-channels.js +2 -2
  134. package/node_modules/@noodle-borg/service/dist/routes/business-information-connections.js +10 -4
  135. package/node_modules/@noodle-borg/service/dist/routes/business-information-dispatch.js +11 -0
  136. package/node_modules/@noodle-borg/service/dist/routes/business-information-installation.js +3 -1
  137. package/node_modules/@noodle-borg/service/dist/routes/business-information-notice.js +6 -2
  138. package/node_modules/@noodle-borg/service/dist/routes/business-information-pages.js +1 -1
  139. package/node_modules/@noodle-borg/service/dist/routes/business-information-paths.js +1 -1
  140. package/node_modules/@noodle-borg/service/dist/routes/business-information-record-create.js +7 -2
  141. package/node_modules/@noodle-borg/service/dist/routes/business-information-record-reads.js +2 -2
  142. package/node_modules/@noodle-borg/service/dist/routes/business-information-records.js +332 -0
  143. package/node_modules/@noodle-borg/service/dist/routes/business-information-settings.js +6 -3
  144. package/node_modules/@noodle-borg/service/dist/routes/business-information-staff.js +9 -5
  145. package/node_modules/@noodle-borg/service/dist/routes/business-information.js +12 -356
  146. package/node_modules/@noodle-borg/service/dist/routes/business-native-lifecycle.js +46 -0
  147. package/node_modules/@noodle-borg/service/dist/routes/business-workspaces.js +14 -1
  148. package/node_modules/@noodle-borg/service/dist/service.js +1 -0
  149. package/node_modules/@noodle-borg/service/dist/web-capabilities.js +3 -1
  150. package/node_modules/@noodle-borg/service/package.json +2 -1
  151. package/node_modules/@noodle-borg/wire-contracts/dist/application-draft-validation.d.ts +59 -0
  152. package/node_modules/@noodle-borg/wire-contracts/dist/application-draft-validation.js +38 -0
  153. package/node_modules/@noodle-borg/wire-contracts/dist/business-information.d.ts +144 -50
  154. package/node_modules/@noodle-borg/wire-contracts/dist/business-information.js +44 -4
  155. package/node_modules/@noodle-borg/wire-contracts/dist/business-workspaces.d.ts +70 -0
  156. package/node_modules/@noodle-borg/wire-contracts/dist/business-workspaces.js +31 -1
  157. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
  158. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
  159. package/node_modules/@noodle-borg/wire-contracts/dist/native-record-lifecycle.d.ts +81 -0
  160. package/node_modules/@noodle-borg/wire-contracts/dist/native-record-lifecycle.js +39 -0
  161. package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.d.ts +26 -0
  162. package/node_modules/@noodle-borg/wire-contracts/dist/whatsapp.js +24 -1
  163. package/node_modules/@noodleseed/assistant/package.json +1 -1
  164. package/package.json +2 -2
@@ -176,6 +176,15 @@ export const COMPILE_ERROR_CODES = [
176
176
  'assistant_capability_unknown',
177
177
  'assistant_public_user_reference',
178
178
  'assistant_public_effect_unconfirmed',
179
+ 'interaction_action_invalid',
180
+ 'interaction_field_unknown',
181
+ 'interaction_field_control_mismatch',
182
+ 'interaction_field_missing',
183
+ 'interaction_initial_value_unknown',
184
+ 'interaction_consent_invalid',
185
+ 'interaction_expiry_invalid',
186
+ 'channel_dependency_missing',
187
+ 'channel_requirement_unsupported',
179
188
  'customer_endpoint_action_unsupported',
180
189
  'customer_endpoint_surface_unsupported',
181
190
  'customer_endpoint_credential_source_unsupported',
@@ -274,7 +274,7 @@ export function renderAuthoringWorkflowReference() {
274
274
  'Omitting `source` makes Noodle authoritative for the collection. To project a read-only collection from an outside system, bind an HTTP connector in `server(..., { use: [...] })` and add `source: { connector: connector.ref(), scan: "scan_operation" }`. The compiler requires the exact normalized scan contract and the same record schema. Noodle ingests a one-way replica; create, update, and delete in the outside system remain ordinary application tools with their own confirmation and policy.',
275
275
  '',
276
276
  'For an explicitly authored native operation, bind `use: { records: noodlePlatform.records.v1 }` and call `connectors.records.submitRecord({ collection: "guest_requests", payload: input })` from an ordinary confirmed tool. Submission accepts only public fields and returns a receipt. `createRecord`, `getRecord`, `listRecords`, `updateRecord`, and `deleteRecord` require a verified platform caller with a live business grant; updates/deletes require the current revision. `updateRecord({ collection, id, expectedRevision, patch, unset: ["optional_field"] })` removes named optional editable fields; omitted keys stay unchanged, `null` is data, and overlap with `patch`, required fields or undeclared fields is rejected. Runtime identity supplies write idempotency and admission; never accept those controls as model arguments. An uninstalled collection is unavailable, including during local tool execution.',
277
- 'A collection declaration does not create a submit tool, grant access, select retention or residency, provision storage, or expose records to MCP or an embedded assistant. Operators activate the definition and configure lifecycle, access, and any external binding. A connector does not become a collection unless the app explicitly supplies the source contract, and an external collection never becomes a writable mirror.',
277
+ 'A collection declaration does not create a submit tool, grant access, select retention or residency, provision storage, or expose records to MCP or an embedded assistant. Operators activate the definition and configure lifecycle, access, and any external binding. Native records retain legacy creation-time expiry until an Owner/Admin explicitly reviews and confirms preservation through Business settings or `noodle solutions records lifecycle`. Then expiry is null (until explicit erasure); activity history caps remain separate. Never change expiry in authored tools or infer an appointment was fulfilled from a saved request. A connector does not become a collection unless the app explicitly supplies the source contract, and an external collection never becomes a writable mirror.',
278
278
  '',
279
279
  '## Boundaries',
280
280
  '',
@@ -7,6 +7,7 @@ export function renderEmbeddedAssistantReference() {
7
7
  '',
8
8
  '## Contents',
9
9
  '',
10
+ '- Collect interactions',
10
11
  '- Architecture',
11
12
  '- Select the architecture before code',
12
13
  '- First-workflow default: continuous onboarding',
@@ -30,10 +31,21 @@ export function renderEmbeddedAssistantReference() {
30
31
  '',
31
32
  '## WhatsApp public alpha',
32
33
  '',
33
- 'Reuse the same server and public read-only capabilities with `publicMessaging({ channel: "whatsapp", capabilities: [product, siteIdentity] })` in `embeddedAssistant.access`. Import `publicMessaging` from `@noodleseed/one`. This projection is explicit: browser widgets, confirmed writes, customer identity and native forms are not supported in this first slice. A WhatsApp participant is anonymous; never infer a customer account from its phone number.',
34
+ 'Reuse the same server and public read-only capabilities with `publicMessaging({ channel: "whatsapp", capabilities: [product, siteIdentity] })` in `embeddedAssistant.access`. Import `publicMessaging` from `@noodleseed/one`. This projection is explicit: knowledge, pure public reads, and a `collect` opener listed with its confirmed native-record action. The platform’s natural collection gathers the opener’s fields in conversation, asks the consent question, renders the review verbatim and confirms; a field marked `private: true` never reaches the conversation model or the transcript. Other writes, connector-backed lookups, customer identity and browser widgets without an interaction are not supported. A WhatsApp participant is anonymous; never infer a customer account from its phone number.',
34
35
  '',
35
36
  'Operator setup uses `noodle channels whatsapp configure`, managed secret references, `webhook configure`, `doctor`, and `enable`. Start paused. Only a service advertising `features.whatsapp: 1`, durable storage, a running worker, a verified cost bound and matching provider webhook can enable. Use `noodle channels whatsapp --help` for the complete operator command catalog. Keep real keys in `noodle secrets`, never in source, flags or messages. ChatGPT and Claude continue to use the same app MCP endpoint with their own host configuration.',
36
37
  '',
38
+ '## Collect interactions',
39
+ '',
40
+ 'An opener `tool()` may declare `interaction: { kind: "collect", action, fields, review: "all", outcome }`: one definition of a form-like exchange that Noodle Seed presents with the controls each channel has. On the website the tool’s React `view` (or a standard form) collects the fields; on a messaging channel the platform renderer gathers the same fields in natural conversation, renders the review verbatim, and saves through the same confirmed `action`. The block carries meaning, never layout, ordering or screen language; `noodle validate` checks it against the action’s input schema.',
41
+ '',
42
+ '- `action` is a tool with `annotations.openAction({ confirm: true })` (never read-only) whose fulfilment saves one native record through `connectors.records`.',
43
+ '- `fields` name the action’s Zod input keys with one of seven controls: `text`, `textarea`, `email`, `phone`, `url`, `select` (a `z.enum` of strings) and `consent` (`z.literal(true)`, at most one; the runtime poses the consent question and never infers it).',
44
+ '- Nothing is private by default. `private: true` keeps that value out of the conversation model, transcript, logs and diagnostics. `optional: true` lets a person skip a field; an optional field the schema still requires must accept `""`.',
45
+ '- `initialValues: { workflow: { fromOutput: "workflow" } }` seeds action inputs from the opener’s `output`; every other required action input must be a field.',
46
+ '- `confirmationExpiry: { seconds }` may only shorten the channel’s default (ten minutes on WhatsApp), never lengthen it.',
47
+ '- On `publicMessaging`, list the opener and its action together. The compiler reports `channel_dependency_missing` for an opener selected alone and `channel_requirement_unsupported` for a widget, write or external connector call that has no collect interaction.',
48
+ '',
37
49
  '## Architecture',
38
50
  '',
39
51
  'The browser never receives a model key, assistant client secret, MCP token, or raw application session. The embedding SaaS authenticates its own user, its backend exchanges that verified identity through `@noodleseed/assistant/server`, and the browser receives only a short-lived assistant session.',
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodle-borg/agent-kit",
3
- "version": "0.106.0",
3
+ "version": "0.107.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,5 +1,6 @@
1
1
  import type { ChannelStore, ChannelTransaction } from './channel-store.js';
2
- import { type ChannelBinding, type ChannelConfigure, type ChannelEvent, type ChannelInbound, type ChannelLimitsInput } from './channel-types.js';
2
+ import { type ChannelBinding, type ChannelConfigure, type ChannelEvent, type ChannelInbound, type ChannelLimitsInput, type ChannelReplyButton } from './channel-types.js';
3
+ import { type ChannelTranscript } from './channel-work.js';
3
4
  import type { TenantRef } from './tenant-ref.js';
4
5
  export interface ChannelBlock {
5
6
  readonly participantId: string;
@@ -115,11 +116,12 @@ export declare class ChannelCoordinator {
115
116
  expireHistory(id: string, retentionMs: number): Promise<void>;
116
117
  claimTool(id: string, participantId: string, name: string): Promise<boolean>;
117
118
  event(id: string, eventId: string): Promise<ChannelEvent | undefined>;
118
- fail(id: string, eventId: string, lease: string, code: string): Promise<void>;
119
+ /** Close a running turn without a reply: `failed` for an error, `cancelled` for input that is deliberately not answered. */
120
+ fail(id: string, eventId: string, lease: string, code: string, state?: 'failed' | 'cancelled'): Promise<void>;
119
121
  receive(id: string, messages: readonly ChannelInbound[], retentionMs?: number): Promise<Pick<ChannelEvent, "state" | "id" | "code" | "participantId">[]>;
120
122
  claim(id: string, retentionMs?: number): Promise<import("./channel-work.js").ChannelWork | undefined>;
121
123
  renew(id: string, eventId: string, lease: string): Promise<void>;
122
- complete(id: string, eventId: string, lease: string, reply: string, retentionMs?: number, code?: string): Promise<void>;
124
+ complete(id: string, eventId: string, lease: string, reply: string, retentionMs?: number, code?: string, transcript?: ChannelTranscript, buttons?: readonly ChannelReplyButton[]): Promise<void>;
123
125
  assertSend(id: string, eventId: string, lease: string): Promise<void>;
124
126
  prepareSend(id: string): Promise<import("./channel-work.js").ChannelWork | undefined>;
125
127
  sent(id: string, eventId: string, lease: string, result: {
@@ -336,12 +336,13 @@ export class ChannelCoordinator {
336
336
  return channelValue(tx, id, eventId);
337
337
  });
338
338
  }
339
- async fail(id, eventId, lease, code) {
339
+ /** Close a running turn without a reply: `failed` for an error, `cancelled` for input that is deliberately not answered. */
340
+ async fail(id, eventId, lease, code, state = 'failed') {
340
341
  await this.store.transaction([id], async (tx) => {
341
342
  const event = await channelValue(tx, id, eventId);
342
343
  if (!event || event.lease !== lease || event.state !== 'running')
343
344
  return;
344
- await writeChannelEvent(tx, id, { ...event, state: 'failed', code }, this.now());
345
+ await writeChannelEvent(tx, id, { ...event, state, code }, this.now());
345
346
  });
346
347
  }
347
348
  receive(id, messages, retentionMs = CHANNEL_RETENTION_MS) {
@@ -353,8 +354,8 @@ export class ChannelCoordinator {
353
354
  renew(id, eventId, lease) {
354
355
  return renewChannelTurn(this.store, id, eventId, lease, this.now());
355
356
  }
356
- complete(id, eventId, lease, reply, retentionMs = CHANNEL_RETENTION_MS, code) {
357
- return completeChannelTurn(this.store, id, eventId, lease, reply, this.now(), retentionMs, code);
357
+ complete(id, eventId, lease, reply, retentionMs = CHANNEL_RETENTION_MS, code, transcript, buttons) {
358
+ return completeChannelTurn(this.store, id, eventId, lease, reply, this.now(), retentionMs, code, transcript, buttons);
358
359
  }
359
360
  assertSend(id, eventId, lease) {
360
361
  return assertChannelSend(this.store, id, eventId, lease, this.now());
@@ -67,7 +67,10 @@ export async function receiveChannelMessages(store, id, messages, now, retention
67
67
  const sequence = ((await channelValue(tx, id, 'sequence')) ?? 0) + 1;
68
68
  await tx.put(id, channelRow('sequence', 'counter', sequence, now));
69
69
  const eventId = `e_${String(sequence).padStart(20, '0')}`;
70
- const unsupported = message.text === undefined || message.text.length > binding.limits.textCharacters;
70
+ const text = message.text !== undefined && message.text.length <= binding.limits.textCharacters
71
+ ? message.text
72
+ : undefined;
73
+ const unsupported = text === undefined && message.button === undefined;
71
74
  let event = {
72
75
  id: eventId,
73
76
  participantId,
@@ -79,7 +82,8 @@ export async function receiveChannelMessages(store, id, messages, now, retention
79
82
  attempts: 0,
80
83
  state: code ? 'refused' : unsupported ? 'reply' : 'queued',
81
84
  ...(code ? { code } : {}),
82
- ...(!code && !unsupported ? { text: message.text } : {}),
85
+ ...(!code && text !== undefined ? { text } : {}),
86
+ ...(!code && message.button !== undefined ? { button: message.button } : {}),
83
87
  ...(!code && unsupported
84
88
  ? {
85
89
  reply: `Please send a text message of up to ${binding.limits.textCharacters} characters. For help, contact ${binding.supportEmail}.`,
@@ -1,7 +1,7 @@
1
1
  /** Private channel journal. Each binding is a transaction/lease authority, not a browser session. */
2
2
  export interface ChannelRow {
3
3
  readonly id: string;
4
- readonly kind: 'binding' | 'asset' | 'participant' | 'event' | 'counter' | 'spend' | 'block' | 'mutation';
4
+ readonly kind: 'binding' | 'asset' | 'participant' | 'event' | 'counter' | 'spend' | 'block' | 'mutation' | 'interaction';
5
5
  readonly state?: string;
6
6
  readonly updatedAt: number;
7
7
  readonly expiresAt?: number;
@@ -71,13 +71,24 @@ export interface ChannelParticipant {
71
71
  readonly cooldownUntil?: number;
72
72
  }
73
73
  export type ChannelEventState = 'queued' | 'running' | 'reply' | 'sending' | 'accepted' | 'delivered' | 'read' | 'refused' | 'expired' | 'failed' | 'cancelled' | 'unknown';
74
+ /** A native reply button: an opaque server-bound id and the label the participant sees. */
75
+ export interface ChannelReplyButton {
76
+ readonly id: string;
77
+ readonly title: string;
78
+ }
74
79
  export interface ChannelEvent {
75
80
  readonly id: string;
76
81
  readonly participantId: string;
77
82
  readonly providerId: string;
78
83
  readonly providerMessageId?: string;
79
84
  readonly text?: string;
85
+ /** The button the participant tapped, in place of text. */
86
+ readonly button?: ChannelReplyButton;
80
87
  readonly reply?: string;
88
+ /** What history keeps of the reply when it must differ from the sent text; replaces `reply` once dispatched. */
89
+ readonly replyTranscript?: string;
90
+ /** Native reply buttons offered with the reply; content-free, dropped once dispatch is attempted. */
91
+ readonly buttons?: readonly ChannelReplyButton[];
81
92
  readonly eventAt: number;
82
93
  readonly receivedAt: number;
83
94
  readonly generation: number;
@@ -94,6 +105,7 @@ export interface ChannelInbound {
94
105
  readonly address: ChannelAddress;
95
106
  readonly eventAt: number;
96
107
  readonly text?: string;
108
+ readonly button?: ChannelReplyButton;
97
109
  }
98
110
  export declare class ChannelError extends Error {
99
111
  readonly code: string;
@@ -1,5 +1,5 @@
1
1
  import type { ChannelStore } from './channel-store.js';
2
- import { type ChannelBinding, type ChannelEvent, type ChannelParticipant } from './channel-types.js';
2
+ import { type ChannelBinding, type ChannelEvent, type ChannelParticipant, type ChannelReplyButton } from './channel-types.js';
3
3
  export interface ChannelWork {
4
4
  readonly binding: ChannelBinding;
5
5
  readonly participant: ChannelParticipant;
@@ -7,7 +7,12 @@ export interface ChannelWork {
7
7
  }
8
8
  export declare function claimChannelTurn(store: ChannelStore, id: string, now: number, retentionMs?: number): Promise<ChannelWork | undefined>;
9
9
  export declare function renewChannelTurn(store: ChannelStore, id: string, eventId: string, lease: string, now: number): Promise<void>;
10
- export declare function completeChannelTurn(store: ChannelStore, id: string, eventId: string, lease: string, reply: string, now: number, retentionMs?: number, code?: string): Promise<void>;
10
+ /** Text the transcript keeps instead of the raw message or the sent reply, e.g. with private values redacted. */
11
+ export interface ChannelTranscript {
12
+ readonly user?: string;
13
+ readonly assistant?: string;
14
+ }
15
+ export declare function completeChannelTurn(store: ChannelStore, id: string, eventId: string, lease: string, reply: string, now: number, retentionMs?: number, code?: string, transcript?: ChannelTranscript, buttons?: readonly ChannelReplyButton[]): Promise<void>;
11
16
  export declare function prepareChannelSend(store: ChannelStore, id: string, now: number): Promise<ChannelWork | undefined>;
12
17
  export declare function updateChannelSend(store: ChannelStore, id: string, eventId: string, lease: string, result: {
13
18
  state: 'accepted' | 'unknown' | 'failed';
@@ -99,7 +99,7 @@ export async function renewChannelTurn(store, id, eventId, lease, now) {
99
99
  await writeChannelEvent(tx, id, { ...event, leaseUntil: Math.min(now + LEASE_MS, event.turnDeadline) }, now);
100
100
  });
101
101
  }
102
- export async function completeChannelTurn(store, id, eventId, lease, reply, now, retentionMs = CHANNEL_RETENTION_MS, code) {
102
+ export async function completeChannelTurn(store, id, eventId, lease, reply, now, retentionMs = CHANNEL_RETENTION_MS, code, transcript = {}, buttons) {
103
103
  if (!reply.trim() || reply.length > 4096)
104
104
  throw new ChannelError('reply_invalid');
105
105
  await store.transaction([id], async (tx) => {
@@ -107,17 +107,23 @@ export async function completeChannelTurn(store, id, eventId, lease, reply, now,
107
107
  const participant = await channelValue(tx, id, event.participantId);
108
108
  if (!participant)
109
109
  throw new ChannelError('context_expired');
110
+ const userText = transcript.user ?? event.text ?? '';
110
111
  const history = [
111
112
  ...participant.history.filter((entry) => entry.at > now - retentionMs),
112
- { role: 'user', content: event.text ?? '', at: event.receivedAt },
113
- { role: 'assistant', content: reply, at: now },
113
+ { role: 'user', content: userText, at: event.receivedAt },
114
+ { role: 'assistant', content: transcript.assistant ?? reply, at: now },
114
115
  ].slice(-20);
115
116
  await tx.put(id, channelRow(participant.id, 'participant', { ...participant, history }, now, {
116
117
  expiresAt: participant.lastInboundAt + retentionMs,
117
118
  }));
118
119
  await writeChannelEvent(tx, id, {
119
120
  ...event,
121
+ ...(event.text === undefined ? {} : { text: userText }),
120
122
  reply,
123
+ ...(transcript.assistant !== undefined && transcript.assistant !== reply
124
+ ? { replyTranscript: transcript.assistant }
125
+ : {}),
126
+ ...(buttons !== undefined && buttons.length > 0 ? { buttons } : {}),
121
127
  state: 'reply',
122
128
  lease: undefined,
123
129
  leaseUntil: undefined,
@@ -160,8 +166,12 @@ export async function updateChannelSend(store, id, eventId, lease, result, now)
160
166
  const early = result.providerMessageId
161
167
  ? await channelValue(tx, id, `delivery:${channelDigest(result.providerMessageId)}`)
162
168
  : undefined;
169
+ // Once dispatch has been attempted the sent text and its buttons are never needed again: the
170
+ // row keeps only what the transcript keeps, so a redacted reply does not outlive its delivery.
171
+ const { replyTranscript: _kept, buttons: _offered, ...dispatched } = event;
163
172
  await writeChannelEvent(tx, id, {
164
- ...event,
173
+ ...dispatched,
174
+ ...(event.replyTranscript === undefined ? {} : { reply: event.replyTranscript }),
165
175
  ...result,
166
176
  ...(early && early !== 'sent' ? { state: early } : {}),
167
177
  leaseUntil: undefined,
@@ -0,0 +1,104 @@
1
+ import { validateJsonSchema } from '@noodle-borg/compiler';
2
+ import { z } from 'zod';
3
+ import { applyCapture, neededFields, } from './collection-ledger.js';
4
+ import { boundedJsonCall } from './collection-model-call.js';
5
+ import { normaliseTypedValue, redactSpans, scanTypedValues, } from './collection-parsers.js';
6
+ const PARSED = new Set(['email', 'phone', 'url']);
7
+ const extraction = z.record(z.string(), z.unknown());
8
+ function isParsed(control) {
9
+ return PARSED.has(control);
10
+ }
11
+ /**
12
+ * Deterministic step: a parser assigns a value only when exactly one candidate meets exactly one
13
+ * needed field of that control; competing candidates mark the field ambiguous. Every candidate,
14
+ * assigned or not, is redacted from the text before a model sees it.
15
+ */
16
+ function parseTurn(needed, utterance) {
17
+ const controls = [...new Set(needed.map((field) => field.control).filter(isParsed))];
18
+ const spans = scanTypedValues(utterance, controls);
19
+ const outcome = {};
20
+ const assigned = new Set();
21
+ for (const control of controls) {
22
+ const fields = needed.filter((field) => field.control === control);
23
+ const candidates = spans.filter((span) => span.control === control);
24
+ if (candidates.length === 0)
25
+ continue;
26
+ const [onlyField] = fields;
27
+ const [onlyCandidate] = candidates;
28
+ if (fields.length === 1 && candidates.length === 1 && onlyField && onlyCandidate) {
29
+ outcome[onlyField.key] = { status: 'captured', value: onlyCandidate.value };
30
+ assigned.add(onlyCandidate);
31
+ continue;
32
+ }
33
+ const reason = candidates.length > 1 ? 'multiple_candidates' : 'multiple_fields';
34
+ for (const field of fields)
35
+ outcome[field.key] = { status: 'ambiguous', reason };
36
+ }
37
+ const redacted = redactSpans(utterance, spans, (span) => assigned.has(span) ? `[${span.control} captured]` : `[${span.control} withheld]`);
38
+ return { outcome, redacted };
39
+ }
40
+ /** Control rules first, then the action's own property schema; a failure is a code, never a value. */
41
+ function validateCandidate(field, raw) {
42
+ let value = raw;
43
+ if (isParsed(field.control)) {
44
+ value = typeof raw === 'string' ? normaliseTypedValue(field.control, raw) : undefined;
45
+ if (value === undefined)
46
+ return { status: 'invalid', reason: 'format' };
47
+ }
48
+ else if (field.control === 'select') {
49
+ const option = typeof raw === 'string'
50
+ ? field.options?.find((candidate) => candidate.toLowerCase() === raw.trim().toLowerCase())
51
+ : undefined;
52
+ if (option === undefined)
53
+ return { status: 'invalid', reason: 'option' };
54
+ value = option;
55
+ }
56
+ else if (typeof raw === 'string') {
57
+ value = raw.trim();
58
+ }
59
+ if (validateJsonSchema(field.schema, value).length > 0)
60
+ return { status: 'invalid', reason: 'schema' };
61
+ return { status: 'captured', value };
62
+ }
63
+ function extractionInstruction(needed, ledger) {
64
+ const fields = needed.map((field) => ({
65
+ key: field.key,
66
+ title: field.title,
67
+ control: field.control,
68
+ ...(field.options === undefined ? {} : { options: field.options }),
69
+ ...(ledger.fields[field.key]?.status === 'rejected_by_user' && field.key in ledger.values
70
+ ? {
71
+ current: ledger.values[field.key],
72
+ note: 'the user said this value is wrong; apply their correction to it',
73
+ }
74
+ : {}),
75
+ }));
76
+ return [
77
+ 'Read the values the user states for these fields and reply with exactly one JSON object whose keys are the field keys.',
78
+ 'Use null for any field the message does not state. Never guess, infer or invent a value.',
79
+ 'Keep the user wording for text fields. For a select field return one of its options exactly, or null.',
80
+ 'Bracketed placeholders such as [email captured] stand for values already taken; leave those fields null.',
81
+ `Fields: ${JSON.stringify(fields)}`,
82
+ ].join('\n');
83
+ }
84
+ /**
85
+ * One user turn while a collection is open: parsers first, then at most one bounded extraction call
86
+ * for whatever still needs a value. Values reach the caller only inside the returned ledger.
87
+ */
88
+ export async function capture(spec, ledger, utterance, deps, now) {
89
+ const needed = neededFields(spec, ledger);
90
+ const { outcome, redacted } = parseTurn(needed, utterance);
91
+ const remaining = needed.filter((field) => outcome[field.key] === undefined);
92
+ const content = redacted.replace(/\[(?:email|phone|url) (?:captured|withheld)\]/g, '').trim();
93
+ if (remaining.length > 0 && /[\p{L}\p{N}]/u.test(content)) {
94
+ const extracted = await boundedJsonCall(deps, extractionInstruction(remaining, ledger), redacted, extraction);
95
+ for (const field of remaining) {
96
+ const raw = extracted?.[field.key];
97
+ if (raw === undefined || raw === null)
98
+ continue;
99
+ outcome[field.key] = validateCandidate(field, raw);
100
+ }
101
+ }
102
+ return applyCapture(spec, ledger, outcome, now);
103
+ }
104
+ //# sourceMappingURL=collection-capture.js.map
@@ -0,0 +1,93 @@
1
+ import { z } from 'zod';
2
+ import { boundedJsonCall } from './collection-model-call.js';
3
+ import { scanTypedValues } from './collection-parsers.js';
4
+ /** A judged consent counts only at or above this confidence; below it the runtime re-asks. */
5
+ export const CONSENT_THRESHOLD = 0.85;
6
+ /** A judged confirmation binds a real write, so it is stricter than consent. */
7
+ export const CONFIRM_THRESHOLD = 0.95;
8
+ export const CONFIRMATION_AFFIRMATIVES = [
9
+ 'yes',
10
+ 'y',
11
+ 'ok',
12
+ 'okay',
13
+ 'confirm',
14
+ 'confirmed',
15
+ 'send',
16
+ 'send it',
17
+ 'go ahead',
18
+ 'sure',
19
+ 'yes please',
20
+ 'do it',
21
+ 'looks good',
22
+ ];
23
+ export const CONFIRMATION_NEGATIVES = [
24
+ 'no',
25
+ 'cancel',
26
+ 'stop',
27
+ 'never mind',
28
+ 'nevermind',
29
+ "don't",
30
+ 'dont',
31
+ 'forget it',
32
+ ];
33
+ /** Words that turn a reply naming a field into an edit; shared with the runtime's reopen rule. */
34
+ export const CORRECTION_CUES = /\b(change|changed|actually|instead|wrong|not|should be|make it|update|correct|rather|swap|replace|edit|fix)\b/;
35
+ const judgment = z.object({ affirmative: z.boolean(), confidence: z.number().min(0).max(1) });
36
+ const PARSED = ['email', 'phone', 'url'];
37
+ /** Lowercase, collapse whitespace, straighten quotes and drop trailing punctuation. */
38
+ export function normaliseReply(text) {
39
+ return text
40
+ .replace(/[‘’]/g, "'")
41
+ .toLowerCase()
42
+ .replace(/\s+/g, ' ')
43
+ .trim()
44
+ .replace(/[\s.!?,;:…]+$/u, '');
45
+ }
46
+ /**
47
+ * Judge whether a free reply answers the fixed question affirmatively. The two messages carry the
48
+ * question and the reply only, never a collected value or the review.
49
+ */
50
+ async function judgeAffirmative(deps, question, reply) {
51
+ return boundedJsonCall(deps, [
52
+ `The assistant asked the user this yes-or-no question: ${JSON.stringify(question)}`,
53
+ 'Judge whether the reply is an affirmative answer to exactly that question.',
54
+ 'A reply that asks for a change, adds a condition, gives new information or answers something else is not affirmative.',
55
+ 'Reply with exactly one JSON object shaped {"affirmative": true or false, "confidence": a number from 0 to 1}.',
56
+ ].join('\n'), reply, judgment);
57
+ }
58
+ export async function consentReply(text, spec, deps) {
59
+ const reply = normaliseReply(text);
60
+ if (reply === 'yes')
61
+ return 'given';
62
+ if (reply === 'no')
63
+ return 'declined';
64
+ const judged = await judgeAffirmative(deps, spec.consentQuestion, text);
65
+ if (judged === undefined || judged.confidence < CONSENT_THRESHOLD)
66
+ return 'unclear';
67
+ return judged.affirmative ? 'given' : 'declined';
68
+ }
69
+ /** A reply that offers a value or names a field with a correction cue is an edit, never a confirmation. */
70
+ function carriesEdit(text, spec) {
71
+ const controls = PARSED.filter((control) => spec.fields.some((field) => field.control === control));
72
+ if (scanTypedValues(text, controls).length > 0)
73
+ return true;
74
+ const lower = text.toLowerCase();
75
+ const namesField = spec.fields.some((field) => field.control !== 'consent' &&
76
+ (lower.includes(field.title.toLowerCase()) ||
77
+ new RegExp(`\\b${field.key}\\b`, 'i').test(text)));
78
+ return namesField && CORRECTION_CUES.test(lower);
79
+ }
80
+ export async function confirmationReply(text, spec, deps) {
81
+ if (carriesEdit(text, spec))
82
+ return 'edit';
83
+ const reply = normaliseReply(text);
84
+ if (CONFIRMATION_AFFIRMATIVES.includes(reply))
85
+ return 'confirm';
86
+ if (CONFIRMATION_NEGATIVES.includes(reply))
87
+ return 'cancel';
88
+ const judged = await judgeAffirmative(deps, 'Shall I send it? Yes or no.', text);
89
+ if (judged === undefined || !judged.affirmative || judged.confidence < CONFIRM_THRESHOLD)
90
+ return 'unclear';
91
+ return 'confirm';
92
+ }
93
+ //# sourceMappingURL=collection-judgment.js.map
@@ -0,0 +1,105 @@
1
+ import type { JsonSchema } from '@noodle-borg/compiler';
2
+ /**
3
+ * Runtime-owned natural collection ledger (ADR 0240 decision 5). The ledger is the pre-proposal
4
+ * stage of the shared confirmation lifecycle: once fields are complete the proposal it stores is an
5
+ * opaque prepared-tool continuation, never a second confirmation engine.
6
+ */
7
+ export type CollectionControl = 'text' | 'textarea' | 'email' | 'phone' | 'url' | 'select' | 'consent';
8
+ export interface CollectionField {
9
+ readonly key: string;
10
+ readonly title: string;
11
+ readonly control: CollectionControl;
12
+ /** Never enters the conversation model, transcript, logs or diagnostics. */
13
+ readonly private?: true;
14
+ readonly optional?: true;
15
+ readonly options?: readonly string[];
16
+ /** The action input property this field fills; every candidate is validated against it. */
17
+ readonly schema: JsonSchema;
18
+ }
19
+ export interface CollectionSpec {
20
+ /** Opener tool name. */
21
+ readonly interactionId: string;
22
+ readonly action: string;
23
+ readonly fields: readonly CollectionField[];
24
+ readonly consentQuestion: string;
25
+ readonly successMessage: string;
26
+ readonly confirmationExpiryMs: number;
27
+ }
28
+ export type CollectionPhase = 'collecting' | 'awaiting_consent' | 'awaiting_confirmation' | 'executing' | 'completed' | 'cancelled' | 'expired';
29
+ export type CollectionFieldStatus = 'missing' | 'captured' | 'invalid' | 'ambiguous' | 'rejected_by_user';
30
+ export interface CollectionFieldState {
31
+ readonly status: CollectionFieldStatus;
32
+ /** Short structured code, never a value. */
33
+ readonly reason?: string;
34
+ readonly attempts: number;
35
+ }
36
+ export interface CollectionConsent {
37
+ readonly given: boolean;
38
+ readonly questionVersion: string;
39
+ readonly at: string;
40
+ }
41
+ export interface CollectionProposal {
42
+ readonly id: string;
43
+ /** Exact runtime-rendered review the confirmation binds to. */
44
+ readonly review: string;
45
+ /** Opaque prepared-tool continuation released to the runtime only after confirmation. */
46
+ readonly continuation: unknown;
47
+ readonly createdAt: string;
48
+ readonly expiresAt: string;
49
+ }
50
+ /** Sealed at rest: values include private fields, so this record never leaves custody unredacted. */
51
+ export interface CollectionLedger {
52
+ readonly id: string;
53
+ readonly bindingId: string;
54
+ readonly participantId: string;
55
+ readonly interactionId: string;
56
+ readonly action: string;
57
+ readonly phase: CollectionPhase;
58
+ readonly fields: Readonly<Record<string, CollectionFieldState>>;
59
+ readonly values: Readonly<Record<string, unknown>>;
60
+ readonly consent?: CollectionConsent;
61
+ readonly proposal?: CollectionProposal;
62
+ /** The proposal that was executed, kept after completion so its native confirmation echoes the same receipt. */
63
+ readonly executedProposalId?: string;
64
+ readonly createdAt: string;
65
+ readonly updatedAt: string;
66
+ readonly expiresAt: string;
67
+ }
68
+ export type CaptureOutcome = Readonly<Record<string, {
69
+ readonly status: 'captured';
70
+ readonly value: unknown;
71
+ } | {
72
+ readonly status: 'invalid' | 'ambiguous';
73
+ readonly reason: string;
74
+ }>>;
75
+ export declare class CollectionError extends Error {
76
+ readonly code: string;
77
+ constructor(code: string);
78
+ }
79
+ export declare function openCollection(spec: CollectionSpec, input: {
80
+ readonly id: string;
81
+ readonly bindingId: string;
82
+ readonly participantId: string;
83
+ readonly now: number;
84
+ readonly retentionMs?: number;
85
+ }): CollectionLedger;
86
+ /** Non-consent fields a capture turn may still fill or repair. */
87
+ export declare function neededFields(spec: CollectionSpec, ledger: CollectionLedger): readonly CollectionField[];
88
+ export declare function applyCapture(spec: CollectionSpec, ledger: CollectionLedger, outcome: CaptureOutcome, now: number): CollectionLedger;
89
+ /** The user said a stored value is wrong; keep it so a partial correction can be applied. */
90
+ export declare function rejectField(spec: CollectionSpec, ledger: CollectionLedger, key: string, now: number): CollectionLedger;
91
+ export declare function applyConsent(spec: CollectionSpec, ledger: CollectionLedger, decision: 'given' | 'declined', questionVersion: string, now: number): CollectionLedger;
92
+ export declare function attachProposal(spec: CollectionSpec, ledger: CollectionLedger, proposal: {
93
+ readonly id: string;
94
+ readonly review: string;
95
+ readonly continuation: unknown;
96
+ }, now: number): CollectionLedger;
97
+ export declare function pendingProposal(ledger: CollectionLedger, now: number): CollectionProposal | undefined;
98
+ export declare function applyConfirmation(ledger: CollectionLedger, act: 'confirm' | 'cancel', now: number): CollectionLedger;
99
+ export declare function completeCollection(ledger: CollectionLedger, now: number): CollectionLedger;
100
+ /** The participant asked to change something before saying what: the proposal and its acts lapse, every value stays. */
101
+ export declare function withdrawProposal(ledger: CollectionLedger, now: number): CollectionLedger;
102
+ export declare function cancelCollection(ledger: CollectionLedger, now: number): CollectionLedger;
103
+ /** Returns the same ledger when nothing has expired, so callers can detect a change by identity. */
104
+ export declare function expireCollection(ledger: CollectionLedger, now: number): CollectionLedger;
105
+ //# sourceMappingURL=collection-ledger.d.ts.map