jsfe 1.37.0 → 1.37.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.
package/README.md CHANGED
@@ -387,7 +387,7 @@ means a reply that picks none of them. A picked choice sets `responseVariable` t
387
387
  `answers` sets `{ key: '' }` unless `responseParameters` are declared too. The request carries the options it offers
388
388
  (`request.options`); a host validates a reply against those, never a fixed list. The form readers use it: an unread
389
389
  answer with any words goes to the ranker with the question's choices (`exact` ones too, since `exact` is how words match
390
- a choice; never the YES of a firm confirmation; a NO always) and the escape every question supports, `__agent` (a person);
390
+ a choice; never the YES of a firm confirmation, which a form's confirmation is when an action it authorizes is `confirm.exact`; a NO always) and the escape every question supports, `__agent` (a person);
391
391
  text is never offered. **Arguments in an unread answer** (jsfe 1.36): the asked input's `rank_parameters` (`[{ name, description, type?, reads? }]`) are extracted when the ranker says the reply answers the question, and a choice's own `parameters` when it is picked (`responseParameters` may name the array variable that holds them). Each value is read by the form's reader as if said alone: `reads` names the input it answers, else it answers the asked question, so that question's rules decide a number's field. Two readings of one input that differ take nothing, and nothing read asks again, as before. A form's `confirm.rank_parameters` do the same at its confirmation (no `reads`: read as a correction there); a value read changes the form, which is confirmed again. A charge or a cancellation is guarded by the action's `confirm: {exact: true}`, not by the choice's
392
392
  `exact`. A confirmation's exhausted attempts go to the `give-up` worker.
393
393
 
