Skip to content

feat(automation): gated executor with safety rails (Arc D phase 3a)#45

Merged
saagpatel merged 1 commit into
mainfrom
arc-d3a-executor
Jun 6, 2026
Merged

feat(automation): gated executor with safety rails (Arc D phase 3a)#45
saagpatel merged 1 commit into
mainfrom
arc-d3a-executor

Conversation

@saagpatel

Copy link
Copy Markdown
Owner

Arc D — Phase 3a: the gated executor (real-repo writes, safe by construction)

The only layer that mutates real repos. Every git/gh call goes through an injected CommandRunner (real subprocess in prod, fake in tests), so the exact command sequence and every rail are unit-tested with no repo or network touched.

Safety rails (all tested)

  • require_approved is the hard pre-gate — an unapproved proposal runs nothing.
  • dry_run defaults to True; in dry-run, no mutating command and no apply_change run (only the read-only dirty check).
  • context-PR: refuses protected/default branch (case-insensitive), rejects unsafe/flag-like branch names (^[A-Za-z0-9._]... — no leading -), requires non-empty default branch, never --force, skips dirty worktrees + non-git repos.
  • fail-fast: a failed checkout/commit never reaches push or gh pr create; an apply_change exception returns failed and best-effort restores the default branch (no stranding).
  • catalog-seed apply is local + reversible (only fills missing fields, reuses _merge_catalog_seeds).
  • mark_executed (added to automation_proposals): APPROVED → EXECUTED only — the lifecycle can't skip approval.

Design seam

Content generation (the managed block, the catalog seed dict) is caller-supplied and computed fresh at execution time via ExecutionPlan.apply_change / seeds — phase 3b wires those to the existing recovery primitives and adds the propose → approve → execute CLI.

Gates

  • 27 executor tests (tests/test_automation_executor.py) + mark_executed lifecycle tests. Full suite 2394 passed, 2 skipped. ruff check . clean.
  • Adversarial /code-review applied: fixed case-insensitive branch bypass, empty default_branch, flag-like-branch injection (-b --force / --head), and the apply_change strand-on-failure hole. Content fields (-m/--title/--body) are flag-consumed values (safe; pr_body legitimately starts with - ).

Next — phase 3b: wire fresh content generation (managed block + seeds) and the operator CLI workflow (--propose-automation, --approve-proposal, --reject-proposal, --execute-proposals [--apply]).

Add src/automation_executor.py: the only layer that mutates real repos, built
safe by construction. Every git/gh call goes through an injected CommandRunner
(real subprocess in prod, fake in tests), so the full command sequence and all
rails are unit-tested without touching a repo or network.

Rails (all tested):
- require_approved is the hard pre-gate; an unapproved proposal runs nothing.
- dry_run defaults to True; in dry-run no mutating command and no apply_change run.
- context-PR refuses protected/default branches (case-insensitive), rejects
  unsafe/flag-like branch names, requires a non-empty default branch, never uses
  --force, skips dirty worktrees and non-git repos.
- fail-fast: a failed checkout/commit never reaches push or gh pr create; an
  apply_change exception returns 'failed' and best-effort restores the branch.
- catalog-seed apply is local + reversible (only fills missing fields).
- mark_executed (added to automation_proposals): APPROVED -> EXECUTED only.

Content generation (managed block, catalog seeds) is supplied by the caller and
computed fresh at execution time; phase 3b wires it to the recovery primitives
and adds the propose/approve/execute CLI.

27 executor tests + mark_executed tests. Full suite 2394 passed, ruff clean.
/code-review (adversarial) applied: fixed case-insensitive branch bypass,
empty default_branch, flag-like branch injection, and apply_change strand.
@saagpatel
saagpatel merged commit 424eecb into main Jun 6, 2026
2 checks passed
@saagpatel
saagpatel deleted the arc-d3a-executor branch June 6, 2026 09:47

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a80e743069

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

)

# Run checkout first, then apply the change, then stage/commit/push/PR.
checkout = runner(["git", "checkout", "-b", branch], plan.repo_path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Branch automation from the declared base

When a repo is clean but currently checked out to any non-default branch, this git checkout -b forks the automation branch from the current HEAD while the PR is opened against default_branch, so the PR can include unrelated commits from the operator's local branch. I checked git checkout -h and -b <branch> only creates/checks out a new branch; because no start point is passed here, the executor needs to first verify/checkout the declared default branch or create the branch from that ref.

Useful? React with 👍 / 👎.

Comment on lines +172 to +175
result = runner(args, plan.repo_path)
if result.returncode != 0:
return ExecutionResult(
proposal.proposal_id, "failed", f"{args[1]}: {result.stderr}".strip()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the repo after post-apply failures

If apply_change succeeds but git add, git commit, or git push fails, this returns failed without undoing the checkout or the file changes; for example, a missing git user config or a commit hook failure leaves the repo on the automation branch with staged/dirty changes. The dirty check ran before these mutations, so the next execution can either skip as dirty or fail because the branch already exists; mirror the restore/cleanup used for apply_change exceptions on these failure paths.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant