$extra * @return array */ public static function build( string $operationType, array $targetScope, array $selectionIdentity, string $fingerprint, array $extra = [], ): array { $targetScope = self::normalizeTargetScope($targetScope); return array_merge($extra, [ 'operation' => [ 'type' => trim($operationType), ], 'target_scope' => $targetScope, 'selection' => $selectionIdentity, 'idempotency' => [ 'fingerprint' => trim($fingerprint), ], ]); } /** * @param array{entra_tenant_id?: mixed, directory_context_id?: mixed} $targetScope * @return array{entra_tenant_id?: string, directory_context_id?: string} */ public static function normalizeTargetScope(array $targetScope): array { $entraTenantId = $targetScope['entra_tenant_id'] ?? null; $directoryContextId = $targetScope['directory_context_id'] ?? null; $normalized = []; if (is_string($entraTenantId) && trim($entraTenantId) !== '') { $normalized['entra_tenant_id'] = trim($entraTenantId); } if (is_string($directoryContextId) && trim($directoryContextId) !== '') { $normalized['directory_context_id'] = trim($directoryContextId); } if (is_int($directoryContextId)) { $normalized['directory_context_id'] = (string) $directoryContextId; } if (! isset($normalized['entra_tenant_id']) && ! isset($normalized['directory_context_id'])) { throw new InvalidArgumentException('Target scope must include entra_tenant_id or directory_context_id.'); } return $normalized; } }