Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
64 changes: 63 additions & 1 deletion gateway/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,17 @@ def config_reload() -> Response:
return jsonify({"status": "ok", "message": "Configuration reloaded"})


# Slice integration-branch shape for the synthetic-session exemption (#2368).
# Matches ``egg/<base>/(slice|phase)-<digits>`` where ``<base>`` is a single
# segment naming the parent pipeline branch (issue-driven, JIRA-driven, or
# qualifier-suffixed) — multi-segment bases are never produced by the
# orchestrator, so the second character class excludes ``/``. Only
# orchestrator-issued sessions can ever set ``synthetic=True`` (the launcher
# secret gates ``/api/v1/sessions/create``), so this exemption is not reachable
# from a sandboxed agent's session token.
_SLICE_INTEGRATION_BRANCH_RE = re.compile(r"^egg/[A-Za-z0-9][A-Za-z0-9_-]*/(?:slice|phase)-\d+$")


@app.route("/api/v1/git/push", methods=["POST"])
@require_session_or_launcher_auth
def git_push() -> tuple[Response, int] | Response:
Expand Down Expand Up @@ -1285,13 +1296,64 @@ def git_push() -> tuple[Response, int] | Response:
INFRASTRUCTURE_BRANCHES = {CHECKPOINT_BRANCH, PIPELINE_STATE_BRANCH}
is_infrastructure_push = branch in INFRASTRUCTURE_BRANCHES

# Slice integration-branch creation (#2368): the orchestrator pre-creates
# ``egg/<base>/(slice|phase)-N`` on origin from the parent branch via a
# synthetic, launcher-authenticated session before any agent runs. That
# push is orchestrator infrastructure — not an agent BRC propose — so it
# must bypass the pipeline-session push block introduced in #2028. The
# ``synthetic=True`` flag can only be set by the launcher (the
# ``/api/v1/sessions/create`` endpoint is gated by ``require_launcher_auth``),
# so a sandboxed agent's session token cannot reach this branch.
is_slice_integration_push = False
if not is_infrastructure_push and _SLICE_INTEGRATION_BRANCH_RE.match(branch):
# ``Session.synthetic`` is a ``bool`` (default ``False``); only an
# orchestrator-issued session can carry ``synthetic=True`` because
# ``/api/v1/sessions/create`` is gated on the launcher secret. Use
# an identity check rather than a truthiness test so a future
# surface that ever stores something other than ``True`` (and any
# MagicMock fake whose default attr is truthy) cannot accidentally
# opt into the exemption.
if hasattr(g, "session") and getattr(g.session, "synthetic", False) is True:
is_slice_integration_push = True
is_infrastructure_push = True
audit_log(
"push_slice_integration_exempt",
"git_push",
success=True,
details={
"repo_path": repo_path,
"remote": remote,
"refspec": refspec,
"branch": branch,
"reason": (
"Synthetic-session slice integration branch push — "
"orchestrator infrastructure (#2368)"
),
},
)

repo_info = parse_owner_repo(repo)
if repo_info:
# Infrastructure operations — always accessible regardless of
# session mode. This covers dedicated checkpoint repos and
# infrastructure branch pushes (checkpoints, pipeline state).
is_ckpt_repo = _is_checkpoint_repo_for_request(repo_info.owner, repo_info.repo)
if is_infrastructure_push or is_ckpt_repo:
if is_ckpt_repo:
exempt_type = "checkpoint_repo"
elif is_slice_integration_push:
exempt_type = "slice_integration_branch"
else:
exempt_type = "infrastructure_branch"
# A successful slice-integration push intentionally emits BOTH
# ``push_slice_integration_exempt`` (above, the orchestrator-
# specific event) AND ``push_infrastructure_exempt`` with
# ``exempt_type="slice_integration_branch"`` (here, the generic
# exemption event). Operators grepping ``push_infrastructure_exempt``
# for "infra pushes" should filter out the slice variant via
# ``exempt_type``; the dual emission is intentional so the
# orchestrator-specific path is also visible to operators
# filtering on the slice-integration event name (#2370 review).
audit_log(
"push_infrastructure_exempt",
"git_push",
Expand All @@ -1300,7 +1362,7 @@ def git_push() -> tuple[Response, int] | Response:
"repo": repo,
"branch": branch,
"reason": "Infrastructure operation exempt from private mode policy",
"exempt_type": "checkpoint_repo" if is_ckpt_repo else "infrastructure_branch",
"exempt_type": exempt_type,
},
)
else:
Expand Down
218 changes: 218 additions & 0 deletions gateway/tests/test_pipeline_push_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def _make_session(
role: str = "coder",
pipeline_id: str | None = "issue-1669",
assigned_branch: str | None = "egg/issue-1669",
synthetic: bool = False,
) -> MagicMock:
"""Create a mock session with the given agent role and pipeline context."""
mock_session = MagicMock()
Expand All @@ -59,6 +60,7 @@ def _make_session(
mock_session.phase = "implement"
mock_session.pipeline_id = pipeline_id
mock_session.assigned_branch = assigned_branch
mock_session.synthetic = synthetic
return mock_session


Expand Down Expand Up @@ -689,3 +691,219 @@ def test_launcher_push_missing_mode_uses_session_default(self, client):
assert response.status_code == 200, (
f"Expected 200 with no mode, got {response.status_code}: {response.data!r}"
)


class TestSliceIntegrationBranchExemption:
"""Slice integration-branch creation exemption (#2368).

The orchestrator's ``create_slice_integration_branch`` registers a
synthetic, launcher-authed session and pushes
``parent:refs/heads/egg/<base>/slice-N`` so the slice PR's diff is
non-empty before agents spawn. That push is orchestrator
infrastructure and must bypass the #2028 pipeline-session block.
The exemption is keyed on the session's ``synthetic`` flag — only
the launcher can set it — and the slice integration-branch shape.
"""

def test_synthetic_session_slice_branch_push_allowed(self, client):
"""Synthetic-session push to ``egg/issue-N/slice-M`` is allowed."""
session = _make_session(synthetic=True, assigned_branch="egg/issue-2261/slice-7")
patches = _push_context(session)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
):
response = _do_push(
client,
refspec="egg/issue-2261:refs/heads/egg/issue-2261/slice-7",
)
assert response.status_code == 200, (
f"Expected 200 for synthetic slice integration push, "
f"got {response.status_code}: {response.data!r}"
)

