Skip to content

test(parity): add OpenAI Tier 2 backend + SkillInvocationBackend Protocol (Fixes #368) [stacked on #370] - #371

Merged
mlieberman85 merged 4 commits into
darnitdevorg:mainfrom
mlieberman85:029-openai-parity-adapter
Aug 14, 2026
Merged

test(parity): add OpenAI Tier 2 backend + SkillInvocationBackend Protocol (Fixes #368) [stacked on #370]#371
mlieberman85 merged 4 commits into
darnitdevorg:mainfrom
mlieberman85:029-openai-parity-adapter

Conversation

@mlieberman85

Copy link
Copy Markdown
Contributor

STACKED ON #370. Do not merge before #370. This branch is based on 028-audit-parity-tests. Until #370 lands, this PR's diff visually includes all of #370's changes plus feature 029. After #370 merges, rebase onto main and the diff collapses to feature 029 only.

Summary

Adds a second Tier 2 provider adapter to feature 028's parity test suite. Introduces a shared SkillInvocationBackend Protocol 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-only NoopBackend, BACKEND_REGISTRY
  • tests/darnit/parity/tier2/claude_agent_sdk_client.py -- backwards-compat shim; feature 028's tests keep working unchanged
  • Runner extensions: --backend, --model, --max-turns; new turn_cap_exhausted outcome (exit code 5); main(argv, backends=) for test-side backend injection
  • .github/workflows/parity-tier2-openai.yml -- manual dispatch, environment: parity-tier2-openai gated with required reviewers, OPENAI_API_KEY at Environment scope only, pinned gpt-4o-2024-08-06 default
  • Zero product package changes -- inherited from feature 028's guard

Constitution + governance notes

  • II (Conservative-by-Default) reinforced across a second provider: strengthens the "WARN counts as FAIL" invariant against OpenAI-side silent reinterpretation.
  • Two-Environment governance: parity-tier2 (Claude, test(parity): two-tier audit parity test suite (Fixes #366) [stacked on #365] #370) and parity-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/*.yml and asserts key exclusivity.
  • SC-010 pinned versioned model: a regex-based workflow-config test rejects moving aliases (gpt-4o alone) 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)

  • SC-005 Protocol conformance: test_backend_protocol_conformance.py parametrizes over BACKEND_REGISTRY + NoopBackend; all pass isinstance check.
  • SC-007 extensibility: test_backend_extensibility.py injects an inline backend via main(backends=...) and confirms invocation without any edit to shared modules.
  • SC-011 turn_cap_exhausted: adversarial test with a mocked SDK that always returns tool_calls (never text); asserts backend returns turn_cap_exhausted=True and runner exits 5.
  • B-17 malicious local_path: adversarial test proves _dispatch_tool_call overrides the model's local_path argument with the fixture directory. A rogue model cannot make the audit wander outside the fixture.
  • SC-002 key exclusivity: pure-Python file iteration verifies OPENAI_API_KEY appears only in parity-tier2-openai.yml.
  • OW-8/OW-9 cross-contamination guard: Claude workflow has no OPENAI_API_KEY, OpenAI workflow has no ANTHROPIC_API_KEY. Two explicit tests.
  • FR-14 (MC2 shared parser): an OpenAI-shaped Markdown response is parseable by feature 028's shared SkillReport.parse(). Guards against silent parser fork.

Test plan

  • uv run pytest tests/ -q -- expect 2621 passed, 15 skipped, 0 failures
  • uv run pytest tests/darnit/parity/tier2/ -q -- expect 53 passed
  • uv run ruff check . -- clean
  • Manual dry-run: uv run python -m tests.darnit.parity.tier2.run --backend openai --fixture-glob 'all_pass_repo' --dry-run --artifact-dir /tmp/parity-openai-dryrun -- writes openai_final_message.md, mcp_tool_result.json, diff_report.md, metadata.json
  • Manual grep: grep -r 'OPENAI_API_KEY' .github/workflows/ -- returns matches ONLY in parity-tier2-openai.yml
  • Manual (post-merge, in the parity-tier2-openai Environment): dispatch the workflow with default fixture_glob='*' and default model='gpt-4o-2024-08-06'; verify the reviewer-approval gate fires; run to completion; download parity-artifacts-openai bundle

Before-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:

  • Create GitHub Environment parity-tier2-openai under Settings -> Environments in darnitdevorg/darnit
  • Add required reviewers (authorized maintainers only)
  • Add OPENAI_API_KEY at the ENVIRONMENT level (NOT repo level)
  • Confirm no other Environment on the repo also holds OPENAI_API_KEY

Reviewer checklist for the governance-critical YAML

  • on: block contains only workflow_dispatch
  • Job declares environment: parity-tier2-openai
  • permissions.contents: read and no write scopes anywhere
  • OPENAI_API_KEY referenced only in the SDK-invocation step's env: block, sourced from secrets.OPENAI_API_KEY (Environment secret)
  • Preflight audit step logs actor + SHA + fixture_glob + model BEFORE the SDK step
  • Artifact upload step has if: always()
  • Model input default matches the version-suffixed pattern (gpt-4o-YYYY-MM-DD or equivalent)
  • No api_key or similar workflow input

Follow-ups

Rebase plan

@kusari-inspector

kusari-inspector Bot commented Aug 11, 2026

Copy link
Copy Markdown

Kusari Inspector

Kusari Analysis Results:

Proceed with these changes

✅ No Flagged Issues Detected
All values appear to be within acceptable risk parameters.

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.


@kusari-inspector rerun - Trigger a re-analysis of this PR
@kusari-inspector feedback [your message] - Send feedback to our AI and team
See Kusari's documentation for setup and configuration.
Commit: f007869, performed at: 2026-08-12T17:57:56Z

Found this helpful? Give it a 👍 or 👎 reaction!

run: uv sync --extra dev

- name: Preflight audit log (OW-10)
run: |

@kusari-inspector kusari-inspector Bot Aug 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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: |

@kusari-inspector kusari-inspector Bot Aug 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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: |

@kusari-inspector kusari-inspector Bot Aug 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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: |

@kusari-inspector kusari-inspector Bot Aug 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@kusari-inspector kusari-inspector Bot Aug 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread .github/workflows/parity-tier2.yml Outdated

steps:
- name: Checkout
uses: actions/checkout@v4

@kusari-inspector kusari-inspector Bot Aug 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@mlieberman85

Copy link
Copy Markdown
Contributor Author

Added T022 (US3 aggregate cross-provider diff script) as a follow-on commit c09bfea. Local maintainer tool at tests/darnit/parity/tier2/scripts/aggregate_provider_diff.py; 10 unit tests. Completes US3, so PR now covers all three user stories from the spec.

@kusari-inspector

Copy link
Copy Markdown

Kusari PR Analysis rerun based on - c09bfea performed at: 2026-08-11T20:24:48Z - link to updated analysis

@pxp928

pxp928 commented Aug 12, 2026

Copy link
Copy Markdown

@kusari-inspector rerun

@kusari-inspector

Copy link
Copy Markdown

🔄 Run triggered at 15:30:26 UTC. Starting fresh analysis...

@kusari-inspector

Copy link
Copy Markdown

Kusari PR Analysis rerun based on - c09bfea performed at: 2026-08-12T15:32:30Z - link to updated analysis

@pxp928

pxp928 commented Aug 12, 2026

Copy link
Copy Markdown

@kusari-inspector rerun

@kusari-inspector

Copy link
Copy Markdown

🔄 Run triggered at 16:14:59 UTC. Starting fresh analysis...

@kusari-inspector

Copy link
Copy Markdown

Kusari PR Analysis rerun based on - c09bfea performed at: 2026-08-12T16:16:29Z - link to updated analysis

mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 12, 2026
…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).
@mlieberman85
mlieberman85 force-pushed the 029-openai-parity-adapter branch from c09bfea to f007869 Compare August 12, 2026 17:57
@kusari-inspector

Copy link
Copy Markdown

Kusari PR Analysis rerun based on - f007869 performed at: 2026-08-12T17:58:30Z - link to updated analysis

@pxp928 pxp928 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 — bare import openai hard-fails with ModuleNotFoundError when the dev extra isn't synced (reproduced). Needs pytest.importorskip. Separately, line 118 does openai.AsyncOpenAI = lambda: mock_client and never restores it, leaking a broken stub into the rest of the pytest session — use monkeypatch.setattr.
  • backends/openai_backend.py:89_dispatch_tool_call pins only local_path; level and output_format are setdefault-ed. A model that chooses level: 1 makes the OpenAI-side audit narrower than _run_mcp_tool's level 3, producing a spurious counts_disagree / exit 1 — the harness reports a parity failure that is really a parameter mismatch. Unknown hallucinated keys reach audit_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 a unit or integration marker. CI runs only -m unit / -m integration, so pytest tests/darnit/parity/ -m unit gives 95 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, and mcp_tool_result.json clobber each other across providers, contradicting both the docstring and the aggregate script's documented single-root mode.
  • diff.py:133 — diff reports hardcode skill_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.

mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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).
@mlieberman85
mlieberman85 force-pushed the 029-openai-parity-adapter branch from f007869 to 77ac9a5 Compare August 13, 2026 15:06
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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).
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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.
@mlieberman85
mlieberman85 force-pushed the 029-openai-parity-adapter branch from 77ac9a5 to 2e0c8d7 Compare August 13, 2026 15:55
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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).
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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.
@mlieberman85
mlieberman85 force-pushed the 029-openai-parity-adapter branch from 2e0c8d7 to cb6b4be Compare August 13, 2026 16:38
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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).
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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.
@mlieberman85
mlieberman85 force-pushed the 029-openai-parity-adapter branch from cb6b4be to 21ab2d5 Compare August 13, 2026 16:43
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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).
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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.
@mlieberman85
mlieberman85 force-pushed the 029-openai-parity-adapter branch from 21ab2d5 to 0e0fe5a Compare August 13, 2026 16:46
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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).
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Aug 13, 2026
…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.
@mlieberman85
mlieberman85 force-pushed the 029-openai-parity-adapter branch from 0e0fe5a to d70f8d6 Compare August 13, 2026 17:48
…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.
@mlieberman85
mlieberman85 force-pushed the 029-openai-parity-adapter branch from d70f8d6 to db4a8c3 Compare August 14, 2026 00:25
@mlieberman85
mlieberman85 merged commit 507dea7 into darnitdevorg:main Aug 14, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants