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
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
ci-gate:
name: CI Gate
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Validate JSON schemas
run: python -m json.tool schemas/issue-contract.schema.json >/dev/null && python -m json.tool schemas/pr-contract.schema.json >/dev/null

- name: Compile flow inspector
run: python -m py_compile scripts/flow/inspect_repo_flow.py
11 changes: 9 additions & 2 deletions docs/autonomous-flow-platform.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,18 @@ Use GitHub to enforce:
- required checks;
- no self-approval where possible;
- linear history or squash policy;
- auto-merge enabled by default for clean approved PRs;
- PR author enables auto-merge by default where GitHub allows it;
- merge queue if repo churn is high enough to keep PRs constantly behind.

For Axiom specifically, a GitHub merge queue may be worth testing if `BEHIND` churn keeps destroying flow.

Author-owned auto-merge contract:
- The PR author is responsible for enabling auto-merge as soon as the PR exists and the repo permits it.
- Standard command: `gh pr merge <number-or-url> --auto --squash` after the PR body is complete and the branch has been pushed.
- If GitHub refuses auto-merge because the repo/plan/ruleset does not allow it, the author records the exact reason in `## Merge Automation`.
- If auto-merge is unsafe because the PR is human-gated, release-sensitive, or intentionally paused, the author records that reason and Pheidon owns the explicit merge decision.
- A healthy PR with no `autoMergeRequest` and no recorded exception is flow drift; route it back to the author/owning lane for metadata repair, not to John.

## The flow state machine

The autonomous loop should run as a controller, not as independent workers polling randomly.
Expand All @@ -153,7 +160,7 @@ Controller loop:
5. Dispatch worker if autonomous.
6. Validate worker output.
7. Update GitHub-visible state.
8. Approve/arm auto-merge when gates are met.
8. Verify the PR author armed auto-merge where possible; otherwise record the unavailable/unsafe reason and route fallback merge approval.
9. Close loop and advance next item.

Key principle: every item must have either:
Expand Down
35 changes: 27 additions & 8 deletions scripts/flow/inspect_repo_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,24 @@ def owner_from_author(pr: dict[str, Any]) -> str:
return LANE_BY_AUTHOR.get(login, 'Pheidon')


def repair_actor_for_pr(owner_lane: str, checks: dict[str, Any], merge_state: str) -> tuple[str, str]:
failing = {str(name).lower() for name in checks.get('failing') or []}
metadata_markers = (
'validate pr description',
'ci gate',
'validate secrets',
'detect relevant changes',
'attest',
)
if merge_state in ('BEHIND', 'DIRTY'):
return 'Hephaestus', f'Restore mergeability from {merge_state.lower()} state, rerun required checks, and push with --force-with-lease when rebasing.'
if any(any(marker in name for marker in metadata_markers) for name in failing):
return 'Hephaestus', 'Repair PR metadata, generated governance surfaces, or CI gate wiring, then rerun required checks.'
if owner_lane in ('Pheidon', 'Apollo'):
return 'Daedalus', 'Reproduce failing checks, make the minimal substantive repair, validate, and push.'
return owner_lane, 'Reproduce failing checks, make the minimal repair, validate, and push.'


def classify_pr(pr: dict[str, Any]) -> dict[str, Any]:
checks = check_summary(pr.get('statusCheckRollup') or [])
labels = label_names(pr)
Expand All @@ -173,25 +191,26 @@ def classify_pr(pr: dict[str, Any]) -> dict[str, Any]:
if pr.get('autoMergeRequest'):
state = 'Auto-merge Armed'
next_action = 'Wait for GitHub auto-merge or merge queue completion.'
reasons.append('clean_approved_green')
else:
state = 'Ready for Approval'
next_actor = 'Pheidon'
next_action = 'Arm auto-merge after final gate check.'
reasons.append('clean_approved_green')
state = 'Needs Repair'
next_actor = owner_lane
next_action = 'PR author should enable auto-merge where GitHub allows it, or record the unavailable/unsafe reason under Merge Automation.'
reasons.extend(['clean_approved_green', 'auto_merge_missing'])
elif changes or review == 'CHANGES_REQUESTED':
state = 'Needs Repair'
next_actor = owner_lane if owner_lane not in ('Pheidon', 'Apollo') else 'Daedalus'
next_action = 'Convert requested changes into a repair task and push an updated PR head.'
reasons.append('changes_requested')
elif merge_state in ('DIRTY', 'BEHIND'):
state = 'Needs Repair'
next_actor = 'Hephaestus' if not checks['failing'] else owner_lane
next_action = f'Restore mergeability from {merge_state.lower()} state, validate, and push.'
next_actor, next_action = repair_actor_for_pr(owner_lane, checks, merge_state)
reasons.append(f'merge_state:{merge_state.lower()}')
if checks['failing']:
reasons.append('failing_checks')
elif checks['failing']:
state = 'Needs Repair'
next_actor = 'Ares' if review == 'APPROVED' else owner_lane
next_action = 'Reproduce failing checks, identify owner, repair or dispatch substantive fix.'
next_actor, next_action = repair_actor_for_pr(owner_lane, checks, merge_state)
reasons.append('failing_checks')
elif review == 'REVIEW_REQUIRED':
state = 'Needs Review'
Expand Down
Loading