89 lines
3.1 KiB
TypeScript
89 lines
3.1 KiB
TypeScript
import type { PermissionMode } from '../models/types.js';
|
|
import type { ProviderPermissionProfiles, ProviderProfileName } from '../models/provider-profiles.js';
|
|
|
|
export interface ResolvePermissionModeInput {
|
|
movementName: string;
|
|
requiredPermissionMode?: PermissionMode;
|
|
provider?: ProviderProfileName;
|
|
projectProviderProfiles?: ProviderPermissionProfiles;
|
|
globalProviderProfiles?: ProviderPermissionProfiles;
|
|
}
|
|
|
|
export const DEFAULT_PROVIDER_PERMISSION_PROFILES: ProviderPermissionProfiles = {
|
|
claude: { defaultPermissionMode: 'edit' },
|
|
codex: { defaultPermissionMode: 'edit' },
|
|
opencode: { defaultPermissionMode: 'edit' },
|
|
mock: { defaultPermissionMode: 'edit' },
|
|
};
|
|
|
|
/**
|
|
* Resolve movement permission mode using provider profiles.
|
|
*
|
|
* Priority:
|
|
* 1. project provider_profiles.<provider>.movement_permission_overrides.<movement>
|
|
* 2. global provider_profiles.<provider>.movement_permission_overrides.<movement>
|
|
* 3. project provider_profiles.<provider>.default_permission_mode
|
|
* 4. global provider_profiles.<provider>.default_permission_mode
|
|
* 5. apply movement.required_permission_mode as minimum floor
|
|
*
|
|
* Throws when unresolved.
|
|
*/
|
|
export function resolveMovementPermissionMode(input: ResolvePermissionModeInput): PermissionMode {
|
|
if (!input.provider) {
|
|
if (input.requiredPermissionMode) {
|
|
return input.requiredPermissionMode;
|
|
}
|
|
throw new Error(
|
|
`Unable to resolve permission mode for movement "${input.movementName}": provider is required when movement.required_permission_mode is omitted.`,
|
|
);
|
|
}
|
|
|
|
const projectProfile = input.projectProviderProfiles?.[input.provider];
|
|
const globalProfile = input.globalProviderProfiles?.[input.provider];
|
|
|
|
const projectOverride = projectProfile?.movementPermissionOverrides?.[input.movementName];
|
|
if (projectOverride) {
|
|
return applyRequiredPermissionFloor(projectOverride, input.requiredPermissionMode);
|
|
}
|
|
|
|
const globalOverride = globalProfile?.movementPermissionOverrides?.[input.movementName];
|
|
if (globalOverride) {
|
|
return applyRequiredPermissionFloor(globalOverride, input.requiredPermissionMode);
|
|
}
|
|
|
|
if (projectProfile?.defaultPermissionMode) {
|
|
return applyRequiredPermissionFloor(projectProfile.defaultPermissionMode, input.requiredPermissionMode);
|
|
}
|
|
|
|
if (globalProfile?.defaultPermissionMode) {
|
|
return applyRequiredPermissionFloor(globalProfile.defaultPermissionMode, input.requiredPermissionMode);
|
|
}
|
|
|
|
if (input.requiredPermissionMode) {
|
|
return input.requiredPermissionMode;
|
|
}
|
|
|
|
throw new Error(
|
|
`Unable to resolve permission mode for movement "${input.movementName}" and provider "${input.provider}": ` +
|
|
'define provider_profiles defaults/overrides or movement.required_permission_mode.',
|
|
);
|
|
}
|
|
|
|
const PERMISSION_MODE_RANK: Record<PermissionMode, number> = {
|
|
readonly: 0,
|
|
edit: 1,
|
|
full: 2,
|
|
};
|
|
|
|
function applyRequiredPermissionFloor(
|
|
resolvedMode: PermissionMode,
|
|
requiredMode?: PermissionMode,
|
|
): PermissionMode {
|
|
if (!requiredMode) {
|
|
return resolvedMode;
|
|
}
|
|
return PERMISSION_MODE_RANK[requiredMode] > PERMISSION_MODE_RANK[resolvedMode]
|
|
? requiredMode
|
|
: resolvedMode;
|
|
}
|