Commit Graph

4 Commits

Author SHA1 Message Date
drew 0bc734c020 style: ruff format the controller-state-machine branch (288 files)
Applies `ruff format` to the accumulated formatting debt on this branch.
Formatting-only — no behavioral changes. Required for CI/lint's format
gate (`nox -s format -- --check`), which the branch was failing on 288
tracked files that drifted from ruff's canonical style.

In-progress WIP files are intentionally excluded so this commit stays a
clean formatting-only diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:09:17 -04:00
drew 52208fd214 Fork-mode safety: env-derived clone URLs + full upstream protection mirroring
Two fork-mode gaps surfaced once we started preparing for Phase 4:

1. tools/merge_drive.py had hard-coded clone URLs:

       GIT_REMOTE   = "ssh://git@git.cleverthis.com/cleveragents/cleveragents-core.git"
       HTTPS_REMOTE = "https://git.cleverthis.com/cleveragents/cleveragents-core.git"

   With FORGEJO_OWNER=drew exported, the driver would correctly
   *read* the fork's API state but *push merge commits* to canonical
   master — exactly the disaster scenario the sentinel design was
   meant to prevent. The URLs are now derived at module-load time
   from API_BASE, REPO_OWNER, and REPO_NAME (host extracted from
   API_BASE), so the same fork-mode env that retargets the API
   automatically retargets the clone destination too. Operators
   with unusual on-prem layouts (separate git host vs. API host)
   can pin the URLs explicitly via FORGEJO_GIT_REMOTE /
   FORGEJO_HTTPS_REMOTE.

2. tools/setup_test_fork.py was only mirroring upstream's required
   status_check_contexts, not the *behavioural* settings on the
   protection rule. Most importantly, required_approvals on the
   fork was the Forgejo default of 0 instead of upstream's 1 —
   meaning a green-CI sentinel could be merged with no review,
   completely short-circuiting the reviewer ↔ driver handoff that
   Phase 4 is supposed to exercise. New
   fetch_upstream_protection_settings() reads the four behavioural
   fields verbatim (required_approvals, block_on_outdated_branch,
   dismiss_stale_approvals, require_signed_commits) and threads
   them through apply_branch_protection. Verified end-to-end: the
   fork's protection now matches upstream byte-for-byte
   (required_approvals=1, block_on_outdated_branch=false,
   dismiss_stale_approvals=false, require_signed_commits=false,
   plus the same 10 wildcard contexts).

Documents the production launch story in AGENTS.md (long-running
merge_drive daemon, 15-min verify_invariant cron, "every few hours"
flag_stale_prs cron, OpenCode-launched supervisor sessions for
reviewer / implementer / merge supervisors) and shows the exact
two-shell command pattern to reproduce production against the fork.

Also documents that fork-mode does NOT need a FORGEJO_MERGE_BOT_EMAILS
override: the driver hard-codes user.email=forgejo@cleverthis.com
in the local-clone git config, and that's already the default
verifier MERGE_BOT_EMAILS — so driver-merged commits are
recognised as bot-merges in both modes.

7 new unit tests cover the env-derived clone URLs (canonical
default, FORGEJO_OWNER override, FORGEJO_API_BASE host extraction,
explicit FORGEJO_GIT_REMOTE override) and the upstream-settings
mirroring (happy / 404 / partial-payload / threading through run
into apply_branch_protection). 176 tests passing in both canonical
and fork modes (up from 169).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 14:03:57 -04:00
drew 5c6f9602df Wire CI on sentinel fork: enable Actions/Issues + mirror upstream gate
Forgejo disables has_actions and has_issues on every newly-created
fork by default, so the workflow YAML files copied across with the
fork never fired. Without runs, the deterministic merge driver had
nothing to wait for; without issues, verify_invariant.py could not
file auto/invariant-violation issues. Branch-protection on the fork
also had an empty required_checks list, so the fork's gate accepted
any commit that passed the push whitelist — the opposite of what we
need to test the invariant.

This commit closes those three gaps in tools/setup_test_fork.py:

1. enable_repo_features() — PATCHes has_actions=true and has_issues=
   true on the fork after creation. Idempotent (no PATCH when both
   flags are already on); partial-diff (only PATCHes missing flags);
   --no-actions / --no-issues opt-outs for scenario tests.

2. fetch_upstream_required_checks() — reads the upstream protection
   rule's status_check_contexts (with the CI / job* wildcard
   convention) and returns the list verbatim. Returns [] on any
   error so callers fall back gracefully.

3. apply_branch_protection() — now accepts a required_checks list
   and includes enable_status_check + status_check_contexts in the
   PATCH/POST body. The list-comparator is order-insensitive so
   reordered upstream entries don't trigger spurious diffs.
   --no-mirror-checks opts out for no-CI scenarios.

Also decouples the upstream selector from FORGEJO_OWNER. The
bootstrap now reads FORGEJO_UPSTREAM_OWNER / FORGEJO_UPSTREAM_REPO
(defaulting to canonical cleveragents/cleveragents-core) and accepts
--upstream-owner / --upstream-repo CLI flags. With this change an
operator can have FORGEJO_OWNER=drew exported in their shell to
retarget downstream tools (driver / verifier / preflight /
duplication) at the fork without accidentally pointing the
bootstrap at drew/<repo> as its own upstream.

