Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
559b6f1
fix(automation): restore hourly fleet coordination
seonghobae Aug 22, 2026
9e08e1a
Merge branch 'main' into fix/organization-loop-oidc-fallback
opencode-agent[bot] Aug 22, 2026
431a3e0
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 22, 2026
6daa442
fix(loop): harden OIDC credential exchange
seonghobae Aug 22, 2026
a1cbf49
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 22, 2026
441b7d4
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 23, 2026
70d9eb0
Merge remote-tracking branch 'refs/remotes/origin/main' into HEAD
seonghobae Aug 23, 2026
38bb069
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 23, 2026
e237b2d
fix(automation): bound app token exchange requests
seonghobae Aug 23, 2026
dfb8e26
fix(strix): gate dependency manifest updates (#935)
seonghobae Aug 23, 2026
9cda8fa
fix(strix): avoid untrusted dependency build hooks
seonghobae Aug 23, 2026
7937d45
Merge main into organization loop repair
seonghobae Aug 25, 2026
028e6e2
Merge branch 'main' into fix/organization-loop-oidc-fallback
opencode-agent[bot] Aug 25, 2026
54ab5bb
Merge branch 'main' into fix/organization-loop-oidc-fallback
opencode-agent[bot] Aug 25, 2026
be6a0cb
Merge branch 'main' into fix/organization-loop-oidc-fallback
opencode-agent[bot] Aug 26, 2026
5304f20
Merge branch 'main' into fix/organization-loop-oidc-fallback
opencode-agent[bot] Aug 26, 2026
fed5112
Merge branch 'main' into fix/organization-loop-oidc-fallback
opencode-agent[bot] Aug 26, 2026
3dc0de8
Merge branch 'main' into fix/organization-loop-oidc-fallback
opencode-agent[bot] Aug 26, 2026
5718a50
Merge branch 'main' into fix/organization-loop-oidc-fallback
opencode-agent[bot] Aug 26, 2026
0e548c5
Merge branch 'main' into fix/organization-loop-oidc-fallback
opencode-agent[bot] Aug 26, 2026
2aa1dae
Merge branch 'main' into fix/organization-loop-oidc-fallback
opencode-agent[bot] Aug 26, 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
64 changes: 63 additions & 1 deletion .github/workflows/organization-commercial-readiness-loop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ jobs:
github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
runs-on: ubuntu-24.04
timeout-minutes: 25
permissions:
contents: read
id-token: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
ORGANIZATION: ContextualWisdomLab
Expand All @@ -32,6 +35,7 @@ jobs:
egress-policy: block
allowed-endpoints: >-
api.github.com:443
api.opencode.ai:443
github.com:443
objects.githubusercontent.com:443
release-assets.githubusercontent.com:443
Comment thread
seonghobae marked this conversation as resolved.
Expand All @@ -53,10 +57,68 @@ jobs:
- name: Coordinate one bounded fleet pass
env:
GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}
OIDC_AUDIENCE: opencode-github-action
OPENCODE_API_BASE_URL: https://api.opencode.ai
shell: bash --noprofile --norc -e -o pipefail {0}
run: |
exchange_unavailable() {
echo "::error::OpenCode app token exchange unavailable: $1"
exit 1
}
Comment thread
seonghobae marked this conversation as resolved.

if [ -z "${GH_TOKEN:-}" ]; then
if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
exchange_unavailable "OIDC request environment is missing."
fi

request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}"
separator="&"
case "$request_url" in
*\?*) ;;
*) separator="?" ;;
esac

if ! oidc_response="$(
curl -fsS \
--connect-timeout 10 \
--max-time 30 \
-H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${request_url}${separator}audience=${OIDC_AUDIENCE}"
)"; then
exchange_unavailable "OIDC token request did not complete."
fi

if ! oidc_token="$(jq -r '.value // empty' <<<"$oidc_response" 2>/dev/null)"; then
exchange_unavailable "OIDC token response was malformed."
fi
if [ -z "$oidc_token" ]; then
exchange_unavailable "OIDC token response was empty."
fi
echo "::add-mask::$oidc_token"

if ! token_response="$(
curl -fsS \
--connect-timeout 10 \
--max-time 30 \
-X POST \
-H "Authorization: Bearer ${oidc_token}" \
"${OPENCODE_API_BASE_URL}/exchange_github_app_token"
)"; then
exchange_unavailable "app token request did not complete."
fi
Comment thread
seonghobae marked this conversation as resolved.

if ! app_token="$(jq -r '.token // empty' <<<"$token_response" 2>/dev/null)"; then
Comment thread
seonghobae marked this conversation as resolved.
exchange_unavailable "app token response was malformed."
fi
if [ -z "$app_token" ]; then
exchange_unavailable "app token response was empty."
fi
echo "::add-mask::$app_token"
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
export GH_TOKEN="$app_token"
fi

if [ -z "${GH_TOKEN:-}" ]; then
echo "::error::PR_REVIEW_MERGE_TOKEN is required; neither the reviewer credential nor repository-scoped GITHUB_TOKEN is accepted."
echo "::error::PR_REVIEW_MERGE_TOKEN or the job-bound OpenCode App token exchange is required; neither the reviewer credential nor repository-scoped GITHUB_TOKEN is accepted."
Comment thread
seonghobae marked this conversation as resolved.
exit 1
fi
Comment thread
seonghobae marked this conversation as resolved.
echo "::add-mask::$GH_TOKEN"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/strix-changed-path-quality-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
- "docs/doctoring/strix-legal-git-paths.md"
- "docs/doctoring/strix-model-behavior-error.md"
- "docs/doctoring/strix-quality-timeout-fixtures.md"
- "docs/doctoring/strix-dependency-manifest-trigger.md"
- "requirements-strix-ci-hashes.txt"
- "scripts/ci/strix_quick_gate.sh"
- "scripts/ci/test_strix_quick_gate.sh"
- "tests/test_strix_changed_path_policy.py"
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,25 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Bound the Strix changed-path quality gate to the complete production hash
lock while keeping pull-request-controlled source distributions out of pip's
executable metadata/build boundary. Production lock resolution stays in the
trusted default-branch Strix workflow; lock changes still trigger the
permanent policy, regression, and security gates.
- Restored the hourly organization commercial-readiness loop after five
consecutive startup failures caused by its mandatory but unprovisioned
`PR_REVIEW_MERGE_TOKEN`. Protected scheduled jobs now prefer that maintainer
secret and otherwise exchange their job-bound GitHub OIDC identity for the
existing short-lived OpenCode GitHub App installation token, without
accepting `OPENCODE_APPROVE_TOKEN`, `GITHUB_TOKEN`, provider credentials, or
exposing either cross-repository credential to checkout, setup, or artifact
actions. The exchange now runs only when the preferred maintainer secret is
absent, masks the OIDC JWT before the
second request, and converts malformed successful JSON responses into the
existing explicit unavailable output instead of exiting early under
`errexit`. Both OIDC and App-token HTTP requests now use a 10-second connect
timeout and 30-second total timeout so a stalled exchange fails within the
bounded coordinator step.
- Resolve Strix visibility from the trusted GitHub event for ordinary push,
schedule, and pull-request runs, reserving API retries for cross-repository
dispatches whose workflow token may not see the target repository.
Expand Down
23 changes: 22 additions & 1 deletion docs/doctoring/organization-commercial-readiness-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The coordinator may dispatch at most one review-repair workflow and one product-

A single workflow cannot safely write every repository merely because it runs in the organization `.github` repository. GitHub's default `GITHUB_TOKEN` is scoped to the repository containing the workflow; cross-repository Actions dispatch therefore requires an explicitly provisioned user or GitHub App credential with the required repository and Actions permissions. This control does not make every repository directly writable. It only considers repositories the live API reports as organization-owned, non-fork, enabled, non-archived, default-branch-bearing, and writable by the authenticated installation.

