Skip to content

SWML: fix shipped doc defects and document messaging transfer/execute dest forms (#510) - #511

Draft
Devon-White wants to merge 3 commits into
mainfrom
Devon/swml-messaging-transfer-execute
Draft

SWML: fix shipped doc defects and document messaging transfer/execute dest forms (#510)#511
Devon-White wants to merge 3 commits into
mainfrom
Devon/swml-messaging-transfer-execute

Conversation

@Devon-White

@Devon-White Devon-White commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Correction to the premise of #510

Issue #510 describes these as "all three forms that Voice SWML already supports." That is
not accurate, and the docs here deliberately avoid repeating it:

  • Voice transfer never accepted inline documents. Verified by enumerating every call
    site of the fetch and parse paths in the C source; none is reachable from the transfer
    handler. Our voice transfer page and TypeSpec had been claiming inline support that
    never existed.
  • Voice execute accepts more than we documented — inline may be YAML as well as JSON,
    and may be a bare instruction array.

So after #9574, messaging transfer is a superset of voice on destination forms, while
being narrower on guardrails (no size cap, no YAML or array inline, no meta). Neither
"parity" nor "matches voice" is stated in either direction.

Shipped-behavior fixes (not gated)

These were found while auditing #510 and are live defects in published docs.

Two broken examples on the voice transfer page. One documented an array-of-objects
form that fails schema validation and rejects the entire document — copying it broke the
reader's script. The other placed a step after a transfer, implying execution returns;
transfer is a tail call and that step never ran.

A variable scope that does not exist in messaging. messaging/overview.mdx and
variables.mdx documented runtime variables under vars.*, including a copy-pasteable
%{vars.request_response.field_name}. Messaging reads those without a prefix, so the
documented form expanded to an empty string — a silent wrong value in the customer's
outgoing message, with no error. vars is real in messaging, but as a field of the
outbound webhook payload rather than an interpolation scope; that distinction is now
explicit so the prefix is not restored later. Voice pages are untouched: voice strips a
vars. prefix on read, so vars.* is correct there.

A parameter renamed on the backend two months ago. transfer_after_bridge became
execute_after_queue on 2026-06-01. connect validates against a closed whitelist, so its
worked example rejected the whole document; on enter_queue the old name was silently
ignored and the post-call SWML never ran. Also dropped the required flag (the same change
made it optional, defaulting to hangup) and removed the parameter from the parallel,
serial, and serial_parallel forms, which have no working placement for it.

Error reference. Two wrong code strings corrected, missing codes added, message drift
fixed, and entries grouped by document, method, and runtime level.

Also: an inverted rule on voice execute (an array result block runs as cond, an
object as switch — the page said the reverse), the previously undocumented bare-string
shorthand, the shared jump budget on goto, and several broken anchors.

Verification

  • yarn build:schema — both SWML projects compile clean
  • yarn fern-md-check — all 2810 MDX files valid

… dest forms

Contains two groups of changes. One is correct against shipped behavior today;
the other describes behavior that has not shipped yet. See the gating note below.

## Shipped-behavior fixes (correct today)

Voice reference:
- transfer.mdx: delete the array-of-objects example. It fails schema validation
  and rejects the entire document, so copying it broke the customer's script.
- transfer.mdx: remove the step placed after a transfer. transfer is a tail call;
  that step never ran. Tail-call behavior is now stated outright.
- transfer.mdx + TypeSpec: drop the inline-SWML-document claim. Voice transfer
  accepts a URL or section name only and never accepted inline documents.
- execute.mdx + TypeSpec: correct an inverted rule. An array result block runs as
  cond and an object runs as switch, not the reverse. Document the previously
  omitted inline YAML and bare-instruction-array forms.
- goto.mdx + TypeSpec: document the shared jump budget and per-label max counting.
- Document the bare-string shorthand for transfer, execute, and goto.

Messaging reference:
- overview.mdx and variables.mdx: remove the vars.* variable scope, which does not
  exist in messaging. Runtime variables are read without a prefix; a vars.-prefixed
  path silently expanded to an empty string in the customer's outgoing message.
  vars remains documented as what it actually is: a field of the outbound webhook
  payload, not an interpolation scope.
- overview.mdx: runtime variables are delivered in the webhook payload but the
  transferred document's context is rebuilt, so drop the propagation claim.
- overview.mdx and request.mdx: request_response_code is a string, not an integer.
- overview.mdx: replace the unenforced content-type requirement with the actual
  parse behavior.
- errors.mdx: correct two wrong error-code strings, add missing codes, fix message
  drift, and group entries by document, method, and runtime level.

enter_queue and connect: rename transfer_after_bridge to execute_after_queue,
renamed on the backend on 2026-06-01. connect validates against a closed parameter
whitelist, so the worked example rejected the whole document. On enter_queue the
old name was silently ignored and the post-call SWML never ran. Also drop the
required flag, since the same change made it optional and defaults to hangup, and
remove the parameter from the parallel, serial, and serial_parallel forms, which
have no working placement for it.

Regenerates fern/apis/signalwire-rest/openapi.yaml, which is emitted from these
SWML models. Changes are confined to SWML.Calling.* and SWML.Messaging.* schemas.

## docs#510 - BLOCKED, do not merge yet

Documents messaging transfer and execute accepting a section name, a URL, or an
inline SWML document, plus the swml_execute_fetch_failed and
swml_execute_document_invalid error codes.

This behavior exists only in signalwire/prime-rails#9574, which is OPEN and
UNMERGED (upstream signalwire/cloud-product#18950). Hold this PR as a draft until
that merges and deploys, then re-verify against the merge commit, since the PR can
still change in review.

Note that issue #510's premise that these are "all three forms that Voice SWML
already supports" is not accurate. Voice transfer never accepted inline documents,
so after #9574 messaging transfer is a superset of voice on destination forms while
being narrower on guardrails. The docs avoid claiming parity in either direction.

Verified: yarn build:schema compiles both SWML projects; yarn fern-md-check reports
all 2810 MDX files valid.
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Four independent reviewers checked the previous commit against source. The
riskiest edits held up: removing inline support from voice transfer, and
removing execute_after_queue from three connect forms, were both confirmed.
These are the claims that did not.

Messaging destination context (the largest error, in five places). Both method
pages, their TypeSpec, and the messaging overview claimed a URL or inline
destination starts from a fresh context of message plus params, and that it
does not inherit the caller's runtime variables. That asymmetry does not exist:
in the ordinary case all three destination forms read the same variables. Where
a real difference occurs it runs the other way, with a URL or inline
destination seeing a subset. The per-form table is replaced with prose stating
what a destination does read, keeping the one genuine exception: a step nested
inside a destination called by execute or transfer does not see variables set
inside that call.

Voice execute inline forms. "A JSON or YAML string ... or just an array of
instructions" implied four combinations, one of which does not exist. A bare
instruction array is JSON only; a YAML string must be a full document.

Error descriptions. swml_invalid_message_handler cited a LaML bin, which is the
one case that provably cannot produce it; the real trigger is a Call Flow or AI
Agent handler. url_failed_to_parse also fires on any non-2xx response, which is
its most common cause. http_retrieval_error covers connection and timeout
failures only. message_filtered does not mark the message failed and is not
carrier filtering, and the section intro no longer over-claims for it.
relay_script_element_invalid_value and relay_script_section_code_missing had
stated triggers that did not match their only raise sites.

Content types are not enforced on either flavor. Both determine the format from
the response body, so the three types are now good practice rather than a
requirement. The calling overview also listed join_conference.wait_url and
enter_queue.wait_url as SWML document fetches; the first is a LaML playlist and
the second an audio file, so both are removed from that list.

Also: the vars webhook field carries return_value once an earlier execute sets
one, and variables.mdx now states the scopes per flavor instead of merging two
different sets into one list.

Verified: yarn build:schema compiles both SWML projects; yarn fern-md-check
reports all 2810 MDX files valid.
Re-verification of the previous commit found the corrected sentence was itself
an approximation. It said a nested step's URL or inline destination sees the
variables accumulated before the enclosing call. That is right only one level
deep. A URL or inline destination reads the runtime variables of the top-level
flow at any depth, because variables set inside a destination that execute or
transfer entered are not visible until control returns to the top level. Nesting
two levels deep is reachable in ordinary documents, so the sentence over-credited
the destination with variables it cannot read.

The wording now separates the two cases rather than stating one rule with an
exception: a section destination reads everything the caller can, while a URL or
inline destination reads the top-level flow's variables, which are the same thing
whenever the step sits in main. Applied to both method pages, the messaging
overview, and both TypeSpec doc strings, with the emitted schema regenerated.

Also on the messaging overview: add return_value to the runtime variables, since
it is readable as %{return_value} and is named in the transfer webhook payload
list, and drop the note saying methods may write additional keys. The variable
set is closed at seven names, verified across every write site, so the note
contradicted the enumeration.

The error-reference corrections from the previous commit were re-verified
independently against their raise sites and need no further change.

Verified: yarn build:schema compiles both SWML projects; yarn fern-md-check
reports all 2810 MDX files valid.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant