Skip to content

feat(provider-facts): declare constrained provider credential operations #486

Open

Description

@mogul

Context

#423 defines ADR-0004's provider-facts model:
provider kits ship static facts; acq validates and materializes them as
host-authoritative, read-only data.

This supports model discovery and agent-harness rendering, but not
provider-neutral credential operations. acq still needs provider-specific
knowledge to choose a low-impact authenticated endpoint, classify expected
responses, and direct a user to safe credential-management guidance.

A follow-on Quickstart issue will add generic acq provider operations once
the facts contract can declare those details.

Decision

Extend the provider-facts model with static, declarative operations metadata.
Do not add a provider-kit executable health-check hook.

Provider-supplied executable code has a deliberately narrow ADR-0004
containment contract: normalizers are pure, credential-free file transforms.
Executing arbitrary provider-kit code with credentials would weaken that
boundary and duplicate acq's generic HTTP behavior.

Proposed Metadata

The exact file split and field names are open. This can extend
acq-provider-facts/v1 or introduce a separately versioned operations
artifact, but it should express an equivalent constrained shape:

{
  "credential": {
    "serviceId": "usai",
    "keyEnv": "USAI_API_KEY",
    "managementUrl": "https://gsa.usai.gov/console/key-management"
  },
  "diagnostic": {
    "url": "https://api.gsa.usai.gov/api/v1/models",
    "method": "GET",
    "credentialTransport": "bearer",
    "successStatus": [200]
  }
}

This metadata identifies the canonical provider credential. It must not model
harness-local aliases such as the Goose kit's CUSTOM_USAI_API_KEY.

Required Validation And Safety Properties

  • Facts remain static data, parsed and validated by acq on the host.
  • No provider-kit operation code receives an injected credential.
  • keyEnv must satisfy the existing env-var-ownership invariant.
  • The diagnostic URL must use HTTPS and its host must equal the declared,
    host-validated provider host and remain within the effective network policy.
  • Request methods must be allowlisted and bodyless in v1.
  • Redirect behavior, timeout, response-size limit, and success statuses must be
    explicit and constrained.
  • Metadata must provide safe, concise user remediation and may provide a
    credential-management URL.
  • The contract must distinguish provider diagnostic capability from credential
    replacement. It must not imply that every provider supports programmatic
    credential rotation.
  • Unsupported capability is an explicit declaration, never inferred as healthy.

Result Contract

Define stable outcomes usable by acq and by a provider kit's own scripts/verify
coverage:

  • healthy;
  • credential absent;
  • credential rejected or expired;
  • authorization denied;
  • DNS, TLS, or transport failure;
  • provider response failure;
  • unsupported;
  • indeterminate / could not check.

Align the distinction between pass and could-not-check with the verification
contract introduced by #460. A diagnostic that
could not run must not be reported as successful.

Acceptance Criteria

  • ADR-0004 or a follow-on ADR defines the credential operations extension.
  • The chosen metadata shape has a formal schema and is included in the
    schema work tracked by chore(isolation): formalize JSON Schema for acq-provider-facts/v1 and acq-neutral-model-catalog/v1 #435 or a successor.
  • Host-side validation enforces provider ID, key environment-variable
    ownership, HTTPS, host equality, effective-egress membership, and request
    constraints.
  • The contract explicitly prohibits credentialed provider-kit executable
    hooks.
  • The result taxonomy distinguishes rejected credentials from transport,
    provider, unsupported, and indeterminate outcomes.
  • usai-provider declares canonical credential and diagnostic metadata.
  • Tests cover malformed metadata, undeclared credential environment
    variables, mismatched hosts, unsupported diagnostics, and omission of
    secret values.
  • The contract documents that harness-specific aliases are not provider
    credentials and are out of scope for provider operations.

Dependencies And References

