Skip to content
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b1f090c
feat(ci): route Noema review through vendored contextual-orchestrator
seonghobae Aug 27, 2026
33d5490
feat(ci): replace placeholder with Noema orchestrator sidecar workflow
seonghobae Aug 27, 2026
ea98ebe
docs: record Noema orchestrator/free ZDR routing in ADR-0003 and doct…
seonghobae Aug 27, 2026
d426e5b
test(ci): contract that Noema review provisions the orchestrator sidecar
seonghobae Aug 27, 2026
e8580a5
WIP: do not keep - truncated gate
seonghobae Aug 27, 2026
b34cf8a
fix(ci): restore full noema_review_gate.py with sidecar SSRF allowlist
seonghobae Aug 27, 2026
fadf4ca
fix(ci): still truncated - STOP
seonghobae Aug 27, 2026
8c1ea4c
test(ci): cover Noema orchestrator sidecar SSRF allowlist
seonghobae Aug 27, 2026
bd5b613
fix(ci): restore complete noema_review_gate.py with sidecar SSRF allo…
seonghobae Aug 27, 2026
4a0934d
fix(noema): allow same-job loopback for vendored contextual-orchestra…
seonghobae Aug 27, 2026
6ec2fb5
fix(ci): restore Noema gate loader and sidecar SSRF half
seonghobae Aug 27, 2026
82514c3
fix(ci): restore Noema gate shared helpers half
seonghobae Aug 27, 2026
b76d9ad
fix(noema): invoke loopback allow before Noema LLM via orchestrator/free
seonghobae Aug 27, 2026
b630336
test(ci): replace Noema NIM hardcode assertions with orchestrator/fre…
seonghobae Aug 27, 2026
c1bb7e6
fix(test): restore newline after autouse fixture decorator
seonghobae Aug 27, 2026
56837cb
fix(ci): remove runtime loopback patch that would allow any localhost
seonghobae Aug 27, 2026
8fc485e
fix(noema): drop runtime loopback helper; gate halves already allow s…
seonghobae Aug 27, 2026
7f80a66
fix(ci): stop invoking deleted runtime SSRF patch in Noema review
seonghobae Aug 27, 2026
117fed6
Flatten noema_review_gate.py into a single module (remove exec-split …
seonghobae Aug 27, 2026
c4e30a1
Restore complete flattened noema_review_gate.py (single module, no ex…
seonghobae Aug 27, 2026
e926822
Restore complete flattened noema_review_gate.py (711-line single modu…
seonghobae Aug 27, 2026
1d4d44d
WIP avoid — do not use this truncated payload
seonghobae Aug 27, 2026
5a4e911
Restore flattened noema_review_gate.py (single 711-line module).
seonghobae Aug 27, 2026
f297e69
Restore complete flattened noema_review_gate.py (711-line single modu…
seonghobae Aug 27, 2026
9f33d4e
Delete exec-split part A after flattening noema_review_gate.py.
seonghobae Aug 27, 2026
05347ff
Delete exec-split part B after flattening noema_review_gate.py.
seonghobae Aug 27, 2026
0ed7591
Document Noema orchestrator/free ZDR sidecar routing in CHANGELOG.
seonghobae Aug 27, 2026
a9d26e7
test(ci): preserve Noema contracts without global skips
seonghobae Aug 27, 2026
46dc050
test(ci): require ZDR-only routing for private Noema targets
seonghobae Aug 27, 2026
907ee2b
fix(ci): require ZDR-only Noema routing for private targets
seonghobae Aug 27, 2026
b87e927
chore(ci): preserve sidecar file mode
seonghobae Aug 27, 2026
b7167a6
test(ci): align Noema visibility contracts with ZDR boundary
seonghobae Aug 27, 2026
64f673b
fix(tests): use portable bash resolution in workflow contract tests
seonghobae Aug 28, 2026
c2fa094
fix(ci): route required reviews through contextual orchestrator
seonghobae Aug 28, 2026
4ee1518
fix(ci): align gateway diagnostics
seonghobae Aug 28, 2026
6896c9e
docs(ci): track gateway review coverage
seonghobae Aug 28, 2026
3bcaf3e
fix(ci): close required review gateway findings
seonghobae Aug 28, 2026
e8fafdf
fix(strix): make gateway origin contract explicit
seonghobae Aug 28, 2026
4680d12
test(strix): align gateway-only workflow contracts
seonghobae Aug 28, 2026
d374930
fix(review): validate dispatch trust before gateway startup
seonghobae Aug 28, 2026
3217584
test(review): refresh dispatch workflow integrity pin
seonghobae Aug 28, 2026
6064831
merge(main): synchronize review workflow branch
seonghobae Aug 28, 2026
531fa2e
fix(noema): retry transient visibility lookups
seonghobae Aug 28, 2026
ae036ca
fix(strix): gate sidecar provisioning after model validation
seonghobae Aug 28, 2026
9505a5b
fix(opencode): require zdr for internal repositories
seonghobae Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 33 additions & 17 deletions .github/workflows/noema-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -257,26 +257,43 @@ jobs:
GH_TOKEN: ${{ secrets.NOEMA_REVIEW_TOKEN || steps.noema_github_app_token.outputs.token || steps.noema_oidc_token.outputs.token }}
run: |
set -euo pipefail
is_private="$(gh api "repos/${TARGET_REPOSITORY}" --jq '.private')"
case "$is_private" in
true | false) ;;
if [ -z "${GH_TOKEN:-}" ]; then
echo "::error::Noema target visibility cannot be resolved without the selected repository-scoped reviewer token."
exit 1
fi
visibility="$(gh api "/repos/${TARGET_REPOSITORY}" --jq '.visibility // (if .private then "private" else "public" end)')"
Comment thread
seonghobae marked this conversation as resolved.
Outdated
case "$visibility" in
private|internal)
echo "require_zdr=true" >>"$GITHUB_OUTPUT"
echo "::notice::Private/internal target requires an attested ZDR-only review pool."
;;
public)
echo "require_zdr=false" >>"$GITHUB_OUTPUT"
;;
*)
echo "::error::Noema target repository visibility did not resolve to true or false."
echo "::error::Noema target repository visibility is missing or unsupported: ${visibility:-<empty>}."
exit 1
;;
esac
echo "is_private=$is_private" >>"$GITHUB_OUTPUT"

