Skip to content

feat(builder): add oauth_providers descriptor + type:oauth wiring for AgentSpec#390

Open
sneumannb5 wants to merge 5 commits into
byte5ai:mainfrom
sneumannb5:feat/oauth-providers-descriptor-field
Open

feat(builder): add oauth_providers descriptor + type:oauth wiring for AgentSpec#390
sneumannb5 wants to merge 5 commits into
byte5ai:mainfrom
sneumannb5:feat/oauth-providers-descriptor-field

Conversation

@sneumannb5

@sneumannb5 sneumannb5 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

oauth_providers[] + provider/scopes on type:oauth fields, wired schema → codegen →
linter → setup-fields UI. Closes #371.

Why

type:oauth was dead: descriptor had nowhere to live (ctx.oauthTokens undefined), no
UI to author it. Now codegen emits it (gated on type:oauth), linter checks wiring +
rejects provider/scopes on non-oauth fields, editor renders provider select + scopes.

Test plan

  • middleware: oauth + codegen 62/62, typecheck clean

  • web-ui: lint + typecheck + vitest 169/169, i18n OK

  • e2e: manifest → real loader arms broker + ctx.oauthTokens; extra_authorize_params round-trips

    Risk

    Additive schema (oauth_providers defaults [], omitted when empty). Codegen gate drops
    stray provider on non-oauth — test-covered. No migration/API/CI/env change.

@sneumannb5 sneumannb5 changed the title feat(builder): add oauth_providers descriptor + type:oauth wiring for… feat(builder): add oauth_providers descriptor + type:oauth wiring for AgentSpec Jun 30, 2026

@ConnysCode ConnysCode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: Request changes — two changes before merge; the core is sound.

The schema/codegen/linter that closes #371 is correct and well-covered. I cloned the head, built the workspace, and ran the relevant suites: OAuth schema + linter tests pass 10/10 and codegen.test.ts passes 47/47, including the e2e test that feeds the generated manifest through the real loadManifestFromPath — so there's no drift between the builder schema and extractOAuthProviders. I also confirmed OAuthProviderSchema faithfully mirrors OAuthProviderDescriptor (api/admin-v1.ts) and the loader (pkce default, token_auth_style enum, client_*_field gating all line up), and that patchSpec is a hard gate (spec isn't persisted on lint failure), so codegen only runs on a validated spec. The required changes below are about not shipping dead/loose surface alongside that core.

Must change before merge

🟠 Don't ship the dead web-ui surface. OAuthProvider, AgentSpecSkeleton.oauth_providers, and the new 'oauth' member of SetupField['type'] (web-ui/app/_lib/builderTypes.ts) are referenced only by each other — no component constructs or reads them, and the manual SetupFieldsEditor dropdown (SetupFieldsEditor.tsx:23) still offers only string|secret|url|number|boolean, so an operator can't author a type:oauth field in the UI. Either wire these types into the builder UI in this PR (add 'oauth' to FIELD_TYPES + render provider/scopes inputs), or remove them and land them with the UI follow-up. As-is they're unused exports.

🟠 Gate the provider/scopes manifest forward on type:oauth. mapSetupFieldSpecToManifest (codegen.ts:590-591) forwards provider/scopes for any field type, and the Zod schema (agentSpec.ts:145-146) allows them on every type, so a non-oauth field carrying a stray provider is emitted into the manifest. Add field.type === 'oauth' to the forward (and/or a linter rule rejecting provider/scopes on a non-oauth field) so the schema's stated "type:'oauth' only" contract is actually enforced.

PR description — factcheck

Claim Reality
"middleware … builder 57/57; 5 pre-existing fails env-only" Couldn't reproduce the exact counts (my clone lacks built workspace dist/, so ~43 route/integration tests fail on missing @omadia/* builds — environmental, not real). The PR-relevant unit suites I could run pass: oauth schema/linter 10/10, codegen 47/47.
"web-ui … vitest (169/169)" Not run (web-ui deps not installed); please keep this current.
"Shape: uses real kernel contract … not the issue's guessed field_key/authorization_url" Confirmed — schema matches OAuthProviderDescriptor + the loader; the e2e test proves the generated manifest arms the broker.
"Additive schema only … No migration/API/CI/env change" Confirmed for middleware; oauth_providers defaults [] and is omitted from the manifest when empty.

Minimum to merge

  1. Resolve the dead web-ui types (wire into the UI, or drop until the UI lands).
  2. Gate the provider/scopes forward on type:oauth.
Minor / nits (non-blocking)
  • agentSpec.ts:446 — the "keep all three in sync" comment really tracks five hand-mirrored copies of this shape (Zod schema, admin-v1.ts, loader, _lib/storeTypes.ts, _lib/builderTypes.ts); admin-v1.ts's header already calls for promoting it to a shared package. Please file a follow-up rather than normalizing manual sync.
  • web-ui/app/_lib/builderTypes.ts:143pkce?: boolean is optional here, but the kernel contract (admin-v1.ts:106) and storeTypes.ts have pkce: boolean (required). Tighten the mirror.
  • Test coverage gaps: no case for multiple type:oauth fields referencing distinct providers, none for provider/scopes on a non-oauth field, and extra_authorize_params is never round-tripped through codegen+loader.

