GraphRuns
Starting, resuming and inspecting declared graphs — the one collaborator the `graph:*` operations work through. It exists so that every surface reaches a graph the same way. A run started from the terminal parks in the log; the decision it is waiting for shows up in the same list a Desktop or an MCP client reads; and whoever answers it does so through the same door, with the same refusals. The engine already guaranteed that — this just gives it one entrance instead of four callers assembling the same four collaborators by hand. ── WHO RUNS THE NODES ────────────────────────────────────────────────────────────────────────────────────────── A run is advanced twice or more, by different people: once by whoever starts it, and again by whoever answers each gate. Every node runs AS THE CALLER OF THE INVOCATION THAT REACHED IT — {@see Caller} — and is judged against what that caller holds at that moment. The starter's authority is not kept at the gate for the approver to spend, and the approver's is not lent back to the starter: nothing about authority is ever written to the log or read from it. What the log keeps is who ran each node and who was refused one ({@see self::show()}'s `trail`), so a run that changed hands at a gate says so. A run whose caller could not run the next node parks there, and {@see self::resume()} is one more invocation that reaches that node: it runs as whoever resumes, under the same rule.
GraphRuns::__construct()
public function __construct(Milpa\Orchestrator\Declaration\GraphRegistry $graphs, Milpa\EventStore\EventStoreInterface $store, Milpa\Orchestrator\HumanGate $gate, Milpa\Interfaces\Event\MilpaEventDispatcherInterface $dispatcher):Parameters
| Name | Type | Description |
|---|---|---|
| $graphs | Milpa\Orchestrator\Declaration\GraphRegistry | |
| $store | Milpa\EventStore\EventStoreInterface | |
| $gate | Milpa\Orchestrator\HumanGate | |
| $dispatcher | Milpa\Interfaces\Event\MilpaEventDispatcherInterface |
GraphRuns::catalogue()
public function catalogue(): arrayWhat this app can run, and what each one needs to start.
GraphRuns::start()
public function start(string $name, array $inputs, string $requester, ?Milpa\Orchestrator\Declaration\Caller $caller = null): arrayStarts a run and advances it until it finishes, needs somebody, or reaches a node its caller may not run. A refused node is said in the result, never thrown: `ok: false`, the sentence, what was `refused`, and the run parked at that node.
Parameters
| Name | Type | Description |
|---|---|---|
| $name | string | |
| $inputs | array<string, mixed> | |
| $requester | string | |
| $caller | (Caller | null) | who is starting it, with the authority the surface verified — the nodes up to the first gate run as this caller. Without one, nobody is driving: a node that declares a need does not run |
GraphRuns::pending()
public function pending(?Milpa\Orchestrator\Declaration\Caller $viewer = null): arrayEvery decision waiting for a human, across every run of every declared graph. Each row also says WHICH graph it belongs to and WHO started the run, which the engine's own row does not carry — and without them a surface can list a decision it cannot answer. ── WHERE EACH OPTION LEADS, AND WHO MAY TAKE IT ──────────────────────────────────────────────────────────────── Each row carries `choices`: for every option, the node it leads to (`leads_to`, `operation`) and what that node declares it needs (`needs`). Given a `$viewer`, each choice also says whether THAT caller may take it (`may`) and, when not, why — as one sentence (`why_not`) and as the rule that refused it (`because`: `unverified`, `needs` or `requester`), so a surface can say it in its own words; and the row says who is looking (`viewer`: whether they are the one who started the run, and whether the surface verified them). An option was otherwise a bare name, and whether it could be taken was found out by pressing it (greenhouse decisions/0584). IT IS THE ENGINE THAT SAYS IT, in the order and with the rule {@see self::decide()} refuses an answer by — so a surface paints what it is told and keeps no judgement of its own to drift. What it does NOT say is whether the viewer may reach `graph:decide` at all: that is the door's, judged by the policy of the surface. IT IS INFORMATION. Nothing is written — a refusal is a line in the log only when an ANSWER is refused — and what a viewer holds is never said back. `graph:decide` judges every answer as it always did, whatever a surface showed.
Parameters
| Name | Type | Description |
|---|---|---|
| $viewer | (Caller | null) | who is looking, with the authority the surface verified — null says nothing about anybody |
GraphRuns::decide()
public function decide(string $name, string $instanceId, string $decision, string $principal, ?Milpa\Orchestrator\Declaration\Caller $caller = null): arrayAnswers one waiting decision and lets the run continue — as the caller who answered. The node the answer leads to is judged BEFORE the answer is recorded ({@see self::refusalAhead()}): a caller who may not run it has answered nothing, and the gate keeps waiting for somebody who may.
Parameters
| Name | Type | Description |
|---|---|---|
| $name | string | |
| $instanceId | string | |
| $decision | string | |
| $principal | string | |
| $caller | (Caller | null) | who is answering, with the authority the surface verified — the nodes from here to the next gate run as this caller, never as whoever started the run |
Throws
CallRefused when the run is not a run of this graph, is not waiting for a decision, or the
answer is not one its gate offers
SelfApprovalException when the principal answering is the one that asked
GraphRuns::resume()
public function resume(string $name, string $instanceId, ?Milpa\Orchestrator\Declaration\Caller $caller = null): arrayCarries on a run that parked because its caller could not run the next node — as the caller who resumes it. ONLY A RUN PARKED BY A REFUSAL. The last thing its log says has to be that the node it stands on was refused. A run waiting for a decision is answered, not resumed — also when what its log says last is that an answer was not recorded: resuming gives nobody's answer. A finished run has nothing left. And a run that stopped because a node BROKE was never refused: that node was reached and may have done half its work, so running it again is not this operation's to decide. THE NODE RUNS AS WHOEVER RESUMES, judged against what this call holds — never against what whoever started the run or answered its gate held, none of which was kept. A caller who may not run it either has resumed nothing: the log gains that refusal and the run stays where it was. A RESUME IS SPENT ONCE IT IS TAKEN. The log says {@see self::RUN_RESUMED} before the node runs, so the run is no longer parked by a refusal; if the node then breaks, this is not the way to run it a second time. WHO ASKED FOR THE RUN DOES NOT CHANGE. Every gate the run opens from here still records whoever started it, so that one still cannot approve it — whoever resumes. Only a run whose log names nobody takes the caller who resumes it as the one who asked.
Parameters
| Name | Type | Description |
|---|---|---|
| $name | string | |
| $instanceId | string | |
| $caller | (Caller | null) | who is resuming it, with the authority the surface verified — the nodes from here to the next gate run as this caller |
Throws
CallRefused when the run is not a run of this graph, or is not parked by a refused node
GraphRuns::show()
public function show(string $name, string $instanceId): arrayWhere a run stands, everything it has accumulated, and who ran each of its nodes. The `trail` is read off the log, in order: one row per node that ran, was refused, or was resumed at, with `outcome` (`ran`, `refused` or `resumed`), the `node` and its `operation`, and `by` — who was driving the run at that moment ({@see Caller::record()}). A refusal also carries what the node `needs`, and the gate `answer` that was not taken when that is what was refused.
Parameters
| Name | Type | Description |
|---|---|---|
| $name | string | |
| $instanceId | string |
Throws
CallRefused when the run is not a run of this graph, or does not exist