- name: Provision contextual-orchestrator review sidecar
if: env.PR_NUMBER != ''
env:
BYTEZ_API_KEY: ${{ secrets.BYTEZ_API_KEY }}
NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}
NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CONTEXTUAL_ORCHESTRATOR_REQUIRE_ZDR: ${{ steps.target_visibility.outputs.require_zdr }}
run: |
set -euo pipefail
bash "$GITHUB_WORKSPACE/scripts/ci/contextual_orchestrator_review_sidecar.sh"
Comment thread
seonghobae marked this conversation as resolved.

- name: Run Noema LLM review and submit verdict
if: env.PR_NUMBER != ''
env:
GH_TOKEN: ${{ secrets.NOEMA_REVIEW_TOKEN || steps.noema_github_app_token.outputs.token || steps.noema_oidc_token.outputs.token }}
NOEMA_REVIEW_TOKEN_SOURCE: ${{ steps.noema_credential.outputs.source == 'pat' && 'noema-review-pat' || steps.noema_credential.outputs.source == 'github-app' && 'noema-review-github-app' || 'noema-review-app-oidc' }}
NOEMA_LLM_API_URL: ${{ vars.NOEMA_LLM_API_URL || '' }}
NOEMA_LLM_MODEL: ${{ vars.NOEMA_LLM_MODEL || '' }}
NOEMA_LLM_API_KEY: ${{ secrets.NOEMA_LLM_API_KEY || secrets.OPENAI_API_KEY || '' }}
NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}
TARGET_REPOSITORY_PRIVATE: ${{ steps.target_visibility.outputs.is_private }}
run: |
set -euo pipefail
if [ -z "${PR_NUMBER:-}" ]; then
Expand All @@ -287,15 +304,14 @@ jobs:
echo "::error::Noema reviewer credential selection succeeded but no token was minted; review cannot submit a verdict."
exit 1
fi
if [ "$TARGET_REPOSITORY_PRIVATE" = "false" ] && [ -n "${NVIDIA_NIM_API_KEY:-}" ] && [ -z "${NOEMA_LLM_API_URL:-}" ] && [ -z "${NOEMA_LLM_MODEL:-}" ]; then
export NOEMA_LLM_API_URL="https://integrate.api.nvidia.com/v1/chat/completions"
export NOEMA_LLM_MODEL="nvidia/nemotron-3-ultra-550b-a55b"
export NOEMA_LLM_API_KEY="${NVIDIA_NIM_API_KEY:-}"
fi
if [ -z "${NOEMA_LLM_API_URL:-}" ] || [ -z "${NOEMA_LLM_MODEL:-}" ] || [ -z "${NOEMA_LLM_API_KEY:-}" ]; then
echo "::error::Noema LLM is unconfigured: NOEMA_LLM_API_URL, NOEMA_LLM_MODEL, and NOEMA_LLM_API_KEY (or OPENAI_API_KEY) are required."
if [ -z "${CONTEXTUAL_ORCHESTRATOR_BASE_URL:-}" ] || [ -z "${CONTEXTUAL_ORCHESTRATOR_TOKEN:-}" ]; then
echo "::error::contextual-orchestrator review sidecar must be provisioned before Noema LLM review."
exit 1
fi
export NOEMA_LLM_API_URL="${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}/v1/chat/completions"
export NOEMA_LLM_MODEL="orchestrator/free"
export NOEMA_LLM_API_KEY="${CONTEXTUAL_ORCHESTRATOR_TOKEN}"
export NOEMA_LLM_VIA_ORCHESTRATOR=1
python3 scripts/ci/noema_review_gate.py \
--repo "$TARGET_REPOSITORY" \
--pr-number "$PR_NUMBER"
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ this file. The format follows Keep a Changelog, and versioned releases follow
Semantic Versioning where the repository publishes a release.

