Repository navigation
feat(provider-facts): declare constrained provider credential operations #486
Description
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.
keyEnvmust 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-providerdeclares 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
- docs(isolation): add ADR 0004 for neutral model-provider discovery #423
- chore(isolation): formalize JSON Schema for acq-provider-facts/v1 and acq-neutral-model-catalog/v1 #435
- chore(isolation): host-materialized provider facts (split from #436, blocked on quickstart#504) #456
- fix(acq-kits): give verify scripts a "could not check" verdict state #460
- feat(kits): add goose-server chat-harness acq mixin kit #415
- docs(adr): add ADR-0035 host-authoritative sandbox configuration agentic-coding-quickstart#504
- Preserve provider facts assignments when loading from a kit agentic-coding-quickstart#551
Activity
wz-gsa commentedon Oct 8, 2026
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
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-evia inheritance), the twotimeout(1)uses fail open whentimeoutis 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.urland ADR-0004'smodelsUrlare the same URLSame 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
acqactually dials.Suggestion: let
diagnosticreference the existingmodelsUrlrather 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
All three agree today. Nothing keeps them agreeing, and the env-var-ownership invariant this issue requires (
keyEnvmust match a variable in the same provider kit's ownspec.yaml) can only be enforced against whichever copy the validator happens to read.GSA-TTS/agentic-coding-patterns#435is 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:
acq-provider-facts/v1#423)usai-model-catalog/v1integrations/providers/usai/catalog.jsonacq-neutral-model-catalog/v1acq-kits/usai-provider/files/home/usai-config/model-catalog-snapshot.jsonThe 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
#460alignmentStrongly agree, and the eight-outcome taxonomy with
indeterminate / could not checkas 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#460exists 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:
indeterminate, nothealthyand nottransport failure.Unsupported capability is an explicit declaration, never inferred as healthyalready 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 a204or a200with an unexpected body count as healthy? The models endpoint returning200with an empty or malformed payload is the shape that would most plausibly fool a status-only check, andprovider response failureis 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.