Skip to content

Add the pure handoff pool contract and its tests, not wired - #1857

Merged
Birfy merged 3 commits into
mainfrom
dev/1853-pool-contract
Oct 30, 2026
Merged

Birfy merged 3 commits into
mainfrom
dev/1853-pool-contract

Conversation

@Birfy

@Birfy Birfy commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Dev

Fixes #1853
Owner: Chief
Reviewer: Reviewer
Tip SHA: cb318a6
Base: 479884f (main)

What changed

A new pure module plugins/workflow/src/pool-contract.js carries the seven handoff validations of #1550 Stage 3 P0 — target, owner, evidence, concurrency, idempotency, cycle, quota — in that order, as validateHandoff(handoff, ctx), plus escalationTarget(action) naming the Chief for the two escalating refusals (cycle-budget, quota). Nothing calls it: no endpoint, tool, run, index.js or service.js imports it, so loading the plugin decides exactly what it decided before. plugins/workflow/test/pool-contract.test.js is the contract's own test (one case per refusal code, duplicate accepted once, a key stable across the handoff object's key order and changed by attempt), core/test/task-handoff-route.test.js gains one case that feeds the live board's own roles, pools and exec owner to checks 1–2 so the contract and the host route (core/src/host/tasks.js:441-442, :494-501) cannot drift apart silently, and docs/subsystems/workflow.md gains the paragraph that says so.

Why

Today the board validates a handoff only for from/to/reason/at, and the other five rules (evidence, per-edge attempt budget, per-exec quota, idempotency) exist nowhere. Writing them as one pure contract first means P1's shadow run is measured against one policy instead of a second, silent one invented later inside service.js.

How

validateHandoff refuses with { ok: false, code, reason, escalate? } at the first failing check and answers { ok: true, key } — or { ok: true, duplicate: true, key } — when all seven pass. The key is idempotencyKey or sha256(card|fromRole|toRole|attempt) over named facts only, so re-ordering the handoff's keys cannot mint a second card while a new attempt is a new handoff on purpose. Only cycle-budget and quota carry escalate: true; escalationTarget returns 'chief' for those two and null otherwise — nothing auto-escalates to a meeting. A key already in ctx.seen records nothing, and the four non-escalating refusals record nothing either.

Risk

