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
66 changes: 62 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,55 @@ 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: Download materialized pull request merge tree
- 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
Comment on lines +463 to +468

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: Attempt-mismatch guard depends on preserved outputs

On a failed-jobs-only rerun the non-rerun producer keeps its original run_attempt output, so the guard compares the stale producer attempt against the new github.run_attempt and fails closed. The whole fail-closed contract hinges on GitHub preserving the earlier attempt's job outputs unchanged.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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 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 }}
shell: bash --noprofile --norc -e -o pipefail {0}
run: |
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
Comment on lines 499 to 501

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: Single artifact-ids download extracts directly to path

The consumer reads opencode-coverage-artifact/opencode-coverage-source.tar after a by-artifact-ids download, relying on single-ID extraction landing directly in path (not a per-artifact subdir). exact-artifact-sbom-attestation.yml:114-118 with root.iterdir() in scripts/ci/verify_exact_artifact_sbom_handoff.py:253 already depends on this, so the path resolves.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Expand Down Expand Up @@ -2447,6 +2504,7 @@ jobs:
ContextualWisdomLab/.github:tests/test_javascript_coverage_gate.py | \
ContextualWisdomLab/.github:tests/test_materialize_base_javascript_packages.py | \
ContextualWisdomLab/.github:tests/test_adversarial_evidence.py | \
ContextualWisdomLab/.github:tests/test_opencode_coverage_artifact_rerun_contract.py | \
ContextualWisdomLab/.github:tests/test_opencode_agent_contract.py | \
ContextualWisdomLab/.github:tests/test_opencode_model_pool_runner.py | \
ContextualWisdomLab/.github:tests/test_pr_head_replay_guard.py | \
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Bound OpenCode coverage source evidence to the current workflow attempt and
immutable upload artifact ID, failing closed with full-rerun guidance when a
failed-jobs-only rerun cannot obtain exact current-attempt evidence.
- Kept the extracted adversarial-evidence gate and its contract test inside the
central OpenCode review-process fallback scope, so changes to that trust
boundary retain the same exact-file eligibility and adversarial review path.
Expand Down
44 changes: 44 additions & 0 deletions docs/doctoring/opencode-coverage-artifact-reruns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# OpenCode coverage artifact reruns

## Customer-operability decision

OpenCode coverage now accepts only the immutable artifact identifier emitted by
`actions/upload-artifact` in the current GitHub Actions workflow attempt. The
producer exports both that identifier and `github.run_attempt`; the
credential-free coverage consumer checks that the producer attempt equals its
own attempt before downloading by ID.

This closes an evidence-integrity gap in failed-jobs-only reruns. GitHub can
reuse successful job outputs from the earlier attempt, while artifacts can be
expired or unavailable independently. A static artifact name therefore cannot
prove that coverage inspected the exact source produced for the current
attempt. Missing, malformed, expired, or prior-attempt evidence now fails
closed and tells an operator to use a full rerun or a fresh repository
dispatch. The existing one-day retention window remains bounded; no lookup or
fallback to an earlier attempt is permitted.

## Verification and rollback

Repository tests parse the complete producer and consumer job blocks. They
require the attempt-scoped artifact name, immutable upload output, current
attempt comparison, exact-ID download, credential-free consumer permissions,
and actionable recovery message. The central workflow's ordinary full quality
gate exercises the new contract at 100% statement, branch, and docstring
coverage.

Rollback is a normal revert of the workflow, contract test, and fallback-scope
entry. Operators must not restore name-based or prior-attempt artifact lookup;
until a replacement contract is available, a missing producer must continue to
fail closed.

## References

GitHub. (n.d.). *Re-running workflows and jobs*. Retrieved August 24, 2026,
from https://docs.github.com/en/actions/how-tos/manage-workflow-runs/re-run-workflows-and-jobs

GitHub. (n.d.). *REST API endpoints for GitHub Actions artifacts*. Retrieved
August 24, 2026, from
https://docs.github.com/en/rest/actions/artifacts?apiVersion=2026-03-10

