jsfe 1.7.1 → 1.7.3

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 (40) hide show
  1. package/README.md +5 -0
  2. package/dist/forms/contracts.d.ts.map +1 -1
  3. package/dist/forms/contracts.js +15 -5
  4. package/dist/forms/flows.d.ts +3 -155
  5. package/dist/forms/flows.d.ts.map +1 -1
  6. package/dist/forms/flows.js +98 -32
  7. package/dist/forms/proposal-runtime.d.ts +5 -0
  8. package/dist/forms/proposal-runtime.d.ts.map +1 -0
  9. package/dist/forms/proposal-runtime.js +9 -0
  10. package/dist/helpers/form-conversation.d.ts +28 -0
  11. package/dist/helpers/form-conversation.d.ts.map +1 -0
  12. package/dist/helpers/form-conversation.js +57 -0
  13. package/dist/helpers/form-proposals.d.ts +17 -0
  14. package/dist/helpers/form-proposals.d.ts.map +1 -0
  15. package/dist/helpers/form-proposals.js +292 -0
  16. package/dist/helpers/form-state.d.ts +1 -0
  17. package/dist/helpers/form-state.d.ts.map +1 -1
  18. package/dist/helpers/form-state.js +10 -2
  19. package/dist/helpers/index.d.ts +35 -1
  20. package/dist/helpers/index.d.ts.map +1 -1
  21. package/dist/helpers/index.js +6 -1
  22. package/dist/helpers/match-inputs.js +1 -1
  23. package/dist/helpers/proposal-contract.d.ts +7 -0
  24. package/dist/helpers/proposal-contract.d.ts.map +1 -0
  25. package/dist/helpers/proposal-contract.js +95 -0
  26. package/dist/host/capabilities.d.ts.map +1 -1
  27. package/dist/host/capabilities.js +6 -0
  28. package/dist/host/flow-recovery.d.ts +19 -0
  29. package/dist/host/flow-recovery.d.ts.map +1 -0
  30. package/dist/host/flow-recovery.js +43 -0
  31. package/dist/host/index.d.ts +2 -0
  32. package/dist/host/index.d.ts.map +1 -1
  33. package/dist/host/index.js +1 -0
  34. package/dist/index.d.ts +13 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +31 -2
  37. package/dist/runtime-identity.js +1 -1
  38. package/docs/development-release.md +12 -0
  39. package/docs/forms.md +40 -1
  40. package/package.json +1 -1
