Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6565efe
fix: keep cross-repo OpenCode evidence healthy
Aug 11, 2026
0a48367
test(opencode): require formal review before status skip
seonghobae Aug 12, 2026
e05e21f
fix(opencode): prove formal review before status skip
seonghobae Aug 12, 2026
a5e8534
fix: fail closed on provider scan evidence
seonghobae Aug 12, 2026
aaeb13f
test: align Strix outage contract
seonghobae Aug 12, 2026
d57398b
docs: align Strix outage gate contract
seonghobae Aug 12, 2026
a956dfc
docs(review): record cross-repo OpenCode evidence fail-closed
seonghobae Aug 13, 2026
b7a5eb2
ci: repair bounded Strix scope guidance
seonghobae Aug 14, 2026
83675e1
chore(ci): remove failed one-shot repair workflow
seonghobae Aug 14, 2026
6c6cefb
ci: correct Strix scope repair anchors
seonghobae Aug 14, 2026
a9c4e04
ci: fix Strix scope repair test source
seonghobae Aug 14, 2026
d007af3
ci: repair Strix scope guidance with literal test fixture
seonghobae Aug 14, 2026
7b89f93
fix(strix): orient bounded CI scope inside sandbox
opencode-agent[bot] Aug 14, 2026
44065b9
docs(strix): record bounded PR scope and CI recursion contract
seonghobae Aug 14, 2026
8c859a9
ci: repair nested requirements lock discovery
seonghobae Aug 14, 2026
a867124
ci: route lock-path repair to ubuntu-24.04
seonghobae Aug 14, 2026
3cac38d
ci: execute lock-path regression from repository root
seonghobae Aug 14, 2026
a6a7399
fix(ci): discover nested requirements lock paths
github-actions[bot] Aug 14, 2026
a55a6cf
docs(ci): record base requirements lock discovery contract
seonghobae Aug 14, 2026
1339c17
fix(ci): stop cancelled required checks from looking failed
cursoragent Aug 16, 2026
701ba6c
test(ci): pin merge scheduler to keep required queue scans
cursoragent Aug 16, 2026
34c1f09
fix: refresh cross-repo reviewer blob contract
seonghobae Aug 20, 2026
2c4c6c6
Reconcile current main for cross-repo fix
seonghobae Aug 21, 2026
e141833
fix(review): close cross-repository evidence review gaps
seonghobae Aug 21, 2026
4282c3c
test: tighten current-head review contracts
seonghobae Aug 21, 2026
48de4d5
Merge remote-tracking branch 'origin/main' into codex/pr939-review-fix
seonghobae Aug 21, 2026
0912b86
Merge remote-tracking branch 'origin/main' into codex/pr939-review-fix
seonghobae Aug 21, 2026
084e6d2
Merge branch 'main' into codex/fix-cross-repo-opencode-evidence
opencode-agent[bot] Aug 21, 2026
6213f1e
Merge main into cross-repo evidence repair
seonghobae Aug 21, 2026
6df0cb4
Merge branch 'main' into codex/fix-cross-repo-opencode-evidence
opencode-agent[bot] Aug 21, 2026
07b554d
Merge current main and refresh scheduler contracts
seonghobae Aug 21, 2026
2d267d4
test(scheduler): accept non-cancelling required scans
seonghobae Aug 21, 2026
4e3bfba
Merge branch 'main' into fix/cross-repo-opencode-evidence (#939)
seonghobae Sep 4, 2026
1bcd1cc
Merge origin/main into codex/fix-cross-repo-opencode-evidence
seonghobae Sep 5, 2026
0db9929
Merge branch 'main' into codex/fix-cross-repo-opencode-evidence
opencode-agent[bot] Sep 5, 2026
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
31 changes: 29 additions & 2 deletions .github/workflows/opencode-review-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7626,8 +7626,35 @@ jobs:
exit 1
fi
if [ "${GH_REPOSITORY:-}" != "${GITHUB_REPOSITORY:-}" ] &&
[ "${OPENCODE_STATUS_TOKEN_SOURCE:-}" = "github-token" ]; then
echo "::notice::OpenCode repository_dispatch status publication is unavailable because only the same-repository github.token can access cross-repository target ${GH_REPOSITORY}. The exact-head formal review remains authoritative; configure PR_REVIEW_MERGE_TOKEN or OPENCODE_APPROVE_TOKEN to publish the optional commit status."
{ [ "${OPENCODE_STATUS_TOKEN_SOURCE:-}" = "github-token" ] ||
[ "${OPENCODE_STATUS_TOKEN_SOURCE:-}" = "opencode-app" ]; }; then
formal_review_file="$(mktemp)"
cleanup_formal_review_evidence() {
rm -f "$formal_review_file"
}
trap cleanup_formal_review_evidence EXIT
if ! gh api "repos/${GH_REPOSITORY}/pulls/${PR_NUMBER}/reviews" --paginate --slurp |
jq 'flatten' >"$formal_review_file"; then
echo "::error::OpenCode repository_dispatch status publication could not prove an exact-head formal OpenCode review before skipping unavailable cross-repository commit-status publication."
exit 1
fi
if ! jq -e --arg head "$PR_HEAD_SHA" '
any(.[];
(.commit_id // "") == $head
and (
(.user.login // "") == "opencode-agent[bot]"
or (.user.login // "") == "opencode-agent"
)
and (
((.state // "") | ascii_upcase) == "APPROVED"
or ((.state // "") | ascii_upcase) == "CHANGES_REQUESTED"
)
)
' "$formal_review_file" >/dev/null; then
echo "::error::OpenCode repository_dispatch status publication could not prove an exact-head formal OpenCode review; refusing to hide a cross-repository review-evidence gap."
exit 1
fi
echo "::notice::OpenCode repository_dispatch status publication is unavailable because only the same-repository github.token can access cross-repository target ${GH_REPOSITORY}, and the OpenCode App token has no cross-repository commit-status permission in this deployment. A verified exact-head formal review remains authoritative; configure PR_REVIEW_MERGE_TOKEN or OPENCODE_APPROVE_TOKEN to publish the optional commit status."
exit 0
fi
Comment thread
seonghobae marked this conversation as resolved.

Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -982,8 +982,9 @@ jobs:
fi

# Preserve configuration failures (exit 2) and any unexpected exit
# code as hard failures — only the scan-failure code (1) can be an
# infrastructure/backend-unavailability outcome.
# code as hard failures. A scan-failure code (1), including provider
# or backend unavailability, is incomplete security evidence and
# remains a hard failure for the required check.
if [ "$strix_rc" -ne 1 ]; then
exit "$strix_rc"
fi
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,7 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Refreshed the read-only review-dispatch blob contract after the protected main workflow changed, keeping the cross-repository OpenCode evidence checks bound to the current canonical reviewer workflow.
- Prefer the job-scoped `github.token` when the central OpenCode dispatch
publishes a commit status back to the same `.github` repository. The job's
declared `statuses: write` permission now reaches the endpoint instead of an
Expand Down Expand Up @@ -1395,6 +1396,10 @@ Semantic Versioning where the repository publishes a release.
- Published substantive OpenCode LLM probes when they already carried an independent proof and exact source-line digest but omitted a duplicated `path:line` citation, so NVIDIA NIM / OpenCode review evidence is no longer discarded as `NO_CONCLUSION`.
- Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367).
- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context.
- Stopped cancelling in-flight required `scan-pr-queue` runs and stopped serializing the OpenCode required-workflow stub jobs, so a later same-head success is not hidden behind a cancelled or queued required check.
- Required an exact-head formal OpenCode review before skipping unavailable cross-repository commit-status publication, and kept Strix failed when provider evidence is incomplete instead of neutralizing an outage into a pass.
- Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367).

- 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
59 changes: 59 additions & 0 deletions docs/doctoring/base-requirements-lock-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Base requirements lock discovery contract

## Purpose

This doctoring record defines how the central review and coverage workflows discover hash-pinned Python requirement locks from an authenticated pull-request base commit. It records the nested-path regression repaired in pull request #939 and preserves the security boundary already developed in pull request #785.

## Incident

The materializer intentionally recognizes two candidate forms:

- conventional file names such as `requirements.txt`, `requirements-dev.txt`, and `requirements.lock`; and
- direct `.txt` children of any directory named `requirements`, such as `requirements/ci.txt` and `service/requirements/package.txt`.

The path predicate implemented both forms, but `base_hash_locks()` still called the basename-only predicate. As a result, a direct child such as `requirements/ci.txt` was rejected before its authenticated base blob and hash-pinned content could be evaluated. The implementation advertised path-aware eligibility while the collector enforced only legacy basename eligibility.

The repair changes the collector to call `_is_candidate_lock_path(candidate)` with the already parsed `PurePosixPath`. It does not broaden the accepted Git object types or relax content validation.

## Trust boundary

A candidate enters the generated build context only when every applicable condition holds:

1. The base revision is an exact 40-character hexadecimal commit SHA.
2. `git ls-tree` reports a regular `100...` blob in that exact base tree.
3. The repository-relative path is non-absolute and contains no `..` component.
4. The path is either a conventional requirements lock name or a direct `.txt` child of a directory named `requirements`.
5. Every substantive requirement is an exact `==` pin with complete SHA-256 hashes, or a separately bounded relative requirements include.
6. Symlinks, gitlinks, malformed tree entries, unpinned files, unsafe includes, and pull-request-only content remain excluded.
7. `uv.lock` follows its separate trusted export path and still requires the corresponding base-owned `pyproject.toml`.

Path eligibility is candidate discovery, not dependency trust. The existing hash, include, export, and downstream closure checks remain authoritative.

## Test-first evidence

Temporary repair workflow run `31787913977` executed the following sequence on head `912313ff92cdcee6f240e9584f79ca37615ee5a2`:

1. Created a temporary Git repository containing hash-pinned `requirements/ci.txt` and `service/requirements/package.txt` blobs.
2. Confirmed the regression test failed before the implementation change because neither path was collected.
3. Replaced the basename-only collector predicate with the repository-relative path predicate.
4. Confirmed both paths were returned in deterministic repository order.
5. Compiled the implementation and regression test and ran `git diff --check`.
6. Deleted the temporary writer workflow before committing the production change.

An earlier repair attempt failed before exercising the assertion because direct script execution omitted the repository root from `sys.path`. The corrected workflow ran both RED and GREEN phases with the same explicit `PYTHONPATH=.` environment, so the observed transition is attributable to the collector change rather than import setup.

## Permanent regression command

```bash
PYTHONPATH=. python3 tests/test_materialize_base_python_requirement_paths.py
python3 -m compileall -q \
scripts/ci/materialize_base_python_requirements.py \
tests/test_materialize_base_python_requirement_paths.py
git diff --check
```

The repository quality workflow must also run the full materializer and Strix regression suites on the exact pull-request head. Focused repair evidence cannot replace protected-branch checks, semantic review, or required independent approvals.

## Change-management rule

Future changes to candidate naming, path parsing, Git tree filtering, requirement includes, `uv.lock` export, or materialized manifests must update the path-discovery tests and the broader materializer suite together. A path predicate and its collector call site must not evolve independently.
32 changes: 32 additions & 0 deletions docs/doctoring/cross-repo-opencode-evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Cross-repository OpenCode evidence

## Incident and buyer impact

Sibling-repo reviews (for example `ContextualWisdomLab/naruon#1317`) lost
coverage-evidence when the former Astral release endpoint rejected requests,
and the OpenCode App token could not publish a commit status across
repositories. A later Strix provider outage was also converted into a green
required check, so incomplete security evidence looked like a pass.

## Decision

1. Download uv from the literal GitHub Releases HTTPS URL with a fixed
`User-Agent`, disable proxies, reject redirects, and retain size, checksum,
and executable-version checks. Repository or user data cannot select the
network origin.
2. Before skipping cross-repository status publication, prove an exact-head
formal OpenCode review from an accepted author (`opencode-agent[bot]` or
`opencode-agent`) with state (`APPROVED` or `CHANGES_REQUESTED`). Missing
proof, an unaccepted author, or a mismatched head fails closed.
3. Keep Strix red when the backend is unavailable. Incomplete provider
evidence is not a clean scan.

## References

Fielding, R., Nottingham, M., & Reschke, J. (Eds.). (2022). *HTTP semantics*
(RFC 9110). Internet Engineering Task Force.
https://doi.org/10.17487/RFC9110

National Institute of Standards and Technology. (2020). *Security and privacy
controls for information systems and organizations* (NIST Special Publication
800-53 Rev. 5). https://doi.org/10.6028/NIST.SP.800-53r5
62 changes: 62 additions & 0 deletions docs/doctoring/strix-bounded-pr-scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Strix bounded pull-request scope and CI recursion contract

## Purpose

This doctoring record defines the trusted boundary used when Strix reviews a bounded set of pull-request changes from an organization-required workflow. It also records the GitHub Actions recursion behavior encountered while repairing the boundary so that future maintainers do not misclassify infrastructure state as a target-code defect.

## Incident chain

A downstream OpenCode review dispatch for `ContextualWisdomLab/pg-llm-batch#190` failed while the central workflow materialized its trusted `uv` executable. The trusted download retained a fixed Astral release URL, a no-proxy/no-redirect opener, a bounded response read, SHA-256 verification, and executable-version verification, but the request did not identify the organization client. Pull request #939 adds a fixed `User-Agent` and regression coverage without weakening those trust checks.

During verification of the central repair, Strix received an intentionally bounded pull-request target. The GitHub Actions runner created that target below a host temporary directory, while the Strix sandbox mounted the same files below `/workspace/<workspace_subdir>`. The original host path was intentionally absent inside the sandbox. Treating that absence as a missing-code vulnerability was therefore a scanner-orientation error, not a finding in the pull-request content.

Repair workflow run `31784776654` established the regression test first, applied the trusted static scope guidance, ran shell syntax validation, ran the focused Python contract, and completed the full `scripts/ci/test_strix_quick_gate.sh` harness before committing the production change. Both temporary repair workflows were removed by the verified commit.

## Trusted scope contract

The following invariants apply:

1. `pull_request_target` executes the protected-base workflow and trusted gate implementation. Pull-request content is materialized as data in a separate bounded directory; it is not executed with privileged credentials.
2. A target created under the runner host temporary directory may be mounted at `/workspace/<workspace_subdir>` inside the Strix sandbox. Absence of the original host pathname inside the sandbox is expected.
3. For the internal bounded pull-request scope only, the trusted gate supplies a static instruction explaining the mount contract and directing Strix to inspect the files present in the current working directory.
4. No repository input, dispatch payload, pull-request field, environment override, or caller-supplied instruction is forwarded to the security model. The instruction is selected only when `TARGET_PATH_IS_INTERNAL_PR_SCOPE=1` was set by trusted scope materialization.
5. The bounded directory is the complete authorized target for the changed-path scan. Strix must continue to report actionable vulnerabilities in the workflow, shell, Python, configuration, and other eligible files that are actually present.
6. Scope orientation must not suppress provider failures, malformed reports, integrity failures, missing authorized files, or vulnerabilities in present content. Those conditions remain fail-closed.

## GitHub Actions recursion behavior

The verified repair commit was pushed by a workflow using the repository `GITHUB_TOKEN`. GitHub created the resulting pull-request workflow runs in an approval-required state and reported `action_required` without jobs. This is GitHub's recursion protection rather than test execution evidence. A maintainer-authenticated commit or explicit workflow approval is required before exact-head CI can run normally.

This repository must not replace the recursion protection with a broadly privileged token merely to make a self-repair workflow recursively trigger CI. Temporary repair workflows must remain narrowly scoped, use least-privilege `contents: write`, verify that the remote branch has not advanced, run the full regression harness before pushing, and delete themselves from the resulting production commit.

## Regression evidence

The minimum local or CI evidence for this boundary is:

```bash
bash -n scripts/ci/strix_quick_gate.sh
python3 -m unittest discover \
--start-directory tests \
--pattern 'test_strix_internal_scope_instruction_contract.py' \
--verbose
bash scripts/ci/test_strix_quick_gate.sh
```

The exact pull-request head must additionally complete the trusted-uv materializer quality workflow, Strix changed-path quality workflow, repository security workflows, required OpenCode review, required Strix scan, and all protected-branch review requirements. A previous-head repair run, an approval-required run with no jobs, or a downstream repository's successful leaf checks cannot substitute for current-head central evidence.

## Operational recovery sequence

1. Confirm the downstream source head and reproduce the central failure against that exact SHA.
2. Repair the central trusted implementation; do not add unrelated downstream source changes.
3. Add a regression contract that fails before the central repair and passes after it.
4. Verify trusted URL, redirect, proxy, size, checksum, executable-version, and credential boundaries remain intact.
5. Run the full Strix gate harness before committing a scope-orientation change.
6. Remove temporary repair automation from the production diff.
7. Obtain exact-head central CI and independent approvals without dismissing reviews or bypassing branch protection.
8. Merge the central repair normally, then rerun the downstream review on the unchanged downstream head so the infrastructure-derived review is superseded through the standard review path.

## References

GitHub. (n.d.). *GITHUB_TOKEN*. GitHub Docs. Retrieved August 14, 2026, from https://docs.github.com/en/actions/concepts/security/github_token

GitHub. (n.d.). *Securely using pull_request_target*. GitHub Docs. Retrieved August 14, 2026, from https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target
11 changes: 10 additions & 1 deletion scripts/ci/strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ NORMALIZED_CHANGED_FILES=()
PULL_REQUEST_SCOPE_DIRS=()
LAST_PULL_REQUEST_SCOPE_DIR=""
TARGET_PATH_IS_INTERNAL_PR_SCOPE=0
INTERNAL_PR_SCOPE_INSTRUCTION="This target is a deliberately bounded pull-request changed-file scope mounted by Strix under /workspace/<workspace_subdir>. The original GitHub Actions runner host path is intentionally absent inside the sandbox, and that absence is not a vulnerability. Treat the files in the current working directory as the complete authorized target for this quick changed-path scan. Inspect the available workflow, shell, Python, and configuration files for actionable content vulnerabilities. Do not report the missing host path or intentional scope bounding as a target-code vulnerability."

resolve_trusted_input_file() {
local label="$1"
Expand Down Expand Up @@ -2617,6 +2618,7 @@ run_strix_once() {
local rc
local llm_api_base_value
local child_model
local child_instruction=""
local child_reasoning_effort="${STRIX_REASONING_EFFORT:-}"
local resolved_target_path
local timeout_seconds="$STRIX_PROCESS_TIMEOUT_SECONDS"
Expand Down Expand Up @@ -2651,6 +2653,9 @@ run_strix_once() {
if ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then
return 1
fi
if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then
child_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"
fi
local start_epoch
start_epoch="$(date +%s)"
local child_llm_api_key=""
Expand Down Expand Up @@ -2682,7 +2687,8 @@ run_strix_once() {
STRIX_CHILD_EXECUTABLE_ROOT="$STRIX_EXECUTABLE_ROOT" \
STRIX_CHILD_EXECUTABLE_SHA256="$STRIX_EXECUTABLE_SHA256" \
STRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \
python3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" "$STRIX_SCAN_WORKING_DIR" <<'PY'
STRIX_CHILD_INSTRUCTION="$child_instruction" \
python3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" "$STRIX_SCAN_WORKING_DIR" <<'PY'
import hashlib
import hmac
import os
Expand Down Expand Up @@ -2856,6 +2862,9 @@ scan_output_dir.mkdir()
# scan target. The target remains explicit and absolute, so changing cwd cannot
# change which source tree is scanned.
command = [resolved_strix_bin, "-n", "-t", str(target_cwd), "--scan-mode", scan_mode]
instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()
if instruction:
command.extend(["--instruction", instruction])
Comment thread
seonghobae marked this conversation as resolved.

try:
process = subprocess.Popen(
Expand Down
Loading
Loading