fix(qg): read result: alongside verdict:/status: in tests gate
_parse_tests_verdict now accepts three equal-rank machine-readable frontmatter fields in 13-test-report.md — result: (canonical tester output), verdict: and status: (legacy/enduro-trails). Any one non-empty field suffices; a negative token in any field stays authoritative. Fixes the producer/consumer contract mismatch where the tester emits `result: PASS` (per .openclaw/agents/tester.md) but the gate only read verdict:/status:, causing a testing->development rollback loop until MAX_DEVELOPER_RETRIES (observed on ORCH-17). Token sets frozen and gate signature/QG_CHECKS unchanged for full backward compatibility. Refs: ORCH-047 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -322,6 +322,64 @@ class TestCheckTestsPassed:
|
||||
assert passed is False
|
||||
assert "not found" in reason.lower()
|
||||
|
||||
# --- ORCH-047: `result:` is read as an equal-rank machine field ---
|
||||
|
||||
def test_result_pass_passes(self, setup_work_item_dir):
|
||||
# TC-01 / AC-01: canonical tester field `result: PASS` (no verdict/status).
|
||||
self._write(
|
||||
setup_work_item_dir,
|
||||
"---\ntype: test-report\nresult: PASS\n---\n\n# Test Report\n",
|
||||
)
|
||||
passed, reason = check_tests_passed("enduro-trails", "ET-001")
|
||||
assert passed is True
|
||||
assert "PASS" in reason
|
||||
|
||||
def test_result_fail_fails(self, setup_work_item_dir):
|
||||
# TC-02 / AC-02: `result: FAIL` (no verdict/status) -> rollback, reason has FAIL.
|
||||
self._write(setup_work_item_dir, "---\nresult: FAIL\n---\n\nbody\n")
|
||||
passed, reason = check_tests_passed("enduro-trails", "ET-001")
|
||||
assert passed is False
|
||||
assert "FAIL" in reason
|
||||
|
||||
def test_result_pass_but_verdict_blocked_fails(self, setup_work_item_dir):
|
||||
# TC-03 / AC-03: negative in another field is authoritative over result: PASS.
|
||||
self._write(
|
||||
setup_work_item_dir,
|
||||
"---\nresult: PASS\nverdict: BLOCKED\n---\n\n23 passed\n",
|
||||
)
|
||||
passed, reason = check_tests_passed("enduro-trails", "ET-001")
|
||||
assert passed is False
|
||||
assert "BLOCKED" in reason
|
||||
|
||||
def test_result_pass_but_status_failed_fails(self, setup_work_item_dir):
|
||||
# TC-04 / AC-03: status: failed authoritative over result: PASS.
|
||||
self._write(
|
||||
setup_work_item_dir,
|
||||
"---\nresult: PASS\nstatus: failed\n---\n\nbody\n",
|
||||
)
|
||||
passed, reason = check_tests_passed("enduro-trails", "ET-001")
|
||||
assert passed is False
|
||||
assert "FAILED" in reason
|
||||
|
||||
def test_result_ready_to_deploy_passes(self, setup_work_item_dir):
|
||||
# TC-05 / AC-04: positive token without the word PASS, in result field.
|
||||
self._write(
|
||||
setup_work_item_dir,
|
||||
"---\nresult: ready-to-deploy\n---\n\nbody\n",
|
||||
)
|
||||
passed, reason = check_tests_passed("enduro-trails", "ET-001")
|
||||
assert passed is True
|
||||
|
||||
def test_no_machine_field_reason_mentions_result(self, setup_work_item_dir):
|
||||
# AC-06: none of result/verdict/status -> fail; reason now lists result too.
|
||||
self._write(
|
||||
setup_work_item_dir,
|
||||
"---\ntype: test-report\nversion: 1\n---\n\nResult: PASS\n",
|
||||
)
|
||||
passed, reason = check_tests_passed("enduro-trails", "ET-001")
|
||||
assert passed is False
|
||||
assert "result" in reason.lower()
|
||||
|
||||
|
||||
class TestCheckDeployStatus:
|
||||
"""BUG 8: deploy -> done must be gated on the deployer's machine-readable
|
||||
|
||||
Reference in New Issue
Block a user