Skip to content

Workflow engine step 4: the running layer — a job instantiates its node cards, the queue hands each by role, current inputs open the next #1316

Closed

Description

@Birfy

Written-by: Triage
Part of #1272 — step 4, its own issue. One child issue per step; the PR for this step carries Fixes #<this issue>, never Fixes #1272.

Owner: Dev3(2026-10-27T04:00Z 改派:Dev4 两次卡死/被停、零进展——一直没打 agent-working、没有分支、没有 PR;切片落在 core/ 面,Dev3 接手,分支 dev3/1316-workflow-run)

The gap, in one paragraph

#1272 answers one question: may this node open, now, on these facts? (workflow.js enterNode :461, inputStates :361, renderRefusal :318; facts from workflow-facts.js, step 2). Nothing answers the question after it: a job arrives, and nothing turns it into the node cards, nothing hands node n to the queue by node.role, and nothing opens node n+1 when n's outputs become current. Today that layer is a person: the lead reads the table, writes one card by hand, @-s the Bot, and when the PR is PASSed writes the next card. The definition is data, the guard is code, the runtime is still a habit — which is exactly the thing #1272 exists to remove.

So this is the running layer, and only that:

job (one issue card) → node cards (its subtasks) → queue (by node.role, #1247)
                     → outputs current → next node's card, in the definition's order

It is not a second engine and not a second board. It uses enterNode to answer "may this open", renderRefusal to say why not, the existing task record to hold a card, and the existing pull queue to hand it out.

What already exists (read before designing; do not build a third of any of these)

What this issue pins

  1. One job, one card, node cards as its children. A node is a task with parentId = the job card, not a second GitHub issue and not a new card schema (that would be a second board). The card gains workflow and node (already in The workflow engine: each node names its Bot and its inputs and outputs, the nodes are drawn as a flowchart, and a node missing an input is refused on the card #1272's model); the child carries nodeId and the artifacts it has produced, nothing more.
  2. A node card exists only when the node may open. Creation calls enterNode with live facts; when it returns not-ok, no card is created and the reason is the definition's own sentence via renderRefusal — never a new sentence invented by the runtime.
  3. The hand-off is the queue, not a mention. Enqueue with role: node.role and no assignee; the claimer is the assignee the card shows. No @, no direct wake, no second dispatch path — driveOnce is the only beat.
  4. Idempotent and resumable. (jobId, nodeId) has at most one open card; a restart, a replayed tick or a re-read of the same facts creates no second card. The node history (The workflow engine: each node names its Bot and its inputs and outputs, the nodes are drawn as a flowchart, and a node missing an input is refused on the card #1272 promise 4) is the record, so "did I already open this one" is answered from state, not from a process-local flag.
  5. Opening the next node is a fact, not a follow-up action. n+1 opens when every artifact on its incoming edges is present and current (artifact.js freshness); a stale one refuses in the producer's words (e.g. 评审 的输入 check:tests-ok@abc1234 已过期:tip 现在是 def5678). If two nodes are ready at once the definition said so — open both; WIP=1 per Bot is still the queue's rule (A pull queue: the lead enqueues work, an idle member of the role claims it #1247), not a second limit here.
  6. A job is done when its terminal node's outputs exist — not when someone says so, and never by claiming a merge GitHub did not perform.
  7. Hold refuses every node of every workflow, including one a maintainer Bot asks for; the runtime refuses to create a card for a held job and says who holds it.
  8. No clock, no timer, no scheduler. Evaluation happens on the driveOnce tick (and on the board write that produced a fact). Nothing polls GitHub from the client; reads go through the host, writes through the task record and its authz allow-list (TASK_ENDPOINTS).

Acceptance (each with its own test; the knives are the point)

  1. A job instantiates: one fixture job on repo-dev produces exactly one node card, parentId = the job card, node = triage, queued for Triage, no assignee. (Knife: delete the enterNode gate → a job whose issue.ready is missing must then wrongly produce a card, and test 2 fails.)
  2. A missing or stale input creates nothing, and the refusal text equals renderRefusal's output for that node/fact pair (byte-equal, asserted).
  3. Hand-off by role: the queued item's queueRole is node.role; a Bot of another role gets not-in-role on claim (the existing A pull queue: the lead enqueues work, an idle member of the role claims it #1247 refusal, not a new one).
  4. Idempotency: two instantiate passes on the same facts → one card. Restart the host between the passes; still one. (Knife: remove the (jobId,nodeId) guard → test 4 red.)
  5. Next node waits: with branch/pr not current, node 2 does not open, and the reason names the missing artifact and its revision; when the fact flips current, the next tick opens exactly node 2.
  6. Stale revision refuses: an old verdict@sha against a moved tip opens nothing and says both revisions; the card that is already open is untouched.
  7. Hold: a held job creates no card on any node, in any workflow; a maintainer Bot's attempt refuses too.
  8. Unreadable fact: no card, and the card/job says the fact could not be read (no pass on a guess, Delivery view fails whole when one request fails: a 404 reads as no GitHub connection, with no retry and no per-endpoint diagnosis #1211).
  9. Append-only: re-running the tick appends history entries and rewrites no earlier one; a definition edit does not rewrite a card's past nodes.
  10. No second dispatch: with the runtime enabled, @-free, a queued node card wakes its role exactly once per item (assert the wake list, not the text); no timer exists to assert because none is added.
  11. node --test for the touched suites plus npm run verify green; node scripts/ds-bot-patches.mjs check green with the new files mirrored under patches/ds-bot/.

Sequencing (each slice reviewable alone)

  • (a) Pure planner — core/src/shared/workflow-run.js: given a definition (or preset id), the job card's state and a facts map, return the nodes that may open and the refusal for the ones that may not. No fs, no clock, no network, no host imports. Tests drive it with a fixture facts map. This is the part that can start today.
  • (b) Host writer — core/src/host/workflow-run.js, called from task-drive.js driveOnce and after a task write: creates/queues the card through the existing record, keyed (jobId, nodeId); waits for One board: the delivery board and the task board are one board, and a repository task is its issue #1265 step 3 so the parent is the real issue card.
  • (c) Closure — when a node's card is done and its declared outputs are current, append the node's completion and open the next. No write to GitHub, no label change, no merge.

Out of scope

  • No second board, no second card schema, no new client surface (the card already renders workflow/node; step 3 shipped it).
  • No scheduler, no cron, no "wake a Bot because time passed" — waking stays A pull queue: the lead enqueues work, an idle member of the role claims it #1247's claim on driveOnce.
  • No GitHub writes from the client, no ruleset change, no model in the path.
  • Not parallel execution of one workflow beyond what its edges declare; not retries/compensation beyond the queue's requeue (a failed node card is blocked and says why).
  • Not the runtime's job to judge a PR or merge one: it asks the facts, it does not become a gate.

Activity

  1. added
    enhancementNew feature or request
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    area:automationScheduled tasks and automation
    on Oct 27, 2026
  2. Birfy commented on Oct 27, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev4

    Claimed #1316 — branch dev4/1272-step4, base 694dfe32 (main after #1312).

    First step: the pure planner core/src/shared/workflow-run.js — planJob returns the node cards a job may open (each decision enterNode's, refusal copied byte for byte) and jobDone/completions answer the closure half; no fs, clock, timer, network or host import. Scope is slice (a) + (c)'s pure half only; the host writer waits for #1265 step 3.

  3. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 27, 2026
  4. Birfy commented on Oct 27, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev3

    Claimed — branch dev3/1316-workflow-run, cut from 694dfe32.

    First step: read workflow.js's enterNode / renderRefusal / artifact.js freshness and the repo-dev preset, then write slice (a) — core/src/shared/workflow-run.js, a pure planner (definition + job card + facts map in, the nodes that may open plus the refusal for each one that may not), driven by a fixture facts map with no fs, clock, network or host import. (b) and (c) will be named as not-this-issue in the PR.

  5. self-assigned this
    on Oct 27, 2026
  6. added
    agent-prPull request opened by an agent
    and removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 27, 2026
  7. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    agent-prPull request opened by an agent
    on Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:automationScheduled tasks and automationenhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions