Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: OpenCode Coverage Artifact Rerun Quality CI

on:
pull_request:
branches: [main]
paths:
- ".github/workflows/opencode-review-dispatch.yml"
- ".github/workflows/opencode-coverage-artifact-rerun-quality-ci.yml"
- "tests/test_opencode_coverage_artifact_rerun_contract.py"
- "docs/doctoring/opencode-coverage-artifact-reruns.md"
- "CHANGELOG.md"
- "requirements-opencode-review-ci-hashes.txt"
- "pyproject.toml"
push:
branches: [main]
paths:
- ".github/workflows/opencode-review-dispatch.yml"
- ".github/workflows/opencode-coverage-artifact-rerun-quality-ci.yml"
- "tests/test_opencode_coverage_artifact_rerun_contract.py"
- "docs/doctoring/opencode-coverage-artifact-reruns.md"
- "CHANGELOG.md"
- "requirements-opencode-review-ci-hashes.txt"
- "pyproject.toml"

concurrency:
group: opencode-coverage-artifact-rerun-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
exact-head-contract:
name: Python 3.14 attempt-scoped artifact contract
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- name: Checkout exact source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Set up current stable Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
cache: pip
cache-dependency-path: requirements-opencode-review-ci-hashes.txt

- name: Install hash-locked quality tooling
run: python -m pip install --disable-pip-version-check --require-hashes -r requirements-opencode-review-ci-hashes.txt

- name: Run attempt-scoped artifact regression
run: python -m pytest tests/test_opencode_coverage_artifact_rerun_contract.py -q

- name: Enforce complete central test and branch coverage
run: |
set -euo pipefail
python -m coverage erase
python -m coverage run -m pytest tests -q
python -m coverage report --show-missing

- name: Enforce production docstring coverage
run: python -m interrogate scripts/ci

- name: Compile permanent contracts
run: python -m compileall -q scripts tests

- name: Reject uncommitted generated state
run: git diff --exit-code --check && test -z "$(git status --porcelain)"
64 changes: 60 additions & 4 deletions .github/workflows/opencode-review-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ jobs:
permissions:
contents: read
id-token: write
outputs:
coverage_source_artifact_id: ${{ steps.coverage_source_upload.outputs.artifact-id }}
coverage_source_run_attempt: ${{ steps.coverage_source_attempt.outputs.run_attempt }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
Expand Down Expand Up @@ -349,10 +352,23 @@ jobs:
git -C "$COVERAGE_SOURCE_WORKDIR" status --short
tar -cf "$COVERAGE_SOURCE_ARCHIVE" -C "$COVERAGE_SOURCE_WORKDIR" .

- name: Record coverage source workflow attempt
id: coverage_source_attempt
env:
GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}
shell: bash --noprofile --norc -e -o pipefail {0}
run: |
if ! [[ "$GITHUB_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]]; then
echo "::error::Coverage producer workflow attempt is not a positive integer."
exit 1
fi
printf 'run_attempt=%s\n' "$GITHUB_RUN_ATTEMPT" >>"$GITHUB_OUTPUT"

- name: Upload materialized pull request merge tree
id: coverage_source_upload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: opencode-coverage-source
name: opencode-coverage-source-${{ github.run_attempt }}
path: ${{ runner.temp }}/opencode-coverage-source.tar
if-no-files-found: error
retention-days: 1
Expand Down Expand Up @@ -431,14 +447,54 @@ jobs:
if: needs.coverage-source-tree.result != 'success'
run: |
echo "::error::Coverage source tree could not be materialized; see the coverage-source-tree job log for the exact target repository, base SHA, head SHA, and fetch or merge failure."
exit 1
# Continue to the unified current-attempt recovery gate for bounded fail-closed guidance.

- name: Verify coverage source identity for current workflow attempt
if: always()
id: coverage_source_identity
continue-on-error: true
env:
COVERAGE_SOURCE_ARTIFACT_ID: ${{ needs.coverage-source-tree.outputs.coverage_source_artifact_id }}
COVERAGE_SOURCE_RUN_ATTEMPT: ${{ needs.coverage-source-tree.outputs.coverage_source_run_attempt }}
CURRENT_RUN_ATTEMPT: ${{ github.run_attempt }}
shell: bash --noprofile --norc -e -o pipefail {0}
run: |
if ! [[ "$CURRENT_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]] || \
[ "$COVERAGE_SOURCE_RUN_ATTEMPT" != "$CURRENT_RUN_ATTEMPT" ]; then
echo "::error::Coverage source was not produced in current workflow attempt ${CURRENT_RUN_ATTEMPT:-missing}; producer attempt=${COVERAGE_SOURCE_RUN_ATTEMPT:-missing}."
echo "::error::Use a full rerun or a fresh repository dispatch; failed-jobs-only reruns cannot reuse prior-attempt source evidence."
exit 1
fi
if ! [[ "$COVERAGE_SOURCE_ARTIFACT_ID" =~ ^[1-9][0-9]*$ ]]; then
echo "::error::Coverage source artifact ID is missing or malformed for current workflow attempt."
echo "::error::Use a full rerun or a fresh repository dispatch so the producer publishes current-attempt evidence."
exit 1
fi
artifact_id=$COVERAGE_SOURCE_ARTIFACT_ID
printf 'artifact_id=%s\n' "$artifact_id" >>"$GITHUB_OUTPUT"

- name: Download materialized pull request merge tree
- name: Download current-attempt materialized pull request merge tree
if: >-
always()
&& needs.coverage-source-tree.result == 'success'
&& steps.coverage_source_identity.outcome == 'success'
id: coverage_source_download
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: opencode-coverage-source
artifact-ids: ${{ steps.coverage_source_identity.outputs.artifact_id }}
path: ${{ runner.temp }}/opencode-coverage-artifact

- name: Report missing current-attempt coverage source
if: always() && (needs.coverage-source-tree.result != 'success' || steps.coverage_source_identity.outcome != 'success' || steps.coverage_source_download.outcome != 'success')
env:
GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
echo "::error::Coverage source evidence is unavailable for workflow run attempt ${GITHUB_RUN_ATTEMPT}; a failed-jobs-only rerun cannot safely reconstruct or reuse source evidence from another attempt."
echo "::error::Use a full rerun or a fresh repository dispatch so coverage-source-tree uploads exact current-attempt evidence."
exit 1

- name: Prepare pull request merge tree for coverage measurement
env:
COVERAGE_SOURCE_ARCHIVE: ${{ runner.temp }}/opencode-coverage-artifact/opencode-coverage-source.tar
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Bound OpenCode coverage source evidence to a validated immutable artifact ID and producer-attested workflow attempt, retained one-day source evidence, and made selective reruns fail closed before download on missing, malformed, or prior-attempt identity with full-rerun or fresh-dispatch guidance.
- Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics.
- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed.
- Bound each review-agent invocation key to the wrapper's complete canonical payload, including the base branch and requesting actor; altered fields with a valid-format key now fail before durable-leader election or forwarding, and wrapper write permission is job-scoped.
Expand Down
95 changes: 95 additions & 0 deletions docs/doctoring/opencode-coverage-artifact-reruns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# OpenCode coverage artifact rerun contract

## Decision

The central OpenCode review workflow binds every materialized pull-request merge tree to one workflow-run attempt and one immutable GitHub Actions artifact identifier. The credential-free `coverage-evidence` job may consume only that exact artifact identifier. It never searches by a mutable artifact name and never falls back to an artifact produced by another run or attempt.

The producer also exports a step-recorded literal workflow attempt. Before download, the consumer verifies that this attempt equals its current `github.run_attempt` and that the immutable artifact ID is a positive decimal identifier. Artifact immutability selects one upload; attempt attestation proves that the producer executed in the current attempt.

The source artifact retains the existing one-day retention period. A failed-jobs-only rerun that does not rerun the successful producer is therefore expected to fail closed once that producer artifact expires. The operator response is a **full rerun or a fresh repository dispatch**, both of which rerun `coverage-source-tree` and create current-attempt evidence. Increasing retention or reusing prior-attempt source evidence is not an accepted repair.

## Incident

On August 7, 2026, failed-jobs-only rerun attempt 2 of OpenCode workflow run `31022108085` retried `coverage-evidence` for `ContextualWisdomLab/pg-llm-batch#53` without retrying the successful `coverage-source-tree` producer. The attempt-1 artifact `opencode-coverage-source` had a one-day retention period and was already expired. `actions/download-artifact` therefore returned `Artifact not found` before any current-head tests or docstring checks could run.

The product pull request was not the source of this failure. The failing boundary was the central producer/consumer lifecycle: a static name did not prove that the consumer received evidence uploaded by the current attempt.

## Contract

```mermaid
sequenceDiagram
participant D as Repository dispatch
participant V as validate-pr-metadata
participant P as coverage-source-tree
participant A as Immutable Actions artifact
participant C as coverage-evidence

D->>V: Exact repository, PR, base SHA, head SHA
V->>P: Validated current-head metadata
P->>P: Materialize exact merge tree
P->>A: Upload attempt-scoped name
A-->>P: artifact-id
P-->>C: Immutable artifact-id job output
C->>A: Download exact artifact-id
alt Artifact belongs to current producer attempt
A-->>C: Merge-tree archive
C->>C: Validate archive, sandbox tests, coverage, docstrings
else Producer was omitted or evidence expired
A-->>C: Download failure
C-->>D: Fail closed; require full rerun or fresh dispatch
end
```

The implementation must preserve all of the following properties:

- `coverage-source-tree` remains the only job with repository-read and OIDC credentials for target-repository materialization.
- `coverage-evidence` remains limited to `actions: read`; it receives no repository-content token, OIDC credential, model secret, or review-write credential.
- The upload name includes `github.run_attempt` for operator diagnostics and collision resistance.
- The upload step exports the immutable `artifact-id`; the consumer validates that it is a positive decimal identifier and passes only the validated step output to `download-artifact`.
- The producer exports its step-recorded run attempt; the consumer rejects empty or prior-attempt provenance before download.
- Retention remains one day to minimize retention of private source evidence.
- Missing current-attempt evidence produces a bounded diagnostic containing the run attempt and the required recovery action.
- Exact-head metadata validation, same-repository validation, merge-tree construction, archive-member validation, isolated execution, coverage, docstring, security, and approval gates remain unchanged.

## Rerun operations

| Operator action | Producer behavior | Consumer behavior | Accepted outcome |
|---|---|---|---|
| Fresh repository dispatch | Producer runs and uploads a new attempt-scoped artifact | Downloads the producer's immutable artifact ID | Accepted |
| Full workflow rerun | Producer reruns and uploads a new attempt-scoped artifact | Downloads the new immutable artifact ID | Accepted |
| Failed-jobs-only rerun while producer is omitted | Producer attempt marker or artifact ID is missing or belongs to an earlier attempt | Rejects identity before download | Expected failure |
| Attempt to reuse an earlier artifact by name | Current-attempt identity is not proven | Rejected by contract | Rejected |
| Increase retention to hide missing producer execution | Stale source remains available longer | Does not repair attempt identity | Rejected |

## Security and privacy rationale

Artifact immutability prevents later jobs from mutating a successfully uploaded archive, but immutability alone does not identify which workflow attempt produced the archive. The producer's exact `artifact-id` closes upload-selection ambiguity, while its step-recorded attempt closes execution-attempt ambiguity. The consumer validates both before download; attempt-qualified names remain diagnostic only.

The one-day retention period is intentionally short because the archive can contain proprietary or otherwise sensitive source code. Recovery must create fresh, exact-head evidence rather than preserve source archives for a longer period. No product test executes in the credentialed producer. No trusted follow-up consumes command files after untrusted coverage execution begins.

## Rollback

Rollback consists of reverting the attempt-scoped producer output and exact-ID consumer selection together. Reverting only one side leaves the workflow unable to exchange evidence. A rollback must preserve one-day retention, credential separation, and fail-closed behavior; it must not restore mutable-name fallback across attempts.

## Verification

The permanent regression suite must verify:

1. attempt-scoped artifact naming and immutable `artifact-id` producer output;
2. producer-attested attempt output and pre-download current-attempt equality;
3. positive-decimal artifact-ID validation and exact-ID download;
4. actionable failure for missing, malformed, or prior-attempt evidence;
5. one-day retention; and
6. absence of repository, OIDC, secret, and review-write credentials from `coverage-evidence`.

The complete repository test suite, Python compilation, production statement and branch coverage, public docstring gate, security and supply-chain checks, current-head review, independent approval, and protected merge remain required.

## References

GitHub. (2026a). *Downloading workflow artifacts*. GitHub Actions documentation. https://docs.github.com/en/actions/how-tos/manage-workflow-runs/download-workflow-artifacts

GitHub. (2026b). *Re-running workflows and jobs*. GitHub Actions documentation. https://docs.github.com/en/actions/how-tos/manage-workflow-runs/re-run-workflows-and-jobs

GitHub. (2026c). *actions/download-artifact* [Computer software]. GitHub. https://github.com/actions/download-artifact

GitHub. (2026d). *actions/upload-artifact* [Computer software]. GitHub. https://github.com/actions/upload-artifact
2 changes: 1 addition & 1 deletion scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" '
assert_file_contains "$workflow_file" "Materialize pull request merge tree for coverage measurement" "required OpenCode reviews measure coverage instead of approving skipped coverage evidence"
assert_file_contains "$workflow_file" "Exchange OpenCode app token for target repository coverage reads" "coverage source materialization can read private target repositories during central manual dispatch"
assert_file_contains "$workflow_file" "Upload materialized pull request merge tree" "coverage source materialization passes only a prepared merge tree artifact to the PR-head coverage job"
assert_file_contains "$workflow_file" "Download materialized pull request merge tree" "coverage evidence consumes the prepared merge tree artifact without target-repository credentials"
assert_file_contains "$workflow_file" "Download current-attempt materialized pull request merge tree" "coverage evidence consumes current-attempt prepared merge-tree evidence without target-repository credentials"
assert_file_contains "$workflow_file" "Report coverage source materialization failure" "coverage evidence logs source materialization failures as the coverage blocker"
local coverage_merge_tree_step
coverage_merge_tree_step="$(
Expand Down
Loading
Loading