-
Notifications
You must be signed in to change notification settings - Fork 0
fix(opencode): scope coverage artifacts to workflow attempts #1278
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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: | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
| 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
Contributor
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. 📝 Info: Single artifact-ids download extracts directly to path The consumer reads Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
@@ -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 | \ | ||
|
|
||
| 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 |
| 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 == [] |
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.
📝 Info: Attempt-mismatch guard depends on preserved outputs
On a failed-jobs-only rerun the non-rerun producer keeps its original
run_attemptoutput, so the guard compares the stale producer attempt against the newgithub.run_attemptand fails closed. The whole fail-closed contract hinges on GitHub preserving the earlier attempt's job outputs unchanged.Was this helpful? React with 👍 or 👎 to provide feedback.