The central job therefore refuses both repository-scoped and reviewer-scoped token fallbacks. It requires the maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; `OPENCODE_APPROVE_TOKEN` remains isolated to the reviewer credential chain and `GITHUB_TOKEN` is not accepted for cross-repository coordination. The maintainer token is exposed only to the final dispatch shell step, not checkout, setup, artifact upload, or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor `COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers.
The central job therefore refuses both repository-scoped and reviewer-scoped token fallbacks. It prefers the maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; when that optional long-lived secret is absent, the protected scheduled job exchanges its GitHub OIDC identity for the existing short-lived OpenCode GitHub App installation token. `OPENCODE_APPROVE_TOKEN` remains isolated to the reviewer credential chain and `GITHUB_TOKEN` is not accepted for cross-repository coordination. Both accepted credentials are exposed only to the final dispatch shell step, not checkout, setup, artifact upload, or other third-party actions. The OIDC exchange receives only `id-token: write`, which permits requesting the job-bound JWT but grants no repository write authority by itself. The exchanged installation token remains bounded by the App installation's selected repositories and permissions; GitHub still requires Contents write for `repository_dispatch` and Actions write for `workflow_dispatch`, so a missing installation permission fails closed. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor `COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers.

## Dynamic repository-writer lease

Expand Down Expand Up @@ -44,10 +44,27 @@ The repository-local entrypoint remains responsible for its own bounded editable

## Failure, evidence, and operations

Runs `32560132644`, `32562851784`, `32565331074`, `32567859925`, and `32570355777` reproduced the same startup failure: the workflow required `PR_REVIEW_MERGE_TOKEN`, but neither the repository nor organization exposed that secret. The coordinator therefore completed no inventory or dispatch work for five consecutive hourly heartbeats. The OIDC installation-token fallback repairs that configuration deadlock without copying a personal token, accepting the repository-scoped `GITHUB_TOKEN`, or reusing `OPENCODE_APPROVE_TOKEN`.

The schedule runs at minute 7 rather than minute 0 to reduce exposure to the documented start-of-hour GitHub Actions load spike. The central workflow has no `workflow_dispatch` entrypoint, so branch-selected coordinator source cannot be executed; scheduled execution occurs only from protected default `main`. Local operators may use the script's `--dry-run` mode from a reviewed checkout without adding a central manual workflow entrypoint.

Organization, workflow, active-run, and pull-request inventories are paginated. One inaccessible repository is recorded as an inspection error while other independently safe repositories continue. A run fails nonzero when every selected repository inspection fails or when every planned dispatch fails; partial, independently contained failures remain visible without discarding successful work.

Exact-head reviews found three exchange-path gaps before activation. A
malformed HTTP-success OIDC or App-token response made `jq` exit under shell
`errexit` before the workflow could publish its explicit unavailable result;
the OIDC JWT was not registered with the runner masker; and the App exchange
ran even when the preferred maintainer secret was already present. The final
coordinator shell step now performs the exchange only when its preferred
`GH_TOKEN` input is empty, guards both JSON parses with a bounded fail-closed
diagnostic, and masks the OIDC JWT immediately after validation. Keeping
selection and exchange in that final first-party shell step preserves the rule
that no checkout, setup, artifact, or other third-party action receives either
credential, and neither response body is logged. Both HTTP calls reuse the
central scheduler's 10-second connection and 30-second total request bounds, so
an unavailable identity or exchange endpoint cannot consume the entire
25-minute coordinator budget.

Each run writes one deterministic JSON receipt and the same bounded evidence to the GitHub Actions job summary. The JSON is uploaded through the immutable, SHA-pinned artifact action with a three-day retention period. Artifact upload receives no maintainer or model credential. The receipt proves only coordinator observations and downstream dispatch acceptance; it is not merge, release, or product-quality evidence.

No queued, pending, skipped-required, cancelled, absent, stale-head, predecessor-head, synthetic-merge-only, or failed check is converted to passing evidence. The coordinator's successful dispatch means only that exact state was revalidated and a bounded downstream workflow was accepted by GitHub.
Expand All @@ -60,10 +77,14 @@ GitHub. (n.d.). *Automatic token authentication*. GitHub Docs. Retrieved August

