test(parity): add OpenAI Tier 2 backend + SkillInvocationBackend Protocol (Fixes #368) [stacked on #370] - #371
Conversation
Kusari Analysis Results:
Both independent analyses return clean results. The dependency analysis found no pinned version dependency changes, presenting no dependency-related risk. The code analysis flagged a low-confidence, medium-severity observation about missing persist-credentials: false on actions/checkout steps in two workflow files (parity-tier2-openai.yml and parity-tier2.yml), but confirmed it is not exploitable given the following compensating controls: (1) workflows are triggered exclusively via workflow_dispatch with no untrusted fork or PR trigger; (2) both workflows are gated behind named GitHub Environments requiring human reviewer approval before any secrets are exposed; (3) all actions are pinned to full 40-character commit SHAs, eliminating supply-chain tag-repointing risk; (4) permissions are scoped to contents: read only; (5) the artifact upload path (parity-artifacts/) does not include the .git directory where a persisted credential would reside, making the artipacked attack vector non-applicable. No injection vulnerabilities, secret leaks, or other code issues were identified. Adding persist-credentials: false remains a best-practice hardening improvement and is recommended as a low-priority follow-up, but does not represent a meaningful security gap given the controls in place. Note View full detailed analysis result for more information on the output and the checks that were run.
Found this helpful? Give it a 👍 or 👎 reaction! |
| run: uv sync --extra dev | ||
|
|
||
| - name: Preflight audit log (OW-10) | ||
| run: | |
There was a problem hiding this comment.
Issue: Move all github context and workflow inputs to env: variables before using them in the run: block to prevent shell injection. Replace direct ${{ }} interpolation with shell environment variable references.
Recommended Code Changes:
- name: Preflight audit log (OW-10)
env:
ACTOR: ${{ github.actor }}
SHA: ${{ github.sha }}
FIXTURE_GLOB: ${{ inputs.fixture_glob }}
MODEL: ${{ inputs.model }}
run: |
{
echo "## Tier 2 (OpenAI) Preflight"
echo ""
echo "- actor: $ACTOR"
echo "- sha: $SHA"
echo "- fixture_glob: $FIXTURE_GLOB"
echo "- model: $MODEL"
echo "- timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
} >> "$GITHUB_STEP_SUMMARY"
| # provider's key -- the Claude/Anthropic-scoped key is only | ||
| # reachable from the sibling workflow (parity-tier2.yml). | ||
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | ||
| run: | |
There was a problem hiding this comment.
Issue: The run: block at this step has OPENAI_API_KEY in scope and directly interpolates inputs.model and inputs.fixture_glob. Move inputs to env: variables to prevent injection-based secret exfiltration.
Recommended Code Changes:
- name: Run Tier 2 parity check (OpenAI)
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
MODEL: ${{ inputs.model }}
FIXTURE_GLOB: ${{ inputs.fixture_glob }}
run: |
uv run python -m tests.darnit.parity.tier2.run \
--backend openai \
--model "$MODEL" \
--fixture-glob "$FIXTURE_GLOB"
| run: uv sync --extra dev | ||
|
|
||
| - name: Preflight audit log (T2-7/T2-8) | ||
| run: | |
There was a problem hiding this comment.
Issue: Move all github context and workflow inputs to env: variables before using them in the run: block to prevent shell injection.
Recommended Code Changes:
- name: Preflight audit log (T2-7/T2-8)
env:
ACTOR: ${{ github.actor }}
SHA: ${{ github.sha }}
FIXTURE_GLOB: ${{ inputs.fixture_glob }}
run: |
{
echo "## Tier 2 Preflight"
echo ""
echo "- actor: $ACTOR"
echo "- sha: $SHA"
echo "- fixture_glob: $FIXTURE_GLOB"
echo "- timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
} >> "$GITHUB_STEP_SUMMARY"
| - name: Run Tier 2 parity check | ||
| env: | ||
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | ||
| run: | |
There was a problem hiding this comment.
Issue: The run: block at this step has ANTHROPIC_API_KEY in scope and directly interpolates inputs.fixture_glob. Move the input to an env: variable to prevent injection-based secret exfiltration.
Recommended Code Changes:
- name: Run Tier 2 parity check
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
FIXTURE_GLOB: ${{ inputs.fixture_glob }}
run: |
uv run python -m tests.darnit.parity.tier2.run \
--fixture-glob "$FIXTURE_GLOB"
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
All action references in this workflow use mutable version tags (actions/checkout@v4, actions/setup-python@v5, astral-sh/setup-uv@v6, actions/upload-artifact@v4). Pin each to a full 40-character commit SHA to prevent supply-chain attacks if an action owner's account is compromised and the tag is repointed.
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
All action references in this workflow use mutable version tags (actions/checkout@v4, actions/setup-python@v5, astral-sh/setup-uv@v6, actions/upload-artifact@v4). Pin each to a full 40-character commit SHA to prevent supply-chain attacks if an action owner's account is compromised and the tag is repointed.
|
Added T022 (US3 aggregate cross-provider diff script) as a follow-on commit |
|
Kusari PR Analysis rerun based on - c09bfea performed at: 2026-08-11T20:24:48Z - link to updated analysis |
|
@kusari-inspector rerun |
|
🔄 Run triggered at 15:30:26 UTC. Starting fresh analysis... |
|
Kusari PR Analysis rerun based on - c09bfea performed at: 2026-08-12T15:32:30Z - link to updated analysis |
|
@kusari-inspector rerun |
|
🔄 Run triggered at 16:14:59 UTC. Starting fresh analysis... |
|
Kusari PR Analysis rerun based on - c09bfea performed at: 2026-08-12T16:16:29Z - link to updated analysis |
…ns into env: vars Addresses Kusari Inspector findings on PR darnitdevorg#371 -- same class of issue as the sibling fix for parity-tier2.yml on the 028 branch: 1. Shell injection risk (HIGH impact / HIGH likelihood): `${{ github.actor }}`, `${{ github.sha }}`, `${{ inputs.fixture_glob }}`, and `${{ inputs.model }}` were interpolated directly into `run:` blocks that had OPENAI_API_KEY set in env. A crafted fixture_glob or model input containing shell metacharacters could execute arbitrary commands and exfiltrate the key. Fix: move each context expression into a step-level `env:` variable and reference it as a quoted shell variable inside the run: block. 2. Supply-chain risk from unpinned actions: Same four actions (actions/checkout, actions/setup-python, astral-sh/setup-uv, actions/upload-artifact) used mutable version tags. Fix: pin each action to the same 40-character commit SHA used by the Claude workflow (both workflows share the identical pinned versions to simplify supply-chain review). Pinned versions match the Claude workflow exactly: actions/checkout 11d5960a326750d5838078e36cf38b85af677262 v4.4.0 actions/setup-python a26af69be951a213d495a4c3e4e4022e16d87065 v5.6.0 astral-sh/setup-uv d0cc045d04ccac9d8b7881df0226f9e82c39688e v6.8.0 actions/upload-artifact ea165f8d65b6e75b540449e92b4886f43607fa02 v4.6.2 All feature-029 workflow-config tests continue to pass (15 tests covering both workflows' governance-critical structure).
c09bfea to
f007869
Compare
|
Kusari PR Analysis rerun based on - f007869 performed at: 2026-08-12T17:58:30Z - link to updated analysis |
pxp928
left a comment
There was a problem hiding this comment.
Approving on the condition that the findings below are addressed before merge. The SkillInvocationBackend Protocol is a clean extraction and the OpenAI backend is a reasonable implementation of it — this is the most straightforward PR of the four. But it currently ships two red tests, inherited from the fixture-discovery bug in #370, so it can't merge before that one is fixed.
Reviewed the incremental diff pr-370..pr-371 (commits a57a34b, a2a87b7, f007869). I ran the new tests and ruff locally. Ruff and format are clean.
Blocker — ships red
test_backend_extensibility.py:52 — both SC-007 tests fail. Verified: 2 failed, 1 passed.
run._discover_fixtures requires .baseline.toml in each fixture dir, but that filename is in .gitignore:32, so no fixture is ever discovered and main() returns 3 instead of exercising the injected backend. The third test passes vacuously for the same reason — exit 3 either way, so it asserts nothing.
The discovery filter itself predates this PR (feature 028 / #370), but these tests depend on it, so this PR can't go in until #370's fixture markers are committed.
Should fix
test_openai_backend_adversarial.py:116— bareimport openaihard-fails withModuleNotFoundErrorwhen the dev extra isn't synced (reproduced). Needspytest.importorskip. Separately, line 118 doesopenai.AsyncOpenAI = lambda: mock_clientand never restores it, leaking a broken stub into the rest of the pytest session — usemonkeypatch.setattr.backends/openai_backend.py:89—_dispatch_tool_callpins onlylocal_path;levelandoutput_formataresetdefault-ed. A model that chooseslevel: 1makes the OpenAI-side audit narrower than_run_mcp_tool's level 3, producing a spuriouscounts_disagree/ exit 1 — the harness reports a parity failure that is really a parameter mismatch. Unknown hallucinated keys reachaudit_openssf_baseline(**args)→TypeError→ the whole run exits 3. Pin all comparison-relevant params and drop unrecognised keys.test_openai_backend_adversarial.py(and the rest of the new tests) — none of the ~40 new tests carry aunitorintegrationmarker. CI runs only-m unit/-m integration, sopytest tests/darnit/parity/ -m unitgives95 deselected / 0 selected. The SC-002 key-exclusivity and SC-010 pinned-model governance tests never run — which is exactly the kind of check you want running unattended.scripts/aggregate_provider_diff.py:126— a present-but-unparseable provider message yields an empty control map; all rows show-and the section reports "0 disagreements" with no warning. Cross-provider drift gets silently reported as agreement. An unparseable message should be a loud error, not an empty map.artifact_writer.py:38— only the final-message file is provider-namespaced.diff_report.md,metadata.json, andmcp_tool_result.jsonclobber each other across providers, contradicting both the docstring and the aggregate script's documented single-root mode.diff.py:133— diff reports hardcodeskill_final_message.md, a filename that doesn't exist in non-Claude artifact bundles.
Nice touch on f007869 pinning the workflow actions to SHAs and moving context expressions into env: vars — that's the right instinct for a workflow that handles provider API keys.
…ns into env: vars Addresses Kusari Inspector findings on PR darnitdevorg#371 -- same class of issue as the sibling fix for parity-tier2.yml on the 028 branch: 1. Shell injection risk (HIGH impact / HIGH likelihood): `${{ github.actor }}`, `${{ github.sha }}`, `${{ inputs.fixture_glob }}`, and `${{ inputs.model }}` were interpolated directly into `run:` blocks that had OPENAI_API_KEY set in env. A crafted fixture_glob or model input containing shell metacharacters could execute arbitrary commands and exfiltrate the key. Fix: move each context expression into a step-level `env:` variable and reference it as a quoted shell variable inside the run: block. 2. Supply-chain risk from unpinned actions: Same four actions (actions/checkout, actions/setup-python, astral-sh/setup-uv, actions/upload-artifact) used mutable version tags. Fix: pin each action to the same 40-character commit SHA used by the Claude workflow (both workflows share the identical pinned versions to simplify supply-chain review). Pinned versions match the Claude workflow exactly: actions/checkout 11d5960a326750d5838078e36cf38b85af677262 v4.4.0 actions/setup-python a26af69be951a213d495a4c3e4e4022e16d87065 v5.6.0 astral-sh/setup-uv d0cc045d04ccac9d8b7881df0226f9e82c39688e v6.8.0 actions/upload-artifact ea165f8d65b6e75b540449e92b4886f43607fa02 v4.6.2 All feature-029 workflow-config tests continue to pass (15 tests covering both workflows' governance-critical structure).
f007869 to
77ac9a5
Compare
…ns into env: vars Addresses Kusari Inspector findings on PR darnitdevorg#371 -- same class of issue as the sibling fix for parity-tier2.yml on the 028 branch: 1. Shell injection risk (HIGH impact / HIGH likelihood): `${{ github.actor }}`, `${{ github.sha }}`, `${{ inputs.fixture_glob }}`, and `${{ inputs.model }}` were interpolated directly into `run:` blocks that had OPENAI_API_KEY set in env. A crafted fixture_glob or model input containing shell metacharacters could execute arbitrary commands and exfiltrate the key. Fix: move each context expression into a step-level `env:` variable and reference it as a quoted shell variable inside the run: block. 2. Supply-chain risk from unpinned actions: Same four actions (actions/checkout, actions/setup-python, astral-sh/setup-uv, actions/upload-artifact) used mutable version tags. Fix: pin each action to the same 40-character commit SHA used by the Claude workflow (both workflows share the identical pinned versions to simplify supply-chain review). Pinned versions match the Claude workflow exactly: actions/checkout 11d5960a326750d5838078e36cf38b85af677262 v4.4.0 actions/setup-python a26af69be951a213d495a4c3e4e4022e16d87065 v5.6.0 astral-sh/setup-uv d0cc045d04ccac9d8b7881df0226f9e82c39688e v6.8.0 actions/upload-artifact ea165f8d65b6e75b540449e92b4886f43607fa02 v4.6.2 All feature-029 workflow-config tests continue to pass (15 tests covering both workflows' governance-critical structure).
…sts integration Reviewer pxp928 flagged one blocker (rebase-driven) and six should-fix items on darnitdevorg#371. This commit addresses all of them. Blocker: - Both SC-007 tests inherited the missing-fixture-config bug from darnitdevorg#370. Rebasing onto the updated 028 pulls the tracked `.baseline.toml` fixtures + the pytest-marker hook forward, so those tests now collect and run. Should-fix: - `import openai` in the OpenAI-backend adversarial test now goes through `pytest.importorskip("openai")`, so a lean env without the `parity-tier2` extra installed skips cleanly instead of exploding with ImportError. - `_dispatch_tool_call` now pins `level=3` and `output_format="json"` the same way `local_path` is pinned. Previously `setdefault` let a rogue model ask for markdown at level 1 and defeat the comparison. - Added a `tier2/conftest.py` `pytest_collection_modifyitems` hook so every Tier 2 test collects with the `integration` marker. Same reasoning as PR darnitdevorg#370's tier1 conftest: CI's `-m unit / -m integration` split would otherwise silently deselect the whole suite. - The cross-provider aggregate script no longer silently reports "0 disagreements" when either provider's message was unparseable. It labels the fixture UNPARSEABLE in the per-fixture summary, lists unparseable fixtures at the bottom, and exits non-zero so CI can tell a "clean run" from a "no signal" run. - The diff-report formatter now accepts a `final_message_filename` keyword so an OpenAI-provider run's failure report points at `openai_final_message.md` instead of hardcoded `skill_final_message.md`. Threaded from `run.py` via `_provider_filename_prefix`. - Same filename thread covers the per-control and unparseable branches, so the diff output is consistent regardless of which provider ran. Workspace sweep: 2635 pass, 15 skip.
77ac9a5 to
2e0c8d7
Compare
…ns into env: vars Addresses Kusari Inspector findings on PR darnitdevorg#371 -- same class of issue as the sibling fix for parity-tier2.yml on the 028 branch: 1. Shell injection risk (HIGH impact / HIGH likelihood): `${{ github.actor }}`, `${{ github.sha }}`, `${{ inputs.fixture_glob }}`, and `${{ inputs.model }}` were interpolated directly into `run:` blocks that had OPENAI_API_KEY set in env. A crafted fixture_glob or model input containing shell metacharacters could execute arbitrary commands and exfiltrate the key. Fix: move each context expression into a step-level `env:` variable and reference it as a quoted shell variable inside the run: block. 2. Supply-chain risk from unpinned actions: Same four actions (actions/checkout, actions/setup-python, astral-sh/setup-uv, actions/upload-artifact) used mutable version tags. Fix: pin each action to the same 40-character commit SHA used by the Claude workflow (both workflows share the identical pinned versions to simplify supply-chain review). Pinned versions match the Claude workflow exactly: actions/checkout 11d5960a326750d5838078e36cf38b85af677262 v4.4.0 actions/setup-python a26af69be951a213d495a4c3e4e4022e16d87065 v5.6.0 astral-sh/setup-uv d0cc045d04ccac9d8b7881df0226f9e82c39688e v6.8.0 actions/upload-artifact ea165f8d65b6e75b540449e92b4886f43607fa02 v4.6.2 All feature-029 workflow-config tests continue to pass (15 tests covering both workflows' governance-critical structure).
…sts integration Reviewer pxp928 flagged one blocker (rebase-driven) and six should-fix items on darnitdevorg#371. This commit addresses all of them. Blocker: - Both SC-007 tests inherited the missing-fixture-config bug from darnitdevorg#370. Rebasing onto the updated 028 pulls the tracked `.baseline.toml` fixtures + the pytest-marker hook forward, so those tests now collect and run. Should-fix: - `import openai` in the OpenAI-backend adversarial test now goes through `pytest.importorskip("openai")`, so a lean env without the `parity-tier2` extra installed skips cleanly instead of exploding with ImportError. - `_dispatch_tool_call` now pins `level=3` and `output_format="json"` the same way `local_path` is pinned. Previously `setdefault` let a rogue model ask for markdown at level 1 and defeat the comparison. - Added a `tier2/conftest.py` `pytest_collection_modifyitems` hook so every Tier 2 test collects with the `integration` marker. Same reasoning as PR darnitdevorg#370's tier1 conftest: CI's `-m unit / -m integration` split would otherwise silently deselect the whole suite. - The cross-provider aggregate script no longer silently reports "0 disagreements" when either provider's message was unparseable. It labels the fixture UNPARSEABLE in the per-fixture summary, lists unparseable fixtures at the bottom, and exits non-zero so CI can tell a "clean run" from a "no signal" run. - The diff-report formatter now accepts a `final_message_filename` keyword so an OpenAI-provider run's failure report points at `openai_final_message.md` instead of hardcoded `skill_final_message.md`. Threaded from `run.py` via `_provider_filename_prefix`. - Same filename thread covers the per-control and unparseable branches, so the diff output is consistent regardless of which provider ran. Workspace sweep: 2635 pass, 15 skip.
2e0c8d7 to
cb6b4be
Compare
…ns into env: vars Addresses Kusari Inspector findings on PR darnitdevorg#371 -- same class of issue as the sibling fix for parity-tier2.yml on the 028 branch: 1. Shell injection risk (HIGH impact / HIGH likelihood): `${{ github.actor }}`, `${{ github.sha }}`, `${{ inputs.fixture_glob }}`, and `${{ inputs.model }}` were interpolated directly into `run:` blocks that had OPENAI_API_KEY set in env. A crafted fixture_glob or model input containing shell metacharacters could execute arbitrary commands and exfiltrate the key. Fix: move each context expression into a step-level `env:` variable and reference it as a quoted shell variable inside the run: block. 2. Supply-chain risk from unpinned actions: Same four actions (actions/checkout, actions/setup-python, astral-sh/setup-uv, actions/upload-artifact) used mutable version tags. Fix: pin each action to the same 40-character commit SHA used by the Claude workflow (both workflows share the identical pinned versions to simplify supply-chain review). Pinned versions match the Claude workflow exactly: actions/checkout 11d5960a326750d5838078e36cf38b85af677262 v4.4.0 actions/setup-python a26af69be951a213d495a4c3e4e4022e16d87065 v5.6.0 astral-sh/setup-uv d0cc045d04ccac9d8b7881df0226f9e82c39688e v6.8.0 actions/upload-artifact ea165f8d65b6e75b540449e92b4886f43607fa02 v4.6.2 All feature-029 workflow-config tests continue to pass (15 tests covering both workflows' governance-critical structure).
…sts integration Reviewer pxp928 flagged one blocker (rebase-driven) and six should-fix items on darnitdevorg#371. This commit addresses all of them. Blocker: - Both SC-007 tests inherited the missing-fixture-config bug from darnitdevorg#370. Rebasing onto the updated 028 pulls the tracked `.baseline.toml` fixtures + the pytest-marker hook forward, so those tests now collect and run. Should-fix: - `import openai` in the OpenAI-backend adversarial test now goes through `pytest.importorskip("openai")`, so a lean env without the `parity-tier2` extra installed skips cleanly instead of exploding with ImportError. - `_dispatch_tool_call` now pins `level=3` and `output_format="json"` the same way `local_path` is pinned. Previously `setdefault` let a rogue model ask for markdown at level 1 and defeat the comparison. - Added a `tier2/conftest.py` `pytest_collection_modifyitems` hook so every Tier 2 test collects with the `integration` marker. Same reasoning as PR darnitdevorg#370's tier1 conftest: CI's `-m unit / -m integration` split would otherwise silently deselect the whole suite. - The cross-provider aggregate script no longer silently reports "0 disagreements" when either provider's message was unparseable. It labels the fixture UNPARSEABLE in the per-fixture summary, lists unparseable fixtures at the bottom, and exits non-zero so CI can tell a "clean run" from a "no signal" run. - The diff-report formatter now accepts a `final_message_filename` keyword so an OpenAI-provider run's failure report points at `openai_final_message.md` instead of hardcoded `skill_final_message.md`. Threaded from `run.py` via `_provider_filename_prefix`. - Same filename thread covers the per-control and unparseable branches, so the diff output is consistent regardless of which provider ran. Workspace sweep: 2635 pass, 15 skip.
cb6b4be to
21ab2d5
Compare
…ns into env: vars Addresses Kusari Inspector findings on PR darnitdevorg#371 -- same class of issue as the sibling fix for parity-tier2.yml on the 028 branch: 1. Shell injection risk (HIGH impact / HIGH likelihood): `${{ github.actor }}`, `${{ github.sha }}`, `${{ inputs.fixture_glob }}`, and `${{ inputs.model }}` were interpolated directly into `run:` blocks that had OPENAI_API_KEY set in env. A crafted fixture_glob or model input containing shell metacharacters could execute arbitrary commands and exfiltrate the key. Fix: move each context expression into a step-level `env:` variable and reference it as a quoted shell variable inside the run: block. 2. Supply-chain risk from unpinned actions: Same four actions (actions/checkout, actions/setup-python, astral-sh/setup-uv, actions/upload-artifact) used mutable version tags. Fix: pin each action to the same 40-character commit SHA used by the Claude workflow (both workflows share the identical pinned versions to simplify supply-chain review). Pinned versions match the Claude workflow exactly: actions/checkout 11d5960a326750d5838078e36cf38b85af677262 v4.4.0 actions/setup-python a26af69be951a213d495a4c3e4e4022e16d87065 v5.6.0 astral-sh/setup-uv d0cc045d04ccac9d8b7881df0226f9e82c39688e v6.8.0 actions/upload-artifact ea165f8d65b6e75b540449e92b4886f43607fa02 v4.6.2 All feature-029 workflow-config tests continue to pass (15 tests covering both workflows' governance-critical structure).
…sts integration Reviewer pxp928 flagged one blocker (rebase-driven) and six should-fix items on darnitdevorg#371. This commit addresses all of them. Blocker: - Both SC-007 tests inherited the missing-fixture-config bug from darnitdevorg#370. Rebasing onto the updated 028 pulls the tracked `.baseline.toml` fixtures + the pytest-marker hook forward, so those tests now collect and run. Should-fix: - `import openai` in the OpenAI-backend adversarial test now goes through `pytest.importorskip("openai")`, so a lean env without the `parity-tier2` extra installed skips cleanly instead of exploding with ImportError. - `_dispatch_tool_call` now pins `level=3` and `output_format="json"` the same way `local_path` is pinned. Previously `setdefault` let a rogue model ask for markdown at level 1 and defeat the comparison. - Added a `tier2/conftest.py` `pytest_collection_modifyitems` hook so every Tier 2 test collects with the `integration` marker. Same reasoning as PR darnitdevorg#370's tier1 conftest: CI's `-m unit / -m integration` split would otherwise silently deselect the whole suite. - The cross-provider aggregate script no longer silently reports "0 disagreements" when either provider's message was unparseable. It labels the fixture UNPARSEABLE in the per-fixture summary, lists unparseable fixtures at the bottom, and exits non-zero so CI can tell a "clean run" from a "no signal" run. - The diff-report formatter now accepts a `final_message_filename` keyword so an OpenAI-provider run's failure report points at `openai_final_message.md` instead of hardcoded `skill_final_message.md`. Threaded from `run.py` via `_provider_filename_prefix`. - Same filename thread covers the per-control and unparseable branches, so the diff output is consistent regardless of which provider ran. Workspace sweep: 2635 pass, 15 skip.
21ab2d5 to
0e0fe5a
Compare
…ns into env: vars Addresses Kusari Inspector findings on PR darnitdevorg#371 -- same class of issue as the sibling fix for parity-tier2.yml on the 028 branch: 1. Shell injection risk (HIGH impact / HIGH likelihood): `${{ github.actor }}`, `${{ github.sha }}`, `${{ inputs.fixture_glob }}`, and `${{ inputs.model }}` were interpolated directly into `run:` blocks that had OPENAI_API_KEY set in env. A crafted fixture_glob or model input containing shell metacharacters could execute arbitrary commands and exfiltrate the key. Fix: move each context expression into a step-level `env:` variable and reference it as a quoted shell variable inside the run: block. 2. Supply-chain risk from unpinned actions: Same four actions (actions/checkout, actions/setup-python, astral-sh/setup-uv, actions/upload-artifact) used mutable version tags. Fix: pin each action to the same 40-character commit SHA used by the Claude workflow (both workflows share the identical pinned versions to simplify supply-chain review). Pinned versions match the Claude workflow exactly: actions/checkout 11d5960a326750d5838078e36cf38b85af677262 v4.4.0 actions/setup-python a26af69be951a213d495a4c3e4e4022e16d87065 v5.6.0 astral-sh/setup-uv d0cc045d04ccac9d8b7881df0226f9e82c39688e v6.8.0 actions/upload-artifact ea165f8d65b6e75b540449e92b4886f43607fa02 v4.6.2 All feature-029 workflow-config tests continue to pass (15 tests covering both workflows' governance-critical structure).
…sts integration Reviewer pxp928 flagged one blocker (rebase-driven) and six should-fix items on darnitdevorg#371. This commit addresses all of them. Blocker: - Both SC-007 tests inherited the missing-fixture-config bug from darnitdevorg#370. Rebasing onto the updated 028 pulls the tracked `.baseline.toml` fixtures + the pytest-marker hook forward, so those tests now collect and run. Should-fix: - `import openai` in the OpenAI-backend adversarial test now goes through `pytest.importorskip("openai")`, so a lean env without the `parity-tier2` extra installed skips cleanly instead of exploding with ImportError. - `_dispatch_tool_call` now pins `level=3` and `output_format="json"` the same way `local_path` is pinned. Previously `setdefault` let a rogue model ask for markdown at level 1 and defeat the comparison. - Added a `tier2/conftest.py` `pytest_collection_modifyitems` hook so every Tier 2 test collects with the `integration` marker. Same reasoning as PR darnitdevorg#370's tier1 conftest: CI's `-m unit / -m integration` split would otherwise silently deselect the whole suite. - The cross-provider aggregate script no longer silently reports "0 disagreements" when either provider's message was unparseable. It labels the fixture UNPARSEABLE in the per-fixture summary, lists unparseable fixtures at the bottom, and exits non-zero so CI can tell a "clean run" from a "no signal" run. - The diff-report formatter now accepts a `final_message_filename` keyword so an OpenAI-provider run's failure report points at `openai_final_message.md` instead of hardcoded `skill_final_message.md`. Threaded from `run.py` via `_provider_filename_prefix`. - Same filename thread covers the per-control and unparseable branches, so the diff output is consistent regardless of which provider ran. Workspace sweep: 2635 pass, 15 skip.
0e0fe5a to
d70f8d6
Compare
…ocol (Fixes darnitdevorg#368) Extends feature 028's parity test suite with a second Tier 2 provider adapter. Introduces a shared `SkillInvocationBackend` Protocol so future adapters (Gemini, xAI, self-hosted) slot in without touching the shared runner, differ, parser, or artifact writer. Two-Environment governance: `parity-tier2` (Claude, feature 028) and `parity-tier2-openai` (OpenAI, this feature) each hold their own reviewer list and secret. `OPENAI_API_KEY` never appears in any workflow other than `parity-tier2-openai.yml`; `ANTHROPIC_API_KEY` never appears in the OpenAI workflow. Enforced by a workflow-config test that iterates `.github/workflows/*.yml` and asserts the exclusivity property. Protocol seam: - `tests/darnit/parity/tier2/backends/base.py` -- @runtime_checkable Protocol with `name`, async `invoke(fixture_dir, model, max_turns)`, classmethod `check_env()`. `SkillInvocationResult` frozen dataclass gains a `turn_cap_exhausted: bool` field (default False). - `tests/darnit/parity/tier2/backends/claude_agent_sdk.py` -- feature 028's `invoke_skill` refactored into a class satisfying the Protocol. Body unchanged. - `tests/darnit/parity/tier2/claude_agent_sdk_client.py` -- backwards- compat shim; feature 028's existing tests continue to import from this path without change. - `tests/darnit/parity/tier2/backends/openai_backend.py` -- new OpenAI Chat Completions API backend. Hand-rolled tool-call loop, stateless per invocation, `temperature=0.0` for reproducibility. Registers the `audit_openssf_baseline` MCP tool as an OpenAI function-callable tool; `_dispatch_tool_call` FORCES `local_path=str(fixture_dir)` so a rogue model cannot make the tool wander outside the fixture (contract B-17, verified by adversarial test). - `tests/darnit/parity/tier2/backends/noop.py` -- test-only NoopBackend used by conformance and extensibility tests. Not registered in BACKEND_REGISTRY by default; tests inject it via `run.main(backends={"noop": NoopBackend})`. - `tests/darnit/parity/tier2/backends/__init__.py` -- BACKEND_REGISTRY dict is the single source of truth for `--backend <name>` lookup. Runner extensions: - `--backend <name>` CLI flag, default `claude_agent_sdk` (preserves feature 028 behavior). - `--model <name>` and `--max-turns <int>` flags (each backend's workflow YAML supplies its provider-appropriate pinned defaults). - New outcome `turn_cap_exhausted` with exit code 5. Diagnostically separate from `unparseable` (exit 2) and `per_control_disagree` (exit 1). Maps to a different fix (adjust prompt / raise cap, not adjust parser). - `main(argv, backends=)` accepts an optional backends dict override for test-side injection (SC-007 -- proves the Protocol seam works without touching shared modules). - Preflight audit log includes backend + model alongside actor + SHA. - Artifact filename convention: `openai_final_message.md` for the OpenAI backend; feature 028's `skill_final_message.md` preserved for Claude so downstream analysis scripts don't churn. OpenAI workflow (`.github/workflows/parity-tier2-openai.yml`): - Manual dispatch only. - `environment: parity-tier2-openai` -- REVIEWER APPROVAL REQUIRED before OPENAI_API_KEY is exposed (configure in GitHub UI; NOT in this YAML). - `permissions: contents: read` only. - Pinned model default `gpt-4o-2024-08-06` -- version-suffixed. A moving alias like `gpt-4o` would fail the SC-010 workflow-config test that regexes the default for a versioned pattern. Bumping the default requires a PR editing the YAML -- reviewable, correlatable with any subsequent test-result changes. - Artifact upload with `if: always()`. Zero product package changes. Feature 028's `test_no_product_changes.py` guard continues to cover `packages/*/src/`. Added `openai>=1.50` to the workspace-level dev group ONLY; `packages/darnit/pyproject.toml` and `packages/darnit-baseline/pyproject.toml` untouched. Tests: 32 new (protocol conformance, extensibility, OpenAI adversarial including turn-cap-exhausted + local_path guard + shared-parser compatibility, workflow config for both YAMLs, shim exports). Full workspace sweep: 2621 passed, 15 skipped, 0 failures. Closes darnitdevorg#368. Sibling to darnitdevorg#367 (feature 027) and darnitdevorg#370 (feature 028); stacks on darnitdevorg#370.
…022 / US3) Local maintainer script (`tests/darnit/parity/tier2/scripts/aggregate_provider_diff.py`) that reads Tier 2 artifact bundles from both providers (Claude via `skill_final_message.md`, OpenAI via `openai_final_message.md`) and produces a Markdown table per fixture showing where the two providers' final assistant messages agree or disagree on per-control status. Not invoked by CI (US3 is P3 in the spec; the useful signal is a local investigation, not an every-run automated check). Documented in quickstart.md as a maintainer workflow. Ten unit tests cover the parsing + diff logic (agreement, disagreement, missing artifacts, discovery-across-roots, exit codes) so the script doesn't silently rot. Closes T022 in feature 029's task list. Completes US3.
…ns into env: vars Addresses Kusari Inspector findings on PR darnitdevorg#371 -- same class of issue as the sibling fix for parity-tier2.yml on the 028 branch: 1. Shell injection risk (HIGH impact / HIGH likelihood): `${{ github.actor }}`, `${{ github.sha }}`, `${{ inputs.fixture_glob }}`, and `${{ inputs.model }}` were interpolated directly into `run:` blocks that had OPENAI_API_KEY set in env. A crafted fixture_glob or model input containing shell metacharacters could execute arbitrary commands and exfiltrate the key. Fix: move each context expression into a step-level `env:` variable and reference it as a quoted shell variable inside the run: block. 2. Supply-chain risk from unpinned actions: Same four actions (actions/checkout, actions/setup-python, astral-sh/setup-uv, actions/upload-artifact) used mutable version tags. Fix: pin each action to the same 40-character commit SHA used by the Claude workflow (both workflows share the identical pinned versions to simplify supply-chain review). Pinned versions match the Claude workflow exactly: actions/checkout 11d5960a326750d5838078e36cf38b85af677262 v4.4.0 actions/setup-python a26af69be951a213d495a4c3e4e4022e16d87065 v5.6.0 astral-sh/setup-uv d0cc045d04ccac9d8b7881df0226f9e82c39688e v6.8.0 actions/upload-artifact ea165f8d65b6e75b540449e92b4886f43607fa02 v4.6.2 All feature-029 workflow-config tests continue to pass (15 tests covering both workflows' governance-critical structure).
…sts integration Reviewer pxp928 flagged one blocker (rebase-driven) and six should-fix items on darnitdevorg#371. This commit addresses all of them. Blocker: - Both SC-007 tests inherited the missing-fixture-config bug from darnitdevorg#370. Rebasing onto the updated 028 pulls the tracked `.baseline.toml` fixtures + the pytest-marker hook forward, so those tests now collect and run. Should-fix: - `import openai` in the OpenAI-backend adversarial test now goes through `pytest.importorskip("openai")`, so a lean env without the `parity-tier2` extra installed skips cleanly instead of exploding with ImportError. - `_dispatch_tool_call` now pins `level=3` and `output_format="json"` the same way `local_path` is pinned. Previously `setdefault` let a rogue model ask for markdown at level 1 and defeat the comparison. - Added a `tier2/conftest.py` `pytest_collection_modifyitems` hook so every Tier 2 test collects with the `integration` marker. Same reasoning as PR darnitdevorg#370's tier1 conftest: CI's `-m unit / -m integration` split would otherwise silently deselect the whole suite. - The cross-provider aggregate script no longer silently reports "0 disagreements" when either provider's message was unparseable. It labels the fixture UNPARSEABLE in the per-fixture summary, lists unparseable fixtures at the bottom, and exits non-zero so CI can tell a "clean run" from a "no signal" run. - The diff-report formatter now accepts a `final_message_filename` keyword so an OpenAI-provider run's failure report points at `openai_final_message.md` instead of hardcoded `skill_final_message.md`. Threaded from `run.py` via `_provider_filename_prefix`. - Same filename thread covers the per-control and unparseable branches, so the diff output is consistent regardless of which provider ran. Workspace sweep: 2635 pass, 15 skip.
d70f8d6 to
db4a8c3
Compare
Summary
Adds a second Tier 2 provider adapter to feature 028's parity test suite. Introduces a shared
SkillInvocationBackendProtocol so future adapters (Gemini, xAI, self-hosted) slot in without touching the shared runner, differ, parser, or artifact writer.Motivated by issue #368: users of darnit through OpenAI-backed coding assistants (Cursor with GPT-4, ChatGPT-in-IDE, custom OpenAI agents) deserve the same drift-detection surface feature 028 gave Claude Code users. Without this, a silent WARN-to-PASS reclassification by an OpenAI-based agent would go undetected while the Claude path stays honest.
Detailed rationale in the commit body.
What ships
tests/darnit/parity/tier2/backends/-- new package: Protocol (base.py), Claude adapter refactored from feature 028 (claude_agent_sdk.py), OpenAI Chat Completions adapter (openai_backend.py), test-onlyNoopBackend,BACKEND_REGISTRYtests/darnit/parity/tier2/claude_agent_sdk_client.py-- backwards-compat shim; feature 028's tests keep working unchanged--backend,--model,--max-turns; newturn_cap_exhaustedoutcome (exit code 5);main(argv, backends=)for test-side backend injection.github/workflows/parity-tier2-openai.yml-- manual dispatch,environment: parity-tier2-openaigated with required reviewers,OPENAI_API_KEYat Environment scope only, pinnedgpt-4o-2024-08-06defaultConstitution + governance notes
parity-tier2(Claude, test(parity): two-tier audit parity test suite (Fixes #366) [stacked on #365] #370) andparity-tier2-openai(OpenAI, this PR) each hold their own reviewer list and secret. A reviewer authorized for OpenAI spend need not be trusted for Anthropic spend, and vice versa. Enforced at the GitHub Actions Environment layer + mechanically verified by a workflow-config test that iterates every.github/workflows/*.ymland asserts key exclusivity.gpt-4oalone) as the default. Bumping the pin requires a PR edit -- explicit, reviewable, correlatable with any subsequent test-result changes. Reproducibility is load-bearing for a diagnostic.Load-bearing safety properties (test-enforced)
test_backend_protocol_conformance.pyparametrizes overBACKEND_REGISTRY+NoopBackend; all passisinstancecheck.test_backend_extensibility.pyinjects an inline backend viamain(backends=...)and confirms invocation without any edit to shared modules.turn_cap_exhausted=Trueand runner exits 5._dispatch_tool_calloverrides the model'slocal_pathargument with the fixture directory. A rogue model cannot make the audit wander outside the fixture.OPENAI_API_KEYappears only inparity-tier2-openai.yml.OPENAI_API_KEY, OpenAI workflow has noANTHROPIC_API_KEY. Two explicit tests.SkillReport.parse(). Guards against silent parser fork.Test plan
uv run pytest tests/ -q-- expect 2621 passed, 15 skipped, 0 failuresuv run pytest tests/darnit/parity/tier2/ -q-- expect 53 passeduv run ruff check .-- cleanuv run python -m tests.darnit.parity.tier2.run --backend openai --fixture-glob 'all_pass_repo' --dry-run --artifact-dir /tmp/parity-openai-dryrun-- writesopenai_final_message.md,mcp_tool_result.json,diff_report.md,metadata.jsongrep -r 'OPENAI_API_KEY' .github/workflows/-- returns matches ONLY inparity-tier2-openai.ymlparity-tier2-openaiEnvironment): dispatch the workflow with defaultfixture_glob='*'and defaultmodel='gpt-4o-2024-08-06'; verify the reviewer-approval gate fires; run to completion; downloadparity-artifacts-openaibundleBefore-merge maintainer actions (manual GitHub UI)
Automated tests verify the workflow YAML shape but cannot verify the Environment's UI-side configuration. Before dispatching Tier 2 OpenAI in production:
parity-tier2-openaiunder Settings -> Environments indarnitdevorg/darnitOPENAI_API_KEYat the ENVIRONMENT level (NOT repo level)OPENAI_API_KEYReviewer checklist for the governance-critical YAML
on:block contains onlyworkflow_dispatchenvironment: parity-tier2-openaipermissions.contents: readand nowritescopes anywhereOPENAI_API_KEYreferenced only in the SDK-invocation step'senv:block, sourced fromsecrets.OPENAI_API_KEY(Environment secret)if: always()gpt-4o-YYYY-MM-DDor equivalent)api_keyor similar workflow inputFollow-ups
Rebase plan
git fetch upstream && git rebase upstream/mainon this branch;git push --force-with-lease. Diff collapses to feature 029 only.