@@ -1 +1 @@
1
- {"version":3,"file":"flow-docs.d.ts","sourceRoot":"","sources":["../../src/docs/flow-docs.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;EA+RC"}
1
+ {"version":3,"file":"flow-docs.d.ts","sourceRoot":"","sources":["../../src/docs/flow-docs.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;;EA+RC"}
@@ -15,6 +15,7 @@
15
15
  import fs from 'fs';
16
16
  import path from 'path';
17
17
  import { resolveSetDataReferences } from '../forms/declarations.js';
18
+ import { confirmationFirmness } from '../forms/flows.js';
18
19
  export function generateFlowDocs({ sets, root, check = false, hostRun = {}, log = console.log }) {
19
20
  // A catalog or value written as {"$data": path} is documented as compilation resolves it.
20
21
  const SETS = sets.map((s) => ({ ...s, flows: resolveSetDataReferences(s.flows) }));
@@ -354,7 +355,7 @@ export function generateFlowDocs({ sets, root, check = false, hostRun = {}, log
354
355
  else { /* no menu */ }
355
356
  return c.done();
356
357
  }
357
- function formChart(fm) {
358
+ function formChart(fm, performs) {
358
359
  const c = builder();
359
360
  if (fm.before && fm.before.length) {
360
361
  c.steps(fm.before);
@@ -365,7 +366,7 @@ export function generateFlowDocs({ sets, root, check = false, hostRun = {}, log
365
366
  c.then({ label: `${i.name}${i.label ? ' (' + i.label + ')' : ''}${extra ? ': ' + extra : ''}`, kind: 'input' });
366
367
  }
367
368
  if (fm.confirm) {
368
- c.then({ label: 'confirm', kind: 'decision' });
369
+ c.then({ label: `confirm (${confirmationFirmness(performs)})`, kind: 'decision' });
369
370
  }
370
371
  else { /* no confirmation */ }
371
372
  if (fm.after && fm.after.length) {
@@ -414,7 +415,7 @@ export function generateFlowDocs({ sets, root, check = false, hostRun = {}, log
414
415
  }
415
416
  else { /* not a provider */ }
416
417
  if (flow.form) {
417
- out.push('', '### Form', '', formChart(flow.form));
418
+ out.push('', '### Form', '', formChart(flow.form, flow.performs));
418
419
  }
419
420
  else { /* not a form */ }
420
421
  // A form the jsfe SDK compiled (the system set) carries its inputs as a variable; its steps are the engine's run-form loop.
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/forms/contracts.js"],"names":[],"mappings":"AAgBA;;;IAGC;AACD;;;;;EAGC;AACD,qGAAqG;AACrG;;QAwCC;AAqBD;;;QAEC;AAgBD;;;;;;;;;QASC;AACD;;;;QAGC;AACD;;;;;;;;QA+BC;AAED,mHAAmH;AACnH,kDAgBC;AAED,+GAA+G;AAC/G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4EC"}
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/forms/contracts.js"],"names":[],"mappings":"AAgBA;;;IAGC;AACD;;;;;EAGC;AACD,qGAAqG;AACrG;;QAwCC;AAqBD;;;QAEC;AAgBD;;;;;;;;;QASC;AACD;;;;QAGC;AACD;;;;;;;;QA8BC;AAED,mHAAmH;AACnH,kDAgBC;AAED,+GAA+G;AAC/G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4EC"}
@@ -5,7 +5,7 @@ import { INPUT_PROPOSALS_REQUIREMENT, hasInputProposals, proposalRuntimeSteps }
5
5
  import { ACTION_TOOLS } from './action-tools.js';
6
6
  import { normalizeHostRequirements } from '../host/capabilities.js';
7
7
  import { validateActionProjection, actionProjectionExpression } from './action-projection.js';
8
- import { formFlow, FIELD_PRESETS } from './flows.js';
8
+ import { formFlow, FIELD_PRESETS, confirmationFirmness } from './flows.js';
9
9
  import { resolveFormInputs } from '../helpers/form-state.js';
10
10
  const ROLES = new Set(['provider', 'performer', 'collector', 'gate', 'router', 'worker']);
11
11
  const EFFECTS = new Set(['read', 'send', 'write', 'charge', 'cancel', 'handoff']);
@@ -158,8 +158,7 @@ export function definePerformer({ performs, contract, prepare, present, project
158
158
  when('bad_request', "!(pf_action === null || pf_action && typeof pf_action === 'object' && pf_action.action) || pf_action !== null && (!pf_request || typeof pf_request !== 'object')", { id: `${spec.id}_prepare_failed`, type: 'FLOW', value: 'give-up', callType: 'reboot', recovery: { version: 1, mode: 'none', reason: 'performer_request_invalid' }, parameters: { context: '{{exit_context}}', context_es: '{{exit_context_es}}' } }),
159
159
  set('proposal', 'pf_record', "pf_action ? prepareFormAction(cargo, form_instance, pf_action, pf_request) : null"),
160
160
  ];
161
- // its confirmation is as firm as what it authorizes: firm when an action is exact (charge, cancel, irreversible), else soft
162
- const flow = formFlow({ ...spec, confirm, confirm_soft: !performs.some(action => action.confirm?.exact), confirmation_condition: "pf_action !== null && pf_action && typeof pf_action === 'object'",
161
+ const flow = formFlow({ ...spec, confirm, confirm_soft: confirmationFirmness(performs) === 'soft', confirmation_condition: "pf_action !== null && pf_action && typeof pf_action === 'object'",
163
162
  extraVars: { ...spec.extraVars, ...(provides ? { provides, questions: providerQuestions(provides) } : {}), performs, projection: project, pf_action: null, pf_request: null, pf_record: null, pf_result: null, pf_receipt: null },
164
163
  before_confirm: confirm ? [...prepareSteps, ...(spec.before_confirm || [])] : spec.before_confirm || [],
165
164
  after: [...(confirm ? [] : prepareSteps),
@@ -13,6 +13,9 @@
13
13
  */
14
14
  /** rank_parameters and a choice's parameters: [{ name, description, type?, reads? }] (reads: the input the value answers). */
15
15
  export function validateRankParameters(id: any, name: any, what: any, list: any): void;
16
+ /** How firm a form's confirmation is: firm when an action it authorizes declares confirm.exact (charge, cancel, irreversible)
17
+ * or it authorizes none (a plain form); soft when it authorizes actions and none is exact (owner 2026-11-08). */
18
+ export function confirmationFirmness(performs: any): "firm" | "soft";
16
19
  export function formFlow({ id, name, version, description, inputs, after, extraVars, parameters, confirm, confirm_soft, before_confirm, declined, stop, close_on_stop, on_no, resume_on_parent_change, before, done, primary, scope, validator, confirmation_condition, prompt, prompt_es, incomplete_outcome, on_exhausted, must_complete, first_task, first_task_es, acknowledge }: {
17
20
  id: any;
18
21
  name: any;
@@ -1 +1 @@
1
- {"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA6GA;;;;;;;GAOG;AACH;;;;GAIG;AACH,8HAA8H;AAC9H,uFAIC;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAolBD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AAtyBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+/B9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
1
+ {"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA6GA;;;;;;;GAOG;AACH;;;;GAIG;AACH,8HAA8H;AAC9H,uFAIC;AAiBD;iHACiH;AACjH,qEAEC;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAolBD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AA3yBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAogC9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
@@ -145,6 +145,11 @@ function validateAcceptanceDeclarations(id, inputs) {
145
145
  else { /* no invites, or a list of names */ }
146
146
  }
147
147
  }
148
+ /** How firm a form's confirmation is: firm when an action it authorizes declares confirm.exact (charge, cancel, irreversible)
149
+ * or it authorizes none (a plain form); soft when it authorizes actions and none is exact (owner 2026-11-08). */
150
+ export function confirmationFirmness(performs) {
151
+ return Array.isArray(performs) && performs.length && !performs.some((action) => action?.confirm?.exact) ? 'soft' : 'firm';
152
+ }
148
153
  export function formFlow({ id, name, version, description, inputs, after = [], extraVars = {}, parameters = [], confirm = null, confirm_soft = false, before_confirm = [], declined = [], stop = '', close_on_stop = false, on_no = '', resume_on_parent_change = false, before = [], done = true, primary = false, scope = 'isolated', validator = '', confirmation_condition = 'true', prompt = undefined, prompt_es = undefined, incomplete_outcome = null, on_exhausted = null, must_complete = false, first_task = '', first_task_es = '', acknowledge = 'all' }) {
149
154
  validateFormExhaustion(on_exhausted);
150
155
  if (acknowledge !== 'all' && acknowledge !== 'data')
@@ -5,7 +5,7 @@ export { explainSession } from "./inspection.js";
5
5
  export { continuationBreaks } from "./continuation-compat.js";
6
6
  export { inheritFlowDeclaration } from "./declaration-capture.js";
7
7
  export { withSafetyRevision, CONTINUATION_SAFETY_REQUIREMENT } from "./continuation-safety.js";
8
- export { FIELD_PRESETS, formFlow, declinedFlow, textValue } from "./flows.js";
8
+ export { FIELD_PRESETS, formFlow, declinedFlow, textValue, confirmationFirmness } from "./flows.js";
9
9
  export { linkFormSchemas, defineWorker, defineProvider, defineForm, definePerformer, validateFlowContracts, operationWorkers, providerQuestions, providerParameter } from "./contracts.js";
10
10
  export { addressDraft, zipLocation, streetLocation, resolvedAddress } from "../helpers/address.js";
11
11
  export { Form, Provider, Performer, Worker, compileExpression } from "./authoring.js";
@@ -5,7 +5,7 @@ import { FLOWS as FORM_FLOWS } from './flows.js';
5
5
  import { FLOWS as CHOICE_FLOWS } from './choice-flow.js';
6
6
  import { FLOWS as IDENTITY } from './identity-flows.js';
7
7
  export const FLOWS = [...FORM_FLOWS, ...ADDRESS_FLOWS, ...CHOICE_FLOWS, ...IDENTITY].map(flow => withSafetyRevision(flow, 'dispatch-intake-2026-10-22'));
8
- export { FIELD_PRESETS, formFlow, declinedFlow, textValue } from './flows.js';
8
+ export { FIELD_PRESETS, formFlow, declinedFlow, textValue, confirmationFirmness } from './flows.js';
9
9
  export { FLOWS as IDENTITY_FLOWS } from './identity-flows.js';
10
10
  export { linkFormSchemas, defineWorker, defineProvider, defineForm, definePerformer, validateFlowContracts, operationWorkers, providerQuestions, providerParameter } from './contracts.js';
11
11
  export { compileFormPrompts } from './prompt-compiler.js';
@@ -1 +1 @@
1
- export const JSFE_RUNTIME_FINGERPRINT = "sha256:f97519aef9f9c5c878c5ef549068d5aa78e092c5ba2a85b99e864841aebe31e8";
1
+ export const JSFE_RUNTIME_FINGERPRINT = "sha256:452fe6731c7d9d4b43d0961d3eb33a429f0d3c2d7392acb968f4cbda7f514c95";
@@ -347,7 +347,7 @@ A Performer requires `form`, nonempty `performs`, `prepare` worker and `present`
347
347
  | `subject` | Ownership path such as `form.data.account` or `form.data.subject`; bind account/service identity, not an unrelated session flag. |
348
348
  | `gate:{flow?,verified:[paths]}` | Execution-time verification; rerun the declared flow before each fresh effect. |
349
349
  | `flow`, `tool` | Installed executor worker and/or registered tool identity. |
350
- | `confirm:{exact:true}` | Required for charge, cancel or `reversible:false`. This requires a real form confirmation question. |
350
+ | `confirm:{exact:true}` | Required for charge, cancel or `reversible:false`. This requires a real form confirmation question, and makes it firm (YES only by `CONFIRM_WORDS` or its own exact phrases, never offered to the reply ranker). A performer none of whose actions is exact (filing a request) confirms softly: the wider YES vocabulary, and YES is offered to the ranker. There is no separate attribute for this. |
351
351
  | `outcomes` | Map declared executor statuses to business outcomes, including unknown results. |
352
352
  | `retry` | Explicit policies: `after:[knownStatuses]`; unknown retry only for SEND with `unknown:"explicit"` and a new accepted recovery choice. |
353
353
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsfe",
3
- "version": "1.37.0",
3
+ "version": "1.37.1",
4
4
  "description": "TypeScript workflow engine for conversational/automation apps: multi-flow orchestration, interrupt/resume, stack-based control, robust errors, and flexible tool/REST integration.",
5
5
  "license": "MIT",
6
6
  "author": "Ron Pinkas",