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
2,588 changes: 2,588 additions & 0 deletions .egg-state/brc-history/3393-implement-slice-7.json

Large diffs are not rendered by default.

2,554 changes: 2,554 additions & 0 deletions .egg-state/brc-history/3393-implement-slice-7.md

Large diffs are not rendered by default.

56 changes: 28 additions & 28 deletions action/tests/verify-feedback-contract.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ make_comments() {
local out_file="$1"
shift
local n=$#
if (( n == 0 )); then
echo "[]" > "$out_file"
if ((n == 0)); then
echo "[]" >"$out_file"
return
fi
python3 - "$out_file" "$@" <<'PY'
Expand All @@ -42,24 +42,24 @@ PY
make_lookup() {
local stub_file="$1"
shift
: > "$stub_file"
echo "#!/usr/bin/env bash" >> "$stub_file"
echo 'case "$1" in' >> "$stub_file"
while (( "$#" >= 2 )); do
: >"$stub_file"
echo "#!/usr/bin/env bash" >>"$stub_file"
echo 'case "$1" in' >>"$stub_file"
while (("$#" >= 2)); do
local n="$1"
local j="$2"
shift 2
if [[ -z "$j" ]]; then
echo " ${n}) exit 0 ;;" >> "$stub_file"
echo " ${n}) exit 0 ;;" >>"$stub_file"
else
echo " ${n}) cat <<'JSON'" >> "$stub_file"
cat "$j" >> "$stub_file"
echo "JSON" >> "$stub_file"
echo " ;;" >> "$stub_file"
echo " ${n}) cat <<'JSON'" >>"$stub_file"
cat "$j" >>"$stub_file"
echo "JSON" >>"$stub_file"
echo " ;;" >>"$stub_file"
fi
done
echo ' *) exit 0 ;;' >> "$stub_file"
echo 'esac' >> "$stub_file"
echo ' *) exit 0 ;;' >>"$stub_file"
echo 'esac' >>"$stub_file"
chmod +x "$stub_file"
}

Expand All @@ -68,13 +68,13 @@ make_lookup() {
run_verifier() {
local comments="$1" stub="$2" run_start="$3"
local violations="${TMP}/violations.txt"
: > "$violations"
: >"$violations"
local rc=0
COMMENTS_JSON_FILE="$comments" \
VIOLATIONS_FILE="$violations" \
RUN_START="$run_start" \
REPO="example/repo" \
ISSUE_LOOKUP_SCRIPT="$stub" \
VIOLATIONS_FILE="$violations" \
RUN_START="$run_start" \
REPO="example/repo" \
ISSUE_LOOKUP_SCRIPT="$stub" \
"$SCRIPT" >/dev/null 2>&1 || rc=$?
echo "$rc"
}
Expand Down Expand Up @@ -104,28 +104,28 @@ assert_violation_contains() {
}

RUN_START="2026-04-29T12:00:00Z"
BEFORE="2026-04-29T11:00:00Z" # 1h before run start — predates threshold
AFTER="2026-04-29T12:30:00Z" # 30m after run start
JUST_BEFORE="2026-04-29T11:59:30Z" # 30s before run start — within 60s grace
BEFORE="2026-04-29T11:00:00Z" # 1h before run start — predates threshold
AFTER="2026-04-29T12:30:00Z" # 30m after run start
JUST_BEFORE="2026-04-29T11:59:30Z" # 30s before run start — within 60s grace

issue_in_run() {
local f="${TMP}/issue-${1}.json"
echo "{\"number\": $1, \"created_at\": \"$AFTER\", \"pull_request\": null}" > "$f"
echo "{\"number\": $1, \"created_at\": \"$AFTER\", \"pull_request\": null}" >"$f"
echo "$f"
}
issue_before() {
local f="${TMP}/issue-${1}.json"
echo "{\"number\": $1, \"created_at\": \"$BEFORE\", \"pull_request\": null}" > "$f"
echo "{\"number\": $1, \"created_at\": \"$BEFORE\", \"pull_request\": null}" >"$f"
echo "$f"
}
issue_grace() {
local f="${TMP}/issue-${1}.json"
echo "{\"number\": $1, \"created_at\": \"$JUST_BEFORE\", \"pull_request\": null}" > "$f"
echo "{\"number\": $1, \"created_at\": \"$JUST_BEFORE\", \"pull_request\": null}" >"$f"
echo "$f"
}
pr_object() {
local f="${TMP}/pr-${1}.json"
echo "{\"number\": $1, \"created_at\": \"$AFTER\", \"pull_request\": {\"url\": \"x\"}}" > "$f"
echo "{\"number\": $1, \"created_at\": \"$AFTER\", \"pull_request\": {\"url\": \"x\"}}" >"$f"
echo "$f"
}

Expand Down Expand Up @@ -180,7 +180,7 @@ assert_pass "forbidden phrase inside inline backticks ignored" "$rc" "${TMP}/vio

# === Test 7: deferred-to #NNNN that does not exist is flagged ===
make_comments "${TMP}/c7.json" "Item: deferred-to #9999"
make_lookup "${TMP}/stub7.sh" # empty mapping
make_lookup "${TMP}/stub7.sh" # empty mapping
rc=$(run_verifier "${TMP}/c7.json" "${TMP}/stub7.sh" "$RUN_START")
assert_violation_contains "non-existent issue" "$rc" "${TMP}/violations.txt" "issue does not exist"

Expand Down Expand Up @@ -213,7 +213,7 @@ rc=$(run_verifier "${TMP}/c11.json" "${TMP}/stub11.sh" "$RUN_START")
assert_violation_contains "case-insensitive Deferred-To" "$rc" "${TMP}/violations.txt" "predates this run"

# === Test 12: empty comment list is itself a violation ===
make_comments "${TMP}/c12.json" # no bodies
make_comments "${TMP}/c12.json" # no bodies
make_lookup "${TMP}/stub12.sh"
rc=$(run_verifier "${TMP}/c12.json" "${TMP}/stub12.sh" "$RUN_START")
assert_violation_contains "no response posted" "$rc" "${TMP}/violations.txt" "no top-level response comment"
Expand Down Expand Up @@ -250,7 +250,7 @@ fi

echo
echo "Results: ${PASSES} passed, ${FAILURES} failed"
if (( FAILURES > 0 )); then
if ((FAILURES > 0)); then
exit 1
fi
exit 0
22 changes: 11 additions & 11 deletions action/verify-feedback-contract.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ set -euo pipefail
: "${RUN_START:?required}"
: "${VIOLATIONS_FILE:?required}"

: > "$VIOLATIONS_FILE"
: >"$VIOLATIONS_FILE"

run_start_epoch=$(date -u -d "$RUN_START" +%s)
threshold_epoch=$((run_start_epoch - 60))
Expand All @@ -44,11 +44,11 @@ forbidden_specific+='|follow[- ]?up later)'

forbidden_broad='(will file|will track|will open an issue)'

count=$(jq 'length' < "$COMMENTS_JSON_FILE")
count=$(jq 'length' <"$COMMENTS_JSON_FILE")
echo "Scanning ${count} response comment(s)"

if [[ "$count" -eq 0 ]]; then
echo "no top-level response comment posted by the agent" >> "$VIOLATIONS_FILE"
echo "no top-level response comment posted by the agent" >>"$VIOLATIONS_FILE"
fi

lookup_issue() {
Expand Down Expand Up @@ -84,37 +84,37 @@ while IFS= read -r b64body; do

if [[ -n "$specific_matches" || -n "$broad_matches" ]]; then
matches=$(printf '%s,%s' "$specific_matches" "$broad_matches" \
| sed 's/^,//; s/,$//; s/,,/,/g')
echo "forbidden phrase(s): ${matches}" >> "$VIOLATIONS_FILE"
| sed 's/^,//; s/,$//; s/,,/,/g')
echo "forbidden phrase(s): ${matches}" >>"$VIOLATIONS_FILE"
fi

while IFS= read -r issue_num; do
[[ -z "$issue_num" ]] && continue
issue_json=$(lookup_issue "$issue_num")
if [[ -z "$issue_json" ]]; then
echo "deferred-to #${issue_num}: issue does not exist" >> "$VIOLATIONS_FILE"
echo "deferred-to #${issue_num}: issue does not exist" >>"$VIOLATIONS_FILE"
continue
fi
is_pr=$(echo "$issue_json" | jq -r '.pull_request != null' 2>/dev/null || echo "false")
if [[ "$is_pr" == "true" ]]; then
echo "deferred-to #${issue_num}: refers to a PR, not an issue" >> "$VIOLATIONS_FILE"
echo "deferred-to #${issue_num}: refers to a PR, not an issue" >>"$VIOLATIONS_FILE"
continue
fi
created=$(echo "$issue_json" | jq -r '.created_at' 2>/dev/null || true)
if [[ -z "$created" || "$created" == "null" ]]; then
continue
fi
created_epoch=$(date -u -d "$created" +%s 2>/dev/null || echo 0)
if (( created_epoch < threshold_epoch )); then
if ((created_epoch < threshold_epoch)); then
msg="deferred-to #${issue_num}: issue created at ${created}"
msg+=" predates this run (started ${RUN_START}); re-deferring to a"
msg+=" prior-round issue is not allowed — create a fresh follow-up"
msg+=" or fix in-PR"
echo "$msg" >> "$VIOLATIONS_FILE"
echo "$msg" >>"$VIOLATIONS_FILE"
fi
done < <(echo "$scan_text" | grep -ioE 'deferred-to #[0-9]+' \
| grep -oE '[0-9]+' | sort -u)
done < <(jq -r '.[].body | @base64' < "$COMMENTS_JSON_FILE")
| grep -oE '[0-9]+' | sort -u)
done < <(jq -r '.[].body | @base64' <"$COMMENTS_JSON_FILE")

if [[ -s "$VIOLATIONS_FILE" ]]; then
exit 1
Expand Down
4 changes: 1 addition & 3 deletions config/repo_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,9 +634,7 @@ def assert_uniform_auth(repos: list[str]) -> None:
for repo in repos:
modes.setdefault(get_auth_mode(repo), []).append(repo)
if len(modes) > 1:
groups = "; ".join(
f"{mode}: {', '.join(sorted(rs))}" for mode, rs in sorted(modes.items())
)
groups = "; ".join(f"{mode}: {', '.join(sorted(rs))}" for mode, rs in sorted(modes.items()))
raise ValueError(
"Mixed auth modes across the pipeline's repos are not supported in v1 "
"(a run must be uniformly 'bot' or 'user'). Diverging repos — " + groups + "."
Expand Down
10 changes: 9 additions & 1 deletion gateway/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -4610,7 +4610,7 @@ def gh_pr_merge_state() -> tuple[Response, int] | Response:
if stdout:
try:
parsed = json.loads(stdout)
except (ValueError, TypeError):
except ValueError, TypeError:
parsed = None
if isinstance(parsed, dict):
state_val = parsed.get("state")
Expand Down Expand Up @@ -7874,6 +7874,13 @@ def worktree_create() -> tuple[Response, int] | Response:
repos = data.get("repos", [])
base_branch = data.get("base_branch") # None = resolve per-repo
assigned_branch = data.get("assigned_branch") # None = skip upstream config
# #3393 slice-7: when True, materialize each repo's pipeline work
# branch on its OWN remote right after the worktree exists (push the
# worktree HEAD to refs/heads/{assigned_branch or work-branch}).
# Multi-repo pipelines set this so secondary-repo context / slice PRs
# find a head branch; single-repo (N=1) callers leave it False, so
# the path stays byte-identical to pre-#3393.
push_branch = bool(data.get("push_branch", False))
# UID/GID for worktree ownership (default: 1000 for egg user)
uid = data.get("uid")
gid = data.get("gid")
Expand Down Expand Up @@ -7932,6 +7939,7 @@ def worktree_create() -> tuple[Response, int] | Response:
gid=gid,
assigned_branch=assigned_branch,
repo_slug=repo,
push_branch=push_branch,
)
# Translate container path to host path for egg launcher mount sources.
# Key by the full ``owner/repo`` slug (#3393 slice-3, operator
Expand Down
46 changes: 46 additions & 0 deletions gateway/tests/test_gateway_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,52 @@ def test_missing_repos(self, client, launcher_auth_headers):
data = json.loads(response.data)
assert "repos" in data["message"].lower()

@patch("gateway.get_worktree_manager")
def test_push_branch_forwarded_to_create_worktree(
self, mock_manager, client, launcher_auth_headers
):
"""push_branch from the request body reaches create_worktree (#3393 slice-7).

Executable-seam guard for the ``gateway route → create_worktree(push_branch=…)``
link: a multi-repo pipeline sends ``push_branch: true`` so the gateway
materializes each participating repo's ``egg/<pipeline_id>/work`` branch
on its own remote before any PR-opening call runs against it.
"""
wt = mock_manager.return_value
wt.resolve_default_branch.return_value = "origin/main"
info = MagicMock()
info.worktree_path = "/wt/egg-123/repo"
info.branch = "egg/egg-123/work"
wt.create_worktree.return_value = info

response = client.post(
"/api/v1/worktree/create",
headers=launcher_auth_headers,
json={"container_id": "egg-123", "repos": ["owner/repo"], "push_branch": True},
)

assert response.status_code == 200
assert wt.create_worktree.call_args.kwargs["push_branch"] is True

@patch("gateway.get_worktree_manager")
def test_push_branch_defaults_false(self, mock_manager, client, launcher_auth_headers):
"""Omitting push_branch keeps create_worktree's push_branch False (N=1 path)."""
wt = mock_manager.return_value
wt.resolve_default_branch.return_value = "origin/main"
info = MagicMock()
info.worktree_path = "/wt/egg-123/repo"
info.branch = "egg/egg-123/work"
wt.create_worktree.return_value = info

response = client.post(
"/api/v1/worktree/create",
headers=launcher_auth_headers,
json={"container_id": "egg-123", "repos": ["owner/repo"]},
)

assert response.status_code == 200
assert wt.create_worktree.call_args.kwargs["push_branch"] is False


class TestWorktreeDeleteEndpoint:
"""Tests for /api/v1/worktree/delete endpoint.
Expand Down
9 changes: 6 additions & 3 deletions gateway/tests/test_repo_visibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,9 +588,12 @@ def _fake(owner, repo, **_):

checker = MagicMock()
checker.get_visibility.side_effect = lambda owner, repo, **_: mapping[f"{owner}/{repo}"]
checker.is_private.side_effect = lambda owner, repo, **_: mapping[f"{owner}/{repo}"] in (
"private",
"internal",
checker.is_private.side_effect = lambda owner, repo, **_: (
mapping[f"{owner}/{repo}"]
in (
"private",
"internal",
)
)
monkeypatch.setattr("repo_visibility.get_visibility_checker", lambda: checker, raising=False)

Expand Down
Loading
Loading