enforce caller execution-mode eligibility across provider pins, inheritance and fallback - #6393
Merged
Conversation
…oundary (#6368) A provider record's type (cli / tui / api) is its executable route mode, and callers are not interchangeable across them: a CoS agent task needs a file-writing harness, the standalone autofixer can only drive a headless CLI, and a tool-free review stage must stay on a direct API provider. Each of those rules used to be re-derived at its own call site, so a rule enforced on the explicit pin was routinely missing from the fallback chain that could replace that pin — a CLI-only run could inherit a TUI route through a saved `fallbackProvider`, and an agent task picked an `api` fallback only to be rejected after it had already burned the cascade's last retry. `server/lib/callerModePolicy.js` is now the single answer to "may THIS caller run on THAT route mode?": four named policies (`agent-harness`, `cli-harness`, `direct-api`, `any-text`) plus an opt-in required-model-capability map. It is applied to explicit pins, to activeProvider inheritance, and to every fallback candidate — task-level, configured and system-priority alike — by riding on `requestCapabilities.allowedModes` into the toolkit's `getFallbackProvider` (which the PortOS wrapper had been dropping entirely). An ineligible candidate is skipped with a reason, never thrown on, so "no eligible fallback" stays the transient condition callers already handle. Unknown never becomes true: a record whose type names no executable mode is refused by every policy, and a required capability with no positive evidence is refused rather than assumed. Callers that declare nothing route exactly as before. The browser mirror (`providerModeSelectionPolicy`) only shows the rule: an ineligible SAVED pin stays visible and disabled with a reason instead of being hidden or silently replaced, and CLI/TUI siblings remain separate options.
atomantic
force-pushed
the
claim/issue-6368
branch
from
September 6, 2026 15:31
b1c0ba3 to
fa1651e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A provider record's
type(cli/tui/api) is its executable route mode, and callers are not interchangeable across them. Each caller's rule used to be re-derived at its own call site, so a rule enforced on the explicit pin was routinely missing from the fallback chain that could replace that pin:fallbackProvider;apifallback and was rejected only after it had burned the cascade's last retry;server/services/providerStatus.js#getFallbackProvidersilently dropped therequestCapabilitiesargument entirely, so no caller policy could reach the toolkit at all.New
server/lib/callerModePolicy.jsis the single answer to "may THIS caller run on THAT route mode?" — four named policies (agent-harness,cli-harness,direct-api,any-text) plus an opt-in required-model-capability map. It is applied to explicit pins, toactiveProviderinheritance, and to every fallback candidate (task-level, configured and system-priority alike), riding onrequestCapabilities.allowedModesinto the toolkit'sgetFallbackProvider.Design points:
typenames no executable mode is refused by every policy, and a required capability with no positive evidence is refused rather than assumed.enabled/benched state, prerequisites and text-transport consent stay with their existing owners.providerModeSelectionPolicy) only shows the rule: an ineligible saved pin stays visible and disabled with a reason instead of being hidden or silently replaced, and CLI/TUI siblings remain separate selectable options. Three ad-hoc inline{ provider: p => p.type === 'api' }policies collapse onto it.No connection graph, storage migration, or peer wire change — this slice ships on the executable provider records an install already runs.
Test plan
cd server && npm test— 2011 files / 39,976 tests pass.cd client && npm test— 871 files / 10,589 tests pass.server/lib/aiToolkit/providerStatus.test.js— a TUI candidate is skipped at the task, configured and system tiers; an all-ineligible chain returnsnull(never throws); an untyped record is refused; an undeclared caller still takes a TUI route.server/services/agentProviderResolution.test.js— the agent policy is carried into fallback selection; an unrecognizedtypeis refused permanently; a TUI pin remains a legitimate agent route (guards the generalization against over-reach).server/services/promptRunner.test.js— an ineligible explicit provider is refused withPROVIDER_MODE_NOT_PERMITTEDbefore any run record is created; the policy reachescreateRunonrequestCapabilities.server/lib/callerModePolicy.test.js— unregistered policy names throw rather than resolving permissively; an unknown required capability does not satisfy the requirement; the locally-declared mode vocabulary is pinned toPROVIDER_TYPESandROUTE_MODES.client/src/utils/providerModePolicy.parity.test.js+ProviderModelSelector.test.jsx— client/server policy parity, and a saved TUI pin renders visible-but-disabled with a reason under a CLI-only policy.server/lib/importScoping.test.jsbudget raised 89,500 → 89,700: the new leaf has zero imports and is reached by the routing boundary, which is the ordinary leaf growth the budget documents as tolerable rather than the eager-edge shape it exists to catch.Closes #6368