def test_synthetic_session_qualified_slice_branch_push_allowed(self, client):
"""Qualifier-suffixed branches (#2368 bonus) — ``egg/issue-N-v3/slice-M`` — pass."""
session = _make_session(synthetic=True, assigned_branch="egg/issue-2261-v3/slice-7")
patches = _push_context(session)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
):
response = _do_push(
client,
refspec="egg/issue-2261-v3:refs/heads/egg/issue-2261-v3/slice-7",
)
assert response.status_code == 200

def test_synthetic_session_jira_slice_branch_push_allowed(self, client):
"""JIRA-driven branches — ``egg/KORE-1234/slice-M`` — pass."""
session = _make_session(synthetic=True, assigned_branch="egg/KORE-1234/slice-3")
patches = _push_context(session)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
):
response = _do_push(
client,
refspec="egg/KORE-1234:refs/heads/egg/KORE-1234/slice-3",
)
assert response.status_code == 200

def test_synthetic_session_legacy_phase_branch_push_allowed(self, client):
"""Legacy ``phase-N`` slice IDs (pre-#2137) still flow through the loader,
so the integration-branch exemption must accept them too."""
session = _make_session(synthetic=True, assigned_branch="egg/issue-2261/phase-1")
patches = _push_context(session)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
):
response = _do_push(
client,
refspec="egg/issue-2261:refs/heads/egg/issue-2261/phase-1",
)
assert response.status_code == 200

def test_non_synthetic_session_slice_branch_push_blocked(self, client):
"""Agent (non-synthetic) push to a slice integration branch is still blocked.

Agents reach a slice's integration branch via ``mcp__brc__propose``
(``consensus_push=true``); a direct push is the very pattern #2028 is
designed to catch. The exemption MUST gate on ``synthetic=True`` —
if the regex alone is enough, agents can use slice-shaped branch
names to bypass enforcement.
"""
session = _make_session(synthetic=False, assigned_branch="egg/issue-2261/slice-7")
patches = _push_context(session)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
):
response = _do_push(
client,
refspec="egg/issue-2261:refs/heads/egg/issue-2261/slice-7",
)
assert response.status_code == 403, (
"Non-synthetic session push to slice integration branch must still "
"be blocked by pipeline-session enforcement"
)
data = json.loads(response.data)
assert "pipeline sessions" in data["message"].lower()

def test_synthetic_session_non_slice_branch_still_blocked(self, client):
"""Synthetic flag alone is not enough — branch must match the slice shape.

Defends against future code paths that mark a session synthetic for
unrelated reasons but would bypass the agent-push block if the
branch-shape gate were missing.
"""
session = _make_session(synthetic=True, assigned_branch="egg/issue-2261")
patches = _push_context(session)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
):
response = _do_push(client, refspec="egg/issue-2261")
assert response.status_code == 403

def test_synthetic_session_multi_segment_base_blocked(self, client):
"""Multi-segment base shapes (``egg/foo/bar/slice-N``) are not produced
by the orchestrator and the regex MUST reject them.

The documented branch shape is ``egg/<single-segment>/(slice|phase)-N``;
accepting multi-segment bases would widen the exemption surface beyond
what the orchestrator actually emits.
"""
session = _make_session(synthetic=True, assigned_branch="egg/foo/bar/slice-1")
patches = _push_context(session)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
):
response = _do_push(
client,
refspec="egg/foo:refs/heads/egg/foo/bar/slice-1",
)
assert response.status_code == 403

