Skip to content

fix(ci): group review catalog admission/diversity by outage domain, not account - #1474

Closed
seonghobae wants to merge 34 commits into
mainfrom
fix/outage-domain-catalog-cap
Closed

fix(ci): group review catalog admission/diversity by outage domain, not account#1474
seonghobae wants to merge 34 commits into
mainfrom
fix/outage-domain-catalog-cap

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to already-merged .github#1468 ("fix(ci): keep sidecar credential accounts independent"), found by review during this session (a Devin Review finding, checked directly against main's actual merged code before acting — not against the now-closed, superseded PR #1470).

#1468 correctly stopped treating nvidia_nim/nvidia_nim_sub as one model-catalog family (they are independent credentials that may expose different models — matching contextual-orchestrator PR #941/#945). But in doing so, it also let the catalog's admission cap and its free_account_diversity evidence field treat them as two fully independent outage domains. They are not: both resolve to the identical https://integrate.api.nvidia.com/v1 upstream (see PROVIDER_BASE_URLS in scripts/ci/zdr_policy.py, and that table's own nvidia_nim_sub ZDR-scope note, which already said as much).

Two genuinely different questions exist for this credential pair:

  1. Model-catalog identity — may these two credentials be entitled to different models? Yes. Correctly fixed by fix(semgrep): make the pinned image digest authoritative #941/[Fleet incident] Govern orphaned GitHub Actions workflow lifecycle organization-wide #945/fix(ci): keep sidecar credential accounts independent #1468.
  2. Outage-domain identity — would one physical infrastructure outage take both credentials down together? Also yes. fix(ci): keep sidecar credential accounts independent #1468's fix flattened this axis to match axis 1.

Concrete consequences fixed here

  • free_account_diversity reported 2 for a discovery report whose only free routes were these two credentials — falsely reassuring for exactly the decision this evidence exists to support (whether a single outage could empty the free catalog; see docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md's "Monitoring evidence" section, and docs/product-goal-directive.md §8's note on the accepted single-outage-domain risk, both amended in this PR).
  • The admission cap (account_cap, sidecar default 8) let the pair jointly consume up to twice its intended per-endpoint budget — a milder recurrence of the 2026-08-30 orchestrator/free exhaustion incident this cap exists to prevent (documented in docs/product-technical-gap-baseline.md): a shared endpoint's rows could crowd out a smaller, genuinely independent provider's free routes even when that provider had capacity available.

Fix

scripts/ci/contextual_orchestrator_review_policy.py gains a second, distinct grouping, _outage_domain(row), keyed on each row's own base_url evidence — not a second hand-maintained provider-name table, so it cannot silently go stale independently of the base_url evidence the catalog already serves from (the exact failure mode that made the removed PROVIDER_FAMILIES mapping wrong in the first place).

  • The admission cap now groups by outage domain: two same-endpoint credentials share one cap budget, they do not each get their own.
  • A new report field, free_outage_domain_diversity, is added alongside the existing free_account_diversity (additive, not a rename — to avoid further naming churn immediately after fix(ci): keep sidecar credential accounts independent #1468's own rename and docs(product-goal-directive): rename free_family_diversity to free_account_diversity #1471's doc-reference fix).
  • scripts/ci/contextual_orchestrator_review_launcher.py's _with_discovery_counts (which recomputes diversity from full discovery-wide rows, not the narrower per-stage set) restores both fields the same way.
  • account_cap/DEFAULT_ACCOUNT_CAP/the CLI --account-cap flag/the sidecar's ORCHESTRATOR_CATALOG_ACCOUNT_CAP env var names are all left unchanged (still meaningful as "the cap value"; only its grouping was wrong) to minimize collision risk with .github#1469, which was concurrently advancing this same sidecar's pin in the same active window.
  • docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md and docs/product-goal-directive.md §8 both get a short correction pointing future readers at free_outage_domain_diversity for the single-outage-domain-risk question specifically.

Developer experience

Two dedicated regressions reproduce the exact gaps:

  • test_build_catalog_counts_same_vendor_credentials_independently (updated): nvidia_nim + nvidia_nim_sub alone report free_account_diversity == 2 but free_outage_domain_diversity == 1 — the semantic-conflation bug.
  • test_build_catalog_prevents_shared_endpoint_from_crowding_out_independent_providers (new): a shared-endpoint credential pair with far more free rows than an independent provider; the shared endpoint's admissions are correctly capped, leaving room for the independent provider (bytez/openrouter both fully admitted, NVIDIA-domain capped to account_cap, not 2 * account_cap).

Existing tests updated to the corrected, domain-aware expectations: test_build_catalog_applies_account_cap, test_build_catalog_reports_free_account_diversity, and two launcher-facing tests in test_contextual_orchestrator_review_runtime_preflight.py (including a new one, test_discovery_counts_distinguish_account_from_outage_domain_diversity).

Full details in docs/product-technical-gap-baseline.md's new 2026-08-31 entry.

User experience

free_outage_domain_diversity, as reported by the sidecar's policy/preflight evidence, now correctly answers "would a single provider outage empty the free catalog" — the question docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md's accepted-risk monitoring and docs/product-goal-directive.md §8's note both actually need. The catalog's admission cap no longer lets two same-endpoint credentials jointly absorb twice their intended share of the bounded route budget, which is directly relevant to Strix's orchestrator/free reliability now that it is hardcoded off orchestrator/auto (ADR-0003's 2026-08-30 amendment).

Test plan

  • coverage run -m pytest tests -q (full suite) — 2095 passed, 1 skipped, 21 subtests passed
  • coverage report --show-missing — 100% on scripts/ci/
  • interrogate — 100%

Related


Generated by Claude Code


Devin Review

Summary by CodeRabbit

  • 개선 사항

    • 동일한 물리적 엔드포인트를 공유하는 계정은 하나의 장애 도메인으로 집계됩니다.
    • 무료 라우트 보고서에 계정 다양성과 별도로 장애 도메인 다양성 정보가 추가되었습니다.
    • 공유 장애 도메인의 라우팅 할당량이 계정별로 공정하게 분배되어 특정 계정에 편중되지 않습니다.
    • 엔드포인트 주소 형식을 정규화해 동일한 공급자 장애 위험을 일관되게 판단합니다.
  • 문서

    • 무료 라우팅 다양성 및 장애 도메인 기준이 관련 문서에 반영되었습니다.

…ot account

model-catalog family (they are independent credentials that may expose
different models), but in doing so also let the admission cap and
free_account_diversity treat them as two fully independent outage domains.
They are not: both resolve to the identical
https://integrate.api.nvidia.com/v1 upstream (see PROVIDER_BASE_URLS in
scripts/ci/zdr_policy.py, and that table's own nvidia_nim_sub ZDR-scope
note, which already said as much).

Two independent questions exist for this credential pair:
1. Model-catalog identity (may they expose different models?) -- yes, fixed
   correctly by #941/#945/#1468.
2. Outage-domain identity (would one physical outage take both down?) --
   also yes, and #1468 flattened this axis to match axis 1.

Concrete consequences fixed here:
- free_account_diversity reported 2 for a discovery report whose only free
  routes were these two credentials -- falsely reassuring for exactly the
  decision this evidence exists to support (open PR #1437's Strix
  orchestrator/free eligibility gate: would a single outage empty the free
  catalog).
- The admission cap (account_cap, sidecar default 8) let the pair jointly
  consume up to twice its intended per-endpoint budget, crowding out a
  genuinely independent provider's free routes even when it had capacity.

