High-recall /sh-security-review fan-out + proof-or-kill verifier found two
confirmed HIGH; both now closed (verified empirically against the working tree):
- LOGIC-RACE-01 (HIGH, CWE-835): the build-loop budget was structurally dead
(verifier read a shared wiring-time VerifierConfig.build_loops, always 0, so
the max_build_loops park never fired -> a perpetually-failing task looped
BUILD->DISPATCH->VERIFY forever, force-pushing + firing a CI run each round).
Threaded build_loops through durable PipelineState/TaskRecord; verifier reads
state.get('build_loops',0), writes the incremented count back on each FAIL, and
PARKS at max_build_loops. Parks after exactly N failures, never unbounded.
- SEC-01 (HIGH, CWE-532) + SEC-02 (MED, CWE-214): p3_rollback.sh echoed the live
App JWT to stdout in default dry-run and passed it as a gh argv literal. Added
redact_secrets (Bearer/Authorization/ghX_/PEM masking) through run_or_plan; the
App uninstall now uses curl -H @<0600 tempfile> (JWT never on argv), shredded
after. Empirical: app/incident/all dry-runs leak 0 JWT occurrences.
- SEC-03 (MED, CWE-798): assert_no_write_token now applies the PEM regex + the
configured App-ID to env/config VALUES (not just files) — an App private key
under a benign env name is caught.
- SEC-04 (LOW) + P3-IAC-08 (LOW): tightened the box GITHUB_TOKEN fallback /
value-scan; staged-only WARN on the live workflow revert.
Suite: 1382 passed, ruff clean. Branch only; not merged/deployed.
NOTE: re-verifier flagged SEC-01 as open by grepping COMMITTED blobs (the fix was
uncommitted working-tree state); independently confirmed closed empirically.
133 lines
3.6 KiB
Python
133 lines
3.6 KiB
Python
"""Unit tests for agent_team.task_model (§3.3, §3.3.1)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from agent_team.task_model import (
|
|
Phase,
|
|
PipelineState,
|
|
TaskRecord,
|
|
TaskStatus,
|
|
new_thread_id,
|
|
task_from_dict,
|
|
task_from_json,
|
|
task_to_dict,
|
|
task_to_json,
|
|
)
|
|
|
|
|
|
def test_new_thread_id_unique_hex() -> None:
|
|
a = new_thread_id()
|
|
b = new_thread_id()
|
|
assert a != b
|
|
assert len(a) == 32
|
|
int(a, 16) # must be valid hex
|
|
|
|
|
|
def test_phase_members() -> None:
|
|
assert {p.name for p in Phase} == {
|
|
"INTAKE",
|
|
"CLARIFY",
|
|
"PLAN",
|
|
"REVIEW",
|
|
"BUILD",
|
|
"VERIFY",
|
|
"PARKED",
|
|
"DONE",
|
|
}
|
|
|
|
|
|
def test_task_record_defaults() -> None:
|
|
rec = TaskRecord(
|
|
thread_id="t1",
|
|
status=TaskStatus.ACTIVE,
|
|
current_phase=Phase.INTAKE,
|
|
)
|
|
assert rec.qa_history == []
|
|
assert rec.plan is None
|
|
assert rec.review_verdicts == []
|
|
assert rec.candidate_diff is None
|
|
assert rec.diff_hash is None
|
|
assert rec.ci_results is None
|
|
assert rec.transport == ""
|
|
|
|
|
|
def test_to_dict_serializes_enums_to_values() -> None:
|
|
rec = TaskRecord(
|
|
thread_id="t1",
|
|
status=TaskStatus.WAITING_HUMAN,
|
|
current_phase=Phase.CLARIFY,
|
|
)
|
|
data = task_to_dict(rec)
|
|
assert data["status"] == "waiting_human"
|
|
assert data["current_phase"] == "clarify"
|
|
|
|
|
|
def test_roundtrip_dict() -> None:
|
|
rec = TaskRecord(
|
|
thread_id="t1",
|
|
status=TaskStatus.PARKED,
|
|
current_phase=Phase.PLAN,
|
|
qa_history=[{"q": "x", "a": "y"}],
|
|
plan={"phases": [1, 2]},
|
|
review_verdicts=["REQUEST_CHANGES"],
|
|
candidate_diff="diff --git a b",
|
|
diff_hash="deadbeef",
|
|
ci_results={"conclusion": "success"},
|
|
run_id="27990718108",
|
|
ci_correlation_tag="t1-1a2b3c",
|
|
dispatched_at="2026-06-17T00:30:00Z",
|
|
build_loops=2,
|
|
transport="slack",
|
|
created_at="2026-06-17T00:00:00Z",
|
|
updated_at="2026-06-17T01:00:00Z",
|
|
)
|
|
restored = task_from_dict(task_to_dict(rec))
|
|
assert restored == rec
|
|
# P3 dispatch->verify plumbing fields survive the dict round-trip.
|
|
assert restored.run_id == "27990718108"
|
|
assert restored.ci_correlation_tag == "t1-1a2b3c"
|
|
assert restored.dispatched_at == "2026-06-17T00:30:00Z"
|
|
# The durable build<->verify loop count round-trips (LOGIC-RACE-01).
|
|
assert restored.build_loops == 2
|
|
|
|
|
|
def test_build_loops_defaults_zero_and_roundtrips() -> None:
|
|
rec = TaskRecord(
|
|
thread_id="t1",
|
|
status=TaskStatus.ACTIVE,
|
|
current_phase=Phase.BUILD,
|
|
)
|
|
assert rec.build_loops == 0
|
|
# A dict missing build_loops (older record) defaults to 0, not a crash.
|
|
legacy = task_to_dict(rec)
|
|
del legacy["build_loops"]
|
|
assert task_from_dict(legacy).build_loops == 0
|
|
|
|
|
|
def test_roundtrip_json() -> None:
|
|
rec = TaskRecord(
|
|
thread_id="t2",
|
|
status=TaskStatus.DONE,
|
|
current_phase=Phase.DONE,
|
|
diff_hash="abc",
|
|
)
|
|
restored = task_from_json(task_to_json(rec))
|
|
assert restored == rec
|
|
assert restored.status is TaskStatus.DONE
|
|
assert restored.current_phase is Phase.DONE
|
|
|
|
|
|
def test_pipeline_state_keys_mirror_task_record() -> None:
|
|
# Every PipelineState key should be a TaskRecord field.
|
|
state_keys = set(PipelineState.__annotations__)
|
|
record_fields = set(TaskRecord.__dataclass_fields__)
|
|
assert state_keys == record_fields
|
|
|
|
|
|
def test_pipeline_state_usable_as_dict() -> None:
|
|
state: PipelineState = {
|
|
"thread_id": "t1",
|
|
"status": "active",
|
|
"current_phase": "intake",
|
|
}
|
|
assert state["thread_id"] == "t1"
|