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

# Graduated gate relaxation for the nightly knowledge refresh PRs: when the
# repo variable KNOWLEDGE_AUTO_MERGE is "on", bot PRs whose diff is confined to
# regenerable projections (okf/generated, okf/observed, exports, reports) and
# whose quality report does not regress are queued for auto-merge. The actual
# gate stays branch protection: `gh pr merge --auto` merges only after every
# required check (validate: ruff/mypy/pytest/validate/quality/export/eval/
# ledger/lifecycle/scan-secrets) succeeds. Anything touching okf/curated,
# ledger/, src/, tests/, evals/, schema/, or .github/ never auto-merges —
# curated knowledge and the production insight stream stay human-reviewed.
#
# KNOWLEDGE_AUTO_MERGE: off (default) | dry_run (comment only) | on
# Runbook: network-operations/docs/runbooks/knowledge-auto-merge.md

on:
pull_request:
types: [opened, synchronize, reopened]

permissions:
contents: write
pull-requests: write

jobs:
auto-merge:
runs-on: ubuntu-latest
if: >
(vars.KNOWLEDGE_AUTO_MERGE == 'on' || vars.KNOWLEDGE_AUTO_MERGE == 'dry_run') &&
(startsWith(github.event.pull_request.head.ref, 'bot/knowledge-loop') ||
startsWith(github.event.pull_request.head.ref, 'bot/knowledge-refresh')) &&
endsWith(github.event.pull_request.user.login, '[bot]')
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
MODE: ${{ vars.KNOWLEDGE_AUTO_MERGE }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0

- name: Enforce regenerable-path confinement
run: |
set -euo pipefail
# Both sides of a rename must satisfy the allowlist: a refresh that
# renames okf/curated/... or ledger/... INTO an allowed path would
# otherwise delete human-reviewed content unreviewed.
gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" \
--paginate --jq '.[] | .filename, (.previous_filename // empty)' > changed-files.txt
echo "Changed files:"
cat changed-files.txt
blocked=0
while IFS= read -r path; do
case "$path" in
okf/generated/*|okf/observed/*|exports/*|reports/*) ;;
*)
echo "::error::'$path' is outside the auto-merge allowlist"
blocked=1
;;
esac
done < changed-files.txt
if [ "$blocked" -ne 0 ]; then
echo "Diff touches human-reviewed paths; leaving the PR for review." >> "$GITHUB_STEP_SUMMARY"
exit 1
fi

- name: Enforce quality non-regression
run: |
set -euo pipefail
git show "${BASE_SHA}:reports/coverage.json" > base-coverage.json
python3 - <<'PY'
import json, sys

base = json.load(open("base-coverage.json"))
head = json.load(open("reports/coverage.json"))
failures = []
if int(head.get("critical_count", 1)) != 0:
failures.append(f"head critical_count={head.get('critical_count')} (must be 0)")
if int(head.get("warning_count", 0)) > int(base.get("warning_count", 0)):
Comment on lines +75 to +80

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 Recompute coverage before trusting warning counts

In .github/workflows/auto-merge.yml, the non-regression gate compares the PR's checked-in reports/coverage.json. The required quality --check only marks reports stale when critical_count or concept_count differ (quality.py:211), so a bot refresh with a stale or incorrect warning_count can bypass this warning-regression guard while changing only allowlisted generated/report paths. Recompute coverage in this job, or make quality --check validate warning_count, before queuing auto-merge.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7ff9865: quality --check now treats a differing warning_count as stale reports (alongside critical_count/concept_count), so the required check itself guarantees the coverage.json the auto-merge gate compares is current — a bot refresh with a stale warning_count fails the required checks before the gate matters.

failures.append(
f"warnings regressed {base.get('warning_count')} -> {head.get('warning_count')}"
)
floor = 0.8 * int(base.get("concept_count", 0))
if int(head.get("concept_count", 0)) < floor:
failures.append(
f"concept_count {head.get('concept_count')} fell below 80% of base "
f"{base.get('concept_count')} (mass-deletion guard)"
)
if failures:
for failure in failures:
print(f"::error::{failure}")
sys.exit(1)
print("quality non-regression ok")
PY

- name: Dry-run comment
if: env.MODE == 'dry_run'
run: |
gh pr comment "$PR_NUMBER" --body \
"auto-merge dry-run: this PR satisfies the path-confinement and quality non-regression guards and **would auto-merge** once KNOWLEDGE_AUTO_MERGE=on (required checks still gate the merge)."

- name: Queue auto-merge behind required checks
if: env.MODE == 'on'
run: |
gh pr merge "$PR_NUMBER" --auto --squash
gh pr comment "$PR_NUMBER" --body \
"auto-merge queued: path confinement + quality non-regression passed; GitHub merges automatically once all required checks succeed."
64 changes: 64 additions & 0 deletions .github/workflows/pr-agent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
name: pr-agent

# Advisory AI PR review (same loop as network-operations/noc-agent). Runs on
# the UNPRIVILEGED `hyrule-public-pr` runner only, with read + PR/issue-comment
# permissions and our own OpenRouter key. Never deploys, never writes code,
# never auto-merges. Config (model/fallback/instructions): .pr_agent.toml.
# Design: network-operations/docs/ci/pr-agent.md.
#
# Org prerequisites (one-time, done 2026-07-10): repo is in the public-pr
# runner group and in OPENROUTER_API_KEY's selected repositories.

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
issue_comment:
types: [created]

permissions:
contents: read
pull-requests: write
issues: write

# Group by event name too: the push-triggered review posts comments, and each
# comment fires an issue_comment run that would otherwise share this group and
# cancel the in-flight check run ("higher priority waiting request",
# network-operations#408). Same-event storms still dedupe.
concurrency:
group: pr-agent-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: true

jobs:
pr-agent:
# Public-fork policy: auto-review ONLY for same-repo (internal) PRs, and
# slash commands ONLY from trusted authors. This keeps OPENROUTER_API_KEY
# off untrusted fork PRs and stops anyone from burning the OpenRouter
# budget via /ask, /improve, etc.
if: >
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association))
runs-on: [self-hosted, linux, x64, hyrule-public-pr]
timeout-minutes: 12
steps:
- name: PR-Agent
uses: The-PR-Agent/pr-agent@8e4d32e5497defd43c023a404f73560c62728961 # v0.39.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# PR-Agent reads the OpenRouter key from openrouter__key; litellm also
# honours OPENROUTER_API_KEY. Set both from the org secret.
OPENROUTER__KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
# Model pin via dynaconf env (SECTION__KEY). .pr_agent.toml is only
# honoured from the DEFAULT branch (no checkout step in the action),
# so until this PR merges the toml is invisible and PR-Agent would
# fall back to its packaged gpt-5.5 default. Keep the env pin even
# after the toml lands (belt and braces; custom models require
# custom_model_max_tokens).
CONFIG__MODEL: openrouter/deepseek/deepseek-v4-flash
CONFIG__FALLBACK_MODELS: '["openrouter/minimax/minimax-m2.7"]'
CONFIG__CUSTOM_MODEL_MAX_TOKENS: "128000"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ __pycache__/
.ruff_cache/
.mypy_cache/
.DS_Store

# Obsidian audit vault: only the committed minimal config, no workspace state.
okf/.obsidian/*
!okf/.obsidian/app.json
!okf/.obsidian/graph.json
52 changes: 52 additions & 0 deletions .pr_agent.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# PR-Agent (advisory) configuration for AS215932/knowledge.
# Replaces hosted Sourcery. Runs only on the unprivileged hyrule-public-pr
# runner with read/comment-only perms, on our OpenRouter key.
# Design: network-operations/docs/ci/pr-agent.md.

[config]
git_provider = "github"
# deepseek-v4-flash / minimax-m2.7 are custom models -> need custom_model_max_tokens.
model = "openrouter/deepseek/deepseek-v4-flash"
fallback_models = ["openrouter/minimax/minimax-m2.7"]
custom_model_max_tokens = 128000
temperature = 0.2
use_repo_settings_file = true
publish_output = true

[github_action_config]
auto_review = true
auto_describe = false
auto_improve = true

[pr_reviewer]
require_security_review = true
require_tests_review = true
require_score_review = true
require_estimate_effort_to_review = true
extra_instructions = """
This is the AS215932 knowledge repo: the OKF bundle (okf/generated regenerable,
okf/curated human-reviewed institutional knowledge, okf/observed evidence-only),
deterministic exports, the learning/insight ledgers, and the read-only
Knowledge MCP. HIGHEST RISK is corpus integrity and determinism. Flag, with
specifics:
- nondeterminism in exports (wall-clock, unsorted iteration, uncommitted
inputs) — `export --check` and the loop's volatile-churn detection depend on
byte-stable regeneration;
- hand edits to okf/generated/** (regenerated over) or exports/** without the
matching source change;
- curated/ledger content lacking citations/source_refs, or authority/
review_status escalation without review (A0-A5 tier integrity);
- sanitization violations: raw logs, secrets, packet captures, command output,
or live telemetry payloads entering ledger/insight rows (learning_ledger_v1
forbidden keys/classes);
- policy evaluator (knowledge-policy.yml) or redaction weakening —
deny-by-default must hold;
- auto-merge workflow guard weakening (path allowlist, quality non-regression,
bot/branch checks);
- unpinned GitHub Actions, broad GITHUB_TOKEN permissions, added
pull_request_target, or pull_request jobs on privileged runner labels.
Do not restate the diff; surface risk, determinism breaks, missing tests.
"""

[pr_description]
publish_description_as_comment = true
Loading