GitHub. (n.d.). *Events that trigger workflows*. GitHub Docs. Retrieved August 8, 2026, from https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows

GitHub. (n.d.). *OpenID Connect reference*. GitHub Docs. Retrieved August 22, 2026, from https://docs.github.com/en/actions/reference/security/oidc

GitHub. (n.d.). *REST API endpoints for artifacts*. GitHub Docs. Retrieved August 8, 2026, from https://docs.github.com/en/rest/actions/artifacts

GitHub. (n.d.). *REST API endpoints for workflows*. GitHub Docs. Retrieved August 8, 2026, from https://docs.github.com/en/rest/actions/workflows

GitHub. (n.d.). *REST API endpoints for repositories*. GitHub Docs. Retrieved August 22, 2026, from https://docs.github.com/en/rest/repos/repos

GitHub. (n.d.). *REST API endpoints for workflow runs*. GitHub Docs. Retrieved August 8, 2026, from https://docs.github.com/en/rest/actions/workflow-runs

National Institute of Standards and Technology. (2022). *Secure software development framework (SSDF) version 1.1: Recommendations for mitigating the risk of software vulnerabilities* (NIST Special Publication 800-218). https://doi.org/10.6028/NIST.SP.800-218
34 changes: 34 additions & 0 deletions docs/doctoring/strix-dependency-manifest-trigger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Strix dependency-manifest quality trigger

## Incident and buyer impact

`requirements-strix-ci-hashes.txt` is executable supply-chain input for the
organization-required Strix gate. The permanent changed-path quality
workflow did not list that file. A Dependabot lock-only pull request could
therefore merge without running the Strix policy, shell-regression, security,
and full-suite contracts.

## Decision

Add the exact repository-root manifest path to
`.github/workflows/strix-changed-path-quality-ci.yml` and bind it with
`test_strix_workflow_reruns_when_dependency_manifest_changes`. Do not pass the
pull-request-controlled lock to `pip` in this job. A hash-matching source
distribution can execute its PEP 517 build backend while pip prepares metadata,
even for `--dry-run --no-deps --require-hashes`. Hosted Strix run `32643804284`
reproduced that execution path at the exact pull-request head. Requiring wheels
is not an equivalent repair because the production closure includes packages
without a compatible wheel. Production resolution therefore remains inside the
trusted default-branch Strix boundary, while every lock change still triggers
the permanent policy, regression, and security review gates. Scanner models,
credentials, timeouts, and result semantics are unchanged.

## References

National Institute of Standards and Technology. (2024). *Cybersecurity
supply chain risk management practices for systems and organizations*
(NIST Special Publication 800-161 Rev. 1).
https://doi.org/10.6028/NIST.SP.800-161r1

Open Source Security Foundation. (2025). *SLSA specification version 1.2*.
https://slsa.dev/spec/v1.2/
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,36 @@


def test_central_schedule_has_no_branch_selected_or_reviewer_credential_path() -> None:
"""The fleet coordinator must be schedule-only and use maintainer authority."""
"""The fleet coordinator uses schedule-bound maintainer or App authority."""
source = WORKFLOW_PATH.read_text(encoding="utf-8")

assert "workflow_dispatch:" not in source
assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in source
assert "steps.opencode_app_token.outputs.token" not in source
assert "id-token: write" in source
assert "OIDC_AUDIENCE: opencode-github-action" in source
assert "https://api.opencode.ai/exchange_github_app_token" not in source
assert '"${OPENCODE_API_BASE_URL}/exchange_github_app_token"' in source
assert "persist-credentials: false" in source
assert "OPENCODE_APPROVE_TOKEN" not in source
assert "|| github.token" not in source
assert "DRY_RUN" not in source
assert "inputs.dry_run" not in source


def test_opencode_exchange_fails_closed_without_wasting_or_exposing_credentials() -> None:
"""Malformed exchanges diagnose safely and the preferred secret skips OIDC."""
source = WORKFLOW_PATH.read_text(encoding="utf-8")

