-
Notifications
You must be signed in to change notification settings - Fork 1
feat(seidroid-review): widen the trigger to every comment event, and let a caller name it #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,38 @@ | ||
| name: Workflow tests | ||
| # The shell and jq inside seidroid-review.yml, run against a gh stub. Nothing here | ||
| # reaches the GitHub API, so this needs no token and no permissions. | ||
| # The shell and jq inside seidroid-review.yml, run against a gh stub, and its job | ||
| # conditions evaluated against synthetic payloads. Nothing here reaches the GitHub | ||
| # API, so this needs no token and no permissions. | ||
| # | ||
| # Two steps, one harness. Placement records which thread each posted comment | ||
| # replaced and the resolve step closes on that record, so the pair is the behaviour | ||
| # worth testing rather than either half. | ||
| # Three jobs, so each check in the list names the step it covers. A job that ran two | ||
| # harnesses would report one name for two things, and one check would go red for the | ||
| # other's failure. | ||
| # | ||
| # `Place findings and resolve threads` covers a pair of steps rather than either half: | ||
| # placement records which thread each posted comment replaced, and the resolve step | ||
| # closes on that record. `The reaction steps` covers which of the three runs in which | ||
| # job state, and what each leaves on the trigger comment. `Guard the request` covers | ||
| # the admission path, from the event the workflow accepts to the collection the | ||
| # acknowledgement reaches. | ||
| on: | ||
| pull_request: | ||
| paths: | ||
| - '.github/workflows/seidroid-review.yml' | ||
| # run-guard.sh asserts what ai-assistant.yml claims of the same comment, so an | ||
| # edit there can break an invariant stated here. Without this the break lands | ||
| # on the next unrelated pull request that touches the file above, pointing at | ||
| # the wrong change. | ||
| - '.github/workflows/ai-assistant.yml' | ||
| - '.github/workflows/workflow-test-self.yml' | ||
| - 'test/seidroid-review/**' | ||
| push: | ||
| branches: [ main ] | ||
| paths: | ||
| - '.github/workflows/seidroid-review.yml' | ||
| # run-guard.sh asserts what ai-assistant.yml claims of the same comment, so an | ||
| # edit there can break an invariant stated here. Without this the break lands | ||
| # on the next unrelated pull request that touches the file above, pointing at | ||
| # the wrong change. | ||
| - '.github/workflows/ai-assistant.yml' | ||
| - '.github/workflows/workflow-test-self.yml' | ||
| - 'test/seidroid-review/**' | ||
| permissions: | ||
|
|
@@ -35,9 +53,6 @@ jobs: | |
| - name: Run the placement and resolve harness | ||
| run: test/seidroid-review/run.sh | ||
|
|
||
| # Its own job, so each check in the list names the step it covers. A job that runs | ||
| # both harnesses would report one name for two things, and the placement check would | ||
| # go red for a reaction. | ||
| reactions: | ||
| name: The reaction steps | ||
| runs-on: ubuntu-latest | ||
|
|
@@ -56,3 +71,18 @@ jobs: | |
| # lives and which the shell harness above cannot see. | ||
| - name: Check the step conditions | ||
| run: python3 test/seidroid-review/conditions.py .github/workflows/seidroid-review.yml | ||
|
|
||
| guard-admission: | ||
| name: Guard the request | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v7 | ||
| - name: Set up Python | ||
| uses: actions/setup-python@v7 | ||
| with: | ||
| python-version: '3.x' | ||
| - name: Install the YAML reader | ||
| run: python3 -m pip install --quiet pyyaml | ||
| - name: Run the guard harness | ||
| run: test/seidroid-review/run-guard.sh | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Group 16 of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| #!/usr/bin/env bash | ||
| # gh stub for the guard steps: logs every call, serves the answer the case chose, | ||
| # and runs the step's own --jq filter over it. | ||
| # | ||
| # A failed read prints nothing and exits non-zero, which is the shape `Admit the | ||
| # request` is written against: it captures stdout, so an empty capture is what | ||
| # tells the fork check and the once-per-PR gate that nobody answered. The | ||
| # placement stub beside this one files an error body instead, because that step | ||
| # reads one. | ||
| # | ||
| # Five reads reach here. The team membership read and the once-per-PR reads are | ||
| # told apart by their path; the fork check and the label check share a path and | ||
| # are told apart by the filter each sends. | ||
| log() { printf '%s\n' "$*" >> "$STUB_LOG"; } | ||
|
|
||
| argv=("$@") | ||
| joined="$*" | ||
|
|
||
| filter="" | ||
| n=${#argv[@]} | ||
| for ((i = 0; i < n; i++)); do | ||
| [ "${argv[i]}" = "--jq" ] && filter="${argv[i + 1]}" | ||
| done | ||
|
|
||
| serve() { # fixture-json | ||
| printf '%s' "$1" | jq -r "$filter" | ||
| exit 0 | ||
| } | ||
|
|
||
| refuse() { # what-failed | ||
| log "CALL $1 FAILED" | ||
| echo "gh: the stub was told this read fails" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| # Ahead of the comment reads below: an issue comment's reactions path carries | ||
| # /issues/ and comments too, and the first matching case wins. | ||
| case "$joined" in | ||
| *"/reactions"*) | ||
| method=GET | ||
| for ((i = 0; i < n; i++)); do | ||
| [ "${argv[i]}" = "-X" ] && method="${argv[i + 1]}" | ||
| done | ||
| for a in "${argv[@]}"; do case "$a" in repos/*/reactions*) url="$a" ;; esac; done | ||
| log "CALL reaction $method ${url:-none}" | ||
| [ "${STUB_REACTIONS:-none}" = "FAIL" ] && refuse reaction | ||
| case "$method" in | ||
| GET) | ||
| case "${STUB_REACTIONS:-none}" in | ||
| eyes) serve '[{"id":1,"content":"eyes","user":{"login":"github-actions[bot]"}}]' ;; | ||
| *) serve '[]' ;; | ||
| esac | ||
| ;; | ||
| *) exit 0 ;; | ||
| esac | ||
| ;; | ||
| esac | ||
|
|
||
| case "$joined" in | ||
| *"/teams/"*"/memberships/"*) | ||
| for a in "${argv[@]}"; do case "$a" in */memberships/*) who="${a##*/}" ;; esac; done | ||
| log "CALL membership $who" | ||
| [ "${STUB_TEAM:-active}" = "FAIL" ] && refuse membership | ||
| serve "{\"state\":\"${STUB_TEAM:-active}\"}" | ||
| ;; | ||
| esac | ||
|
|
||
| case "$joined" in | ||
| *"/pulls/"*"/reviews"*) | ||
| log "CALL reviews" | ||
| [ "${STUB_REVIEWS:-none}" = "FAIL" ] && refuse reviews | ||
| case "${STUB_REVIEWS:-none}" in | ||
| blocked) serve "[{\"id\":11,\"state\":\"CHANGES_REQUESTED\",\"body\":\"${VERDICT_MARKER}a block\"}]" ;; | ||
| *) serve '[]' ;; | ||
| esac | ||
| ;; | ||
| esac | ||
|
|
||
| case "$joined" in | ||
| *"/issues/"*"/comments"*) | ||
| log "CALL comments" | ||
| [ "${STUB_COMMENTS:-none}" = "FAIL" ] && refuse comments | ||
| case "${STUB_COMMENTS:-none}" in | ||
| verdict) serve "[{\"id\":22,\"user\":{\"type\":\"Bot\"},\"body\":\"${VERDICT_MARKER}a verdict\"}]" ;; | ||
| *) serve '[]' ;; | ||
| esac | ||
| ;; | ||
| esac | ||
|
|
||
| case "$joined" in | ||
| *"/pulls/"*) | ||
| # The fork check names head.repo.id in its filter; the label check names labels. | ||
| case "$filter" in | ||
| *head.repo.id*) | ||
| log "CALL origin" | ||
| [ "${STUB_ORIGIN:-same}" = "FAIL" ] && refuse origin | ||
| case "${STUB_ORIGIN:-same}" in | ||
| same) serve '{"head":{"repo":{"id":1}},"base":{"repo":{"id":1}}}' ;; | ||
| fork) serve '{"head":{"repo":{"id":2}},"base":{"repo":{"id":1}}}' ;; | ||
| null) serve '{"head":{"repo":null},"base":{"repo":{"id":1}}}' ;; | ||
| esac | ||
| ;; | ||
| *labels*) | ||
| log "CALL labels" | ||
| [ "${STUB_LABELS:-}" = "FAIL" ] && refuse labels | ||
| names="$(jq -nc --arg list "${STUB_LABELS:-}" \ | ||
| 'if $list == "" then [] else ($list | split(",") | map({name: .})) end')" | ||
| serve "{\"labels\":$names}" | ||
| ;; | ||
| esac | ||
| ;; | ||
| esac | ||
|
|
||
| log "CALL unhandled $joined" | ||
| exit 1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,11 @@ | ||
| import sys, yaml | ||
| path, step, out = sys.argv[1], sys.argv[2], sys.argv[3] | ||
| key = sys.argv[4] if len(sys.argv) > 4 else "FINDING_MARKER" | ||
| d = yaml.safe_load(open(path, encoding="utf-8")) | ||
| for job in d["jobs"].values(): | ||
| for s in job.get("steps", []): | ||
| if s.get("name") == step: | ||
| if s.get("name") == step or s.get("id") == step: | ||
| open(out, "w", encoding="utf-8").write(s["run"]) | ||
| print(d["env"]["FINDING_MARKER"]) | ||
| print(d["env"][key]) | ||
| sys.exit(0) | ||
| sys.exit("step not found: " + step) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[suggestion]
run-guard.shgroup 16 asserts a cross-file invariant against.github/workflows/ai-assistant.yml— that itstrigger-phrasedefault is still@seidroid, and what itsreplycondition claims of each body. Butai-assistant.ymlis not in eitherpaths:filter above, so editing that file runs none of this. The invariant can break silently and then surface as a redGuard the requestcheck on the next unrelatedseidroid-review.ymlchange, pointing at the wrong PR.Adding
'.github/workflows/ai-assistant.yml'to bothpaths:lists makes the check fire where the breakage happens.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Taken —
ai-assistant.ymlis in bothpaths:lists.I audited every file the three harnesses read, as you asked. It was the only one outside the filter:
run.shandreactions.shreadseidroid-review.ymlalone, andconditions.pytakes its target from the CI command line, which names the same watched file. So no other cross-file assertion has this shape.