Skip to content

RSPEED: workflow provider catalog and secret registry (#51 Phase 1) - #62

Open
jameswnl wants to merge 1 commit into
harnessfrom
worktree-phase-1-catalog-registry
Open

jameswnl wants to merge 1 commit into
harnessfrom
worktree-phase-1-catalog-registry

Conversation

@jameswnl

@jameswnl jameswnl commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 1 of the #51 plan (catalog and registry). Callers now send logical provider names; the stack maps them to operator config and builds the credential reference itself.

  • workflow_engine.providers / secrets / default_provider / default_model, with SecretRef, SecretBinding, WorkflowInferenceProvider. One credential per entry.
  • Load-time checks: approved executor_type (checked against the installed cloud-agents), bound credential, env-backed before cloud-agents#269, defaults resolve and default_model set, no duplicates. allowed_models: [] is rejected for workflow providers and for Llama Stack inference.providers.
  • workflow/catalog.py: governed mode (non-empty catalog) vs legacy mode (unchanged apart from rejecting credential_ref). The provider dict handed to cloud-agents is rebuilt from operator config, never from caller keys.
  • Deprecated credentials_secret is accepted only as the entry's logical credential name, with Deprecation: @1790812800 (RFC 9745) and Sunset: Thu, 01 Apr 2027 00:00:00 GMT (RFC 8594).
  • Same-entry rule for definition.provider and step inference_provider (moved up from Phase 2: logical names must be rewritten to executor types before cloud-agents validates, so the rule belongs with the rewrite). Overrides must name a catalog entry; the caller's definition is never mutated.
  • authz_context records the catalog provider and credential ref name (names only).
  • examples/workflows: every case now carries a phase, and tests/unit/cloud_agents/test_workflow_contract.py runs them against the real handler and config (38 run, 27 skipped for Phases 2 and 3). Also fixed the example workflows' template syntax, which failed cloud-agents' own validate_definition; verify.py now checks that.

Breaking

  • Legacy mode (no catalog) now rejects credential_ref with 400.
  • A secret-shaped credentials_secret is now 400 (was 422): it is rejected at provider resolution.

Test plan

  • New unit tests for the models, load checks, resolver and handler; dump-config tests updated for the new fields
  • uv run make test-unit: 3505 passed, 28 skipped; format, schema, pyright clean on new code
  • uv run python examples/workflows/verify.py passes
  • Independent evaluator (Sonnet): PASS WITH NOTES; model-only/unnamed overrides, non-string model and missing default_model fixed with tests
  • Not run: e2e suite

🤖 Generated with Claude Code

- workflow_engine.providers / secrets / default_provider / default_model
  with SecretRef, SecretBinding and WorkflowInferenceProvider; one
  credential per entry; load-time checks (approved executor_type, bound
  env credential before cloud-agents#269, defaults resolve, no duplicates)
- reject allowed_models: [] for workflow and Llama Stack providers
- workflow/catalog.py: resolve the run provider through the catalog; the
  stack rebuilds the provider dict from operator config; credential_ref
  must match the entry; deprecated credentials_secret accepted only as a
  logical name with Deprecation/Sunset headers; same-entry rule and
  executor-type rewrite for definition.provider and step overrides
- authz_context records the catalog provider and credential ref name
- examples/workflows: cases tagged by phase and run against the real
  handler by tests/unit/cloud_agents/test_workflow_contract.py; fix wrong
  template syntax in the example workflows and validate them with
  cloud-agents' validate_definition

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

@beesarmy beesarmy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the Phase 1 catalog and registry implementation against the installed cloud-agents executor. Three reproducible P2 findings need fixes before merge; see inline comments.

Validation: 158 focused tests passed, 27 skipped. Direct reproductions confirmed malformed-definition exceptions, discarded base_url, and environment-key normalization mismatches. No e2e suite was run.

Comment thread src/workflow/catalog.py
return definition
normalized = copy.deepcopy(definition)
scopes = [(normalized, "provider")]
steps = normalized.get("spec", {}).get("steps", [])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Guard definition shape before walking provider overrides

In governed mode, normalize_definition runs before _validate_workflow_submission, but assumes spec is a dictionary and spec.steps is iterable. RunWorkflowRequest.definition accepts arbitrary dictionaries, so requests with spec: null, spec: [], or spec.steps: null reach this code and raise uncaught AttributeError/TypeError. These become HTTP 500 instead of the existing 422 shape response. The installed cloud-agents validator reports these cases correctly. Check the container types before traversing and let the shape validator return 422; add endpoint regression coverage for these inputs.

Comment thread src/workflow/catalog.py
engine: WorkflowEngineConfiguration, entry: WorkflowInferenceProvider, model: str
) -> dict[str, str]:
"""Build the provider dict for cloud-agents from operator config only."""
provider = {"name": entry.executor_type, "model": model}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Preserve the configured endpoint or reject unsupported endpoint configuration

WorkflowInferenceProvider.base_url is advertised as the operator-set endpoint for Azure and self-hosted models, but _build_provider copies only name, model, and credentials_secret. A configured URL is therefore silently discarded before execution. A provider with base_url: https://team.example/v1 resolves to a dictionary without that field, so execution cannot use the selected catalog endpoint and instead uses ambient/default endpoint configuration or fails. Carry the endpoint through a supported executor contract, or reject non-null base_url at load time until that contract exists. Simply adding base_url to this dictionary is insufficient: the installed inference_spec_from_provider_config rejects it as an unknown field.

Comment thread src/workflow/catalog.py
if entry.credential is not None:
binding = engine.binding(entry.credential.name)
if binding is not None and binding.env is not None:
provider["credentials_secret"] = binding.env

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Validate env bindings against the executor's actual key lookup

SecretBinding.env accepts any string and is passed unchanged as credentials_secret. The installed executor's resolve_credential_env_key uppercases that reference and replaces hyphens with underscores. A valid case-sensitive binding env: team_key loads successfully, but looks up TEAM_KEY instead of team_key; if only the configured variable is set, no credential resolves, and if both exist, the wrong credential can be selected. Require canonical compatible environment names at configuration load, or implement an exact-name executor handoff. Add a regression that checks actual executor credential resolution rather than only the rebuilt dictionary.

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