def test_audit_event_records_slice_integration_exempt_type(self, client):
"""Exemption emits a distinct audit event so operators can trace
synthetic-session pushes separately from checkpoint/state writes."""
session = _make_session(synthetic=True, assigned_branch="egg/issue-2261/slice-7")
patches = _push_context(session)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
):
with patch.object(gateway, "audit_log") as mock_audit:
response = _do_push(
client,
refspec="egg/issue-2261:refs/heads/egg/issue-2261/slice-7",
)
assert response.status_code == 200
events = [
(call.args[0] if call.args else None, call.kwargs.get("details") or {})
for call in mock_audit.call_args_list
]
slice_events = [e for e in events if e[0] == "push_slice_integration_exempt"]
assert slice_events, (
f"Expected push_slice_integration_exempt event, got: {[e[0] for e in events]}"
)
infra_events = [
e
for e in events
if e[0] == "push_infrastructure_exempt"
and e[1].get("exempt_type") == "slice_integration_branch"
]
assert infra_events, (
"Expected push_infrastructure_exempt with exempt_type=slice_integration_branch"
)
17 changes: 11 additions & 6 deletions orchestrator/gateway_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1529,12 +1529,17 @@ def create_slice_integration_branch(
"""Create the slice integration branch on origin from ``parent_branch``.

Sends ``git push origin parent_branch:refs/heads/integration_branch``
through the existing per-agent ``/api/v1/git/push`` endpoint so
no privileged orchestrator-role surface is introduced
(decision-15). The branch ownership check uses the
``integration_branch`` name as the target — naming convention
``egg/issue-N/slice-M`` is owned by the orchestrator role's
existing prefix-allowlist.
via a synthetic, launcher-authenticated session through
``/api/v1/git/push``. The gateway treats the push as
orchestrator infrastructure: the synthetic flag (only settable
by ``/api/v1/sessions/create``, which is gated on the launcher
secret) combined with the slice integration-branch name
``egg/<base>/(slice|phase)-N`` short-circuits the
pipeline-session push block from #2028 — see the
``_SLICE_INTEGRATION_BRANCH_RE`` exemption in
``gateway/gateway.py``. The branch itself still passes the
normal ``egg/`` prefix branch-ownership check, so no
orchestrator-role push surface is introduced.

Returns ``True`` on success, ``False`` on any error (the
caller logs and surfaces a clear error to the run loop).
Expand Down
6 changes: 5 additions & 1 deletion orchestrator/routes/pipelines.py
Original file line number Diff line number Diff line change
Expand Up @@ -10982,7 +10982,11 @@ def _run_implement_phase_slices(
else f"egg/{pipeline_id}/work"
)
issue_number = pipeline.issue_number
issue_branch = f"egg/issue-{issue_number}" if issue_number is not None else pipeline_branch
# Slice integration branches stack under ``pipeline_branch`` directly
# so any qualifier suffix (``-v3``, ``-backend``) is preserved — two
# qualified pipelines for the same issue would otherwise collide in
# the ``egg/issue-N/slice-M`` namespace (#2368).
issue_branch = pipeline_branch

# Wrap scheduler construction so the run loop doesn't crash if the
# contract bypassed plan-ingestion validation and reaches the
Expand Down
15 changes: 12 additions & 3 deletions orchestrator/stacked_pr_reconciler.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,18 @@ def find_orphaned_child_prs(
pr_by_head[head] = pr

orphans: list[OrphanedChildPR] = []
issue_number = contract.issue.number if contract.issue is not None else None
pipeline_id = contract.contract_key
issue_branch = f"egg/issue-{issue_number}" if issue_number else f"egg/{pipeline_id}"
# ``contract_key`` returns the canonical pipeline id for all
# supported shapes — issue-driven (``issue-N``), qualified
# (``issue-N-v3``, ``issue-N-backend``), and JIRA (``ENG-1234``).
# The orchestrator's slice-integration branches preserve the
# qualifier (see ``routes/pipelines.py`` ``pipeline.branch``
# propagation), so deriving the issue branch from ``contract_key``
# keeps the reconciler's lookup shape aligned with the producer's
# branch shape. A prior ``f"egg/issue-{issue_number}"`` ternary
# here hard-coded the unqualified shape for any contract with a
# populated ``issue`` field, silently no-op'ing orphan detection
# on every qualified pipeline.
issue_branch = f"egg/{contract.contract_key}"
slices_by_id = {s.id: s for s in contract.slices}

for slice_ in contract.slices:
Expand Down
Loading
Loading