Skip to content

A dispatch error names the symbol the author typed - #728

Merged
David-Noble-at-work merged 1 commit into
developfrom
dispatch-error-names
Aug 28, 2026
Merged

David-Noble-at-work merged 1 commit into
developfrom
dispatch-error-names

Conversation

@David-Noble-at-work

Copy link
Copy Markdown
Contributor

Closes #710. Plan: docs/plans/dispatch-error-names.md.

>>> print("a", "b")
ui.print: got 2 arguments, want at most 1

ui.print does not exist. ui is promoted, so its methods are the top-level globals and the provider name
is not a symbol any script can use — an author who takes the error at its word and writes ui.print(...) gets
undefined: ui. The error sent them toward a second, unrelated error, which is worse than a vague message.

Pre-existing, not introduced by #708: flow has done this since promotion existed.

The rule

Where a call site exists, an error names what the author typed; where none exists, it names the action.
The two cannot be one name because the action name is a node's identity field — it is in the serialized
document and the checksum — so tying it to placement would rewrite every saved workflow when a provider's
placement changed.

print("a", "b")          # script evaluation — the call site is right here
→ print: got 2 arguments, want at most 1        (was: ui.print)

plan.failed("db down")   # runs later, possibly from a workflow loaded off disk
→ flow.failed executed: db down                 (unchanged — there is no call site to name)

An execution error genuinely cannot use a call-site name: a loaded workflow has nodes, not call sites. So the
boundary is explainable rather than arbitrary, which is what would otherwise confuse an author.

Three sites, each already knowing its own surface

Site Surface Placement Before After
starlarkbridge/go_receiver.go script either ui.print print promoted, file.copy qualified
plan/provider.go workflow promoted flow.choose plan.choose
plan/adapter.go workflow qualified file.copy plan.file.copy

Only the first consults Placement(). The other two are the promoted and qualified paths, so the site
itself determines the shape.

This is what #715 unblocked. Before it, placement was a bit tangled with dispatch and "which name would the
author have typed" had no clean answer. flags.Placement() == op.PlacementPromoted is now a one-line question.

actionName was a misnomer, and it made this look riskier than it is

Both workflow sites named their variable actionName and passed it to dispatchBuiltinBody — which reads as
though changing it would move the serialized action name. It does not. That function uses the string only
in error text
, and a node's action name comes from unit.Action().Name() at marshal.

A display string was wearing an identity's name. Renamed to callSiteName at both sites, with a comment saying
which it is.

Step 1 classified every fixture before anything changed

Four fixture expectations name a provider-qualified symbol. Exactly one moves.

Fixture Runs a graph Kind Verdict
test_imm_ui_print_replaces_builtin no script evaluation moves → print:
test_compensation yes execution unchanged
test_judgment_1_delete_then_copy yes execution unchanged
test_judgment_resolve_dangling yes execution unchanged
test_flow_fatal yes execution unchanged

"Runs a graph" is the mechanical discriminator. The change then confirmed that classification rather than
merely being consistent with it: all four execution fixtures passed untouched, because they never reach these
sites.

Tests assert both directions

A promoted provider reports the bare name and a qualified one keeps its qualifier — together, because a fix
that made every name bare would satisfy the first alone. file.copy is exactly what an author types.

And flow.choose, flow.gather, and flow.failed must still resolve through BuildAction: a saved workflow
says flow.choose and has to load. That is the boundary made explicit rather than incidental.

test_imm_ui_print_replaces_builtin pinned ui\.print: got 2 arguments deliberately, with a comment saying
the #710 fix had to come back through it. It did — the fixture failed on the first run of the fix.

Verified

make check, make test-race, make test-scenario — all pass.

Closes #710.

  >>> print("a", "b")
  ui.print: got 2 arguments, want at most 1

ui.print does not exist. ui is promoted, so its methods ARE the top-level globals and the
provider name is not a symbol any script can use -- an author who takes the error at its
word and writes ui.print(...) gets `undefined: ui`. The error sent them toward a second,
unrelated error, which is worse than a vague message.

Pre-existing, not introduced by #708: flow has done this since promotion existed.

The rule
  Where a call site exists, an error names what the author typed; where none exists, it
  names the action. The two cannot be one name because the action name is a node's
  identity field -- it is in the serialized document and the checksum -- so tying it to
  placement would rewrite every saved workflow when a provider's placement changed.

    print("a", "b")          script evaluation -- the call site is right here
    -> print: got 2 arguments, want at most 1

    plan.failed("db down")   runs later, possibly from a workflow loaded off disk
    -> flow.failed executed: db down          unchanged; there is no call site to name

Three sites, and each already knew its own surface
    starlarkbridge/go_receiver.go   script     consults Placement(): bare when promoted
    plan/provider.go                workflow   promoted   -> plan.<method>
    plan/adapter.go                 workflow   qualified  -> plan.<provider>.<method>

  Only the first consults placement. The other two ARE the promoted and qualified paths,
  so the site itself determines the shape.

  This is what #715 unblocked. Before it, placement was a bit tangled with dispatch and
  "which name would the author have typed" had no clean answer.
  flags.Placement() == op.PlacementPromoted is now a one-line question.

actionName was a misnomer, and it made the change look risky
  Both workflow sites named the variable actionName and passed it to dispatchBuiltinBody,
  which reads as though changing it would move the serialized action name. It does not:
  that function uses the string ONLY in error text, and a node's action name comes from
  unit.Action().Name() at marshal. A display string was wearing an identity's name.
  Renamed to callSiteName at both sites, with a comment saying which it is.

Step 1 classified every fixture before anything changed
  Four expectations name a provider-qualified symbol; exactly ONE moves.
  test_imm_ui_print_replaces_builtin is script evaluation. The three file.* expectations
  and the flow.* one are execution errors and stay exactly as they are. "Runs a graph" is
  the mechanical discriminator -- every execution fixture calls t.run(graph).

  The change then confirmed the classification rather than merely being consistent with
  it: those four fixtures passed untouched, because they never reach these sites.

Tests assert both directions
  A promoted provider reports the bare name AND a qualified one keeps its qualifier --
  together, because a fix that made every name bare would satisfy the first alone. And
  flow.choose, flow.gather, and flow.failed must still resolve through BuildAction: a
  saved workflow says flow.choose and has to load.

  test_imm_ui_print_replaces_builtin pinned `ui\.print: got 2 arguments` deliberately,
  with a comment saying the fix to #710 had to come back through it. It did.
@David-Noble-at-work David-Noble-at-work self-assigned this Aug 28, 2026
@David-Noble-at-work
David-Noble-at-work merged commit 30900cc into develop Aug 28, 2026
12 checks passed
@David-Noble-at-work
David-Noble-at-work deleted the dispatch-error-names branch August 28, 2026 17:33
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.

Dispatch errors on a root-placed provider name a symbol the script cannot use

1 participant