Honor repository branch-name policy before builder PR creation - #970
Honor repository branch-name policy before builder PR creation#970devin-ai-integration[bot] wants to merge 6 commits into
Conversation
Add repositories[].delivery_policy.branch_template, resolved and validated independently of builder_identity.branch_prefixes, and wire it into the hosted Devin work-order seam and the generated local lane runner. Closes #865 Co-Authored-By: bot_apk <apk@cognition.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Codex audit (merge-authority lane)Head SHA: Codex Audit: BLOCKED Summary: Branch-policy compliance is conflated with builder ownership, breaking single-writer enforcement and risking misattributed delivery outcomes. Findings:
|
|
Devin is currently unreachable - the session may have died. |
Claude audit (merge-authority lane)Head SHA: Claude Audit: PASS Summary: Branch-policy feature (template compilation, config validation, hosted work-order integration, and generated Mac-lane-runner enforcement) is well-scoped and heavily tested, including an end-to-end runner execution test. No P0/P1/P2 correctness, security, or contract-breaking issues found; only minor non-blocking observations. Findings:
|
…rity A branch that satisfies repositories[].delivery_policy is valid for every builder and for humans, so matching it proves nothing about provenance. The runner now decides ownership from configured builder labels plus the authenticated PR author (at least one current-lane signal, none mapping elsewhere) for explicit targets, automatic fix selection, and delivery snapshots; snapshots also require a same-repository head, the exact pre-resolved branch, and exactly one candidate. The pre-push guard authorizes the exact resolved branch instead of the policy regex. Repository slugs are deduplicated case-insensitively to match policy lookup, and WorkOrder.from_manifest requires the loaded config (or an explicit policy) so a hosted dispatcher cannot skip a configured policy by omission; WorkOrder.resolve_branch covers the tracker-key path. Co-Authored-By: bot_apk <apk@cognition.ai>
Codex audit (merge-authority lane)Head SHA: Codex Audit: BLOCKED Summary: The runner inconsistently enforces branch restrictions, and its provenance map omits identities needed to detect cross-builder ownership conflicts. Findings:
|
Claude audit (merge-authority lane)Head SHA: Claude Audit: PASS Summary: PR #970 adds a repository branch-name policy contract (branch_policy.py), threads it through Devin hosted work orders (devin_work_orders.py), config validation (config.py), and the generated/local Mac lane runner script (three synced copies). The change is extensively covered by new tests (test_branch_policy.py, updates to test_devin_builder_lane.py, test_devin_work_orders.py, test_init_build_loop.py, test_lane_delivery_contract.py) including end-to-end subprocess fixtures for the generated runner, pre-push guard authority, and provenance/ownership edge cases (conflicting labels, forks, ambiguous candidates). Core invariants (template validation, branch resolution, case-insensitive duplicate-slug rejection, hosted work-order binding requiring an explicit config or branch_policy, pattern portability to jq) are each backed by targeted tests. The one behavior that looked at first like a bypass — the pre-push guard still allowing a lane's legacy branch prefix even when a repository delivery_policy is configured — is a deliberate, explicitly tested design choice (test_only_the_exact_resolved_policy_branch_is_writable_without_a_lane_prefix), not an oversight, so it is not flagged as blocking. No P0/P1/P2 correctness, security, or data-loss issues were found. Independence guard: head branch is devin/865-branch-policy (not claude/*), so this is a normal, applicable audit, not a Claude-authored PR. Findings:
|
…ovenance A policy-enabled issue build now authorizes only the resolved branch: allowed_prefixes is emptied while allowed_branch is set and the guard's lane_prefix path requires that no exact policy branch exists. Provenance conflict detection maps labels (including aliases and builder_identity labels) and authenticated authors for every configured builder lane to its lane, so a PR carrying a nonlocal builder's signal is a conflict rather than unowned. Execution eligibility remains mac_lanes. Applied to all synchronized runner copies with regression tests. Co-Authored-By: bot_apk <apk@cognition.ai>
The runner now checks the rendered policy branch with the same
conservative git-check-ref-format subset as branch_policy.is_valid_ref
(plus git check-ref-format --branch) before installing the pre-push guard
or starting a provider. A repository named .github with template
{repo_name}/{issue_number} renders .github/12, which matches the policy
pattern but is not a valid branch; the run now fails before dispatch.
Applied to all synchronized runner copies with regression tests that
exercise the .github case end to end and compare the shell and Python
validators over the same inputs.
Co-Authored-By: bot_apk <apk@cognition.ai>
Codex audit (merge-authority lane)Head SHA: Codex Audit: BLOCKED Summary: Policy-named branches cannot use explicit recovery handoffs, and transient title lookup failures can change branch identity and cause duplicate delivery. Both issues affect the generated runner and its mirrored copies. Findings:
|
Claude audit (merge-authority lane)Head SHA: Claude Audit: PASS Summary: Reviewed the branch-name-policy feature (branch_policy.py, config validation, WorkOrder integration, and the mirrored bash logic in the generated/repo run_mac_lane.sh templates). The Python resolver/validator and its bash mirror (is_valid_ref, pattern matching, provenance checks) are logically consistent with each other and with the extensive new test suite (test_branch_policy.py plus updates across test_devin_builder_lane.py, test_devin_work_orders.py, test_init_build_loop.py, test_lane_delivery_contract.py), which specifically exercises the security-relevant edge cases: fork heads, conflicting builder labels/authors, multiple PR candidates, unconfigured repos, case-insensitive slug dedup, and ref-validity parity between the Python and shell implementations. The single-writer pre-push guard correctly narrows write authority to the exact resolved branch when a repository delivery_policy is configured, and falls back to lane-prefix authority otherwise. No correctness, security, or data-loss blockers were found; the WorkOrder.from_manifest signature change (now requiring config or branch_policy) is a documented, intentional breaking change for external dispatchers, not an oversight. Head branch is devin/865-branch-policy (Devin-authored), so the Claude-audit independence guard does not apply. Findings: none. |
A failed or empty gh issue title lookup no longer degrades into an empty slug: the runner aborts before branch resolution, guard installation, or any provider start, so a transient GitHub failure cannot resolve a different branch, miss the PR an earlier run opened, and deliver twice. Applied to all three synchronized run_mac_lane.sh copies with a regression test for both the failed and the empty lookup. The explicit-handoff refusal for a repository-policy-named branch now points at #962, which owns provenance-aware handoff; the refusal itself is unchanged and stays fail-closed. Co-Authored-By: bot_apk <apk@cognition.ai>
Codex audit (merge-authority lane)Head SHA: Could not validate a Codex structured verdict artifact. The CLI may have produced no review, the structured-output pass may have failed, or the verdict format may have drifted. Requeuing for re-review. |
Claude audit (merge-authority lane)Head SHA: Claude Audit: PASS Summary: Repository branch-name policy feature (branch_policy.py + config validation + Devin work-order integration + generated/committed run_mac_lane.sh runner) is well-tested and fail-closed. Python resolver/validator, jq-based provenance queries, and bash ref validation were cross-checked and are consistent; provenance separation (label/author, never branch name alone) correctly prevents an attacker from hijacking builder write authority via a policy-conforming branch name. No P0/P1/P2 correctness or security blockers found. Findings:
|
Before the guard is installed or a provider starts, a policy-bound issue run now inspects the resolved branch on origin. An existing branch is only writable when every pull request attached to it carries this lane's provenance; a foreign builder's or a human's pull request, a branch with no attributable pull request, or a failed lookup refuses the run. The delivery-snapshot filter can no longer make a foreign PR look absent and grant authority over its branch. Cross-builder recovery of a policy-named branch stays a fail-closed explicit handoff concern (#962). In pull-request fix rounds on a repository with a configured branch policy, the target head must match that policy; a lane prefix alone no longer authorizes an off-policy branch, and the guard is installed for exactly the validated target with the lane prefixes withheld. Repositories without a policy keep the provider-prefix behavior. Applied to all synchronized run_mac_lane.sh copies with regressions for a foreign-builder branch, a human-owned pull request, an unattributed branch, same-lane continuation, an off-policy prefixed fix target, the exact policy-compliant target, and the no-policy prefix path. Co-Authored-By: bot_apk <apk@cognition.ai>
Codex audit (merge-authority lane)Head SHA: Codex Audit: BLOCKED Summary: Policy-based issue retries can lose track of an existing PR after a title change, allowing duplicate delivery instead of preserving the original branch. Findings:
|
|
The hosted Devin qualification run is stopping here without merge. The exact-head Code Mower Codex audit at This PR required five bounded recovery sessions after the initial delivery. The provider is now suspended; its API rejected cancellation, and no further provider writes are authorized. Issue #865 remains open and will continue on a fresh Codex-owned branch and pull request so ownership and review evidence are unambiguous. This PR remains part of the public qualification evidence and is not counted as a successful Devin delivery. |
Summary
Closes #865
code_mower/branch_policy.py:repositories[].delivery_policy.branch_templateis a per-repository branch-naming contract separate frombuilder_identity.branch_prefixes(provenance).compile_template(template) -> BranchPolicy(template, pattern, example, configured),resolve_branch(policy, lane=, issue_key=, issue_number=, slug=, work_type=, repository=),validate_branch(policy, branch). Only{lane} {issue_key} {issue_number} {slug} {work_type} {repo_name}are accepted and a template must contain{issue_key}or{issue_number}.{issue_key}falls back to the GitHub issue number; an empty slug drops itself and its leading separator (fix/{issue_key}-{slug}->fix/907). No policy configured ->default_policy()({lane}/{issue_key}-{slug},configured=False), i.e. the provider-prefix convention is unchanged.config.validate_configvalidatesdelivery_policy(onlybranch_template, compiled) and deduplicates repository slugs case-insensitively, matching the case-insensitive policy lookup;branch_policy.policies_by_repository(config)raises on such a collision instead of letting one entry shadow another.devin_work_orders.py):WorkOrder.from_manifest(..., config=)(or an explicitbranch_policy=; exactly one is required, omission raisesbranch_policy_required) applies the configured repository policy and rejects a nonconforming branch withRemoteError("branch_policy_mismatch: ...")before any provider create.WorkOrder.repository_policy(config, repo)andWorkOrder.resolve_branch(policy, lane=, issue=, work_item=, slug=)are the maintained dispatcher path: the bound tracker key (e.g. a Jira issue key) becomes{issue_key}, so a Jira-keyed order resolvesfix/MB-9506-nv-accessible-labelon the first attempt. Unconfigured/default policies leave the order, its_fields, binding hash, and prompt byte-identical to before.run_mac_lane.sh, all three synchronized copies):initembeds__LANE_MAC_RUNNER_BRANCH_POLICY_JSON__and__LANE_MAC_RUNNER_BUILDER_AUTHORS_JSON__. For an issue target with a policy the runner resolves the exact branch before the provider runs and tells the provider the template/pattern/example plus that exact branch.lane_provenance): configured builder labels and the authenticated PR author are mapped to lanes; at least one must map to the current lane and none may map to another lane (conflicts fail closed). Delivery-policy compliance is never a provenance signal. The predicate applies to explicitpr:targets, automatic fix-round selection, and delivery-snapshot discovery. Snapshot discovery additionally requires a same-repository head, the exact pre-resolved branch (lane prefix only when no policy is configured), the closing-issue reference, and exactly one candidate (multiple -> failed lookup, not a guess).allowed_patternis gone.allowed_branchcarries the exact resolved branch (authorityrepo_policy_branch); lane prefixes, the verified explicit target branch, and validated handoffs are unchanged. A regex can no longer grant write authority to any other policy-matching branch.docs/github-setup.mdexample,docs/devin-work-orders.mddispatcher example now passesconfigand resolves the branch throughWorkOrder, commenteddelivery_policystanza incode-mower.example.yml.Assumptions: the local runner derives
{slug}from the issue title after slugification ([a-z0-9-], max 48 chars);{work_type}renders asfixin the runner. Existing fakeghfixtures that model "this run opened its PR" now carryheadRepository,labels, andauthorbecause a PR without provenance is (correctly) no longer attributable to a lane.tests/test_devin_work_orders.pypasses an unconfiguredconfigwhere it previously relied on the omitted-policy default.Validation
python scripts/privacy_scan.py,python -m ruff check src tests,python -m compileall -q src scripts,python scripts/guard_package_workflows.py,python -m code_mower.migration release-readiness --json: passpython -m pytest tests: 3634 passed, 19 skippedtemplates/lanes/run_mac_lane.shandsrc/code_mower/templates/lanes/run_mac_lane.share byte-identical;tools/lanes/run_mac_lane.shdiffers only in generated substitutions.allowed_patternin the guard config) is writable; case-insensitive duplicate repository slugs are a config error and are rejected byWorkOrder.repository_policy; omitting bothconfigandbranch_policyis rejected; Jira-keyed orders resolve a conforming branch through the maintainedWorkOrderpath.Risk / Rollback
delivery_policybehave as before, except thatWorkOrder.from_manifestnow requiresconfig=orbranch_policy=(a dispatcher that omitted both must pass its loaded config). Revert the two commits to roll back.Data Or Secret Exposure
Link to Devin session: https://app.devin.ai/sessions/60ec1193b3d340459132d91e5df39f9d
Open in Devin Desktop: https://app.devin.ai/desktop/session/60ec1193b3d340459132d91e5df39f9d?variant=devin