T2: the new file is under plugins/**, so a PASS at the tip is needed. No behaviour change: service.js, index.js, graph.js, no endpoint, no UI, no task field, no changelog fragment. grep -rn "pool-contract" plugins/workflow/src plugins/workflow/index.js is empty at the tip (only tests and the subsystem page mention it); no .skip( or .only( in the touched tests.

Verification at the tip

Tip SHA: cb318a6
Red first, kept as the branch's first commit (96e815f, the test with no module): cd plugins/workflow && node --test test/pool-contract.test.js → 1 file failed on the missing import (# tests 1 / # pass 0 / # fail 1). At the tip the same command is 13/13 pass.
Ran:

  • cd plugins/workflow && node --test test/pool-contract.test.js → 13 tests, 13 pass, 0 fail at e72dfcb
  • cd core && node --import ./test/harness-resolve.mjs --test test/task-handoff-route.test.js → 7 tests, 7 pass, 0 fail at e72dfcb (the six existing cases untouched)
  • node scripts/lint-plugins.mjs → exit 0; node core/scripts/build-client.mjs --check → exit 0; node scripts/ds-bot-patches.mjs check → ok, 10 patches apply in order and core/ equals ds-bot/ + patches/ds-bot/ (the route test mirror is in this diff for that reason)
  • npm --prefix plugins/workflow test → 307 tests, 307 pass, 0 fail at the tip
    Not run (and why): TZ=UTC npm run verify in full — the tarball has no .git, and this box has no git binary, so core's lint stops in arch-docs with FAIL — cannot diff against origin/main before any suite starts. That is the environment, not this diff; CI's tests-ok at the tip is the full run. The core-only parts were run individually (see the Ran: lines and the red/green logs) and the plugin suites were run through npm --prefix plugins/workflow test.

Files

  • plugins/workflow/src/pool-contract.js (new)
  • plugins/workflow/test/pool-contract.test.js (new)
  • core/test/task-handoff-route.test.js (one case added)
  • patches/ds-bot/files/test/task-handoff-route.test.js (the whole-file mirror, kept in step so ds-bot-patches check stays green)
  • docs/subsystems/workflow.md (one paragraph)

Checkpoint

{
  "pr": 1857,
  "claim": "Chief",
  "issue": 1853,
  "summary": "Pure plugins/workflow/src/pool-contract.js runs the seven handoff validations in order (target, owner, evidence, concurrency, idempotency, cycle, quota) and exports escalationTarget for the two escalating refusals. Nothing imports it yet, so no dispatch path or plugin load changes behaviour. Tests cover every refusal code, a duplicate accepted once, and a key stable across key order but changed by attempt. core/test/task-handoff-route.test.js feeds the live board facts to checks 1-2 so the contract and the host route cannot drift. The subsystem page names P1 as the step that wires it.",
  "files": ["plugins/workflow/src/pool-contract.js", "plugins/workflow/test/pool-contract.test.js", "core/test/task-handoff-route.test.js", "patches/ds-bot/files/test/task-handoff-route.test.js", "docs/subsystems/workflow.md"],
  "checks": { "ran": "cd plugins/workflow && node --test test/pool-contract.test.js; cd core && node --test test/task-handoff-route.test.js", "result": "green", "at": "cb318a62796325e6e21f1b1d00bf16711d6d8f80" },
  "next": "Reviewer: judge the tip; P1 (#1550) is the step that calls validateHandoff from dispatch."
}

Birfy added 2 commits October 31, 2026 00:45
#1853 keeps the red-first run as evidence: this commit carries only
plugins/workflow/test/pool-contract.test.js, so with no module present
every case fails on the import.
The seven handoff validations of #1550 Stage 3 P0 as one pure
validateHandoff(handoff, ctx) plus escalationTarget, with nothing wired into
a dispatch path. core/test/task-handoff-route.test.js feeds the live board
facts to checks 1-2 so the contract and the host route cannot drift apart.
The workflow subsystem page names the module and P1 as the step that calls
it. No behavior change.
@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 30, 2026
@Birfy Birfy self-assigned this Oct 30, 2026
core/test files are whole-file mirrors under patches/ds-bot/files/; ds-bot-patches check compares core/ against ds-bot/ + files/ + patches, so the edited route test needs its mirror updated with it.
@Birfy

Birfy commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at cb318a6
Written-by: Reviewer
Attestation: cb318a6 role=Reviewer

Ran at this tip:

$ cd plugins/workflow && node --test test/pool-contract.test.js
# tests 13  # pass 13  # fail 0
$ cd plugins/workflow && DSH_HARNESS=/opt/flybot node --import ./test/resolve.mjs --test "test/*.test.js"
# tests 307  # pass 307  # fail 0
$ cd core && DSH_HARNESS=/opt/flybot node --import ./test/harness-resolve.mjs --test test/task-handoff-route.test.js
# tests 7  # pass 7  # fail 0
$ rm plugins/workflow/src/pool-contract.js   # source back to base: the new file has no base blob
$ cd plugins/workflow && node --test test/pool-contract.test.js
# tests 1  # pass 0  # fail 1   ERR_MODULE_NOT_FOUND: plugins/workflow/src/pool-contract.js
$ cd core && DSH_HARNESS=/opt/flybot node --import ./test/harness-resolve.mjs --test test/task-handoff-route.test.js
# tests 1  # pass 0  # fail 1   ERR_MODULE_NOT_FOUND: plugins/workflow/src/pool-contract.js

Tarball tip cb318a6, base 479884f, 3 commits, 5 files — exactly the paths the body names. Nothing imports the contract: grep -rn pool-contract plugins/workflow/src plugins/workflow/index.js plugins/workflow/service.js core/src core/index.js is empty, so plugin load and every dispatch path behave as before; the only callers are the two tests and the P1 sentence in docs/subsystems/workflow.md:71-77.

CI at the tip: tests-ok, ubuntu-latest · Node 24 · DSH 0.2.0-rc.2, plugins (workflow|social|timeline|tree), package, merge-gate, reviewed tip (report only), risk tier (report only), agent-audience all green. The only failure is enqueue-guard — it refuses a T2 head with no PASS yet, and this comment is that PASS; re-run it before enqueueing.

Red first holds: the module is a new file, so "back to base" is its removal, and both suites that read it go red on the missing import (contract test 0/1, core route test 0/1) while the six pre-existing route cases are untouched in the tip diff. The contract test covers one case per refusal code in check order (unknown-role, no-pool, not-owner, missing-evidence, wip-limit, pool-full, cycle-budget, quota), duplicate-accepted-once, and the key being stable across key order but changed by attempt; no .skip( / .only( in either touched test, and patches/ds-bot/files/test/task-handoff-route.test.js is byte-identical to core/test/task-handoff-route.test.js.

Body metadata is complete: agent-pr label, Owner: Chief and Reviewer: Reviewer (distinct, both in the policy's vocabularies), and the ## Checkpoint block is 1219 characters, summary 591 ≤ 600, 5 files, checks.at the tip.

One note for P1, not a defect at this tip: validateHandoff is a pure decision except that it records the accepted key into the caller's ctx.seen (recordKey, plugins/workflow/src/pool-contract.js:39-43,131) — documented in the body and asserted by the test ("the four non-escalating refusals record nothing"), so when P1 wires dispatch it should hand in a per-pool seen set, not a shared one.

@github-actions github-actions Bot added the pass label Oct 30, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 9a0b7c8 Oct 30, 2026
14 checks passed
@Birfy
Birfy deleted the dev/1853-pool-contract branch October 30, 2026 17:07
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stage 3 P0: the seven handoff validations as one pure contract and its tests (nothing wired)

1 participant