## [Unreleased]
- Required Noema review now routes through the same vendored
`contextual-orchestrator` sidecar as the autofix writer: `noema-review.yml`
provisions the gateway with the five provider secrets, points the LLM step
at the loopback `orchestrator/free` pool (ZDR-first auto-discovery), and
deletes the public-repo NVIDIA NIM hardcode. `call_llm` keeps SSRF closed
for arbitrary private and `localhost` targets and allows only the
orchestrator sidecar loopback (`127.0.0.1` / `::1`) when it matches the
sidecar base URL or an explicit via-orchestrator flag. Reviewer identity
is unchanged (`NOEMA_REVIEW_TOKEN` / GitHub App / OIDC; never
`github.token`). Strix and the hourly-review-repair roster are untouched.
- Central review now routes through the vendored `contextual-orchestrator`
gateway sidecar: the write-capable PR autofix and the shared `opencode.jsonc`
default use the fail-closed zero-cost pool `orchestrator/free`, with
Expand Down
28 changes: 19 additions & 9 deletions docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

- Status: accepted
- Date: 2026-08-27
- Scope: ContextualWisdomLab/.github central review pipelines (OpenCode autofix + shared `opencode.jsonc` default)
- Scope: ContextualWisdomLab/.github central review pipelines (OpenCode autofix + shared `opencode.jsonc` default + required Noema review)
- Decision: Route every central CI review write/model execution that touches contracts in this repository through the **vendored** `contextual-orchestrator` gateway, served as a per-runner sidecar, using the fail-closed zero-cost virtual model id `orchestrator/free`, with **Zero Data Retention (ZDR)-compliant routes prioritized** inside that pool.
- Ownership: `.github` owns control-plane evidence; `ContextualWisdomLab/contextual-orchestrator` owns the gateway. The 2026-08-18 org decision (recorded in `ContextualWisdomLab/contextual-orchestrator` AGENTS.md) already migrated OpenCode/Noema/Strix to the orchestrator backend; this ADR is the org-repo (provider-config) half of that decision.
- Figma File ID: N/A (no customer UI).
Expand Down Expand Up @@ -50,10 +50,13 @@ all five, and auto-optimize routing by cost.
4. **Wiring**: `pr-review-autofix.yml` provisions the sidecar with the five
secrets before both OpenCode runs and points the write-capable agent at
`contextual-orchestrator/orchestrator/free`; the shared `opencode.jsonc`
default `model`/`small_model` becomes the gateway route. The required
authorization model is unchanged: mutation still requires
`PR_REVIEW_MERGE_TOKEN` / `OPENCODE_APPROVE_TOKEN` / the exchanged OpenCode
app token, never `github.token`; model subprocesses still run with
default `model`/`small_model` becomes the gateway route. `noema-review.yml`
provisions the same sidecar and points `call_llm` at the loopback
chat-completions URL with virtual model `orchestrator/free`. Noema reviewer
identity remains `NOEMA_REVIEW_TOKEN` / GitHub App / OIDC and is still never
`github.token`. Autofix mutation still requires `PR_REVIEW_MERGE_TOKEN` /
`OPENCODE_APPROVE_TOKEN` / the exchanged OpenCode app token, never
`github.token`; model subprocesses still run with
`GITHUB_TOKEN`/`GH_TOKEN`/OIDC request env stripped.
5. **Evidence**: the sidecar writes a discovery report, the policy report (pool,
counts, ZDR sources, feed-used flag, selected routes), and exports
Expand All @@ -70,13 +73,20 @@ all five, and auto-optimize routing by cost.
- A new central dep (vendored repo pinned to a SHA) must be reviewed when the
orchestrator upgrades; the pin is centralized in one script and one contract
test.
- The other stages named by the 2026-08-18 decision (the read-only dispatch
pool, `noema-review.yml`, `strix.yml`) remain on their pinned direct-pool
providers in this increment and are migrated in follow-up PRs.
- `noema-review.yml` now reviews through the same gateway; the public-repo
NVIDIA NIM hardcode is gone. Reviewer identity is unchanged. The remaining
stages named by the 2026-08-18 decision (`strix.yml` and the read-only
dispatch pool) stay on their pinned direct-pool providers and remain
follow-up PRs. The hourly-review-repair roster is not collapsed here.