GitHub. (n.d.). *upload-artifact* [Computer software]. Retrieved August 24,
2026, from https://github.com/actions/upload-artifact/blob/main/README.md
4 changes: 4 additions & 0 deletions tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,10 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
"ContextualWisdomLab/.github:tests/test_adversarial_evidence.py | \\"
in workflow
)
assert (
"ContextualWisdomLab/.github:tests/test_opencode_coverage_artifact_rerun_contract.py | \\"
in workflow
)
assert "tests/test_opencode_agent_contract.py | \\" in workflow
assert (
"ContextualWisdomLab/appguardrail:scripts/ci/collect_org_security_failures.py"
Expand Down
187 changes: 187 additions & 0 deletions tests/test_opencode_coverage_artifact_rerun_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
"""Contracts for rerun-safe OpenCode coverage artifact handoff."""

from pathlib import Path


WORKFLOW_PATH = Path(".github/workflows/opencode-review-dispatch.yml")
TEMPORARY_REPAIR_GLOBS = (
".github/opencode-attempt-scoped-coverage-artifact*.trigger",
".github/pr812*.trigger",
".github/workflows/*opencode*artifact*materializ*.yml",
".github/workflows/*opencode*artifact*repair*.yml",
".github/workflows/pr812-finalize*.yml",
"scripts/ci/*opencode*artifact*patch*.py",
)


def _workflow_text() -> str:
"""Return the protected OpenCode repository-dispatch workflow source."""
return WORKFLOW_PATH.read_text(encoding="utf-8")


def _job_block(workflow: str, job_name: str, next_job_name: str) -> str:
"""Return one top-level workflow job block bounded by the next job."""
start = workflow.index(f" {job_name}:\n")
end = workflow.index(f"\n {next_job_name}:\n", start)
return workflow[start:end]


def _step_block(job: str, step_name: str, next_step_name: str) -> str:
"""Return one workflow step bounded by the following named step."""
start = job.index(f" - name: {step_name}\n")
end = job.index(f"\n - name: {next_step_name}\n", start)
return job[start:end]


def test_coverage_source_artifact_is_attempt_scoped_and_downloaded_by_id() -> None:
"""Bind every producer attempt to its immutable uploaded artifact ID."""
workflow = _workflow_text()
source_job = _job_block(workflow, "coverage-source-tree", "coverage-evidence")
evidence_job = _job_block(workflow, "coverage-evidence", "opencode-review-target")

assert (
"coverage_source_artifact_id: "
"${{ steps.coverage_source_upload.outputs.artifact-id }}"
in source_job
)
assert "id: coverage_source_upload" in source_job
assert "name: opencode-coverage-source-${{ github.run_attempt }}" in source_job
assert "retention-days: 1" in source_job

identity = _step_block(
evidence_job,
"Verify coverage source identity for current workflow attempt",
"Download current-attempt materialized pull request merge tree",
)
download = _step_block(
evidence_job,
"Download current-attempt materialized pull request merge tree",
"Report missing current-attempt coverage source",
)
assert "id: coverage_source_identity" in identity
assert (
"COVERAGE_SOURCE_ARTIFACT_ID: "
"${{ needs.coverage-source-tree.outputs.coverage_source_artifact_id }}"
in identity
)
assert '[[ "$COVERAGE_SOURCE_ARTIFACT_ID" =~ ^[1-9][0-9]*$ ]]' in identity
assert "artifact_id=$COVERAGE_SOURCE_ARTIFACT_ID" in identity
assert (
"artifact-ids: ${{ steps.coverage_source_identity.outputs.artifact_id }}"
in download
)
assert (
"artifact-ids: "
"${{ needs.coverage-source-tree.outputs.coverage_source_artifact_id }}"
not in download
)
assert "name: opencode-coverage-source\n" not in download


def test_coverage_source_requires_current_producer_attempt() -> None:
"""Reject reused producer output when a selective rerun advances the attempt."""
workflow = _workflow_text()
source_job = _job_block(workflow, "coverage-source-tree", "coverage-evidence")
evidence_job = _job_block(workflow, "coverage-evidence", "opencode-review-target")
identity = _step_block(
evidence_job,
"Verify coverage source identity for current workflow attempt",
"Download current-attempt materialized pull request merge tree",
)

assert (
"coverage_source_run_attempt: "
"${{ steps.coverage_source_attempt.outputs.run_attempt }}"
in source_job
)
assert "id: coverage_source_attempt" in source_job
assert "GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}" in source_job
assert "run_attempt=%s" in source_job

assert (
"COVERAGE_SOURCE_RUN_ATTEMPT: "
"${{ needs.coverage-source-tree.outputs.coverage_source_run_attempt }}"
in identity
)
assert "CURRENT_RUN_ATTEMPT: ${{ github.run_attempt }}" in identity
assert '[ "$COVERAGE_SOURCE_RUN_ATTEMPT" != "$CURRENT_RUN_ATTEMPT" ]' in identity
assert "failed-jobs-only reruns cannot reuse prior-attempt source evidence" in identity
assert "full rerun or a fresh repository dispatch" in identity

guard_index = evidence_job.index(
"- name: Verify coverage source identity for current workflow attempt"
)
download_index = evidence_job.index(
"- name: Download current-attempt materialized pull request merge tree"
)
assert guard_index < download_index


def test_missing_or_expired_artifact_fails_with_bounded_recovery_guidance() -> None:
"""Keep fail-closed recovery reachable after producer or download failures."""
workflow = _workflow_text()
evidence_job = _job_block(workflow, "coverage-evidence", "opencode-review-target")
producer_failure = _step_block(
evidence_job,
"Report coverage source materialization failure",
"Verify coverage source identity for current workflow attempt",
)
identity = _step_block(
evidence_job,
"Verify coverage source identity for current workflow attempt",
"Download current-attempt materialized pull request merge tree",
)
download = _step_block(
evidence_job,
"Download current-attempt materialized pull request merge tree",
"Report missing current-attempt coverage source",
)
recovery = _step_block(
evidence_job,
"Report missing current-attempt coverage source",
"Prepare pull request merge tree for coverage measurement",
)

assert "if: needs.coverage-source-tree.result != 'success'" in producer_failure
assert "exit 1" not in producer_failure
assert "id: coverage_source_identity" in identity
assert "if: always()" in identity
assert "continue-on-error: true" in identity
assert "id: coverage_source_download" in download
assert "continue-on-error: true" in download
assert "needs.coverage-source-tree.result == 'success'" in download
assert "steps.coverage_source_identity.outcome == 'success'" in download
assert "if: always() && (" in recovery
assert "needs.coverage-source-tree.result != 'success'" in recovery
assert "steps.coverage_source_identity.outcome != 'success'" in recovery
assert "steps.coverage_source_download.outcome != 'success'" in recovery
assert "failed-jobs-only rerun" in recovery
assert "full rerun or a fresh repository dispatch" in recovery
assert "GITHUB_RUN_ATTEMPT" in recovery
assert "exit 1" in recovery
assert "list-artifacts" not in identity + download + recovery


def test_coverage_consumer_remains_credential_free() -> None:
"""Keep repository and OIDC credentials outside the untrusted-test job."""
workflow = _workflow_text()
evidence_job = _job_block(workflow, "coverage-evidence", "opencode-review-target")
permissions = evidence_job.split(" outputs:\n", 1)[0]

assert "actions: read" in permissions
assert "contents:" not in permissions
assert "id-token:" not in permissions
assert "secrets." not in evidence_job
assert "GH_TOKEN:" not in evidence_job


def test_temporary_branch_writers_are_absent_from_final_tree() -> None:
"""Reject versioned or renamed materializers and branch finalizers."""
unexpected = sorted(
{
str(path)
for pattern in TEMPORARY_REPAIR_GLOBS
for path in Path(".").glob(pattern)
}
)
assert unexpected == []
2 changes: 1 addition & 1 deletion tests/test_pr_review_autofix_nvidia_nim_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md")
CHANGELOG = Path("CHANGELOG.md")
REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml")
REVIEW_DISPATCH_BLOB_SHA = "13d47026c7a25b33f6e29efb967a81a1835c2b4c"
REVIEW_DISPATCH_BLOB_SHA = "f6d15b78004cd0e2fa0c8c59a05fc834fd91b79a"


def _workflow_text(path: Path) -> str:
Expand Down
Loading