Fix: contextual_orchestrator_review_policy.py gains _outage_domain(row),
keyed on each row's own base_url evidence (not a second hand-maintained
provider-name table, so it cannot go stale independently of the base_url
evidence the catalog already serves from -- the exact failure mode that
made the removed PROVIDER_FAMILIES mapping wrong). The admission cap now
groups by outage domain; a new, additive free_outage_domain_diversity
report field sits alongside the existing free_account_diversity (kept, not
renamed, to avoid further naming churn right after #1468's own rename).
contextual_orchestrator_review_launcher.py's _with_discovery_counts
restores both fields from full discovery rows the same way.

account_cap/DEFAULT_ACCOUNT_CAP/--account-cap/ORCHESTRATOR_CATALOG_ACCOUNT_CAP
names are all left unchanged (still meaningful as "the cap value"; only its
grouping was wrong) to minimize collision risk with .github#1469, which was
concurrently advancing this same sidecar's pin.

Tests: two dedicated regressions (semantic-conflation shape: 2 accounts, 1
domain; crowding-out shape: a shared-endpoint pair with many free rows vs.
an independent provider with few) plus updated existing tests
(test_build_catalog_applies_account_cap and friends, two launcher-facing
tests in test_contextual_orchestrator_review_runtime_preflight.py). Full
suite: 2095 passed, 1 skipped, 21 subtests passed. 100% coverage and 100%
docstring coverage on scripts/ci/.

Not touched: open PR #1437's own gating logic -- its reviewer should read
free_outage_domain_diversity, not free_account_diversity, for the >= 2
eligibility check. Does not revive closed PR #1470 (a different, now-
superseded fix); this is a fresh, narrowly-scoped follow-up found by review
against current main after #1468 merged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 38 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: c295655f-127c-41c1-ad82-6def3c381f89

📥 Commits

Reviewing files that changed from the base of the PR and between ad3d2ce and 1eaa3b3.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md
  • docs/product-technical-gap-baseline.md
  • scripts/ci/contextual_orchestrator_review_launcher.py
  • scripts/ci/contextual_orchestrator_review_policy.py
  • tests/test_contextual_orchestrator_free_pool_enrichment.py
  • tests/test_contextual_orchestrator_review_policy.py
  • tests/test_contextual_orchestrator_review_runtime_preflight.py
  • tests/test_contextual_orchestrator_review_sidecar_contract.py
  • tests/test_noema_removed_file_context.py
  • tests/test_noema_review_gate.py
  • tests/test_opencode_agent_contract.py
📝 Walkthrough

Walkthrough

공유 upstream을 사용하는 계정의 admission cap과 장애 도메인 집계를 base_url 기준으로 통합했다. URL 정규화와 공정한 라운드로빈 admission을 추가했다. 런처 보고서, 테스트, ADR 및 운영 문서를 갱신했다.

Changes

장애 도메인 기반 무료 라우트 정책

Layer / File(s) Summary
장애 도메인 식별과 URL 정규화
scripts/ci/contextual_orchestrator_review_policy.py, tests/test_contextual_orchestrator_review_policy.py
base_url의 scheme, host, 기본 포트, 후행 슬래시를 정규화한다. 잘못된 IPv6와 포트 입력은 예외 없이 fallback 처리한다. 정규화된 URL을 장애 도메인 식별자로 사용한다.
공유 도메인 admission과 다양성 보고
scripts/ci/contextual_orchestrator_review_policy.py, tests/test_contextual_orchestrator_review_policy.py
account_cap을 장애 도메인별로 적용한다. 공유 도메인의 계정은 라운드로빈 순서로 admission한다. free_outage_domain_diversity를 보고서에 추가한다.
런처 통합과 전체 discovery 검증
scripts/ci/contextual_orchestrator_review_launcher.py, tests/test_contextual_orchestrator_review_runtime_preflight.py, CHANGELOG.md, docs/adr/..., docs/product-goal-directive.md, docs/product-technical-gap-baseline.md
전체 discovery 결과에서 계정 다양성과 장애 도메인 다양성을 다시 계산하도록 런처와 테스트를 갱신한다. 정책 변경을 관련 문서에 기록한다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: 🟡 Moderate · up to ad3d2

The PR improves shared-upstream admission and outage-diversity reporting, but the current implementation can still prioritize priced or non-ZDR routes over free/ZDR routes under a small cap, reducing the intended free catalog. Documentation also reports an incorrect default cap, and IPv6 endpoint normalization can produce incorrect domain grouping, so merge should wait for these fixes.

Sequence Diagram(s)

