Problem/Motivation

#3623406 gave a workflow a step that runs its child on another Orchestra. What it left out is everything that makes the pair of sites tell each other things. A caller learned nothing until it asked: the sweep was the only channel, so a child that finished a second after the last sweep waited for the next one. That is not wrong, it is the whole of what polling can do.

The run's events come back as signed notices

The site running the child queues word of it on the engine's own lifecycle transitions and delivers it separately. Queued rather than sent, because those transitions are dispatched inside the advancing worker's transaction: an outbound call there would announce runs that then roll back, and a caller whose host hangs would hold the worker advancing everybody else's runs. Delivery has its own queue for the same reason. It retries on the engine's attempt budget with a doubling wait, and when the budget is spent the caller, the event and the last error are recorded against the run as well as logged, because a notice dropped in silence is indistinguishable from a run that never moved.

The address is open, because the far end is another site posting hours after anybody was at a keyboard. What authenticates it is the signature: the body signed with a secret the two sides agreed, over the bytes that arrived rather than a re-encoding of them, the timestamp inside the digest with a symmetric tolerance, and no agreed secret meaning refused rather than trusted. Each caller listens on its own address, so an arriving notice names its sender by where it arrived rather than by anything in a body its sender chose. Every way of failing to be that sender is refused alike. Delivery is best effort and the sweep stays the backstop.

Saving a remote is a handshake

A remote was saved on trust and the first token to reach a node found out. Saving now calls the far side and settles what an operator has just typed: whether the credentials are accepted, which wire version the two sides share, and which tenant those credentials act in. It is also where the caller announces where its notices go, and where the signing secret is minted per caller and handed over once, on the connection the client is already authenticated on, so an operator types it nowhere. Not fatal to the save: a remote is routinely configured before the site it names exists.

Whose runs are whose

A caller acts in one tenant and cannot reach another, but two callers can share a tenant, a staging caller and a production caller or two peers, and the tenant was the only scope there was, so each of them could read and drive the other's runs. A run now records the calling consumer's uuid, written at creation from the authenticated identity, and every read and write is scoped to it. Another caller's run reads as absent rather than refused, because a refusal confirms it exists. A run this site started for itself carries no caller and stays reachable.

A start that is not answered may still have happened

If a start times out after the far side committed, the child is running and nothing here remembers it. A start may now carry an idempotency key, and asking again is answered with the run the first ask made. What identifies an attempt is the caller, the workflow and the key together.

The reads an author needs

A caller can ask a remote which workflows it will start and what each declares its runs begin with. Only the tenant those credentials act in, and only workflows a start would be accepted for right now, so the listing cannot answer a question the start refusal is careful not to.

The open address is rate limited

Both limits ship at zero and count nothing until request limits are turned on for the site, with a page to set them and a status report line while nothing is capping them. It reuses the guard built for the interaction doorway in #3623602 rather than growing a second mechanism.

Remaining tasks

Carrying the initiator across the hop as a login is #3624146. Offering the remote's workflow list in the node form, rather than typing the machine name, needs a cache, because building that form must not call another site and a snapshot of another site's workflows does not belong in this site's exported configuration, so it is not here.

AI-Generated: Yes (Claude Code was used to help draft this issue summary and to write the code and tests on the merge request. I reviewed and ran the work myself before posting it.)

Issue fork orchestra-3623524

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

mably’s picture

Issue summary: View changes
mably’s picture

Issue summary: View changes
mably’s picture

Issue summary: View changes
mably’s picture

Issue summary: View changes

mably’s picture

Status: Active » Needs review
mably’s picture

Title: Tell the caller what its remote child is doing: signed event notices, a handshake, and the reads the API does not offer yet » Tell the caller what its remote child is doing: signed notices, a handshake, whose runs are whose, and the reads that were missing
Issue summary: View changes

  • mably committed e74733bf on 1.x
    feat: #3623524 Tell the caller what its remote child is doing: signed...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.