Qualify wait states in the coordination graph (CUST-WP-0074-T04)
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run

Task nodes carry wait_kind (external | human | both | unqualified, null
unless wait): the hub field wins, otherwise derived from needs_human and
dependency edges (task-level depends_on, or a workplan dependency row whose
target the task cites). Workplan nodes carry blocked_kind (human | external
| none, null unless blocked). Task-level depends_on becomes a task ->
workplan/task depends_on edge; depends_on edges carry edge_kind commitment
(cyan #0891b2), human gates are marked on the node (humanGate, rose
#be123c). A needs_human=true query / "Needs human" toggle / --needs-human
flag keeps only blocked_kind=human workplans and their human-wait tasks;
the parameter survives in the shareable URL and mode switch like
include_residuals. Fabric stays a read of State Hub.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 237582@bnt-lap001
Assistant-Session: f2b3d9f1-8fb9-4b9c-bc2b-837ec5dfc826
This commit is contained in:
codex 2026-09-28 22:26:44 +02:00
parent b47dd43b04
commit 9dbb8f0bcb
7 changed files with 426 additions and 33 deletions

View file

@ -16,7 +16,9 @@ def test_graph_switch_replaces_data_and_retains_previous_graph_on_failure(tmp_pa
)[0]
script = r'''
const assert = require("node:assert/strict");
let graphMode = "full", includeResidualsParam = false, activeMode = "full";
let graphMode = "full", includeResidualsParam = false, needsHumanParam = false, activeMode = "full";
const coordinationUrl = (includeResiduals, needsHuman) =>
`/exports/graph-explorer?mode=coordination${includeResiduals ? "&include_residuals=true" : ""}${needsHuman ? "&needs_human=true" : ""}`;
let selected, selectedAnchor, selectedZoneId, focusSet, manualOverrides, filterRules;
let editingRuleId, currentProfileId, allEdgeTypes;
const layerColors = {}, allNodeTypes = [], nodeTypeLabels = {};
@ -43,6 +45,14 @@ let graphRequest = 0;
assert.equal(modeSelect.value, "coordination");
assert.equal(controls["include-residuals-wrap"].style.display, "");
assert.match(urls[0], /include_residuals=true/);
assert.doesNotMatch(urls[0], /needs_human/);
await switchGraph("coordination", true, null, true);
assert.match(urls[1], /include_residuals=true&needs_human=true/);
assert.equal(needsHumanParam, true);
assert.equal(controls["needs-human"].checked, true);
assert.equal(controls["needs-human-wrap"].style.display, "");
await switchGraph("coordination", true, null, false);
assert.equal(needsHumanParam, false);
response = {ok: false, status: 503};
await switchGraph("full");
assert.equal(data[0].data.id, "workplan:a");
@ -54,6 +64,7 @@ let graphRequest = 0;
assert.equal(activeMode, "by-access-zone");
assert.equal(graphMode, "full");
assert.equal(controls["include-residuals-wrap"].style.display, "none");
assert.equal(controls["needs-human-wrap"].style.display, "none");
let resolveOld;
fetch = () => new Promise(resolve => {resolveOld = resolve;});
const oldRequest = switchGraph("coordination");
@ -62,7 +73,7 @@ let graphRequest = 0;
resolveOld({ok: true, json: async () => ({elements: [{data: {id: "stale"}}]})});
await oldRequest;
assert.equal(data[0].data.id, "latest");
assert.equal(updated, 3);
assert.equal(updated, 5);
})().catch(error => {console.error(error); process.exitCode = 1;});
'''
path = tmp_path / "switch.cjs"