(Reviewed against head 2c0f40e.)

Comment thread middleware/src/plugins/builder/codegen.ts Outdated
Comment thread web-ui/app/_lib/builderTypes.ts
// Spec 005 (#371) — inert authorization-code descriptor consumed by the
// kernel OAuth engine; no plugin code touches the flow. Mirrors
// OAuthProviderDescriptor in api/admin-v1.ts + manifestLoader.
// extractOAuthProviders — keep all three in sync (pkce defaults true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking, but flag the sync debt. This comment says "keep all three in sync," but the shape is now hand-mirrored across five places: this Zod schema, OAuthProviderDescriptor (api/admin-v1.ts), extractOAuthProviders (manifestLoader.ts), and two web-ui interfaces (_lib/storeTypes.ts, _lib/builderTypes.ts). The admin-v1.ts header already documents the intended fix (promote the canonical type to a shared package — @omadia/plugin-api is the natural home, with the Zod schema satisfies-ing it). Please file a follow-up issue so manual sync doesn't become the norm.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still open after this push — non-blocking. The shape is now hand-mirrored across ~6 places (this Zod schema, admin-v1.ts OAuthProviderDescriptor, manifestLoader/extractOAuthProviders, _lib/storeTypes.ts, and the new _lib/builderTypes.ts OAuthProvider). Please file a follow-up to promote the canonical type to the shared package admin-v1.ts already points at (Zod satisfies-ing it), rather than letting manual sync become the norm. Not blocking this PR. See my original comment above.

@ConnysCode ConnysCode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: Approve

Both changes I requested last round are done, and the middleware core is still green — I re-cloned the head, built @omadia/plugin-api, and ran the two relevant suites: 62/62 pass (agentSpecOAuthProviders + codegen, incl. the e2e that feeds the generated manifest through the real loadManifestFromPath — so no drift between the builder schema and extractOAuthProviders).

Already changed since the last review

  • Web-ui surface is now wired, not dead. SetupFieldsEditor takes oauthProviders, 'oauth' is in FIELD_TYPES, and a type:oauth row renders a provider <select> + scopes input; SpecEditor passes spec.oauth_providers ?? [] (SpecEditor.tsx:311). The previously-unused OAuthProvider / oauth_providers / 'oauth' exports are now consumed. Blast radius checked: the builder SetupField union is only switched on inside the builder editor — the operator-facing config renderer (admin/settings/page.tsx) uses a separate type, so adding 'oauth' doesn't reach it.
  • provider/scopes forward is gated on type:oauth. mapSetupFieldSpecToManifest now guards the forward with field.type === 'oauth' (codegen.ts:592), and the linter adds setup_field_provider_on_non_oauth to reject a stray provider/scopes on a non-oauth field up-front. The "type:'oauth' only" contract is enforced end-to-end; covered by the two new codegen cases + two linter cases.
  • Nit closed: pkce is now pkce: boolean (required) in builderTypes.ts, matching the kernel contract.

Verified while I was in there: the full-array replace on /setup_fields (SetupFieldsEditor.tsx:65) means switching a row away from oauth genuinely drops the old provider/scopes server-side (not just the belt-and-suspenders undefined in the patch), so no stale-provider field can survive a type change. The scopes local-buffer / adjust-state-on-prop-change logic handles the in-progress-separator and external-edit cases correctly.

PR description — factcheck

Claim Reality
"middleware: oauth + codegen 62/62, typecheck clean" Confirmed 62/62 (built @omadia/plugin-api, ran both suites). Typecheck not independently run.
"e2e: manifest → real loader arms broker + ctx.oauthTokens; extra_authorize_params round-trips" Confirmed — the loader e2e + extra_authorize_params round-trip tests are part of the 62 and pass.
"web-ui: lint + typecheck + vitest 169/169, i18n OK" Not run here (web-ui deps not installed). i18n spot-check: all 5 oauth.* keys used by the component exist in both de.json + en.json. Please keep the counts current.
"Additive schema — oauth_providers defaults [], omitted when empty" Confirmed (omits oauth_providers … when the spec declares none test passes).
Optional nits (non-blocking)
  • SetupFieldsEditor.tsx:281 — the provider <label htmlFor={providerId}> always renders, but when oauthProviders is empty the <select id={providerId}> is replaced by a <p>, so the label points at a non-existent control. Minor a11y dangle; consider dropping htmlFor (or wrapping the label) in the no-providers branch.
  • SetupFieldsEditor.tsx:289 — if field.provider references a descriptor id that was later removed, the <select value=…> has no matching <option> and silently renders empty. The linter's oauth_field_provider_unresolved catches it on save, so functionally fine; a stale-value hint would be nicer UX. Non-blocking.
  • Sync debt (my earlier nit) — the descriptor shape is now hand-mirrored across ~6 places (Zod, admin-v1.ts, loader, storeTypes.ts, builderTypes.ts). Still worth a follow-up to promote it to the shared package admin-v1.ts already points at, rather than normalizing manual sync. Non-blocking; see the thread I left open.

(Reviewed against head 5dda400.)

@ConnysCode ConnysCode enabled auto-merge July 2, 2026 08:11
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.

AgentSpec exposes type:oauth setup field but no oauth_providers descriptor field

2 participants