Add missing Phase 2a gateway endpoints - #19
Conversation
Implements 6 REST endpoints from issue #14: - POST /api/v1/gh/pr/create - Create PR with policy checks - POST /api/v1/gh/pr/comment - Comment on PR - POST /api/v1/gh/pr/edit - Edit PR (ownership verified) - POST /api/v1/gh/pr/close - Close PR (ownership verified) - POST /api/v1/sessions/<token>/heartbeat - Extend session TTL - GET /api/v1/repos/visibility - Query repo visibility Additional changes: - Add check_pr_create_allowed() to PolicyEngine - Add get_session_by_ip() to SessionManager (for Phase 2b proxy) - Wire up check_heartbeat_rate_limit() for heartbeat endpoint - Update docs/api.md with new endpoints Closes #14
There was a problem hiding this comment.
Review: Phase 2a Gateway Endpoints
✅ Issue #14 Coverage
All 6 required endpoints are implemented:
| Requirement | Status |
|---|---|
POST /api/v1/gh/pr/create |
✅ Implemented with policy checks |
POST /api/v1/gh/pr/comment |
✅ Implemented (any PR allowed) |
POST /api/v1/gh/pr/edit |
✅ Implemented with ownership check |
POST /api/v1/gh/pr/close |
✅ Implemented with ownership check |
POST /sessions/{token}/heartbeat |
✅ Implemented with rate limiting |
GET /api/v1/repos/visibility |
✅ Implemented |
get_session_by_ip() |
✅ Added to SessionManager |
check_pr_create_allowed() |
✅ Added to PolicyEngine |
🔍 Findings
1. Code Duplication (Medium)
The four PR endpoints (pr_create, pr_comment, pr_edit, pr_close) share ~80% identical boilerplate:
- Request validation
- Auth mode determination
- Private repo access checks
- Audit logging patterns
Consider extracting a helper like _handle_pr_operation(operation, required_fields, policy_check_fn) in a follow-up.
2. Inconsistent Exception Handling (Low)
gh_pr_create has a try/except wrapper but gh_pr_comment, gh_pr_edit, and gh_pr_close don't. Either all should have it or none.
3. Missing Error Context in /repos/visibility (Low)
If get_repo_visibility() fails for one repo, it returns None but doesn't indicate why (invalid format vs. API error vs. not found). Consider richer error reporting.
4. No Unit Tests Included
The test plan mentions running pytest tests/unit/ but no new tests are added for the 6 new endpoints. For a PR this size (713 additions), tests would catch regressions.
✅ What's Done Well
- Policy enforcement: PR ownership and private mode checks consistently applied
- Audit logging: All operations logged with appropriate details
- Documentation:
docs/api.mdupdated with complete examples - Rate limiting: Heartbeat rate limiting properly integrated
- Alignment with PR #693: Follows the sandbox extraction proposal architecture
Recommendations
- Before merge: Add at least happy-path tests for each new endpoint
- Follow-up PR: Refactor duplicated code across PR endpoints
- Minor: Add consistent exception handling across all new endpoints
Verdict
The PR correctly implements issue #14's requirements and aligns with the Phase 2a plan. The code is functional but would benefit from tests before merging. The duplicated patterns are acceptable for now given this is a phase-based extraction.
— Authored by jib
Add comprehensive test coverage for the 6 new endpoints added in PR #19: - gh_pr_create: Tests for successful creation, validation errors, user mode blocking - gh_pr_comment: Tests for comment addition, validation, any-PR access - gh_pr_edit: Tests for editing, ownership verification - gh_pr_close: Tests for closing, ownership verification - session_heartbeat: Tests for TTL extension, rate limiting - repos_visibility: Tests for visibility queries, multiple repos Also adds migrated and enhanced tests from james-in-a-box: - git_client tests: SSH URL handling, arg validation, path validation - policy tests: PolicyEngine methods, branch/PR ownership, PR creation rules This addresses review feedback requesting tests before merge.
Response to Review FeedbackThank you for the thorough review. ✅ Addressed: Unit Tests AddedAdded comprehensive test coverage for all 6 new endpoints (101 new tests total): Endpoint tests (
Migrated from james-in-a-box (
Policy engine tests (
📋 Follow-up Items (Acknowledged)As suggested in the review, these can be addressed in follow-up PRs:
— Authored by jib |
1. Code Duplication (Medium) - Extracted helper functions:
- check_pr_operation_access(): Common access checks (auth mode,
session mode, private repo policy)
- execute_pr_operation(): Consistent GitHub CLI execution with
error handling and audit logging
2. Inconsistent Exception Handling (Low) - All four PR endpoints
now use execute_pr_operation() which has consistent try/except
wrapping for GitHub operations
3. Missing Error Context in /repos/visibility (Low) - Response now
includes richer error information:
- Each repo returns {"visibility": "...", "error": "..."}
- Errors list aggregates all issues for debugging
- Error types: invalid_format, not_found_or_no_access, api_error
These changes reduce ~80 lines of duplicated code across the PR
endpoints while maintaining the same behavior and test coverage.
Update: All Review Feedback Addressed✅ Code Duplication (Medium) - FixedExtracted two helper functions to reduce ~80 lines of duplicated code: # Common access checks for all PR operations
check_pr_operation_access(repo, operation, endpoint_name, pr_number?)
-> Returns (PROperationContext, None) or (None, error_response)
# Consistent execution with error handling
execute_pr_operation(operation_name, endpoint_name, repo, auth_mode,
gh_args, audit_details, success_message, timeout?)
-> Returns Flask response with try/except wrapping✅ Inconsistent Exception Handling (Low) - FixedAll four PR endpoints now use
✅ Missing Error Context in /repos/visibility (Low) - FixedResponse format now includes richer error information: {
"visibilities": {
"owner/repo1": {"visibility": "public"},
"owner/repo2": {"visibility": null, "error": "not_found_or_no_access"},
"owner/repo3": {"visibility": null, "error": "invalid_format"}
},
"errors": ["owner/repo2: repository not found or no access", ...]
}Error types: Test CoverageAll 101 tests continue to pass with updated assertions for the new visibility response format. — Authored by jib |
Minor Fix: Audit Log Event Naming ConsistencyThe - operation_name="pr_created",
+ operation_name="pr_create",
- operation_name="pr_comment_added",
+ operation_name="pr_comment",
- operation_name="pr_edited",
+ operation_name="pr_edit",
- operation_name="pr_closed",
+ operation_name="pr_close",Rationale:
All tests still pass after this change. Re: observations 2 & 3 from review:
— Authored by jib |
Change operation_name values to match docstring convention: - pr_created → pr_create - pr_comment_added → pr_comment - pr_edited → pr_edit - pr_closed → pr_close This produces consistent audit log patterns (pr_create, pr_create_failed) that align with existing conventions (push_success, push_failed).
|
Update: Fix pushed in commit 5660c8d. The PR now has consistent — Authored by jib |
Add assert statements after check_pr_operation_access() early returns to help mypy understand that ctx cannot be None after the error check.
|
Fix pushed: Added mypy assertions in commit 8fb6a9f. The — Authored by jib |
…lockers Reviewer_plan NACKed the v1 plan with one blocking item (single-OR JQL fails on team-managed Jira projects) plus 20 non-blocking flags ranked by impact. This revision lands the blocker plus the 10 highest-impact non-blockers in one re-propose. Blocker: - TASK-1-3 + TASK-1-12: replace the single-OR JQL `parent = <K> OR "Epic Link" = <K>` with two separate queries (`parent = "<K>"` and `"Epic Link" = "<K>"`) and merge results, tolerating per-query HTTP 400 (architect ad-9 / risk_analyst R4). Single-OR fails on team-managed projects that lack the "Epic Link" custom field; auto-detection silently downgrades to fresh-path and the sweep returns empty. Exports the helper `search_epic_children` so TASK-1-12 reuses it. Top non-blocking (reviewer-flagged as most impactful): - #1 In-flight gate trust-boundary trade-off: add explicit acknowledgement that gateway-side enforcement is deferred and v1 relies on agent-side gating + apply-time re-check by TASK-1-13. - #5 APPLY_EPIC role registration: expand TASK-1-10 to enumerate all FIVE registration steps (AgentRole, AgentRoleDefinition, get_roles_for_phase, file-restrictions patterns, spawner branch). - #6 epic_apply persistence MCP surface: add `mcp__sdlc__update_epic_apply` MCP tool to TASK-1-7 so the sandbox-side agent can persist artifact updates. - #7 Concurrent-edit guard: TASK-1-10 now fetches the current epic Description, sha256s it, and registers a divergence HITL on mismatch; TASK-1-9 records the baseline sha256; TASK-1-7 adds `refine_description_sha256` to the schema. Additional non-blockers folded in: - #2: jira_effective_mode added to primitives table. - #3: TASK-1-5 introduces `shared/egg_jira_credentials.py` shared module to eliminate the orchestrator → gateway coupling. - #8: TASK-1-11 commits to extending `parse_plan` (not pass-through). - #9: TASK-1-5/TASK-1-14 add already-in-state idempotent short-circuit for Won't-Do transitions. - #10: TASK-1-15 introduces `Pipeline.jira_parent_epic_key` so PR phase doesn't need an extra Jira call. - #11: TASK-1-16 adds `PipelinePhase.PLAN_STOPPED` documented terminal phase + updates overseer monitor short-circuit. - #14: TASK-1-11 requires `wont_do_reason` per node + ⚠ warning rendering in the plan draft (R6). - #15: TASK-1-5 gates the orchestrator-direct cred surface behind `EGG_ENABLE_ORCH_JIRA_TRANSITIONS` (default off — R1). - #16: TASK-1-7 schema gains `version`, `idempotency_seed`, per-edit `summary_hash` + `applied_at`, `wont_do_reason`, signal_source as a list (R10). - #19: TASK-1-19 drops orchestrator-cli.md, adds submit-task-mcp.md. - #13: TASK-1-18 adds the lint regression test `test_no_outbound_jira_writes.py` (R7). - #12: TASK-1-12 introduces a reverse-index `.egg-state/jira-child-pipeline-index.json` to bound the sweep to O(K) (R3 performance mitigation). - #20: New "Risk-analyst items addressed" section summarises how R1/R2/R6/R7/R10/R12 are resolved in-plan (no fresh HITLs). Plan still parses cleanly: 1 slice, 19 tasks, 0 warnings, 0 role-alignment errors. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ouble-fire guard, register Tier 3 exceptions, reorder midturn_messages Reviewer feedback identified four issues in v1: 1. AC-1 was unsatisfiable: claimed all 13 fields populated but only 5 in scope 2. detect_heartbeat_stall/ConsensusStallCheck double-fire not addressed 3. TASK-2-2 (#19) and TASK-3-2 (#20) are Tier 3 without registered decisions 4. midturn_messages sequenced last despite gating the primary deliverable All four corrected. ACK verdict unchanged.
Summary
Implements all 6 missing REST API endpoints from issue #14, completing Phase 2a of the gateway extraction.
New endpoints:
POST /api/v1/gh/pr/create- Create PR with policy checks (blocked in user mode)POST /api/v1/gh/pr/comment- Comment on any PRPOST /api/v1/gh/pr/edit- Edit PR title/body (ownership verified)POST /api/v1/gh/pr/close- Close PR (ownership verified)POST /api/v1/sessions/<token>/heartbeat- Extend session TTL with rate limitingGET /api/v1/repos/visibility- Query visibility for multiple reposSupporting changes:
check_pr_create_allowed()to PolicyEngine for user mode blockingget_session_by_ip()to SessionManager (required for Phase 2b Anthropic API proxy)check_heartbeat_rate_limit()for heartbeat endpointIssue: #14
Test plan
python -c "import gateway"ruff check gateway/pytest tests/unit/Authored-by: jib