Verified end-to-end against the live fork:
- has_actions: False -> True, has_issues: False -> True (HTTP 200).
- Fork's branch protection now requires the same 10 wildcard
  contexts upstream requires (CI / build*, CI / coverage*,
  CI / docker*, CI / e2e_tests*, CI / integration_tests*,
  CI / lint*, CI / quality*, CI / security*, CI / typecheck*,
  CI / unit_tests*).
- merge_drive.py --status against the fork returns the same 10
  required_checks list — the driver auto-discovers it via the
  branch-protection API.
- An empty smoke commit pushed to one sentinel branch fired a
  pull_request: synchronize Actions run; all 14 status contexts
  reported back to the head SHA (5 success, 4 pending, 1 failure
  — the failure is a real pre-existing lint issue in the diff,
  which is fine; it confirms the fail-closed path also works).

12 new unit tests cover the three new helpers and the run() ->
apply_branch_protection wiring (169 total, up from 157). Documents
the Forgejo-specific quirks (has_actions disabled by default,
status field name vs GitHub's state, wildcard required-check
convention) and the bootstrap env-var split (FORGEJO_UPSTREAM_*
for setup_test_fork.py vs FORGEJO_OWNER/_REPO for downstream
tools) in AGENTS.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 13:38:15 -04:00
drew 73581e0f19 feat(auto-agents): personal-fork sentinel testing scaffolding
Establishes a fully-isolated test surface for the deterministic
auto-agents pipeline so the existing review / implementation / merge
supervisors never touch sentinel PRs. Achieves isolation by leveraging
what the existing supervisors *already* ignore: PRs in any repo other
than cleveragents/cleveragents-core. The supervisors hard-code their
--owner / --repo flags through the list_prs_* wrappers, so PRs in a
fork are absolutely invisible to them. No agent or prompt change is
required — this is entirely additive.

Env-var refactor (8 tools)
  All eight pipeline tools now read FORGEJO_OWNER / FORGEJO_REPO /
  FORGEJO_API_BASE (and where applicable FORGEJO_ORG /
  FORGEJO_DEFAULT_BRANCH / FORGEJO_MERGE_BOT_EMAILS /
  FORGEJO_PUSH_WHITELIST) at module-import time:

    merge_drive.py, verify_invariant.py, forgejo_audit.py,
    audit_branch_protection.py, flag_stale_prs.py,
    setup_auto_labels.py, setup_branch_protection.py,
    migrate_to_new_driver.py

  Defaults preserve canonical-repo behaviour. Setting the env vars
  retargets the entire stack at a fork:

    export FORGEJO_OWNER=drew
    export FORGEJO_REPO=cleveragents-core
    export FORGEJO_PUSH_WHITELIST=drew

  verify_invariant.py's cursor file is now partitioned by
  <owner>.<repo> so a fork run never clobbers the canonical cursor;
  the default canonical filename is preserved for backward
  compatibility.

tools/setup_test_fork.py (new)
  One-shot fork bootstrap. Identifies the calling user via GET /user,
  creates the fork via POST /repos/{upstream}/forks if absent, applies
  a minimum-viable branch-protection rule on the fork's master, and
  provisions the canonical auto/* label set at the repo scope (forks
  do not inherit org-level labels). Idempotent. --dry-run projects
  what would change without mutating Forgejo.

tools/duplicate_prs_to_fork.py (new)
  Duplicates the N most recent open upstream PRs into the personal
  fork: fetches each head ref, pushes it to the fork as
  tests/sentinel-<N>-<safe-branch>, opens a sentinel PR titled
  '[sentinel #<N>] <upstream-title>' with the auto/sentinel label,
  and assigns to the calling user (or --assignee). Blacklists every
  operational auto/* label so sentinels start automation-clean.
  Flags: --count, --pr <N> (repeatable), --fork-owner, --assignee,
  --workdir, --no-reset-master, --dry-run.

auto/sentinel label
  Added to setup_auto_labels.py so the fork bootstrap creates it.
  Identifies sentinel PRs in the fork.

tools/preflight_phase01.sh
  Banner now surfaces the Forgejo target (FORGEJO_OWNER/FORGEJO_REPO)
  and warns when targeting a non-canonical repo. The harness itself
  was already env-driven via the underlying Python tools.

47 new unit tests
  test_env_var_overrides.py (22 tests) — every tool's env-var
  resolution + canonical defaults + cursor-path partitioning.
  test_setup_test_fork.py (11 tests) — fork bootstrap pure-logic +
  API-mocked happy / dry-run / failure paths.
  test_duplicate_prs_to_fork.py (14 tests) — filter_labels,
  existing_fork_pr, duplicate_one (skip / dry-run / created /
  failed), run() orchestration with mocked git + API.

  tests/auto_agents/ count: 108 -> 155 passing.

AGENTS.md
  New 'Targeting a personal-fork test repo' how-to documenting the
  env-var override pattern and explaining why the existing
  supervisors are absolutely-isolated from a fork without any agent
  change. Operational tooling list extended with setup_test_fork.py
  and duplicate_prs_to_fork.py. auto/* label registry includes the
  new auto/sentinel row.

Verification
  pytest tests/auto_agents: 155/155 passing.
  setup_test_fork.py --dry-run against production: identifies
    drew/cleveragents-core fork (not yet created) and projects the
    full label set + branch-protection create plan.
  duplicate_prs_to_fork.py --dry-run: correctly aborts with a clear
    'fork does not exist; run setup_test_fork.py first' message.

CHANGELOG entry added under [Unreleased] / Added.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 11:37:55 -04:00