## References (and ZDR standardization)

- OpenRouter. (2026, August). *Zero data retention* [Documentation]. https://openrouter.ai/docs/guides/features/zdr
- OpenRouter. (2026, August). *Provider logging: Data retention & logging* [Documentation]. https://openrouter.ai/docs/guides/privacy/provider-logging
- OpenRouter. (n.d.). *List all models and their properties* API reference; the per-model data-retention metadata (`data_retention: crichton | none`) and the ZDR endpoint feed `https://openrouter.ai/api/v1/endpoints/zdr` are consumed at runtime.
- ContextualWisdomLab/contextual-orchestrator. (2026, August 18). *AGENTS.md*, section “Policy change” — org migration of OpenCode/Noema/Strix to the gateway with the five KV credentials and auto-discovery.
- ContextualWisdomLab/contextual-orchestrator. (2026, August 18). *AGENTS.md*, section “Policy change” — org migration of OpenCode/Noema/Strix to the gateway with the five KV credentials and auto-discovery.

- **Private-target boundary (2026-08-27):** Noema resolves target visibility with
the selected repository-scoped reviewer token. Private/internal repositories
set `CONTEXTUAL_ORCHESTRATOR_REQUIRE_ZDR=true`; the catalog then excludes
every non-ZDR route and fails closed when no attested free ZDR route exists.
13 changes: 12 additions & 1 deletion docs/doctoring/contextual-orchestrator-vendored-sidecar.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ ZDR-prioritized free catalog at loopback. Both the ordinary autofix and the
conflict-repair agent run `--model contextual-orchestrator/orchestrator/free`.
The shared `opencode.jsonc` default route changes identically.

