Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b87a819
Initialize SDLC contract for issue #1557
May 12, 2026
e06160d
refine(#1557): analysis for Jira-epic SDLC pipeline support
May 12, 2026
6dbfb23
refine(#1557): address reviewer_refine non-blocking nudges
May 12, 2026
8a6d96b
Persist agent statefile writes before refine sync
May 12, 2026
d28314c
Persist statefiles after refine phase
May 12, 2026
819be00
Persist HITL resolution after refine phase gate
May 12, 2026
9ed031d
risk-analyst(#1557): risk assessment for Jira-epic SDLC pipeline support
May 12, 2026
6f8520a
plan(architect): #1557 architecture analysis for Slice 1 (A+B+C+D fre…
May 12, 2026
043dcea
plan(#1557): task plan for Jira-epic SDLC pipeline support
May 12, 2026
52b7348
plan(#1557): split TASK-2-7 to honor coder/documenter file boundaries
May 12, 2026
24dfdbd
plan(#1557): address reviewer_plan v1 NACK (3 blocking + non-blocking)
May 12, 2026
d4a7dc9
Persist agent statefile writes before plan sync
May 12, 2026
1f159ba
Persist statefiles after plan phase
May 12, 2026
cd2df23
implement(#1557): documenter prompts for epic-mode + apply-phase
May 12, 2026
edc658a
implement(#1557): documenter v2 — address reviewer_code 3 blocking NACKs
May 12, 2026
4d5e073
recover(#1557-v2): restore plan + analysis drafts to integration branch
jwbron May 12, 2026
5158e33
recover(#1557-v2): restore plan + analysis drafts AND populated contr…
jwbron May 12, 2026
7efbf49
Merge branch 'egg/issue-1557-v2/work' into egg/issue-1557-v2/slice-1
jwbron May 12, 2026
cb5c458
Add SDLC pipeline support for Jira epics (#1557) (#2677)
james-in-a-box[bot] May 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .egg-state/agent-outputs/coder-to-tester-1557-test-followups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Coder → Tester handoff: test follow-ons for issue #1557 (apply-phase scheduler + APPLIER + epic_link_field)

The coder commit `4cf20c886` (`implement(#1557): apply-phase scheduler + wontdo drain + test fixes`) introduces three production changes that have mechanical follow-on test deltas. Per the gateway's file-restriction policy
(`shared/egg_restrictions/patterns.py`), tests under `gateway/tests/`,
`orchestrator/tests/`, and `shared/tests/` are tester scope — the
coder role cannot push them. The patch below captures those deltas
verbatim; please apply them on the slice-2 integration branch and
re-ACK my proposal.

## Files affected

| Path | Why the test needs updating |
|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| `gateway/tests/test_phase_transition.py` | `PHASE_TRANSITIONS[PLAN]` grew from `[IMPLEMENT]` to `[IMPLEMENT, APPLY]`; a new `APPLY → IMPLEMENT` edge needs coverage. |
| `gateway/tests/test_jira_routes.py` | NEW: task-1-6 acceptance — two tests verifying the ticket-create route propagates `JiraPolicy.epic_link_field()` verbatim to `JiraClient.create_issue` for both `parent` (default) and `customfield_10014`. |
| `orchestrator/tests/test_advance_phase_thread.py` | The auto-advance source-inspection block window was 3000 chars; the new applier-handoff + Won't-Do drain hooks push the `_spawn_pipeline_run_thread` call past that window. Widen to 5000. |
| `orchestrator/tests/test_models.py` | `AgentRole` count moved from 19 → 20 (APPLIER added); `PipelinePhase` declaration order now has APPLY between PLAN and IMPLEMENT. |
| `shared/tests/test_egg_restrictions.py` | Same registry-count bump on the `AGENT_PATTERNS` parity assertions. |

## How to apply

```bash
git apply .egg-state/agent-outputs/coder-to-tester-1557-test-followups.patch
git add gateway/tests/test_jira_routes.py \
gateway/tests/test_phase_transition.py \
orchestrator/tests/test_advance_phase_thread.py \
orchestrator/tests/test_models.py \
shared/tests/test_egg_restrictions.py
git commit -m 'test(#1557): follow-on assertions for APPLY phase + APPLIER role + epic_link_field'
```

The patch is mechanical — it only adjusts assertions that have hard-coded counts / ordering / window-sizes the coder's production change shifted. There are no behavioral test rewrites and no new fixture infrastructure.

## Validation that the patch passes locally

Each touched test file was run against the coder's production diff
before the test files were extracted from the commit:

- `gateway/tests/test_jira_routes.py` — 104 tests pass (including the
two new `test_epic_link_dispatches_via_{parent_field,customfield}`).
- `gateway/tests/test_phase_transition.py` — 29 tests pass (including
the new `test_apply_to_implement`).
- `orchestrator/tests/test_advance_phase_thread.py` — 15 tests pass.
- `orchestrator/tests/test_models.py` — 85 tests pass.
- `shared/tests/test_egg_restrictions.py` — 211 tests pass.

## Reviewer pointer

Pair with my proposal's `pre_merge_condition`: the human reviewer
(or you, with `mcp__brc__resolve_obligation`) closes the obligation
once this patch is on the integration branch.
219 changes: 219 additions & 0 deletions .egg-state/agent-outputs/coder-to-tester-1557-test-followups.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
diff --git a/gateway/tests/test_jira_routes.py b/gateway/tests/test_jira_routes.py
index ed7a96492..759a65377 100644
--- a/gateway/tests/test_jira_routes.py
+++ b/gateway/tests/test_jira_routes.py
@@ -979,6 +979,86 @@ class TestTicketCreate:
kwargs = fake_client.create_issue.call_args.kwargs
assert kwargs["description"] == adf

+ # -------------------------------------------------------------------
+ # Issue #1557 task-1-6 — per-project ``epic_link_field`` dispatch.
+ # -------------------------------------------------------------------
+ #
+ # The dispatch from the ``epicLink`` shorthand to either ``parent``
+ # (next-gen / company-managed projects, default) or
+ # ``customfield_10014`` (classic / team-managed projects) is wired
+ # at ``gateway/gateway.py:6097`` — the route reads
+ # ``JiraPolicy.epic_link_field`` and passes it to
+ # ``JiraClient.create_issue``. ``JiraClient.create_issue``'s wire
+ # translation is covered by
+ # ``gateway/tests/test_jira_client.py::TestCreateIssue::test_epic_
+ # link_with_{parent,customfield}_dispatch``. The tests below close
+ # the route-layer half: they assert the gateway route reads the
+ # policy and propagates the resolved field name verbatim to the
+ # JiraClient call. Together the two sides verify the operator-
+ # managed ``epic_link_field`` setting (refine decision-3) is
+ # exercised end-to-end before the epic pipeline relies on it for
+ # child-ticket creation.
+
+ def test_epic_link_dispatches_via_parent_field(
+ self, client, private_headers, allow_eng, captured_audit, monkeypatch
+ ):
+ """Default ``epic_link_field='parent'`` (next-gen / company-managed
+ sites) → the route hands ``epic_link_field='parent'`` to
+ ``JiraClient.create_issue``, which then writes
+ ``fields: {parent: {key: <KEY>}}`` on the Atlassian wire.
+ Verified at the JiraClient layer by
+ ``test_epic_link_with_parent_dispatch`` in test_jira_client.py."""
+ monkeypatch.setattr(gateway, "jira_epic_link_field", lambda: "parent")
+ fake_client = MagicMock()
+ fake_client.create_issue.return_value = (
+ 201,
+ {"id": "1", "key": "ENG-2", "self": "https://e.atlassian.net/rest/api/3/issue/1"},
+ False,
+ )
+ with patch.object(gateway, "get_jira_client", return_value=fake_client):
+ resp = client.post(
+ self.PATH,
+ headers=private_headers,
+ data=json.dumps({**self._valid_body(), "epicLink": "ENG-1"}),
+ content_type="application/json",
+ )
+ assert resp.status_code == 200, resp.data
+ kwargs = fake_client.create_issue.call_args.kwargs
+ # The route must forward both the requested epic link AND the
+ # operator-configured dispatch field — the JiraClient layer
+ # then translates ``epic_link_field='parent'`` into
+ # ``fields.parent: {key: <KEY>}`` (covered in test_jira_client.py).
+ assert kwargs["epic_link"] == "ENG-1"
+ assert kwargs["epic_link_field"] == "parent"
+
+ def test_epic_link_dispatches_via_customfield(
+ self, client, private_headers, allow_eng, captured_audit, monkeypatch
+ ):
+ """``epic_link_field='customfield_10014'`` (classic / team-managed
+ sites) → the route hands the customfield name to
+ ``JiraClient.create_issue``, which writes
+ ``fields: {customfield_10014: <KEY>}`` on the wire. Verified at
+ the JiraClient layer by ``test_epic_link_with_customfield_
+ dispatch`` in test_jira_client.py."""
+ monkeypatch.setattr(gateway, "jira_epic_link_field", lambda: "customfield_10014")
+ fake_client = MagicMock()
+ fake_client.create_issue.return_value = (
+ 201,
+ {"id": "1", "key": "ENG-2", "self": "https://e.atlassian.net/rest/api/3/issue/1"},
+ False,
+ )
+ with patch.object(gateway, "get_jira_client", return_value=fake_client):
+ resp = client.post(
+ self.PATH,
+ headers=private_headers,
+ data=json.dumps({**self._valid_body(), "epicLink": "ENG-1"}),
+ content_type="application/json",
+ )
+ assert resp.status_code == 200, resp.data
+ kwargs = fake_client.create_issue.call_args.kwargs
+ assert kwargs["epic_link"] == "ENG-1"
+ assert kwargs["epic_link_field"] == "customfield_10014"
+
def test_upstream_error_passes_through(
self, client, private_headers, allow_eng, captured_audit
):
diff --git a/gateway/tests/test_phase_transition.py b/gateway/tests/test_phase_transition.py
index f664696d9..a53adda6f 100644
--- a/gateway/tests/test_phase_transition.py
+++ b/gateway/tests/test_phase_transition.py
@@ -96,9 +96,34 @@ class TestValidTransitions:
assert len(VALID_TRANSITIONS[PipelinePhase.REFINE]) == 1

def test_plan_to_implement(self):
- """Plan can only transition to implement."""
+ """Plan can transition to implement (and to apply for epic pipelines).
+
+ Issue #1557: ``PLAN`` gained ``APPLY`` as a second valid
+ successor so epic-mode pipelines can route Jira mutations
+ through a dedicated APPLY phase between PLAN and IMPLEMENT.
+ Non-epic pipelines continue to use the IMPLEMENT edge —
+ ``IMPLEMENT`` is listed first so ``get_next_phase`` keeps the
+ pre-#1557 default."""
assert PipelinePhase.IMPLEMENT in VALID_TRANSITIONS[PipelinePhase.PLAN]
- assert len(VALID_TRANSITIONS[PipelinePhase.PLAN]) == 1
+ assert PipelinePhase.APPLY in VALID_TRANSITIONS[PipelinePhase.PLAN]
+ assert len(VALID_TRANSITIONS[PipelinePhase.PLAN]) == 2
+ # Default-first ordering invariant: epic-aware schedulers pick
+ # APPLY by name; non-epic flows that take ``next_phases[0]``
+ # must still see IMPLEMENT.
+ assert VALID_TRANSITIONS[PipelinePhase.PLAN][0] == PipelinePhase.IMPLEMENT
+
+ def test_apply_to_implement(self):
+ """Apply (Jira-epic phase) advances only to implement.
+
+ Issue #1557: the new ``APPLY`` phase is the second step in the
+ epic-mode pipeline (PLAN → APPLY → IMPLEMENT). The orchestrator-
+ side scheduler in ``orchestrator.routes.pipelines.
+ _next_phases_for_epic`` picks APPLY only when ``Pipeline.is_epic``
+ is true; this transition is what carries the pipeline back into
+ the standard IMPLEMENT phase once the applier has driven all Jira
+ mutations and BRC consensus has confirmed."""
+ assert PipelinePhase.IMPLEMENT in VALID_TRANSITIONS[PipelinePhase.APPLY]
+ assert len(VALID_TRANSITIONS[PipelinePhase.APPLY]) == 1

def test_implement_to_pr(self):
"""Implement can only transition to PR."""
diff --git a/orchestrator/tests/test_advance_phase_thread.py b/orchestrator/tests/test_advance_phase_thread.py
index 6a3de2df2..ff73bf07b 100644
--- a/orchestrator/tests/test_advance_phase_thread.py
+++ b/orchestrator/tests/test_advance_phase_thread.py
@@ -278,7 +278,10 @@ class TestAutoAdvanceRespawnsThread:
)
idx = source.index(self._BLOCK_MARKER)
# Take a generous window so the block including the return is included.
- return source[idx : idx + 3000]
+ # Widened from 3000 to 5000 in issue #1557 to absorb the epic-mode
+ # applier-handoff write + Won't-Do drain hook the auto-advance
+ # block now performs before respawning the next-phase thread.
+ return source[idx : idx + 5000]

def test_auto_advance_bumps_run_epoch(self):
block = self._auto_advance_block()
diff --git a/orchestrator/tests/test_models.py b/orchestrator/tests/test_models.py
index af463e384..d8bde8aee 100644
--- a/orchestrator/tests/test_models.py
+++ b/orchestrator/tests/test_models.py
@@ -819,6 +819,10 @@ class TestAgentRole:
assert AgentRole.CODER in roles
assert AgentRole.TESTER in roles
assert AgentRole.DOCUMENTER in roles
+ # Issue #1557 — APPLIER joined the registry for Jira-epic
+ # SDLC support (drives gateway Jira mutations after HITL
+ # approval on epic-mode pipelines).
+ assert AgentRole.APPLIER in roles
assert AgentRole.ARCHITECT in roles
assert AgentRole.TASK_PLANNER in roles
assert AgentRole.RISK_ANALYST in roles
@@ -835,7 +839,7 @@ class TestAgentRole:
assert AgentRole.OVERSEER in roles
assert AgentRole.AUTOFIXER in roles
assert AgentRole.CONFLICT_RESOLVER in roles
- assert len(roles) == 19
+ assert len(roles) == 20


class TestBackwardCompatibility:
@@ -898,9 +902,18 @@ class TestPipelinePhase:
"""Tests for PipelinePhase enum."""

def test_phase_order(self):
- """Test phases are defined in SDLC order."""
+ """Test phases are defined in SDLC order.
+
+ Issue #1557 inserted ``APPLY`` between ``PLAN`` and ``IMPLEMENT``
+ — the new phase runs only on epic-mode pipelines (gated by
+ ``Pipeline.is_epic`` in the orchestrator-side scheduler) so the
+ enum declaration order reflects the SDLC reading order for an
+ epic pipeline; non-epic pipelines skip APPLY entirely via
+ ``orchestrator.routes.pipelines._next_phases_for_epic``.
+ """
phases = list(PipelinePhase)
assert phases[0] == PipelinePhase.REFINE
assert phases[1] == PipelinePhase.PLAN
- assert phases[2] == PipelinePhase.IMPLEMENT
- assert phases[3] == PipelinePhase.PR
+ assert phases[2] == PipelinePhase.APPLY
+ assert phases[3] == PipelinePhase.IMPLEMENT
+ assert phases[4] == PipelinePhase.PR
diff --git a/shared/tests/test_egg_restrictions.py b/shared/tests/test_egg_restrictions.py
index 23a7c7765..0097fd278 100644
--- a/shared/tests/test_egg_restrictions.py
+++ b/shared/tests/test_egg_restrictions.py
@@ -77,14 +77,18 @@ class TestAgentRole:


class TestAgentPatterns:
- def test_registry_has_all_19_roles(self):
- assert len(AGENT_PATTERNS) == 19
+ def test_registry_has_all_20_roles(self):
+ # Issue #1557 — APPLIER joined the registry (Jira-epic SDLC
+ # support); the count grew from 19 to 20.
+ assert len(AGENT_PATTERNS) == 20

def test_registry_keys_match_role_constants(self):
expected_roles = {
AgentRole.CODER,
AgentRole.TESTER,
AgentRole.DOCUMENTER,
+ # Issue #1557 — Jira-epic SDLC pipeline support.
+ AgentRole.APPLIER,
AgentRole.ARCHITECT,
AgentRole.TASK_PLANNER,
AgentRole.RISK_ANALYST,
Loading
Loading