sequenceDiagram
  participant Discovery
  participant contextual_orchestrator_review_launcher
  participant build_zdr_prioritized_catalog
  Discovery->>contextual_orchestrator_review_launcher: 무료 라우트와 base_url 제공
  contextual_orchestrator_review_launcher->>build_zdr_prioritized_catalog: 계정 및 장애 도메인 집계 함수 전달
  build_zdr_prioritized_catalog-->>contextual_orchestrator_review_launcher: catalog과 diversity 보고서 반환
  contextual_orchestrator_review_launcher-->>Discovery: 전체 discovery 기준 다양성 결과 기록
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 계정이 아닌 장애 도메인 기준으로 리뷰 카탈로그의 admission과 diversity를 그룹화하는 핵심 변경을 정확하고 간결하게 설명합니다.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 31 functions across 4 files. (4 skipped: 4…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 31 functions across 4 files. (4 skipped: 4 unsupported.)

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/outage-domain-catalog-cap

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

Devin Review finding on #1474: _outage_domain(row) compared raw base_url
strings, so two rows for the identical physical endpoint but spelled
differently (hostname case, an explicit default port like :443, a trailing
slash) would be treated as two different outage domains -- directly
undermining the fix free_outage_domain_diversity/the admission cap exist
to provide.

Verified against this codebase's actual code before acting: every
DiscoveredModel.chat_base_url in contextual-orchestrator traces to one of a
fixed set of hardcoded string literals (nvidia_nim/nvidia_nim_sub are
byte-identical), and this repo's launcher copies that value verbatim,
falling back only to zdr_policy.PROVIDER_BASE_URLS (confirmed
byte-identical to the same literals). So this repo's one production caller
(the sidecar/launcher) cannot produce inconsistent spellings today. It IS
reachable through this script's own public --discovery-report CLI, which
reads an arbitrary JSON file and isn't restricted to the launcher's exact
generation path, and isn't wired into any current production workflow --
latent, not live, but real for that public surface. The fix is cheap and
behavior-neutral on every input the sidecar produces today, so it's applied
rather than left as an unstated assumption.

_outage_domain now compares _normalize_base_url(row["base_url"]):
lowercases scheme/host (case-insensitive per RFC 3986), drops an explicit
port equal to the scheme's default, strips one trailing slash from the
path. A different host, non-default port, path, or scheme still stays
genuinely distinct. Falls back to a lowercased/stripped whole-string
comparison (never raises) for anything unparseable into a scheme, host,
and numeric port -- including a non-numeric port substring, which
urlsplit(...).port raises ValueError on.

Five new tests: the exact equivalent-spelling cases Devin named (case,
default port, trailing slash), genuine distinctions still separate,
no-raise on malformed/empty/bad-port input, and one end-to-end test
through build_zdr_prioritized_catalog with two differently-spelled rows
for the same endpoint (confirms the admission cap and diversity count both
honor the normalization, not just the unit-level helper).

Full suite: 2106 passed, 1 skipped, 21 subtests passed. 100% coverage
(including the new fallback branch) and 100% docstring coverage on
scripts/ci/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw

Copy link
Copy Markdown
Contributor Author

Addressed the Devin Review finding on _outage_domain(row) at scripts/ci/contextual_orchestrator_review_policy.py:84 ("equivalent endpoint spellings split outage domains").

Verified it's real, but latent, not live, before fixing it. Traced every DiscoveredModel.chat_base_url in contextual-orchestrator back to its source: all six providers (including nvidia_nim/nvidia_nim_sub) resolve from hardcoded Python string literals in contextual_orchestrator/model_discovery.py (the two NVIDIA entries are byte-identical), and this repo's launcher (_report_rows) copies that value verbatim, falling back only to zdr_policy.PROVIDER_BASE_URLS — confirmed byte-identical to the same literals for all five tracked providers. So this repo's one production caller (the sidecar/launcher) cannot actually produce two differently-spelled rows for the same endpoint today. It is reachable through this script's own public --discovery-report CLI (reads an arbitrary JSON file, not restricted to the launcher's generation path) — not wired into any current production workflow, so latent rather than live, but real for that public surface.

Given the fix is cheap and behavior-neutral on every input the sidecar produces today, applied it rather than leaving it as an unstated assumption: _outage_domain now compares _normalize_base_url(row["base_url"]) — lowercases scheme/host, drops an explicit port equal to the scheme's default (:443/:80), strips one trailing slash — while a different host, non-default port, path, or scheme still stays genuinely distinct. Falls back to a lowercased/stripped whole-string comparison (never raises) for anything unparseable into a scheme, host, and numeric port, including a non-numeric port substring (urlsplit(...).port raises ValueError on that).

Five new tests: the exact equivalent-spelling cases named (hostname case, default port, trailing slash), genuine distinctions still separate, no-raise on malformed/empty/bad-port input, and one end-to-end test through build_zdr_prioritized_catalog with two differently-spelled rows for the same endpoint confirming both the admission cap and the diversity count honor the normalization.

Full suite: 2106 passed, 1 skipped, 21 subtests passed. 100% coverage (including the new fallback branch) and 100% docstring coverage on scripts/ci/.


Generated by Claude Code

devin-ai-integration[bot]

This comment was marked as resolved.

Two Devin Review findings on this PR, one severe.

Severe: shared-cap starvation within a domain. Grouping the admission cap
by outage domain (this PR's own earlier fix) correctly stopped
nvidia_nim/nvidia_nim_sub from jointly consuming 2x the intended budget
across domains -- but the admission loop still walked rows in one strict
sorted (cost-tier, ZDR, provider, model) order and admitted greedily until
a domain's cap was reached. Since "nvidia_nim" < "nvidia_nim_sub" in every
real fixture, nvidia_nim's rows always sort first, so nvidia_nim alone
could consume the ENTIRE shared cap before a single nvidia_nim_sub row was
ever considered. Verified concretely: 6 free nvidia_nim rows + 6 free
nvidia_nim_sub rows, account_cap=4 -> nvidia_nim_sub got zero rows. Not
"prevented from taking more than its share" (the bug already fixed), but
"the alphabetically-first credential takes the whole shared budget, the
other gets nothing" -- the same crowding-out problem, now within one
domain instead of across domains.

Fixed with a new _fair_admission_order() reordering step applied before
the existing (otherwise unchanged) greedy admission loop: rows are
partitioned by outage domain (each domain's whole block stays at the
position of its first row's original appearance, so domain-vs-domain
ordering is unaffected), and within any domain contributed to by more than
one account, rows are taken in round-robin turns across those accounts --
one from each account's own priority-ordered queue per round -- instead of
exhausting whichever account sorts first. A domain with only one
contributing account (every provider except the shared NVIDIA pair, as of
this writing) is returned completely untouched.

Real: urlsplit() itself can raise, not only .port. _normalize_base_url's
existing fallback wrapped only the .port property access; urlsplit()
itself raises ValueError for an unmatched IPv6-literal bracket (e.g.
https://[::1/v1, confirmed: "Invalid IPv6 URL"), before any scheme/host is
even available to inspect -- an uncaught exception past this function's own
"must never raise" contract. Fixed by wrapping the urlsplit() call itself
in the same catch-and-fall-back pattern already used for .port.

Noted, not chased further (info-level, optional per this session's
coordinator): hostname canonicalization stops at lowercasing -- a trailing
root-label dot, IDN Unicode-vs-punycode forms, and differently-compressed
IPv6 literals aren't folded together. None of these shapes occur in any
base_url this codebase produces today (every value traces to a fixed set
of hardcoded, already-canonical HTTPS hostnames), so this is documented as
a deliberate residual gap in _normalize_base_url's own docstring rather
than implemented prophylactically.

Tests: two existing tests whose assertions had encoded the starvation
behavior were corrected to the fair-split expectation
(test_build_catalog_applies_account_cap,
test_build_catalog_prevents_shared_endpoint_from_crowding_out_independent_providers);
added an end-to-end regression
(test_build_catalog_shared_domain_cap_does_not_starve_second_account) and
two unit-level tests directly against _fair_admission_order() (untouched
single-account case; visible round-robin reordering with domain-block
position preserved); added a regression for the IPv6 urlsplit() crash.

Full suite: 2111 passed, 1 skipped, 21 subtests passed. 100% coverage
(including the new reordering function and both new fallback branches) and
100% docstring coverage on scripts/ci/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw

Copy link
Copy Markdown
Contributor Author

Addressed the two remaining Devin Review findings on this PR.

🔴 Severe — shared-cap starvation within a domain (scripts/ci/contextual_orchestrator_review_policy.py:374-377). Confirmed concretely before fixing: 6 free nvidia_nim rows + 6 free nvidia_nim_sub rows, account_cap=4nvidia_nim_sub was admitted zero rows. Root cause: the earlier fix correctly made the two credentials share one cap budget, but the admission loop still walks rows in strict sorted order and admits greedily — since "nvidia_nim" < "nvidia_nim_sub" in every real fixture, nvidia_nim alone could exhaust the entire shared budget before nvidia_nim_sub was ever considered.

Fixed with a new _fair_admission_order() reordering step, applied before the existing (otherwise unchanged) greedy loop: rows are partitioned by outage domain (each domain's block stays at its original position relative to other domains — no change for the common single-account-per-domain case), and within a domain shared by more than one account, rows are taken in round-robin turns across those accounts instead of exhausting whichever one sorts first. Re-verified: same scenario now gives nvidia_nim: 2, nvidia_nim_sub: 2. Two existing tests whose assertions had encoded the starvation behavior were corrected; added an end-to-end regression plus two unit-level tests directly against _fair_admission_order().

🟡 Real — malformed IPv6 URLs crash generation (_normalize_base_url, line ~133). Confirmed: urlsplit("https://[::1/v1") itself raises ValueError: Invalid IPv6 URL, before the existing fallback (which only wrapped the .port property access) could catch anything. Fixed by wrapping the urlsplit() call itself in the same catch-and-fall-back pattern. Added a regression test with a malformed IPv6-bracket URL (and its differently-cased twin, confirming both fall back to the same normalized value).

🔍 Info #3 (hostname canonicalization) — left as a documented, deliberate residual gap rather than expanded further: root-label dots, IDN forms, and IPv6-canonical-form differences don't occur in any base_url this codebase produces today (every value traces to a fixed set of hardcoded, already-canonical hostnames), so implementing more exhaustive canonicalization now would be speculative. Noted explicitly in _normalize_base_url's own docstring for whoever adds a provider that might actually need it.

📝 Info #4 — no action, as suggested; the "different path on one host counts as a separate domain" behavior is already documented in _normalize_base_url's existing docstring.

Full suite: 2111 passed, 1 skipped, 21 subtests passed. 100% coverage (including the new reordering function and both new fallback branches) and 100% docstring coverage on scripts/ci/.


Generated by Claude Code

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

…llision

Two Devin Review findings, one a real correctness regression the previous
round's round-robin fix introduced.

Real regression: fairness reordering could drop a free route for a paid
one. _fair_admission_order grouped every row for one outage domain into a
single block, emitted at the position of that domain's first appearance in
the (already tier-sorted) input -- but a domain's rows can span multiple
tiers (e.g. openai contributes both a free and a priced route, one
single-account domain). Grouping by domain first, tier-blind, let a
domain's lower-tier row get pulled into the same block as its higher-tier
row, ahead of a different domain's higher-tier row that only sorted later
because of the (provider, model) tie-break. Verified concretely: sorted
[free openai, free openrouter, priced openai] reordered to [free openai,
priced openai, free openrouter], and with limit=2 the genuinely free
openrouter route got dropped for the priced openai one.

Fixed by scoping the round-robin fairness pass strictly within one
admission-priority tier at a time: eligible_rows and _fair_admission_order
now share one _admission_priority_key() function (sort key and
tier-boundary detector can no longer drift apart), the input is split into
contiguous same-tier runs (safe -- already tier-sorted), and the existing
domain/account round-robin logic (renamed _fair_order_within_tier) applies
independently to each run, then the runs concatenate back in original
order. Re-verified both the tier-priority scenario and the earlier
starvation-fix scenario pass together.

Real: IPv6 host normalization could collide two different endpoints.
urlsplit().hostname strips IPv6 literal brackets ([::1] -> ::1); appending
a port without re-adding them meant [::1]:8443 (host ::1, port 8443) and
[::1:8443] (one IPv6 literal, no separate port) both normalized to the
identical, syntactically-invalid ::1:8443. Fixed by re-wrapping a
colon-bearing host in brackets before conditionally appending a port.

Optional, applied since already in this code: round-robin queues switched
from list.pop(0) (O(n)) to collections.deque.popleft() (O(1)).

Tests: new unit-level and end-to-end regressions for tier-priority
preservation; two new regressions for the IPv6 fix (distinct normalization,
default-port-drop still works); existing _fair_admission_order tests
updated for the now-required zdr_endpoints parameter.

Full suite: 2115 passed, 1 skipped, 21 subtests passed. 100% coverage and
100% docstring coverage on scripts/ci/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw

Copy link
Copy Markdown
Contributor Author

Addressed the round-4 Devin Review findings.

🟡 Real, most urgent — fairness reordering broke route priority. Confirmed concretely before fixing: sorted input [free openai, free openrouter, priced openai] reordered to [free openai, priced openai, free openrouter], and with limit=2 the genuinely free openrouter route was dropped in favor of the priced openai route.

Root cause: _fair_admission_order grouped every row for one outage domain into a single contiguous block emitted at that domain's first appearance — but a domain's rows can span multiple admission-priority tiers (openai alone contributes both a free and a priced route through one single-account domain). Grouping by domain first, tier-blind, let the domain's lower-tier row ride along with its higher-tier row ahead of a different domain's higher-tier row.

Redesigned per your guidance as two composable passes: eligible_rows's sort and _fair_admission_order's tier-boundary detection now share one _admission_priority_key() function (can't drift apart), the input is split into contiguous same-tier runs (safe — already tier-sorted), and the existing domain/account round-robin (renamed _fair_order_within_tier) applies independently within each run, then the runs concatenate back in original order. Re-verified both this scenario and the earlier starvation-fix scenario together, programmatically, before committing — both now correct simultaneously. Added Devin's suggested regression (unit-level, directly against _fair_admission_order) plus an end-to-end one through build_zdr_prioritized_catalog.

🟡 Real — IPv6 domains collide during normalization. Confirmed: urlsplit("https://[::1]:8443/v1").hostname returns ::1 (brackets stripped), so appending the port without re-adding them made [::1]:8443 (host ::1, port 8443) and [::1:8443] (one IPv6 literal, no separate port) both normalize to the identical, syntactically-invalid ::1:8443. Fixed exactly as you described: re-wrap a colon-bearing host in brackets before conditionally appending a port. Reviewed against the current implementation — applies cleanly, and confirmed the earlier malformed-IPv6-bracket fallback (round 3) still works unchanged. Two new regression tests (distinct normalization for the two example URLs; default IPv6 port still dropped correctly).

🔍 #3 (perf, optional) — applied since already in this code: round-robin queues switched from list.pop(0) to collections.deque.popleft().

📝 #4 — no action, as noted.

Full suite: 2115 passed, 1 skipped, 21 subtests passed. 100% coverage and 100% docstring coverage on scripts/ci/.


Generated by Claude Code

devin-ai-integration[bot]

This comment was marked as resolved.

claude added 4 commits August 31, 2026 04:56
scripts/ci/contextual_orchestrator_review_launcher.py's
_routable_discovered_models() unconditionally dropped every discovery row
with evidence_only=True. contextual-orchestrator's OpenRouter
ProviderModelSource hardcodes evidence_only=True for every discovered
model unconditionally -- not computed per model from real evidence, even
though genuine per-model ZDR evidence is fetched and parsed for OpenRouter
in that same module. The upstream half of this bug is being fixed
separately (a dispatched agent, PR forthcoming) -- not touched here.

Consequence for this repo: with 100% of OpenRouter rows carrying
evidence_only=True, this filter excluded ALL OpenRouter rows before
scripts/ci/zdr_policy.py's own purpose-built, already-correct, already-
wired per-route OpenRouter ZDR-feed check (is_zdr_model()'s
openrouter_endpoints_feed branch) ever got a chance to evaluate a single
one -- making that mechanism dead code for OpenRouter specifically, and
leaving OpenRouter contributing zero routes to any pool despite genuinely
offering ZDR-attested free models via its own documented feed.

OpenRouter rows are now exempt from the evidence_only exclusion. A
genuinely non-servable OpenRouter row is still excluded downstream by the
same provider-agnostic chat-capability check every other provider's rows
already go through (is_general_chat_agent_model_id + _has_text_output, in
main()) -- this exemption relies on that existing, independent check, not
on trusting evidence_only's current, wrong, blanket value for OpenRouter.

Sequencing note, verified before writing this: this fix has real,
immediate effect once merged, not only once contextual-orchestrator's own
evidence_only fix and a matching ORCHESTRATOR_PIN_SHA bump also land.
OpenRouter discovery already runs in this sidecar today, and for the
general (non-private, require_zdr=False) pool -- what Noema/OpenCode/
default Strix use -- _zdr_admitted_rows() returns every row unfiltered
regardless of ZDR status; is_zdr_model() only affects sort priority and
tagging there, never admission. So genuinely chat-capable OpenRouter rows
start reaching selection as soon as this merges. What remains gated on the
upstream fix is OpenRouter rows being correctly excluded from
evidence_only on a real per-model basis (e.g. a non-chat listing).
Documented in the function's own docstring and this PR description.

Tests: test_routable_discovered_models_excludes_evidence_only_rows
(existing) corrected to use a non-OpenRouter provider for its
evidence_only=True fixture; new
test_routable_discovered_models_exempts_openrouter_from_evidence_only
confirms both an evidence_only-tagged and untagged OpenRouter row pass
through while a same-shaped row from a different provider does not; a
contract-test assertion pins the exemption's presence in source.

Full suite: 2093 passed, 1 skipped, 21 subtests passed. 100% coverage and
100% docstring coverage on scripts/ci/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw
Devin Review on PR #1476 flagged that the blanket OpenRouter exemption in
_routable_discovered_models() doesn't distinguish "vendored
contextual-orchestrator still has the confirmed blanket
evidence_only=True bug" from "vendored copy now computes evidence_only
correctly per model" (the fix in contextual-orchestrator#950, open, not
yet merged) -- so once #950 merges and ORCHESTRATOR_PIN_SHA bumps past it,
this launcher would keep admitting genuinely evidence-only OpenRouter rows
the corrected upstream code means to exclude.

Considered gating on ORCHESTRATOR_PIN_SHA via git ancestry
(merge-base --is-ancestor against #950's eventual merge commit, reachable
from the sidecar's already-full vendored clone) but #950 has no merge
commit yet, so there is no concrete threshold to gate on, and wiring the
plumbing now (new CLI arg/env var, subprocess git call, sidecar/contract
changes) would be built against a value that doesn't exist.

Implemented instead: _openrouter_reports_per_model_evidence() reads this
run's own discovered OpenRouter rows and turns the exemption off the
moment any row reports evidence_only=False (real per-model evidence).
While every row still reports True (today's exact bug signature), the
exemption stays active. Self-corrects with no pin tracking and no manual
conversion step once #950 merges. Known, accepted limitation documented
in the docstring: a genuinely-fixed vendored copy that happens to report
all-True in one run (feed-fetch failure, or zero attested models that
run) is indistinguishable from the still-buggy signature by this check
alone.

Tests: split the previous mixed-fixture regression into
test_routable_discovered_models_exempts_openrouter_when_every_row_reports_evidence_only
(pre-fix blanket-True shape) and
test_routable_discovered_models_stops_exempting_openrouter_once_a_row_shows_real_evidence
(post-fix mixed shape). Full suite: 2094 passed, 1 skipped, 21 subtests
passed. 100% coverage and 100% docstring coverage on scripts/ci/.

Follow-up recorded in docs/product-technical-gap-baseline.md with an
explicit TODO referencing contextual-orchestrator#950 and this repo's
#1476.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw
…ts in outage-domain key

Round-5 Devin Review found two real bugs left over in the outage-domain
fairness logic:

- _fair_order_within_tier collapsed every domain to one contiguous block
  positioned at that domain's first appearance, which silently displaced
  an unrelated domain's row whenever a shared domain's own rows were not
  already contiguous in priority order (e.g. [A1, B1, A2] became
  [A1, A2, B1], dropping B1 under a tight limit even though it outranked
  A2). Fixed by recording each domain's own global positions up front and
  only reordering which of a domain's own rows fills its own positions,
  never touching another domain's slot.

- _normalize_base_url folded query and fragment into the outage-domain
  key together, so two identical endpoints differing only by a
  client-side-only #fragment reported as two domains with separate
  diversity counts and separate admission-cap budgets. The fragment is
  now stripped while the query string, which can be a real routing
  distinction, is still preserved.

Both are covered by new regression tests exercising the internal
reordering helper directly and the full build_zdr_prioritized_catalog
path with a tight limit. 100% coverage/docstring gates re-verified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw
…finding

contextual-orchestrator#950 (cited by this PR as "the upstream half of this
bug, not yet merged") was closed as redundant/superseded. The fix actually
merged as contextual-orchestrator#949 ("fix(discovery): route OpenRouter by
model evidence", 8cd99f139915131ba0239bce12a5d6a5fd85394e); .github#1477
already advances ORCHESTRATOR_PIN_SHA to that commit. Corrects every stale
#950 reference in the PR's own docstrings and docs/product-technical-gap-
baseline.md, and folds in two things only visible from #949's actual diff:

- #949 also added DiscoveredModel.spend_admitted (default True, False for a
  priced OpenRouter row when openrouter_paid_inference_available() cannot
  confirm usable credit). orchestrator/free never considers priced rows, so
  it was never exposed to this, but orchestrator/auto (real, reachable via
  CONTEXTUAL_ORCHESTRATOR_POOL=auto, no other code change needed) does
  consider priced rows and had no spend_admitted check anywhere in this
  repo's pipeline. _routable_discovered_models() now excludes a
  spend_admitted=False row the same way it excludes evidence_only=True,
  with regression coverage including an end-to-end auto-pool composition.

- A fresh Devin Review red finding on this PR argued the OpenRouter
  evidence_only exemption could let private/--require-zdr review content
  reach ZDR-forbidden routes. Traced end to end: build_zdr_prioritized_
  catalog() independently re-applies is_zdr_model()'s real OpenRouter ZDR-
  feed check as its own admission gate whenever require_zdr=True, entirely
  independent of evidence_only. Confirmed false alarm with a regression
  test proving a non-ZDR-attested OpenRouter row is excluded from a
  require_zdr=True catalog even while every discovered OpenRouter row still
  carries the evidence_only=True bug signature; documented in the gap
  baseline and replied/resolved on the GitHub review thread.

docs/product-technical-gap-baseline.md gets a new 2026-08-31 correction
subsection recording all of the above. Full suite: 2098 passed, 1 skipped,
21 subtests passed; 100% coverage on scripts/ci/; 100% docstring coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw
claude and others added 5 commits August 31, 2026 08:22
…utcome

Devin Review (discussion r3891875665) on tests/test_contextual_orchestrator_
review_sidecar_contract.py:290 correctly noted the contract test's source
check only requires the OpenRouter provider_name comparison to appear
somewhere in source text -- a reversed (`!=` for `==`) or disconnected
exemption would still satisfy that check. Verified by mutation: both
mutations were applied locally and confirmed the old assertion alone would
not have caught them (a "disconnect" mutation was crafted to leave the
literal source fragment byte-for-byte intact while making the exemption a
no-op).

Add a direct behavioral assertion in the same test, against the already
runpy-loaded launcher module's real `_routable_discovered_models`, that
exercises one OpenRouter row that must be exempted and one same-shaped
non-OpenRouter row that must not, asserting the actual filtered output.
This fails under both the reversal and the disconnection mutation, closing
the gap Devin identified without weakening the existing source-text checks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw
Ports the identical fix from #1506 into this branch. This PR's
exact-head-path-policy check runs its own head-branch copy of
scripts/ci/test_strix_quick_gate.sh (plain `pull_request` trigger in
strix-changed-path-quality-ci.yml, not pull_request_target), so the
pre-existing main-branch bug is not fixed here just by #1506 merging
into main -- it needs porting into this branch directly.

Root cause: assert_opencode_review_uses_codegraph_and_contextual_orchestrator
extracted the required-workflow-bootstrap job block from
opencode-review.yml with awk '/^  required-workflow-bootstrap:$/,/^[^ ]/'.
Every job key in that workflow is indented 2 spaces (never column 0), so
the end pattern never matched until EOF, sweeping an unrelated `if:`
line from a later job (added by already-merged PR #1497) into the
"block" and failing the assertion on unrelated content.

Fixed by using an explicit state flag so the end pattern
(`^  [A-Za-z0-9_-]+:`) is only tested starting on the line after the
start match, correctly bounding the block to just its own lines.

See #1506 for the full root-cause writeup
and validation against origin/main.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw
grep -q exits on first match and closes its end of the pipe; if the
upstream awk is still writing a large block, it gets SIGPIPE (141).
Under `set -o pipefail` that non-zero awk status wins over grep's real
0, so `if pipeline; then` sees the pipeline as failed even though grep
found a genuine match — silently missing e.g. a forbidden `if:` key or
a fenced-diff marker that should have failed the check.

Ports the same-file fix from PR #1506 to this branch's two call sites
(required-workflow-bootstrap job-block check; opencode review
REQUEST_CHANGES fenced-diff check). This branch already carried
#1506's awk job-block-boundary correction, so only the grep -q removal
was needed here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gs7KmNvH75nxz1sL8mKjw
@seonghobae

Copy link
Copy Markdown
Contributor Author

Contextual-Orchestrator를 같이 손보든 어쩌든 해결하세요. Bypass merge 필요하면 가능 (chicken and eggs 상황이라면) + NVIDIA NIM 만 쓰는 건 허용하지 않아요. Contextual-Orchestrator를 쓰세요.

…alog-cap

# Conflicts:
#	docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md
devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Contextual-Orchestrator와 관계한 것들을 같이 손보든 어쩌든 해결하세요. Bypass merge 필요하면 가능 (chicken and eggs 상황이라면) + NVIDIA NIM 만 쓰는 건 허용하지 않아요. Contextual-Orchestrator를 쓰세요. Timeout은 적어도 3시간으로 잡으세요. 120초 같은 건 당황스럽군요. Opencode와 Noema 는 Coderabbitai 및 Devin 수준으로 실제로 리뷰를 하게 하시오. Strix도 보안 리뷰를 꼼꼼하게 하도록 하시오. 특히 보안 리뷰는 전체 코드로 수행하는 것입니다. Contextual-Orchestrator는 실시간으로 빠르면서 능력이 좋은 모델에 요청을 보내어 시간을 당기시오. @opencode-agent 라고 부르면 호출되는 기능도 인터넷 가이드에는 /oc 라고 나와있기 때문에 이 점도 확인해 보는 게 좋겠습니다.

Ports the identical fix from #1506 into this branch. This PR's
exact-head-path-policy check runs its own head-branch copy of
scripts/ci/test_strix_quick_gate.sh (plain `pull_request` trigger in
strix-changed-path-quality-ci.yml, not pull_request_target), so the
pre-existing main-branch bug is not fixed here just by #1506 merging
into main -- it needs porting into this branch directly.

Root cause: assert_opencode_review_uses_codegraph_and_contextual_orchestrator
extracted the required-workflow-bootstrap job block from
opencode-review.yml with awk '/^  required-workflow-bootstrap:$/,/^[^ ]/'.
Every job key in that workflow is indented 2 spaces (never column 0), so
the end pattern never matched until EOF, sweeping an unrelated `if:`
line from a later job into the "block" and failing the assertion on
unrelated content.

Fixed by using an explicit state flag so the end pattern
(`^  [A-Za-z0-9_-]+:`) is only tested starting on the line after the
start match, correctly bounding the block to just its own lines.

Confirmed FAIL before this fix, PASS after (bash scripts/ci/test_strix_quick_gate.sh).

See #1506 for the full root-cause writeup.

Co-Authored-By: Claude <noreply@anthropic.com>
… after rebase

Same fix as #1444's identical rebase-time
finding, applied here since this branch independently merged the same
concurrent main PRs (#1532, #1533). Concurrent main PRs legitimately
changed .github/workflows/opencode-review-dispatch.yml since this
branch's last rebase, and this rebase's merge picked those changes up
byte-for-byte (confirmed: `git diff origin/main --
.github/workflows/opencode-review-dispatch.yml` is empty). Two
pre-existing contract tests were left pointing at stale expectations
by that upstream change -- reproducible on origin/main's own tip, not
introduced by this branch's diff:

- REVIEW_DISPATCH_BLOB_SHA pinned the workflow's pre-#1532/#1533 blob
  SHA; updated to the current `git hash-object` value.
- test_opencode_privileged_review_security_boundaries_are_fail_closed
  asserted the pre-#1533 strict `[ "$SUPPLIED_HEAD_SHA" =
  "$live_head_sha" ]` equality check. #1533
  ("fix(opencode): proceed on head-only advance in review dispatch
  validation") deliberately removed head_sha from the fail-closed
  mismatch list -- a head advance between dispatch capture and this
  job is normal PR activity that every downstream job already
  re-validates independently (STALE_HEAD guards), so failing closed on
  it only starved the required review check of a verdict. Updated the
  assertion to check for the new warn-and-proceed behavior instead of
  the old fail-closed check it replaced.

Co-Authored-By: Claude <noreply@anthropic.com>
Appends to this PR's own still-unmerged CHANGELOG bullet (matching
this repo's convention of amending an unmerged PR's own entry in
place rather than stacking a separate bullet for the same PR).

Co-Authored-By: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Contributor Author

Rebase status update, folding in the review findings posted on this PR's pushed head during the rebase:

Fixed: "Fallback remains single-domain" (Devin Review). Verified concretely: with both defaults at 4, fallback_limit == account_cap meant the priced-fallback stage's per-domain cap provided no diversity protection — one dominant domain could exhaust the whole stage before an independent domain's row was ever considered. Fixed with _fallback_domain_aware_account_cap() in contextual_orchestrator_review_launcher.py: shrinks the cap to fallback_limit // domain_count whenever multiple domains compete, guaranteeing every domain at least one turn (cap * domain_count <= fallback_limit); the common single-domain case is unchanged. Three new regression tests, including Devin's own suggested shape. Full reply on the review thread.

Checked, false positive: CodeRabbit's "incorrect default cap" doc finding. The doc's "sidecar default 8" is correct — that's contextual_orchestrator_review_sidecar.sh's own CATALOG_ACCOUNT_CAP="${ORCHESTRATOR_CATALOG_ACCOUNT_CAP:-8}", genuinely distinct from the Python module's DEFAULT_ACCOUNT_CAP = 4 fallback (used only when the env var is completely unset, which the sidecar never leaves it). No doc change needed. Full reply on the review thread.

Checked, already correct: IPv6 endpoint normalization. _normalize_base_url already re-brackets a colon-bearing host before appending a port (fixed earlier in this PR's own review cycle, commit 0c90beb); spot-checked https://[::1]:8080/v1 against several equivalence/distinctness cases directly — behaves correctly.

Also, while rebasing across two rounds of concurrent main activity: ported the .github#1506 awk-boundary fix into this branch's own test_strix_quick_gate.sh copy, and refreshed two contract tests (REVIEW_DISPATCH_BLOB_SHA, the opencode-review-dispatch.yml head-advance assertion) that #1532/#1533 made stale on main itself. No direct-NVIDIA-NIM-bypass issue found — this PR's own _outage_domain/base_url grouping logic only reasons about evidence already produced by the existing catalog/discovery/ZDR-policy machinery; no new outbound HTTP calls.

Full suite green (2159 passed, 1 skipped), 100% coverage and 100% docstrings on scripts/ci/. The two named regression tests from this PR's own test plan (test_build_catalog_counts_same_vendor_credentials_independently, test_build_catalog_prevents_shared_endpoint_from_crowding_out_independent_providers) and the four other updated tests all still pass post-rebase.


Generated by Claude Code

devin-ai-integration[bot]

This comment was marked as resolved.

Devin Review follow-up finding on this PR's pushed head: the previous
commit's _fallback_domain_aware_account_cap() shrank the per-domain
cap to fallback_limit // domain_count (floor) to guarantee every
outage domain a seat. That fixes starvation but wastes capacity
whenever fallback_limit does not divide evenly by domain_count --
concretely, limit=4 across 3 domains floored every domain to cap=1,
admitting only 3 routes even though a 4th eligible row existed in one
of those domains ("fallback quota wastes probe slots").

A single scalar per-domain cap cannot solve both problems at once (no
uniform cap value simultaneously guarantees every domain a seat *and*
leaves no capacity unused on an uneven split), so this replaces the
cap-shrinking helper with a new guarantee_domain_coverage flag on
build_zdr_prioritized_catalog itself: admission now runs in two passes
when set. The first pass admits at most one row per outage domain (in
the same priority order, bounded by account_cap and limit as before),
guaranteeing representation before any domain claims a second seat.
The second pass fills any remaining limit budget from the rows the
first pass did not pick, still respecting each domain's account_cap
ceiling (inclusive of the first pass's contribution) -- so the full
budget is used whenever enough eligible rows exist anywhere. The
picked order places every diversity (first-pass) row ahead of every
fill (second-pass) row, which is the more useful preflight try-order
for a pool whose entire purpose is outage-domain resilience, not
merely an implementation artifact.

_fallback_domain_aware_account_cap() is removed entirely rather than
kept alongside the new mechanism: it computed a value the new two-pass
admission no longer needs (both launcher call sites now pass
_catalog_account_cap(DEFAULT_ACCOUNT_CAP) directly again, unshrunk;
only the fallback call site additionally sets
guarantee_domain_coverage=True), and keeping an unused helper around
would just be a second, silently-driftable place answering the same
question.

Six new/replacement regression tests at the policy level, including
Devin's own two suggested non-divisible splits (limit=4 with 3
domains, and limit=8 with 3 domains) verifying both domain
representation and full use of available capacity, plus the
single-domain-unchanged case, the account_cap ceiling still holding,
and a domains-outnumber-limit edge case. Full suite green (2163
passed), 100% coverage and 100% docstrings on scripts/ci/.

Co-Authored-By: Claude <noreply@anthropic.com>
Updates the CHANGELOG bullet added two commits ago to describe the
two-pass guarantee_domain_coverage mechanism instead of the
now-removed cap-shrinking helper it originally described.

Co-Authored-By: Claude <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Devin Review follow-up finding on this PR's pushed head: the
guarantee_domain_coverage fix two commits ago only covered the
priced-fallback stage. The identical single-scalar-cap-equals-limit
coincidence is independently reachable through the *primary* auto-pool
stage too, under the sidecar's real deployed configuration (not the
DEFAULT_ACCOUNT_CAP=4 fixture value most of this file's tests use):
contextual_orchestrator_review_sidecar.sh exports
ORCHESTRATOR_CATALOG_ACCOUNT_CAP=8 by default, and this launcher's own
REVIEW_PREFLIGHT_PRIMARY_ROUTE_LIMIT is also 8 for the auto pool's
primary stage -- eight free routes from one dominant outage domain
could exclude every independent free domain from the primary catalog
entirely, at account_cap=limit=8 rather than the fallback stage's
account_cap=limit=4.

Wires guarantee_domain_coverage=True into main()'s primary
build_zdr_prioritized_catalog call site as well. Full local suite
(2163 passed before this change) confirmed no regressions from this
extension -- guarantee_domain_coverage is a no-op whenever a stage's
eligible rows only ever touch one outage domain, which covers every
existing primary-stage test fixture.

Adds a dedicated regression using the real deployed values
(account_cap=8, limit=8, matching the sidecar's actual default rather
than this file's usual account_cap=4 fixtures) reproducing Devin's
exact scenario, plus extends the source-level wiring contract test to
require guarantee_domain_coverage=True at both call sites. Full suite
green (2164 passed), 100% coverage and 100% docstrings on scripts/ci/.

Co-Authored-By: Claude <noreply@anthropic.com>
…1533

Main moved again mid-rebase: .github#1540 reverted #1533's
warn-and-proceed head_sha check entirely (no rationale recorded beyond
the revert itself), restoring the original strict fail-closed
equality and, with it, the workflow file's original blob SHA
(confirmed: git hash-object on origin/main's copy is exactly
2aa245e, byte-identical to what this
file's REVIEW_DISPATCH_BLOB_SHA pinned before this whole detour
started).

Reverts this branch's own two prior commits' changes to these same
two spots: REVIEW_DISPATCH_BLOB_SHA back to the original pin, and
test_opencode_privileged_review_security_boundaries_are_fail_closed
back to asserting the strict equality check instead of the
now-reverted warn-and-proceed behavior.

Co-Authored-By: Claude <noreply@anthropic.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 1 new potential issue.

🐛 1 issue in files not directly in the diff

🐛 Head advances strand required reviews

When a pull request advances after dispatch, SUPPLIED_HEAD_SHA rejects the queued review although downstream work validates the live head. Active pull requests lose their required review verdict.

Devin Review

claude and others added 4 commits September 1, 2026 03:10
Protected main regressed to 99% scripts/ci coverage after #1546 added
live_head_matches, a no-active/no-stale fall-through in
prepare_autofix_slot, and an "already queued or running" wait branch
to pr_review_fix_scheduler.py without covering them, while the
pre-existing inspect_pr conflicted-draft/conflicted-unauthorized
returns and pr_review_merge_scheduler.py's
fetch_workflow_names_by_check_suite_rest pagination/filtering/
permission-denied paths stayed untested. Every PR rebasing onto main
inherits this via the coverage-evidence required check regardless of
its own diff. Test-only change; no production code touched.
Adds a dated traceability entry for the coverage gap this PR closes:
root cause (#1546's uncovered additions plus the older #1547/#1551/
#1554 gap, neither of which merged or transfers evidence here), the
fix and its verification, the resolved Devin false-positive on
sub-clause coverage, and the known pre-existing SIGPIPE test flake
left unremediated as out of scope.
Raise scoped docstring coverage for the newly added scheduler REST regression helpers to 100% without changing test behavior or production code.

Copy link
Copy Markdown
Contributor Author

Converge the sidecar routing PRs into one non-conflicting stack instead of three parallel launcher writers. Canonical order: #1476 (OpenRouter evidence_only / spend_admitted admission correctness) → this #1474 (outage-domain cap + diversity evidence) → #1529 (plain-text review input-modality filter). @opencode-agent wait for/read #1476's live current-main head, merge it into this branch non-destructively, retarget this PR to #1476's branch, and make the base-relative delta only _outage_domain grouping, free_outage_domain_diversity, associated launcher count restoration, tests, ADR/goal/baseline corrections. Preserve #1546 lifecycle and #1476 routing/security contracts. Do not duplicate #1567 scheduler coverage tests; treat #1567 as merged-tree prerequisite until integrated. Fresh exact-head reviews after every parent movement.

…ure-evidence-only-filter

# Conflicts:
#	CHANGELOG.md
…7) into fix/openrouter-premature-evidence-only-filter

Interim stack: #1567 is not yet merged to main and closes the
pre-existing post-#1546 scheduler coverage regression this PR would
otherwise inherit via the merged-tree coverage-evidence gate. This
merge is a no-op once #1567 lands on main and a future main-merge
picks it up normally.
…e-only-filter' into fix/outage-domain-catalog-cap

# Conflicts:
#	docs/product-technical-gap-baseline.md

Copy link
Copy Markdown
Contributor Author

Merged #1476's live branch (fix/openrouter-premature-evidence-only-filter, which itself carries current main + an interim #1567 stack) non-destructively — regular merge, no rebase/force. Only docs/product-technical-gap-baseline.md conflicted, and it's a pure append-only narrative log: both sides added distinct dated sections at the same anchor with no overlapping edits, so I concatenated both (this PR's "outage-domain diversity" entry, then #1476's entries) rather than dropping either. #1546's exact-head lifecycle and #1476's routing/security contracts both came through auto-merge untouched on every other file.

Validated on the new head (7287643c): coverage run -m pytest tests -q → 2294 passed, 1 skipped, 21 subtests; coverage report → 100% (repo-wide); interrogate → 100.0%. REVIEW_DISPATCH_BLOB_SHA pin still matches.

@opencode-agent please review the new exact head 7287643c; no predecessor evidence should transfer.


Generated by Claude Code


Generated by Claude Code

devin-ai-integration[bot]

This comment was marked as resolved.

Devin Review found that build_zdr_prioritized_catalog's
guarantee_domain_coverage two-pass admission ran across the full
tier-sorted row list instead of within one admission-priority tier at
a time, letting a worse-tier row win a first-pass "guaranteed
representation" seat for its domain ahead of a better-tier row from
an already-represented domain. Restructured both passes to iterate
contiguous same-tier runs of ordered_rows in priority order, finishing
a tier's own first and second pass before ever looking at the next,
worse tier -- the same tier-boundary discipline _fair_admission_order
already enforces for its own reordering. covered_domains/per_domain
still accumulate across tiers so a domain already seated in a better
tier does not claim a redundant guaranteed seat in a worse one.

Added a regression proving the pre-fix behavior red (two free/ZDR
openrouter routes plus one free/non-ZDR bytez route, limit=2: both
admitted rows must stay free/ZDR) before confirming it green after.
# Conflicts:
#	CHANGELOG.md
#	docs/product-technical-gap-baseline.md
#	scripts/ci/contextual_orchestrator_review_launcher.py
#	scripts/ci/contextual_orchestrator_review_policy.py
#	tests/test_contextual_orchestrator_review_policy.py


Merging current main pulled in three overlapping changes to the same
review-catalog code this PR's own outage-domain-cap-grouping fix
touches:

- #1587 added FREE_POOL_CREDENTIAL_NAMES source-authorization
  filtering (scripts/ci/contextual_orchestrator_review_policy.py and
  contextual_orchestrator_review_launcher.py's _with_discovery_counts,
  plus tests/test_contextual_orchestrator_free_pool_enrichment.py) and
  is independent of this PR's outage-domain grouping -- combined both
  additions in _with_discovery_counts (free_outage_domain_diversity
  alongside the free_pool_* fields) and in build_zdr_prioritized_catalog's
  report dict, dropping one duplicate free_account_diversity key the
  merge introduced.
- #1592 fixed test_build_catalog_applies_account_cap's stale "openai"
  fixture (pre-#1587) to "openrouter" on protected main; this PR's own
  branch had already independently rewritten the same test's assertions
  for the outage-domain-grouped cap semantics (nvidia_nim/nvidia_nim_sub
  sharing one domain's cap) but still referenced the stale "openai" key
  in its own assertion -- corrected to "openrouter", matching the
  fixture's already-current provider name.
- test_contextual_orchestrator_free_pool_enrichment.py's own new test
  called _with_discovery_counts without the outage_domain keyword this
  PR's fix requires; added a matching lambda and a
  free_outage_domain_diversity assertion, preserving the test's
  original free-pool-enrichment intent.
- #1564 (merge-base-anchored deleted-file review evidence) left
  tests/test_noema_review_gate.py and tests/test_noema_removed_file_context.py
  broken against renamed/removed noema_review_gate.py functions; ported
  the same fix already opened as its own dedicated PR (#1598).

Full suite: 2362 passed, 100% branch coverage, 100% docstrings.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 4 new potential issues.

Devin Review

Comment on lines +657 to +697
if guarantee_domain_coverage:
covered_domains: set[str] = set()
tier_start = 0
total = len(ordered_rows)
while tier_start < total and len(picked) < limit:
tier = _admission_priority_key(
ordered_rows[tier_start], zdr_endpoints=zdr_endpoints
)[:2]
tier_end = tier_start + 1
while (
tier_end < total
and _admission_priority_key(
ordered_rows[tier_end], zdr_endpoints=zdr_endpoints
)[:2]
== tier
):
tier_end += 1
tier_rows = ordered_rows[tier_start:tier_end]
tier_start = tier_end

first_pass_ids: set[int] = set()
for row in tier_rows:
if len(picked) >= limit:
break
domain = _outage_domain(row)
if domain in covered_domains or per_domain[domain] >= account_cap:
continue
covered_domains.add(domain)
per_domain[domain] += 1
picked.append(row)
first_pass_ids.add(id(row))
for row in tier_rows:
if len(picked) >= limit:
break
if id(row) in first_pass_ids:
continue
domain = _outage_domain(row)
if per_domain[domain] >= account_cap:
continue
per_domain[domain] += 1
picked.append(row)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: Tier priority survives domain coverage

guarantee_domain_coverage finishes both passes within each cost/ZDR tier. Lower-tier routes cannot displace higher-tier routes, while caps remain catalog-wide.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +331 to +355
domain_positions: dict[str, list[int]] = {}
for index, row in enumerate(rows):
domain_positions.setdefault(_outage_domain(row), []).append(index)

ordered: list[Mapping[str, Any]] = list(rows)
for positions in domain_positions.values():
bucket = [rows[index] for index in positions]
account_order: list[str] = []
queues: dict[str, deque[Mapping[str, Any]]] = {}
for row in bucket:
account = provider_account(str(row["provider"]))
if account not in queues:
account_order.append(account)
queues[account] = deque()
queues[account].append(row)
if len(account_order) <= 1:
continue
reordered: list[Mapping[str, Any]] = []
while any(queues[account] for account in account_order):
for account in account_order:
queue = queues[account]
if queue:
reordered.append(queue.popleft())
for index, row in zip(positions, reordered):
ordered[index] = row

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: Fairness preserves independent positions

Round-robin replacement touches only slots already owned by one outage domain. Interleaved independent routes retain their original priority positions.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +285 to +297
discovered = list(discovered or [])
openrouter_still_blanket_marked = not _openrouter_reports_per_model_evidence(discovered)
return [
model
for model in discovered
if (
not getattr(model, "evidence_only", False)
or (
openrouter_still_blanket_marked
and getattr(model, "provider_name", None) == "openrouter"
)
)
and getattr(model, "spend_admitted", True) is not False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: Private routing remains ZDR-gated

The OpenRouter workaround broadens discovery candidates only. Private-target candidates still pass the independent is_zdr_model filter before admission.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +170 to +198
text = base_url.strip()
try:
parsed = urlsplit(text)
except ValueError:
return text.casefold()
if not parsed.scheme or not parsed.hostname:
return text.casefold()
try:
port = parsed.port
except ValueError:
return text.casefold()
scheme = parsed.scheme.casefold()
host = parsed.hostname.casefold()
# urlsplit().hostname strips IPv6 literal brackets (``[::1]`` -> ``::1``).
# Re-adding them whenever the host itself contains a colon -- before ever
# conditionally appending a port -- is required for two reasons: without
# it, an explicit-port IPv6 URL (``[::1]:8443``) and a bracketless,
# colon-bearing literal address that merely *looks* like host:port when
# flattened (``[::1:8443]``, port None) collapse to the identical
# ``::1:8443`` string despite being different addresses; and the
# reassembled ``netloc`` must stay valid host:port syntax regardless.
bracketed_host = f"[{host}]" if ":" in host else host
netloc = (
bracketed_host
if port is None or port == _DEFAULT_PORTS.get(scheme)
else f"{bracketed_host}:{port}"
)
path = parsed.path.rstrip("/")
return urlunsplit((scheme, netloc, path, parsed.query, ""))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: URL normalization avoids false domains

Normalization folds equivalent endpoint spellings while preserving meaningful distinctions. Malformed evidence falls back deterministically instead of aborting catalog construction.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Closing as superseded by the current no-heuristics review-admission owner lane #1629. This PR deliberately makes an outage-domain account_cap affect route admission and reserves bounded route budget by endpoint. The current organization contract forbids hand-authored heuristic cardinality/priority/admission quotas; #1629 explicitly preserves outage-domain/account diversity as evidence while refusing to resurrect #1474's admission quota. Keeping this PR open would both consume Actions capacity and risk reintroducing the policy defect #1629 is repairing. No predecessor checks or review evidence are transferred.

@seonghobae seonghobae closed this Sep 1, 2026
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