`noema-review.yml` provisions the same sidecar with the same five secrets and
points the required Noema LLM step at the loopback `orchestrator/free` pool.
The public-repo NVIDIA NIM hardcode is deleted. `call_llm` keeps SSRF closed
for arbitrary private IPs and `localhost`, and allows only the sidecar
loopback (`127.0.0.1` / `::1`) when it matches the sidecar base URL or an
explicit via-orchestrator flag.

`NVIDIA_NIM_API_KEY_SUB` and `BYTEZ_API_KEY` have no other workflow reference in
this repository; the gateway KV is their only consumer, matching the
orchestrator's `review_gateway.REVIEW_CREDENTIAL_NAMES`.
Expand All @@ -33,6 +40,8 @@ orchestrator's `review_gateway.REVIEW_CREDENTIAL_NAMES`.
- The gateway binds to loopback only; it never leaves the runner. Secrets are
bootstrap transport into the KV and are never read back from environment at
request time.
- Noema reviewer identity is unchanged: `NOEMA_REVIEW_TOKEN` / GitHub App /
OIDC. Review mutation is still not `github.token`.

## ZDR definition and evidence

Expand Down Expand Up @@ -61,4 +70,6 @@ training (OpenRouter's own stance). Evidence sources:
`tests/test_contextual_orchestrator_review_policy.py`,
`tests/test_contextual_orchestrator_review_sidecar_contract.py`,
`tests/test_pr_review_autofix_nvidia_nim_contract.py`,
`tests/test_pr_review_autofix_writer_security_contract.py`.
`tests/test_pr_review_autofix_writer_security_contract.py`,
`tests/test_noema_review_gate.py`,
`docs/doctoring/noema-orchestrator-free-zdr.md`.
65 changes: 65 additions & 0 deletions docs/doctoring/noema-orchestrator-free-zdr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Doctoring record: Noema review through the vendored orchestrator sidecar

- **Date:** 2026-08-27
- **Subject:** Required Noema review no longer pins a public-repo NVIDIA NIM
endpoint. It uses the same vendored `contextual-orchestrator` sidecar as the
autofix writer (`orchestrator/free`, ZDR-first auto-discovery).
- **Decision record:** [`docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md`](../adr/0003-contextual-orchestrator-vendored-free-zdr.md)
- **Related:** [`docs/doctoring/contextual-orchestrator-vendored-sidecar.md`](contextual-orchestrator-vendored-sidecar.md)

## What changed

`.github/workflows/noema-review.yml` provisions
`scripts/ci/contextual_orchestrator_review_sidecar.sh` with the five provider
secrets (`BYTEZ_API_KEY`, `NVIDIA_NIM_API_KEY`, `NVIDIA_NIM_API_KEY_SUB`,
`OPENROUTER_API_KEY`, `OPENAI_API_KEY`) and points `NOEMA_LLM_API_URL` at the
sidecar loopback chat-completions URL, `NOEMA_LLM_MODEL` at
`orchestrator/free`, and `NOEMA_LLM_API_KEY` at the process-local sidecar
bearer. The public-repo `integrate.api.nvidia.com` /
`nvidia/nemotron-3-ultra-550b-a55b` hardcode is deleted. There is no sequential
OpenAI or Azure fallback hop.

`scripts/ci/noema_review_gate.py` `call_llm` still rejects `localhost` and
arbitrary private, link-local, multicast, and unspecified targets. It allows
only `127.0.0.1` / `::1` when that origin matches
`CONTEXTUAL_ORCHESTRATOR_BASE_URL` or `NOEMA_LLM_VIA_ORCHESTRATOR` is an
explicit truthy flag.

Noema reviewer identity is unchanged: `NOEMA_REVIEW_TOKEN` / GitHub App /
OIDC. Review mutation is still not `github.token`. `strix.yml` and the
hourly-review-repair roster are untouched.

## Verification contract

`tests/test_contextual_orchestrator_review_sidecar_contract.py` and
`tests/test_required_workflow_queue_contract.py` assert the workflow provisions
the sidecar, the five secrets, and `orchestrator/free`, and no longer mentions
the NIM hardcode. `tests/test_noema_review_gate.py` covers the sidecar
allowlist and keeps localhost / non-sidecar private IP rejection.

## Rollback

Restore the previous Noema LLM env (`vars.NOEMA_LLM_API_URL` /
`secrets.NOEMA_LLM_API_KEY`) only if the sidecar cannot be provisioned. Do not
reintroduce a public-repo direct-provider pin. Do not weaken `call_llm` to
"any localhost".

## References (APA 7th)

GitHub. (n.d.). *Workflow syntax for GitHub Actions*. GitHub Docs. Retrieved
August 27, 2026, from
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax

OpenRouter. (2026, August). *Zero data retention* [Documentation].
https://openrouter.ai/docs/guides/features/zdr

ContextualWisdomLab/.github. (2026, August 27).
*ADR-0003: Vendored contextual-orchestrator review sidecar with the ZDR-first
orchestrator/free pool*.

## Private/internal repository routing

Noema resolves target visibility with its repository-scoped reviewer token.
Private/internal targets require an attested ZDR-only `orchestrator/free`
catalog. Missing visibility, malformed policy input, or an empty ZDR pool fails
the required review; it never falls back to a non-ZDR provider.
2 changes: 2 additions & 0 deletions scripts/ci/contextual_orchestrator_review_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def main(argv: list[str] | None = None) -> int:
parser.add_argument("--catalog-out", required=True, help="Path to write the agents catalog JSON")
parser.add_argument("--report-out", required=True, help="Path to write the policy evidence JSON")
parser.add_argument("--zdr-endpoints", default=None, help="Optional OpenRouter /api/v1/endpoints/zdr JSON path")
parser.add_argument("--require-zdr", action="store_true")
args = parser.parse_args(argv)

from contextual_orchestrator.credentials import get_credential
Expand Down Expand Up @@ -141,6 +142,7 @@ def main(argv: list[str] | None = None) -> int:
limit=int(os.environ.get("ORCHESTRATOR_CATALOG_LIMIT", "12")),
family_cap=int(os.environ.get("ORCHESTRATOR_CATALOG_FAMILY_CAP", "4")),
zdr_endpoints=zdr_endpoints,
require_zdr=args.require_zdr,
)
Path(args.catalog_out).write_text(
json.dumps(result["agents"], indent=2) + "\n", encoding="utf-8"
Expand Down
24 changes: 21 additions & 3 deletions scripts/ci/contextual_orchestrator_review_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def build_zdr_prioritized_catalog(
limit: int = DEFAULT_CATALOG_LIMIT,
family_cap: int = DEFAULT_FAMILY_CAP,
zdr_endpoints: frozenset[str] = frozenset(),
require_zdr: bool = False,
) -> dict[str, Any]:
"""Select and rank free routes into a ZDR-first, family-diverse catalog.

Expand All @@ -171,6 +172,8 @@ def build_zdr_prioritized_catalog(
family_cap: Maximum agents per provider outage-domain family.
zdr_endpoints: ``provider/model`` route keys from the OpenRouter ZDR
feed; authoritative when non-empty for the openrouter scope.
require_zdr: Admit only routes with attested ZDR evidence. Intended for
private/internal target repositories; an empty ZDR pool fails closed.

Returns:
A dict with ``agents`` (orchestrator ``ModelAgent.to_config()`` rows,
Expand All @@ -189,7 +192,15 @@ def family_is_open(family: str) -> bool:
"""Return whether a provider family still has catalog capacity."""
return per_family[family] < family_cap

free_rows = [row for row in rows if row["is_free"]]
all_free_rows = [row for row in rows if row["is_free"]]
free_rows = [
row
for row in all_free_rows
if not require_zdr
or is_zdr_model(
row["provider"], model=row["model"], zdr_endpoints=zdr_endpoints
)
]
free_rows.sort(
key=lambda row: (
0
Comment thread
seonghobae marked this conversation as resolved.
Expand All @@ -210,8 +221,9 @@ def family_is_open(family: str) -> bool:
break

if not picked:
route_kind = "attested ZDR free" if require_zdr else "free (zero-cost)"
raise PolicyError(
"no free (zero-cost) model route is available with the ZDR policy; "
f"no {route_kind} model route is available with the ZDR policy; "
"orchestrator/free would fail closed"
)
Comment thread
seonghobae marked this conversation as resolved.

Expand Down Expand Up @@ -245,7 +257,8 @@ def family_is_open(family: str) -> bool:
"agents": catalog_rows,
"report": {
"pool": "orchestrator/free",
"total_free_routes": len(free_rows),
"total_free_routes": len(all_free_rows),
"zdr_required": require_zdr,
"selected_count": len(catalog_rows),
"free_selected_count": len(picked),
"zdr_selected_count": zdr_count,
Expand Down Expand Up @@ -298,6 +311,7 @@ def build_catalog_from_paths(
limit: int = DEFAULT_CATALOG_LIMIT,
family_cap: int = DEFAULT_FAMILY_CAP,
zdr_endpoints_path: str | None = None,
require_zdr: bool = False,
) -> dict[str, Any]:
"""Build and persist the ZDR-prioritized ``orchestrator/free`` catalog.

Expand All @@ -308,6 +322,7 @@ def build_catalog_from_paths(
limit: Maximum number of catalog agents.
family_cap: Maximum agents per provider outage-domain family.
zdr_endpoints_path: Optional OpenRouter ZDR feed JSON path.
require_zdr: Admit only attested ZDR routes and fail closed otherwise.

Returns:
The return value of ``build_zdr_prioritized_catalog`` (both files were
Expand All @@ -320,6 +335,7 @@ def build_catalog_from_paths(
limit=limit,
family_cap=family_cap,
zdr_endpoints=zdr_endpoints,
require_zdr=require_zdr,
)
Path(out_path).write_text(json.dumps(result["agents"], indent=2) + "\n", encoding="utf-8")
Path(report_path).write_text(json.dumps(result["report"], indent=2) + "\n", encoding="utf-8")
Expand All @@ -337,6 +353,7 @@ def _build_parser() -> argparse.ArgumentParser:
parser.add_argument("--limit", type=int, default=DEFAULT_CATALOG_LIMIT)
parser.add_argument("--family-cap", type=int, default=DEFAULT_FAMILY_CAP)
parser.add_argument("--zdr-endpoints", default=None, help="Optional OpenRouter /api/v1/endpoints/zdr JSON path")
parser.add_argument("--require-zdr", action="store_true", help="Fail closed unless every selected route has attested ZDR evidence")
return parser


Expand All @@ -358,6 +375,7 @@ def main(argv: list[str] | None = None) -> int:
limit=args.limit,
family_cap=args.family_cap,
zdr_endpoints_path=args.zdr_endpoints,
require_zdr=args.require_zdr,
)
except (PolicyError, OSError, json.JSONDecodeError) as exc:
print(f"contextual-orchestrator review policy: {exc}", file=sys.stderr)
Expand Down
Loading
Loading