coordinate = source.split(
" - name: Coordinate one bounded fleet pass\n", maxsplit=1
)[1]
assert 'if [ -z "${GH_TOKEN:-}" ]; then' in coordinate
assert 'export GH_TOKEN="$app_token"' in coordinate
assert 'if ! oidc_token="$(jq -r' in source
assert 'if ! app_token="$(jq -r' in source
assert "OIDC token response was malformed" in source
assert "app token response was malformed" in source
assert 'echo "::add-mask::$oidc_token"' in source
assert coordinate.count("--connect-timeout 10") == 2
assert coordinate.count("--max-time 30") == 2
3 changes: 3 additions & 0 deletions tests/test_organization_commercial_readiness_loop_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ def test_workflow_and_doctoring_contracts() -> None:
assert 'MAX_REVIEW_DISPATCHES: "1"' in workflow_source
assert 'MAX_DEVELOPMENT_DISPATCHES: "1"' in workflow_source
assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in workflow_source
assert 'export GH_TOKEN="$app_token"' in workflow_source
assert "id-token: write" in workflow_source
assert "OIDC_AUDIENCE: opencode-github-action" in workflow_source
assert "OPENCODE_APPROVE_TOKEN" not in workflow_source
assert "workflow_dispatch:" not in workflow_source
assert "|| github.token" not in workflow_source
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ def test_json_receipt_is_retained_as_an_immutable_short_lived_artifact() -> None
assert "results-receiver.actions.githubusercontent.com:443" in source
assert "*.actions.githubusercontent.com:443" in source
assert "*.blob.core.windows.net:443" in source
assert "api.opencode.ai:443" in source
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
)


def test_maintainer_token_is_scoped_only_to_the_dispatch_step() -> None:
"""Third-party setup actions must never receive the cross-repository token."""
def test_coordinator_token_is_scoped_only_to_the_dispatch_step() -> None:
"""Third-party actions never receive either cross-repository credential."""
source = WORKFLOW_PATH.read_text(encoding="utf-8")
before_dispatch, dispatch_step = source.split(
" - name: Coordinate one bounded fleet pass\n", maxsplit=1
Expand All @@ -19,3 +19,10 @@ def test_maintainer_token_is_scoped_only_to_the_dispatch_step() -> None:
assert "PR_REVIEW_MERGE_TOKEN" not in before_dispatch
assert "GH_TOKEN:" not in before_dispatch
assert "env:\n GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in dispatch_step
assert "steps.opencode_app_token.outputs.token" not in source
artifact_step = dispatch_step.split(
" - name: Preserve the exact fleet receipt\n", maxsplit=1
)[1]
assert "GH_TOKEN:" not in artifact_step
assert "PR_REVIEW_MERGE_TOKEN" not in artifact_step
assert "steps.opencode_app_token.outputs.token" not in artifact_step
18 changes: 18 additions & 0 deletions tests/test_strix_workflow_dependency_hashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ def test_strix_workflow_reruns_when_hash_contract_changes() -> None:
assert ' - "tests/test_strix_workflow_dependency_hashes.py"' in workflow


def test_strix_workflow_reruns_when_dependency_manifest_changes() -> None:
"""Changing the Strix dependency lock must trigger its install contract."""
workflow = WORKFLOW.read_text(encoding="utf-8")

assert (ROOT / "requirements-strix-ci-hashes.txt").is_file()
assert ' - "requirements-strix-ci-hashes.txt"' in workflow
assert ' - "docs/doctoring/strix-dependency-manifest-trigger.md"' in workflow


def test_strix_workflow_does_not_execute_pr_dependency_build_hooks() -> None:
"""The PR gate must not resolve its untrusted production lock with pip."""

workflow = WORKFLOW.read_text(encoding="utf-8")

assert "Preflight exact hashed Strix dependency closure" not in workflow
assert "-r requirements-strix-ci-hashes.txt" not in workflow


def test_strix_workflow_rejects_branch_selected_manual_dispatch() -> None:
"""Central executable workflows load no branch-selected manual source."""
workflow = WORKFLOW.read_text(encoding="utf-8")
Expand Down
Loading