Activity

  1. wz-gsa commented on Oct 8, 2026

    @wz-gsa
    Contributor

    Reviewed this against the current tree and against GSA-TTS/agentic-coding-patterns#423's ADR-0004, which it extends. The containment reasoning holds up — I want to confirm one part of it with a measurement, then flag two overlaps and one gap.

    The no-executable-hook decision is correct, and I can show why

    Executing arbitrary provider-kit code with credentials would weaken that boundary

    This is the right call, and it is stronger than the issue states. ADR-0004's normalizer containment contract is documented but not enforced — I verified against acq.backends/ that none of its four properties has an existing primitive: no per-subprocess network restriction exists at all (policy is sandbox-wide and create-time-only), no environment-minimization helper exists (both exec paths only add -e via inheritance), the two timeout(1) uses fail open when timeout is absent and kill only the immediate child, and no subprocess output-size cap exists anywhere in either repo.

    So a credentialed provider-kit hook would not merely "weaken" the boundary — it would hand provider code a credential in an environment where nothing currently constrains what that code can reach. Static metadata plus one acq-owned HTTP probe is the only shape that is safe today.

    Overlap 1 — diagnostic.url and ADR-0004's modelsUrl are the same URL

    ADR-0004 facts.json:   "modelsUrl":      "https://api.gsa.usai.gov/api/v1/models"
    this proposal:         "diagnostic.url": "https://api.gsa.usai.gov/api/v1/models"
    

    Same URL, same artifact, two field names. That is not a coincidence — the models endpoint is the low-impact authenticated endpoint, which is exactly why it is a good diagnostic. If both fields ship, they will drift, and a reader will have no way to know which one acq actually dials.

    Suggestion: let diagnostic reference the existing modelsUrl rather than restate it — e.g. "diagnostic": { "source": "modelsUrl", "method": "GET", ... } — so the URL has one authority and the diagnostic contributes only the method, transport and success statuses.

    Overlap 2 — the credential env-var name is now recorded in three places

    ADR-0004 facts.json          keyEnv              = USAI_API_KEY
    this proposal                credential.keyEnv   = USAI_API_KEY
    providers/usai/catalog.json  gateway.apiKeyEnv   = USAI_API_KEY
    

    All three agree today. Nothing keeps them agreeing, and the env-var-ownership invariant this issue requires (keyEnv must match a variable in the same provider kit's own spec.yaml) can only be enforced against whichever copy the validator happens to read.

    GSA-TTS/agentic-coding-patterns#435 is the natural place to collapse this to one authority, and this issue's acceptance criteria already route the schema work there.

    A fourth catalog shape exists, and it is unowned

    Worth surfacing while the schema question is open — there are now four schemas describing one provider:

    Shape Where Carries
    acq-provider-facts/v1 ADR-0004 (pending, #423) host, baseUrl, modelsUrl, keyEnv
    (this proposal) pending credential + diagnostic
    usai-model-catalog/v1 integrations/providers/usai/catalog.json gateway, vendors, models
    acq-neutral-model-catalog/v1 acq-kits/usai-provider/files/home/usai-config/model-catalog-snapshot.json providerId, models

    The last one already ships in the kit today and nothing in the epic tracks it. GSA-TTS/agentic-coding-patterns#357's own "Later" list says "reconcile coexisting catalog schemas first (#435)", so the reconciliation is at least named — but it is now four shapes, not two.

    Result contract — agreeing with the #460 alignment

    Align the distinction between pass and could-not-check with the verification contract introduced by GSA-TTS/agentic-coding-patterns#460.

    Strongly agree, and the eight-outcome taxonomy with indeterminate / could not check as a first-class state is the right shape. One detail from #460's implementation worth carrying over: give "could not check" its own exit code, not just its own label. A caller that cannot distinguish "we probed and the credential was rejected" from "we could not probe" will eventually treat the second as the first — which is the failure mode #460 exists to prevent, and which this repo has now hit nine times in other tools.

    Two concrete cases I would add to the acceptance criteria's test list:

    • The probe binary is absent from the guest. Must classify indeterminate, not healthy and not transport failure.
    • The diagnostic metadata is absent for a provider that declares no capability. Unsupported capability is an explicit declaration, never inferred as healthy already covers this in prose; a test should pin it, because the natural implementation is a falsy check that passes through to the success branch.

    One question

    successStatus: [200] — does a 204 or a 200 with an unexpected body count as healthy? The models endpoint returning 200 with an empty or malformed payload is the shape that would most plausibly fool a status-only check, and provider response failure is already in the taxonomy. Worth saying whether v1 validates the body shape at all, or deliberately does not.

    AI-assisted (OpenCode). Advisory — requires human review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions