Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
146 changes: 146 additions & 0 deletions .github/workflows/provider-catalog-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: Provider Catalog Sync

on:
pull_request:
workflow_dispatch:
schedule:
- cron: "17 */6 * * *"

permissions:
contents: read

concurrency:
group: provider-catalog-${{ github.ref }}
cancel-in-progress: false

jobs:
contract:
name: Offline provider-catalog contracts
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout exact revision
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # actions/checkout@v7
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # actions/setup-python@v6
with:
python-version: "3.12"

- name: Install hash-locked test dependencies
run: |
python -m pip install --require-hashes -r requirements-opencode-review-ci.txt
python -m pip install --require-hashes -r fuzz/requirements-property.txt

- name: Run provider-catalog contracts
run: |
python -m pytest tests/test_provider_catalog.py tests/test_provider_catalog_coverage.py -q
python -m compileall -q contextual_orchestrator

synchronize:
name: Seed credentials and refresh durable catalog
if: >-
github.event_name != 'pull_request' &&
github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 20
environment: production
env:
CONTEXTUAL_ORCHESTRATOR_KV_BACKEND: postgres
CONTEXTUAL_ORCHESTRATOR_KV_DSN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_KV_DSN }}
CONTEXTUAL_ORCHESTRATOR_CATALOG_DSN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_KV_DSN }}
CONTEXTUAL_ORCHESTRATOR_KV_PASSPHRASE: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_KV_PASSPHRASE }}
NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}
NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }}
BYTEZ_API_KEY: ${{ secrets.BYTEZ_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- name: Checkout protected default-branch revision
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # actions/checkout@v7
with:
ref: ${{ github.sha }}
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # actions/setup-python@v6
with:
python-version: "3.12"

- name: Install hash-locked runtime and database dependencies
run: python -m pip install --require-hashes -r requirements.lock

- name: Validate trusted bootstrap inventory
shell: bash
run: |
set +x
required=(
CONTEXTUAL_ORCHESTRATOR_KV_DSN
CONTEXTUAL_ORCHESTRATOR_KV_PASSPHRASE
NVIDIA_NIM_API_KEY
NVIDIA_NIM_API_KEY_SUB
BYTEZ_API_KEY
OPENROUTER_API_KEY
OPENAI_API_KEY
)
for name in "${required[@]}"; do
value="${!name:-}"
if [[ -z "$value" ]]; then
echo "::error title=Provider catalog bootstrap blocked::Required secret $name is not configured"
exit 2
fi
echo "::add-mask::$value"
done

- name: Seed encrypted credential registry and refresh model catalog
shell: bash
run: |
set +x
python -m contextual_orchestrator.provider_catalog \
bootstrap-and-sync \
--require-all \
--agents-output "$RUNNER_TEMP/provider-agents.json" \
> "$RUNNER_TEMP/provider-catalog-summary.json"

- name: Verify secret-free generated agent pool
shell: bash
run: |
python - <<'PY'
import json
import os
from pathlib import Path

agents_path = Path(os.environ["RUNNER_TEMP"]) / "provider-agents.json"
summary_path = Path(os.environ["RUNNER_TEMP"]) / "provider-catalog-summary.json"
agents = json.loads(agents_path.read_text(encoding="utf-8"))["agents"]
summary = json.loads(summary_path.read_text(encoding="utf-8"))
if not agents:
raise SystemExit("provider catalog produced no candidate agents")
forbidden = {
os.environ[name]
for name in (
"NVIDIA_NIM_API_KEY",
"NVIDIA_NIM_API_KEY_SUB",
"BYTEZ_API_KEY",
"OPENROUTER_API_KEY",
"OPENAI_API_KEY",
)
}
serialized = json.dumps({"agents": agents, "summary": summary})
if any(secret and secret in serialized for secret in forbidden):
raise SystemExit("generated provider evidence contains a secret value")
print(json.dumps({
"candidate_agent_count": len(agents),
"candidate_model_count": summary["candidate_model_count"],
"measurement_status": summary["measurement_status"],
}, sort_keys=True))
PY

- name: Confirm runtime secret-source boundary
shell: bash
run: |
unset NVIDIA_NIM_API_KEY NVIDIA_NIM_API_KEY_SUB BYTEZ_API_KEY OPENROUTER_API_KEY OPENAI_API_KEY
echo "Provider credentials are persisted in the encrypted KV registry; runtime resolves names only."
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and

## [Unreleased]

### Added

- Add a durable, normalized provider catalog for the organization `NVIDIA_NIM_API_KEY`, `NVIDIA_NIM_API_KEY_SUB`, `BYTEZ_API_KEY`, `OPENROUTER_API_KEY`, and `OPENAI_API_KEY` accounts; trusted bootstrap writes values only to the encrypted credential registry, discovers provider models account by account, preserves last-known-good catalogs on isolated failures, generates role-tagged agents, and starts the gateway from enabled database candidates with `--provider-catalog-dsn`.
- Add a provider-aware runtime client that preserves the hardened OpenAI-compatible transport for OpenAI, OpenRouter, and NVIDIA NIM while using a narrow native Bytez Key/input adapter and failing closed for unsupported Bytez passthrough response shapes.
- Add a trust-separated Provider Catalog Sync workflow: pull requests run secret-free offline contracts, while protected-main scheduled/manual runs require the complete five-key inventory plus durable KV DSN/passphrase, verify generated evidence contains no secret value, and never downgrade a configured database to process memory.

### Security

- Fail closed with a stable redacted error when an explicitly configured Postgres KV backend cannot be imported, initialized, or seeded, and route `CostRoutingCoordinator(postgres_dsn=...)` through that authoritative factory, preventing a silent downgrade of configuration, routing, price, and credential authority to process-local memory.
Expand All @@ -31,6 +37,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and

### Documentation

- Add durable provider-catalog design, implementation plan, operator guide, and APA 7 doctoring covering credential/catalog separation, normalized data, account-isolated refresh, route/conduct pool construction, native Bytez handling, trusted Actions bootstrap, rotation, incident response, evidence interpretation, and rollback.
- Add APA 7 doctoring for Python environment-marker semantics, Atheris artifact availability and hashes, and the supported-platform uncertainty boundary.
- Add provider-response resource-bound doctoring covering the 8 MiB fail-closed limit, HTTP framing preflight, `text/event-stream` media-type enforcement, bounded SSE reads, OpenAI-compatible `[DONE]` completion evidence, malformed-event and premature-EOF handling, batch-output partitioning, incident handling, and operational rollback.
- Add provider-stream UTF-8 doctoring grounding strict SSE/JSON decoding and redacted malformed-input handling in the WHATWG HTML Standard and RFC 8259, with verification, failure, rollback, and authority boundaries.
Expand Down
31 changes: 31 additions & 0 deletions contextual_orchestrator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@
get_config_store,
)
from .orchestrator import ModelAgent, TaskOrchestrator, WorkflowStep, load_agents
from .provider_catalog import (
DEFAULT_PROVIDER_ACCOUNTS,
CatalogHttpError,
CatalogModelRecord,
DiscoveredModel,
InMemoryProviderCatalogStore,
PostgresProviderCatalogStore,
ProviderAccount,
ProviderAwareModelClient,
ProviderCatalogHttpClient,
ProviderCatalogService,
ProviderCatalogUnavailable,
bootstrap_provider_credentials,
build_catalog_orchestrator,
normalize_models_document,
)
from .token_counting import HeuristicTokenCounter, build_token_counter

__all__ = [
Expand All @@ -56,6 +72,21 @@
"get_credential",
"register_credential",
"NotConfigured",
# durable provider catalog
"DEFAULT_PROVIDER_ACCOUNTS",
"ProviderAccount",
"DiscoveredModel",
"CatalogModelRecord",
"CatalogHttpError",
"ProviderCatalogUnavailable",
"InMemoryProviderCatalogStore",
"PostgresProviderCatalogStore",
"ProviderCatalogHttpClient",
"ProviderAwareModelClient",
"ProviderCatalogService",
"bootstrap_provider_credentials",
"normalize_models_document",
"build_catalog_orchestrator",
# cost review
"ATTRIBUTION_DIMENSIONS",
"AttributionDimensions",
Expand Down
44 changes: 42 additions & 2 deletions contextual_orchestrator/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@

from .credentials import register_credential
from .orchestrator import ModelClient, TaskOrchestrator, load_agents
from .provider_catalog import (
PostgresProviderCatalogStore,
ProviderAwareModelClient,
ProviderCatalogService,
ProviderCatalogUnavailable,
)
from .server import SecurityConfig, serve


Expand Down Expand Up @@ -55,6 +61,22 @@ def _register_credential_command(argv: list[str]) -> None:
print(json.dumps({"registered": args.name, "backend": "kv"}, ensure_ascii=False))


def _runtime_agents(parser: argparse.ArgumentParser, args: argparse.Namespace):
"""Load either the durable discovered pool or the explicit seed-file pool."""
if not args.provider_catalog_dsn:
return load_agents(args.agents)
try:
store = PostgresProviderCatalogStore(args.provider_catalog_dsn)
agents = ProviderCatalogService(store=store).candidate_agents()
except ProviderCatalogUnavailable as exc:
parser.error(str(exc))
if not agents:
parser.error(
"provider catalog contains no enabled candidates; run the trusted provider-catalog sync first"
)
return agents


def main() -> None:
"""Parse CLI options and run bootstrap, prompt completion, or the HTTP server."""
if len(sys.argv) > 1 and sys.argv[1] == "register-credential":
Expand All @@ -64,6 +86,14 @@ def main() -> None:
parser = argparse.ArgumentParser(description="Route or conduct chat requests across model agents.")
parser.add_argument("prompt", nargs="?", help="User prompt for CLI mode.")
parser.add_argument("--agents", default="examples/agents.mock.json", help="Agent config JSON.")
parser.add_argument(
"--provider-catalog-dsn",
default=os.environ.get("CONTEXTUAL_ORCHESTRATOR_CATALOG_DSN") or None,
help=(
"Optional PostgreSQL provider-catalog DSN. When set, discovered enabled models "
"replace the seed agent file and provider credentials resolve from the KV registry."
),
)
parser.add_argument("--state-db", default=os.environ.get("CONTEXTUAL_ORCHESTRATOR_STATE_DB", "") or None,
help="Optional sqlite path to persist runs/audit/analytics across restarts (default: in-memory).")
parser.add_argument("--mode", choices=["auto", "route", "conduct"], default="auto")
Expand Down Expand Up @@ -94,9 +124,19 @@ def main() -> None:
help="Measure orchestration vs a single-worker baseline on these prompts and print the report.")
args = parser.parse_args()

client = ModelClient(ca_bundle=args.provider_ca_bundle, verify_tls=not args.insecure_skip_tls_verify)
agents = _runtime_agents(parser, args)
if args.provider_catalog_dsn:
client = ProviderAwareModelClient(
ca_bundle=args.provider_ca_bundle,
verify_tls=not args.insecure_skip_tls_verify,
)
else:
client = ModelClient(
ca_bundle=args.provider_ca_bundle,
verify_tls=not args.insecure_skip_tls_verify,
)
orchestrator = TaskOrchestrator(
load_agents(args.agents),
agents,
client=client,
state_db=args.state_db,
agents_db=args.agents_db,
Expand Down
Loading
Loading