feat(dify-builder): Edit live test reuses prepared input, routes input-failures to testdata gate

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Charles YaoandClaude Opus 4.8 committed 2026-08-30 10:03:47 -07:00
1 parent ef88f6532c
commit db150d1a43
2 files changed
+98 -3

No files matched your search

+50 -3
View File
@@ -37,6 +37,7 @@ from core.dify_builder.handlers_fix import (
build_form_fields,
emit_canvas,
first_failed_node,
is_input_failure,
merge_known_keys,
mint_checkpoint,
perform_revert,
@@ -322,13 +323,13 @@ def handle_test_affected_paths(env: Env, turn: Turn, s: Session, fc: DifyBuilder
edit.review; failure -> real diagnose + propose_repair, staged for the
edit.await_repair approval gate. No auto-apply (human-gated)."""
graph, _hash = env.dify.read_graph(s.app_id, turn.actor)
if fc.test_input_ref == "":
if fc.test_input_ref:
inputs = env.repo.get_test_input(fc.test_input_ref).inputs
else: # defensive: the gate normally prepares inputs first
inputs = env.agent.generate_mock_inputs(start_schema(graph), {})
ti = TestInput(session_id=s.id, source="mock", inputs=inputs)
env.repo.save_test_input(ti)
fc.test_input_ref = ti.id
else:
inputs = env.repo.get_test_input(fc.test_input_ref).inputs
emit = env.emit if env.emit is not None else (lambda _e: None)
try:
@@ -390,6 +391,52 @@ def handle_test_affected_paths(env: Env, turn: Turn, s: Session, fc: DifyBuilder
run.culprit_node_id = first_failed_node(per_node)
fc.verify_run_id = run.id
emit_canvas(env, "mark_test_error")
if is_input_failure(run):
# the run failed on its INPUT, not the config -- route back to the
# testdata gate instead of the config-repair gate; clear the stale
# input ref (and the verify_run_id we just set) so a fresh
# provide_testdata cycle starts clean.
fc.test_input_ref = ""
fc.verify_run_id = ""
test_items = append_card(
fc,
TestResultCard(
title="Affected-path tests",
subtitle="Failed",
tone="error",
stats=[TestStat(value="1", label="runs"), TestStat(value="1", label="errors")],
run_ids=[run.id],
),
)
form_items = append_card(
fc,
FormCard(
variant="testdata",
fields=testdata_form_fields(start_schema(graph)),
values={},
frozen=False,
),
)
turn_items = append_card(
fc,
AssistantTurnItem(
turn_id=str(uuid.uuid4()),
stage_id="edit.await_testdata",
trace=Trace(status="completed", steps=[]),
reply_text="The run failed on its inputs — provide test data and retry.",
cards=["test_result", "form"],
),
)
return StepResult(
next=PcState.EDIT_AWAIT_TESTDATA,
context=fc,
items=[*test_items, *form_items, *turn_items],
run=run,
run_id_sink=[run.id],
)
# config failure: existing diagnose + propose_repair -> EDIT_AWAIT_REPAIR
diagnosis = env.agent.diagnose(run, graph, per_node)
intents, risk = env.agent.propose_repair(diagnosis, graph)
fc.diagnosis = diagnosis
@@ -351,6 +351,54 @@ def test_edit_test_run_draft_raises_routes_to_await_repair_failed():
assert result.run.status == "failed"
def test_edit_test_input_failure_routes_to_testdata_gate():
"""An INPUT-caused run failure (missing/invalid test data, per
is_input_failure's signal match) must clear the stale input ref and route
back to the testdata gate -- not the config-repair gate."""
from core.dify_builder.handlers_edit import handle_test_affected_paths
from core.dify_builder.models import TestInput
env, _ = _new_env()
env.dify.verify_pass = False
env.dify.fail_error = "File variable not found for selector: ['start', 'document']"
s = _session(entry_mode=EntryMode.EDIT, current_state=PcState.EDIT_TEST_AFFECTED_PATHS)
env.repo.save_test_input(TestInput(id="ti-1", session_id=s.id, source="mock", inputs={}))
fc = DifyBuilderContext(edit_target_node_ids=["llm"], test_input_ref="ti-1")
result = handle_test_affected_paths(env, Turn(actor=_actor()), s, fc)
assert result.next == PcState.EDIT_AWAIT_TESTDATA
assert result.context.test_input_ref == "" # stale input cleared
assert result.context.verify_run_id == ""
kinds = [i.kind for i in result.items]
assert "form" in kinds
assert "change_set" not in kinds # gate, not repair
test_result = next(i for i in result.items if i.kind == "test_result")
assert test_result.payload["tone"] == "error"
assistant = next(i for i in result.items if i.kind == "assistant_turn")
assert assistant.payload["stage_id"] == "edit.await_testdata"
def test_edit_test_config_failure_still_routes_to_repair_gate():
"""A config-caused run failure (the fake's default error, which matches no
input-failure signal) must still route to the config-repair gate,
unchanged."""
from core.dify_builder.handlers_edit import handle_test_affected_paths
from core.dify_builder.models import TestInput
env, _ = _new_env(agent=StubAgent())
env.dify.verify_pass = False # default error "boom" -> config, not input
s = _session(entry_mode=EntryMode.EDIT, current_state=PcState.EDIT_TEST_AFFECTED_PATHS)
env.repo.save_test_input(TestInput(id="ti-1", session_id=s.id, source="mock", inputs={}))
fc = DifyBuilderContext(edit_target_node_ids=["llm"], test_input_ref="ti-1")
result = handle_test_affected_paths(env, Turn(actor=_actor()), s, fc)
assert result.next == PcState.EDIT_AWAIT_REPAIR
assert result.context.staged_repair # StubAgent proposes a repair
assert result.context.test_input_ref == "ti-1" # untouched on the config path
def test_review_publish_reaches_terminal_edit_publish_with_publish_card():
from core.dify_builder.handlers_edit import handle_review