Compare commits
1 Commits
main
...
feat/S1-sp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66afc2f7d2 |
@@ -128,6 +128,12 @@ def tick() -> dict:
|
||||
extra_fields["branch"] = verdict_feedback["branch"]
|
||||
if "commit" in verdict_feedback:
|
||||
extra_fields["base_commit"] = verdict_feedback["commit"]
|
||||
# ADR-004: persist spec_path on spec-phase pass so build/review/dashboard
|
||||
# can read it from the row instead of hitting disk every time. Schema
|
||||
# already has the column; _find_spec_file fallback remains defense-in-depth.
|
||||
if result["verdict"] == "pass" and item["phase"] == "spec":
|
||||
if "spec_path" in verdict_feedback:
|
||||
extra_fields["spec_path"] = verdict_feedback["spec_path"]
|
||||
|
||||
# Amendment §4: `spec_ambiguous` does NOT consume the autonomous budget.
|
||||
# The claim already incremented attempts; roll it back so a human-blocked
|
||||
|
||||
86
tests/test_spec_path_persistence.py
Normal file
86
tests/test_spec_path_persistence.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
Unit tests for ADR-004: persist `spec_path` on spec-phase pass.
|
||||
|
||||
Story: S1 — Persist spec_path on spec-phase pass
|
||||
ADR: wiki/decisions/ADR-004-persist-spec-path-on-pass.md
|
||||
|
||||
Contract:
|
||||
- verdict=pass + phase=spec => spec_path from feedback is written to the row.
|
||||
- verdict != pass + phase=spec => spec_path is unchanged.
|
||||
|
||||
These tests drive `cycle.tick()` against the real test Postgres (conftest's
|
||||
default `db-test` service) and stub `phases.refine_spec` so the LLM is
|
||||
never called. The other moveable parts (wiki, relay) are also stubbed so
|
||||
the test is hermetic.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from conftest import get_row, insert_work_item
|
||||
from damascus import cycle, phases, relay, wiki
|
||||
|
||||
|
||||
def _stub_phase_returning(verdict: str, feedback: dict):
|
||||
"""Build a fake phases.refine_spec that returns a fixed verdict/feedback."""
|
||||
def fake_refine_spec(cur, item):
|
||||
print(f"DEBUG stub: item phase={item.get('phase')!r} id={item.get('id')!r}")
|
||||
print(f"DEBUG stub: returning verdict={verdict!r} feedback={feedback!r}")
|
||||
return {"verdict": verdict, "feedback": feedback}
|
||||
return fake_refine_spec
|
||||
|
||||
|
||||
def _run_tick_with_stub(monkeypatch, verdict: str, feedback: dict) -> None:
|
||||
"""Run one orchestrator tick with the spec phase stubbed.
|
||||
|
||||
wiki.init_wiki and relay.post are no-ops so the test does not touch
|
||||
the host filesystem or any external service.
|
||||
"""
|
||||
monkeypatch.setattr(wiki, "init_wiki", lambda: None)
|
||||
monkeypatch.setattr(relay, "post", lambda line: None)
|
||||
monkeypatch.setattr(phases, "refine_spec", _stub_phase_returning(verdict, feedback))
|
||||
|
||||
out = cycle.tick()
|
||||
print(f"DEBUG tick: claimed={out['claimed']!r} transition={out['transition']!r}")
|
||||
assert out["claimed"] is not None, "tick did not claim a row"
|
||||
assert out["transition"]["verdict"] == verdict
|
||||
|
||||
|
||||
def test_pass_verdict_persists_spec_path(monkeypatch):
|
||||
"""AC: On verdict=pass in spec phase, work_items.spec_path equals
|
||||
the absolute path returned in verdict_feedback."""
|
||||
rid = insert_work_item(phase="spec", story_id="S1-pass", title="Persist spec path on pass")
|
||||
expected_path = "/data/specs/wh40k-pc/S1-pass.spec.md"
|
||||
|
||||
_run_tick_with_stub(monkeypatch, "pass", {
|
||||
"spec_path": expected_path,
|
||||
"preview": "# Goal\n...",
|
||||
})
|
||||
|
||||
row = get_row(rid)
|
||||
assert row["spec_path"] == expected_path, (
|
||||
f"spec_path not persisted on pass: row has {row['spec_path']!r}, "
|
||||
f"expected {expected_path!r}"
|
||||
)
|
||||
# The phase should have advanced spec -> build (the contract for pass).
|
||||
assert row["phase"] == "build"
|
||||
|
||||
|
||||
def test_non_pass_verdict_does_not_persist(monkeypatch):
|
||||
"""AC: On a non-pass verdict in spec phase, work_items.spec_path is unchanged.
|
||||
For a freshly-inserted row, spec_path starts NULL and stays NULL."""
|
||||
rid = insert_work_item(phase="spec", story_id="S1-nopass",
|
||||
title="Spec ambiguous case")
|
||||
|
||||
_run_tick_with_stub(monkeypatch, "spec_ambiguous", {
|
||||
"issue_id": "test-issue-id",
|
||||
"preview": "# Goal\n...",
|
||||
})
|
||||
|
||||
row = get_row(rid)
|
||||
assert row["spec_path"] is None, (
|
||||
f"spec_path must be unchanged on non-pass; row has {row['spec_path']!r}"
|
||||
)
|
||||
# spec_ambiguous rolls back the attempts increment AND routes to
|
||||
# awaiting_human (contract per ADR-004 + design doc §5).
|
||||
assert row["phase"] == "awaiting_human"
|
||||
Reference in New Issue
Block a user