@@ -0,0 +1,292 @@
1
+ /** Owned input proposals. Candidates and their consent remain separate from canonical values and action proposals. */
2
+ import { formInputApplies, formValuesEqual, hasFormValue } from './form-state.js';
3
+ import { formSignature, actionConfirmationValid } from './form-actions.js';
4
+ import { validateFormProposalPolicy, validateFormProposalDescriptor, validateProposalData } from './proposal-contract.js';
5
+ const own = (value, key) => Object.prototype.hasOwnProperty.call(value || {}, key);
6
+ const statuses = new Set(['pending', 'confirmed', 'declined', 'superseded']);
7
+ const fail = message => { throw new Error('FORM input proposal ' + message); };
8
+ const text = value => typeof value === 'string' && !!value.trim();
9
+ function data(value) {
10
+ const seen = new Set();
11
+ const inspect = item => {
12
+ if (!item || typeof item !== 'object')
13
+ return;
14
+ if (seen.has(item))
15
+ fail('requires acyclic JSON data');
16
+ seen.add(item);
17
+ if (Object.getOwnPropertySymbols(item).length)
18
+ fail('requires JSON data without symbols');
19
+ for (const [key, property] of Object.entries(Object.getOwnPropertyDescriptors(item))) {
20
+ if (Array.isArray(item) && key === 'length')
21
+ continue;
22
+ if (Array.isArray(item) && (!/^(0|[1-9]\d*)$/.test(key) || Number(key) >= item.length))
23
+ fail('requires JSON arrays without extra properties');
24
+ if (property.get || property.set || !property.enumerable)
25
+ fail('requires enumerable JSON data properties');
26
+ inspect(property.value);
27
+ }
28
+ seen.delete(item);
29
+ };
30
+ inspect(value);
31
+ validateProposalData(value);
32
+ return structuredClone(value);
33
+ }
34
+ function inputOwner(cargo, id, name) {
35
+ const state = cargo?.forms?.[id], spec = state?.specs?.flatMap(input => input.any_of || [input]).find(input => input.name === name);
36
+ if (!state || state.id !== id || !spec || ['__proto__', 'prototype', 'constructor'].includes(name))
37
+ fail('requires a live owning input');
38
+ if (!Number.isSafeInteger(state.revision) || state.revision < 0 || !text(state.definition) || !text(state.task))
39
+ fail('has invalid owner identity or revision');
40
+ return { state, spec };
41
+ }
42
+ function candidate(cargo, reading) {
43
+ if (!reading || reading.ok !== true || !hasFormValue(reading.value))
44
+ fail('requires a validated nonempty candidate');
45
+ const owner = inputOwner(cargo, reading.form, reading.name);
46
+ data(reading.value);
47
+ return owner;
48
+ }
49
+ const identity = state => ({ id: state.id, definition: state.definition, task: state.task });
50
+ const signature = (owner, input, value, revision, descriptor) => formSignature({ owner, input, value, revision, binding: descriptor.binding ?? null, descriptor });
51
+ const token = (value, record) => !!value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).every(key => ['id', 'signature'].includes(key)) && value.id === record?.id && value.signature === record?.signature;
52
+ const sourceReading = reading => Object.fromEntries(['raw', 'utterance', 'origin', 'current', 'asked'].filter(key => reading[key] !== undefined).map(key => [key, data(reading[key])]));
53
+ function recordFor(cargo, state, name) {
54
+ const record = state.inputProposals?.[name];
55
+ if (!record)
56
+ return null;
57
+ if (!text(record.id) || record.form !== state.id || record.input !== name || !statuses.has(record.status) || typeof record.committed !== 'boolean' || !Number.isSafeInteger(record.owner_revision) || record.owner_revision < 0 || !Number.isSafeInteger(record.revision) || record.revision < 1 || typeof record.child !== 'string' || !hasFormValue(record.value) || !record.reading || typeof record.reading !== 'object' || Array.isArray(record.reading))
58
+ fail('has invalid retained state');
59
+ data(record.value);
60
+ data(record.reading);
61
+ const descriptor = validateFormProposalDescriptor(record.descriptor);
62
+ if (!formValuesEqual(record.owner, identity(state)) || !formValuesEqual(record.binding, descriptor.binding ?? null) || record.signature !== signature(record.owner, name, record.value, record.owner_revision, descriptor))
63
+ fail('retained identity or binding changed');
64
+ return record;
65
+ }
66
+ const active = record => !!record && !record.committed && ['pending', 'confirmed'].includes(record.status);
67
+ function contextChild(cargo, state, id) {
68
+ const child = cargo.forms?.[id];
69
+ return child && child.id === id && child.context_only === true && child.parent === state.id && child.task === state.task && state.contexts?.[child.definition] === id ? child : null;
70
+ }
71
+ function withdraw(cargo, state, record) {
72
+ const child = contextChild(cargo, state, record.child);
73
+ if (child && child.proposalPrompt?.id === record.id && child.proposalPrompt?.signature === record.signature && child.proposalPrompt?.input === record.input) {
74
+ child.confirmation = null;
75
+ delete child.proposalPrompt;
76
+ }
77
+ delete record.approval;
78
+ }
79
+ const current = (state, record) => record.owner_revision === state.revision && record.signature === signature(identity(state), record.input, record.value, state.revision, record.descriptor);
80
+ function proofValid(cargo, state, record) {
81
+ if (!record || record.status !== 'confirmed' || record.committed || record.deferred || !current(state, record) || !record.approval)
82
+ return false;
83
+ const proof = record.approval;
84
+ if (proof.id !== record.id || proof.signature !== record.signature || proof.child !== record.child || !Number.isSafeInteger(proof.revision) || proof.confirmation?.revision !== proof.revision)
85
+ return false;
86
+ const child = cargo.forms?.[record.child];
87
+ return !child || !!contextChild(cargo, state, record.child) && child.proposalPrompt?.id === record.id && child.proposalPrompt?.signature === record.signature && child.revision === proof.revision && formValuesEqual(child.confirmation, proof.confirmation) && actionConfirmationValid(cargo, child.id, { signature: child.proposal?.signature || null });
88
+ }
89
+ function approved(cargo, state, record, reading) {
90
+ return token(reading.proposalResume, record) && token(reading.proposalApproval, record) && formValuesEqual(reading.value, record.value) && proofValid(cargo, state, record);
91
+ }
92
+ /** Static policy selectors use this batch's proposed values; a validator's explicit descriptor independently applies. */
93
+ export function formProposalPolicy(cargo, reading, check, accepted = []) {
94
+ const { state, spec } = inputOwner(cargo, reading.form, reading.name);
95
+ const declared = spec.proposal === undefined ? null : validateFormProposalPolicy(spec.proposal);
96
+ const dynamic = check && own(check, 'proposal') && check.proposal !== undefined ? validateFormProposalDescriptor(check.proposal) : null;
97
+ if (check?.ok === false)
98
+ return null;
99
+ const record = recordFor(cargo, state, reading.name), values = { ...state.values, ...Object.fromEntries(accepted.filter(hit => hit.form === state.id && hit.ok === true).map(hit => [hit.name, hit.value])), [reading.name]: reading.value };
100
+ const origin = reading.origin || 'answer';
101
+ const matches = declared && formInputApplies(declared, values, cargo, state) && (!declared.origins || declared.origins.includes(origin) || active(record));
102
+ return matches || dynamic ? { ...(matches ? declared : {}), ...(dynamic || {}) } : null;
103
+ }
104
+ /** A null descriptor admits ordinary input, but cannot silently waive the same retained candidate's consent. */
105
+ export function stageFormProposal(cargo, reading, descriptor) {
106
+ const { state } = candidate(cargo, reading), decision = descriptor === null ? null : validateFormProposalDescriptor(descriptor);
107
+ let old = recordFor(cargo, state, reading.name);
108
+ const resumed = own(reading, 'proposalResume');
109
+ if (resumed && !token(reading.proposalResume, old))
110
+ return false;
111
+ if (old?.committed && resumed)
112
+ return false;
113
+ if (decision === null) {
114
+ if (!old || old.status === 'superseded' || old.committed)
115
+ return !own(reading, 'proposalApproval');
116
+ if (old.status === 'declined') {
117
+ if (!resumed && !own(reading, 'proposalApproval')) {
118
+ withdraw(cargo, state, old);
119
+ old.status = 'superseded';
120
+ return true;
121
+ }
122
+ return false;
123
+ }
124
+ if (formValuesEqual(old.value, reading.value)) {
125
+ if (approved(cargo, state, old, reading))
126
+ return true;
127
+ withdraw(cargo, state, old);
128
+ old.status = 'superseded';
129
+ return false;
130
+ }
131
+ if (resumed)
132
+ return false;
133
+ withdraw(cargo, state, old);
134
+ old.status = 'superseded';
135
+ return !own(reading, 'proposalApproval');
136
+ }
137
+ const binding = data(decision.binding ?? null), value = data(reading.value), nextSignature = signature(identity(state), reading.name, value, state.revision, decision);
138
+ const same = old && !old.committed && ['pending', 'confirmed', 'declined'].includes(old.status) && formValuesEqual(old.value, value) && formValuesEqual(old.binding, binding);
139
+ if (same && old.status === 'declined' && resumed)
140
+ return false;
141
+ if (!same || old.status === 'declined') {
142
+ if (old) {
143
+ withdraw(cargo, state, old);
144
+ old.status = 'superseded';
145
+ }
146
+ const sequence = state.inputProposalSequence ?? 0;
147
+ if (!Number.isSafeInteger(sequence) || sequence < 0 || !Number.isSafeInteger(sequence + 1))
148
+ fail('has an invalid proposal sequence');
149
+ state.inputProposalSequence = sequence + 1;
150
+ old = { id: `${state.id}:proposal:${state.inputProposalSequence}`, form: state.id, input: reading.name, owner: identity(state), value, reading: sourceReading(reading), descriptor: decision, binding, signature: nextSignature, owner_revision: state.revision, revision: 1, status: 'pending', committed: false, deferred: false, child: '' };
151
+ state.inputProposals ||= {};
152
+ state.inputProposals[reading.name] = old;
153
+ }
154
+ else if (old.signature !== nextSignature) {
155
+ withdraw(cargo, state, old);
156
+ Object.assign(old, { value, descriptor: decision, binding, signature: nextSignature, owner_revision: state.revision, revision: old.revision + 1, status: 'pending' });
157
+ }
158
+ old.deferred = false;
159
+ return approved(cargo, state, old, reading);
160
+ }
161
+ /** Only actionable records enter the confirmation worker; dependency-deferred records await the normal batch. */
162
+ export function nextFormProposal(cargo, owner) {
163
+ const state = cargo.forms?.[owner];
164
+ if (!state)
165
+ return null;
166
+ for (const spec of state.specs.flatMap(input => input.any_of || [input])) {
167
+ const record = recordFor(cargo, state, spec.name);
168
+ if (active(record) && !record.deferred && formInputApplies(spec, { ...state.values, [spec.name]: record.value }, cargo, state))
169
+ return record;
170
+ }
171
+ return null;
172
+ }
173
+ /** Owner replay issues approval only for confirmed records; named child refresh never invents approval. */
174
+ export function formProposalReadings(cargo, owner, input = '', approve = true) {
175
+ const state = cargo.forms?.[owner];
176
+ if (!state)
177
+ return [];
178
+ const names = input ? [input] : Object.keys(state.inputProposals || {}), out = [];
179
+ for (const name of names) {
180
+ const { spec } = inputOwner(cargo, owner, name), record = recordFor(cargo, state, name);
181
+ if (!record || record.committed || record.status === 'superseded' || record.deferred || approve && record.status !== 'confirmed' || !input && record.status === 'declined')
182
+ continue;
183
+ const hit = { ...spec, ...data(record.reading), form: owner, name, value: data(record.value), revalidate: true, proposalResume: { id: record.id, signature: record.signature } };
184
+ delete hit.proposalApproval;
185
+ if (approve && proofValid(cargo, state, record))
186
+ hit.proposalApproval = { id: record.id, signature: record.signature };
187
+ out.push(hit);
188
+ }
189
+ return out;
190
+ }
191
+ /** Bind only a real context child; ordinary child slots and unrelated owners cannot collect consent. */
192
+ export function bindFormProposal(cargo, owner, input, childId) {
193
+ const { state } = inputOwner(cargo, owner, input), record = recordFor(cargo, state, input), child = contextChild(cargo, state, childId);
194
+ if (!active(record) || record.deferred || !current(state, record) || !child)
195
+ fail('cannot bind a stale proposal or foreign confirmation child');
196
+ if (record.child && record.child !== childId)
197
+ withdraw(cargo, state, record);
198
+ if (child.proposalPrompt?.id !== record.id || child.proposalPrompt?.signature !== record.signature)
199
+ child.confirmation = null;
200
+ record.child = childId;
201
+ child.proposalPrompt = { id: record.id, signature: record.signature, input };
202
+ return record;
203
+ }
204
+ /** Exact YES must have passed the ordinary confirm-form revision check; NO never admits the candidate. */
205
+ export function decideFormProposal(cargo, owner, input, childId, decision) {
206
+ if (!['confirmed', 'declined'].includes(decision))
207
+ fail('has an invalid decision');
208
+ const { state } = inputOwner(cargo, owner, input), record = recordFor(cargo, state, input), child = contextChild(cargo, state, childId);
209
+ if (!active(record) || record.deferred || !current(state, record) || !child || record.child !== childId || child.proposalPrompt?.id !== record.id || child.proposalPrompt?.signature !== record.signature || child.proposalPrompt?.input !== input)
210
+ return false;
211
+ if (decision === 'confirmed' && !actionConfirmationValid(cargo, childId, { signature: child.proposal?.signature || null }))
212
+ return false;
213
+ record.status = decision;
214
+ if (decision === 'confirmed')
215
+ record.approval = { id: record.id, signature: record.signature, child: childId, revision: child.revision, confirmation: data(child.confirmation) };
216
+ else {
217
+ withdraw(cargo, state, record);
218
+ child.confirmation = null;
219
+ }
220
+ return true;
221
+ }
222
+ /** Validate the entire final batch before consuming any proof; invoke immediately before acceptFormReadings. */
223
+ export function commitFormProposals(cargo, accepted = []) {
224
+ const changes = [], readings = [];
225
+ for (const hit of accepted) {
226
+ const { state } = candidate(cargo, hit);
227
+ readings.push({ state, hit });
228
+ const record = recordFor(cargo, state, hit.name);
229
+ if (!record) {
230
+ if (own(hit, 'proposalResume') || own(hit, 'proposalApproval'))
231
+ fail('approval has no live record');
232
+ continue;
233
+ }
234
+ const tagged = own(hit, 'proposalResume') || own(hit, 'proposalApproval');
235
+ if (tagged) {
236
+ if (!approved(cargo, state, record, hit))
237
+ fail('requires current unconsumed approval before commitment');
238
+ if (changes.some(change => change.record === record))
239
+ fail('approval cannot be consumed twice in one batch');
240
+ changes.push({ state, record, commit: true });
241
+ }
242
+ else if (!record.committed && record.status !== 'superseded' && formValuesEqual(record.value, hit.value))
243
+ fail('cannot commit a retained candidate without approval');
244
+ else if (!formValuesEqual(record.value, hit.value) && record.status !== 'superseded')
245
+ changes.push({ state, record, commit: false });
246
+ }
247
+ // Canonical exclusive selection also replaces uncommitted peer candidates, even if this input has no proposal.
248
+ // Plan every withdrawal after validation, so an invalid later reading cannot partially consume consent.
249
+ for (const { state, hit } of readings)
250
+ for (const group of state.specs.filter(spec => spec.exclusive === true && spec.any_of?.some(member => member.name === hit.name))) {
251
+ const peers = group.any_of.filter(member => member.name !== hit.name);
252
+ if (readings.some(other => other.state.id === state.id && peers.some(peer => peer.name === other.hit.name)))
253
+ fail('exclusive alternatives require clarification before commitment');
254
+ for (const peer of peers) {
255
+ const record = recordFor(cargo, state, peer.name);
256
+ if (active(record) && !changes.some(change => change.record === record))
257
+ changes.push({ state, record, commit: false });
258
+ }
259
+ }
260
+ for (const { state, record, commit } of changes) {
261
+ if (commit) {
262
+ record.committed = true;
263
+ record.deferred = false;
264
+ }
265
+ else
266
+ record.status = 'superseded';
267
+ withdraw(cargo, state, record);
268
+ }
269
+ return true;
270
+ }
271
+ /** Failed validation withdraws the candidate; unmet prerequisites wait for the normal dependency replay. */
272
+ export function settleFormProposalValidation(cargo, batch = {}) {
273
+ for (const hit of [...(batch.rejected || []), ...(batch.ignored || [])]) {
274
+ const state = cargo.forms?.[hit.form];
275
+ if (!state)
276
+ continue;
277
+ const record = recordFor(cargo, state, hit.name);
278
+ if (active(record)) {
279
+ withdraw(cargo, state, record);
280
+ record.status = 'superseded';
281
+ }
282
+ }
283
+ for (const hit of batch.pending || []) {
284
+ const state = cargo.forms?.[hit.form];
285
+ if (!state)
286
+ continue;
287
+ const record = recordFor(cargo, state, hit.name);
288
+ if (active(record))
289
+ record.deferred = true;
290
+ }
291
+ return batch;
292
+ }
@@ -12,6 +12,7 @@ export function enrichFormMatches(matches: any, chain: any, form: any, names?: a
12
12
  export function expandFormActivations(cargo: any, hits?: any[]): any[];
13
13
  /** No partial activation: validation failure suppresses inferred changes; pending prerequisites retain the complete group. */
14
14
  export function settleFormActivations(batch?: {}, cargo?: null): {
15
+ ignored: any[];
15
16
  accepted: any[];
16
17
  rejected: any[];
17
18
  pending: any[];
@@ -1 +1 @@
1
- {"version":3,"file":"form-state.d.ts","sourceRoot":"","sources":["../../src/helpers/form-state.js"],"names":[],"mappings":"AAMA,qDAKC;AAED,oDAEC;AAED,qFAAqF;AACrF,oEAsBC;AAuCD,2FAEC;AACD,8FAcC;AACD,2DAOC;AACD,uEAuCC;AAED,kGAAkG;AAClG,2GAOC;AACD,0HAA0H;AAC1H,uEAwBC;AACD,8HAA8H;AAC9H;;;;EAWC;AACD,uGAAuG;AACvG;;;;;;;;;;QA8BC;AAED,0FAA0F;AAC1F;;;SAIC;AACD,gGAAgG;AAChG;;;;;;;;;;;EAQC;AAED,sGAAsG;AACtG,2DAIC;AACD;;;GAGG;AACH;;;;;;;SAkBC;AAED,2FAA2F;AAC3F,oGAIC;AACD,gFA4BC;AAED,2GAA2G;AAC3G;;;EASC;AACD,mEAoBC;AACD,mEAGC;AACD,sEAUC;AACD,+EAEC;AACD,qFASC;AACD,oGAoCC;AACD;;;SAKC;AACD,qEAGC;AACD,gEAgBC;AACD,2DAQC;AACD,oGAUC;AAED,uEAAuE;AACvE,wHA+BC;AAED,uHAAuH;AACvH,6EAaC;AACD,mHAAmH;AACnH,iGA4CC;AACD,8DAQC;AACD,8EAeC;AAljBM,8CAAmE"}
1
+ {"version":3,"file":"form-state.d.ts","sourceRoot":"","sources":["../../src/helpers/form-state.js"],"names":[],"mappings":"AAOA,qDAKC;AAED,oDAEC;AAED,qFAAqF;AACrF,oEA2BC;AAuCD,2FAEC;AACD,8FAcC;AACD,2DAOC;AACD,uEAuCC;AAED,kGAAkG;AAClG,2GAOC;AACD,0HAA0H;AAC1H,uEAwBC;AACD,8HAA8H;AAC9H;;;;;EAWC;AACD,uGAAuG;AACvG;;;;;;;;;;QA8BC;AAED,0FAA0F;AAC1F;;;SAIC;AACD,gGAAgG;AAChG;;;;;;;;;;;EAQC;AAED,sGAAsG;AACtG,2DAIC;AACD;;;GAGG;AACH;;;;;;;SAkBC;AAED,2FAA2F;AAC3F,oGAIC;AACD,gFA4BC;AAED,2GAA2G;AAC3G;;;EASC;AACD,mEAoBC;AACD,mEAGC;AACD,sEAUC;AACD,+EAEC;AACD,qFASC;AACD,oGAoCC;AACD;;;SAKC;AACD,qEAGC;AACD,gEAgBC;AACD,2DAQC;AACD,oGAUC;AAED,uEAAuE;AACvE,wHA+BC;AAED,uHAAuH;AACvH,6EAaC;AACD,mHAAmH;AACnH,iGA4CC;AACD,8DAQC;AACD,8EAeC;AAvjBM,8CAAmE"}
@@ -1,3 +1,4 @@
1
+ import { validateFormProposalPolicy } from './proposal-contract.js';
1
2
  /** Shared FORM semantics. Pure readers plus explicit mutations of the supplied session container; no host I/O. */
2
3
  import { readInputProfile } from './match-inputs.js';
3
4
  import { validateMoneyOptions } from './money.js';
@@ -35,6 +36,13 @@ export function resolveFormInputs(inputs, presets = {}) {
35
36
  validateMoneyOptions(input.profile.money);
36
37
  }
37
38
  validateFormExhaustion(input.on_exhausted);
39
+ if (input.proposal !== undefined) {
40
+ if (!input.profile || input.form || input.any_of || input.bind)
41
+ throw new Error('FORM proposal requires a readable leaf input; declare it on each group member or inside the subform');
42
+ validateFormProposalPolicy(input.proposal);
43
+ }
44
+ if (input.max_misses !== undefined && (!Number.isInteger(input.max_misses) || input.max_misses < 1))
45
+ throw new Error('FORM max_misses must be a positive integer');
38
46
  if (input.max_attempts !== undefined && (!Number.isInteger(input.max_attempts) || input.max_attempts < 1))
39
47
  throw new Error('FORM max_attempts must be a positive integer');
40
48
  if (input.any_of)
@@ -262,7 +270,7 @@ export function settleFormActivations(batch = {}, cargo = null) {
262
270
  for (const [name, hit] of Object.entries(state.pending || {}))
263
271
  if (discarded.has(hit.activation_group))
264
272
  delete state.pending[name];
265
- return { accepted: accepted.filter(h => !held.has(h.activation_group) && !discarded.has(h.activation_group)),
273
+ return { ignored: [...(batch.ignored || [])], accepted: accepted.filter(h => !held.has(h.activation_group) && !discarded.has(h.activation_group)),
266
274
  rejected: rejected.map(h => h.activation_inferred ? { ...h, ...h.activation_entry, activation_inferred: false } : h), pending: pending.concat(accepted.filter(h => held.has(h.activation_group))).filter(h => !discarded.has(h.activation_group)).map(h => held.has(h.activation_group) ? { ...h, ok: undefined, outcome: 'pending', silent: true } : h) };
267
275
  }
268
276
  /** Explicit ownership: unrelated tasks and sibling inputs never share an instance by definition id. */
@@ -300,7 +308,7 @@ export function openFormInstance(cargo, definition, specs, { parent = null, inpu
300
308
  cargo.formSequence = Number(cargo.formSequence || 0) + 1;
301
309
  const id = `${definition}:${cargo.formSequence}`;
302
310
  cargo.forms[id] = { id, definition, task: owner?.task || task || id, parent, result_input: input,
303
- on_exhausted: on_exhausted || '', incomplete_outcome: incomplete, context_only: !!context, parent_revision: owner?.revision ?? null, specs, values: {}, accepted: {}, errors: {}, pending: {}, seeds: {}, attempts: {}, prepared: {}, data: {}, arguments: {}, contexts: {}, children: {}, childResults: {}, revision: 0,
311
+ on_exhausted: on_exhausted || '', incomplete_outcome: incomplete, context_only: !!context, parent_revision: owner?.revision ?? null, specs, values: {}, accepted: {}, errors: {}, pending: {}, seeds: {}, attempts: {}, misses: {}, feedback: {}, prepared: {}, data: {}, arguments: {}, contexts: {}, children: {}, childResults: {}, revision: 0,
304
312
  providers: providers || owner?.providers || null, questions: questions?.length ? questions : owner?.questions || [], status: 'collecting' };
305
313
  if (owner) {
306
314
  if (context) {
@@ -1,4 +1,21 @@
1
1
  export namespace STANDARD_FUNCTIONS {
2
+ export { formProposalPolicy };
3
+ export { stageFormProposal };
4
+ export { nextFormProposal };
5
+ export { formProposalReadings };
6
+ export { bindFormProposal };
7
+ export { decideFormProposal };
8
+ export { commitFormProposals };
9
+ export { settleFormProposalValidation };
10
+ export { formProposalProtocol };
11
+ export { validateFormProposalPolicy };
12
+ export { validateFormProposalDescriptor };
13
+ export { formProposalDescriptor };
14
+ export { clearFormMisses };
15
+ export { missFormInput };
16
+ export { formInputPrompt };
17
+ export { consumeFormFeedback };
18
+ export { formConversationOutcome };
2
19
  export { readMoneyAmount };
3
20
  export { isMoneyAmount };
4
21
  export { moneyIntentAllows };
@@ -88,6 +105,23 @@ export namespace STANDARD_FUNCTIONS {
88
105
  export { urlToSpeech };
89
106
  export { textWithUrlToSpeech };
90
107
  }
108
+ import { formProposalPolicy } from './form-proposals.js';
109
+ import { stageFormProposal } from './form-proposals.js';
110
+ import { nextFormProposal } from './form-proposals.js';
111
+ import { formProposalReadings } from './form-proposals.js';
112
+ import { bindFormProposal } from './form-proposals.js';
113
+ import { decideFormProposal } from './form-proposals.js';
114
+ import { commitFormProposals } from './form-proposals.js';
115
+ import { settleFormProposalValidation } from './form-proposals.js';
116
+ import { formProposalProtocol } from './proposal-contract.js';
117
+ import { validateFormProposalPolicy } from './proposal-contract.js';
118
+ import { validateFormProposalDescriptor } from './proposal-contract.js';
119
+ import { formProposalDescriptor } from './proposal-contract.js';
120
+ import { clearFormMisses } from './form-conversation.js';
121
+ import { missFormInput } from './form-conversation.js';
122
+ import { formInputPrompt } from './form-conversation.js';
123
+ import { consumeFormFeedback } from './form-conversation.js';
124
+ import { formConversationOutcome } from './form-conversation.js';
91
125
  import { readMoneyAmount } from './money.js';
92
126
  import { isMoneyAmount } from './money.js';
93
127
  import { moneyIntentAllows } from './money.js';
@@ -181,5 +215,5 @@ import { validatePhone } from './validators.js';
181
215
  import { validateEmail } from './validators.js';
182
216
  import { urlToSpeech } from './url-speech.js';
183
217
  import { textWithUrlToSpeech } from './url-speech.js';
184
- export { readMoneyAmount, isMoneyAmount, moneyIntentAllows, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech };
218
+ export { formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech };
185
219
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAkBmF,YAAY;8BAAZ,YAAY;kCAAZ,YAAY;qCAAZ,YAAY;gCAlB/D,mBAAmB;8BAiBrB,qBAAqB;6BAEkG,aAAa;8BAAb,aAAa;yBAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;kCAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;2BAAb,aAAa;0BACT,cAAc;0BAAd,cAAc;uCAAd,cAAc;+BAAd,cAAc;sBAAd,cAAc;0BAAd,cAAc;iCAAd,cAAc;gCAAd,cAAc;sCAAd,cAAc;wCAC/H,cAAc;6BACP,cAAc;wBAAd,cAAc;wBAAd,cAAc;4BACjC,iBAAiB;+BACd,uBAAuB;mCACC,kBAAkB;mCAAlB,kBAAkB;qCAxB+I,mBAAmB;yCAAnB,mBAAmB;8BAAnB,mBAAmB;iCAAnB,mBAAmB;oCAAnB,mBAAmB;kCAAnB,mBAAmB;wCAAnB,mBAAmB;gCAAnB,mBAAmB;iCAAnB,mBAAmB;mCAAnB,mBAAmB;gCAe1I,mBAAmB;mCAAnB,mBAAmB;sCAAnB,mBAAmB;qCAAnB,mBAAmB;4BAUtD,mBAAmB;+BAAnB,mBAAmB;iCAAnB,mBAAmB;kCAC6mB,iBAAiB;iCAAjB,iBAAiB;gCAAjB,iBAAiB;8BAAjB,iBAAiB;+BAAjB,iBAAiB;iCAAjB,iBAAiB;8CAAjB,iBAAiB;sCAAjB,iBAAiB;uCAAjB,iBAAiB;iCAAjB,iBAAiB;oCAAjB,iBAAiB;kCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;wCAAjB,iBAAiB;sCAAjB,iBAAiB;kCAAjB,iBAAiB;sCAAjB,iBAAiB;sCAAjB,iBAAiB;sCAAjB,iBAAiB;gCAAjB,iBAAiB;6BAAjB,iBAAiB;yBAAjB,iBAAiB;gCAAjB,iBAAiB;iCAAjB,iBAAiB;oCAAjB,iBAAiB;gCAAjB,iBAAiB;kCAAjB,iBAAiB;oCAAjB,iBAAiB;8BAAjB,iBAAiB;mCAAjB,iBAAiB;qCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;kCAAjB,iBAAiB;yBACzoB,gBAAgB;gCAAhB,gBAAgB;4BAAhB,gBAAgB;8BAAhB,gBAAgB;+BACzB,iBAAiB;8BAAjB,iBAAiB;8BAAjB,iBAAiB;4BAC7B,iBAAiB;oCAAjB,iBAAiB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCAA8K,qBAAqB;kCAArB,qBAAqB;iCAArB,qBAAqB;qCAArB,qBAAqB;iCAArB,qBAAqB;mCAArB,qBAAqB;oCAArB,qBAAqB;6CAArB,qBAAqB;qCAC/E,wBAAwB;2CAAxB,wBAAwB;+CAAxB,wBAAwB;uCAAxB,wBAAwB;gCACpC,wBAAwB;8BAAxB,wBAAwB;gCAAxB,wBAAwB;oCAAxB,wBAAwB;wCAAxB,wBAAwB;gCAmB7C,YAAY;8BAAZ,YAAY;kCAAZ,YAAY;qCAAZ,YAAY;gCAlB/D,mBAAmB;8BAiBrB,qBAAqB;6BAEkG,aAAa;8BAAb,aAAa;yBAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;kCAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;2BAAb,aAAa;0BACT,cAAc;0BAAd,cAAc;uCAAd,cAAc;+BAAd,cAAc;sBAAd,cAAc;0BAAd,cAAc;iCAAd,cAAc;gCAAd,cAAc;sCAAd,cAAc;wCAC/H,cAAc;6BACP,cAAc;wBAAd,cAAc;wBAAd,cAAc;4BACjC,iBAAiB;+BACd,uBAAuB;mCACC,kBAAkB;mCAAlB,kBAAkB;qCAxB+I,mBAAmB;yCAAnB,mBAAmB;8BAAnB,mBAAmB;iCAAnB,mBAAmB;oCAAnB,mBAAmB;kCAAnB,mBAAmB;wCAAnB,mBAAmB;gCAAnB,mBAAmB;iCAAnB,mBAAmB;mCAAnB,mBAAmB;gCAe1I,mBAAmB;mCAAnB,mBAAmB;sCAAnB,mBAAmB;qCAAnB,mBAAmB;4BAUtD,mBAAmB;+BAAnB,mBAAmB;iCAAnB,mBAAmB;kCAC6mB,iBAAiB;iCAAjB,iBAAiB;gCAAjB,iBAAiB;8BAAjB,iBAAiB;+BAAjB,iBAAiB;iCAAjB,iBAAiB;8CAAjB,iBAAiB;sCAAjB,iBAAiB;uCAAjB,iBAAiB;iCAAjB,iBAAiB;oCAAjB,iBAAiB;kCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;wCAAjB,iBAAiB;sCAAjB,iBAAiB;kCAAjB,iBAAiB;sCAAjB,iBAAiB;sCAAjB,iBAAiB;sCAAjB,iBAAiB;gCAAjB,iBAAiB;6BAAjB,iBAAiB;yBAAjB,iBAAiB;gCAAjB,iBAAiB;iCAAjB,iBAAiB;oCAAjB,iBAAiB;gCAAjB,iBAAiB;kCAAjB,iBAAiB;oCAAjB,iBAAiB;8BAAjB,iBAAiB;mCAAjB,iBAAiB;qCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;kCAAjB,iBAAiB;yBACzoB,gBAAgB;gCAAhB,gBAAgB;4BAAhB,gBAAgB;8BAAhB,gBAAgB;+BACzB,iBAAiB;8BAAjB,iBAAiB;8BAAjB,iBAAiB;4BAC7B,iBAAiB;oCAAjB,iBAAiB"}
@@ -1,3 +1,6 @@
1
+ import { formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation } from './form-proposals.js';
2
+ import { formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor } from './proposal-contract.js';
3
+ import { clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome } from './form-conversation.js';
1
4
  import { takeChoiceOffer } from './choice-offer.js';
2
5
  import { consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified } from './form-actions.js';
3
6
  /**
@@ -29,9 +32,11 @@ import { resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, fo
29
32
  import { checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS } from './otp-check.js';
30
33
  import { validateDigits, validatePhone, validateEmail } from './validators.js';
31
34
  import { urlToSpeech, textWithUrlToSpeech } from './url-speech.js';
32
- export { readMoneyAmount, isMoneyAmount, moneyIntentAllows, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech, };
35
+ export { formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech, };
33
36
  /** The functions flows call by name, ready to spread into a host's approved functions. */
34
37
  export const STANDARD_FUNCTIONS = {
38
+ formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor,
39
+ clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome,
35
40
  readMoneyAmount, isMoneyAmount, moneyIntentAllows, validateMoneyOptions, takeChoiceOffer, matchesChoice, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice,
36
41
  readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational,
37
42
  normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, closeFormInstance,
@@ -232,7 +232,7 @@ export function readFormAnswer(answer, candidates, current, choiceKey, questions
232
232
  let consumed = false;
233
233
  const remaining = parts.map(part => {
234
234
  const text = part.trim();
235
- if (!/^[¿\s]*(?:how\b|what\b|when\b|where\b|which\b|why\b|who\b|cu[aá]nt[oa]s?\b|qu[eé]\b|cu[aá]ndo\b|d[oó]nde\b|c[oó]mo\b|(?:can|could) you (?:tell|show)\b|(?:i (?:want|would like) to know|tell me|show me|quiero saber|quisiera saber|me gustar[ií]a saber|d[ií]game|dime|mu[eé]strame)\b)/i.test(text))
235
+ if (!/^[¿\s]*(?:how\b|what\b|when\b|where\b|which\b|why\b|who\b|cu[aá]nt[oa]s?\b|cu[aá]l(?:es)?\b|qu[eé](?=$|[^\p{L}\p{N}_])|cu[aá]ndo\b|d[oó]nde\b|c[oó]mo\b|(?:can|could) you (?:tell|show)\b|(?:i (?:want|would like) to know|tell me|show me|quiero saber|quisiera saber|me gustar[ií]a saber|d[ií]game|dime|mu[eé]strame)\b)/iu.test(text))
236
236
  return part;
237
237
  const key = pickProviderQuestion(text, questions);
238
238
  if (!key || key.startsWith('__'))
@@ -0,0 +1,7 @@
1
+ export function validateProposalData(value: any, seen?: Set<any>): any;
2
+ export function validateFormProposalPolicy(value: any): any;
3
+ export function validateFormProposalDescriptor(value: any): any;
4
+ /** Keep policy selectors out of the runtime decision descriptor; null is an explicit opt-out. */
5
+ export function formProposalDescriptor(policy: any, prepared?: undefined): any;
6
+ export function formProposalProtocol(): number;
7
+ //# sourceMappingURL=proposal-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposal-contract.d.ts","sourceRoot":"","sources":["../../src/helpers/proposal-contract.js"],"names":[],"mappings":"AAMA,uEAcC;AAUD,4DAWC;AACD,gEAIC;AACD,iGAAiG;AACjG,+EASC;AAzDM,+CAAoC"}
@@ -0,0 +1,95 @@
1
+ export const formProposalProtocol = () => 1;
2
+ /** Data-only authoring and runtime contract for input proposal confirmation. */
3
+ const safeName = value => typeof value === 'string' && /^[A-Za-z0-9][\w.:-]*$/.test(value);
4
+ const plain = value => !!value && typeof value === 'object' && !Array.isArray(value) && [Object.prototype, null].includes(Object.getPrototypeOf(value));
5
+ const string = value => typeof value === 'string' && !!value.trim();
6
+ const fail = message => { throw new Error('FORM proposal ' + message); };
7
+ export function validateProposalData(value, seen = new Set()) {
8
+ if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value))
9
+ return value;
10
+ if (!value || typeof value !== 'object' || seen.has(value) || !Array.isArray(value) && !plain(value))
11
+ fail('binding must be finite, acyclic JSON data');
12
+ seen.add(value);
13
+ const array = Array.isArray(value), keys = Reflect.ownKeys(value);
14
+ for (const key of keys) {
15
+ const property = Object.getOwnPropertyDescriptor(value, key);
16
+ if (typeof key !== 'string' || !Object.hasOwn(property, 'value') || !property.enumerable && !(array && key === 'length'))
17
+ fail('must contain only enumerable JSON data properties');
18
+ if (array && key === 'length')
19
+ continue;
20
+ if (['__proto__', 'prototype', 'constructor'].includes(key) || array && !/^(0|[1-9]\d*)$/.test(key))
21
+ fail('binding contains an unsafe key');
22
+ validateProposalData(property.value, seen);
23
+ }
24
+ if (array && keys.length !== value.length + 1)
25
+ fail('binding cannot contain array holes');
26
+ seen.delete(value);
27
+ return value;
28
+ }
29
+ const confirmationKeys = ['question', 'question_es', 'binding', 'words', 'interrupts', 'no_keys', 'clarifications', 'on_declined'];
30
+ function confirmation(value, required) {
31
+ if (required && !string(value.question))
32
+ fail('needs a nonempty question');
33
+ for (const key of ['question', 'question_es'])
34
+ if (value[key] !== undefined && !string(value[key]))
35
+ fail(key + ' must be nonempty text');
36
+ if (value.on_declined !== undefined && !safeName(value.on_declined))
37
+ fail('on_declined needs a static worker id');
38
+ for (const key of ['words', 'no_keys', 'clarifications'])
39
+ if (value[key] !== undefined && (!Array.isArray(value[key]) || !value[key].every(string)))
40
+ fail(key + ' must be an array of nonempty words');
41
+ if (value.interrupts !== undefined) {
42
+ if (!Array.isArray(value.interrupts))
43
+ fail('interrupts must be an array');
44
+ const keys = new Set();
45
+ for (const event of value.interrupts) {
46
+ if (!plain(event) || Object.keys(event).some(k => !['key', 'words', 'flow', 'exact'].includes(k)) || !safeName(event.key) || keys.has(event.key) || !safeName(event.flow) || !Array.isArray(event.words) || !event.words.length || !event.words.every(string) || event.exact !== undefined && typeof event.exact !== 'boolean')
47
+ fail('invalid interrupt declaration');
48
+ keys.add(event.key);
49
+ }
50
+ }
51
+ if (value.binding !== undefined)
52
+ validateProposalData(value.binding);
53
+ }
54
+ export function validateFormProposalPolicy(value) {
55
+ validateProposalData(value);
56
+ if (!plain(value) || Object.keys(value).some(key => !['prepare', 'origins', 'when', 'unless', ...confirmationKeys].includes(key)))
57
+ fail('invalid input policy');
58
+ if (value.prepare !== undefined && !safeName(value.prepare))
59
+ fail('prepare needs a static worker id');
60
+ if (!value.prepare && !string(value.question))
61
+ fail('policy needs a prepare worker or question');
62
+ if (value.origins !== undefined && (!Array.isArray(value.origins) || !value.origins.length || new Set(value.origins).size !== value.origins.length || !value.origins.every(origin => ['activation', 'answer'].includes(origin))))
63
+ fail('invalid origins');
64
+ const path = key => typeof key === 'string' && key.split('.').every(part => /^[A-Za-z_$][\w$-]*$/.test(part) && !['__proto__', 'prototype', 'constructor'].includes(part));
65
+ const scalar = item => item === null || typeof item === 'string' || typeof item === 'boolean' || typeof item === 'number' && Number.isFinite(item);
66
+ if (value.when !== undefined && (!plain(value.when) || Object.entries(value.when).some(([key, wanted]) => !path(key) || !(Array.isArray(wanted) ? wanted.length && wanted.every(scalar) : scalar(wanted)))))
67
+ fail('when must map safe paths to scalar conditions or nonempty scalar arrays');
68
+ if (value.unless !== undefined && (!Array.isArray(value.unless) || !value.unless.every(path)))
69
+ fail('unless must be an array of safe paths');
70
+ confirmation(value, false);
71
+ return structuredClone(value);
72
+ }
73
+ export function validateFormProposalDescriptor(value) {
74
+ validateProposalData(value);
75
+ if (!plain(value) || Object.keys(value).some(key => !confirmationKeys.includes(key)))
76
+ fail('invalid decision descriptor');
77
+ confirmation(value, true);
78
+ return structuredClone(value);
79
+ }
80
+ /** Keep policy selectors out of the runtime decision descriptor; null is an explicit opt-out. */
81
+ export function formProposalDescriptor(policy, prepared = undefined) {
82
+ if (!policy)
83
+ return null;
84
+ validateFormProposalPolicy(policy);
85
+ if (policy.prepare && prepared === undefined)
86
+ fail('prepare must explicitly return a descriptor or null');
87
+ if (prepared === null)
88
+ return null;
89
+ if (prepared !== undefined)
90
+ validateProposalData(prepared);
91
+ if (prepared !== undefined && (!prepared || typeof prepared !== 'object' || Array.isArray(prepared)))
92
+ fail('prepare must return a descriptor or null');
93
+ const base = Object.fromEntries(confirmationKeys.filter(key => policy[key] !== undefined).map(key => [key, policy[key]]));
94
+ return validateFormProposalDescriptor({ ...base, ...prepared });
95
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/host/capabilities.js"],"names":[],"mappings":"AAWA,8FAA8F;AAC9F,0FASC;AAED,2FAA2F;AAC3F,iFAKC;AAED,gHAAgH;AAChH,mFA0BC;AA1DD,kGAAkG;AAClG;IACG,uDAMC;IAHE,aAA2C;IAC3C,YAA0B;IAC1B,gBAAkC;CAEvC"}
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/host/capabilities.js"],"names":[],"mappings":"AAYA,8FAA8F;AAC9F,0FASC;AAED,2FAA2F;AAC3F,iFAKC;AAED,gHAAgH;AAChH,mFA6BC;AA7DD,kGAAkG;AAClG;IACG,uDAMC;IAHE,aAA2C;IAC3C,YAA0B;IAC1B,gBAAkC;CAEvC"}
@@ -1,3 +1,4 @@
1
+ import { INPUT_PROPOSALS_REQUIREMENT } from '../forms/proposal-runtime.js';
1
2
  /** Declarative flow admission. No engine imports, I/O, or implicit host capability activation. */
2
3
  export class JSFEHostCapabilityError extends Error {
3
4
  constructor(flowId, capability, reason) {
@@ -40,6 +41,11 @@ export function assertFlowHostCapabilities(flows, capabilities = {}) {
40
41
  for (const flow of flows) {
41
42
  const requirements = normalizeHostRequirements(flow?.variables?.contract?.value?.requires, flow?.id);
42
43
  for (const capability of requirements) {
44
+ // Intrinsic runtime support, not a host option or an implicit host permission.
45
+ if (capability === INPUT_PROPOSALS_REQUIREMENT)
46
+ continue;
47
+ if (capability.startsWith('jsfe:'))
48
+ throw new JSFEHostCapabilityError(flow?.id, capability, 'unsupported intrinsic runtime contract');
43
49
  if (!capabilities || typeof capabilities !== 'object' || Array.isArray(capabilities)) {
44
50
  throw new JSFEHostCapabilityError(flow?.id, capability, 'explicit host capabilities must be an object');
45
51
  }
@@ -0,0 +1,19 @@
1
+ /** @returns {FlowRecovery} */
2
+ export function validateFlowRecovery(value: any): FlowRecovery;
3
+ /** Undefined means historical behavior; null explicitly clears a completed task's handoff. */
4
+ export function flowHandoffInfo(result: any): {
5
+ flow: any;
6
+ reason: string;
7
+ kind: string;
8
+ recovery: FlowRecovery;
9
+ } | null | undefined;
10
+ /** Instructions belong to JSFE; the host only composes this explicit, consume-once note. */
11
+ export function renderFlowRecovery(handoff: any): string | undefined;
12
+ export type FlowRecovery = {
13
+ version: 1;
14
+ mode: "resume" | "restart" | "hold" | "none";
15
+ reason: string;
16
+ form?: string;
17
+ flow?: string;
18
+ };
19
+ //# sourceMappingURL=flow-recovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-recovery.d.ts","sourceRoot":"","sources":["../../src/host/flow-recovery.js"],"names":[],"mappings":"AAIA,8BAA8B;AAC9B,kDADc,YAAY,CAMzB;AACD,8FAA8F;AAC9F;;;;;qBAKC;AACD,4FAA4F;AAC5F,qEAWC;2BA7Ba;IAAC,OAAO,EAAC,CAAC,CAAC;IAAA,IAAI,EAAC,QAAQ,GAAC,SAAS,GAAC,MAAM,GAAC,MAAM,CAAC;IAAA,MAAM,EAAC,MAAM,CAAC;IAAA,IAAI,CAAC,EAAC,MAAM,CAAC;IAAA,IAAI,CAAC,EAAC,MAAM,CAAA;CAAC"}