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
10 changes: 6 additions & 4 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ reference and its `harness-ref` input to the same immutable commit SHA.

The reviewer invokes [`setup-openshell`](../actions/setup-openshell/action.yml)
to install the pinned OpenShell CLI and wait for the local CI gateway. The
trusted [`scripts/pr-review.sh`](../../scripts/pr-review.sh) wrapper stages the
diff, creates the temporary workspace and providers, configures inference, and
renders the PR-specific policy before invoking the `harness` CLI. The CLI
[`scripts/pr-review-local.sh`](../../scripts/pr-review-local.sh) wrapper creates
the temporary workspace/providers and configures inference. It calls
[`pr-review.sh run`](../../scripts/pr-review.sh) and tears down its setup
afterward. The review script stages the diff in `prepare`, checks eligibility,
renders the PR-specific policy, invokes the CLI, and validates output. The CLI
composes the task and manages its sandbox lifecycle.

The CLI already supports a direct managed-gateway connection. Moving this
Expand All @@ -31,7 +33,7 @@ provider name does not by itself keep a short-lived GitHub token usable.
See [managed reviewer requirements](../../docs/ci.md#managed-reviewer-transition).

Once that contract is established, replace the job's local setup and temporary
provider bootstrap with the managed connection. Preserve the task's allowed
provider bootstrap with managed authentication and `pr-review.sh run`. Preserve the task's allowed
operations and equivalent OpenShell policy and provider boundaries.

Comments may be posted during agent execution. Artifacts retain diagnostics;
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-review-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ jobs:
token="$(gcloud auth print-access-token)"
echo "::add-mask::$token"
export GOOGLE_VERTEX_AI_TOKEN="$token"
bash scripts/pr-review.sh run
bash scripts/pr-review-local.sh
- uses: actions/upload-artifact@v7
if: always() && steps.prepare.outputs.eligible == 'true'
with:
Expand Down
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ The intended StackRox deployment connects repository workflows to a
platform-managed gateway. The CLI already supports local and direct managed
connections; the current reusable reviewer uses
[`setup-openshell`](.github/actions/setup-openshell/action.yml) and a
[trusted wrapper](scripts/pr-review.sh) to prepare a local CI gateway and
temporary workspace.
[local setup wrapper](scripts/pr-review-local.sh) to prepare a local CI gateway
and temporary workspace. [`pr-review.sh`](scripts/pr-review.sh) prepares and
runs the review against that target or an already-configured target.

## What an agent can do

Expand Down Expand Up @@ -117,7 +118,8 @@ data. The `ai-review` label is explicit opt-in. See
|---|---|
| [Reusable workflow](.github/workflows/pr-review-reusable.yml) | Trusted checkout, job permissions, App token, and setup/execution steps |
| [`setup-openshell`](.github/actions/setup-openshell/action.yml) | Invoke the installer for the pinned OpenShell CLI release and wait for gateway readiness |
| [`scripts/pr-review.sh`](scripts/pr-review.sh) | Stage the diff, create a temporary workspace and providers, configure inference, render the PR policy, and invoke the CLI |
| [`scripts/pr-review-local.sh`](scripts/pr-review-local.sh) | Create temporary workspace/providers, configure inference, run the review, and remove its setup resources |
| [`scripts/pr-review.sh`](scripts/pr-review.sh) | Stage the diff, check PR eligibility, render the PR policy, invoke the CLI, and validate the output |
| [`harness` CLI](runner/) | Compose the task and manage its sandbox lifecycle |

The current reviewer uses a local gateway on the CI runner. The CLI's direct
Expand Down Expand Up @@ -185,7 +187,8 @@ agent, collects declared output files, and deletes the sandbox.
| Consuming repository | Opt-in triggers, trusted task inputs, review criteria, and approval rules |
| [.github/workflows/](.github/workflows/) | Repository CI and reusable jobs with fixed permissions, trusted checkout, concurrency, and task selection |
| [.github/actions/setup-openshell/](.github/actions/setup-openshell/action.yml) | OpenShell installation and gateway readiness for the current local CI path |
| [scripts/pr-review.sh](scripts/pr-review.sh) | Trusted review preparation and temporary workspace/provider bootstrap |
| [scripts/pr-review.sh](scripts/pr-review.sh) | Trusted review preparation, execution, and output validation |
| [scripts/pr-review-local.sh](scripts/pr-review-local.sh) | Temporary workspace/provider bootstrap and teardown for local CI |
| [tasks/](tasks/) | Task instructions, policy, provider references, image selection, payloads, and outputs |
| [runner/](runner/) | Generic `plan`/`apply` composition and sandbox lifecycle |
| [images/](images/) | Reusable runtime toolchains |
Expand All @@ -208,10 +211,11 @@ durable workflow database, scheduler, release history, or rollback mechanism.
- GitHub Actions owns run state, labels, artifacts, concurrency, and approvals.
OpenShell and the platform own gateway runtime resources.

The current inference-route write is a compatibility bridge for isolated or
explicitly administered workspaces. Shared managed workspaces should have a
matching route provisioned by the platform so ordinary runs remain
reference-only. See [docs/ci.md](docs/ci.md) for the credential and setup contract.
The PR review task consumes `inference.local`; setup owns its configuration.
The local wrapper configures the route in its temporary workspace, while a
managed platform supplies the matching route before review execution. Other
workflow documents can still explicitly request inference reconciliation. See
[docs/ci.md](docs/ci.md) for the credential and setup contract.

## CLI

Expand Down
50 changes: 36 additions & 14 deletions docs/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ comments only; they do not approve, request changes, or merge.

The active reviewer runs OpenCode with Gemini 2.5 Pro through `inference.local`
and Google Vertex AI. The model is selected in
[`scripts/pr-review.sh`](../scripts/pr-review.sh) and
[`opencode-harness.yaml`](../tasks/github-pr-reviewer/workflow/opencode-harness.yaml).
[`scripts/pr-review-local.sh`](../scripts/pr-review-local.sh) and the agent
arguments in [`opencode-harness.yaml`](../tasks/github-pr-reviewer/workflow/opencode-harness.yaml).
The task consumes the existing inference route; it does not configure it.
Keep those selections aligned and verify model access with the CI identity
when changing them.

Expand Down Expand Up @@ -132,9 +133,23 @@ make cli
export REVIEW_REPOSITORY=stackrox/harness-openshell REVIEW_PR=123
export REVIEW_DIR="$PWD/review-artifacts-123"
bash scripts/pr-review.sh prepare
bash scripts/pr-review.sh run
bash scripts/pr-review-local.sh
```

The local wrapper needs a reachable local gateway and a repository-scoped
`GITHUB_TOKEN` for provider bootstrap, in addition to the Vertex variables.
It creates a fresh workspace, runs the review, and removes its setup resources.
A setup or teardown failure fails the command.

For an already-configured target, run `bash scripts/pr-review.sh run` after
preparation instead. Select a registered gateway/workspace with
`OPENSHELL_GATEWAY` and `OPENSHELL_WORKSPACE`, or use the existing
`target.registration` fields in the trusted task document for a direct
connection (see [workflow contract](#workflow-contract)). The review command
uses the selected target and only creates its task sandbox. It needs host `gh`
authentication for PR checks, while the platform supplies the `github-review`
provider with usable credentials and the Gemini 2.5 Pro inference route.

Unit tests use fake commands, not Vertex. The agent can already publish inline
comments directly through the allowed API endpoint. A structured findings
format and a separate publication stage remain deferred.
Expand All @@ -144,14 +159,15 @@ format and a separate publication stage remain deferred.
The [reusable workflow](../.github/workflows/pr-review-reusable.yml) invokes
[`setup-openshell`](../.github/actions/setup-openshell/action.yml), which installs
the pinned OpenShell CLI and waits for gateway readiness. This CI path uses a
local gateway. The trusted [`scripts/pr-review.sh`](../scripts/pr-review.sh)
wrapper creates a temporary workspace, registers `github-review` and
`vertex-review`, configures inference, renders the PR-specific policy, and
invokes the `harness` CLI. The wrapper removes the temporary providers and
workspace during cleanup.
local gateway. [`scripts/pr-review-local.sh`](../scripts/pr-review-local.sh)
creates a temporary workspace, registers `github-review` and `vertex-review`,
and configures inference. It calls [`pr-review.sh run`](../scripts/pr-review.sh),
then removes the providers, any profile it imported, and the workspace.

The CLI verifies provider references and manages sandbox execution. It does
not perform the wrapper's provider provisioning or handle provider credentials.
`pr-review.sh` handles PR checks, diff preparation, policy rendering, and output
validation. It invokes the existing `harness workflow apply` command with a
unique sandbox name. The CLI owns sandbox execution and deletion, including
normal cancellation; the local wrapper waits for it before tearing down setup.

## Managed reviewer transition

Expand All @@ -171,10 +187,16 @@ agreed managed integration contract:
- **Inference and policy:** a matching provider/model route and equivalent
policy enforcement, so ordinary task runs can use existing references.

Once these requirements are met, replace the reviewer's local gateway and
temporary provider setup with managed authentication and platform-owned
resources. Preserve the task inputs and allowed GitHub operations. This
transition does not require adding provider management to the `harness` CLI.
Once these requirements are met, replace `setup-openshell`, Google bootstrap,
and `pr-review-local.sh` in the job with managed authentication and
`bash scripts/pr-review.sh run`. Keep host GitHub authentication for preparation
and PR checks. The task inputs, review command, and allowed GitHub operations
stay the same. The reusable workflow currently exposes only the local CI path;
the review command provides the execution step for a future managed caller.

The POC deliberately fixes the task document, `github-review` provider name,
and Gemini 2.5 Pro model. Configure a managed target through the existing
workflow format and provide those resources before invoking it.

The following bootstrap examples describe the managed validation environment;
they are not evidence that the reusable reviewer has completed this transition.
Expand Down
2 changes: 1 addition & 1 deletion runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ runner packages themselves.
The same binary can connect to a developer's selected local gateway or directly
to a managed gateway. The current reusable reviewer still uses
[`setup-openshell`](../.github/actions/setup-openshell/action.yml) and the
[trusted review wrapper](../scripts/pr-review.sh) for its local CI path.
[local setup wrapper](../scripts/pr-review-local.sh) for its local CI path.
Moving that integration requires managed access and provider-lifecycle setup
around the CLI; see [the transition requirements](../docs/ci.md#managed-reviewer-transition).
66 changes: 66 additions & 0 deletions scripts/pr-review-local.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Temporary local CI resources around the existing PR review script.
set -euo pipefail
umask 077
cd "$(dirname "$0")/.."
: "${GOOGLE_VERTEX_AI_TOKEN:?set a short-lived Vertex token}" "${VERTEX_AI_PROJECT_ID:?set Vertex project}"
: "${GITHUB_TOKEN:?set the repository-scoped GitHub App token for bootstrap}"
gateway="${OPENSHELL_GATEWAY:-openshell}"
workspace="review-$(openssl rand -hex 12)"
created_workspace=false
created_vertex=false
created_github=false
imported_profile=false
review_pid=""
cleanup() {
local status=$?
trap - EXIT INT TERM
if [[ -n "$review_pid" ]]; then
kill -TERM "$review_pid" 2>/dev/null || true
wait "$review_pid" || true
fi
# Wait for the runner's sandbox cleanup before removing only our resources.
if $created_workspace; then
if $created_vertex; then
timeout 30s openshell provider delete --gateway "$gateway" --workspace "$workspace" vertex-review || status=1
fi
if $created_github; then
timeout 30s openshell provider delete --gateway "$gateway" --workspace "$workspace" github-review || status=1
fi
if $imported_profile; then
timeout 30s openshell provider profile delete --gateway "$gateway" --workspace "$workspace" github-review || status=1
fi
timeout 30s openshell workspace delete --gateway "$gateway" "$workspace" || status=1
fi
exit "$status"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

timeout 60s openshell workspace create --gateway "$gateway" --name "$workspace"
created_workspace=true
profiles="$(timeout 60s openshell provider list-profiles --gateway "$gateway" --workspace "$workspace" -o json)"
jq -e 'type == "array" and all(.[]; (.id | type) == "string")' <<< "$profiles" >/dev/null
if ! jq -e 'any(.[]; .id == "github-review")' <<< "$profiles" >/dev/null; then
timeout 60s openshell provider profile import --gateway "$gateway" --workspace "$workspace" \
--file tasks/github-pr-reviewer/openshell/providers/github-review.yaml
imported_profile=true
fi
timeout 60s openshell provider create --gateway "$gateway" --workspace "$workspace" \
--name vertex-review --type google-vertex-ai --from-existing \
--config "VERTEX_AI_PROJECT_ID=$VERTEX_AI_PROJECT_ID" --config "VERTEX_AI_REGION=${VERTEX_AI_REGION:-global}"
created_vertex=true
timeout 60s openshell provider create --gateway "$gateway" --workspace "$workspace" \
--name github-review --type github-review --credential GITHUB_TOKEN
created_github=true
timeout 60s openshell inference set --gateway "$gateway" --workspace "$workspace" \
--provider vertex-review --model gemini-2.5-pro
OPENSHELL_GATEWAY="$gateway" OPENSHELL_WORKSPACE="$workspace" bash scripts/pr-review.sh run &
review_pid=$!
set +e
wait "$review_pid"
status=$?
set -e
review_pid=""
exit "$status"
75 changes: 13 additions & 62 deletions scripts/pr-review.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Trusted host orchestration. PR content is data, never runner code.
# Review using an already-configured OpenShell target. PR content is data.
set -euo pipefail
umask 077
cd "$(dirname "$0")/.."
Expand All @@ -8,14 +8,8 @@ cd "$(dirname "$0")/.."
[[ "$REVIEW_DIR" == /* && "$REVIEW_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ && "$REVIEW_PR" =~ ^[1-9][0-9]*$ ]] || exit 1
mode="${1:?usage: pr-review.sh prepare|run}"
[[ "$mode" == prepare || "$mode" == run ]] || exit 1
gateway="${OPENSHELL_GATEWAY:-openshell}"
allow_draft_reviews="${ALLOW_DRAFT_REVIEWS:-false}"
workspace="rev-$RANDOM-$$"
max_diff_bytes=262144
created_workspace=false
imported_github_profile=false
created_vertex_provider=false
created_github_provider=false
apply_pid=""
head="${REVIEW_HEAD:-}"
base=""
Expand All @@ -38,38 +32,17 @@ write_summary() {

cleanup_runtime() {
trap - EXIT INT TERM
local cleanup_status=0
delete_sandbox() {
local output
if output=$(timeout 30s openshell sandbox delete --gateway "$gateway" --workspace "$workspace" ai-review 2>&1); then
return 0
fi
# A workflow with keep:false lets Harness delete the sandbox before this
# wrapper's best-effort cleanup runs. That is already the desired state.
if [[ "$output" == *"sandbox not found"* ]]; then
return 0
fi
printf '%s\n' "$output" >&2
return 1
}
# The runner owns sandbox deletion, including on normal cancellation.
if [[ -n "$apply_pid" ]]; then
# timeout can exit before its command child handles a forwarded signal.
# Signal the runner child first, then reap the timeout wrapper.
runner_pids="$(pgrep -P "$apply_pid" 2>/dev/null || true)"
for runner_pid in $runner_pids; do
kill -TERM "$runner_pid" 2>/dev/null || true
done
kill -TERM "$apply_pid" 2>/dev/null || true
wait "$apply_pid" || true
fi
if $created_workspace; then
delete_sandbox || cleanup_status=1
if $created_vertex_provider; then
timeout 30s openshell provider delete --gateway "$gateway" --workspace "$workspace" vertex-review || cleanup_status=1
fi
if $created_github_provider; then
timeout 30s openshell provider delete --gateway "$gateway" --workspace "$workspace" github-review || cleanup_status=1
fi
if $imported_github_profile; then
timeout 30s openshell provider profile delete --gateway "$gateway" --workspace "$workspace" github-review || cleanup_status=1
fi
timeout 30s openshell workspace delete --gateway "$gateway" "$workspace" || cleanup_status=1
fi
return "$cleanup_status"
}

finish() {
Expand Down Expand Up @@ -116,40 +89,20 @@ run_review() {
base="$(jq -er '.base | select(test("^[0-9a-f]{40}$"))' "$REVIEW_DIR/input.json")"
(cd "$REVIEW_DIR" && shasum -a 256 -c pr.diff.sha256 >/dev/null)
ensure_current
: "${GOOGLE_VERTEX_AI_TOKEN:?set a short-lived Vertex token}" "${VERTEX_AI_PROJECT_ID:?set Vertex project}"
: "${GITHUB_TOKEN:?set the workflow GitHub token for provider bootstrap}"

timeout 60s openshell workspace create --gateway "$gateway" --name "$workspace"
created_workspace=true
if ! profile_list="$(timeout 60s openshell provider list-profiles --gateway "$gateway" --workspace "$workspace" -o json)" ||
! jq -e 'any(.[]; .id == "github-review")' <<<"$profile_list" >/dev/null 2>&1; then
timeout 60s openshell provider profile import --gateway "$gateway" --workspace "$workspace" \
--file workloads/github-pr-reviewer/openshell/providers/github-review.yaml
imported_github_profile=true
fi
timeout 60s openshell provider create --gateway "$gateway" --workspace "$workspace" \
--name vertex-review --type google-vertex-ai --from-existing \
--config "VERTEX_AI_PROJECT_ID=$VERTEX_AI_PROJECT_ID" --config "VERTEX_AI_REGION=${VERTEX_AI_REGION:-global}"
created_vertex_provider=true
timeout 60s openshell provider create --gateway "$gateway" --workspace "$workspace" \
--name github-review --type github-review --credential GITHUB_TOKEN
created_github_provider=true
timeout 60s openshell inference set --gateway "$gateway" --workspace "$workspace" \
--provider vertex-review --model gemini-2.5-pro --no-verify

export REVIEW_DIFF="$REVIEW_DIR/pr.diff"
export REVIEW_POLICY="$REVIEW_DIR/review-policy.yaml"
export REVIEW_SKILL="${REVIEW_SKILL:-skills/pr-review/SKILL.md}"
export REVIEW_SKILL="${REVIEW_SKILL:-$PWD/tasks/github-pr-reviewer/workflow/skills/pr-review/SKILL.md}"
policy_template="${REVIEW_POLICY_TEMPLATE:-tasks/github-pr-reviewer/openshell/policy.yaml}"
sed \
-e "s|\${REVIEW_REPOSITORY}|$REVIEW_REPOSITORY|g" \
-e "s|\${REVIEW_PR}|$REVIEW_PR|g" \
"$policy_template" > "$REVIEW_POLICY"

sandbox_name="review-$(openssl rand -hex 12)"
(
ulimit -f 2048 # Bound raw diagnostic output as well as runtime.
exec timeout -s TERM -k 35s 8m ./harness workflow apply tasks/github-pr-reviewer/workflow/opencode-harness.yaml \
--gateway "$gateway" --workspace "$workspace" --result-file "$REVIEW_DIR/execution.json"
--name "$sandbox_name" --result-file "$REVIEW_DIR/execution.json"
) > "$REVIEW_DIR/agent.ndjson" 2> "$REVIEW_DIR/agent.stderr" &
apply_pid=$!
set +e
Expand All @@ -158,11 +111,9 @@ run_review() {
set -e
apply_pid=""

((apply_status == 0)) || return "$apply_status"
scripts/review/validate-agent-output.sh "$REVIEW_DIR"
if [[ -s "$REVIEW_DIR/execution.json" ]] && ! jq -e '.status == "succeeded" and .phase == "complete"' "$REVIEW_DIR/execution.json" >/dev/null; then
((apply_status != 0)) && return "$apply_status"
return 1
fi
jq -e '.status == "succeeded" and .phase == "complete"' "$REVIEW_DIR/execution.json" >/dev/null
ensure_current
jq -Rr 'fromjson? | select(.type == "text") | .part.text' \
"$REVIEW_DIR/agent.ndjson" > "$REVIEW_DIR/review.txt"
Expand Down
Loading
Loading